# # Patch managed by http://www.mn-logistik.de/unsupported/pxa250/patcher # --- linux/arch/alpha/config.in~bluetooth-2.4.18-mh11 2004-01-25 23:28:10.000000000 +0100 +++ linux/arch/alpha/config.in 2004-01-25 23:37:39.000000000 +0100 @@ -372,9 +372,7 @@ source drivers/usb/Config.in source drivers/input/Config.in -if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then - source net/bluetooth/Config.in -fi +source net/bluetooth/Config.in mainmenu_option next_comment comment 'Kernel hacking' --- linux/arch/arm/config.in~bluetooth-2.4.18-mh11 2004-01-25 23:28:31.000000000 +0100 +++ linux/arch/arm/config.in 2004-01-25 23:37:39.000000000 +0100 @@ -796,9 +796,7 @@ source drivers/usb/Config.in -if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then - source net/bluetooth/Config.in -fi +source net/bluetooth/Config.in mainmenu_option next_comment comment 'Kernel hacking' --- linux/arch/i386/config.in~bluetooth-2.4.18-mh11 2004-01-25 23:28:31.000000000 +0100 +++ linux/arch/i386/config.in 2004-01-25 23:37:39.000000000 +0100 @@ -415,9 +415,7 @@ source drivers/usb/Config.in -if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then - source net/bluetooth/Config.in -fi +source net/bluetooth/Config.in mainmenu_option next_comment comment 'Kernel hacking' --- linux/arch/ppc/config.in~bluetooth-2.4.18-mh11 2004-01-25 23:28:11.000000000 +0100 +++ linux/arch/ppc/config.in 2004-01-25 23:37:39.000000000 +0100 @@ -390,9 +390,7 @@ source drivers/usb/Config.in -if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then - source net/bluetooth/Config.in -fi +source net/bluetooth/Config.in mainmenu_option next_comment comment 'Kernel hacking' --- linux/arch/sparc/config.in~bluetooth-2.4.18-mh11 2004-01-25 23:28:11.000000000 +0100 +++ linux/arch/sparc/config.in 2004-01-25 23:37:39.000000000 +0100 @@ -252,9 +252,7 @@ source fs/Config.in -if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then - source net/bluetooth/Config.in -fi +source net/bluetooth/Config.in mainmenu_option next_comment comment 'Watchdog' --- linux/arch/sparc64/config.in~bluetooth-2.4.18-mh11 2004-01-25 23:28:11.000000000 +0100 +++ linux/arch/sparc64/config.in 2004-01-25 23:37:39.000000000 +0100 @@ -284,9 +284,7 @@ source drivers/usb/Config.in -if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then - source net/bluetooth/Config.in -fi +source net/bluetooth/Config.in mainmenu_option next_comment comment 'Watchdog' --- linux/arch/sparc64/kernel/ioctl32.c~bluetooth-2.4.18-mh11 2002-02-25 20:37:56.000000000 +0100 +++ linux/arch/sparc64/kernel/ioctl32.c 2004-01-25 23:37:39.000000000 +0100 @@ -92,6 +92,7 @@ #include #include +#include #include #include @@ -3822,6 +3823,15 @@ return err; } +/* Bluetooth ioctls */ +#define HCIUARTSETPROTO _IOW('U', 200, int) +#define HCIUARTGETPROTO _IOR('U', 201, int) + +#define BNEPCONNADD _IOW('B', 200, int) +#define BNEPCONNDEL _IOW('B', 201, int) +#define BNEPGETCONNLIST _IOR('B', 210, int) +#define BNEPGETCONNINFO _IOR('B', 211, int) + struct mtd_oob_buf32 { u32 start; u32 length; @@ -3878,6 +3888,11 @@ return ((0 == ret) ? 0 : -EFAULT); } +#define CMTPCONNADD _IOW('C', 200, int) +#define CMTPCONNDEL _IOW('C', 201, int) +#define CMTPGETCONNLIST _IOR('C', 210, int) +#define CMTPGETCONNINFO _IOR('C', 211, int) + struct ioctl_trans { unsigned int cmd; unsigned int handler; @@ -4540,6 +4555,21 @@ COMPATIBLE_IOCTL(HCISETSCAN) COMPATIBLE_IOCTL(HCISETAUTH) COMPATIBLE_IOCTL(HCIINQUIRY) +COMPATIBLE_IOCTL(HCIUARTSETPROTO) +COMPATIBLE_IOCTL(HCIUARTGETPROTO) +COMPATIBLE_IOCTL(RFCOMMCREATEDEV) +COMPATIBLE_IOCTL(RFCOMMRELEASEDEV) +COMPATIBLE_IOCTL(RFCOMMGETDEVLIST) +COMPATIBLE_IOCTL(RFCOMMGETDEVINFO) +COMPATIBLE_IOCTL(RFCOMMSTEALDLC) +COMPATIBLE_IOCTL(BNEPCONNADD) +COMPATIBLE_IOCTL(BNEPCONNDEL) +COMPATIBLE_IOCTL(BNEPGETCONNLIST) +COMPATIBLE_IOCTL(BNEPGETCONNINFO) +COMPATIBLE_IOCTL(CMTPCONNADD) +COMPATIBLE_IOCTL(CMTPCONNDEL) +COMPATIBLE_IOCTL(CMTPGETCONNLIST) +COMPATIBLE_IOCTL(CMTPGETCONNINFO) /* Misc. */ COMPATIBLE_IOCTL(0x41545900) /* ATYIO_CLKR */ COMPATIBLE_IOCTL(0x41545901) /* ATYIO_CLKW */ --- linux/CREDITS~bluetooth-2.4.18-mh11 2004-01-25 23:28:29.000000000 +0100 +++ linux/CREDITS 2004-01-25 23:37:39.000000000 +0100 @@ -1317,6 +1317,15 @@ S: Provo, Utah 84606-5607 S: USA +N: Marcel Holtmann +E: marcel@holtmann.org +W: http://www.holtmann.org +D: Maintainer of the Linux Bluetooth Subsystem +D: Author and maintainer of the various Bluetooth HCI drivers +D: Author and maintainer of the CAPI message transport protocol driver +D: Various other Bluetooth related patches, cleanups and fixes +S: Germany + N: Rob W. W. Hooft E: hooft@EMBL-Heidelberg.DE D: Shared libs for graphics-tools and for the f2c compiler @@ -2555,6 +2564,7 @@ N: Aristeu Sergio Rozanski Filho E: aris@conectiva.com.br D: Support for EtherExpress 10 ISA (i82595) in eepro driver +D: User level driver support for input S: Conectiva S.A. S: R. Tocantins, 89 - Cristo Rei S: 80050-430 - Curitiba - Paraná --- linux/Documentation/Configure.help~bluetooth-2.4.18-mh11 2004-01-25 23:28:31.000000000 +0100 +++ linux/Documentation/Configure.help 2004-01-25 23:37:39.000000000 +0100 @@ -2847,14 +2847,6 @@ If unsure, say N. -HCI EMU (virtual device) driver -CONFIG_BLUEZ_HCIEMU - 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_usb.o). - # Choice: alphatype Alpha system type CONFIG_ALPHA_GENERIC @@ -11067,6 +11059,12 @@ If unsure, say N. +Hotplug firmware loading support (EXPERIMENTAL) +CONFIG_FW_LOADER + This option is provided for the case where no in-kernel-tree modules require + hotplug firmware loading support, but a module built outside the kernel tree + does. + Use PCI shared memory for NIC registers CONFIG_TULIP_MMIO Use PCI shared memory for the NIC registers, rather than going through @@ -12967,6 +12965,15 @@ accessible under char device 13:64+ - /dev/input/eventX in a generic way. This is the future ... +CONFIG_INPUT_UINPUT + Say Y here if you want to support user level drivers for input + subsystem accessible under char device 10:223 - /dev/input/uinput. + + This driver is also available as a module ( = code which can be + inserted in and removed from the running kernel whenever you want). + The module will be called uinput.o. If you want to compile it as a + module, say M here and read . + USB Scanner support CONFIG_USB_SCANNER Say Y here if you want to connect a USB scanner to your computer's @@ -19986,11 +19993,15 @@ Bluetooth can be found at . Linux Bluetooth subsystem consist of several layers: - HCI Core (device and connection manager, scheduler) + BlueZ Core (HCI device and connection manager, scheduler) HCI Device drivers (interface to the hardware) L2CAP Module (L2CAP protocol) + SCO Module (SCO links) + RFCOMM Module (RFCOMM protocol) + BNEP Module (BNEP protocol) + CMTP Module (CMTP protocol) - Say Y here to enable Linux Bluetooth support and to build HCI Core + Say Y here to enable Linux Bluetooth support and to build BlueZ Core layer. To use Linux Bluetooth subsystem, you will need several user-space @@ -19998,7 +20009,7 @@ Bluetooth kernel modules are provided in the BlueZ package. For more information, see . - If you want to compile HCI Core as module (hci.o) say M here. + If you want to compile BlueZ Core as module (bluez.o) say M here. L2CAP protocol support CONFIG_BLUEZ_L2CAP @@ -20009,15 +20020,91 @@ Say Y here to compile L2CAP support into the kernel or say M to compile it as module (l2cap.o). +SCO links support +CONFIG_BLUEZ_SCO + SCO link provides voice transport over Bluetooth. SCO support is + required for voice applications like Headset and Audio. + + Say Y here to compile SCO support into the kernel or say M to + compile it as module (sco.o). + +RFCOMM protocol support +CONFIG_BLUEZ_RFCOMM + RFCOMM provides connection oriented stream transport. RFCOMM + support is required for Dialup Networking, OBEX and other Bluetooth + applications. + + Say Y here to compile RFCOMM support into the kernel or say M to + compile it as module (rfcomm.o). + +RFCOMM TTY emulation support +CONFIG_BLUEZ_RFCOMM_TTY + This option enables TTY emulation support for RFCOMM channels. + +BNEP protocol support +CONFIG_BLUEZ_BNEP + BNEP (Bluetooth Network Encapsulation Protocol) is Ethernet + emulation layer on top of Bluetooth. BNEP is required for Bluetooth + PAN (Personal Area Network). + + To use BNEP, you will need user-space utilities provided in the + BlueZ-PAN package. + For more information, see . + + Say Y here to compile BNEP support into the kernel or say M to + compile it as module (bnep.o). + +CMTP protocol support +CONFIG_BLUEZ_CMTP + CMTP (CAPI Message Transport Protocol) is a transport layer + for CAPI messages. CMTP is required for the Bluetooth Common + ISDN Access Profile. + + Say Y here to compile CMTP support into the kernel or say M to + compile it as module (cmtp.o). + +BNEP multicast filter support +CONFIG_BLUEZ_BNEP_MC_FILTER + This option enables the multicast filter support for BNEP. + +BNEP protocol filter support +CONFIG_BLUEZ_BNEP_PROTO_FILTER + This option enables the protocol filter support for BNEP. + HCI UART driver CONFIG_BLUEZ_HCIUART Bluetooth HCI UART driver. This driver is required if you want to use Bluetooth devices with - serial port interface. + 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.o). +HCI UART (H4) protocol support +CONFIG_BLUEZ_HCIUART_H4 + 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. + +HCI BCSP protocol support +CONFIG_BLUEZ_HCIUART_BCSP + 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. + +HCI BCSP transmit CRC with every BCSP packet +CONFIG_BLUEZ_HCIUART_BCSP_TXCRC + 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. + HCI USB driver CONFIG_BLUEZ_HCIUSB Bluetooth HCI USB driver. @@ -20027,7 +20114,16 @@ Say Y here to compile support for Bluetooth USB devices into the kernel or say M to compile it as module (hci_usb.o). -HCI VHCI virtual HCI device driver +HCI USB SCO (voice) support +CONFIG_BLUEZ_HCIUSB_SCO + This option enables the SCO support in the HCI USB driver. You need this + to transmit voice data with your Bluetooth USB device. And your device + must also support sending SCO data over the HCI layer, because some of + them sends the SCO data to an internal PCM adapter. + + Say Y here to compile support for HCI SCO data. + +HCI VHCI Virtual HCI device driver CONFIG_BLUEZ_HCIVHCI Bluetooth Virtual HCI device driver. This driver is required if you want to use HCI Emulation software. @@ -20035,6 +20131,66 @@ Say Y here to compile support for virtual HCI devices into the kernel or say M to compile it as module (hci_vhci.o). +HCI BFUSB device driver +CONFIG_BLUEZ_HCIBFUSB + 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.o). + +HCI DTL1 (PC Card) device driver +CONFIG_BLUEZ_HCIDTL1 + 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.o). + +HCI BT3C (PC Card) device driver +CONFIG_BLUEZ_HCIBT3C + 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 + + The HCI BT3C driver uses external firmware loader program provided in + the BlueFW package. For more information, see . + + Say Y here to compile support for HCI BT3C devices into the + kernel or say M to compile it as module (bt3c_cs.o). + +HCI BlueCard (PC Card) device driver +CONFIG_BLUEZ_HCIBLUECARD + 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.o). + +HCI UART (PC Card) device driver +CONFIG_BLUEZ_HCIBTUART + 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.o). + # The following options are for Linux when running on the Hitachi # SuperH family of RISC microprocessors. --- linux/Documentation/devices.txt~bluetooth-2.4.18-mh11 2001-11-07 23:46:01.000000000 +0100 +++ linux/Documentation/devices.txt 2004-01-25 23:37:39.000000000 +0100 @@ -419,6 +419,7 @@ 220 = /dev/mptctl Message passing technology (MPT) control 221 = /dev/mvista/hssdsi Montavista PICMG hot swap system driver 222 = /dev/mvista/hasi Montavista PICMG high availability + 223 = /dev/input/uinput User level driver support for input 240-255 Reserved for local use 11 char Raw keyboard device --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/Documentation/firmware_class/firmware_sample_driver.c 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1,121 @@ +/* + * firmware_sample_driver.c - + * + * Copyright (c) 2003 Manuel Estrada Sainz + * + * Sample code on how to use request_firmware() from drivers. + * + * Note that register_firmware() is currently useless. + * + */ + +#include +#include +#include +#include + +#include "linux/firmware.h" + +#define WE_CAN_NEED_FIRMWARE_BEFORE_USERSPACE_IS_AVAILABLE +#ifdef WE_CAN_NEED_FIRMWARE_BEFORE_USERSPACE_IS_AVAILABLE +char __init inkernel_firmware[] = "let's say that this is firmware\n"; +#endif + +static char ghost_device[] = "ghost0"; + +static void sample_firmware_load(char *firmware, int size) +{ + u8 buf[size+1]; + memcpy(buf, firmware, size); + buf[size] = '\0'; + printk("firmware_sample_driver: firmware: %s\n", buf); +} + +static void sample_probe_default(void) +{ + /* uses the default method to get the firmware */ + const struct firmware *fw_entry; + printk("firmware_sample_driver: a ghost device got inserted :)\n"); + + if(request_firmware(&fw_entry, "sample_driver_fw", ghost_device)!=0) + { + printk(KERN_ERR + "firmware_sample_driver: Firmware not available\n"); + return; + } + + sample_firmware_load(fw_entry->data, fw_entry->size); + + release_firmware(fw_entry); + + /* finish setting up the device */ +} +static void sample_probe_specific(void) +{ + /* Uses some specific hotplug support to get the firmware from + * userspace directly into the hardware, or via some sysfs file */ + + /* NOTE: This currently doesn't work */ + + printk("firmware_sample_driver: a ghost device got inserted :)\n"); + + if(request_firmware(NULL, "sample_driver_fw", ghost_device)!=0) + { + printk(KERN_ERR + "firmware_sample_driver: Firmware load failed\n"); + return; + } + + /* request_firmware blocks until userspace finished, so at + * this point the firmware should be already in the device */ + + /* finish setting up the device */ +} +static void sample_probe_async_cont(const struct firmware *fw, void *context) +{ + if(!fw){ + printk(KERN_ERR + "firmware_sample_driver: firmware load failed\n"); + return; + } + + printk("firmware_sample_driver: device pointer \"%s\"\n", + (char *)context); + sample_firmware_load(fw->data, fw->size); +} +static void sample_probe_async(void) +{ + /* Let's say that I can't sleep */ + int error; + error = request_firmware_nowait (THIS_MODULE, + "sample_driver_fw", ghost_device, + "my device pointer", + sample_probe_async_cont); + if(error){ + printk(KERN_ERR + "firmware_sample_driver:" + " request_firmware_nowait failed\n"); + } +} + +static int sample_init(void) +{ +#ifdef WE_CAN_NEED_FIRMWARE_BEFORE_USERSPACE_IS_AVAILABLE + register_firmware("sample_driver_fw", inkernel_firmware, + sizeof(inkernel_firmware)); +#endif + /* since there is no real hardware insertion I just call the + * sample probe functions here */ + sample_probe_specific(); + sample_probe_default(); + sample_probe_async(); + return 0; +} +static void __exit sample_exit(void) +{ +} + +module_init (sample_init); +module_exit (sample_exit); + +MODULE_LICENSE("GPL"); --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/Documentation/firmware_class/hotplug-script 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1,16 @@ +#!/bin/sh + +# Simple hotplug script sample: +# +# Both $DEVPATH and $FIRMWARE are already provided in the environment. + +HOTPLUG_FW_DIR=/usr/lib/hotplug/firmware/ + +echo 1 > /sysfs/$DEVPATH/loading +cat $HOTPLUG_FW_DIR/$FIRMWARE > /sysfs/$DEVPATH/data +echo 0 > /sysfs/$DEVPATH/loading + +# To cancel the load in case of error: +# +# echo -1 > /sysfs/$DEVPATH/loading +# --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/Documentation/firmware_class/README 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1,58 @@ + + request_firmware() hotplug interface: + ------------------------------------ + Copyright (C) 2003 Manuel Estrada Sainz + + Why: + --- + + Today, the most extended way to use firmware in the Linux kernel is linking + it statically in a header file. Which has political and technical issues: + + 1) Some firmware is not legal to redistribute. + 2) The firmware occupies memory permanently, even though it often is just + used once. + 3) Some people, like the Debian crowd, don't consider some firmware free + enough and remove entire drivers (e.g.: keyspan). + + about in-kernel persistence: + --------------------------- + Under some circumstances, as explained below, it would be interesting to keep + firmware images in non-swappable kernel memory or even in the kernel image + (probably within initramfs). + + Note that this functionality has not been implemented. + + - Why OPTIONAL in-kernel persistence may be a good idea sometimes: + + - If the device that needs the firmware is needed to access the + filesystem. When upon some error the device has to be reset and the + firmware reloaded, it won't be possible to get it from userspace. + e.g.: + - A diskless client with a network card that needs firmware. + - The filesystem is stored in a disk behind an scsi device + that needs firmware. + - Replacing buggy DSDT/SSDT ACPI tables on boot. + Note: this would require the persistent objects to be included + within the kernel image, probably within initramfs. + + And the same device can be needed to access the filesystem or not depending + on the setup, so I think that the choice on what firmware to make + persistent should be left to userspace. + + - Why register_firmware()+__init can be useful: + - For boot devices needing firmware. + - To make the transition easier: + The firmware can be declared __init and register_firmware() + called on module_init. Then the firmware is warranted to be + there even if "firmware hotplug userspace" is not there yet or + it doesn't yet provide the needed firmware. + Once the firmware is widely available in userspace, it can be + removed from the kernel. Or made optional (CONFIG_.*_FIRMWARE). + + In either case, if firmware hotplug support is there, it can move the + firmware out of kernel memory into the real filesystem for later + usage. + + Note: If persistence is implemented on top of initramfs, + register_firmware() may not be appropriate. --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/drivers/bluetooth/bfusb.c 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1,781 @@ +/* + * + * AVM BlueFRITZ! USB driver + * + * Copyright (C) 2003 Marcel Holtmann + * + * + * 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 +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#ifndef CONFIG_BLUEZ_HCIBFUSB_DEBUG +#undef BT_DBG +#define BT_DBG(D...) +#endif + +#define VERSION "1.1" + +static struct usb_device_id bfusb_table[] = { + /* AVM BlueFRITZ! USB */ + { USB_DEVICE(0x057c, 0x2200) }, + + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, bfusb_table); + + +#define BFUSB_MAX_BLOCK_SIZE 256 + +#define BFUSB_BLOCK_TIMEOUT (HZ * 3) + +#define BFUSB_TX_PROCESS 1 +#define BFUSB_TX_WAKEUP 2 + +#define BFUSB_MAX_BULK_TX 1 +#define BFUSB_MAX_BULK_RX 1 + +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); +static void bfusb_rx_complete(struct urb *urb); + +static struct urb *bfusb_get_completed(struct bfusb *bfusb) +{ + struct sk_buff *skb; + struct urb *urb = NULL; + + BT_DBG("bfusb %p", bfusb); + + skb = skb_dequeue(&bfusb->completed_q); + if (skb) { + urb = ((struct bfusb_scb *) skb->cb)->urb; + kfree_skb(skb); + } + + return urb; +} + +static inline void bfusb_unlink_urbs(struct bfusb *bfusb) +{ + struct sk_buff *skb; + struct urb *urb; + + BT_DBG("bfusb %p", bfusb); + + while ((skb = skb_dequeue(&bfusb->pending_q))) { + urb = ((struct bfusb_scb *) skb->cb)->urb; + usb_unlink_urb(urb); + skb_queue_tail(&bfusb->completed_q, skb); + } + + while ((urb = bfusb_get_completed(bfusb))) + usb_free_urb(urb); +} + + +static int bfusb_send_bulk(struct bfusb *bfusb, struct sk_buff *skb) +{ + struct bfusb_scb *scb = (void *) skb->cb; + struct urb *urb = bfusb_get_completed(bfusb); + int err, pipe; + + BT_DBG("bfusb %p skb %p len %d", bfusb, skb, skb->len); + + if (!urb && !(urb = usb_alloc_urb(0))) + return -ENOMEM; + + pipe = usb_sndbulkpipe(bfusb->udev, bfusb->bulk_out_ep); + + FILL_BULK_URB(urb, bfusb->udev, pipe, skb->data, skb->len, + bfusb_tx_complete, skb); + + urb->transfer_flags = USB_QUEUE_BULK; + + scb->urb = urb; + + skb_queue_tail(&bfusb->pending_q, skb); + + err = usb_submit_urb(urb); + 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 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))) + return -ENOMEM; + + if (!(skb = bluez_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); + + FILL_BULK_URB(urb, bfusb->udev, pipe, skb->data, size, + bfusb_rx_complete, skb); + + urb->transfer_flags = USB_QUEUE_BULK; + + skb_queue_tail(&bfusb->pending_q, skb); + + err = usb_submit_urb(urb); + 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) { + hci_event_hdr *hdr = (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) { + hci_acl_hdr *hdr = (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) { + hci_sco_hdr *hdr = (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 = bluez_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 sk_buff *skb = (struct sk_buff *) urb->context; + struct bfusb *bfusb = (struct bfusb *) skb->dev; + unsigned char *buf = urb->transfer_buffer; + int count = urb->actual_length; + int err, hdr, len; + + BT_DBG("bfusb %p urb %p skb %p len %d", bfusb, urb, skb, skb->len); + + if (!test_bit(HCI_RUNNING, &bfusb->hdev.flags)) + return; + + read_lock(&bfusb->lock); + + if (urb->status || !count) + goto resubmit; + + bfusb->hdev.stat.byte_rx += count; + + skb_put(skb, count); + + while (count) { + hdr = buf[0] | (buf[1] << 8); + + if (hdr & 0x4000) { + len = 0; + count -= 2; + buf += 2; + } else { + len = (buf[2] == 0) ? 256 : buf[2]; + count -= 3; + buf += 3; + } + + if (count < len) { + BT_ERR("%s block extends over URB buffer ranges", + bfusb->hdev.name); + } + + if ((hdr & 0xe1) == 0xc1) + bfusb_recv_block(bfusb, hdr, buf, len); + + count -= len; + buf += len; + } + + skb_unlink(skb); + kfree_skb(skb); + + bfusb_rx_submit(bfusb, urb); + + read_unlock(&bfusb->lock); + + return; + +resubmit: + urb->dev = bfusb->udev; + + err = usb_submit_urb(urb); + if (err) { + BT_ERR("%s bulk resubmit failed urb %p err %d", + bfusb->hdev.name, urb, err); + } + + read_unlock(&bfusb->lock); +} + + +static int bfusb_open(struct hci_dev *hdev) +{ + struct bfusb *bfusb = (struct bfusb *) hdev->driver_data; + unsigned long flags; + int i, err; + + BT_DBG("hdev %p bfusb %p", hdev, bfusb); + + if (test_and_set_bit(HCI_RUNNING, &hdev->flags)) + return 0; + + MOD_INC_USE_COUNT; + + 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); + MOD_DEC_USE_COUNT; + } + + 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); + + bfusb_unlink_urbs(bfusb); + bfusb_flush(hdev); + + write_unlock_irqrestore(&bfusb->lock, flags); + + MOD_DEC_USE_COUNT; + + 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 = bluez_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 firmware %p count %d", bfusb, bfusb->udev, firmware, count); + + BT_INFO("BlueFRITZ! USB loading firmware"); + + if (usb_set_configuration(bfusb->udev, 1) < 0) { + BT_ERR("Can't change to loading configuration"); + return -EBUSY; + } + + buf = kmalloc(BFUSB_MAX_BLOCK_SIZE + 3, GFP_ATOMIC); + if (!buf) { + BT_ERR("Can't allocate memory chunk for firmware"); + return -ENOMEM; + } + + pipe = usb_sndbulkpipe(bfusb->udev, bfusb->bulk_out_ep); + + while (count) { + size = min_t(uint, count, BFUSB_MAX_BLOCK_SIZE + 3); + + memcpy(buf, firmware + sent, size); + + err = usb_bulk_msg(bfusb->udev, pipe, buf, size, + &len, BFUSB_BLOCK_TIMEOUT); + + if (err || (len != size)) { + BT_ERR("Error in firmware loading"); + goto error; + } + + sent += size; + count -= size; + } + + if ((err = usb_bulk_msg(bfusb->udev, pipe, NULL, 0, + &len, BFUSB_BLOCK_TIMEOUT)) < 0) { + BT_ERR("Error in null packet request"); + goto error; + } + + if ((err = usb_set_configuration(bfusb->udev, 2)) < 0) { + BT_ERR("Can't change to running configuration"); + goto error; + } + + BT_INFO("BlueFRITZ! USB device ready"); + + kfree(buf); + return 0; + +error: + kfree(buf); + + pipe = usb_sndctrlpipe(bfusb->udev, 0); + + usb_control_msg(bfusb->udev, pipe, USB_REQ_SET_CONFIGURATION, + 0, 0, 0, NULL, 0, BFUSB_BLOCK_TIMEOUT); + + return err; +} + +static void *bfusb_probe(struct usb_device *udev, unsigned int ifnum, const struct usb_device_id *id) +{ + const struct firmware *firmware; + char device[16]; + struct usb_interface *iface; + struct usb_interface_descriptor *iface_desc; + struct usb_endpoint_descriptor *bulk_out_ep; + struct usb_endpoint_descriptor *bulk_in_ep; + struct hci_dev *hdev; + struct bfusb *bfusb; + + BT_DBG("udev %p ifnum %d id %p", udev, ifnum, id); + + /* Check number of endpoints */ + iface = &udev->actconfig->interface[0]; + iface_desc = &iface->altsetting[0]; + + if (iface_desc->bNumEndpoints < 2) + return NULL; + + bulk_out_ep = &iface_desc->endpoint[0]; + bulk_in_ep = &iface_desc->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->bEndpointAddress; + bfusb->bulk_out_ep = bulk_out_ep->bEndpointAddress; + bfusb->bulk_pkt_size = bulk_out_ep->wMaxPacketSize; + + bfusb->lock = RW_LOCK_UNLOCKED; + + bfusb->reassembly = NULL; + + skb_queue_head_init(&bfusb->transmit_q); + skb_queue_head_init(&bfusb->pending_q); + skb_queue_head_init(&bfusb->completed_q); + + snprintf(device, sizeof(device), "bfusb%3.3d%3.3d", udev->bus->busnum, udev->devnum); + + if (request_firmware(&firmware, "bfubase.frm", device) < 0) { + BT_ERR("Firmware request failed"); + goto error; + } + + if (bfusb_load_firmware(bfusb, firmware->data, firmware->size) < 0) { + BT_ERR("Firmware loading failed"); + goto release; + } + + release_firmware(firmware); + + /* Initialize and register HCI device */ + hdev = &bfusb->hdev; + + hdev->type = HCI_USB; + hdev->driver_data = bfusb; + + hdev->open = bfusb_open; + hdev->close = bfusb_close; + hdev->flush = bfusb_flush; + hdev->send = bfusb_send_frame; + hdev->destruct = bfusb_destruct; + hdev->ioctl = bfusb_ioctl; + + if (hci_register_dev(hdev) < 0) { + BT_ERR("Can't register HCI device"); + goto error; + } + + return bfusb; + +release: + release_firmware(firmware); + +error: + kfree(bfusb); + +done: + return NULL; +} + +static void bfusb_disconnect(struct usb_device *udev, void *ptr) +{ + struct bfusb *bfusb = (struct bfusb *) ptr; + struct hci_dev *hdev = &bfusb->hdev; + + BT_DBG("udev %p ptr %p", udev, ptr); + + if (!hdev) + return; + + bfusb_close(hdev); + + if (hci_unregister_dev(hdev) < 0) + BT_ERR("Can't unregister HCI device %s", hdev->name); +} + +static struct usb_driver bfusb_driver = { + 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); + BT_INFO("Copyright (C) 2003 Marcel Holtmann "); + + if ((err = usb_register(&bfusb_driver)) < 0) + BT_ERR("Failed to register BlueFRITZ! USB driver"); + + return err; +} + +static void __exit bfusb_cleanup(void) +{ + usb_deregister(&bfusb_driver); +} + +module_init(bfusb_init); +module_exit(bfusb_cleanup); + +MODULE_AUTHOR("Marcel Holtmann "); +MODULE_DESCRIPTION("BlueFRITZ! USB driver ver " VERSION); +MODULE_LICENSE("GPL"); --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/drivers/bluetooth/bluecard_cs.c 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1,1113 @@ +/* + * + * Bluetooth driver for the Anycom BlueCard (LSE039/LSE041) + * + * Copyright (C) 2001-2002 Marcel Holtmann + * + * + * 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 + * . Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + + +/* ======================== Module parameters ======================== */ + + +/* Bit map of interrupts to choose from */ +static u_int irq_mask = 0x86bc; +static int irq_list[4] = { -1 }; + +MODULE_PARM(irq_mask, "i"); +MODULE_PARM(irq_list, "1-4i"); + +MODULE_AUTHOR("Marcel Holtmann "); +MODULE_DESCRIPTION("BlueZ 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; + + +void bluecard_config(dev_link_t *link); +void bluecard_release(u_long arg); +int bluecard_event(event_t event, int priority, event_callback_args_t *args); + +static dev_info_t dev_info = "bluecard_cs"; + +dev_link_t *bluecard_attach(void); +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 ======================== */ + + +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_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_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) { + printk(KERN_WARNING "bluecard_cs: Call of write_wakeup for unknown device.\n"); + 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) { + + wait_queue_head_t 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 */ + init_waitqueue_head(&wait); + interruptible_sleep_on_timeout(&wait, HZ / 10); + + /* 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 */ + interruptible_sleep_on_timeout(&wait, HZ); + + } + + 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) { + printk(KERN_WARNING "bluecard_cs: Call of receive for unknown device.\n"); + 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 = bluez_skb_alloc(HCI_MAX_FRAME_SIZE, GFP_ATOMIC))) { + printk(KERN_WARNING "bluecard_cs: Can't allocate mem for new packet.\n"); + 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 */ + printk(KERN_WARNING "bluecard_cs: Unknown HCI packet with type 0x%02x received.\n", 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; + hci_event_hdr *eh; + hci_acl_hdr *ah; + hci_sco_hdr *sh; + + switch (info->rx_state) { + + case RECV_WAIT_EVENT_HEADER: + eh = (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 = (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 = (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; +} + + +void 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) { + printk(KERN_WARNING "bluecard_cs: Call of irq %d for unknown device.\n", irq); + return; + } + + if (!test_bit(CARD_READY, &(info->hw_state))) + return; + + 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)); +} + + + +/* ======================== 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 = bluez_skb_alloc(HCI_MAX_FRAME_SIZE, GFP_ATOMIC))) { + printk(KERN_WARNING "bluecard_cs: Can't allocate mem for new packet.\n"); + 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; + + bluecard_hci_set_baud_rate(hdev, DEFAULT_BAUD_RATE); + + if (test_and_set_bit(HCI_RUNNING, &(hdev->flags))) + return 0; + + /* 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); + + /* 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) { + printk(KERN_WARNING "bluecard_cs: 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 ======================== */ + + +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; + + 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 */ + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(HZ / 100); + + /* 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); + + /* 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 */ + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout((HZ * 5) / 4); // or set it to 3/2 + + + /* Initialize and register HCI device */ + + hdev = &(info->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; + + if (hci_register_dev(hdev) < 0) { + printk(KERN_WARNING "bluecard_cs: Can't register HCI device %s.\n", hdev->name); + return -ENODEV; + } + + return 0; +} + + +int bluecard_close(bluecard_info_t *info) +{ + unsigned int iobase = info->link.io.BasePort1; + struct hci_dev *hdev = &(info->hdev); + + 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) + printk(KERN_WARNING "bluecard_cs: Can't unregister HCI device %s.\n", hdev->name); + + return 0; +} + + + +/* ======================== Card services ======================== */ + + +static void cs_error(client_handle_t handle, int func, int ret) +{ + error_info_t err = { func, ret }; + + CardServices(ReportError, handle, &err); +} + + +dev_link_t *bluecard_attach(void) +{ + bluecard_info_t *info; + client_reg_t client_reg; + dev_link_t *link; + int i, 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->release.function = &bluecard_release; + link->release.data = (u_long)link; + 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_INFO2_VALID | IRQ_LEVEL_ID; + + if (irq_list[0] == -1) + link->irq.IRQInfo2 = irq_mask; + else + for (i = 0; i < 4; i++) + link->irq.IRQInfo2 |= 1 << irq_list[i]; + + 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.Attributes = INFO_IO_CLIENT | INFO_CARD_SHARE; + 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 = CardServices(RegisterClient, &link->handle, &client_reg); + if (ret != CS_SUCCESS) { + cs_error(link->handle, RegisterClient, ret); + bluecard_detach(link); + return NULL; + } + + return link; +} + + +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; + + del_timer(&link->release); + if (link->state & DEV_CONFIG) + bluecard_release((u_long)link); + + if (link->handle) { + ret = CardServices(DeregisterClient, 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(int fn, client_handle_t handle, tuple_t *tuple, cisparse_t *parse) +{ + int i; + + i = CardServices(fn, handle, tuple); + if (i != CS_SUCCESS) + return CS_NO_MORE_ITEMS; + + i = CardServices(GetTupleData, handle, tuple); + if (i != CS_SUCCESS) + return i; + + return CardServices(ParseTuple, handle, tuple, parse); +} + + +#define first_tuple(a, b, c) get_tuple(GetFirstTuple, a, b, c) +#define next_tuple(a, b, c) get_tuple(GetNextTuple, a, b, c) + +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 = CardServices(GetConfigurationInfo, 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 = CardServices(RequestIO, link->handle, &link->io); + if (i == CS_SUCCESS) + break; + } + + if (i != CS_SUCCESS) { + cs_error(link->handle, RequestIO, i); + goto failed; + } + + i = CardServices(RequestIRQ, link->handle, &link->irq); + if (i != CS_SUCCESS) { + cs_error(link->handle, RequestIRQ, i); + link->irq.AssignedIRQ = 0; + } + + i = CardServices(RequestConfiguration, link->handle, &link->conf); + if (i != CS_SUCCESS) { + cs_error(link->handle, RequestConfiguration, i); + goto failed; + } + + MOD_INC_USE_COUNT; + + 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((u_long)link); +} + + +void bluecard_release(u_long arg) +{ + dev_link_t *link = (dev_link_t *)arg; + bluecard_info_t *info = link->priv; + + if (link->state & DEV_PRESENT) + bluecard_close(info); + + MOD_DEC_USE_COUNT; + + link->dev = NULL; + + CardServices(ReleaseConfiguration, link->handle); + CardServices(ReleaseIO, link->handle, &link->io); + CardServices(ReleaseIRQ, link->handle, &link->irq); + + link->state &= ~DEV_CONFIG; +} + + +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); + mod_timer(&link->release, jiffies + HZ / 20); + } + 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) + CardServices(ReleaseConfiguration, link->handle); + break; + case CS_EVENT_PM_RESUME: + link->state &= ~DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_CARD_RESET: + if (DEV_OK(link)) + CardServices(RequestConfiguration, link->handle, &link->conf); + break; + } + + return 0; +} + + + +/* ======================== Module initialization ======================== */ + + +int __init init_bluecard_cs(void) +{ + servinfo_t serv; + int err; + + CardServices(GetCardServicesInfo, &serv); + if (serv.Revision != CS_RELEASE_CODE) { + printk(KERN_NOTICE "bluecard_cs: Card Services release does not match!\n"); + return -1; + } + + err = register_pccard_driver(&dev_info, &bluecard_attach, &bluecard_detach); + + return err; +} + + +void __exit exit_bluecard_cs(void) +{ + unregister_pccard_driver(&dev_info); + + while (dev_list != NULL) + bluecard_detach(dev_list); +} + + +module_init(init_bluecard_cs); +module_exit(exit_bluecard_cs); + +EXPORT_NO_SYMBOLS; --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/drivers/bluetooth/bt3c_cs.c 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1,946 @@ +/* + * + * Driver for the 3Com Bluetooth PCMCIA card + * + * Copyright (C) 2001-2002 Marcel Holtmann + * Jose Orlando Pereira + * + * + * 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 + * . Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + */ + +#include +#include + +#define __KERNEL_SYSCALLS__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + + +/* ======================== Module parameters ======================== */ + + +/* Bit map of interrupts to choose from */ +static u_int irq_mask = 0xffff; +static int irq_list[4] = { -1 }; + +MODULE_PARM(irq_mask, "i"); +MODULE_PARM(irq_list, "1-4i"); + +MODULE_AUTHOR("Marcel Holtmann , Jose Orlando Pereira "); +MODULE_DESCRIPTION("BlueZ 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; + + +void bt3c_config(dev_link_t *link); +void bt3c_release(u_long arg); +int bt3c_event(event_t event, int priority, event_callback_args_t *args); + +static dev_info_t dev_info = "bt3c_cs"; + +dev_link_t *bt3c_attach(void); +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 + + +inline void bt3c_address(unsigned int iobase, unsigned short addr) +{ + outb(addr & 0xff, iobase + ADDR_L); + outb((addr >> 8) & 0xff, iobase + ADDR_H); +} + + +inline void bt3c_put(unsigned int iobase, unsigned short value) +{ + outb(value & 0xff, iobase + DATA_L); + outb((value >> 8) & 0xff, iobase + DATA_H); +} + + +inline void bt3c_io_write(unsigned int iobase, unsigned short addr, unsigned short value) +{ + bt3c_address(iobase, addr); + bt3c_put(iobase, value); +} + + +inline unsigned short bt3c_get(unsigned int iobase) +{ + unsigned short value = inb(iobase + DATA_L); + + value |= inb(iobase + DATA_H) << 8; + + return value; +} + + +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, int from) +{ + unsigned long flags; + + if (!info) { + printk(KERN_WARNING "bt3c_cs: Call of write_wakeup for unknown device.\n"); + return; + } + + if (test_and_set_bit(XMIT_SENDING, &(info->tx_state))) + return; + + spin_lock_irqsave(&(info->lock), flags); + + 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) { + printk(KERN_WARNING "bt3c_cs: very strange\n"); + } + + kfree_skb(skb); + + info->hdev.stat.byte_tx += len; + + } while (0); + + spin_unlock_irqrestore(&(info->lock), flags); +} + + +static void bt3c_receive(bt3c_info_t *info) +{ + unsigned int iobase; + int size = 0, avail; + + if (!info) { + printk(KERN_WARNING "bt3c_cs: Call of receive for unknown device.\n"); + 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 = bluez_skb_alloc(HCI_MAX_FRAME_SIZE, GFP_ATOMIC))) { + printk(KERN_WARNING "bt3c_cs: Can't allocate mem for new packet.\n"); + 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 */ + printk(KERN_WARNING "bt3c_cs: Unknown HCI packet with type 0x%02x received.\n", 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; + hci_event_hdr *eh; + hci_acl_hdr *ah; + hci_sco_hdr *sh; + + switch (info->rx_state) { + + case RECV_WAIT_EVENT_HEADER: + eh = (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 = (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 = (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); +} + + +void bt3c_interrupt(int irq, void *dev_inst, struct pt_regs *regs) +{ + bt3c_info_t *info = dev_inst; + unsigned int iobase; + int iir; + + if (!info) { + printk(KERN_WARNING "bt3c_cs: Call of irq %d for unknown device.\n", irq); + return; + } + + 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) { + printk(KERN_WARNING "bt3c_cs: STRANGE stat=%04x\n", stat); + } else if ((stat & 0xff) != 0xff) { + if (stat & 0x0020) { + int stat = bt3c_read(iobase, 0x7002) & 0x10; + printk(KERN_WARNING "bt3c_cs: antena %s\n", stat ? "OUT" : "IN"); + } + if (stat & 0x0001) + bt3c_receive(info); + if (stat & 0x0002) { + //printk("bt3c_cs: ACK %04x\n", stat); + clear_bit(XMIT_SENDING, &(info->tx_state)); + bt3c_write_wakeup(info, 1); + } + + bt3c_io_write(iobase, 0x7001, 0x0000); + + outb(iir, iobase + CONTROL); + } + } + + spin_unlock(&(info->lock)); +} + + + + +/* ======================== 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); + + if (!hdev) { + printk(KERN_WARNING "bt3c_cs: 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); + + bt3c_write_wakeup(info, 0); + + 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; +} + + + +/* ======================== User mode firmware loader ======================== */ + + +#define FW_LOADER "/sbin/bluefw" +static int errno; + + +static int bt3c_fw_loader_exec(void *dev) +{ + char *argv[] = { FW_LOADER, "pccard", dev, NULL }; + char *envp[] = { "HOME=/", "TERM=linux", "PATH=/sbin:/usr/sbin:/bin:/usr/bin", NULL }; + int err; + + err = exec_usermodehelper(FW_LOADER, argv, envp); + if (err) + printk(KERN_WARNING "bt3c_cs: Failed to exec \"%s pccard %s\".\n", FW_LOADER, (char *)dev); + + return err; +} + + +static int bt3c_firmware_load(bt3c_info_t *info) +{ + sigset_t tmpsig; + char dev[16]; + pid_t pid; + int result; + + /* Check if root fs is mounted */ + if (!current->fs->root) { + printk(KERN_WARNING "bt3c_cs: Root filesystem is not mounted.\n"); + return -EPERM; + } + + sprintf(dev, "%04x", info->link.io.BasePort1); + + pid = kernel_thread(bt3c_fw_loader_exec, (void *)dev, 0); + if (pid < 0) { + printk(KERN_WARNING "bt3c_cs: Forking of kernel thread failed (errno=%d).\n", -pid); + return pid; + } + + /* Block signals, everything but SIGKILL/SIGSTOP */ + spin_lock_irq(¤t->sigmask_lock); + tmpsig = current->blocked; + siginitsetinv(¤t->blocked, sigmask(SIGKILL) | sigmask(SIGSTOP)); + recalc_sigpending(current); + spin_unlock_irq(¤t->sigmask_lock); + + result = waitpid(pid, NULL, __WCLONE); + + /* Allow signals again */ + spin_lock_irq(¤t->sigmask_lock); + current->blocked = tmpsig; + recalc_sigpending(current); + spin_unlock_irq(¤t->sigmask_lock); + + if (result != pid) { + printk(KERN_WARNING "bt3c_cs: Waiting for pid %d failed (errno=%d).\n", pid, -result); + return -result; + } + + return 0; +} + + + +/* ======================== Card services HCI interaction ======================== */ + + +int bt3c_open(bt3c_info_t *info) +{ + 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; + + /* Load firmware */ + + if ((err = bt3c_firmware_load(info)) < 0) + return err; + + /* Timeout before it is safe to send the first HCI packet */ + + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(HZ); + + + /* Initialize and register HCI device */ + + hdev = &(info->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; + + if (hci_register_dev(hdev) < 0) { + printk(KERN_WARNING "bt3c_cs: Can't register HCI device %s.\n", hdev->name); + return -ENODEV; + } + + return 0; +} + + +int bt3c_close(bt3c_info_t *info) +{ + struct hci_dev *hdev = &(info->hdev); + + bt3c_hci_close(hdev); + + if (hci_unregister_dev(hdev) < 0) + printk(KERN_WARNING "bt3c_cs: Can't unregister HCI device %s.\n", hdev->name); + + return 0; +} + + + +/* ======================== Card services ======================== */ + + +static void cs_error(client_handle_t handle, int func, int ret) +{ + error_info_t err = { func, ret }; + + CardServices(ReportError, handle, &err); +} + + +dev_link_t *bt3c_attach(void) +{ + bt3c_info_t *info; + client_reg_t client_reg; + dev_link_t *link; + int i, 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->release.function = &bt3c_release; + link->release.data = (u_long)link; + 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_INFO2_VALID | IRQ_LEVEL_ID; + + if (irq_list[0] == -1) + link->irq.IRQInfo2 = irq_mask; + else + for (i = 0; i < 4; i++) + link->irq.IRQInfo2 |= 1 << irq_list[i]; + + 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.Attributes = INFO_IO_CLIENT | INFO_CARD_SHARE; + 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 = CardServices(RegisterClient, &link->handle, &client_reg); + if (ret != CS_SUCCESS) { + cs_error(link->handle, RegisterClient, ret); + bt3c_detach(link); + return NULL; + } + + return link; +} + + +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; + + del_timer(&link->release); + + if (link->state & DEV_CONFIG) + bt3c_release((u_long)link); + + if (link->handle) { + ret = CardServices(DeregisterClient, 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(int fn, client_handle_t handle, tuple_t *tuple, cisparse_t *parse) +{ + int i; + + i = CardServices(fn, handle, tuple); + if (i != CS_SUCCESS) + return CS_NO_MORE_ITEMS; + + i = CardServices(GetTupleData, handle, tuple); + if (i != CS_SUCCESS) + return i; + + return CardServices(ParseTuple, handle, tuple, parse); +} + + +#define first_tuple(a, b, c) get_tuple(GetFirstTuple, a, b, c) +#define next_tuple(a, b, c) get_tuple(GetNextTuple, a, b, c) + +void bt3c_config(dev_link_t *link) +{ + static ioaddr_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 = CardServices(GetConfigurationInfo, 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 = CardServices(RequestIO, 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 = CardServices(RequestIO, link->handle, &link->io); + if (i == CS_SUCCESS) + goto found_port; + } + } + i = next_tuple(handle, &tuple, &parse); + } + +found_port: + if (i != CS_SUCCESS) { + printk(KERN_NOTICE "bt3c_cs: No usable port range found. Giving up.\n"); + cs_error(link->handle, RequestIO, i); + goto failed; + } + + i = CardServices(RequestIRQ, link->handle, &link->irq); + if (i != CS_SUCCESS) { + cs_error(link->handle, RequestIRQ, i); + link->irq.AssignedIRQ = 0; + } + + i = CardServices(RequestConfiguration, link->handle, &link->conf); + if (i != CS_SUCCESS) { + cs_error(link->handle, RequestConfiguration, i); + goto failed; + } + + MOD_INC_USE_COUNT; + + 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((u_long)link); +} + + +void bt3c_release(u_long arg) +{ + dev_link_t *link = (dev_link_t *)arg; + bt3c_info_t *info = link->priv; + + if (link->state & DEV_PRESENT) + bt3c_close(info); + + MOD_DEC_USE_COUNT; + + link->dev = NULL; + + CardServices(ReleaseConfiguration, link->handle); + CardServices(ReleaseIO, link->handle, &link->io); + CardServices(ReleaseIRQ, link->handle, &link->irq); + + link->state &= ~DEV_CONFIG; +} + + +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); + mod_timer(&link->release, jiffies + HZ / 20); + } + 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) + CardServices(ReleaseConfiguration, link->handle); + break; + case CS_EVENT_PM_RESUME: + link->state &= ~DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_CARD_RESET: + if (DEV_OK(link)) + CardServices(RequestConfiguration, link->handle, &link->conf); + break; + } + + return 0; +} + + + +/* ======================== Module initialization ======================== */ + + +int __init init_bt3c_cs(void) +{ + servinfo_t serv; + int err; + + CardServices(GetCardServicesInfo, &serv); + if (serv.Revision != CS_RELEASE_CODE) { + printk(KERN_NOTICE "bt3c_cs: Card Services release does not match!\n"); + return -1; + } + + err = register_pccard_driver(&dev_info, &bt3c_attach, &bt3c_detach); + + return err; +} + + +void __exit exit_bt3c_cs(void) +{ + unregister_pccard_driver(&dev_info); + + while (dev_list != NULL) + bt3c_detach(dev_list); +} + + +module_init(init_bt3c_cs); +module_exit(exit_bt3c_cs); + +EXPORT_NO_SYMBOLS; --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/drivers/bluetooth/btuart_cs.c 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1,906 @@ +/* + * + * Driver for Bluetooth PCMCIA cards with HCI UART interface + * + * Copyright (C) 2001-2002 Marcel Holtmann + * + * + * 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 + * . Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + + +/* ======================== Module parameters ======================== */ + + +/* Bit map of interrupts to choose from */ +static u_int irq_mask = 0xffff; +static int irq_list[4] = { -1 }; + +MODULE_PARM(irq_mask, "i"); +MODULE_PARM(irq_list, "1-4i"); + +MODULE_AUTHOR("Marcel Holtmann "); +MODULE_DESCRIPTION("BlueZ 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; + + +void btuart_config(dev_link_t *link); +void btuart_release(u_long arg); +int btuart_event(event_t event, int priority, event_callback_args_t *args); + +static dev_info_t dev_info = "btuart_cs"; + +dev_link_t *btuart_attach(void); +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) { + printk(KERN_WARNING "btuart_cs: Call of write_wakeup for unknown device.\n"); + 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) { + printk(KERN_WARNING "btuart_cs: Call of receive for unknown device.\n"); + 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 = bluez_skb_alloc(HCI_MAX_FRAME_SIZE, GFP_ATOMIC))) { + printk(KERN_WARNING "btuart_cs: Can't allocate mem for new packet.\n"); + 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 */ + printk(KERN_WARNING "btuart_cs: Unknown HCI packet with type 0x%02x received.\n", 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; + hci_event_hdr *eh; + hci_acl_hdr *ah; + hci_sco_hdr *sh; + + + switch (info->rx_state) { + + case RECV_WAIT_EVENT_HEADER: + eh = (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 = (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 = (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 to long */ + if (boguscount++ > 16) + break; + + } while (inb(iobase + UART_LSR) & UART_LSR_DR); +} + + +void 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) { + printk(KERN_WARNING "btuart_cs: Call of irq %d for unknown device.\n", irq); + return; + } + + 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: + printk(KERN_NOTICE "btuart_cs: RLSI\n"); + 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: + printk(KERN_NOTICE "btuart_cs: Unhandled IIR=%#x\n", iir); + break; + } + + /* Make sure we don't stay here to long */ + if (boguscount++ > 100) + break; + + iir = inb(iobase + UART_IIR) & UART_IIR_ID; + + } + + spin_unlock(&(info->lock)); +} + + +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) { + printk(KERN_WARNING "btuart_cs: Call of change speed for unknown device.\n"); + 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) { + printk(KERN_WARNING "btuart_cs: 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 ======================== */ + + +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; + + 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 */ + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(HZ); + + + /* Initialize and register HCI device */ + + hdev = &(info->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; + + if (hci_register_dev(hdev) < 0) { + printk(KERN_WARNING "btuart_cs: Can't register HCI device %s.\n", hdev->name); + return -ENODEV; + } + + return 0; +} + + +int btuart_close(btuart_info_t *info) +{ + unsigned long flags; + unsigned int iobase = info->link.io.BasePort1; + struct hci_dev *hdev = &(info->hdev); + + 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) + printk(KERN_WARNING "btuart_cs: Can't unregister HCI device %s.\n", hdev->name); + + return 0; +} + + + +/* ======================== Card services ======================== */ + + +static void cs_error(client_handle_t handle, int func, int ret) +{ + error_info_t err = { func, ret }; + + CardServices(ReportError, handle, &err); +} + + +dev_link_t *btuart_attach(void) +{ + btuart_info_t *info; + client_reg_t client_reg; + dev_link_t *link; + int i, 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->release.function = &btuart_release; + link->release.data = (u_long)link; + 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_INFO2_VALID | IRQ_LEVEL_ID; + + if (irq_list[0] == -1) + link->irq.IRQInfo2 = irq_mask; + else + for (i = 0; i < 4; i++) + link->irq.IRQInfo2 |= 1 << irq_list[i]; + + 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.Attributes = INFO_IO_CLIENT | INFO_CARD_SHARE; + 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 = CardServices(RegisterClient, &link->handle, &client_reg); + if (ret != CS_SUCCESS) { + cs_error(link->handle, RegisterClient, ret); + btuart_detach(link); + return NULL; + } + + return link; +} + + +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; + + del_timer(&link->release); + if (link->state & DEV_CONFIG) + btuart_release((u_long)link); + + if (link->handle) { + ret = CardServices(DeregisterClient, 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(int fn, client_handle_t handle, tuple_t *tuple, cisparse_t *parse) +{ + int i; + + i = CardServices(fn, handle, tuple); + if (i != CS_SUCCESS) + return CS_NO_MORE_ITEMS; + + i = CardServices(GetTupleData, handle, tuple); + if (i != CS_SUCCESS) + return i; + + return CardServices(ParseTuple, handle, tuple, parse); +} + + +#define first_tuple(a, b, c) get_tuple(GetFirstTuple, a, b, c) +#define next_tuple(a, b, c) get_tuple(GetNextTuple, a, b, c) + +void btuart_config(dev_link_t *link) +{ + static ioaddr_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 = CardServices(GetConfigurationInfo, 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 = CardServices(RequestIO, 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 = CardServices(RequestIO, link->handle, &link->io); + if (i == CS_SUCCESS) + goto found_port; + } + } + i = next_tuple(handle, &tuple, &parse); + } + +found_port: + if (i != CS_SUCCESS) { + printk(KERN_NOTICE "btuart_cs: No usable port range found. Giving up.\n"); + cs_error(link->handle, RequestIO, i); + goto failed; + } + + i = CardServices(RequestIRQ, link->handle, &link->irq); + if (i != CS_SUCCESS) { + cs_error(link->handle, RequestIRQ, i); + link->irq.AssignedIRQ = 0; + } + + i = CardServices(RequestConfiguration, link->handle, &link->conf); + if (i != CS_SUCCESS) { + cs_error(link->handle, RequestConfiguration, i); + goto failed; + } + + MOD_INC_USE_COUNT; + + 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((u_long) link); +} + + +void btuart_release(u_long arg) +{ + dev_link_t *link = (dev_link_t *)arg; + btuart_info_t *info = link->priv; + + if (link->state & DEV_PRESENT) + btuart_close(info); + + MOD_DEC_USE_COUNT; + + link->dev = NULL; + + CardServices(ReleaseConfiguration, link->handle); + CardServices(ReleaseIO, link->handle, &link->io); + CardServices(ReleaseIRQ, link->handle, &link->irq); + + link->state &= ~DEV_CONFIG; +} + + +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); + mod_timer(&link->release, jiffies + HZ / 20); + } + 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) + CardServices(ReleaseConfiguration, link->handle); + break; + case CS_EVENT_PM_RESUME: + link->state &= ~DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_CARD_RESET: + if (DEV_OK(link)) + CardServices(RequestConfiguration, link->handle, &link->conf); + break; + } + + return 0; +} + + + +/* ======================== Module initialization ======================== */ + + +int __init init_btuart_cs(void) +{ + servinfo_t serv; + int err; + + CardServices(GetCardServicesInfo, &serv); + if (serv.Revision != CS_RELEASE_CODE) { + printk(KERN_NOTICE "btuart_cs: Card Services release does not match!\n"); + return -1; + } + + err = register_pccard_driver(&dev_info, &btuart_attach, &btuart_detach); + + return err; +} + + +void __exit exit_btuart_cs(void) +{ + unregister_pccard_driver(&dev_info); + + while (dev_list != NULL) + btuart_detach(dev_list); +} + + +module_init(init_btuart_cs); +module_exit(exit_btuart_cs); + +EXPORT_NO_SYMBOLS; --- linux/drivers/bluetooth/Config.in~bluetooth-2.4.18-mh11 2001-09-07 18:28:38.000000000 +0200 +++ linux/drivers/bluetooth/Config.in 2004-01-25 23:37:39.000000000 +0100 @@ -1,8 +1,33 @@ +# +# Bluetooth HCI device drivers configuration +# + mainmenu_option next_comment comment 'Bluetooth device drivers' dep_tristate 'HCI USB driver' CONFIG_BLUEZ_HCIUSB $CONFIG_BLUEZ $CONFIG_USB +if [ "$CONFIG_BLUEZ_HCIUSB" != "n" ]; then + bool ' SCO (voice) support' CONFIG_BLUEZ_HCIUSB_SCO +fi + dep_tristate 'HCI UART driver' CONFIG_BLUEZ_HCIUART $CONFIG_BLUEZ -dep_tristate 'HCI VHCI virtual HCI device driver' CONFIG_BLUEZ_HCIVHCI $CONFIG_BLUEZ +if [ "$CONFIG_BLUEZ_HCIUART" != "n" ]; then + bool ' UART (H4) protocol support' CONFIG_BLUEZ_HCIUART_H4 + bool ' BCSP protocol support' CONFIG_BLUEZ_HCIUART_BCSP + dep_bool ' Transmit CRC with every BCSP packet' CONFIG_BLUEZ_HCIUART_BCSP_TXCRC $CONFIG_BLUEZ_HCIUART_BCSP +fi + +dep_tristate 'HCI BlueFRITZ! USB driver' CONFIG_BLUEZ_HCIBFUSB $CONFIG_BLUEZ $CONFIG_USB + +dep_tristate 'HCI DTL1 (PC Card) driver' CONFIG_BLUEZ_HCIDTL1 $CONFIG_PCMCIA $CONFIG_BLUEZ + +dep_tristate 'HCI BT3C (PC Card) driver' CONFIG_BLUEZ_HCIBT3C $CONFIG_PCMCIA $CONFIG_BLUEZ + +dep_tristate 'HCI BlueCard (PC Card) driver' CONFIG_BLUEZ_HCIBLUECARD $CONFIG_PCMCIA $CONFIG_BLUEZ + +dep_tristate 'HCI UART (PC Card) driver' CONFIG_BLUEZ_HCIBTUART $CONFIG_PCMCIA $CONFIG_BLUEZ + +dep_tristate 'HCI VHCI (Virtual HCI device) driver' CONFIG_BLUEZ_HCIVHCI $CONFIG_BLUEZ endmenu + --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/drivers/bluetooth/dtl1_cs.c 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1,858 @@ +/* + * + * A driver for Nokia Connectivity Card DTL-1 devices + * + * Copyright (C) 2001-2002 Marcel Holtmann + * + * + * 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 + * . Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + + +/* ======================== Module parameters ======================== */ + + +/* Bit map of interrupts to choose from */ +static u_int irq_mask = 0xffff; +static int irq_list[4] = { -1 }; + +MODULE_PARM(irq_mask, "i"); +MODULE_PARM(irq_list, "1-4i"); + +MODULE_AUTHOR("Marcel Holtmann "); +MODULE_DESCRIPTION("BlueZ 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; + + +void dtl1_config(dev_link_t *link); +void dtl1_release(u_long arg); +int dtl1_event(event_t event, int priority, event_callback_args_t *args); + +static dev_info_t dev_info = "dtl1_cs"; + +dev_link_t *dtl1_attach(void); +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) { + printk(KERN_WARNING "dtl1_cs: Call of write_wakeup for unknown device.\n"); + 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 "dtl1_cs: 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) { + printk(KERN_WARNING "dtl1_cs: Call of receive for unknown device.\n"); + return; + } + + iobase = info->link.io.BasePort1; + + do { + info->hdev.stat.byte_rx++; + + /* Allocate packet */ + if (info->rx_skb == NULL) + if (!(info->rx_skb = bluez_skb_alloc(HCI_MAX_FRAME_SIZE, GFP_ATOMIC))) { + printk(KERN_WARNING "dtl1_cs: Can't allocate mem for new packet.\n"); + 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 */ + printk(KERN_WARNING "dtl1_cs: Unknown HCI packet with type 0x%02x received.\n", 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 to long */ + if (boguscount++ > 32) + break; + + } while (inb(iobase + UART_LSR) & UART_LSR_DR); +} + + +void 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) { + printk(KERN_WARNING "dtl1_cs: Call of irq %d for unknown device.\n", irq); + return; + } + + 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: + printk(KERN_NOTICE "dtl1_cs: RLSI\n"); + 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: + printk(KERN_NOTICE "dtl1_cs: Unhandled IIR=%#x\n", iir); + break; + } + + /* Make sure we don't stay here to 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)); +} + + + +/* ======================== 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) { + printk(KERN_WARNING "dtl1_cs: 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 = bluez_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 ======================== */ + + +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)); + + 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 */ + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(HZ * 2); + + + /* Initialize and register HCI device */ + + hdev = &(info->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; + + if (hci_register_dev(hdev) < 0) { + printk(KERN_WARNING "dtl1_cs: Can't register HCI device %s.\n", hdev->name); + return -ENODEV; + } + + return 0; +} + + +int dtl1_close(dtl1_info_t *info) +{ + unsigned long flags; + unsigned int iobase = info->link.io.BasePort1; + struct hci_dev *hdev = &(info->hdev); + + 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) + printk(KERN_WARNING "dtl1_cs: Can't unregister HCI device %s.\n", hdev->name); + + return 0; +} + + + +/* ======================== Card services ======================== */ + + +static void cs_error(client_handle_t handle, int func, int ret) +{ + error_info_t err = { func, ret }; + + CardServices(ReportError, handle, &err); +} + + +dev_link_t *dtl1_attach(void) +{ + dtl1_info_t *info; + client_reg_t client_reg; + dev_link_t *link; + int i, 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->release.function = &dtl1_release; + link->release.data = (u_long)link; + 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_INFO2_VALID | IRQ_LEVEL_ID; + + if (irq_list[0] == -1) + link->irq.IRQInfo2 = irq_mask; + else + for (i = 0; i < 4; i++) + link->irq.IRQInfo2 |= 1 << irq_list[i]; + + 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.Attributes = INFO_IO_CLIENT | INFO_CARD_SHARE; + 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 = CardServices(RegisterClient, &link->handle, &client_reg); + if (ret != CS_SUCCESS) { + cs_error(link->handle, RegisterClient, ret); + dtl1_detach(link); + return NULL; + } + + return link; +} + + +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; + + del_timer(&link->release); + if (link->state & DEV_CONFIG) + dtl1_release((u_long)link); + + if (link->handle) { + ret = CardServices(DeregisterClient, 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(int fn, client_handle_t handle, tuple_t *tuple, cisparse_t *parse) +{ + int i; + + i = CardServices(fn, handle, tuple); + if (i != CS_SUCCESS) + return CS_NO_MORE_ITEMS; + + i = CardServices(GetTupleData, handle, tuple); + if (i != CS_SUCCESS) + return i; + + return CardServices(ParseTuple, handle, tuple, parse); +} + + +#define first_tuple(a, b, c) get_tuple(GetFirstTuple, a, b, c) +#define next_tuple(a, b, c) get_tuple(GetNextTuple, a, b, c) + +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 = CardServices(GetConfigurationInfo, 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 = CardServices(RequestIO, 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 = CardServices(RequestIRQ, link->handle, &link->irq); + if (i != CS_SUCCESS) { + cs_error(link->handle, RequestIRQ, i); + link->irq.AssignedIRQ = 0; + } + + i = CardServices(RequestConfiguration, link->handle, &link->conf); + if (i != CS_SUCCESS) { + cs_error(link->handle, RequestConfiguration, i); + goto failed; + } + + MOD_INC_USE_COUNT; + + 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((u_long)link); +} + + +void dtl1_release(u_long arg) +{ + dev_link_t *link = (dev_link_t *)arg; + dtl1_info_t *info = link->priv; + + if (link->state & DEV_PRESENT) + dtl1_close(info); + + MOD_DEC_USE_COUNT; + + link->dev = NULL; + + CardServices(ReleaseConfiguration, link->handle); + CardServices(ReleaseIO, link->handle, &link->io); + CardServices(ReleaseIRQ, link->handle, &link->irq); + + link->state &= ~DEV_CONFIG; +} + + +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); + mod_timer(&link->release, jiffies + HZ / 20); + } + 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) + CardServices(ReleaseConfiguration, link->handle); + break; + case CS_EVENT_PM_RESUME: + link->state &= ~DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_CARD_RESET: + if (DEV_OK(link)) + CardServices(RequestConfiguration, link->handle, &link->conf); + break; + } + + return 0; +} + + + +/* ======================== Module initialization ======================== */ + + +int __init init_dtl1_cs(void) +{ + servinfo_t serv; + int err; + + CardServices(GetCardServicesInfo, &serv); + if (serv.Revision != CS_RELEASE_CODE) { + printk(KERN_NOTICE "dtl1_cs: Card Services release does not match!\n"); + return -1; + } + + err = register_pccard_driver(&dev_info, &dtl1_attach, &dtl1_detach); + + return err; +} + + +void __exit exit_dtl1_cs(void) +{ + unregister_pccard_driver(&dev_info); + + while (dev_list != NULL) + dtl1_detach(dev_list); +} + + +module_init(init_dtl1_cs); +module_exit(exit_dtl1_cs); + +EXPORT_NO_SYMBOLS; --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/drivers/bluetooth/hci_bcsp.c 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1,710 @@ +/* + BlueCore Serial Protocol (BCSP) for Linux Bluetooth stack (BlueZ). + Copyright 2002 by Fabrizio Gennari + + Based on + hci_h4.c by Maxim Krasnyansky + ABCSP by Carl Orsborn + + 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.1" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "hci_uart.h" +#include "hci_bcsp.h" + +#ifndef HCI_UART_DEBUG +#undef BT_DBG +#define BT_DBG( A... ) +#undef BT_DMP +#define BT_DMP( A... ) +#endif + +/* ---- 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_BLUEZ_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; + } + + /* 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_BLUEZ_HCIUART_BCSP_TXCRC + hdr[0] |= 0x40; +#endif + + hdr[1] = (len << 4) & 0xFF; + hdr[1] |= 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_BLUEZ_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_BLUEZ_HCIUART_BCSP_TXCRC + bcsp_crc_update(&bcsp_txmsg_crc, data[i]); +#endif + } + +#ifdef CONFIG_BLUEZ_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 = (struct bcsp_struct *) 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) { + if ((bcsp->rx_skb->data[1] & 0x0f) != 0 && + (bcsp->rx_skb->data[1] & 0x0f) != 1) { + 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 { + /* 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 %ld 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 = bluez_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 = (struct bcsp_struct *) hu->priv; + struct sk_buff *skb; + unsigned long flags; + + BT_ERR("Timeout, retransmitting %u pkts", 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) +{ + return hci_uart_register_proto(&bcsp); +} + +int bcsp_deinit(void) +{ + return hci_uart_unregister_proto(&bcsp); +} --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/drivers/bluetooth/hci_bcsp.h 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1,70 @@ +/* + BlueCore Serial Protocol (BCSP) for Linux Bluetooth stack (BlueZ). + Copyright 2002 by Fabrizio Gennari + + Based on + hci_h4.c by Maxim Krasnyansky + ABCSP by Carl Orsborn + + 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__ */ --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/drivers/bluetooth/hci_h4.c 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1,277 @@ +/* + BlueZ - Bluetooth protocol stack for Linux + Copyright (C) 2000-2001 Qualcomm Incorporated + + Written 2000,2001 by Maxim Krasnyansky + + 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. +*/ + +/* + * BlueZ HCI UART(H4) protocol. + * + * $Id: hci_h4.c,v 1.3 2002/09/09 01:17:32 maxk Exp $ + */ +#define VERSION "1.2" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "hci_uart.h" +#include "hci_h4.h" + +#ifndef HCI_UART_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; + hci_event_hdr *eh; + hci_acl_hdr *ah; + 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(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 = (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 = (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 = (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 = bluez_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) +{ + return hci_uart_register_proto(&h4p); +} + +int h4_deinit(void) +{ + return hci_uart_unregister_proto(&h4p); +} --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/drivers/bluetooth/hci_h4.h 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1,44 @@ +/* + BlueZ - Bluetooth protocol stack for Linux + Copyright (C) 2000-2001 Qualcomm Incorporated + + Written 2000,2001 by Maxim Krasnyansky + + 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__ */ --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/drivers/bluetooth/hci_ldisc.c 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1,580 @@ +/* + BlueZ - Bluetooth protocol stack for Linux + Copyright (C) 2000-2001 Qualcomm Incorporated + + Written 2000,2001 by Maxim Krasnyansky + + 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. +*/ + +/* + * BlueZ HCI UART driver. + * + * $Id: hci_ldisc.c,v 1.5 2002/10/02 18:37:20 maxk Exp $ + */ +#define VERSION "2.1" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "hci_uart.h" + +#ifndef HCI_UART_DEBUG +#undef BT_DBG +#define BT_DBG( A... ) +#undef BT_DMP +#define BT_DMP( A... ) +#endif + +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, 0, 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. */ + if (tty->ldisc.flush_buffer) + tty->ldisc.flush_buffer(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); + + MOD_DEC_USE_COUNT; +} + +/* ------ 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 */ + if (tty->ldisc.flush_buffer) + tty->ldisc.flush_buffer(tty); + + if (tty->driver.flush_buffer) + tty->driver.flush_buffer(tty); + + MOD_INC_USE_COUNT; + 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); + } + + MOD_DEC_USE_COUNT; + } +} + +/* 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 = &hu->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; + + if (hci_register_dev(hdev) < 0) { + BT_ERR("Can't register HCI device %s", hdev->name); + return -ENODEV; + } + MOD_INC_USE_COUNT; + 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 *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_BLUEZ_HCIUART_H4 +int h4_init(void); +int h4_deinit(void); +#endif +#ifdef CONFIG_BLUEZ_HCIUART_BCSP +int bcsp_init(void); +int bcsp_deinit(void); +#endif + +int __init hci_uart_init(void) +{ + static struct tty_ldisc hci_uart_ldisc; + int err; + + BT_INFO("BlueZ HCI UART driver ver %s Copyright (C) 2000,2001 Qualcomm Inc", + VERSION); + BT_INFO("Written 2000,2001 by Maxim Krasnyansky "); + + /* 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; + + if ((err = tty_register_ldisc(N_HCI, &hci_uart_ldisc))) { + BT_ERR("Can't register HCI line discipline (%d)", err); + return err; + } + +#ifdef CONFIG_BLUEZ_HCIUART_H4 + h4_init(); +#endif +#ifdef CONFIG_BLUEZ_HCIUART_BCSP + bcsp_init(); +#endif + + return 0; +} + +void hci_uart_cleanup(void) +{ + int err; + +#ifdef CONFIG_BLUEZ_HCIUART_H4 + h4_deinit(); +#endif +#ifdef CONFIG_BLUEZ_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_cleanup); + +MODULE_AUTHOR("Maxim Krasnyansky "); +MODULE_DESCRIPTION("BlueZ HCI UART driver ver " VERSION); +MODULE_LICENSE("GPL"); --- linux/drivers/bluetooth/hci_uart.c~bluetooth-2.4.18-mh11 +++ linux/drivers/bluetooth/hci_uart.c -/* - BlueZ - Bluetooth protocol stack for Linux - Copyright (C) 2000-2001 Qualcomm Incorporated - - Written 2000,2001 by Maxim Krasnyansky - - 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. -*/ - -/* - * BlueZ HCI UART driver. - * - * $Id: hci_uart.c,v 1.5 2001/07/05 18:42:44 maxk Exp $ - */ -#define VERSION "1.0" - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#ifndef HCI_UART_DEBUG -#undef DBG -#define DBG( A... ) -#undef DMP -#define DMP( A... ) -#endif - -/* ------- Interface to HCI layer ------ */ -/* Initialize device */ -int n_hci_open(struct hci_dev *hdev) -{ - DBG("%s %p", hdev->name, hdev); - - /* Nothing to do for UART driver */ - - hdev->flags |= HCI_RUNNING; - - return 0; -} - -/* Reset device */ -int n_hci_flush(struct hci_dev *hdev) -{ - struct n_hci *n_hci = (struct n_hci *) hdev->driver_data; - struct tty_struct *tty = n_hci->tty; - - DBG("hdev %p tty %p", hdev, tty); - - /* Drop TX queue */ - skb_queue_purge(&n_hci->txq); - - /* Flush any pending characters in the driver and discipline. */ - if (tty->ldisc.flush_buffer) - tty->ldisc.flush_buffer(tty); - - if (tty->driver.flush_buffer) - tty->driver.flush_buffer(tty); - - return 0; -} - -/* Close device */ -int n_hci_close(struct hci_dev *hdev) -{ - DBG("hdev %p", hdev); - - hdev->flags &= ~HCI_RUNNING; - - n_hci_flush(hdev); - - return 0; -} - -int n_hci_tx_wakeup(struct n_hci *n_hci) -{ - register struct tty_struct *tty = n_hci->tty; - - if (test_and_set_bit(TRANS_SENDING, &n_hci->tx_state)) { - set_bit(TRANS_WAKEUP, &n_hci->tx_state); - return 0; - } - - DBG(""); - do { - register struct sk_buff *skb; - register int len; - - clear_bit(TRANS_WAKEUP, &n_hci->tx_state); - - if (!(skb = skb_dequeue(&n_hci->txq))) - break; - - DMP(skb->data, skb->len); - - /* Send frame to TTY driver */ - tty->flags |= (1 << TTY_DO_WRITE_WAKEUP); - len = tty->driver.write(tty, 0, skb->data, skb->len); - - n_hci->hdev.stat.byte_tx += len; - - DBG("sent %d", len); - - if (len == skb->len) { - /* Full frame was sent */ - kfree_skb(skb); - } else { - /* Subtract sent part and requeue */ - skb_pull(skb, len); - skb_queue_head(&n_hci->txq, skb); - } - } while (test_bit(TRANS_WAKEUP, &n_hci->tx_state)); - clear_bit(TRANS_SENDING, &n_hci->tx_state); - - return 0; -} - -/* Send frames from HCI layer */ -int n_hci_send_frame(struct sk_buff *skb) -{ - struct hci_dev* hdev = (struct hci_dev *) skb->dev; - struct tty_struct *tty; - struct n_hci *n_hci; - - if (!hdev) { - ERR("Frame for uknown device (hdev=NULL)"); - return -ENODEV; - } - - if (!(hdev->flags & HCI_RUNNING)) - return -EBUSY; - - n_hci = (struct n_hci *) hdev->driver_data; - tty = n_hci2tty(n_hci); - - DBG("%s: type %d len %d", hdev->name, skb->pkt_type, skb->len); - - 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.cmd_tx++; - break; - }; - - /* Prepend skb with frame type and queue */ - memcpy(skb_push(skb, 1), &skb->pkt_type, 1); - skb_queue_tail(&n_hci->txq, skb); - - n_hci_tx_wakeup(n_hci); - - return 0; -} - -/* ------ LDISC part ------ */ - -/* n_hci_tty_open - * - * Called when line discipline changed to N_HCI. - * - * Arguments: - * tty pointer to tty info structure - * Return Value: - * 0 if success, otherwise error code - */ -static int n_hci_tty_open(struct tty_struct *tty) -{ - struct n_hci *n_hci = tty2n_hci(tty); - struct hci_dev *hdev; - - DBG("tty %p", tty); - - if (n_hci) - return -EEXIST; - - if (!(n_hci = kmalloc(sizeof(struct n_hci), GFP_KERNEL))) { - ERR("Can't allocate controll structure"); - return -ENFILE; - } - memset(n_hci, 0, sizeof(struct n_hci)); - - /* Initialize and register HCI device */ - hdev = &n_hci->hdev; - - hdev->type = HCI_UART; - hdev->driver_data = n_hci; - - hdev->open = n_hci_open; - hdev->close = n_hci_close; - hdev->flush = n_hci_flush; - hdev->send = n_hci_send_frame; - - if (hci_register_dev(hdev) < 0) { - ERR("Can't register HCI device %s", hdev->name); - kfree(n_hci); - return -ENODEV; - } - - tty->disc_data = n_hci; - n_hci->tty = tty; - - spin_lock_init(&n_hci->rx_lock); - n_hci->rx_state = WAIT_PACKET_TYPE; - - skb_queue_head_init(&n_hci->txq); - - MOD_INC_USE_COUNT; - - /* Flush any pending characters in the driver and discipline. */ - if (tty->ldisc.flush_buffer) - tty->ldisc.flush_buffer(tty); - - if (tty->driver.flush_buffer) - tty->driver.flush_buffer(tty); - - return 0; -} - -/* n_hci_tty_close() - * - * Called when the line discipline is changed to something - * else, the tty is closed, or the tty detects a hangup. - */ -static void n_hci_tty_close(struct tty_struct *tty) -{ - struct n_hci *n_hci = tty2n_hci(tty); - struct hci_dev *hdev = &n_hci->hdev; - - DBG("tty %p hdev %p", tty, hdev); - - if (n_hci != NULL) { - n_hci_close(hdev); - - if (hci_unregister_dev(hdev) < 0) { - ERR("Can't unregister HCI device %s",hdev->name); - } - - hdev->driver_data = NULL; - tty->disc_data = NULL; - kfree(n_hci); - - MOD_DEC_USE_COUNT; - } -} - -/* n_hci_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 n_hci_tty_wakeup( struct tty_struct *tty ) -{ - struct n_hci *n_hci = tty2n_hci(tty); - - DBG(""); - - if (!n_hci) - return; - - tty->flags &= ~(1 << TTY_DO_WRITE_WAKEUP); - - if (tty != n_hci->tty) - return; - - n_hci_tx_wakeup(n_hci); -} - -/* n_hci_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 n_hci_tty_room (struct tty_struct *tty) -{ - return 65536; -} - -static inline int n_hci_check_data_len(struct n_hci *n_hci, int len) -{ - register int room = skb_tailroom(n_hci->rx_skb); - - DBG("len %d room %d", len, room); - if (!len) { - DMP(n_hci->rx_skb->data, n_hci->rx_skb->len); - hci_recv_frame(n_hci->rx_skb); - } else if (len > room) { - ERR("Data length is to large"); - kfree_skb(n_hci->rx_skb); - n_hci->hdev.stat.err_rx++; - } else { - n_hci->rx_state = WAIT_DATA; - n_hci->rx_count = len; - return len; - } - - n_hci->rx_state = WAIT_PACKET_TYPE; - n_hci->rx_skb = NULL; - n_hci->rx_count = 0; - return 0; -} - -static inline void n_hci_rx(struct n_hci *n_hci, const __u8 * data, char *flags, int count) -{ - register const char *ptr; - hci_event_hdr *eh; - hci_acl_hdr *ah; - hci_sco_hdr *sh; - register int len, type, dlen; - - DBG("count %d state %ld rx_count %ld", count, n_hci->rx_state, n_hci->rx_count); - - n_hci->hdev.stat.byte_rx += count; - - ptr = data; - while (count) { - if (n_hci->rx_count) { - len = MIN(n_hci->rx_count, count); - memcpy(skb_put(n_hci->rx_skb, len), ptr, len); - n_hci->rx_count -= len; count -= len; ptr += len; - - if (n_hci->rx_count) - continue; - - switch (n_hci->rx_state) { - case WAIT_DATA: - DBG("Complete data"); - - DMP(n_hci->rx_skb->data, n_hci->rx_skb->len); - - hci_recv_frame(n_hci->rx_skb); - - n_hci->rx_state = WAIT_PACKET_TYPE; - n_hci->rx_skb = NULL; - continue; - - case WAIT_EVENT_HDR: - eh = (hci_event_hdr *) n_hci->rx_skb->data; - - DBG("Event header: evt 0x%2.2x plen %d", eh->evt, eh->plen); - - n_hci_check_data_len(n_hci, eh->plen); - continue; - - case WAIT_ACL_HDR: - ah = (hci_acl_hdr *) n_hci->rx_skb->data; - dlen = __le16_to_cpu(ah->dlen); - - DBG("ACL header: dlen %d", dlen); - - n_hci_check_data_len(n_hci, dlen); - continue; - - case WAIT_SCO_HDR: - sh = (hci_sco_hdr *) n_hci->rx_skb->data; - - DBG("SCO header: dlen %d", sh->dlen); - - n_hci_check_data_len(n_hci, sh->dlen); - continue; - }; - } - - /* WAIT_PACKET_TYPE */ - switch (*ptr) { - case HCI_EVENT_PKT: - DBG("Event packet"); - n_hci->rx_state = WAIT_EVENT_HDR; - n_hci->rx_count = HCI_EVENT_HDR_SIZE; - type = HCI_EVENT_PKT; - break; - - case HCI_ACLDATA_PKT: - DBG("ACL packet"); - n_hci->rx_state = WAIT_ACL_HDR; - n_hci->rx_count = HCI_ACL_HDR_SIZE; - type = HCI_ACLDATA_PKT; - break; - - case HCI_SCODATA_PKT: - DBG("SCO packet"); - n_hci->rx_state = WAIT_SCO_HDR; - n_hci->rx_count = HCI_SCO_HDR_SIZE; - type = HCI_SCODATA_PKT; - break; - - default: - ERR("Unknown HCI packet type %2.2x", (__u8)*ptr); - n_hci->hdev.stat.err_rx++; - ptr++; count--; - continue; - }; - ptr++; count--; - - /* Allocate packet */ - if (!(n_hci->rx_skb = bluez_skb_alloc(HCI_MAX_FRAME_SIZE, GFP_ATOMIC))) { - ERR("Can't allocate mem for new packet"); - - n_hci->rx_state = WAIT_PACKET_TYPE; - n_hci->rx_count = 0; - return; - } - n_hci->rx_skb->dev = (void *) &n_hci->hdev; - n_hci->rx_skb->pkt_type = type; - } -} - -/* n_hci_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 n_hci_tty_receive(struct tty_struct *tty, const __u8 * data, char *flags, int count) -{ - struct n_hci *n_hci = tty2n_hci(tty); - - if (!n_hci || tty != n_hci->tty) - return; - - spin_lock(&n_hci->rx_lock); - n_hci_rx(n_hci, data, flags, count); - spin_unlock(&n_hci->rx_lock); - - if (test_and_clear_bit(TTY_THROTTLED,&tty->flags) && tty->driver.unthrottle) - tty->driver.unthrottle(tty); -} - -/* n_hci_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 n_hci_tty_ioctl (struct tty_struct *tty, struct file * file, - unsigned int cmd, unsigned long arg) -{ - struct n_hci *n_hci = tty2n_hci(tty); - int error = 0; - - DBG(""); - - /* Verify the status of the device */ - if (!n_hci) - return -EBADF; - - switch (cmd) { - default: - error = n_tty_ioctl(tty, file, cmd, arg); - break; - }; - - return error; -} - -/* - * We don't provide read/write/poll interface for user space. - */ -static ssize_t n_hci_tty_read(struct tty_struct *tty, struct file *file, unsigned char *buf, size_t nr) -{ - return 0; -} -static ssize_t n_hci_tty_write(struct tty_struct *tty, struct file *file, const unsigned char *data, size_t count) -{ - return 0; -} -static unsigned int n_hci_tty_poll(struct tty_struct *tty, struct file *filp, poll_table *wait) -{ - return 0; -} - -int __init n_hci_init(void) -{ - static struct tty_ldisc n_hci_ldisc; - int err; - - INF("BlueZ HCI UART driver ver %s Copyright (C) 2000,2001 Qualcomm Inc", - VERSION); - INF("Written 2000,2001 by Maxim Krasnyansky "); - - /* Register the tty discipline */ - - memset(&n_hci_ldisc, 0, sizeof (n_hci_ldisc)); - n_hci_ldisc.magic = TTY_LDISC_MAGIC; - n_hci_ldisc.name = "n_hci"; - n_hci_ldisc.open = n_hci_tty_open; - n_hci_ldisc.close = n_hci_tty_close; - n_hci_ldisc.read = n_hci_tty_read; - n_hci_ldisc.write = n_hci_tty_write; - n_hci_ldisc.ioctl = n_hci_tty_ioctl; - n_hci_ldisc.poll = n_hci_tty_poll; - n_hci_ldisc.receive_room= n_hci_tty_room; - n_hci_ldisc.receive_buf = n_hci_tty_receive; - n_hci_ldisc.write_wakeup= n_hci_tty_wakeup; - - if ((err = tty_register_ldisc(N_HCI, &n_hci_ldisc))) { - ERR("Can't register HCI line discipline (%d)", err); - return err; - } - - return 0; -} - -void n_hci_cleanup(void) -{ - int err; - - /* Release tty registration of line discipline */ - if ((err = tty_register_ldisc(N_HCI, NULL))) - ERR("Can't unregister HCI line discipline (%d)", err); -} - -module_init(n_hci_init); -module_exit(n_hci_cleanup); - -MODULE_AUTHOR("Maxim Krasnyansky "); -MODULE_DESCRIPTION("BlueZ HCI UART driver ver " VERSION); -MODULE_LICENSE("GPL"); --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/drivers/bluetooth/hci_uart.h 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1,81 @@ +/* + BlueZ - Bluetooth protocol stack for Linux + Copyright (C) 2000-2001 Qualcomm Incorporated + + Written 2000,2001 by Maxim Krasnyansky + + 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 3 + +#define HCI_UART_H4 0 +#define HCI_UART_BCSP 1 +#define HCI_UART_NCSP 2 + +#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__ */ --- linux/drivers/bluetooth/hci_usb.c~bluetooth-2.4.18-mh11 2001-09-07 18:28:38.000000000 +0200 +++ linux/drivers/bluetooth/hci_usb.c 2004-01-25 23:37:39.000000000 +0100 @@ -1,9 +1,10 @@ /* - BlueZ - Bluetooth protocol stack for Linux + HCI USB driver for Linux Bluetooth protocol stack (BlueZ) Copyright (C) 2000-2001 Qualcomm Incorporated - Written 2000,2001 by Maxim Krasnyansky + Copyright (C) 2003 Maxim Krasnyansky + 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; @@ -23,598 +24,914 @@ */ /* - * BlueZ HCI USB driver. * Based on original USB Bluetooth driver for Linux kernel * Copyright (c) 2000 Greg Kroah-Hartman * Copyright (c) 2000 Mark Douglas Corner * - * $Id: hci_usb.c,v 1.5 2001/07/05 18:42:44 maxk Exp $ + * $Id: hci_usb.c,v 1.8 2002/07/18 17:23:09 maxk Exp $ */ -#define VERSION "1.0" +#define VERSION "2.4" #include #include #include -#include #include #include #include +#include #include -#include #include -#include -#include #include -#include #include #include -#include -#include #include #include #include -#include #include -#include + +#include "hci_usb.h" #ifndef HCI_USB_DEBUG -#undef DBG -#define DBG( A... ) -#undef DMP -#define DMP( A... ) +#undef BT_DBG +#define BT_DBG( A... ) +#undef BT_DMP +#define BT_DMP( A... ) #endif -static struct usb_device_id usb_bluetooth_ids [] = { +#ifndef CONFIG_BLUEZ_HCIUSB_ZERO_PACKET +#undef USB_ZERO_PACKET +#define USB_ZERO_PACKET 0 +#endif + +static struct usb_driver hci_usb_driver; + +static struct usb_device_id bluetooth_ids[] = { + /* Digianswer device */ + { USB_DEVICE(0x08fd, 0x0001), driver_info: HCI_DIGIANSWER }, + + /* Generic Bluetooth USB device */ { USB_DEVICE_INFO(HCI_DEV_CLASS, HCI_DEV_SUBCLASS, HCI_DEV_PROTOCOL) }, + + /* Ericsson with non-standard id */ + { USB_DEVICE(0x0bdb, 0x1002) }, + + /* ALPS Module with non-standard id */ + { USB_DEVICE(0x044e, 0x3002) }, + + /* Bluetooth Ultraport Module from IBM */ + { USB_DEVICE(0x04bf, 0x030a) }, + { } /* Terminating entry */ }; -MODULE_DEVICE_TABLE (usb, usb_bluetooth_ids); +MODULE_DEVICE_TABLE (usb, bluetooth_ids); -static int hci_usb_ctrl_msg(struct hci_usb *husb, struct sk_buff *skb); -static int hci_usb_write_msg(struct hci_usb *husb, struct sk_buff *skb); +static struct usb_device_id ignore_ids[] = { + /* Broadcom BCM2033 without firmware */ + { USB_DEVICE(0x0a5c, 0x2033) }, -static void hci_usb_unlink_urbs(struct hci_usb *husb) -{ - usb_unlink_urb(husb->read_urb); - usb_unlink_urb(husb->intr_urb); - usb_unlink_urb(husb->ctrl_urb); - usb_unlink_urb(husb->write_urb); -} + { } /* Terminating entry */ +}; -static void hci_usb_free_bufs(struct hci_usb *husb) +struct _urb *_urb_alloc(int isoc, int gfp) { - if (husb->read_urb) { - if (husb->read_urb->transfer_buffer) - kfree(husb->read_urb->transfer_buffer); - usb_free_urb(husb->read_urb); + struct _urb *_urb = kmalloc(sizeof(struct _urb) + + sizeof(iso_packet_descriptor_t) * isoc, gfp); + if (_urb) { + memset(_urb, 0, sizeof(*_urb)); + spin_lock_init(&_urb->urb.lock); } + return _urb; +} - if (husb->intr_urb) { - if (husb->intr_urb->transfer_buffer) - kfree(husb->intr_urb->transfer_buffer); - usb_free_urb(husb->intr_urb); +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; +} - if (husb->ctrl_urb) - usb_free_urb(husb->ctrl_urb); +static void hci_usb_rx_complete(struct urb *urb); +static void hci_usb_tx_complete(struct urb *urb); - if (husb->write_urb) - usb_free_urb(husb->write_urb); +#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]) - if (husb->intr_skb) - kfree_skb(husb->intr_skb); +static inline struct _urb *__get_completed(struct hci_usb *husb, int type) +{ + return _urb_dequeue(__completed_q(husb, type)); } -/* ------- Interface to HCI layer ------ */ -/* Initialize device */ -int hci_usb_open(struct hci_dev *hdev) +#ifdef CONFIG_BLUEZ_HCIUSB_SCO +static void __fill_isoc_desc(struct urb *urb, int len, int mtu) { - struct hci_usb *husb = (struct hci_usb *) hdev->driver_data; - int status; - - DBG("%s", hdev->name); - - husb->read_urb->dev = husb->udev; - if ((status = usb_submit_urb(husb->read_urb))) - DBG("read submit failed. %d", status); - - husb->intr_urb->dev = husb->udev; - if ((status = usb_submit_urb(husb->intr_urb))) - DBG("interrupt submit failed. %d", status); + int offset = 0, i; - hdev->flags |= HCI_RUNNING; + BT_DBG("len %d mtu %d", len, mtu); - return 0; + 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 -/* Reset device */ -int hci_usb_flush(struct hci_dev *hdev) +static int hci_usb_intr_rx_submit(struct hci_usb *husb) { - struct hci_usb *husb = (struct hci_usb *) hdev->driver_data; + struct _urb *_urb; + struct urb *urb; + int err, pipe, interval, size; + void *buf; - DBG("%s", hdev->name); + BT_DBG("%s", husb->hdev.name); - /* Drop TX queues */ - skb_queue_purge(&husb->tx_ctrl_q); - skb_queue_purge(&husb->tx_write_q); + size = husb->intr_in_ep->wMaxPacketSize; - return 0; + 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->bEndpointAddress); + interval = husb->intr_in_ep->bInterval; + FILL_INT_URB(urb, husb->udev, pipe, buf, size, hci_usb_rx_complete, husb, interval); + + err = usb_submit_urb(urb); + 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; } -/* Close device */ -int hci_usb_close(struct hci_dev *hdev) +static int hci_usb_bulk_rx_submit(struct hci_usb *husb) { - struct hci_usb *husb = (struct hci_usb *) hdev->driver_data; + struct _urb *_urb; + struct urb *urb; + int err, pipe, size = HCI_MAX_FRAME_SIZE; + void *buf; - DBG("%s", hdev->name); + buf = kmalloc(size, GFP_ATOMIC); + if (!buf) + return -ENOMEM; - hdev->flags &= ~HCI_RUNNING; - hci_usb_unlink_urbs(husb); + _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); - hci_usb_flush(hdev); + urb = &_urb->urb; + pipe = usb_rcvbulkpipe(husb->udev, husb->bulk_in_ep->bEndpointAddress); + FILL_BULK_URB(urb, husb->udev, pipe, buf, size, hci_usb_rx_complete, husb); + urb->transfer_flags = USB_QUEUE_BULK; - return 0; + BT_DBG("%s urb %p", husb->hdev.name, urb); + + err = usb_submit_urb(urb); + 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; } -void hci_usb_ctrl_wakeup(struct hci_usb *husb) +#ifdef CONFIG_BLUEZ_HCIUSB_SCO +static int hci_usb_isoc_rx_submit(struct hci_usb *husb) { - struct sk_buff *skb; - - if (test_and_set_bit(HCI_TX_CTRL, &husb->tx_state)) - return; + struct _urb *_urb; + struct urb *urb; + int err, mtu, size; + void *buf; - DBG("%s", husb->hdev.name); + mtu = husb->isoc_in_ep->wMaxPacketSize; + size = mtu * HCI_MAX_ISOC_FRAMES; - if (!(skb = skb_dequeue(&husb->tx_ctrl_q))) - goto done; + buf = kmalloc(size, GFP_ATOMIC); + if (!buf) + return -ENOMEM; - if (hci_usb_ctrl_msg(husb, skb)){ - kfree_skb(skb); - goto done; + _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); - DMP(skb->data, skb->len); + urb = &_urb->urb; - husb->hdev.stat.byte_tx += skb->len; - return; + urb->context = husb; + urb->dev = husb->udev; + urb->pipe = usb_rcvisocpipe(husb->udev, husb->isoc_in_ep->bEndpointAddress); + urb->complete = hci_usb_rx_complete; -done: - clear_bit(HCI_TX_CTRL, &husb->tx_state); - return; + urb->transfer_buffer_length = size; + urb->transfer_buffer = buf; + urb->transfer_flags = USB_ISO_ASAP; + + __fill_isoc_desc(urb, size, mtu); + + BT_DBG("%s urb %p", husb->hdev.name, urb); + + err = usb_submit_urb(urb); + 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 -void hci_usb_write_wakeup(struct hci_usb *husb) +/* Initialize device */ +static int hci_usb_open(struct hci_dev *hdev) { - struct sk_buff *skb; + struct hci_usb *husb = (struct hci_usb *) hdev->driver_data; + int i, err; + unsigned long flags; - if (test_and_set_bit(HCI_TX_WRITE, &husb->tx_state)) - return; + BT_DBG("%s", hdev->name); - DBG("%s", husb->hdev.name); + if (test_and_set_bit(HCI_RUNNING, &hdev->flags)) + return 0; - if (!(skb = skb_dequeue(&husb->tx_write_q))) - goto done; + MOD_INC_USE_COUNT; - if (hci_usb_write_msg(husb, skb)) { - skb_queue_head(&husb->tx_write_q, skb); - goto done; + 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_BLUEZ_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); + MOD_DEC_USE_COUNT; } - DMP(skb->data, skb->len); + write_unlock_irqrestore(&husb->completion_lock, flags); + return err; +} - husb->hdev.stat.byte_tx += skb->len; - return; +/* Reset device */ +static int hci_usb_flush(struct hci_dev *hdev) +{ + struct hci_usb *husb = (struct hci_usb *) hdev->driver_data; + int i; -done: - clear_bit(HCI_TX_WRITE, &husb->tx_state); - return; + BT_DBG("%s", hdev->name); + + for (i=0; i < 4; i++) + skb_queue_purge(&husb->transmit_q[i]); + return 0; } -/* Send frames from HCI layer */ -int hci_usb_send_frame(struct sk_buff *skb) +static void hci_usb_unlink_urbs(struct hci_usb *husb) { - struct hci_dev *hdev = (struct hci_dev *) skb->dev; - struct hci_usb *husb; + int i; - if (!hdev) { - ERR("frame for uknown device (hdev=NULL)"); - return -ENODEV; - } + BT_DBG("%s", husb->hdev.name); - if (!(hdev->flags & HCI_RUNNING)) - return 0; + for (i=0; i < 4; i++) { + struct _urb *_urb; + struct urb *urb; - husb = (struct hci_usb *) hdev->driver_data; + /* 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_unlink_urb(urb); + _urb_queue_tail(__completed_q(husb, _urb->type), _urb); + } - DBG("%s type %d len %d", hdev->name, skb->pkt_type, skb->len); + /* 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); + } - switch (skb->pkt_type) { - case HCI_COMMAND_PKT: - skb_queue_tail(&husb->tx_ctrl_q, skb); - hci_usb_ctrl_wakeup(husb); - hdev->stat.cmd_tx++; - return 0; + /* Release reassembly buffers */ + if (husb->reassembly[i]) { + kfree_skb(husb->reassembly[i]); + husb->reassembly[i] = NULL; + } + } +} - case HCI_ACLDATA_PKT: - skb_queue_tail(&husb->tx_write_q, skb); - hci_usb_write_wakeup(husb); - hdev->stat.acl_tx++; - return 0; +/* 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; - case HCI_SCODATA_PKT: - return -EOPNOTSUPP; - }; + BT_DBG("%s", hdev->name); + + write_lock_irqsave(&husb->completion_lock, flags); + + hci_usb_unlink_urbs(husb); + hci_usb_flush(hdev); + + write_unlock_irqrestore(&husb->completion_lock, flags); + MOD_DEC_USE_COUNT; return 0; } -/* ---------- USB ------------- */ +static int __tx_submit(struct hci_usb *husb, struct _urb *_urb) +{ + struct urb *urb = &_urb->urb; + int err; -static void hci_usb_ctrl(struct urb *urb) + 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); + 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 sk_buff *skb = (struct sk_buff *) urb->context; - struct hci_dev *hdev; - struct hci_usb *husb; + struct _urb *_urb = __get_completed(husb, skb->pkt_type); + devrequest *dr; + struct urb *urb; - if (!skb) - return; - hdev = (struct hci_dev *) skb->dev; - husb = (struct hci_usb *) hdev->driver_data; + if (!_urb) { + _urb = _urb_alloc(0, GFP_ATOMIC); + if (!_urb) + return -ENOMEM; + _urb->type = skb->pkt_type; - DBG("%s", hdev->name); + dr = kmalloc(sizeof(*dr), GFP_ATOMIC); + if (!dr) { + _urb_free(_urb); + return -ENOMEM; + } + } else + dr = (void *) _urb->urb.setup_packet; - if (urb->status) - DBG("%s ctrl status: %d", hdev->name, urb->status); + dr->requesttype = husb->ctrl_req; + dr->request = 0; + dr->index = 0; + dr->value = 0; + dr->length = __cpu_to_le16(skb->len); - clear_bit(HCI_TX_CTRL, &husb->tx_state); - kfree_skb(skb); + urb = &_urb->urb; + FILL_CONTROL_URB(urb, husb->udev, usb_sndctrlpipe(husb->udev, 0), + (void *) dr, skb->data, skb->len, hci_usb_tx_complete, husb); - /* Wake up device */ - hci_usb_ctrl_wakeup(husb); + BT_DBG("%s skb %p len %d", husb->hdev.name, skb, skb->len); + + _urb->priv = skb; + return __tx_submit(husb, _urb); } -static void hci_usb_bulk_write(struct urb *urb) +static inline int hci_usb_send_bulk(struct hci_usb *husb, struct sk_buff *skb) { - struct sk_buff *skb = (struct sk_buff *) urb->context; - struct hci_dev *hdev; - struct hci_usb *husb; - - if (!skb) - return; - hdev = (struct hci_dev *) skb->dev; - husb = (struct hci_usb *) hdev->driver_data; - - DBG("%s", hdev->name); + struct _urb *_urb = __get_completed(husb, skb->pkt_type); + struct urb *urb; + int pipe; - if (urb->status) - DBG("%s bulk write status: %d", hdev->name, urb->status); + if (!_urb) { + _urb = _urb_alloc(0, GFP_ATOMIC); + if (!_urb) + return -ENOMEM; + _urb->type = skb->pkt_type; + } - clear_bit(HCI_TX_WRITE, &husb->tx_state); - kfree_skb(skb); + urb = &_urb->urb; + pipe = usb_sndbulkpipe(husb->udev, husb->bulk_out_ep->bEndpointAddress); + FILL_BULK_URB(urb, husb->udev, pipe, skb->data, skb->len, + hci_usb_tx_complete, husb); + urb->transfer_flags = USB_QUEUE_BULK | USB_ZERO_PACKET; - /* Wake up device */ - hci_usb_write_wakeup(husb); + BT_DBG("%s skb %p len %d", husb->hdev.name, skb, skb->len); - return; + _urb->priv = skb; + return __tx_submit(husb, _urb); } -static void hci_usb_intr(struct urb *urb) +#ifdef CONFIG_BLUEZ_HCIUSB_SCO +static inline int hci_usb_send_isoc(struct hci_usb *husb, struct sk_buff *skb) { - struct hci_usb *husb = (struct hci_usb *) urb->context; - unsigned char *data = urb->transfer_buffer; - register int count = urb->actual_length; - register struct sk_buff *skb = husb->intr_skb; - hci_event_hdr *eh; - register int len; + 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; + } - if (!husb) - return; + BT_DBG("%s skb %p len %d", husb->hdev.name, skb, skb->len); - DBG("%s count %d", husb->hdev.name, count); + urb = &_urb->urb; + + urb->context = husb; + urb->dev = husb->udev; + urb->pipe = usb_sndisocpipe(husb->udev, husb->isoc_out_ep->bEndpointAddress); + urb->complete = hci_usb_tx_complete; + urb->transfer_flags = USB_ISO_ASAP; - if (urb->status || !count) { - DBG("%s intr status %d, count %d", husb->hdev.name, urb->status, count); - return; - } + urb->transfer_buffer = skb->data; + urb->transfer_buffer_length = skb->len; + + __fill_isoc_desc(urb, skb->len, husb->isoc_out_ep->wMaxPacketSize); - /* Do we really have to handle continuations here ? */ - if (!skb) { - /* New frame */ - if (count < HCI_EVENT_HDR_SIZE) { - DBG("%s bad frame len %d", husb->hdev.name, count); - return; - } + _urb->priv = skb; + return __tx_submit(husb, _urb); +} +#endif - eh = (hci_event_hdr *) data; - len = eh->plen + HCI_EVENT_HDR_SIZE; +static void hci_usb_tx_process(struct hci_usb *husb) +{ + struct sk_buff_head *q; + struct sk_buff *skb; - if (count > len) { - DBG("%s corrupted frame, len %d", husb->hdev.name, count); - return; + 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); } - /* Allocate skb */ - if (!(skb = bluez_skb_alloc(len, GFP_ATOMIC))) { - ERR("Can't allocate mem for new packet"); - return; +#ifdef CONFIG_BLUEZ_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); } - skb->dev = (void *) &husb->hdev; - skb->pkt_type = HCI_EVENT_PKT; +#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)); +} - husb->intr_skb = skb; - husb->intr_count = len; - } else { - /* Continuation */ - if (count > husb->intr_count) { - ERR("%s bad frame len %d (expected %d)", husb->hdev.name, count, husb->intr_count); +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); +} - kfree_skb(skb); - husb->intr_skb = NULL; - husb->intr_count = 0; - return; - } +/* 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; } - memcpy(skb_put(skb, count), data, count); - husb->intr_count -= count; + if (!test_bit(HCI_RUNNING, &hdev->flags)) + return -EBUSY; - DMP(data, count); + BT_DBG("%s type %d len %d", hdev->name, skb->pkt_type, skb->len); - if (!husb->intr_count) { - /* Got complete frame */ + husb = (struct hci_usb *) hdev->driver_data; - husb->hdev.stat.byte_rx += skb->len; - hci_recv_frame(skb); + switch (skb->pkt_type) { + case HCI_COMMAND_PKT: + hdev->stat.cmd_tx++; + break; - husb->intr_skb = NULL; - } -} + case HCI_ACLDATA_PKT: + hdev->stat.acl_tx++; + break; -static void hci_usb_bulk_read(struct urb *urb) -{ - struct hci_usb *husb = (struct hci_usb *) urb->context; - unsigned char *data = urb->transfer_buffer; - int count = urb->actual_length, status; - struct sk_buff *skb; - hci_acl_hdr *ah; - register __u16 dlen; +#ifdef CONFIG_BLUEZ_HCIUSB_SCO + case HCI_SCODATA_PKT: + hdev->stat.sco_tx++; + break; +#endif - if (!husb) - return; + default: + kfree_skb(skb); + return 0; + } - DBG("%s status %d, count %d, flags %x", husb->hdev.name, urb->status, count, urb->transfer_flags); + read_lock(&husb->completion_lock); - if (urb->status) { - /* Do not re-submit URB on critical errors */ - switch (urb->status) { - case -ENOENT: - return; - default: - goto resubmit; - }; - } - if (!count) - goto resubmit; + skb_queue_tail(__transmit_q(husb, skb->pkt_type), skb); + hci_usb_tx_wakeup(husb); - DMP(data, count); + read_unlock(&husb->completion_lock); + return 0; +} - ah = (hci_acl_hdr *) data; - dlen = le16_to_cpu(ah->dlen); +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); - /* Verify frame len and completeness */ - if ((count - HCI_ACL_HDR_SIZE) != dlen) { - ERR("%s corrupted ACL packet: count %d, plen %d", husb->hdev.name, count, dlen); - goto resubmit; - } + husb->hdev.stat.byte_rx += count; - /* Allocate packet */ - if (!(skb = bluez_skb_alloc(count, GFP_ATOMIC))) { - ERR("Can't allocate mem for new packet"); - goto resubmit; - } + while (count) { + struct sk_buff *skb = __reassembly(husb, type); + struct { int expect; } *scb; + int len = 0; + + if (!skb) { + /* Start of the frame */ - memcpy(skb_put(skb, count), data, count); - skb->dev = (void *) &husb->hdev; - skb->pkt_type = HCI_ACLDATA_PKT; + switch (type) { + case HCI_EVENT_PKT: + if (count >= HCI_EVENT_HDR_SIZE) { + hci_event_hdr *h = data; + len = HCI_EVENT_HDR_SIZE + h->plen; + } else + return -EILSEQ; + break; - husb->hdev.stat.byte_rx += skb->len; + case HCI_ACLDATA_PKT: + if (count >= HCI_ACL_HDR_SIZE) { + hci_acl_hdr *h = data; + len = HCI_ACL_HDR_SIZE + __le16_to_cpu(h->dlen); + } else + return -EILSEQ; + break; +#ifdef CONFIG_BLUEZ_HCIUSB_SCO + case HCI_SCODATA_PKT: + if (count >= HCI_SCO_HDR_SIZE) { + 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 = bluez_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; - hci_recv_frame(skb); + scb = (void *) skb->cb; + scb->expect = len; + } else { + /* Continuation */ + scb = (void *) skb->cb; + len = scb->expect; + } -resubmit: - husb->read_urb->dev = husb->udev; - if ((status = usb_submit_urb(husb->read_urb))) - DBG("%s read URB submit failed %d", husb->hdev.name, status); + len = min(len, count); + + memcpy(skb_put(skb, len), data, len); - DBG("%s read URB re-submited", husb->hdev.name); + scb->expect -= len; + if (!scb->expect) { + /* Complete frame */ + __reassembly(husb, type) = NULL; + hci_recv_frame(skb); + } + + count -= len; data += len; + } + return 0; } -static int hci_usb_ctrl_msg(struct hci_usb *husb, struct sk_buff *skb) +static void hci_usb_rx_complete(struct urb *urb) { - struct urb *urb = husb->ctrl_urb; - devrequest *dr = &husb->dev_req; - int pipe, status; + 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; - DBG("%s len %d", husb->hdev.name, skb->len); + BT_DBG("%s urb %p type %d status %d count %d flags %x", hdev->name, urb, + _urb->type, urb->status, count, urb->transfer_flags); - pipe = usb_sndctrlpipe(husb->udev, 0); + if (!test_bit(HCI_RUNNING, &hdev->flags)) + return; - dr->requesttype = HCI_CTRL_REQ; - dr->request = 0; - dr->index = 0; - dr->value = 0; - dr->length = cpu_to_le16(skb->len); + read_lock(&husb->completion_lock); - FILL_CONTROL_URB(urb, husb->udev, pipe, (void*)dr, skb->data, skb->len, - hci_usb_ctrl, skb); + if (urb->status || !count) + goto resubmit; - if ((status = usb_submit_urb(urb))) { - DBG("%s control URB submit failed %d", husb->hdev.name, status); - return status; + if (_urb->type == HCI_SCODATA_PKT) { +#ifdef CONFIG_BLUEZ_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++; + } } - return 0; +resubmit: + if (_urb->type != HCI_EVENT_PKT) { + urb->dev = husb->udev; + err = usb_submit_urb(urb); + BT_DBG("%s urb %p type %d resubmit status %d", hdev->name, urb, + _urb->type, err); + } + read_unlock(&husb->completion_lock); } -static int hci_usb_write_msg(struct hci_usb *husb, struct sk_buff *skb) +static void hci_usb_tx_complete(struct urb *urb) { - struct urb *urb = husb->write_urb; - int pipe, status; + struct _urb *_urb = container_of(urb, struct _urb, urb); + struct hci_usb *husb = (void *) urb->context; + struct hci_dev *hdev = &husb->hdev; - DBG("%s len %d", husb->hdev.name, skb->len); + BT_DBG("%s urb %p status %d flags %x", hdev->name, urb, + urb->status, urb->transfer_flags); - pipe = usb_sndbulkpipe(husb->udev, husb->bulk_out_ep_addr); + atomic_dec(__pending_tx(husb, _urb->type)); - FILL_BULK_URB(urb, husb->udev, pipe, skb->data, skb->len, - hci_usb_bulk_write, skb); - urb->transfer_flags |= USB_QUEUE_BULK; + urb->transfer_buffer = NULL; + kfree_skb((struct sk_buff *) _urb->priv); - if ((status = usb_submit_urb(urb))) { - DBG("%s write URB submit failed %d", husb->hdev.name, status); - return status; - } + if (!test_bit(HCI_RUNNING, &hdev->flags)) + return; - return 0; + 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_probe(struct usb_device *udev, unsigned int ifnum, const struct usb_device_id *id) +static void hci_usb_destruct(struct hci_dev *hdev) { - struct usb_endpoint_descriptor *bulk_out_ep, *intr_in_ep, *bulk_in_ep; + struct hci_usb *husb = (struct hci_usb *) hdev->driver_data; + + BT_DBG("%s", hdev->name); + + kfree(husb); +} + +static void *hci_usb_probe(struct usb_device *udev, unsigned int ifnum, const struct usb_device_id *id) +{ + struct usb_endpoint_descriptor *bulk_out_ep[HCI_MAX_IFACE_NUM]; + struct usb_endpoint_descriptor *isoc_out_ep[HCI_MAX_IFACE_NUM]; + struct usb_endpoint_descriptor *bulk_in_ep[HCI_MAX_IFACE_NUM]; + struct usb_endpoint_descriptor *isoc_in_ep[HCI_MAX_IFACE_NUM]; + struct usb_endpoint_descriptor *intr_in_ep[HCI_MAX_IFACE_NUM]; struct usb_interface_descriptor *uif; struct usb_endpoint_descriptor *ep; + struct usb_interface *iface, *isoc_iface; struct hci_usb *husb; struct hci_dev *hdev; - int i, size, pipe; - __u8 * buf; - - DBG("udev %p ifnum %d", udev, ifnum); + int i, a, e, size, ifn, isoc_ifnum, isoc_alts; - /* Check device signature */ - if ((udev->descriptor.bDeviceClass != HCI_DEV_CLASS) || - (udev->descriptor.bDeviceSubClass != HCI_DEV_SUBCLASS)|| - (udev->descriptor.bDeviceProtocol != HCI_DEV_PROTOCOL) ) - return NULL; + BT_DBG("udev %p ifnum %d", udev, ifnum); - MOD_INC_USE_COUNT; + iface = &udev->actconfig->interface[0]; - uif = &udev->actconfig->interface[ifnum].altsetting[0]; + /* Check our black list */ + if (usb_match_id(udev, iface, ignore_ids)) + return NULL; - if (uif->bNumEndpoints != 3) { - DBG("Wrong number of endpoints %d", uif->bNumEndpoints); - MOD_DEC_USE_COUNT; + /* Check number of endpoints */ + if (udev->actconfig->interface[ifnum].altsetting[0].bNumEndpoints < 3) return NULL; - } - bulk_out_ep = intr_in_ep = bulk_in_ep = NULL; + memset(bulk_out_ep, 0, sizeof(bulk_out_ep)); + memset(isoc_out_ep, 0, sizeof(isoc_out_ep)); + memset(bulk_in_ep, 0, sizeof(bulk_in_ep)); + memset(isoc_in_ep, 0, sizeof(isoc_in_ep)); + memset(intr_in_ep, 0, sizeof(intr_in_ep)); + size = 0; + isoc_iface = NULL; + isoc_alts = isoc_ifnum = 0; + /* Find endpoints that we need */ - for ( i = 0; i < uif->bNumEndpoints; ++i) { - ep = &uif->endpoint[i]; - switch (ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) { - case USB_ENDPOINT_XFER_BULK: - if (ep->bEndpointAddress & USB_DIR_IN) - bulk_in_ep = ep; - else - bulk_out_ep = ep; - break; - - case USB_ENDPOINT_XFER_INT: - intr_in_ep = ep; - break; - }; - } + ifn = MIN(udev->actconfig->bNumInterfaces, HCI_MAX_IFACE_NUM); + for (i = 0; i < ifn; i++) { + iface = &udev->actconfig->interface[i]; + for (a = 0; a < iface->num_altsetting; a++) { + uif = &iface->altsetting[a]; + for (e = 0; e < uif->bNumEndpoints; e++) { + ep = &uif->endpoint[e]; - if (!bulk_in_ep || !bulk_out_ep || !intr_in_ep) { - DBG("Endpoints not found: %p %p %p", bulk_in_ep, bulk_out_ep, intr_in_ep); - MOD_DEC_USE_COUNT; - return NULL; - } + switch (ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) { + case USB_ENDPOINT_XFER_INT: + if (ep->bEndpointAddress & USB_DIR_IN) + intr_in_ep[i] = ep; + break; - if (!(husb = kmalloc(sizeof(struct hci_usb), GFP_KERNEL))) { - ERR("Can't allocate: control structure"); - MOD_DEC_USE_COUNT; - return NULL; - } + case USB_ENDPOINT_XFER_BULK: + if (ep->bEndpointAddress & USB_DIR_IN) + bulk_in_ep[i] = ep; + else + bulk_out_ep[i] = ep; + break; - memset(husb, 0, sizeof(struct hci_usb)); +#ifdef CONFIG_BLUEZ_HCIUSB_SCO + case USB_ENDPOINT_XFER_ISOC: + if (ep->wMaxPacketSize < size || a > 2) + break; + size = ep->wMaxPacketSize; - husb->udev = udev; - husb->bulk_out_ep_addr = bulk_out_ep->bEndpointAddress; + isoc_iface = iface; + isoc_alts = a; + isoc_ifnum = i; - if (!(husb->ctrl_urb = usb_alloc_urb(0))) { - ERR("Can't allocate: control URB"); - goto probe_error; + if (ep->bEndpointAddress & USB_DIR_IN) + isoc_in_ep[i] = ep; + else + isoc_out_ep[i] = ep; + break; +#endif + } + } + } } - if (!(husb->write_urb = usb_alloc_urb(0))) { - ERR("Can't allocate: write URB"); - goto probe_error; + if (!bulk_in_ep[0] || !bulk_out_ep[0] || !intr_in_ep[0]) { + BT_DBG("Bulk endpoints not found"); + goto done; } - if (!(husb->read_urb = usb_alloc_urb(0))) { - ERR("Can't allocate: read URB"); - goto probe_error; +#ifdef CONFIG_BLUEZ_HCIUSB_SCO + if (!isoc_in_ep[1] || !isoc_out_ep[1]) { + BT_DBG("Isoc endpoints not found"); + isoc_iface = NULL; } +#endif - ep = bulk_in_ep; - pipe = usb_rcvbulkpipe(udev, ep->bEndpointAddress); - size = HCI_MAX_FRAME_SIZE; - - if (!(buf = kmalloc(size, GFP_KERNEL))) { - ERR("Can't allocate: read buffer"); - goto probe_error; + if (!(husb = kmalloc(sizeof(struct hci_usb), GFP_KERNEL))) { + BT_ERR("Can't allocate: control structure"); + goto done; } - FILL_BULK_URB(husb->read_urb, udev, pipe, buf, size, hci_usb_bulk_read, husb); - husb->read_urb->transfer_flags |= USB_QUEUE_BULK; + memset(husb, 0, sizeof(struct hci_usb)); - ep = intr_in_ep; - pipe = usb_rcvintpipe(udev, ep->bEndpointAddress); - size = usb_maxpacket(udev, pipe, usb_pipeout(pipe)); + husb->udev = udev; + husb->bulk_out_ep = bulk_out_ep[0]; + husb->bulk_in_ep = bulk_in_ep[0]; + husb->intr_in_ep = intr_in_ep[0]; - if (!(husb->intr_urb = usb_alloc_urb(0))) { - ERR("Can't allocate: interrupt URB"); - goto probe_error; - } + if (id->driver_info & HCI_DIGIANSWER) + husb->ctrl_req = HCI_DIGI_REQ; + else + husb->ctrl_req = HCI_CTRL_REQ; - if (!(buf = kmalloc(size, GFP_KERNEL))) { - ERR("Can't allocate: interrupt buffer"); - goto probe_error; +#ifdef CONFIG_BLUEZ_HCIUSB_SCO + if (isoc_iface) { + BT_DBG("isoc ifnum %d alts %d", isoc_ifnum, isoc_alts); + if (usb_set_interface(udev, isoc_ifnum, isoc_alts)) { + BT_ERR("Can't set isoc interface settings"); + isoc_iface = NULL; + } + usb_driver_claim_interface(&hci_usb_driver, isoc_iface, husb); + husb->isoc_iface = isoc_iface; + husb->isoc_in_ep = isoc_in_ep[isoc_ifnum]; + husb->isoc_out_ep = isoc_out_ep[isoc_ifnum]; } +#endif + + husb->completion_lock = RW_LOCK_UNLOCKED; - FILL_INT_URB(husb->intr_urb, udev, pipe, buf, size, hci_usb_intr, husb, ep->bInterval); - - skb_queue_head_init(&husb->tx_ctrl_q); - skb_queue_head_init(&husb->tx_write_q); + 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 = &husb->hdev; - hdev->type = HCI_USB; + hdev->type = HCI_USB; hdev->driver_data = husb; hdev->open = hci_usb_open; hdev->close = hci_usb_close; hdev->flush = hci_usb_flush; - hdev->send = hci_usb_send_frame; + hdev->send = hci_usb_send_frame; + hdev->destruct = hci_usb_destruct; if (hci_register_dev(hdev) < 0) { - ERR("Can't register HCI device %s", hdev->name); + BT_ERR("Can't register HCI device"); goto probe_error; } return husb; probe_error: - hci_usb_free_bufs(husb); kfree(husb); - MOD_DEC_USE_COUNT; + +done: return NULL; } @@ -626,38 +943,34 @@ if (!husb) return; - DBG("%s", hdev->name); + BT_DBG("%s", hdev->name); hci_usb_close(hdev); - if (hci_unregister_dev(hdev) < 0) { - ERR("Can't unregister HCI device %s", hdev->name); - } - - hci_usb_free_bufs(husb); - kfree(husb); + if (husb->isoc_iface) + usb_driver_release_interface(&hci_usb_driver, husb->isoc_iface); - MOD_DEC_USE_COUNT; + if (hci_unregister_dev(hdev) < 0) + BT_ERR("Can't unregister HCI device %s", hdev->name); } -static struct usb_driver hci_usb_driver = -{ +static struct usb_driver hci_usb_driver = { name: "hci_usb", probe: hci_usb_probe, disconnect: hci_usb_disconnect, - id_table: usb_bluetooth_ids, + id_table: bluetooth_ids, }; int hci_usb_init(void) { int err; - INF("BlueZ HCI USB driver ver %s Copyright (C) 2000,2001 Qualcomm Inc", + BT_INFO("BlueZ HCI USB driver ver %s Copyright (C) 2000,2001 Qualcomm Inc", VERSION); - INF("Written 2000,2001 by Maxim Krasnyansky "); + BT_INFO("Written 2000,2001 by Maxim Krasnyansky "); if ((err = usb_register(&hci_usb_driver)) < 0) - ERR("Failed to register HCI USB driver"); + BT_ERR("Failed to register HCI USB driver"); return err; } --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/drivers/bluetooth/hci_usb.h 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1,144 @@ +/* + HCI USB driver for Linux Bluetooth protocol stack (BlueZ) + Copyright (C) 2000-2001 Qualcomm Incorporated + Written 2000,2001 by Maxim Krasnyansky + + Copyright (C) 2003 Maxim Krasnyansky + + 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_usb.h,v 1.2 2002/03/18 19:10:04 maxk Exp $ + */ + +#ifdef __KERNEL__ + +/* 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_CTRL_REQ 0x20 +#define HCI_DIGI_REQ 0x40 + +#define HCI_DIGIANSWER 0x01 + +#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; +}; + +struct _urb *_urb_alloc(int isoc, int gfp); + +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 _urb *_urb_dequeue(struct _urb_queue *q); + +#ifndef container_of +#define container_of(ptr, type, member) ({ \ + const typeof( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - offsetof(type,member) );}) +#endif + +struct hci_usb { + struct hci_dev hdev; + + unsigned long state; + + struct usb_device *udev; + + struct usb_endpoint_descriptor *bulk_in_ep; + struct usb_endpoint_descriptor *bulk_out_ep; + struct usb_endpoint_descriptor *intr_in_ep; + + struct usb_interface *isoc_iface; + struct usb_endpoint_descriptor *isoc_out_ep; + struct usb_endpoint_descriptor *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 + +#endif /* __KERNEL__ */ --- linux/drivers/bluetooth/hci_vhci.c~bluetooth-2.4.18-mh11 2001-09-07 18:28:38.000000000 +0200 +++ linux/drivers/bluetooth/hci_vhci.c 2004-01-25 23:37:39.000000000 +0100 @@ -25,9 +25,9 @@ /* * BlueZ HCI virtual device driver. * - * $Id: hci_vhci.c,v 1.3 2001/08/03 04:19:50 maxk Exp $ + * $Id: hci_vhci.c,v 1.3 2002/04/17 17:37:20 maxk Exp $ */ -#define VERSION "1.0" +#define VERSION "1.1" #include #include @@ -49,43 +49,56 @@ #include #include -#include #include -#include +#include "hci_vhci.h" /* HCI device part */ -int hci_vhci_open(struct hci_dev *hdev) +static int hci_vhci_open(struct hci_dev *hdev) { - hdev->flags |= HCI_RUNNING; + set_bit(HCI_RUNNING, &hdev->flags); return 0; } -int hci_vhci_flush(struct hci_dev *hdev) +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; } -int hci_vhci_close(struct hci_dev *hdev) +static int hci_vhci_close(struct hci_dev *hdev) { - hdev->flags &= ~HCI_RUNNING; + if (!test_and_clear_bit(HCI_RUNNING, &hdev->flags)) + return 0; + hci_vhci_flush(hdev); return 0; } -int hci_vhci_send_frame(struct sk_buff *skb) +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); + + MOD_DEC_USE_COUNT; +} + +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) { - ERR("Frame for uknown device (hdev=NULL)"); + BT_ERR("Frame for uknown device (hdev=NULL)"); return -ENODEV; } - if (!(hdev->flags & HCI_RUNNING)) + if (!test_bit(HCI_RUNNING, &hdev->flags)) return -EBUSY; hci_vhci = (struct hci_vhci_struct *) hdev->driver_data; @@ -188,7 +201,7 @@ add_wait_queue(&hci_vhci->read_wait, &wait); while (count) { - current->state = TASK_INTERRUPTIBLE; + set_current_state(TASK_INTERRUPTIBLE); /* Read frames from device queue */ if (!(skb = skb_dequeue(&hci_vhci->readq))) { @@ -214,8 +227,7 @@ kfree_skb(skb); break; } - - current->state = TASK_RUNNING; + set_current_state(TASK_RUNNING); remove_wait_queue(&hci_vhci->read_wait, &wait); return ret; @@ -270,11 +282,13 @@ hdev->close = hci_vhci_close; hdev->flush = hci_vhci_flush; hdev->send = hci_vhci_send_frame; + hdev->destruct = hci_vhci_destruct; if (hci_register_dev(hdev) < 0) { kfree(hci_vhci); return -EBUSY; } + MOD_INC_USE_COUNT; file->private_data = hci_vhci; return 0; @@ -285,12 +299,10 @@ struct hci_vhci_struct *hci_vhci = (struct hci_vhci_struct *) file->private_data; if (hci_unregister_dev(&hci_vhci->hdev) < 0) { - ERR("Can't unregister HCI device %s", hci_vhci->hdev.name); + BT_ERR("Can't unregister HCI device %s", hci_vhci->hdev.name); } - kfree(hci_vhci); file->private_data = NULL; - return 0; } @@ -315,12 +327,12 @@ int __init hci_vhci_init(void) { - INF("BlueZ VHCI driver ver %s Copyright (C) 2000,2001 Qualcomm Inc", + BT_INFO("BlueZ VHCI driver ver %s Copyright (C) 2000,2001 Qualcomm Inc", VERSION); - INF("Written 2000,2001 by Maxim Krasnyansky "); + BT_INFO("Written 2000,2001 by Maxim Krasnyansky "); if (misc_register(&hci_vhci_miscdev)) { - ERR("Can't register misc device %d\n", VHCI_MINOR); + BT_ERR("Can't register misc device %d\n", VHCI_MINOR); return -EIO; } @@ -337,4 +349,4 @@ MODULE_AUTHOR("Maxim Krasnyansky "); MODULE_DESCRIPTION("BlueZ VHCI driver ver " VERSION); -MODULE_LICENSE("GPL"); +MODULE_LICENSE("GPL"); --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/drivers/bluetooth/hci_vhci.h 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1,50 @@ +/* + BlueZ - Bluetooth protocol stack for Linux + Copyright (C) 2000-2001 Qualcomm Incorporated + + Written 2000,2001 by Maxim Krasnyansky + + 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 */ --- linux/drivers/bluetooth/Makefile~bluetooth-2.4.18-mh11 2001-09-07 18:28:38.000000000 +0200 +++ linux/drivers/bluetooth/Makefile 2004-01-25 23:37:39.000000000 +0100 @@ -1,11 +1,27 @@ # -# Makefile for Bluetooth HCI device drivers. +# Makefile for the Linux Bluetooth HCI device drivers # O_TARGET := bluetooth.o +list-multi := hci_uart.o + obj-$(CONFIG_BLUEZ_HCIUSB) += hci_usb.o -obj-$(CONFIG_BLUEZ_HCIUART) += hci_uart.o obj-$(CONFIG_BLUEZ_HCIVHCI) += hci_vhci.o +obj-$(CONFIG_BLUEZ_HCIUART) += hci_uart.o +uart-y := hci_ldisc.o +uart-$(CONFIG_BLUEZ_HCIUART_H4) += hci_h4.o +uart-$(CONFIG_BLUEZ_HCIUART_BCSP) += hci_bcsp.o + +obj-$(CONFIG_BLUEZ_HCIBFUSB) += bfusb.o + +obj-$(CONFIG_BLUEZ_HCIDTL1) += dtl1_cs.o +obj-$(CONFIG_BLUEZ_HCIBT3C) += bt3c_cs.o +obj-$(CONFIG_BLUEZ_HCIBLUECARD) += bluecard_cs.o +obj-$(CONFIG_BLUEZ_HCIBTUART) += btuart_cs.o + include $(TOPDIR)/Rules.make + +hci_uart.o: $(uart-y) + $(LD) -r -o $@ $(uart-y) --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/drivers/bluetooth/Makefile.lib 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1 @@ +obj-$(CONFIG_BLUEZ_HCIBFUSB) += firmware_class.o --- linux/drivers/char/pcmcia/serial_cs.c~bluetooth-2.4.18-mh11 2004-01-25 23:28:22.000000000 +0100 +++ linux/drivers/char/pcmcia/serial_cs.c 2004-01-25 23:37:39.000000000 +0100 @@ -72,14 +72,14 @@ static int irq_list[4] = { -1 }; MODULE_PARM(irq_list, "1-4i"); -/* Enable the speaker? */ -INT_MODULE_PARM(do_sound, 1); +INT_MODULE_PARM(do_sound, 1); /* Enable the speaker? */ +INT_MODULE_PARM(buggy_uart, 0); /* Skip strict UART tests? */ #ifdef PCMCIA_DEBUG INT_MODULE_PARM(pc_debug, PCMCIA_DEBUG); #define DEBUG(n, args...) if (pc_debug>(n)) printk(KERN_DEBUG args) static char *version = -"serial_cs.c 1.128 2001/10/18 12:18:35 (David Hinds)"; +"serial_cs.c 1.138 2002/10/25 06:24:52 (David Hinds)"; #else #define DEBUG(n, args...) #endif @@ -98,6 +98,7 @@ { MANFID_OMEGA, PRODID_OMEGA_QSP_100, 4 }, { MANFID_QUATECH, PRODID_QUATECH_DUAL_RS232, 2 }, { MANFID_QUATECH, PRODID_QUATECH_DUAL_RS232_D1, 2 }, + { MANFID_QUATECH, PRODID_QUATECH_DUAL_RS232_D2, 2 }, { MANFID_QUATECH, PRODID_QUATECH_QUAD_RS232, 4 }, { MANFID_QUATECH, PRODID_QUATECH_DUAL_RS422, 2 }, { MANFID_QUATECH, PRODID_QUATECH_QUAD_RS422, 4 }, @@ -151,7 +152,7 @@ client_reg_t client_reg; dev_link_t *link; int i, ret; - + DEBUG(0, "serial_attach()\n"); /* Create new serial device */ @@ -163,7 +164,7 @@ link->release.function = &serial_release; link->release.data = (u_long)link; link->io.Attributes1 = IO_DATA_PATH_WIDTH_8; - link->io.NumPorts1 = 8; + link->io.Attributes2 = IO_DATA_PATH_WIDTH_8; link->irq.Attributes = IRQ_TYPE_EXCLUSIVE; link->irq.IRQInfo1 = IRQ_INFO2_VALID|IRQ_LEVEL_ID; if (irq_list[0] == -1) @@ -172,13 +173,12 @@ for (i = 0; i < 4; i++) link->irq.IRQInfo2 |= 1 << irq_list[i]; link->conf.Attributes = CONF_ENABLE_IRQ; - link->conf.Vcc = 50; if (do_sound) { link->conf.Attributes |= CONF_ENABLE_SPKR; link->conf.Status = CCSR_AUDIO_ENA; } link->conf.IntType = INT_MEMORY_AND_IO; - + /* Register with Card Services */ link->next = dev_list; dev_list = link; @@ -197,7 +197,7 @@ serial_detach(link); return NULL; } - + return link; } /* serial_attach */ @@ -217,7 +217,7 @@ int ret; DEBUG(0, "serial_detach(0x%p)\n", link); - + /* Locate device structure */ for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next) if (*linkp == link) break; @@ -227,17 +227,17 @@ del_timer(&link->release); if (link->state & DEV_CONFIG) serial_release((u_long)link); - + if (link->handle) { ret = CardServices(DeregisterClient, link->handle); if (ret != CS_SUCCESS) cs_error(link->handle, DeregisterClient, ret); } - + /* Unlink device structure, free bits */ *linkp = link->next; kfree(info); - + } /* serial_detach */ /*====================================================================*/ @@ -246,18 +246,20 @@ { struct serial_struct serial; int line; - + memset(&serial, 0, sizeof(serial)); serial.port = port; serial.irq = irq; serial.flags = ASYNC_SKIP_TEST | ASYNC_SHARE_IRQ; + if (buggy_uart) + serial.flags |= ASYNC_BUGGY_UART; line = register_serial(&serial); if (line < 0) { printk(KERN_NOTICE "serial_cs: register_serial() at 0x%04lx," " irq %d failed\n", (u_long)serial.port, serial.irq); return -1; } - + info->line[info->ndev] = line; sprintf(info->node[info->ndev].dev_name, "ttyS%d", line); info->node[info->ndev].major = TTY_MAJOR; @@ -265,7 +267,7 @@ if (info->ndev > 0) info->node[info->ndev-1].next = &info->node[info->ndev]; info->ndev++; - + return 0; } @@ -316,7 +318,10 @@ return setup_serial(info, port, config.AssignedIRQ); } link->conf.Vcc = config.Vcc; - + + link->io.NumPorts1 = 8; + link->io.NumPorts2 = 0; + /* First pass: look for a config entry that looks normal. */ tuple.TupleData = (cisdata_t *)buf; tuple.TupleOffset = 0; tuple.TupleDataMax = 255; @@ -343,7 +348,7 @@ 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. */ @@ -355,8 +360,7 @@ for (j = 0; j < 5; j++) { link->io.BasePort1 = base[j]; link->io.IOAddrLines = base[j] ? 16 : 3; - i = CardServices(RequestIO, link->handle, - &link->io); + i = CardServices(RequestIO, link->handle, &link->io); if (i == CS_SUCCESS) goto found_port; } } @@ -368,7 +372,7 @@ cs_error(link->handle, RequestIO, i); return -1; } - + i = CardServices(RequestIRQ, link->handle, &link->irq); if (i != CS_SUCCESS) { cs_error(link->handle, RequestIRQ, i); @@ -393,8 +397,12 @@ u_char buf[256]; cisparse_t parse; cistpl_cftable_entry_t *cf = &parse.cftable_entry; + config_info_t config; int i, base2 = 0; + CardServices(GetConfigurationInfo, handle, &config); + link->conf.Vcc = config.Vcc; + tuple.TupleData = (cisdata_t *)buf; tuple.TupleOffset = 0; tuple.TupleDataMax = 255; tuple.Attributes = 0; @@ -436,12 +444,12 @@ i = next_tuple(handle, &tuple, &parse); } } - + if (i != CS_SUCCESS) { - cs_error(link->handle, RequestIO, i); - return -1; + /* At worst, try to configure as a single port */ + return simple_config(link); } - + i = CardServices(RequestIRQ, link->handle, &link->irq); if (i != CS_SUCCESS) { cs_error(link->handle, RequestIRQ, i); @@ -457,14 +465,27 @@ cs_error(link->handle, RequestConfiguration, i); return -1; } - + + /* The Oxford Semiconductor OXCF950 cards are in fact single-port: + 8 registers are for the UART, the others are extra registers */ + if (info->manfid == MANFID_OXSEMI) { + if (cf->index == 1 || cf->index == 3) { + setup_serial(info, base2, link->irq.AssignedIRQ); + outb(12,link->io.BasePort1+1); + } else { + setup_serial(info, link->io.BasePort1, link->irq.AssignedIRQ); + outb(12,base2+1); + } + return 0; + } + setup_serial(info, link->io.BasePort1, link->irq.AssignedIRQ); /* The Nokia cards are not really multiport cards */ if (info->manfid == MANFID_NOKIA) return 0; for (i = 0; i < info->multi-1; i++) setup_serial(info, base2+(8*i), link->irq.AssignedIRQ); - + return 0; } @@ -490,7 +511,7 @@ int i, last_ret, last_fn; DEBUG(0, "serial_config(0x%p)\n", link); - + tuple.TupleData = (cisdata_t *)buf; tuple.TupleOffset = 0; tuple.TupleDataMax = 255; tuple.Attributes = 0; @@ -525,7 +546,7 @@ } link->conf.ConfigBase = parse.config.base; link->conf.Present = parse.config.rmask[0]; - + /* Configure card */ link->state |= DEV_CONFIG; @@ -533,8 +554,8 @@ tuple.DesiredTuple = CISTPL_LONGLINK_MFC; tuple.Attributes = TUPLE_RETURN_COMMON | TUPLE_RETURN_LINK; info->multi = (first_tuple(handle, &tuple, &parse) == CS_SUCCESS); - - /* Is this a multiport card? */ + + /* Scan list of known multiport card ID's */ tuple.DesiredTuple = CISTPL_MANFID; if (first_tuple(handle, &tuple, &parse) == CS_SUCCESS) { info->manfid = le16_to_cpu(buf[0]); @@ -589,15 +610,15 @@ } #endif } - + if (info->multi > 1) multi_config(link); else simple_config(link); - + if (info->ndev == 0) goto failed; - + if (info->manfid == MANFID_IBM) { conf_reg_t reg = { 0, CS_READ, 0x800, 0 }; CS_CHECK(AccessConfigurationRegister, link->handle, ®); @@ -614,6 +635,7 @@ cs_error(link->handle, last_fn, last_ret); failed: serial_release((u_long)link); + link->state &= ~DEV_CONFIG_PENDING; } /* serial_config */ @@ -621,7 +643,7 @@ After a card is removed, serial_release() will unregister the net device, and release the PCMCIA configuration. - + ======================================================================*/ void serial_release(u_long arg) @@ -629,7 +651,7 @@ dev_link_t *link = (dev_link_t *)arg; serial_info_t *info = link->priv; int i; - + DEBUG(0, "serial_release(0x%p)\n", link); for (i = 0; i < info->ndev; i++) { @@ -642,7 +664,7 @@ CardServices(ReleaseIO, link->handle, &link->io); CardServices(ReleaseIRQ, link->handle, &link->irq); } - + link->state &= ~DEV_CONFIG; } /* serial_release */ @@ -653,7 +675,7 @@ stuff to run after an event is received. A CARD_REMOVAL event also sets some flags to discourage the serial drivers from talking to the ports. - + ======================================================================*/ static int serial_event(event_t event, int priority, @@ -661,9 +683,9 @@ { dev_link_t *link = args->client_data; serial_info_t *info = link->priv; - + DEBUG(1, "serial_event(0x%06x)\n", event); - + switch (event) { case CS_EVENT_CARD_REMOVAL: link->state &= ~DEV_PRESENT; @@ -702,7 +724,7 @@ if (serv.Revision != CS_RELEASE_CODE) { printk(KERN_NOTICE "serial_cs: Card Services release " "does not match!\n"); - return -1; + return -EINVAL; } register_pccard_driver(&dev_info, &serial_attach, &serial_detach); return 0; --- linux/drivers/input/Config.in~bluetooth-2.4.18-mh11 2001-09-13 00:34:06.000000000 +0200 +++ linux/drivers/input/Config.in 2004-01-25 23:37:39.000000000 +0100 @@ -14,5 +14,6 @@ fi dep_tristate ' Joystick support' CONFIG_INPUT_JOYDEV $CONFIG_INPUT dep_tristate ' Event interface support' CONFIG_INPUT_EVDEV $CONFIG_INPUT +dep_tristate ' User level driver support' CONFIG_INPUT_UINPUT $CONFIG_INPUT endmenu --- linux/drivers/input/keybdev.c~bluetooth-2.4.18-mh11 2001-10-11 18:14:32.000000000 +0200 +++ linux/drivers/input/keybdev.c 2004-01-25 23:37:39.000000000 +0100 @@ -154,16 +154,18 @@ static struct input_handler keybdev_handler; +static unsigned int ledstate = 0xff; + void keybdev_ledfunc(unsigned int led) { struct input_handle *handle; - for (handle = keybdev_handler.handle; handle; handle = handle->hnext) { + ledstate = led; + for (handle = keybdev_handler.handle; handle; handle = handle->hnext) { input_event(handle->dev, EV_LED, LED_SCROLLL, !!(led & 0x01)); input_event(handle->dev, EV_LED, LED_NUML, !!(led & 0x02)); input_event(handle->dev, EV_LED, LED_CAPSL, !!(led & 0x04)); - } } @@ -202,6 +204,12 @@ // printk(KERN_INFO "keybdev.c: Adding keyboard: input%d\n", dev->number); + if (ledstate != 0xff) { + input_event(dev, EV_LED, LED_SCROLLL, !!(ledstate & 0x01)); + input_event(dev, EV_LED, LED_NUML, !!(ledstate & 0x02)); + input_event(dev, EV_LED, LED_CAPSL, !!(ledstate & 0x04)); + } + return handle; } --- linux/drivers/input/Makefile~bluetooth-2.4.18-mh11 2000-12-29 23:07:22.000000000 +0100 +++ linux/drivers/input/Makefile 2004-01-25 23:37:39.000000000 +0100 @@ -24,6 +24,7 @@ obj-$(CONFIG_INPUT_MOUSEDEV) += mousedev.o obj-$(CONFIG_INPUT_JOYDEV) += joydev.o obj-$(CONFIG_INPUT_EVDEV) += evdev.o +obj-$(CONFIG_INPUT_UINPUT) += uinput.o # The global Rules.make. --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/drivers/input/uinput.c 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1,428 @@ +/* + * User level driver support for input subsystem + * + * Heavily based on evdev.c by Vojtech Pavlik + * + * 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 + * + * Author: Aristeu Sergio Rozanski Filho + * + * Changes/Revisions: + * 0.1 20/06/2002 + * - first public version + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int uinput_dev_open(struct input_dev *dev) +{ + return 0; +} + +static void uinput_dev_close(struct input_dev *dev) +{ +} + +static int uinput_dev_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) +{ + struct uinput_device *udev; + + udev = (struct uinput_device *)dev->private; + + udev->buff[udev->head].type = type; + udev->buff[udev->head].code = code; + udev->buff[udev->head].value = value; + do_gettimeofday(&udev->buff[udev->head].time); + udev->head = (udev->head + 1) % UINPUT_BUFFER_SIZE; + + wake_up_interruptible(&udev->waitq); + + return 0; +} + +static int uinput_dev_upload_effect(struct input_dev *dev, struct ff_effect *effect) +{ + return 0; +} + +static int uinput_dev_erase_effect(struct input_dev *dev, int effect_id) +{ + return 0; +} + +static int uinput_create_device(struct uinput_device *udev) +{ + if (!udev->dev->name) { + printk(KERN_DEBUG "%s: write device info first\n", UINPUT_NAME); + return -EINVAL; + } + + udev->dev->open = uinput_dev_open; + udev->dev->close = uinput_dev_close; + udev->dev->event = uinput_dev_event; + udev->dev->upload_effect = uinput_dev_upload_effect; + udev->dev->erase_effect = uinput_dev_erase_effect; + udev->dev->private = udev; + + init_waitqueue_head(&(udev->waitq)); + + input_register_device(udev->dev); + + set_bit(UIST_CREATED, &(udev->state)); + + return 0; +} + +static int uinput_destroy_device(struct uinput_device *udev) +{ + if (!test_bit(UIST_CREATED, &(udev->state))) { + printk(KERN_WARNING "%s: create the device first\n", UINPUT_NAME); + return -EINVAL; + } + + input_unregister_device(udev->dev); + + clear_bit(UIST_CREATED, &(udev->state)); + + return 0; +} + +static int uinput_open(struct inode *inode, struct file *file) +{ + struct uinput_device *newdev; + struct input_dev *newinput; + + newdev = kmalloc(sizeof(struct uinput_device), GFP_KERNEL); + if (!newdev) + goto error; + memset(newdev, 0, sizeof(struct uinput_device)); + + newinput = kmalloc(sizeof(struct input_dev), GFP_KERNEL); + if (!newinput) + goto cleanup; + memset(newinput, 0, sizeof(struct input_dev)); + + newdev->dev = newinput; + + file->private_data = newdev; + + return 0; +cleanup: + kfree(newdev); +error: + return -ENOMEM; +} + +static int uinput_validate_absbits(struct input_dev *dev) +{ + unsigned int cnt; + int retval = 0; + + for (cnt = 0; cnt < ABS_MAX; cnt++) { + if (!test_bit(cnt, dev->absbit)) + continue; + + if (/*!dev->absmin[cnt] || !dev->absmax[cnt] || */ + (dev->absmax[cnt] <= dev->absmin[cnt])) { + printk(KERN_DEBUG + "%s: invalid abs[%02x] min:%d max:%d\n", + UINPUT_NAME, cnt, + dev->absmin[cnt], dev->absmax[cnt]); + retval = -EINVAL; + break; + } + + if ((dev->absflat[cnt] < dev->absmin[cnt]) || + (dev->absflat[cnt] > dev->absmax[cnt])) { + printk(KERN_DEBUG + "%s: absflat[%02x] out of range: %d " + "(min:%d/max:%d)\n", + UINPUT_NAME, cnt, dev->absflat[cnt], + dev->absmin[cnt], dev->absmax[cnt]); + retval = -EINVAL; + break; + } + } + return retval; +} + +static int uinput_alloc_device(struct file *file, const char *buffer, size_t count) +{ + struct uinput_user_dev *user_dev; + struct input_dev *dev; + struct uinput_device *udev; + int size, + retval; + + retval = count; + + udev = (struct uinput_device *)file->private_data; + dev = udev->dev; + + user_dev = kmalloc(sizeof(*user_dev), GFP_KERNEL); + if (!user_dev) { + retval = -ENOMEM; + goto exit; + } + + if (copy_from_user(user_dev, buffer, sizeof(struct uinput_user_dev))) { + retval = -EFAULT; + goto exit; + } + + if (NULL != dev->name) + kfree(dev->name); + + size = strnlen(user_dev->name, UINPUT_MAX_NAME_SIZE) + 1; + dev->name = kmalloc(size, GFP_KERNEL); + if (!dev->name) { + retval = -ENOMEM; + goto exit; + } + + strncpy(dev->name, user_dev->name, size); + dev->idbus = user_dev->idbus; + dev->idvendor = user_dev->idvendor; + dev->idproduct = user_dev->idproduct; + dev->idversion = user_dev->idversion; + dev->ff_effects_max = user_dev->ff_effects_max; + + size = sizeof(int) * (ABS_MAX + 1); + memcpy(dev->absmax, user_dev->absmax, size); + memcpy(dev->absmin, user_dev->absmin, size); + memcpy(dev->absfuzz, user_dev->absfuzz, size); + memcpy(dev->absflat, user_dev->absflat, size); + + /* check if absmin/absmax/absfuzz/absflat are filled as + * told in Documentation/input/input-programming.txt */ + if (test_bit(EV_ABS, dev->evbit)) { + retval = uinput_validate_absbits(dev); + if (retval < 0) + kfree(dev->name); + } + +exit: + kfree(user_dev); + return retval; +} + +static ssize_t uinput_write(struct file *file, const char *buffer, size_t count, loff_t *ppos) +{ + struct uinput_device *udev = file->private_data; + + if (test_bit(UIST_CREATED, &(udev->state))) { + struct input_event ev; + + if (copy_from_user(&ev, buffer, sizeof(struct input_event))) + return -EFAULT; + input_event(udev->dev, ev.type, ev.code, ev.value); + } + else + count = uinput_alloc_device(file, buffer, count); + + return count; +} + +static ssize_t uinput_read(struct file *file, char *buffer, size_t count, loff_t *ppos) +{ + struct uinput_device *udev = file->private_data; + int retval = 0; + + if (!test_bit(UIST_CREATED, &(udev->state))) + return -ENODEV; + + if ((udev->head == udev->tail) && (file->f_flags & O_NONBLOCK)) + return -EAGAIN; + + retval = wait_event_interruptible(udev->waitq, + (udev->head != udev->tail) || + !test_bit(UIST_CREATED, &(udev->state))); + + if (retval) + return retval; + + if (!test_bit(UIST_CREATED, &(udev->state))) + return -ENODEV; + + while ((udev->head != udev->tail) && + (retval + sizeof(struct input_event) <= count)) { + if (copy_to_user(buffer + retval, &(udev->buff[udev->tail]), + sizeof(struct input_event))) return -EFAULT; + udev->tail = (udev->tail + 1) % UINPUT_BUFFER_SIZE; + retval += sizeof(struct input_event); + } + + return retval; +} + +static unsigned int uinput_poll(struct file *file, poll_table *wait) +{ + struct uinput_device *udev = file->private_data; + + poll_wait(file, &udev->waitq, wait); + + if (udev->head != udev->tail) + return POLLIN | POLLRDNORM; + + return 0; +} + +static int uinput_burn_device(struct uinput_device *udev) +{ + if (test_bit(UIST_CREATED, &(udev->state))) + uinput_destroy_device(udev); + + kfree(udev->dev); + kfree(udev); + + return 0; +} + +static int uinput_close(struct inode *inode, struct file *file) +{ + return uinput_burn_device((struct uinput_device *)file->private_data); +} + +static int uinput_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + int retval = 0; + struct uinput_device *udev; + + udev = (struct uinput_device *)file->private_data; + + /* device attributes can not be changed after the device is created */ + if (cmd >= UI_SET_EVBIT && test_bit(UIST_CREATED, &(udev->state))) + return -EINVAL; + + switch (cmd) { + case UI_DEV_CREATE: + retval = uinput_create_device(udev); + break; + + case UI_DEV_DESTROY: + retval = uinput_destroy_device(udev); + break; + + case UI_SET_EVBIT: + if (arg > EV_MAX) { + retval = -EINVAL; + break; + } + set_bit(arg, udev->dev->evbit); + break; + + case UI_SET_KEYBIT: + if (arg > KEY_MAX) { + retval = -EINVAL; + break; + } + set_bit(arg, udev->dev->keybit); + break; + + case UI_SET_RELBIT: + if (arg > REL_MAX) { + retval = -EINVAL; + break; + } + set_bit(arg, udev->dev->relbit); + break; + + case UI_SET_ABSBIT: + if (arg > ABS_MAX) { + retval = -EINVAL; + break; + } + set_bit(arg, udev->dev->absbit); + break; + + case UI_SET_MSCBIT: + if (arg > MSC_MAX) { + retval = -EINVAL; + break; + } + set_bit(arg, udev->dev->mscbit); + break; + + case UI_SET_LEDBIT: + if (arg > LED_MAX) { + retval = -EINVAL; + break; + } + set_bit(arg, udev->dev->ledbit); + break; + + case UI_SET_SNDBIT: + if (arg > SND_MAX) { + retval = -EINVAL; + break; + } + set_bit(arg, udev->dev->sndbit); + break; + + case UI_SET_FFBIT: + if (arg > FF_MAX) { + retval = -EINVAL; + break; + } + set_bit(arg, udev->dev->ffbit); + break; + + default: + retval = -EFAULT; + } + return retval; +} + +struct file_operations uinput_fops = { + owner: THIS_MODULE, + open: uinput_open, + release: uinput_close, + read: uinput_read, + write: uinput_write, + poll: uinput_poll, + ioctl: uinput_ioctl, +}; + +static struct miscdevice uinput_misc = { + fops: &uinput_fops, + minor: UINPUT_MINOR, + name: UINPUT_NAME, +}; + +static int __init uinput_init(void) +{ + return misc_register(&uinput_misc); +} + +static void __exit uinput_exit(void) +{ + misc_deregister(&uinput_misc); +} + +MODULE_AUTHOR("Aristeu Sergio Rozanski Filho"); +MODULE_DESCRIPTION("User level driver support for input subsystem"); +MODULE_LICENSE("GPL"); + +module_init(uinput_init); +module_exit(uinput_exit); + --- linux/drivers/isdn/avmb1/capidrv.c~bluetooth-2.4.18-mh11 2001-12-21 18:41:54.000000000 +0100 +++ linux/drivers/isdn/avmb1/capidrv.c 2004-01-25 23:37:39.000000000 +0100 @@ -514,13 +514,25 @@ static void send_message(capidrv_contr * card, _cmsg * cmsg) { - struct sk_buff *skb; - size_t len; + struct sk_buff *skb; + size_t len; + u16 err; + capi_cmsg2message(cmsg, cmsg->buf); len = CAPIMSG_LEN(cmsg->buf); skb = alloc_skb(len, GFP_ATOMIC); + if(!skb) { + printk(KERN_ERR "no skb len(%d) memory\n", len); + return; + } memcpy(skb_put(skb, len), cmsg->buf, len); - (*capifuncs->capi_put_message) (global.appid, skb); + err = (*capifuncs->capi_put_message) (global.appid, skb); + if (err) { + printk(KERN_WARNING "%s: capi_put_message error: %04x\n", + __FUNCTION__, err); + kfree_skb(skb); + return; + } global.nsentctlpkt++; } @@ -2179,10 +2191,10 @@ free_ncci(card, card->bchans[card->nbchan-1].nccip); if (card->bchans[card->nbchan-1].plcip) free_plci(card, card->bchans[card->nbchan-1].plcip); - if (card->plci_list) - printk(KERN_ERR "capidrv: bug in free_plci()\n"); card->nbchan--; } + if (card->plci_list) + printk(KERN_ERR "capidrv: bug in free_plci()\n"); kfree(card->bchans); card->bchans = 0; --- linux/drivers/isdn/avmb1/kcapi.c~bluetooth-2.4.18-mh11 2001-12-21 18:41:54.000000000 +0100 +++ linux/drivers/isdn/avmb1/kcapi.c 2004-01-25 23:37:39.000000000 +0100 @@ -545,7 +545,13 @@ static void notify_up(__u32 contr) { struct capi_interface_user *p; + __u16 appl; + for (appl = 1; appl <= CAPI_MAXAPPL; appl++) { + if (!VALID_APPLID(appl)) continue; + if (APPL(appl)->releasing) continue; + CARD(contr)->driver->register_appl(CARD(contr), appl, &APPL(appl)->rparam); + } printk(KERN_NOTICE "kcapi: notify up contr %d\n", contr); spin_lock(&capi_users_lock); for (p = capi_users; p; p = p->next) { @@ -705,12 +711,16 @@ nextpp = &(*pp)->next; } } - APPL(appl)->releasing--; - if (APPL(appl)->releasing <= 0) { - APPL(appl)->signal = 0; - APPL_MARK_FREE(appl); - printk(KERN_INFO "kcapi: appl %d down\n", appl); - } + if (APPL(appl)->releasing) { /* only release if the application was marked for release */ + printk(KERN_DEBUG "kcapi: appl %d releasing(%d)\n", appl, APPL(appl)->releasing); + APPL(appl)->releasing--; + if (APPL(appl)->releasing <= 0) { + APPL(appl)->signal = 0; + APPL_MARK_FREE(appl); + printk(KERN_INFO "kcapi: appl %d down\n", appl); + } + } else + printk(KERN_WARNING "kcapi: appl %d card%d released without request\n", appl, card->cnr); } /* * ncci management @@ -863,16 +873,7 @@ static void controllercb_ready(struct capi_ctr * card) { - __u16 appl; - card->cardstate = CARD_RUNNING; - - for (appl = 1; appl <= CAPI_MAXAPPL; appl++) { - if (!VALID_APPLID(appl)) continue; - if (APPL(appl)->releasing) continue; - card->driver->register_appl(card, appl, &APPL(appl)->rparam); - } - printk(KERN_NOTICE "kcapi: card %d \"%s\" ready.\n", CARDNR(card), card->name); --- linux/drivers/usb/Config.in~bluetooth-2.4.18-mh11 2004-01-25 23:28:22.000000000 +0100 +++ linux/drivers/usb/Config.in 2004-01-25 23:37:39.000000000 +0100 @@ -39,7 +39,13 @@ comment 'USB Device Class drivers' dep_tristate ' USB Audio support' CONFIG_USB_AUDIO $CONFIG_USB $CONFIG_SOUND -dep_tristate ' USB Bluetooth support (EXPERIMENTAL)' CONFIG_USB_BLUETOOTH $CONFIG_USB $CONFIG_EXPERIMENTAL +if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then + if [ "$CONFIG_BLUEZ" = "n" ]; then + dep_tristate ' USB Bluetooth support (EXPERIMENTAL)' CONFIG_USB_BLUETOOTH $CONFIG_USB + else + comment ' USB Bluetooth can only be used with disabled Bluetooth subsystem' + fi +fi if [ "$CONFIG_SCSI" = "n" ]; then comment ' SCSI support is needed for USB Storage' fi --- linux/drivers/usb/hid-core.c~bluetooth-2.4.18-mh11 2001-12-21 18:41:55.000000000 +0100 +++ linux/drivers/usb/hid-core.c 2004-01-25 23:37:39.000000000 +0100 @@ -217,6 +217,8 @@ offset = report->size; report->size += parser->global.report_size * parser->global.report_count; + if (usages < parser->global.report_count) + usages = parser->global.report_count; if (usages == 0) return 0; /* ignore padding fields */ --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/include/linux/firmware.h 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1,20 @@ +#ifndef _LINUX_FIRMWARE_H +#define _LINUX_FIRMWARE_H +#include +#include +#define FIRMWARE_NAME_MAX 30 +struct firmware { + size_t size; + u8 *data; +}; +int request_firmware (const struct firmware **fw, const char *name, + const char *device); +int request_firmware_nowait ( + struct module *module, + const char *name, const char *device, void *context, + void (*cont)(const struct firmware *fw, void *context)); +/* On 2.5 'device' is 'struct device *' */ + +void release_firmware (const struct firmware *fw); +void register_firmware (const char *name, const u8 *data, size_t size); +#endif --- linux/include/linux/input.h~bluetooth-2.4.18-mh11 2001-09-13 00:34:06.000000000 +0200 +++ linux/include/linux/input.h 2004-01-25 23:37:39.000000000 +0100 @@ -468,6 +468,8 @@ #define BUS_PCI 0x01 #define BUS_ISAPNP 0x02 #define BUS_USB 0x03 +#define BUS_HIL 0x04 +#define BUS_BLUETOOTH 0x05 #define BUS_ISA 0x10 #define BUS_I8042 0x11 --- linux/include/linux/kernel.h~bluetooth-2.4.18-mh11 2002-02-25 20:38:13.000000000 +0100 +++ linux/include/linux/kernel.h 2004-01-25 23:37:39.000000000 +0100 @@ -11,6 +11,7 @@ #include #include #include +#include /* Optimization barrier */ /* The "volatile" is due to gcc bugs */ @@ -181,4 +182,6 @@ char _f[20-2*sizeof(long)-sizeof(int)]; /* Padding: libc5 uses this.. */ }; -#endif +#define BUG_ON(condition) do { if (unlikely((condition)!=0)) BUG(); } while(0) + +#endif /* _LINUX_KERNEL_H */ --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/include/linux/uinput.h 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1,79 @@ +/* + * User level driver support for input subsystem + * + * Heavily based on evdev.c by Vojtech Pavlik + * + * 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 + * + * Author: Aristeu Sergio Rozanski Filho + * + * Changes/Revisions: + * 0.1 20/06/2002 + * - first public version + */ + +#ifndef __UINPUT_H_ +#define __UINPUT_H_ + +#ifdef __KERNEL__ +#define UINPUT_MINOR 223 +#define UINPUT_NAME "uinput" +#define UINPUT_BUFFER_SIZE 16 + +/* state flags => bit index for {set|clear|test}_bit ops */ +#define UIST_CREATED 0 + +struct uinput_device { + struct input_dev *dev; + unsigned long state; + wait_queue_head_t waitq; + unsigned char ready, + head, + tail; + struct input_event buff[UINPUT_BUFFER_SIZE]; +}; +#endif /* __KERNEL__ */ + +/* ioctl */ +#define UINPUT_IOCTL_BASE 'U' +#define UI_DEV_CREATE _IO(UINPUT_IOCTL_BASE, 1) +#define UI_DEV_DESTROY _IO(UINPUT_IOCTL_BASE, 2) +#define UI_SET_EVBIT _IOW(UINPUT_IOCTL_BASE, 100, int) +#define UI_SET_KEYBIT _IOW(UINPUT_IOCTL_BASE, 101, int) +#define UI_SET_RELBIT _IOW(UINPUT_IOCTL_BASE, 102, int) +#define UI_SET_ABSBIT _IOW(UINPUT_IOCTL_BASE, 103, int) +#define UI_SET_MSCBIT _IOW(UINPUT_IOCTL_BASE, 104, int) +#define UI_SET_LEDBIT _IOW(UINPUT_IOCTL_BASE, 105, int) +#define UI_SET_SNDBIT _IOW(UINPUT_IOCTL_BASE, 106, int) +#define UI_SET_FFBIT _IOW(UINPUT_IOCTL_BASE, 107, int) + +#ifndef NBITS +#define NBITS(x) ((((x)-1)/(sizeof(long)*8))+1) +#endif /* NBITS */ + +#define UINPUT_MAX_NAME_SIZE 80 +struct uinput_user_dev { + char name[UINPUT_MAX_NAME_SIZE]; + unsigned short idbus; + unsigned short idvendor; + unsigned short idproduct; + unsigned short idversion; + int ff_effects_max; + int absmax[ABS_MAX + 1]; + int absmin[ABS_MAX + 1]; + int absfuzz[ABS_MAX + 1]; + int absflat[ABS_MAX + 1]; +}; +#endif /* __UINPUT_H_ */ --- linux/include/net/bluetooth/bluetooth.h~bluetooth-2.4.18-mh11 2001-09-07 18:28:38.000000000 +0200 +++ linux/include/net/bluetooth/bluetooth.h 2004-01-25 23:37:39.000000000 +0100 @@ -23,7 +23,7 @@ */ /* - * $Id: bluetooth.h,v 1.6 2001/08/03 04:19:49 maxk Exp $ + * $Id: bluetooth.h,v 1.9 2002/05/06 21:11:55 maxk Exp $ */ #ifndef __BLUETOOTH_H @@ -31,17 +31,63 @@ #include #include +#include +#include #ifndef AF_BLUETOOTH #define AF_BLUETOOTH 31 #define PF_BLUETOOTH AF_BLUETOOTH #endif +/* Reserv for core and drivers use */ +#define BLUEZ_SKB_RESERVE 8 + +#ifndef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif + #define BTPROTO_L2CAP 0 #define BTPROTO_HCI 1 +#define BTPROTO_SCO 2 +#define BTPROTO_RFCOMM 3 +#define BTPROTO_BNEP 4 +#define BTPROTO_CMTP 5 #define SOL_HCI 0 #define SOL_L2CAP 6 +#define SOL_SCO 17 +#define SOL_RFCOMM 18 + +/* Debugging */ +#ifdef CONFIG_BLUEZ_DEBUG + +#define HCI_CORE_DEBUG 1 +#define HCI_SOCK_DEBUG 1 +#define HCI_UART_DEBUG 1 +#define HCI_USB_DEBUG 1 +//#define HCI_DATA_DUMP 1 + +#define L2CAP_DEBUG 1 +#define SCO_DEBUG 1 +#define AF_BLUETOOTH_DEBUG 1 + +#endif /* CONFIG_BLUEZ_DEBUG */ + +extern void bluez_dump(char *pref, __u8 *buf, int count); + +#if __GNUC__ <= 2 && __GNUC_MINOR__ < 95 +#define __func__ __FUNCTION__ +#endif + +#define BT_INFO(fmt, arg...) printk(KERN_INFO fmt "\n" , ## arg) +#define BT_DBG(fmt, arg...) printk(KERN_INFO "%s: " fmt "\n" , __func__ , ## arg) +#define BT_ERR(fmt, arg...) printk(KERN_ERR "%s: " fmt "\n" , __func__ , ## arg) + +#ifdef HCI_DATA_DUMP +#define BT_DMP(buf, len) bluez_dump(__func__, buf, len) +#else +#define BT_DMP(D...) +#endif /* Connection and socket states */ enum { @@ -50,6 +96,7 @@ BT_BOUND, BT_LISTEN, BT_CONNECT, + BT_CONNECT2, BT_CONFIG, BT_DISCONN, BT_CLOSED @@ -66,7 +113,8 @@ __u8 b[6]; } __attribute__((packed)) bdaddr_t; -#define BDADDR_ANY ((bdaddr_t *)"\000\000\000\000\000") +#define BDADDR_ANY (&(bdaddr_t) {{0, 0, 0, 0, 0, 0}}) +#define BDADDR_LOCAL (&(bdaddr_t) {{0, 0, 0, 0xff, 0xff, 0xff}}) /* Copy, swap, convert BD Address */ static inline int bacmp(bdaddr_t *ba1, bdaddr_t *ba2) @@ -82,6 +130,91 @@ char *batostr(bdaddr_t *ba); bdaddr_t *strtoba(char *str); +/* Common socket structures and functions */ + +#define bluez_pi(sk) ((struct bluez_pinfo *) &sk->protinfo) +#define bluez_sk(pi) ((struct sock *) \ + ((void *)pi - (unsigned long)(&((struct sock *)0)->protinfo))) + +struct bluez_pinfo { + bdaddr_t src; + bdaddr_t dst; + + struct list_head accept_q; + struct sock *parent; +}; + +struct bluez_sock_list { + struct sock *head; + rwlock_t lock; +}; + +int bluez_sock_register(int proto, struct net_proto_family *ops); +int bluez_sock_unregister(int proto); +void bluez_sock_init(struct socket *sock, struct sock *sk); +void bluez_sock_link(struct bluez_sock_list *l, struct sock *s); +void bluez_sock_unlink(struct bluez_sock_list *l, struct sock *s); +int bluez_sock_recvmsg(struct socket *sock, struct msghdr *msg, int len, int flags, struct scm_cookie *scm); +uint bluez_sock_poll(struct file * file, struct socket *sock, poll_table *wait); +int bluez_sock_wait_state(struct sock *sk, int state, unsigned long timeo); + +void bluez_accept_enqueue(struct sock *parent, struct sock *sk); +struct sock * bluez_accept_dequeue(struct sock *parent, struct socket *newsock); + +/* Skb helpers */ +struct bluez_skb_cb { + int incomming; +}; +#define bluez_cb(skb) ((struct bluez_skb_cb *)(skb->cb)) + +static inline struct sk_buff *bluez_skb_alloc(unsigned int len, int how) +{ + struct sk_buff *skb; + + if ((skb = alloc_skb(len + BLUEZ_SKB_RESERVE, how))) { + skb_reserve(skb, BLUEZ_SKB_RESERVE); + bluez_cb(skb)->incomming = 0; + } + return skb; +} + +static inline struct sk_buff *bluez_skb_send_alloc(struct sock *sk, unsigned long len, + int nb, int *err) +{ + struct sk_buff *skb; + + if ((skb = sock_alloc_send_skb(sk, len + BLUEZ_SKB_RESERVE, nb, err))) { + skb_reserve(skb, BLUEZ_SKB_RESERVE); + bluez_cb(skb)->incomming = 0; + } + + return skb; +} + +static inline int skb_frags_no(struct sk_buff *skb) +{ + register struct sk_buff *frag = skb_shinfo(skb)->frag_list; + register int n = 1; + + for (; frag; frag=frag->next, n++); + return n; +} + +int hci_core_init(void); +int hci_core_cleanup(void); +int hci_sock_init(void); +int hci_sock_cleanup(void); + int bterr(__u16 code); +#ifndef MODULE_LICENSE +#define MODULE_LICENSE(x) +#endif + +#ifndef list_for_each_safe +#define list_for_each_safe(pos, n, head) \ + for (pos = (head)->next, n = pos->next; pos != (head); \ + pos = n, n = pos->next) +#endif + #endif /* __BLUETOOTH_H */ --- linux/include/net/bluetooth/bluez.h~bluetooth-2.4.18-mh11 +++ linux/include/net/bluetooth/bluez.h -/* - BlueZ - Bluetooth protocol stack for Linux - Copyright (C) 2000-2001 Qualcomm Incorporated - - Written 2000,2001 by Maxim Krasnyansky - - 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: bluez.h,v 1.4 2001/08/03 04:19:49 maxk Exp $ - */ - -#ifndef __IF_BLUEZ_H -#define __IF_BLUEZ_H - -#include - -#define BLUEZ_MAX_PROTO 2 - -/* Reserv for core and drivers use */ -#define BLUEZ_SKB_RESERVE 8 - -#ifndef MIN -#define MIN(a,b) ((a) < (b) ? (a) : (b)) -#endif - -/* Debugging */ -#ifdef BLUEZ_DEBUG - -#define HCI_CORE_DEBUG 1 -#define HCI_SOCK_DEBUG 1 -#define HCI_UART_DEBUG 1 -#define HCI_USB_DEBUG 1 -//#define HCI_DATA_DUMP 1 - -#define L2CAP_DEBUG 1 - -#endif /* BLUEZ_DEBUG */ - -extern void bluez_dump(char *pref, __u8 *buf, int count); - -#define INF(fmt, arg...) printk(KERN_INFO fmt "\n" , ## arg) -#define DBG(fmt, arg...) printk(KERN_INFO __FUNCTION__ ": " fmt "\n" , ## arg) -#define ERR(fmt, arg...) printk(KERN_ERR __FUNCTION__ ": " fmt "\n" , ## arg) - -#ifdef HCI_DATA_DUMP -#define DMP(buf, len) bluez_dump(__FUNCTION__, buf, len) -#else -#define DMP(D...) -#endif - -/* ----- Sockets ------ */ -struct bluez_sock_list { - struct sock *head; - rwlock_t lock; -}; - -extern int bluez_sock_register(int proto, struct net_proto_family *ops); -extern int bluez_sock_unregister(int proto); - -extern void bluez_sock_link(struct bluez_sock_list *l, struct sock *s); -extern void bluez_sock_unlink(struct bluez_sock_list *l, struct sock *s); - -/* ----- SKB helpers ----- */ -struct bluez_skb_cb { - int incomming; -}; -#define bluez_cb(skb) ((struct bluez_skb_cb *)(skb->cb)) - -static inline struct sk_buff *bluez_skb_alloc(unsigned int len, int how) -{ - struct sk_buff *skb; - - if ((skb = alloc_skb(len + BLUEZ_SKB_RESERVE, how))) { - skb_reserve(skb, BLUEZ_SKB_RESERVE); - bluez_cb(skb)->incomming = 0; - } - return skb; -} - -static inline struct sk_buff *bluez_skb_send_alloc(struct sock *sk, unsigned long len, - int nb, int *err) -{ - struct sk_buff *skb; - - if ((skb = sock_alloc_send_skb(sk, len + BLUEZ_SKB_RESERVE, nb, err))) { - skb_reserve(skb, BLUEZ_SKB_RESERVE); - bluez_cb(skb)->incomming = 0; - } - - return skb; -} - -static inline int skb_frags_no(struct sk_buff *skb) -{ - register struct sk_buff *frag = skb_shinfo(skb)->frag_list; - register int n = 1; - - for (; frag; frag=frag->next, n++); - return n; -} - -extern int hci_core_init(void); -extern int hci_core_cleanup(void); -extern int hci_sock_init(void); -extern int hci_sock_cleanup(void); - -#endif /* __IF_BLUEZ_H */ --- linux/include/net/bluetooth/hci_core.h~bluetooth-2.4.18-mh11 2001-09-07 18:28:38.000000000 +0200 +++ linux/include/net/bluetooth/hci_core.h 2004-01-25 23:37:39.000000000 +0100 @@ -23,7 +23,7 @@ */ /* - * $Id: hci_core.h,v 1.11 2001/08/05 06:02:15 maxk Exp $ + * $Id: hci_core.h,v 1.5 2002/06/27 04:56:30 maxk Exp $ */ #ifndef __HCI_CORE_H @@ -32,14 +32,12 @@ #include /* HCI upper protocols */ -#define HCI_MAX_PROTO 1 #define HCI_PROTO_L2CAP 0 +#define HCI_PROTO_SCO 1 #define HCI_INIT_TIMEOUT (HZ * 10) -/* ----- Inquiry cache ----- */ -#define INQUIRY_CACHE_AGE_MAX (HZ*5) // 5 seconds -#define INQUIRY_ENTRY_AGE_MAX (HZ*60) // 60 seconds +/* HCI Core structures */ struct inquiry_entry { struct inquiry_entry *next; @@ -53,111 +51,186 @@ struct inquiry_entry *list; }; -static inline void inquiry_cache_init(struct inquiry_cache *cache) -{ - spin_lock_init(&cache->lock); - cache->list = NULL; -} +struct conn_hash { + struct list_head list; + spinlock_t lock; + unsigned int num; +}; -static inline void inquiry_cache_lock(struct inquiry_cache *cache) -{ - spin_lock(&cache->lock); -} +struct hci_dev { + struct list_head list; + spinlock_t lock; + atomic_t refcnt; -static inline void inquiry_cache_unlock(struct inquiry_cache *cache) -{ - spin_unlock(&cache->lock); -} + char name[8]; + unsigned long flags; + __u16 id; + __u8 type; + bdaddr_t bdaddr; + __u8 features[8]; -static inline void inquiry_cache_lock_bh(struct inquiry_cache *cache) -{ - spin_lock_bh(&cache->lock); -} + __u16 pkt_type; + __u16 link_policy; + __u16 link_mode; + + atomic_t cmd_cnt; + unsigned int acl_cnt; + unsigned int sco_cnt; -static inline void inquiry_cache_unlock_bh(struct inquiry_cache *cache) -{ - spin_unlock_bh(&cache->lock); -} + unsigned int acl_mtu; + unsigned int sco_mtu; + unsigned int acl_pkts; + unsigned int sco_pkts; -static inline long inquiry_cache_age(struct inquiry_cache *cache) -{ - return jiffies - cache->timestamp; -} + unsigned long cmd_last_tx; + unsigned long acl_last_tx; + unsigned long sco_last_tx; + + struct tasklet_struct cmd_task; + struct tasklet_struct rx_task; + struct tasklet_struct tx_task; -static inline long inquiry_entry_age(struct inquiry_entry *e) -{ - return jiffies - e->timestamp; -} -extern void inquiry_cache_flush(struct inquiry_cache *cache); + struct sk_buff_head rx_q; + struct sk_buff_head raw_q; + struct sk_buff_head cmd_q; -struct hci_dev; + struct sk_buff *sent_cmd; + + struct semaphore req_lock; + wait_queue_head_t req_wait_q; + __u32 req_status; + __u32 req_result; + + struct inquiry_cache inq_cache; + struct conn_hash conn_hash; + + struct hci_dev_stats stat; + + void *driver_data; + void *core_data; + + atomic_t promisc; + + int (*open)(struct hci_dev *hdev); + int (*close)(struct hci_dev *hdev); + int (*flush)(struct hci_dev *hdev); + int (*send)(struct sk_buff *skb); + void (*destruct)(struct hci_dev *hdev); + int (*ioctl)(struct hci_dev *hdev, unsigned int cmd, unsigned long arg); +}; -/* ----- HCI Connections ----- */ struct hci_conn { struct list_head list; + + atomic_t refcnt; + spinlock_t lock; + bdaddr_t dst; __u16 handle; + __u16 state; __u8 type; - unsigned int sent; + __u8 out; + __u32 link_mode; + unsigned long pend; + + unsigned int sent; + + struct sk_buff_head data_q; + struct timer_list timer; + struct hci_dev *hdev; void *l2cap_data; + void *sco_data; void *priv; - struct sk_buff_head data_q; + struct hci_conn *link; }; -struct conn_hash { - struct list_head list; - spinlock_t lock; - unsigned int num; -}; +extern struct hci_proto *hci_proto[]; +extern struct list_head hdev_list; +extern rwlock_t hdev_list_lock; -static inline void conn_hash_init(struct conn_hash *h) +/* ----- Inquiry cache ----- */ +#define INQUIRY_CACHE_AGE_MAX (HZ*30) // 30 seconds +#define INQUIRY_ENTRY_AGE_MAX (HZ*60) // 60 seconds + +#define inquiry_cache_lock(c) spin_lock(&c->lock) +#define inquiry_cache_unlock(c) spin_unlock(&c->lock) +#define inquiry_cache_lock_bh(c) spin_lock_bh(&c->lock) +#define inquiry_cache_unlock_bh(c) spin_unlock_bh(&c->lock) + +static inline void inquiry_cache_init(struct hci_dev *hdev) { - INIT_LIST_HEAD(&h->list); - spin_lock_init(&h->lock); - h->num = 0; + struct inquiry_cache *c = &hdev->inq_cache; + spin_lock_init(&c->lock); + c->list = NULL; } -static inline void conn_hash_lock(struct conn_hash *h) +static inline int inquiry_cache_empty(struct hci_dev *hdev) { - spin_lock(&h->lock); + struct inquiry_cache *c = &hdev->inq_cache; + return (c->list == NULL); } -static inline void conn_hash_unlock(struct conn_hash *h) +static inline long inquiry_cache_age(struct hci_dev *hdev) { - spin_unlock(&h->lock); + struct inquiry_cache *c = &hdev->inq_cache; + return jiffies - c->timestamp; } -static inline void __conn_hash_add(struct conn_hash *h, __u16 handle, struct hci_conn *c) +static inline long inquiry_entry_age(struct inquiry_entry *e) { - list_add(&c->list, &h->list); - h->num++; + return jiffies - e->timestamp; } -static inline void conn_hash_add(struct conn_hash *h, __u16 handle, struct hci_conn *c) +struct inquiry_entry *inquiry_cache_lookup(struct hci_dev *hdev, bdaddr_t *bdaddr); +void inquiry_cache_update(struct hci_dev *hdev, inquiry_info *info); +void inquiry_cache_flush(struct hci_dev *hdev); +int inquiry_cache_dump(struct hci_dev *hdev, int num, __u8 *buf); + +/* ----- HCI Connections ----- */ +enum { + HCI_CONN_AUTH_PEND, + HCI_CONN_ENCRYPT_PEND +}; + +#define hci_conn_lock(c) spin_lock(&c->lock) +#define hci_conn_unlock(c) spin_unlock(&c->lock) +#define hci_conn_lock_bh(c) spin_lock_bh(&c->lock) +#define hci_conn_unlock_bh(c) spin_unlock_bh(&c->lock) + +#define conn_hash_lock(d) spin_lock(&d->conn_hash->lock) +#define conn_hash_unlock(d) spin_unlock(&d->conn_hash->lock) +#define conn_hash_lock_bh(d) spin_lock_bh(&d->conn_hash->lock) +#define conn_hash_unlock_bh(d) spin_unlock_bh(&d->conn_hash->lock) + +static inline void conn_hash_init(struct hci_dev *hdev) { - conn_hash_lock(h); - __conn_hash_add(h, handle, c); - conn_hash_unlock(h); + struct conn_hash *h = &hdev->conn_hash; + INIT_LIST_HEAD(&h->list); + spin_lock_init(&h->lock); + h->num = 0; } -static inline void __conn_hash_del(struct conn_hash *h, struct hci_conn *c) +static inline void conn_hash_add(struct hci_dev *hdev, struct hci_conn *c) { - list_del(&c->list); - h->num--; + struct conn_hash *h = &hdev->conn_hash; + list_add(&c->list, &h->list); + h->num++; } -static inline void conn_hash_del(struct conn_hash *h, struct hci_conn *c) +static inline void conn_hash_del(struct hci_dev *hdev, struct hci_conn *c) { - conn_hash_lock(h); - __conn_hash_del(h, c); - conn_hash_unlock(h); + struct conn_hash *h = &hdev->conn_hash; + list_del(&c->list); + h->num--; } -static inline struct hci_conn *__conn_hash_lookup(struct conn_hash *h, __u16 handle) +static inline struct hci_conn *conn_hash_lookup_handle(struct hci_dev *hdev, + __u16 handle) { + register struct conn_hash *h = &hdev->conn_hash; register struct list_head *p; register struct hci_conn *c; @@ -169,101 +242,97 @@ return NULL; } -static inline struct hci_conn *conn_hash_lookup(struct conn_hash *h, __u16 handle) +static inline struct hci_conn *conn_hash_lookup_ba(struct hci_dev *hdev, + __u8 type, bdaddr_t *ba) { - struct hci_conn *conn; + register struct conn_hash *h = &hdev->conn_hash; + register struct list_head *p; + register struct hci_conn *c; - conn_hash_lock(h); - conn = __conn_hash_lookup(h, handle); - conn_hash_unlock(h); - return conn; + list_for_each(p, &h->list) { + c = list_entry(p, struct hci_conn, list); + if (c->type == type && !bacmp(&c->dst, ba)) + return c; + } + return NULL; } -/* ----- HCI Devices ----- */ -struct hci_dev { - atomic_t refcnt; - - char name[8]; - __u32 flags; - __u16 id; - __u8 type; - bdaddr_t bdaddr; - __u8 features[8]; - - __u16 pkt_type; - - atomic_t cmd_cnt; - unsigned int acl_cnt; - unsigned int sco_cnt; - - unsigned int acl_mtu; - unsigned int sco_mtu; - unsigned int acl_max; - unsigned int sco_max; - - void *driver_data; - void *l2cap_data; - void *priv; - - struct tasklet_struct cmd_task; - struct tasklet_struct rx_task; - struct tasklet_struct tx_task; - - struct sk_buff_head rx_q; - struct sk_buff_head raw_q; - struct sk_buff_head cmd_q; - - struct sk_buff *sent_cmd; - - struct semaphore req_lock; - wait_queue_head_t req_wait_q; - __u32 req_status; - __u32 req_result; +void hci_acl_connect(struct hci_conn *conn); +void hci_acl_disconn(struct hci_conn *conn, __u8 reason); +void hci_add_sco(struct hci_conn *conn, __u16 handle); - struct inquiry_cache inq_cache; +struct hci_conn *hci_conn_add(struct hci_dev *hdev, int type, bdaddr_t *dst); +int hci_conn_del(struct hci_conn *conn); +void hci_conn_hash_flush(struct hci_dev *hdev); - struct conn_hash conn_hash; +struct hci_conn *hci_connect(struct hci_dev *hdev, int type, bdaddr_t *src); +int hci_conn_auth(struct hci_conn *conn); +int hci_conn_encrypt(struct hci_conn *conn); - struct hci_dev_stats stat; +static inline void hci_conn_set_timer(struct hci_conn *conn, long timeout) +{ + mod_timer(&conn->timer, jiffies + timeout); +} - int (*open)(struct hci_dev *hdev); - int (*close)(struct hci_dev *hdev); - int (*flush)(struct hci_dev *hdev); - int (*send)(struct sk_buff *skb); -}; +static inline void hci_conn_del_timer(struct hci_conn *conn) +{ + del_timer(&conn->timer); +} -static inline void hci_dev_hold(struct hci_dev *hdev) +static inline void hci_conn_hold(struct hci_conn *conn) { - atomic_inc(&hdev->refcnt); + atomic_inc(&conn->refcnt); + hci_conn_del_timer(conn); } -static inline void hci_dev_put(struct hci_dev *hdev) +static inline void hci_conn_put(struct hci_conn *conn) { - atomic_dec(&hdev->refcnt); + if (atomic_dec_and_test(&conn->refcnt)) { + if (conn->type == ACL_LINK) { + unsigned long timeo = (conn->out) ? + HCI_DISCONN_TIMEOUT : HCI_DISCONN_TIMEOUT * 2; + hci_conn_set_timer(conn, timeo); + } else + hci_conn_set_timer(conn, HZ / 100); + } } -extern struct hci_dev *hci_dev_get(int index); -extern int hci_register_dev(struct hci_dev *hdev); -extern int hci_unregister_dev(struct hci_dev *hdev); -extern int hci_dev_open(__u16 dev); -extern int hci_dev_close(__u16 dev); -extern int hci_dev_reset(__u16 dev); -extern int hci_dev_reset_stat(__u16 dev); -extern int hci_dev_info(unsigned long arg); -extern int hci_dev_list(unsigned long arg); -extern int hci_dev_setscan(unsigned long arg); -extern int hci_dev_setauth(unsigned long arg); -extern int hci_dev_setptype(unsigned long arg); -extern int hci_conn_list(unsigned long arg); -extern int hci_inquiry(unsigned long arg); +/* ----- HCI Devices ----- */ +static inline void hci_dev_put(struct hci_dev *d) +{ + if (atomic_dec_and_test(&d->refcnt)) + d->destruct(d); +} +#define hci_dev_hold(d) atomic_inc(&d->refcnt) -extern __u32 hci_dev_setmode(struct hci_dev *hdev, __u32 mode); -extern __u32 hci_dev_getmode(struct hci_dev *hdev); +#define hci_dev_lock(d) spin_lock(&d->lock) +#define hci_dev_unlock(d) spin_unlock(&d->lock) +#define hci_dev_lock_bh(d) spin_lock_bh(&d->lock) +#define hci_dev_unlock_bh(d) spin_unlock_bh(&d->lock) -extern int hci_recv_frame(struct sk_buff *skb); +struct hci_dev *hci_dev_get(int index); +struct hci_dev *hci_get_route(bdaddr_t *src, bdaddr_t *dst); +int hci_register_dev(struct hci_dev *hdev); +int hci_unregister_dev(struct hci_dev *hdev); +int hci_suspend_dev(struct hci_dev *hdev); +int hci_resume_dev(struct hci_dev *hdev); +int hci_dev_open(__u16 dev); +int hci_dev_close(__u16 dev); +int hci_dev_reset(__u16 dev); +int hci_dev_reset_stat(__u16 dev); +int hci_dev_cmd(unsigned int cmd, unsigned long arg); +int hci_get_dev_list(unsigned long arg); +int hci_get_dev_info(unsigned long arg); +int hci_get_conn_list(unsigned long arg); +int hci_get_conn_info(struct hci_dev *hdev, unsigned long arg); +int hci_inquiry(unsigned long arg); + +int hci_recv_frame(struct sk_buff *skb); +void hci_event_packet(struct hci_dev *hdev, struct sk_buff *skb); /* ----- LMP capabilities ----- */ #define lmp_rswitch_capable(dev) (dev->features[0] & LMP_RSWITCH) +#define lmp_encrypt_capable(dev) (dev->features[0] & LMP_ENCRYPT) /* ----- HCI tasks ----- */ static inline void hci_sched_cmd(struct hci_dev *hdev) @@ -284,43 +353,130 @@ /* ----- HCI protocols ----- */ struct hci_proto { char *name; - __u32 id; - __u32 flags; + unsigned int id; + unsigned long flags; void *priv; - int (*connect_ind) (struct hci_dev *hdev, bdaddr_t *bdaddr); - int (*connect_cfm) (struct hci_dev *hdev, bdaddr_t *bdaddr, __u8 status, struct hci_conn *conn); + int (*connect_ind) (struct hci_dev *hdev, bdaddr_t *bdaddr, __u8 type); + int (*connect_cfm) (struct hci_conn *conn, __u8 status); int (*disconn_ind) (struct hci_conn *conn, __u8 reason); - int (*recv_acldata) (struct hci_conn *conn, struct sk_buff *skb , __u16 flags); + int (*recv_acldata) (struct hci_conn *conn, struct sk_buff *skb, __u16 flags); int (*recv_scodata) (struct hci_conn *conn, struct sk_buff *skb); + int (*auth_cfm) (struct hci_conn *conn, __u8 status); + int (*encrypt_cfm) (struct hci_conn *conn, __u8 status); }; -extern int hci_register_proto(struct hci_proto *hproto); -extern int hci_unregister_proto(struct hci_proto *hproto); -extern int hci_register_notifier(struct notifier_block *nb); -extern int hci_unregister_notifier(struct notifier_block *nb); -extern int hci_connect(struct hci_dev * hdev, bdaddr_t * bdaddr); -extern int hci_disconnect(struct hci_conn *conn, __u8 reason); -extern int hci_send_cmd(struct hci_dev *hdev, __u16 ogf, __u16 ocf, __u32 plen, void * param); -extern int hci_send_raw(struct sk_buff *skb); -extern int hci_send_acl(struct hci_conn *conn, struct sk_buff *skb, __u16 flags); -extern int hci_send_sco(struct hci_conn *conn, struct sk_buff *skb); +static inline int hci_proto_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr, __u8 type) +{ + register struct hci_proto *hp; + int mask = 0; + + hp = hci_proto[HCI_PROTO_L2CAP]; + if (hp && hp->connect_ind) + mask |= hp->connect_ind(hdev, bdaddr, type); + + hp = hci_proto[HCI_PROTO_SCO]; + if (hp && hp->connect_ind) + mask |= hp->connect_ind(hdev, bdaddr, type); + + return mask; +} + +static inline void hci_proto_connect_cfm(struct hci_conn *conn, __u8 status) +{ + register struct hci_proto *hp; + + hp = hci_proto[HCI_PROTO_L2CAP]; + if (hp && hp->connect_cfm) + hp->connect_cfm(conn, status); + + hp = hci_proto[HCI_PROTO_SCO]; + if (hp && hp->connect_cfm) + hp->connect_cfm(conn, status); +} + +static inline void hci_proto_disconn_ind(struct hci_conn *conn, __u8 reason) +{ + register struct hci_proto *hp; + + hp = hci_proto[HCI_PROTO_L2CAP]; + if (hp && hp->disconn_ind) + hp->disconn_ind(conn, reason); + + hp = hci_proto[HCI_PROTO_SCO]; + if (hp && hp->disconn_ind) + hp->disconn_ind(conn, reason); +} + +static inline void hci_proto_auth_cfm(struct hci_conn *conn, __u8 status) +{ + register struct hci_proto *hp; + + hp = hci_proto[HCI_PROTO_L2CAP]; + if (hp && hp->auth_cfm) + hp->auth_cfm(conn, status); + + hp = hci_proto[HCI_PROTO_SCO]; + if (hp && hp->auth_cfm) + hp->auth_cfm(conn, status); +} + +static inline void hci_proto_encrypt_cfm(struct hci_conn *conn, __u8 status) +{ + register struct hci_proto *hp; + + hp = hci_proto[HCI_PROTO_L2CAP]; + if (hp && hp->encrypt_cfm) + hp->encrypt_cfm(conn, status); + + hp = hci_proto[HCI_PROTO_SCO]; + if (hp && hp->encrypt_cfm) + hp->encrypt_cfm(conn, status); +} + +int hci_register_proto(struct hci_proto *hproto); +int hci_unregister_proto(struct hci_proto *hproto); +int hci_register_notifier(struct notifier_block *nb); +int hci_unregister_notifier(struct notifier_block *nb); + +int hci_send_cmd(struct hci_dev *hdev, __u16 ogf, __u16 ocf, __u32 plen, void *param); +int hci_send_acl(struct hci_conn *conn, struct sk_buff *skb, __u16 flags); +int hci_send_sco(struct hci_conn *conn, struct sk_buff *skb); + +void *hci_sent_cmd_data(struct hci_dev *hdev, __u16 ogf, __u16 ocf); + +void hci_si_event(struct hci_dev *hdev, int type, int dlen, void *data); /* ----- HCI Sockets ----- */ -extern void hci_send_to_sock(struct hci_dev *hdev, struct sk_buff *skb); +void hci_send_to_sock(struct hci_dev *hdev, struct sk_buff *skb); /* HCI info for socket */ -#define hci_pi(sk) ((struct hci_pinfo *) &sk->protinfo) +#define hci_pi(sk) ((struct hci_pinfo *) &sk->tp_pinfo) struct hci_pinfo { struct hci_dev *hdev; struct hci_filter filter; __u32 cmsg_mask; }; +/* HCI security filter */ +#define HCI_SFLT_MAX_OGF 5 + +struct hci_sec_filter { + __u32 type_mask; + __u32 event_mask[2]; + __u32 ocf_mask[HCI_SFLT_MAX_OGF + 1][4]; +}; + /* ----- HCI requests ----- */ #define HCI_REQ_DONE 0 #define HCI_REQ_PEND 1 #define HCI_REQ_CANCELED 2 +#define hci_req_lock(d) down(&d->req_lock) +#define hci_req_unlock(d) up(&d->req_lock) + +void hci_req_complete(struct hci_dev *hdev, int result); +void hci_req_cancel(struct hci_dev *hdev, int err); + #endif /* __HCI_CORE_H */ --- linux/include/net/bluetooth/hci.h~bluetooth-2.4.18-mh11 2001-09-07 18:28:38.000000000 +0200 +++ linux/include/net/bluetooth/hci.h 2004-01-25 23:37:39.000000000 +0100 @@ -23,59 +23,75 @@ */ /* - * $Id: hci.h,v 1.15 2001/08/05 06:02:15 maxk Exp $ + * $Id: hci.h,v 1.5 2002/06/27 17:29:30 maxk Exp $ */ #ifndef __HCI_H #define __HCI_H -#include - -#define HCI_MAX_DEV 8 -#define HCI_MAX_FRAME_SIZE 2048 +#define HCI_MAX_ACL_SIZE 1024 +#define HCI_MAX_SCO_SIZE 255 +#define HCI_MAX_EVENT_SIZE 260 +#define HCI_MAX_FRAME_SIZE (HCI_MAX_ACL_SIZE + 4) /* HCI dev events */ #define HCI_DEV_REG 1 #define HCI_DEV_UNREG 2 #define HCI_DEV_UP 3 #define HCI_DEV_DOWN 4 +#define HCI_DEV_SUSPEND 5 +#define HCI_DEV_RESUME 6 /* HCI device types */ -#define HCI_UART 0 +#define HCI_VHCI 0 #define HCI_USB 1 -#define HCI_VHCI 2 +#define HCI_PCCARD 2 +#define HCI_UART 3 +#define HCI_RS232 4 +#define HCI_PCI 5 -/* HCI device modes */ -#define HCI_NORMAL 0x0001 -#define HCI_RAW 0x0002 -#define HCI_MODE_MASK (HCI_NORMAL | HCI_RAW) -#define HCI_SOCK 0x1000 +/* HCI device flags */ +enum { + HCI_UP, + HCI_INIT, + HCI_RUNNING, -/* HCI device states */ -#define HCI_INIT 0x0010 -#define HCI_UP 0x0020 -#define HCI_RUNNING 0x0040 + HCI_PSCAN, + HCI_ISCAN, + HCI_AUTH, + HCI_ENCRYPT, + HCI_INQUIRY, -/* HCI device flags */ -#define HCI_PSCAN 0x0100 -#define HCI_ISCAN 0x0200 -#define HCI_AUTH 0x0400 + HCI_RAW +}; -/* HCI Ioctl defines */ +/* HCI ioctl defines */ #define HCIDEVUP _IOW('H', 201, int) #define HCIDEVDOWN _IOW('H', 202, int) #define HCIDEVRESET _IOW('H', 203, int) -#define HCIRESETSTAT _IOW('H', 204, int) -#define HCIGETINFO _IOR('H', 205, int) -#define HCIGETDEVLIST _IOR('H', 206, int) -#define HCISETRAW _IOW('H', 207, int) -#define HCISETSCAN _IOW('H', 208, int) -#define HCISETAUTH _IOW('H', 209, int) -#define HCIINQUIRY _IOR('H', 210, int) -#define HCISETPTYPE _IOW('H', 211, int) +#define HCIDEVRESTAT _IOW('H', 204, int) + +#define HCIGETDEVLIST _IOR('H', 210, int) +#define HCIGETDEVINFO _IOR('H', 211, int) #define HCIGETCONNLIST _IOR('H', 212, int) +#define HCIGETCONNINFO _IOR('H', 213, int) -#ifndef __NO_HCI_DEFS +#define HCISETRAW _IOW('H', 220, int) +#define HCISETSCAN _IOW('H', 221, int) +#define HCISETAUTH _IOW('H', 222, int) +#define HCISETENCRYPT _IOW('H', 223, int) +#define HCISETPTYPE _IOW('H', 224, int) +#define HCISETLINKPOL _IOW('H', 225, int) +#define HCISETLINKMODE _IOW('H', 226, int) +#define HCISETACLMTU _IOW('H', 227, int) +#define HCISETSCOMTU _IOW('H', 228, int) + +#define HCIINQUIRY _IOR('H', 240, int) + +/* HCI timeouts */ +#define HCI_CONN_TIMEOUT (HZ * 40) +#define HCI_DISCONN_TIMEOUT (HZ * 2) +#define HCI_CONN_IDLE_TIMEOUT (HZ * 60) /* HCI Packet types */ #define HCI_COMMAND_PKT 0x01 @@ -92,11 +108,18 @@ #define HCI_DH3 0x0800 #define HCI_DH5 0x8000 +#define HCI_HV1 0x0020 +#define HCI_HV2 0x0040 +#define HCI_HV3 0x0080 + +#define SCO_PTYPE_MASK (HCI_HV1 | HCI_HV2 | HCI_HV3) +#define ACL_PTYPE_MASK (~SCO_PTYPE_MASK) + /* ACL flags */ -#define ACL_CONT 0x0001 -#define ACL_START 0x0002 -#define ACL_ACTIVE_BCAST 0x0010 -#define ACL_PICO_BCAST 0x0020 +#define ACL_CONT 0x01 +#define ACL_START 0x02 +#define ACL_ACTIVE_BCAST 0x04 +#define ACL_PICO_BCAST 0x08 /* Baseband links */ #define SCO_LINK 0x00 @@ -125,6 +148,20 @@ #define LMP_PSCHEME 0x02 #define LMP_PCONTROL 0x04 +/* Link policies */ +#define HCI_LP_RSWITCH 0x0001 +#define HCI_LP_HOLD 0x0002 +#define HCI_LP_SNIFF 0x0004 +#define HCI_LP_PARK 0x0008 + +/* Link mode */ +#define HCI_LM_ACCEPT 0x8000 +#define HCI_LM_MASTER 0x0001 +#define HCI_LM_AUTH 0x0002 +#define HCI_LM_ENCRYPT 0x0004 +#define HCI_LM_TRUSTED 0x0008 +#define HCI_LM_RELIABLE 0x0010 + /* ----- HCI Commands ----- */ /* OGF & OCF values */ @@ -137,9 +174,10 @@ __u8 hci_ver; __u16 hci_rev; __u8 lmp_ver; - __u16 man_name; - __u16 lmp_sub; + __u16 manufacturer; + __u16 lmp_subver; } __attribute__ ((packed)) read_local_version_rp; +#define READ_LOCAL_VERSION_RP_SIZE 9 #define OCF_READ_LOCAL_FEATURES 0x0003 typedef struct { @@ -165,18 +203,24 @@ /* Host Controller and Baseband */ #define OGF_HOST_CTL 0x03 #define OCF_RESET 0x0003 +#define OCF_READ_AUTH_ENABLE 0x001F #define OCF_WRITE_AUTH_ENABLE 0x0020 - #define AUTH_DISABLED 0x00 - #define AUTH_ENABLED 0x01 + #define AUTH_DISABLED 0x00 + #define AUTH_ENABLED 0x01 + +#define OCF_READ_ENCRYPT_MODE 0x0021 +#define OCF_WRITE_ENCRYPT_MODE 0x0022 + #define ENCRYPT_DISABLED 0x00 + #define ENCRYPT_P2P 0x01 + #define ENCRYPT_BOTH 0x02 #define OCF_WRITE_CA_TIMEOUT 0x0016 #define OCF_WRITE_PG_TIMEOUT 0x0018 #define OCF_WRITE_SCAN_ENABLE 0x001A - #define SCANS_DISABLED 0x00 - #define IS_ENA_PS_DIS 0x01 - #define IS_DIS_PS_ENA 0x02 - #define IS_ENA_PS_ENA 0x03 + #define SCAN_DISABLED 0x00 + #define SCAN_INQUIRY 0x01 + #define SCAN_PAGE 0x02 #define OCF_SET_EVENT_FLT 0x0005 typedef struct { @@ -226,9 +270,18 @@ } __attribute__ ((packed)) write_class_of_dev_cp; #define WRITE_CLASS_OF_DEV_CP_SIZE 3 +#define OCF_HOST_BUFFER_SIZE 0x0033 +typedef struct { + __u16 acl_mtu; + __u8 sco_mtu; + __u16 acl_max_pkt; + __u16 sco_max_pkt; +} __attribute__ ((packed)) host_buffer_size_cp; +#define HOST_BUFFER_SIZE_CP_SIZE 7 + /* Link Control */ #define OGF_LINK_CTL 0x01 -#define OCF_CREATE_CONN 0x0005 +#define OCF_CREATE_CONN 0x0005 typedef struct { bdaddr_t bdaddr; __u16 pkt_type; @@ -246,6 +299,13 @@ } __attribute__ ((packed)) accept_conn_req_cp; #define ACCEPT_CONN_REQ_CP_SIZE 7 +#define OCF_REJECT_CONN_REQ 0x000a +typedef struct { + bdaddr_t bdaddr; + __u8 reason; +} __attribute__ ((packed)) reject_conn_req_cp; +#define REJECT_CONN_REQ_CP_SIZE 7 + #define OCF_DISCONNECT 0x0006 typedef struct { __u16 handle; @@ -253,17 +313,142 @@ } __attribute__ ((packed)) disconnect_cp; #define DISCONNECT_CP_SIZE 3 +#define OCF_ADD_SCO 0x0007 +typedef struct { + __u16 handle; + __u16 pkt_type; +} __attribute__ ((packed)) add_sco_cp; +#define ADD_SCO_CP_SIZE 4 + #define OCF_INQUIRY 0x0001 typedef struct { __u8 lap[3]; - __u8 lenght; + __u8 length; __u8 num_rsp; } __attribute__ ((packed)) inquiry_cp; #define INQUIRY_CP_SIZE 5 -#define OGF_LINK_POLICY 0x02 /* Link Policy */ +typedef struct { + __u8 status; + bdaddr_t bdaddr; +} __attribute__ ((packed)) status_bdaddr_rp; +#define STATUS_BDADDR_RP_SIZE 7 -/* --------- HCI Events --------- */ +#define OCF_INQUIRY_CANCEL 0x0002 + +#define OCF_LINK_KEY_REPLY 0x000B +#define OCF_LINK_KEY_NEG_REPLY 0x000C +typedef struct { + bdaddr_t bdaddr; + __u8 link_key[16]; +} __attribute__ ((packed)) link_key_reply_cp; +#define LINK_KEY_REPLY_CP_SIZE 22 + +#define OCF_PIN_CODE_REPLY 0x000D +#define OCF_PIN_CODE_NEG_REPLY 0x000E +typedef struct { + bdaddr_t bdaddr; + __u8 pin_len; + __u8 pin_code[16]; +} __attribute__ ((packed)) pin_code_reply_cp; +#define PIN_CODE_REPLY_CP_SIZE 23 + +#define OCF_CHANGE_CONN_PTYPE 0x000F +typedef struct { + __u16 handle; + __u16 pkt_type; +} __attribute__ ((packed)) change_conn_ptype_cp; +#define CHANGE_CONN_PTYPE_CP_SIZE 4 + +#define OCF_AUTH_REQUESTED 0x0011 +typedef struct { + __u16 handle; +} __attribute__ ((packed)) auth_requested_cp; +#define AUTH_REQUESTED_CP_SIZE 2 + +#define OCF_SET_CONN_ENCRYPT 0x0013 +typedef struct { + __u16 handle; + __u8 encrypt; +} __attribute__ ((packed)) set_conn_encrypt_cp; +#define SET_CONN_ENCRYPT_CP_SIZE 3 + +#define OCF_REMOTE_NAME_REQ 0x0019 +typedef struct { + bdaddr_t bdaddr; + __u8 pscan_rep_mode; + __u8 pscan_mode; + __u16 clock_offset; +} __attribute__ ((packed)) remote_name_req_cp; +#define REMOTE_NAME_REQ_CP_SIZE 10 + +#define OCF_READ_REMOTE_FEATURES 0x001B +typedef struct { + __u16 handle; +} __attribute__ ((packed)) read_remote_features_cp; +#define READ_REMOTE_FEATURES_CP_SIZE 2 + +#define OCF_READ_REMOTE_VERSION 0x001D +typedef struct { + __u16 handle; +} __attribute__ ((packed)) read_remote_version_cp; +#define READ_REMOTE_VERSION_CP_SIZE 2 + +/* Link Policy */ +#define OGF_LINK_POLICY 0x02 +#define OCF_ROLE_DISCOVERY 0x0009 +typedef struct { + __u16 handle; +} __attribute__ ((packed)) role_discovery_cp; +#define ROLE_DISCOVERY_CP_SIZE 2 +typedef struct { + __u8 status; + __u16 handle; + __u8 role; +} __attribute__ ((packed)) role_discovery_rp; +#define ROLE_DISCOVERY_RP_SIZE 4 + +#define OCF_READ_LINK_POLICY 0x000C +typedef struct { + __u16 handle; +} __attribute__ ((packed)) read_link_policy_cp; +#define READ_LINK_POLICY_CP_SIZE 2 +typedef struct { + __u8 status; + __u16 handle; + __u16 policy; +} __attribute__ ((packed)) read_link_policy_rp; +#define READ_LINK_POLICY_RP_SIZE 5 + +#define OCF_SWITCH_ROLE 0x000B +typedef struct { + bdaddr_t bdaddr; + __u8 role; +} __attribute__ ((packed)) switch_role_cp; +#define SWITCH_ROLE_CP_SIZE 7 + +#define OCF_WRITE_LINK_POLICY 0x000D +typedef struct { + __u16 handle; + __u16 policy; +} __attribute__ ((packed)) write_link_policy_cp; +#define WRITE_LINK_POLICY_CP_SIZE 4 +typedef struct { + __u8 status; + __u16 handle; +} __attribute__ ((packed)) write_link_policy_rp; +#define WRITE_LINK_POLICY_RP_SIZE 3 + +/* Status params */ +#define OGF_STATUS_PARAM 0x05 + +/* Testing commands */ +#define OGF_TESTING_CMD 0x3e + +/* Vendor specific commands */ +#define OGF_VENDOR_CMD 0x3f + +/* ---- HCI Events ---- */ #define EVT_INQUIRY_COMPLETE 0x01 #define EVT_INQUIRY_RESULT 0x02 @@ -272,11 +457,22 @@ __u8 pscan_rep_mode; __u8 pscan_period_mode; __u8 pscan_mode; - __u8 class[3]; + __u8 dev_class[3]; __u16 clock_offset; } __attribute__ ((packed)) inquiry_info; #define INQUIRY_INFO_SIZE 14 +#define EVT_INQUIRY_RESULT_WITH_RSSI 0x22 +typedef struct { + bdaddr_t bdaddr; + __u8 pscan_rep_mode; + __u8 pscan_period_mode; + __u8 dev_class[3]; + __u16 clock_offset; + __u8 rssi; +} __attribute__ ((packed)) inquiry_info_with_rssi; +#define INQUIRY_INFO_WITH_RSSI_SIZE 14 + #define EVT_CONN_COMPLETE 0x03 typedef struct { __u8 status; @@ -303,6 +499,44 @@ } __attribute__ ((packed)) evt_disconn_complete; #define EVT_DISCONN_COMPLETE_SIZE 4 +#define EVT_AUTH_COMPLETE 0x06 +typedef struct { + __u8 status; + __u16 handle; +} __attribute__ ((packed)) evt_auth_complete; +#define EVT_AUTH_COMPLETE_SIZE 3 + +#define EVT_REMOTE_NAME_REQ_COMPLETE 0x07 +typedef struct { + __u8 status; + bdaddr_t bdaddr; + __u8 name[248]; +} __attribute__ ((packed)) evt_remote_name_req_complete; +#define EVT_REMOTE_NAME_REQ_COMPLETE_SIZE 255 + +#define EVT_ENCRYPT_CHANGE 0x08 +typedef struct { + __u8 status; + __u16 handle; + __u8 encrypt; +} __attribute__ ((packed)) evt_encrypt_change; +#define EVT_ENCRYPT_CHANGE_SIZE 5 + +#define EVT_QOS_SETUP_COMPLETE 0x0D +typedef struct { + __u8 service_type; + __u32 token_rate; + __u32 peak_bandwidth; + __u32 latency; + __u32 delay_variation; +} __attribute__ ((packed)) hci_qos; +typedef struct { + __u8 status; + __u16 handle; + hci_qos qos; +} __attribute__ ((packed)) evt_qos_setup_complete; +#define EVT_QOS_SETUP_COMPLETE_SIZE 20 + #define EVT_CMD_COMPLETE 0x0e typedef struct { __u8 ncmd; @@ -321,16 +555,78 @@ #define EVT_NUM_COMP_PKTS 0x13 typedef struct { __u8 num_hndl; - /* variable lenght part */ + /* variable length part */ } __attribute__ ((packed)) evt_num_comp_pkts; #define EVT_NUM_COMP_PKTS_SIZE 1 -#define EVT_HCI_DEV_EVENT 0xfd +#define EVT_ROLE_CHANGE 0x12 +typedef struct { + __u8 status; + bdaddr_t bdaddr; + __u8 role; +} __attribute__ ((packed)) evt_role_change; +#define EVT_ROLE_CHANGE_SIZE 8 + +#define EVT_PIN_CODE_REQ 0x16 +typedef struct { + bdaddr_t bdaddr; +} __attribute__ ((packed)) evt_pin_code_req; +#define EVT_PIN_CODE_REQ_SIZE 6 + +#define EVT_LINK_KEY_REQ 0x17 +typedef struct { + bdaddr_t bdaddr; +} __attribute__ ((packed)) evt_link_key_req; +#define EVT_LINK_KEY_REQ_SIZE 6 + +#define EVT_LINK_KEY_NOTIFY 0x18 +typedef struct { + bdaddr_t bdaddr; + __u8 link_key[16]; + __u8 key_type; +} __attribute__ ((packed)) evt_link_key_notify; +#define EVT_LINK_KEY_NOTIFY_SIZE 23 + +#define EVT_READ_REMOTE_FEATURES_COMPLETE 0x0B +typedef struct { + __u8 status; + __u16 handle; + __u8 features[8]; +} __attribute__ ((packed)) evt_read_remote_features_complete; +#define EVT_READ_REMOTE_FEATURES_COMPLETE_SIZE 11 + +#define EVT_READ_REMOTE_VERSION_COMPLETE 0x0C +typedef struct { + __u8 status; + __u16 handle; + __u8 lmp_ver; + __u16 manufacturer; + __u16 lmp_subver; +} __attribute__ ((packed)) evt_read_remote_version_complete; +#define EVT_READ_REMOTE_VERSION_COMPLETE_SIZE 8 + +/* Internal events generated by BlueZ stack */ +#define EVT_STACK_INTERNAL 0xfd +typedef struct { + __u16 type; + __u8 data[0]; +} __attribute__ ((packed)) evt_stack_internal; +#define EVT_STACK_INTERNAL_SIZE 2 + +#define EVT_SI_DEVICE 0x01 +typedef struct { + __u16 event; + __u16 dev_id; +} __attribute__ ((packed)) evt_si_device; +#define EVT_SI_DEVICE_SIZE 4 + +#define EVT_SI_SECURITY 0x02 typedef struct { __u16 event; - __u16 param; -} __attribute__ ((packed)) evt_hci_dev_event; -#define EVT_HCI_DEV_EVENT_SIZE 4 + __u16 proto; + __u16 subproto; + __u8 incomming; +} __attribute__ ((packed)) evt_si_security; /* -------- HCI Packet structures -------- */ #define HCI_TYPE_LEN 1 @@ -369,14 +665,14 @@ #define acl_handle(h) (h & 0x0fff) #define acl_flags(h) (h >> 12) -#endif /* _NO_HCI_DEFS */ - /* HCI Socket options */ -#define HCI_DATA_DIR 0x0001 -#define HCI_FILTER 0x0002 +#define HCI_DATA_DIR 1 +#define HCI_FILTER 2 +#define HCI_TIME_STAMP 3 /* HCI CMSG flags */ #define HCI_CMSG_DIR 0x0001 +#define HCI_CMSG_TSTAMP 0x0002 struct sockaddr_hci { sa_family_t hci_family; @@ -387,27 +683,29 @@ struct hci_filter { __u32 type_mask; __u32 event_mask[2]; + __u16 opcode; }; -struct hci_dev_req { - __u16 dev_id; - __u32 dev_opt; -}; - -struct hci_dev_list_req { - __u16 dev_num; - struct hci_dev_req dev_req[0]; /* hci_dev_req structures */ -}; +#define HCI_FLT_TYPE_BITS 31 +#define HCI_FLT_EVENT_BITS 63 +#define HCI_FLT_OGF_BITS 63 +#define HCI_FLT_OCF_BITS 127 -struct hci_inquiry_req { - __u16 dev_id; - __u16 flags; - __u8 lap[3]; - __u8 length; - __u8 num_rsp; -}; -#define IREQ_CACHE_FLUSH 0x0001 +#if BITS_PER_LONG == 64 +static inline void hci_set_bit(int nr, void *addr) +{ + *((__u32 *) addr + (nr >> 5)) |= ((__u32) 1 << (nr & 31)); +} +static inline int hci_test_bit(int nr, void *addr) +{ + return *((__u32 *) addr + (nr >> 5)) & ((__u32) 1 << (nr & 31)); +} +#else +#define hci_set_bit set_bit +#define hci_test_bit test_bit +#endif +/* Ioctl requests structures */ struct hci_dev_stats { __u32 err_rx; __u32 err_tx; @@ -433,11 +731,13 @@ __u8 features[8]; __u32 pkt_type; + __u32 link_policy; + __u32 link_mode; __u16 acl_mtu; - __u16 acl_max; + __u16 acl_pkts; __u16 sco_mtu; - __u16 sco_max; + __u16 sco_pkts; struct hci_dev_stats stat; }; @@ -445,6 +745,20 @@ struct hci_conn_info { __u16 handle; bdaddr_t bdaddr; + __u8 type; + __u8 out; + __u16 state; + __u32 link_mode; +}; + +struct hci_dev_req { + __u16 dev_id; + __u32 dev_opt; +}; + +struct hci_dev_list_req { + __u16 dev_num; + struct hci_dev_req dev_req[0]; /* hci_dev_req structures */ }; struct hci_conn_list_req { @@ -453,4 +767,26 @@ struct hci_conn_info conn_info[0]; }; +struct hci_conn_info_req { + bdaddr_t bdaddr; + __u8 type; + struct hci_conn_info conn_info[0]; +}; + +struct hci_inquiry_req { + __u16 dev_id; + __u16 flags; + __u8 lap[3]; + __u8 length; + __u8 num_rsp; +}; +#define IREQ_CACHE_FLUSH 0x0001 + +struct hci_remotename_req { + __u16 dev_id; + __u16 flags; + bdaddr_t bdaddr; + __u8 name[248]; +}; + #endif /* __HCI_H */ --- linux/include/net/bluetooth/hci_uart.h~bluetooth-2.4.18-mh11 +++ linux/include/net/bluetooth/hci_uart.h -/* - BlueZ - Bluetooth protocol stack for Linux - Copyright (C) 2000-2001 Qualcomm Incorporated - - Written 2000,2001 by Maxim Krasnyansky - - 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 2001/06/02 01:40:08 maxk Exp $ - */ - -#ifndef N_HCI -#define N_HCI 15 -#endif - -#ifdef __KERNEL__ - -#define tty2n_hci(tty) ((struct n_hci *)((tty)->disc_data)) -#define n_hci2tty(n_hci) ((n_hci)->tty) - -struct n_hci { - struct tty_struct *tty; - struct hci_dev hdev; - - struct sk_buff_head txq; - unsigned long tx_state; - - spinlock_t rx_lock; - unsigned long rx_state; - unsigned long rx_count; - struct sk_buff *rx_skb; -}; - -/* Transmit states */ -#define TRANS_SENDING 1 -#define TRANS_WAKEUP 2 - -/* Receiver States */ -#define WAIT_PACKET_TYPE 0 -#define WAIT_EVENT_HDR 1 -#define WAIT_ACL_HDR 2 -#define WAIT_SCO_HDR 3 -#define WAIT_DATA 4 - -#endif /* __KERNEL__ */ --- linux/include/net/bluetooth/hci_usb.h~bluetooth-2.4.18-mh11 +++ linux/include/net/bluetooth/hci_usb.h -/* - BlueZ - Bluetooth protocol stack for Linux - Copyright (C) 2000-2001 Qualcomm Incorporated - - Written 2000,2001 by Maxim Krasnyansky - - 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_usb.h,v 1.3 2001/06/02 01:40:08 maxk Exp $ - */ - -#ifdef __KERNEL__ - -/* 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_CTRL_REQ 0x20 - -struct hci_usb { - struct usb_device *udev; - - devrequest dev_req; - struct urb *ctrl_urb; - struct urb *intr_urb; - struct urb *read_urb; - struct urb *write_urb; - - __u8 *read_buf; - __u8 *intr_buf; - struct sk_buff *intr_skb; - int intr_count; - - __u8 bulk_out_ep_addr; - __u8 bulk_in_ep_addr; - __u8 intr_in_ep_addr; - __u8 intr_in_interval; - - struct hci_dev hdev; - - unsigned long tx_state; - struct sk_buff_head tx_ctrl_q; - struct sk_buff_head tx_write_q; -}; - -/* Transmit states */ -#define HCI_TX_CTRL 1 -#define HCI_TX_WRITE 2 - -#endif /* __KERNEL__ */ --- linux/include/net/bluetooth/hci_vhci.h~bluetooth-2.4.18-mh11 +++ linux/include/net/bluetooth/hci_vhci.h -/* - BlueZ - Bluetooth protocol stack for Linux - Copyright (C) 2000-2001 Qualcomm Incorporated - - Written 2000,2001 by Maxim Krasnyansky - - 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.2 2001/08/01 01:02:20 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 */ --- linux/include/net/bluetooth/l2cap_core.h~bluetooth-2.4.18-mh11 +++ linux/include/net/bluetooth/l2cap_core.h -/* - BlueZ - Bluetooth protocol stack for Linux - Copyright (C) 2000-2001 Qualcomm Incorporated - - Written 2000,2001 by Maxim Krasnyansky - - 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: l2cap_core.h,v 1.6 2001/08/03 04:19:49 maxk Exp $ - */ - -#ifndef __L2CAP_CORE_H -#define __L2CAP_CORE_H - -#ifdef __KERNEL__ - -/* ----- L2CAP interface ----- */ -struct l2cap_iff { - struct list_head list; - struct hci_dev *hdev; - bdaddr_t *bdaddr; - __u16 mtu; - spinlock_t lock; - struct list_head conn_list; -}; - -static inline void l2cap_iff_lock(struct l2cap_iff *iff) -{ - spin_lock(&iff->lock); -} - -static inline void l2cap_iff_unlock(struct l2cap_iff *iff) -{ - spin_unlock(&iff->lock); -} - -/* ----- L2CAP connections ----- */ -struct l2cap_chan_list { - struct sock *head; - rwlock_t lock; - long num; -}; - -struct l2cap_conn { - struct l2cap_iff *iff; - struct list_head list; - - struct hci_conn *hconn; - - __u16 state; - __u8 out; - bdaddr_t src; - bdaddr_t dst; - - spinlock_t lock; - atomic_t refcnt; - - struct sk_buff *rx_skb; - __u32 rx_len; - __u8 rx_ident; - __u8 tx_ident; - - struct l2cap_chan_list chan_list; - - struct timer_list timer; -}; - -static inline void __l2cap_conn_link(struct l2cap_iff *iff, struct l2cap_conn *c) -{ - list_add(&c->list, &iff->conn_list); -} - -static inline void __l2cap_conn_unlink(struct l2cap_iff *iff, struct l2cap_conn *c) -{ - list_del(&c->list); -} - -/* ----- L2CAP channel and socket info ----- */ -#define l2cap_pi(sk) ((struct l2cap_pinfo *) &sk->protinfo) - -struct l2cap_accept_q { - struct sock *head; - struct sock *tail; -}; - -struct l2cap_pinfo { - bdaddr_t src; - bdaddr_t dst; - __u16 psm; - __u16 dcid; - __u16 scid; - __u32 flags; - - __u16 imtu; - __u16 omtu; - __u16 flush_to; - - __u8 conf_state; - __u16 conf_mtu; - - __u8 ident; - - struct l2cap_conn *conn; - struct sock *next_c; - struct sock *prev_c; - - struct sock *parent; - struct sock *next_q; - struct sock *prev_q; - - struct l2cap_accept_q accept_q; -}; - -#define CONF_REQ_SENT 0x01 -#define CONF_INPUT_DONE 0x02 -#define CONF_OUTPUT_DONE 0x04 - -extern struct bluez_sock_list l2cap_sk_list; -extern struct list_head l2cap_iff_list; -extern rwlock_t l2cap_rt_lock; - -extern void l2cap_register_proc(void); -extern void l2cap_unregister_proc(void); - -#endif /* __KERNEL__ */ - -#endif /* __L2CAP_CORE_H */ --- linux/include/net/bluetooth/l2cap.h~bluetooth-2.4.18-mh11 2001-09-07 18:28:38.000000000 +0200 +++ linux/include/net/bluetooth/l2cap.h 2004-01-25 23:37:39.000000000 +0100 @@ -23,22 +23,17 @@ */ /* - * $Id: l2cap.h,v 1.5 2001/06/14 21:28:26 maxk Exp $ + * $Id: l2cap.h,v 1.1.1.1 2002/03/08 21:03:15 maxk Exp $ */ #ifndef __L2CAP_H #define __L2CAP_H -#include -#include - /* L2CAP defaults */ #define L2CAP_DEFAULT_MTU 672 #define L2CAP_DEFAULT_FLUSH_TO 0xFFFF #define L2CAP_CONN_TIMEOUT (HZ * 40) -#define L2CAP_DISCONN_TIMEOUT (HZ * 2) -#define L2CAP_CONN_IDLE_TIMEOUT (HZ * 60) /* L2CAP socket address */ struct sockaddr_l2 { @@ -47,17 +42,12 @@ bdaddr_t l2_bdaddr; }; -/* set/get sockopt defines */ -#define L2CAP_OPTIONS 0x01 +/* Socket options */ +#define L2CAP_OPTIONS 0x01 struct l2cap_options { __u16 omtu; __u16 imtu; __u16 flush_to; - __u32 token_rate; - __u32 bucket_size; - __u32 pick_band; - __u32 latency; - __u32 delay_var; }; #define L2CAP_CONNINFO 0x02 @@ -65,6 +55,27 @@ __u16 hci_handle; }; +#define L2CAP_LM 0x03 +#define L2CAP_LM_MASTER 0x0001 +#define L2CAP_LM_AUTH 0x0002 +#define L2CAP_LM_ENCRYPT 0x0004 +#define L2CAP_LM_TRUSTED 0x0008 +#define L2CAP_LM_RELIABLE 0x0010 + +#define L2CAP_QOS 0x04 +struct l2cap_qos { + __u16 service_type; + __u32 token_rate; + __u32 token_bucket_size; + __u32 peak_bandwidth; + __u32 latency; + __u32 delay_variation; +}; + +#define L2CAP_SERV_NO_TRAFFIC 0x00 +#define L2CAP_SERV_BEST_EFFORT 0x01 +#define L2CAP_SERV_GUARANTEED 0x02 + /* L2CAP command codes */ #define L2CAP_COMMAND_REJ 0x01 #define L2CAP_CONN_REQ 0x02 @@ -79,7 +90,6 @@ #define L2CAP_INFO_RSP 0x0b /* L2CAP structures */ - typedef struct { __u16 len; __u16 cid; @@ -112,11 +122,17 @@ } __attribute__ ((packed)) l2cap_conn_rsp; #define L2CAP_CONN_RSP_SIZE 8 -#define L2CAP_CONN_SUCCESS 0x0000 -#define L2CAP_CONN_PEND 0x0001 -#define L2CAP_CONN_BAD_PSM 0x0002 -#define L2CAP_CONN_SEC_BLOCK 0x0003 -#define L2CAP_CONN_NO_MEM 0x0004 +/* connect result */ +#define L2CAP_CR_SUCCESS 0x0000 +#define L2CAP_CR_PEND 0x0001 +#define L2CAP_CR_BAD_PSM 0x0002 +#define L2CAP_CR_SEC_BLOCK 0x0003 +#define L2CAP_CR_NO_MEM 0x0004 + +/* connect status */ +#define L2CAP_CS_NO_INFO 0x0000 +#define L2CAP_CS_AUTHEN_PEND 0x0001 +#define L2CAP_CS_AUTHOR_PEND 0x0002 typedef struct { __u16 dcid; @@ -147,6 +163,8 @@ #define L2CAP_CONF_FLUSH_TO 0x02 #define L2CAP_CONF_QOS 0x03 +#define L2CAP_CONF_MAX_SIZE 22 + typedef struct { __u16 dcid; __u16 scid; @@ -159,4 +177,74 @@ } __attribute__ ((packed)) l2cap_disconn_rsp; #define L2CAP_DISCONN_RSP_SIZE 4 +typedef struct { + __u16 type; + __u8 data[0]; +} __attribute__ ((packed)) l2cap_info_req; +#define L2CAP_INFO_REQ_SIZE 2 + +typedef struct { + __u16 type; + __u16 result; + __u8 data[0]; +} __attribute__ ((packed)) l2cap_info_rsp; +#define L2CAP_INFO_RSP_SIZE 4 + +/* ----- L2CAP connections ----- */ +struct l2cap_chan_list { + struct sock *head; + rwlock_t lock; + long num; +}; + +struct l2cap_conn { + struct hci_conn *hcon; + + bdaddr_t *dst; + bdaddr_t *src; + + unsigned int mtu; + + spinlock_t lock; + + struct sk_buff *rx_skb; + __u32 rx_len; + __u8 rx_ident; + __u8 tx_ident; + + struct l2cap_chan_list chan_list; +}; + +/* ----- L2CAP channel and socket info ----- */ +#define l2cap_pi(sk) ((struct l2cap_pinfo *) &sk->tp_pinfo) + +struct l2cap_pinfo { + __u16 psm; + __u16 dcid; + __u16 scid; + + __u16 imtu; + __u16 omtu; + __u16 flush_to; + + __u32 link_mode; + + __u8 conf_state; + __u8 conf_retry; + __u16 conf_mtu; + + __u8 ident; + + struct l2cap_conn *conn; + struct sock *next_c; + struct sock *prev_c; +}; + +#define L2CAP_CONF_REQ_SENT 0x01 +#define L2CAP_CONF_INPUT_DONE 0x02 +#define L2CAP_CONF_OUTPUT_DONE 0x04 +#define L2CAP_CONF_MAX_RETRIES 2 + +void l2cap_load(void); + #endif /* __L2CAP_H */ --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/include/net/bluetooth/rfcomm.h 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1,361 @@ +/* + RFCOMM implementation for Linux Bluetooth stack (BlueZ). + Copyright (C) 2002 Maxim Krasnyansky + Copyright (C) 2002 Marcel Holtmann + + 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. +*/ + +/* + RPN support - Dirk Husemann +*/ + +/* + * $Id: rfcomm.h,v 1.31 2002/10/18 20:12:11 maxk Exp $ + */ + +#ifndef __RFCOMM_H +#define __RFCOMM_H + +#define RFCOMM_PSM 3 + +#define RFCOMM_CONN_TIMEOUT (HZ * 30) +#define RFCOMM_DISC_TIMEOUT (HZ * 20) + +#define RFCOMM_DEFAULT_MTU 127 +#define RFCOMM_DEFAULT_CREDITS 7 + +#define RFCOMM_MAX_L2CAP_MTU 1024 +#define RFCOMM_MAX_CREDITS 40 + +#define RFCOMM_SKB_HEAD_RESERVE 8 +#define RFCOMM_SKB_TAIL_RESERVE 2 +#define RFCOMM_SKB_RESERVE (RFCOMM_SKB_HEAD_RESERVE + RFCOMM_SKB_TAIL_RESERVE) + +#define RFCOMM_SABM 0x2f +#define RFCOMM_DISC 0x43 +#define RFCOMM_UA 0x63 +#define RFCOMM_DM 0x0f +#define RFCOMM_UIH 0xef + +#define RFCOMM_TEST 0x08 +#define RFCOMM_FCON 0x28 +#define RFCOMM_FCOFF 0x18 +#define RFCOMM_MSC 0x38 +#define RFCOMM_RPN 0x24 +#define RFCOMM_RLS 0x14 +#define RFCOMM_PN 0x20 +#define RFCOMM_NSC 0x04 + +#define RFCOMM_V24_FC 0x02 +#define RFCOMM_V24_RTC 0x04 +#define RFCOMM_V24_RTR 0x08 +#define RFCOMM_V24_IC 0x40 +#define RFCOMM_V24_DV 0x80 + +#define RFCOMM_RPN_BR_2400 0x0 +#define RFCOMM_RPN_BR_4800 0x1 +#define RFCOMM_RPN_BR_7200 0x2 +#define RFCOMM_RPN_BR_9600 0x3 +#define RFCOMM_RPN_BR_19200 0x4 +#define RFCOMM_RPN_BR_38400 0x5 +#define RFCOMM_RPN_BR_57600 0x6 +#define RFCOMM_RPN_BR_115200 0x7 +#define RFCOMM_RPN_BR_230400 0x8 + +#define RFCOMM_RPN_DATA_5 0x0 +#define RFCOMM_RPN_DATA_6 0x1 +#define RFCOMM_RPN_DATA_7 0x2 +#define RFCOMM_RPN_DATA_8 0x3 + +#define RFCOMM_RPN_STOP_1 0 +#define RFCOMM_RPN_STOP_15 1 + +#define RFCOMM_RPN_PARITY_NONE 0x0 +#define RFCOMM_RPN_PARITY_ODD 0x4 +#define RFCOMM_RPN_PARITY_EVEN 0x5 +#define RFCOMM_RPN_PARITY_MARK 0x6 +#define RFCOMM_RPN_PARITY_SPACE 0x7 + +#define RFCOMM_RPN_FLOW_NONE 0x00 + +#define RFCOMM_RPN_XON_CHAR 0x11 +#define RFCOMM_RPN_XOFF_CHAR 0x13 + +#define RFCOMM_RPN_PM_BITRATE 0x0001 +#define RFCOMM_RPN_PM_DATA 0x0002 +#define RFCOMM_RPN_PM_STOP 0x0004 +#define RFCOMM_RPN_PM_PARITY 0x0008 +#define RFCOMM_RPN_PM_PARITY_TYPE 0x0010 +#define RFCOMM_RPN_PM_XON 0x0020 +#define RFCOMM_RPN_PM_XOFF 0x0040 +#define RFCOMM_RPN_PM_FLOW 0x3F00 + +#define RFCOMM_RPN_PM_ALL 0x3F7F + +struct rfcomm_hdr { + u8 addr; + u8 ctrl; + u8 len; // Actual size can be 2 bytes +} __attribute__ ((packed)); + +struct rfcomm_cmd { + u8 addr; + u8 ctrl; + u8 len; + u8 fcs; +} __attribute__ ((packed)); + +struct rfcomm_mcc { + u8 type; + u8 len; +} __attribute__ ((packed)); + +struct rfcomm_pn { + u8 dlci; + u8 flow_ctrl; + u8 priority; + u8 ack_timer; + u16 mtu; + u8 max_retrans; + u8 credits; +} __attribute__ ((packed)); + +struct rfcomm_rpn { + u8 dlci; + u8 bit_rate; + u8 line_settings; + u8 flow_ctrl; + u8 xon_char; + u8 xoff_char; + u16 param_mask; +} __attribute__ ((packed)); + +struct rfcomm_rls { + u8 dlci; + u8 status; +} __attribute__ ((packed)); + +struct rfcomm_msc { + u8 dlci; + u8 v24_sig; +} __attribute__ ((packed)); + +/* ---- Core structures, flags etc ---- */ + +struct rfcomm_session { + struct list_head list; + struct socket *sock; + unsigned long state; + unsigned long flags; + atomic_t refcnt; + int initiator; + + /* Default DLC parameters */ + int cfc; + uint mtu; + + struct list_head dlcs; +}; + +struct rfcomm_dlc { + struct list_head list; + struct rfcomm_session *session; + struct sk_buff_head tx_queue; + struct timer_list timer; + + spinlock_t lock; + unsigned long state; + unsigned long flags; + atomic_t refcnt; + u8 dlci; + u8 addr; + u8 priority; + u8 v24_sig; + u8 mscex; + + uint mtu; + uint cfc; + uint rx_credits; + uint tx_credits; + + void *owner; + + void (*data_ready)(struct rfcomm_dlc *d, struct sk_buff *skb); + void (*state_change)(struct rfcomm_dlc *d, int err); + void (*modem_status)(struct rfcomm_dlc *d, u8 v24_sig); +}; + +/* DLC and session flags */ +#define RFCOMM_RX_THROTTLED 0 +#define RFCOMM_TX_THROTTLED 1 +#define RFCOMM_MSC_PENDING 2 +#define RFCOMM_TIMED_OUT 3 + +/* Scheduling flags and events */ +#define RFCOMM_SCHED_STATE 0 +#define RFCOMM_SCHED_RX 1 +#define RFCOMM_SCHED_TX 2 +#define RFCOMM_SCHED_TIMEO 3 +#define RFCOMM_SCHED_WAKEUP 31 + +/* MSC exchange flags */ +#define RFCOMM_MSCEX_TX 1 +#define RFCOMM_MSCEX_RX 2 +#define RFCOMM_MSCEX_OK (RFCOMM_MSCEX_TX + RFCOMM_MSCEX_RX) + +/* CFC states */ +#define RFCOMM_CFC_UNKNOWN -1 +#define RFCOMM_CFC_DISABLED 0 +#define RFCOMM_CFC_ENABLED RFCOMM_MAX_CREDITS + +extern struct task_struct *rfcomm_thread; +extern unsigned long rfcomm_event; + +static inline void rfcomm_schedule(uint event) +{ + if (!rfcomm_thread) + return; + set_bit(RFCOMM_SCHED_WAKEUP, &rfcomm_event); + wake_up_process(rfcomm_thread); +} + +extern struct semaphore rfcomm_sem; +#define rfcomm_lock() down(&rfcomm_sem); +#define rfcomm_unlock() up(&rfcomm_sem); + +/* ---- RFCOMM DLCs (channels) ---- */ +struct rfcomm_dlc *rfcomm_dlc_alloc(int prio); +void rfcomm_dlc_free(struct rfcomm_dlc *d); +int rfcomm_dlc_open(struct rfcomm_dlc *d, bdaddr_t *src, bdaddr_t *dst, u8 channel); +int rfcomm_dlc_close(struct rfcomm_dlc *d, int reason); +int rfcomm_dlc_send(struct rfcomm_dlc *d, struct sk_buff *skb); +int rfcomm_dlc_set_modem_status(struct rfcomm_dlc *d, u8 v24_sig); +int rfcomm_dlc_get_modem_status(struct rfcomm_dlc *d, u8 *v24_sig); + +#define rfcomm_dlc_lock(d) spin_lock(&d->lock) +#define rfcomm_dlc_unlock(d) spin_unlock(&d->lock) + +static inline void rfcomm_dlc_hold(struct rfcomm_dlc *d) +{ + atomic_inc(&d->refcnt); +} + +static inline void rfcomm_dlc_put(struct rfcomm_dlc *d) +{ + if (atomic_dec_and_test(&d->refcnt)) + rfcomm_dlc_free(d); +} + +extern void FASTCALL(__rfcomm_dlc_throttle(struct rfcomm_dlc *d)); +extern void FASTCALL(__rfcomm_dlc_unthrottle(struct rfcomm_dlc *d)); + +static inline void rfcomm_dlc_throttle(struct rfcomm_dlc *d) +{ + if (!test_and_set_bit(RFCOMM_RX_THROTTLED, &d->flags)) + __rfcomm_dlc_throttle(d); +} + +static inline void rfcomm_dlc_unthrottle(struct rfcomm_dlc *d) +{ + if (test_and_clear_bit(RFCOMM_RX_THROTTLED, &d->flags)) + __rfcomm_dlc_unthrottle(d); +} + +/* ---- RFCOMM sessions ---- */ +struct rfcomm_session *rfcomm_session_add(struct socket *sock, int state); +struct rfcomm_session *rfcomm_session_get(bdaddr_t *src, bdaddr_t *dst); +struct rfcomm_session *rfcomm_session_create(bdaddr_t *src, bdaddr_t *dst, int *err); +void rfcomm_session_del(struct rfcomm_session *s); +void rfcomm_session_close(struct rfcomm_session *s, int err); +void rfcomm_session_getaddr(struct rfcomm_session *s, bdaddr_t *src, bdaddr_t *dst); + +static inline void rfcomm_session_hold(struct rfcomm_session *s) +{ + atomic_inc(&s->refcnt); +} + +static inline void rfcomm_session_put(struct rfcomm_session *s) +{ + if (atomic_dec_and_test(&s->refcnt)) + rfcomm_session_del(s); +} + +/* ---- RFCOMM chechsum ---- */ +extern u8 rfcomm_crc_table[]; + +/* ---- RFCOMM sockets ---- */ +struct sockaddr_rc { + sa_family_t rc_family; + bdaddr_t rc_bdaddr; + u8 rc_channel; +}; + +#define rfcomm_pi(sk) ((struct rfcomm_pinfo *) &sk->tp_pinfo) + +struct rfcomm_pinfo { + struct rfcomm_dlc *dlc; + u8 channel; +}; + +int rfcomm_init_sockets(void); +void rfcomm_cleanup_sockets(void); + +int rfcomm_connect_ind(struct rfcomm_session *s, u8 channel, struct rfcomm_dlc **d); + +/* ---- RFCOMM TTY ---- */ +#define RFCOMM_MAX_DEV 256 + +#define RFCOMMCREATEDEV _IOW('R', 200, int) +#define RFCOMMRELEASEDEV _IOW('R', 201, int) +#define RFCOMMGETDEVLIST _IOR('R', 210, int) +#define RFCOMMGETDEVINFO _IOR('R', 211, int) +#define RFCOMMSTEALDLC _IOW('R', 220, int) + +#define RFCOMM_REUSE_DLC 0 +#define RFCOMM_RELEASE_ONHUP 1 +#define RFCOMM_HANGUP_NOW 2 +#define RFCOMM_TTY_ATTACHED 3 + +struct rfcomm_dev_req { + s16 dev_id; + u32 flags; + bdaddr_t src; + bdaddr_t dst; + u8 channel; +}; + +struct rfcomm_dev_info { + s16 id; + u32 flags; + u16 state; + bdaddr_t src; + bdaddr_t dst; + u8 channel; +}; + +struct rfcomm_dev_list_req { + u16 dev_num; + struct rfcomm_dev_info dev_info[0]; +}; + +int rfcomm_dev_ioctl(struct sock *sk, unsigned int cmd, unsigned long arg); +int rfcomm_init_ttys(void); +void rfcomm_cleanup_ttys(void); + +#endif /* __RFCOMM_H */ --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/include/net/bluetooth/sco.h 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1,81 @@ +/* + BlueZ - Bluetooth protocol stack for Linux + Copyright (C) 2000-2001 Qualcomm Incorporated + + Written 2000,2001 by Maxim Krasnyansky + + 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: sco.h,v 1.1.1.1 2002/03/08 21:03:15 maxk Exp $ + */ + +#ifndef __SCO_H +#define __SCO_H + +/* SCO defaults */ +#define SCO_DEFAULT_MTU 500 +#define SCO_DEFAULT_FLUSH_TO 0xFFFF + +#define SCO_CONN_TIMEOUT (HZ * 40) +#define SCO_DISCONN_TIMEOUT (HZ * 2) +#define SCO_CONN_IDLE_TIMEOUT (HZ * 60) + +/* SCO socket address */ +struct sockaddr_sco { + sa_family_t sco_family; + bdaddr_t sco_bdaddr; +}; + +/* set/get sockopt defines */ +#define SCO_OPTIONS 0x01 +struct sco_options { + __u16 mtu; +}; + +#define SCO_CONNINFO 0x02 +struct sco_conninfo { + __u16 hci_handle; +}; + +/* ---- SCO connections ---- */ +struct sco_conn { + struct hci_conn *hcon; + + bdaddr_t *dst; + bdaddr_t *src; + + spinlock_t lock; + struct sock *sk; + + unsigned int mtu; +}; + +#define sco_conn_lock(c) spin_lock(&c->lock); +#define sco_conn_unlock(c) spin_unlock(&c->lock); + +/* ----- SCO socket info ----- */ +#define sco_pi(sk) ((struct sco_pinfo *) &sk->tp_pinfo) + +struct sco_pinfo { + __u32 flags; + struct sco_conn *conn; +}; + +#endif /* __SCO_H */ --- linux/include/pcmcia/ciscode.h~bluetooth-2.4.18-mh11 2001-12-21 18:42:04.000000000 +0100 +++ linux/include/pcmcia/ciscode.h 2004-01-25 23:37:39.000000000 +0100 @@ -1,5 +1,5 @@ /* - * ciscode.h 1.48 2001/08/24 12:16:12 + * ciscode.h 1.57 2002/11/03 20:38:14 * * The contents of this file are subject to the Mozilla Public License * Version 1.1 (the "License"); you may not use this file except in @@ -60,6 +60,10 @@ #define PRODID_INTEL_DUAL_RS232 0x0301 #define PRODID_INTEL_2PLUS 0x8422 +#define MANFID_KME 0x0032 +#define PRODID_KME_KXLC005_A 0x0704 +#define PRODID_KME_KXLC005_B 0x2904 + #define MANFID_LINKSYS 0x0143 #define PRODID_LINKSYS_PCMLM28 0xc0ab #define PRODID_LINKSYS_3400 0x3341 @@ -94,6 +98,8 @@ #define PRODID_OSITECH_JACK_336 0x0007 #define PRODID_OSITECH_SEVEN 0x0008 +#define MANFID_OXSEMI 0x0279 + #define MANFID_PIONEER 0x000b #define MANFID_PSION 0x016c @@ -103,6 +109,7 @@ #define PRODID_QUATECH_SPP100 0x0003 #define PRODID_QUATECH_DUAL_RS232 0x0012 #define PRODID_QUATECH_DUAL_RS232_D1 0x0007 +#define PRODID_QUATECH_DUAL_RS232_D2 0x0052 #define PRODID_QUATECH_QUAD_RS232 0x001b #define PRODID_QUATECH_DUAL_RS422 0x000e #define PRODID_QUATECH_QUAD_RS422 0x0045 @@ -120,9 +127,12 @@ #define MANFID_TDK 0x0105 #define PRODID_TDK_CF010 0x0900 +#define PRODID_TDK_GN3410 0x4815 #define MANFID_TOSHIBA 0x0098 +#define MANFID_UNGERMANN 0x02c0 + #define MANFID_XIRCOM 0x0105 #endif /* _LINUX_CISCODE_H */ --- linux/kernel/ksyms.c~bluetooth-2.4.18-mh11 2004-01-25 23:28:30.000000000 +0100 +++ linux/kernel/ksyms.c 2004-01-25 23:37:39.000000000 +0100 @@ -47,6 +47,7 @@ #include #include #include +#include #include #if defined(CONFIG_PROC_FS) @@ -543,6 +544,13 @@ EXPORT_SYMBOL(strspn); EXPORT_SYMBOL(strsep); +#ifdef CONFIG_FW_LOADER +EXPORT_SYMBOL(release_firmware); +EXPORT_SYMBOL(request_firmware); +EXPORT_SYMBOL(request_firmware_nowait); +EXPORT_SYMBOL(register_firmware); +#endif + /* software interrupts */ EXPORT_SYMBOL(tasklet_hi_vec); EXPORT_SYMBOL(tasklet_vec); --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/lib/Config.in 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1,12 @@ +# +# Library configuration +# +mainmenu_option next_comment +comment 'Library routines' + +if [ "$CONFIG_EXPERIMENTAL" = "y" -a \ + "$CONFIG_HOTPLUG" = "y" ]; then + tristate 'Hotplug firmware loading support (EXPERIMENTAL)' CONFIG_FW_LOADER +fi + +endmenu --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/lib/firmware_class.c 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1,573 @@ +/* + * firmware_class.c - Multi purpose firmware loading support + * + * Copyright (c) 2003 Manuel Estrada Sainz + * + * Please see Documentation/firmware_class/ for more information. + * + */ +/* + * Based on kernel/kmod.c and drivers/usb/usb.c + */ +/* + kernel/kmod.c + Kirk Petersen + + Reorganized not to be a daemon by Adam Richter, with guidance + from Greg Zornetzer. + + Modified to avoid chroot and file sharing problems. + Mikael Pettersson + + Limit the concurrent number of kmod modprobes to catch loops from + "modprobe needs a service that is in a module". + Keith Owens December 1999 + + Unblock all signals when we exec a usermode process. + Shuu Yamaguchi December 2000 +*/ +/* + * drivers/usb/usb.c + * + * (C) Copyright Linus Torvalds 1999 + * (C) Copyright Johannes Erdfelt 1999-2001 + * (C) Copyright Andreas Gal 1999 + * (C) Copyright Gregory P. Smith 1999 + * (C) Copyright Deti Fliegl 1999 (new USB architecture) + * (C) Copyright Randy Dunlap 2000 + * (C) Copyright David Brownell 2000 (kernel hotplug, usb_device_id) + * (C) Copyright Yggdrasil Computing, Inc. 2000 + * (usb_device_id matching changes by Adam J. Richter) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "linux/firmware.h" + +MODULE_AUTHOR("Manuel Estrada Sainz "); +MODULE_DESCRIPTION("Multi purpose firmware loading support"); +MODULE_LICENSE("GPL"); + +#define err(format, arg...) \ + printk(KERN_ERR "%s:%s: " format "\n",__FILE__, __FUNCTION__ , ## arg) +#define warn(format, arg...) \ + printk(KERN_WARNING "%s:%s: " format "\n",__FILE__, __FUNCTION__ , ## arg) +#define dbg(format, arg...) \ + printk(KERN_DEBUG "%s:%s: " format "\n",__FILE__, __FUNCTION__ , ## arg) + +static int loading_timeout = 10; /* In seconds */ +static struct proc_dir_entry *proc_dir_timeout; +static struct proc_dir_entry *proc_dir; + +#ifdef CONFIG_HOTPLUG + +static int +call_helper(char *verb, const char *name, const char *device) +{ + char *argv[3], **envp, *buf, *scratch; + int i = 0; + + int retval = 0; + + if (!hotplug_path[0]) + return -ENOENT; + if (in_interrupt()) { + err("in_interrupt"); + return -EFAULT; + } + if (!current->fs->root) { + warn("call_policy %s -- no FS yet", verb); + return -EPERM; + } + + if (!(envp = (char **) kmalloc(20 * sizeof (char *), GFP_KERNEL))) { + err("unable to allocate envp"); + return -ENOMEM; + } + if (!(buf = kmalloc(256, GFP_KERNEL))) { + kfree(envp); + err("unable to allocate buf"); + return -ENOMEM; + } + + /* only one standardized param to hotplug command: type */ + argv[0] = hotplug_path; + argv[1] = "firmware"; + argv[2] = 0; + + /* minimal command environment */ + envp[i++] = "HOME=/"; + envp[i++] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin"; + +#ifdef DEBUG + /* hint that policy agent should enter no-stdout debug mode */ + envp[i++] = "DEBUG=kernel"; +#endif + scratch = buf; + + if (device) { + envp[i++] = scratch; + scratch += snprintf(scratch, FIRMWARE_NAME_MAX+25, + "DEVPATH=/driver/firmware/%s", device) + 1; + } + + envp[i++] = scratch; + scratch += sprintf(scratch, "ACTION=%s", verb) + 1; + + envp[i++] = scratch; + scratch += snprintf(scratch, FIRMWARE_NAME_MAX, + "FIRMWARE=%s", name) + 1; + + envp[i++] = 0; + +#ifdef DEBUG + dbg("firmware: %s %s %s", argv[0], argv[1], verb); +#endif + + retval = call_usermodehelper(argv[0], argv, envp); + if (retval) { + printk("call_usermodehelper return %d\n", retval); + } + + kfree(buf); + kfree(envp); + return retval; +} +#else + +static inline int +call_helper(char *verb, const char *name, const char *device) +{ + return -ENOENT; +} + +#endif /* CONFIG_HOTPLUG */ + +struct firmware_priv { + struct completion completion; + struct proc_dir_entry *proc_dir; + struct proc_dir_entry *attr_data; + struct proc_dir_entry *attr_loading; + struct firmware *fw; + int loading; + int abort; + int alloc_size; + struct timer_list timeout; +}; + +static int +firmware_timeout_show(char *buf, char **start, off_t off, + int count, int *eof, void *data) +{ + return sprintf(buf, "%d\n", loading_timeout); +} + +/** + * firmware_timeout_store: + * Description: + * Sets the number of seconds to wait for the firmware. Once + * this expires an error will be return to the driver and no + * firmware will be provided. + * + * Note: zero means 'wait for ever' + * + **/ +static int +firmware_timeout_store(struct file *file, const char *buf, + unsigned long count, void *data) +{ + loading_timeout = simple_strtol(buf, NULL, 10); + return count; +} + +static int +firmware_loading_show(char *buf, char **start, off_t off, + int count, int *eof, void *data) +{ + struct firmware_priv *fw_priv = data; + return sprintf(buf, "%d\n", fw_priv->loading); +} + +/** + * firmware_loading_store: - loading control file + * Description: + * The relevant values are: + * + * 1: Start a load, discarding any previous partial load. + * 0: Conclude the load and handle the data to the driver code. + * -1: Conclude the load with an error and discard any written data. + **/ +static int +firmware_loading_store(struct file *file, const char *buf, + unsigned long count, void *data) +{ + struct firmware_priv *fw_priv = data; + int prev_loading = fw_priv->loading; + + fw_priv->loading = simple_strtol(buf, NULL, 10); + + switch (fw_priv->loading) { + case -1: + fw_priv->abort = 1; + wmb(); + complete(&fw_priv->completion); + break; + case 1: + kfree(fw_priv->fw->data); + fw_priv->fw->data = NULL; + fw_priv->fw->size = 0; + fw_priv->alloc_size = 0; + break; + case 0: + if (prev_loading == 1) + complete(&fw_priv->completion); + break; + } + + return count; +} + +static int +firmware_data_read(char *buffer, char **start, off_t offset, + int count, int *eof, void *data) +{ + struct firmware_priv *fw_priv = data; + struct firmware *fw = fw_priv->fw; + + if (offset > fw->size) + return 0; + if (offset + count > fw->size) + count = fw->size - offset; + + memcpy(buffer, fw->data + offset, count); + *start = (void *) ((long) count); + return count; +} +static int +fw_realloc_buffer(struct firmware_priv *fw_priv, int min_size) +{ + u8 *new_data; + int new_size; + + if (min_size <= fw_priv->alloc_size) + return 0; + if((min_size % PAGE_SIZE) == 0) + new_size = min_size; + else + new_size = (min_size + PAGE_SIZE) & PAGE_MASK; + new_data = vmalloc(new_size); + if (!new_data) { + printk(KERN_ERR "%s: unable to alloc buffer\n", __FUNCTION__); + /* Make sure that we don't keep incomplete data */ + fw_priv->abort = 1; + return -ENOMEM; + } + fw_priv->alloc_size = new_size; + if (fw_priv->fw->data) { + memcpy(new_data, fw_priv->fw->data, fw_priv->fw->size); + vfree(fw_priv->fw->data); + } + fw_priv->fw->data = new_data; + BUG_ON(min_size > fw_priv->alloc_size); + return 0; +} + +/** + * firmware_data_write: + * + * Description: + * + * Data written to the 'data' attribute will be later handled to + * the driver as a firmware image. + **/ +static int +firmware_data_write(struct file *file, const char *buffer, + unsigned long count, void *data) +{ + struct firmware_priv *fw_priv = data; + struct firmware *fw = fw_priv->fw; + int offset = file->f_pos; + int retval; + + retval = fw_realloc_buffer(fw_priv, offset + count); + if (retval) { + printk("%s: retval:%d\n", __FUNCTION__, retval); + return retval; + } + + memcpy(fw->data + offset, buffer, count); + + fw->size = max_t(size_t, offset + count, fw->size); + file->f_pos += count; + return count; +} + +static void +firmware_class_timeout(u_long data) +{ + struct firmware_priv *fw_priv = (struct firmware_priv *) data; + fw_priv->abort = 1; + wmb(); + complete(&fw_priv->completion); +} +static int +fw_setup_class_device(struct firmware_priv **fw_priv_p, + const char *fw_name, const char *device) +{ + int retval; + struct firmware_priv *fw_priv = kmalloc(sizeof (struct firmware_priv), + GFP_KERNEL); + *fw_priv_p = fw_priv; + if (!fw_priv) { + retval = -ENOMEM; + goto out; + } + memset(fw_priv, 0, sizeof (*fw_priv)); + + init_completion(&fw_priv->completion); + + fw_priv->timeout.function = firmware_class_timeout; + fw_priv->timeout.data = (u_long) fw_priv; + init_timer(&fw_priv->timeout); + + retval = -EAGAIN; + fw_priv->proc_dir = create_proc_entry(device, 0644 | S_IFDIR, proc_dir); + if (!fw_priv->proc_dir) + goto err_free_fw_priv; + + fw_priv->attr_data = create_proc_entry("data", 0644 | S_IFREG, + fw_priv->proc_dir); + if (!fw_priv->attr_data) + goto err_remove_dir; + + fw_priv->attr_data->read_proc = firmware_data_read; + fw_priv->attr_data->write_proc = firmware_data_write; + fw_priv->attr_data->data = fw_priv; + + fw_priv->attr_loading = create_proc_entry("loading", 0644 | S_IFREG, + fw_priv->proc_dir); + if (!fw_priv->attr_loading) + goto err_remove_data; + + fw_priv->attr_loading->read_proc = firmware_loading_show; + fw_priv->attr_loading->write_proc = firmware_loading_store; + fw_priv->attr_loading->data = fw_priv; + + retval = 0; + fw_priv->fw = kmalloc(sizeof (struct firmware), GFP_KERNEL); + if (!fw_priv->fw) { + printk(KERN_ERR "%s: kmalloc(struct firmware) failed\n", + __FUNCTION__); + retval = -ENOMEM; + goto err_remove_loading; + } + memset(fw_priv->fw, 0, sizeof (*fw_priv->fw)); + + goto out; + +err_remove_loading: + remove_proc_entry("loading", fw_priv->proc_dir); +err_remove_data: + remove_proc_entry("data", fw_priv->proc_dir); +err_remove_dir: + remove_proc_entry(device, proc_dir); +err_free_fw_priv: + kfree(fw_priv); +out: + return retval; +} +static void +fw_remove_class_device(struct firmware_priv *fw_priv) +{ + remove_proc_entry("loading", fw_priv->proc_dir); + remove_proc_entry("data", fw_priv->proc_dir); + remove_proc_entry(fw_priv->proc_dir->name, proc_dir); +} + +/** + * request_firmware: - request firmware to hotplug and wait for it + * Description: + * @firmware will be used to return a firmware image by the name + * of @name for device @device. + * + * Should be called from user context where sleeping is allowed. + * + * @name will be use as $FIRMWARE in the hotplug environment and + * should be distinctive enough not to be confused with any other + * firmware image for this or any other device. + **/ +int +request_firmware(const struct firmware **firmware, const char *name, + const char *device) +{ + struct firmware_priv *fw_priv; + int retval; + + if (!firmware) { + retval = -EINVAL; + goto out; + } + *firmware = NULL; + + retval = fw_setup_class_device(&fw_priv, name, device); + if (retval) + goto out; + + retval = call_helper("add", name, device); + if (retval) + goto out; + if (loading_timeout) { + fw_priv->timeout.expires = jiffies + loading_timeout * HZ; + add_timer(&fw_priv->timeout); + } + + wait_for_completion(&fw_priv->completion); + + del_timer(&fw_priv->timeout); + fw_remove_class_device(fw_priv); + + if (fw_priv->fw->size && !fw_priv->abort) { + *firmware = fw_priv->fw; + } else { + retval = -ENOENT; + vfree(fw_priv->fw->data); + kfree(fw_priv->fw); + } +out: + kfree(fw_priv); + return retval; +} + +void +release_firmware(const struct firmware *fw) +{ + if (fw) { + vfree(fw->data); + kfree(fw); + } +} + +/** + * register_firmware: - provide a firmware image for later usage + * + * Description: + * Make sure that @data will be available by requesting firmware @name. + * + * Note: This will not be possible until some kind of persistence + * is available. + **/ +void +register_firmware(const char *name, const u8 *data, size_t size) +{ + /* This is meaningless without firmware caching, so until we + * decide if firmware caching is reasonable just leave it as a + * noop */ +} + +/* Async support */ +struct firmware_work { + struct tq_struct work; + struct module *module; + const char *name; + const char *device; + void *context; + void (*cont)(const struct firmware *fw, void *context); +}; + +static void +request_firmware_work_func(void *arg) +{ + struct firmware_work *fw_work = arg; + const struct firmware *fw; + if (!arg) + return; + request_firmware(&fw, fw_work->name, fw_work->device); + fw_work->cont(fw, fw_work->context); + release_firmware(fw); + __MOD_DEC_USE_COUNT(fw_work->module); + kfree(fw_work); +} + +/** + * request_firmware_nowait: + * + * Description: + * Asynchronous variant of request_firmware() for contexts where + * it is not possible to sleep. + * + * @cont will be called asynchronously when the firmware request is over. + * + * @context will be passed over to @cont. + * + * @fw may be %NULL if firmware request fails. + * + **/ +int +request_firmware_nowait( + struct module *module, + const char *name, const char *device, void *context, + void (*cont)(const struct firmware *fw, void *context)) +{ + struct firmware_work *fw_work = kmalloc(sizeof (struct firmware_work), + GFP_ATOMIC); + if (!fw_work) + return -ENOMEM; + if (!try_inc_mod_count(module)) { + kfree(fw_work); + return -EFAULT; + } + + *fw_work = (struct firmware_work) { + .module = module, + .name = name, + .device = device, + .context = context, + .cont = cont, + }; + INIT_TQUEUE(&fw_work->work, request_firmware_work_func, fw_work); + + schedule_task(&fw_work->work); + return 0; +} + +static int __init +firmware_class_init(void) +{ + proc_dir = create_proc_entry("driver/firmware", 0755 | S_IFDIR, NULL); + if (!proc_dir) + return -EAGAIN; + proc_dir_timeout = create_proc_entry("timeout", + 0644 | S_IFREG, proc_dir); + if (!proc_dir_timeout) { + remove_proc_entry("driver/firmware", NULL); + return -EAGAIN; + } + proc_dir_timeout->read_proc = firmware_timeout_show; + proc_dir_timeout->write_proc = firmware_timeout_store; + return 0; +} +static void __exit +firmware_class_exit(void) +{ + remove_proc_entry("timeout", proc_dir); + remove_proc_entry("driver/firmware", NULL); +} + +module_init(firmware_class_init); +module_exit(firmware_class_exit); + +#ifndef CONFIG_FW_LOADER +EXPORT_SYMBOL(release_firmware); +EXPORT_SYMBOL(request_firmware); +EXPORT_SYMBOL(request_firmware_nowait); +EXPORT_SYMBOL(register_firmware); +#endif --- linux/lib/Makefile~bluetooth-2.4.18-mh11 2001-09-18 00:31:15.000000000 +0200 +++ linux/lib/Makefile 2004-01-25 23:37:39.000000000 +0100 @@ -8,13 +8,17 @@ L_TARGET := lib.a -export-objs := cmdline.o dec_and_lock.o rwsem-spinlock.o rwsem.o +export-objs := cmdline.o dec_and_lock.o rwsem-spinlock.o rwsem.o \ + firmware_class.o obj-y := errno.o ctype.o string.o vsprintf.o brlock.o cmdline.o bust_spinlocks.o rbtree.o +obj-$(CONFIG_FW_LOADER) += firmware_class.o obj-$(CONFIG_RWSEM_GENERIC_SPINLOCK) += rwsem-spinlock.o obj-$(CONFIG_RWSEM_XCHGADD_ALGORITHM) += rwsem.o +include $(TOPDIR)/drivers/bluetooth/Makefile.lib + ifneq ($(CONFIG_HAVE_DEC_LOCK),y) obj-y += dec_and_lock.o endif --- linux/MAINTAINERS~bluetooth-2.4.18-mh11 2004-01-25 23:28:29.000000000 +0100 +++ linux/MAINTAINERS 2004-01-25 23:37:39.000000000 +0100 @@ -259,7 +259,84 @@ L: linux-kernel@vger.kernel.org S: Maintained -BLUETOOTH SUBSYSTEM (BlueZ) +BLUETOOTH SUBSYSTEM +P: Marcel Holtmann +M: marcel@holtmann.org +P: Maxim Krasnyansky +M: maxk@qualcomm.com +L: bluez-devel@lists.sf.net +W: http://bluez.sf.net +S: Maintained + +BLUETOOTH RFCOMM LAYER +P: Marcel Holtmann +M: marcel@holtmann.org +P: Maxim Krasnyansky +M: maxk@qualcomm.com +W: http://bluez.sf.net +S: Maintained + +BLUETOOTH BNEP LAYER +P: Marcel Holtmann +M: marcel@holtmann.org +P: Maxim Krasnyansky +M: maxk@qualcomm.com +W: http://bluez.sf.net +S: Maintained + +BLUETOOTH CMTP LAYER +P: Marcel Holtmann +M: marcel@holtmann.org +W: http://www.holtmann.org/linux/bluetooth/ +S: Maintained + +BLUETOOTH HCI USB DRIVER +P: Marcel Holtmann +M: marcel@holtmann.org +P: Maxim Krasnyansky +M: maxk@qualcomm.com +W: http://bluez.sf.net +S: Maintained + +BLUETOOTH HCI UART DRIVER +P: Marcel Holtmann +M: marcel@holtmann.org +P: Maxim Krasnyansky +M: maxk@qualcomm.com +W: http://bluez.sf.net +S: Maintained + +BLUETOOTH HCI BFUSB DRIVER +P: Marcel Holtmann +M: marcel@holtmann.org +W: http://www.holtmann.org/linux/bluetooth/ +S: Maintained + +BLUETOOTH HCI DTL1 DRIVER +P: Marcel Holtmann +M: marcel@holtmann.org +W: http://www.holtmann.org/linux/bluetooth/ +S: Maintained + +BLUETOOTH HCI BLUECARD DRIVER +P: Marcel Holtmann +M: marcel@holtmann.org +W: http://www.holtmann.org/linux/bluetooth/ +S: Maintained + +BLUETOOTH HCI BT3C DRIVER +P: Marcel Holtmann +M: marcel@holtmann.org +W: http://www.holtmann.org/linux/bluetooth/ +S: Maintained + +BLUETOOTH HCI BTUART DRIVER +P: Marcel Holtmann +M: marcel@holtmann.org +W: http://www.holtmann.org/linux/bluetooth/ +S: Maintained + +BLUETOOTH HCI VHCI DRIVER P: Maxim Krasnyansky M: maxk@qualcomm.com W: http://bluez.sf.net --- linux/net/bluetooth/af_bluetooth.c~bluetooth-2.4.18-mh11 2001-09-07 18:28:38.000000000 +0200 +++ linux/net/bluetooth/af_bluetooth.c 2004-01-25 23:37:39.000000000 +0100 @@ -25,14 +25,15 @@ /* * BlueZ Bluetooth address family and sockets. * - * $Id: af_bluetooth.c,v 1.4 2001/07/05 18:42:44 maxk Exp $ + * $Id: af_bluetooth.c,v 1.8 2002/07/22 20:32:54 maxk Exp $ */ -#define VERSION "1.1" +#define VERSION "2.3" #include #include #include +#include #include #include #include @@ -40,6 +41,7 @@ #include #include #include +#include #include #include @@ -48,70 +50,79 @@ #endif #include -#include + +#ifndef AF_BLUETOOTH_DEBUG +#undef BT_DBG +#define BT_DBG( A... ) +#endif /* Bluetooth sockets */ -static struct net_proto_family *bluez_sock[BLUEZ_MAX_PROTO]; +#define BLUEZ_MAX_PROTO 6 +static struct net_proto_family *bluez_proto[BLUEZ_MAX_PROTO]; int bluez_sock_register(int proto, struct net_proto_family *ops) { - if (proto > BLUEZ_MAX_PROTO) + if (proto >= BLUEZ_MAX_PROTO) return -EINVAL; - if (bluez_sock[proto]) + if (bluez_proto[proto]) return -EEXIST; - bluez_sock[proto] = ops; + bluez_proto[proto] = ops; return 0; } int bluez_sock_unregister(int proto) { - if (proto > BLUEZ_MAX_PROTO) + if (proto >= BLUEZ_MAX_PROTO) return -EINVAL; - if (!bluez_sock[proto]) + if (!bluez_proto[proto]) return -ENOENT; - bluez_sock[proto] = NULL; + bluez_proto[proto] = NULL; return 0; } static int bluez_sock_create(struct socket *sock, int proto) { - if (proto > BLUEZ_MAX_PROTO) + if (proto >= BLUEZ_MAX_PROTO) return -EINVAL; #if defined(CONFIG_KMOD) - if (!bluez_sock[proto]) { + if (!bluez_proto[proto]) { char module_name[30]; sprintf(module_name, "bt-proto-%d", proto); request_module(module_name); } #endif - if (!bluez_sock[proto]) + if (!bluez_proto[proto]) return -ENOENT; - return bluez_sock[proto]->create(sock, proto); + return bluez_proto[proto]->create(sock, proto); +} + +void bluez_sock_init(struct socket *sock, struct sock *sk) +{ + sock_init_data(sock, sk); + INIT_LIST_HEAD(&bluez_pi(sk)->accept_q); } void bluez_sock_link(struct bluez_sock_list *l, struct sock *sk) { - write_lock(&l->lock); - + write_lock_bh(&l->lock); sk->next = l->head; l->head = sk; sock_hold(sk); - - write_unlock(&l->lock); + write_unlock_bh(&l->lock); } void bluez_sock_unlink(struct bluez_sock_list *l, struct sock *sk) { struct sock **skp; - write_lock(&l->lock); + write_lock_bh(&l->lock); for (skp = &l->head; *skp; skp = &((*skp)->next)) { if (*skp == sk) { *skp = sk->next; @@ -119,7 +130,162 @@ break; } } - write_unlock(&l->lock); + write_unlock_bh(&l->lock); +} + +void bluez_accept_enqueue(struct sock *parent, struct sock *sk) +{ + BT_DBG("parent %p, sk %p", parent, sk); + + sock_hold(sk); + list_add_tail(&bluez_pi(sk)->accept_q, &bluez_pi(parent)->accept_q); + bluez_pi(sk)->parent = parent; + parent->ack_backlog++; +} + +static void bluez_accept_unlink(struct sock *sk) +{ + BT_DBG("sk %p state %d", sk, sk->state); + + list_del_init(&bluez_pi(sk)->accept_q); + bluez_pi(sk)->parent->ack_backlog--; + bluez_pi(sk)->parent = NULL; + sock_put(sk); +} + +struct sock *bluez_accept_dequeue(struct sock *parent, struct socket *newsock) +{ + struct list_head *p, *n; + struct bluez_pinfo *pi; + struct sock *sk; + + BT_DBG("parent %p", parent); + + list_for_each_safe(p, n, &bluez_pi(parent)->accept_q) { + pi = list_entry(p, struct bluez_pinfo, accept_q); + sk = bluez_sk(pi); + + lock_sock(sk); + if (sk->state == BT_CLOSED) { + release_sock(sk); + bluez_accept_unlink(sk); + continue; + } + + if (sk->state == BT_CONNECTED || !newsock) { + bluez_accept_unlink(sk); + if (newsock) + sock_graft(sk, newsock); + release_sock(sk); + return sk; + } + release_sock(sk); + } + return NULL; +} + +int bluez_sock_recvmsg(struct socket *sock, struct msghdr *msg, int len, int flags, struct scm_cookie *scm) +{ + int noblock = flags & MSG_DONTWAIT; + struct sock *sk = sock->sk; + struct sk_buff *skb; + int copied, err; + + BT_DBG("sock %p sk %p len %d", sock, sk, len); + + if (flags & (MSG_OOB)) + return -EOPNOTSUPP; + + if (!(skb = skb_recv_datagram(sk, flags, noblock, &err))) { + if (sk->shutdown & RCV_SHUTDOWN) + return 0; + return err; + } + + msg->msg_namelen = 0; + + copied = skb->len; + if (len < copied) { + msg->msg_flags |= MSG_TRUNC; + copied = len; + } + + skb->h.raw = skb->data; + err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied); + + skb_free_datagram(sk, skb); + + return err ? : copied; +} + +unsigned int bluez_sock_poll(struct file * file, struct socket *sock, poll_table *wait) +{ + struct sock *sk = sock->sk; + unsigned int mask; + + BT_DBG("sock %p, sk %p", sock, sk); + + poll_wait(file, sk->sleep, wait); + mask = 0; + + if (sk->err || !skb_queue_empty(&sk->error_queue)) + mask |= POLLERR; + + if (sk->shutdown == SHUTDOWN_MASK) + mask |= POLLHUP; + + if (!skb_queue_empty(&sk->receive_queue) || + !list_empty(&bluez_pi(sk)->accept_q) || + (sk->shutdown & RCV_SHUTDOWN)) + mask |= POLLIN | POLLRDNORM; + + if (sk->state == BT_CLOSED) + mask |= POLLHUP; + + if (sk->state == BT_CONNECT || sk->state == BT_CONNECT2) + return mask; + + if (sock_writeable(sk)) + mask |= POLLOUT | POLLWRNORM | POLLWRBAND; + else + set_bit(SOCK_ASYNC_NOSPACE, &sk->socket->flags); + + return mask; +} + +int bluez_sock_wait_state(struct sock *sk, int state, unsigned long timeo) +{ + DECLARE_WAITQUEUE(wait, current); + int err = 0; + + BT_DBG("sk %p", sk); + + add_wait_queue(sk->sleep, &wait); + while (sk->state != state) { + set_current_state(TASK_INTERRUPTIBLE); + + if (!timeo) { + err = -EAGAIN; + break; + } + + if (signal_pending(current)) { + err = sock_intr_errno(timeo); + break; + } + + release_sock(sk); + timeo = schedule_timeout(timeo); + lock_sock(sk); + + if (sk->err) { + err = sock_error(sk); + break; + } + } + set_current_state(TASK_RUNNING); + remove_wait_queue(sk->sleep, &wait); + return err; } struct net_proto_family bluez_sock_family_ops = @@ -129,9 +295,9 @@ int bluez_init(void) { - INF("BlueZ HCI Core ver %s Copyright (C) 2000,2001 Qualcomm Inc", + BT_INFO("BlueZ Core ver %s Copyright (C) 2000,2001 Qualcomm Inc", VERSION); - INF("Written 2000,2001 by Maxim Krasnyansky "); + BT_INFO("Written 2000,2001 by Maxim Krasnyansky "); proc_mkdir("bluetooth", NULL); @@ -164,5 +330,6 @@ module_exit(bluez_cleanup); MODULE_AUTHOR("Maxim Krasnyansky "); -MODULE_DESCRIPTION("BlueZ HCI Core ver " VERSION); +MODULE_DESCRIPTION("BlueZ Core ver " VERSION); +MODULE_LICENSE("GPL"); #endif --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/net/bluetooth/bnep/bnep.h 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1,185 @@ +/* + BNEP protocol definition for Linux Bluetooth stack (BlueZ). + Copyright (C) 2002 Maxim Krasnyansky + + 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. + + 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 +*/ + +/* + * $Id: bnep2.h,v 1.9 2002/07/14 07:09:19 maxk Exp $ + */ + +#ifndef _BNEP_H +#define _BNEP_H + +#include +#include + +#include "crc32.h" + +// Limits +#define BNEP_MAX_PROTO_FILTERS 5 +#define BNEP_MAX_MULTICAST_FILTERS 20 + +// UUIDs +#define BNEP_BASE_UUID 0x0000000000001000800000805F9B34FB +#define BNEP_UUID16 0x02 +#define BNEP_UUID32 0x04 +#define BNEP_UUID128 0x16 + +#define BNEP_SVC_PANU 0x1115 +#define BNEP_SVC_NAP 0x1116 +#define BNEP_SVC_GN 0x1117 + +// Packet types +#define BNEP_GENERAL 0x00 +#define BNEP_CONTROL 0x01 +#define BNEP_COMPRESSED 0x02 +#define BNEP_COMPRESSED_SRC_ONLY 0x03 +#define BNEP_COMPRESSED_DST_ONLY 0x04 + +// Control types +#define BNEP_CMD_NOT_UNDERSTOOD 0x00 +#define BNEP_SETUP_CONN_REQ 0x01 +#define BNEP_SETUP_CONN_RSP 0x02 +#define BNEP_FILTER_NET_TYPE_SET 0x03 +#define BNEP_FILTER_NET_TYPE_RSP 0x04 +#define BNEP_FILTER_MULTI_ADDR_SET 0x05 +#define BNEP_FILTER_MULTI_ADDR_RSP 0x06 + +// Extension types +#define BNEP_EXT_CONTROL 0x00 + +// Response messages +#define BNEP_SUCCESS 0x00 + +#define BNEP_CONN_INVALID_DST 0x01 +#define BNEP_CONN_INVALID_SRC 0x02 +#define BNEP_CONN_INVALID_SVC 0x03 +#define BNEP_CONN_NOT_ALLOWED 0x04 + +#define BNEP_FILTER_UNSUPPORTED_REQ 0x01 +#define BNEP_FILTER_INVALID_RANGE 0x02 +#define BNEP_FILTER_INVALID_MCADDR 0x02 +#define BNEP_FILTER_LIMIT_REACHED 0x03 +#define BNEP_FILTER_DENIED_SECURITY 0x04 + +// L2CAP settings +#define BNEP_MTU 1691 +#define BNEP_PSM 0x0f +#define BNEP_FLUSH_TO 0xffff +#define BNEP_CONNECT_TO 15 +#define BNEP_FILTER_TO 15 + +// Headers +#define BNEP_TYPE_MASK 0x7f +#define BNEP_EXT_HEADER 0x80 + +struct bnep_setup_conn_req { + __u8 type; + __u8 ctrl; + __u8 uuid_size; + __u8 service[0]; +} __attribute__((packed)); + +struct bnep_set_filter_req { + __u8 type; + __u8 ctrl; + __u16 len; + __u8 list[0]; +} __attribute__((packed)); + +struct bnep_control_rsp { + __u8 type; + __u8 ctrl; + __u16 resp; +} __attribute__((packed)); + +struct bnep_ext_hdr { + __u8 type; + __u8 len; + __u8 data[0]; +} __attribute__((packed)); + +/* BNEP ioctl defines */ +#define BNEPCONNADD _IOW('B', 200, int) +#define BNEPCONNDEL _IOW('B', 201, int) +#define BNEPGETCONNLIST _IOR('B', 210, int) +#define BNEPGETCONNINFO _IOR('B', 211, int) + +struct bnep_connadd_req { + int sock; // Connected socket + __u32 flags; + __u16 role; + char device[16]; // Name of the Ethernet device +}; + +struct bnep_conndel_req { + __u32 flags; + __u8 dst[ETH_ALEN]; +}; + +struct bnep_conninfo { + __u32 flags; + __u16 role; + __u16 state; + __u8 dst[ETH_ALEN]; + char device[16]; +}; + +struct bnep_connlist_req { + __u32 cnum; + struct bnep_conninfo *ci; +}; + +struct bnep_proto_filter { + __u16 start; + __u16 end; +}; + +int bnep_add_connection(struct bnep_connadd_req *req, struct socket *sock); +int bnep_del_connection(struct bnep_conndel_req *req); +int bnep_get_connlist(struct bnep_connlist_req *req); +int bnep_get_conninfo(struct bnep_conninfo *ci); + +// BNEP sessions +struct bnep_session { + struct list_head list; + + unsigned int role; + unsigned long state; + unsigned long flags; + atomic_t killed; + + struct ethhdr eh; + struct msghdr msg; + + struct bnep_proto_filter proto_filter[BNEP_MAX_PROTO_FILTERS]; + u64 mc_filter; + + struct socket *sock; + struct net_device dev; + struct net_device_stats stats; +}; + +int bnep_net_init(struct net_device *dev); +int bnep_sock_init(void); +int bnep_sock_cleanup(void); + +static inline int bnep_mc_hash(__u8 *addr) +{ + return (bnep_crc32(~0, addr, ETH_ALEN) >> 26); +} + +#endif --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/net/bluetooth/bnep/Config.in 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1,11 @@ +# +# Bluetooth BNEP layer configuration +# + +dep_tristate 'BNEP protocol support' CONFIG_BLUEZ_BNEP $CONFIG_BLUEZ_L2CAP + +if [ "$CONFIG_BLUEZ_BNEP" != "n" ]; then + bool ' Multicast filter support' CONFIG_BLUEZ_BNEP_MC_FILTER + bool ' Protocol filter support' CONFIG_BLUEZ_BNEP_PROTO_FILTER +fi + --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/net/bluetooth/bnep/core.c 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1,708 @@ +/* + BNEP implementation for Linux Bluetooth stack (BlueZ). + Copyright (C) 2001-2002 Inventel Systemes + Written 2001-2002 by + Clément Moreau + David Libault + + Copyright (C) 2002 Maxim Krasnyanskiy + + 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: core.c,v 1.18 2002/07/14 07:09:19 maxk Exp $ + */ + +#define __KERNEL_SYSCALLS__ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include + +#include +#include + +#include "bnep.h" + +#ifndef CONFIG_BLUEZ_BNEP_DEBUG +#undef BT_DBG +#define BT_DBG(D...) +#endif + +#define VERSION "1.1" + +static LIST_HEAD(bnep_session_list); +static DECLARE_RWSEM(bnep_session_sem); + +static struct bnep_session *__bnep_get_session(u8 *dst) +{ + struct bnep_session *s; + struct list_head *p; + + BT_DBG(""); + + list_for_each(p, &bnep_session_list) { + s = list_entry(p, struct bnep_session, list); + if (!memcmp(dst, s->eh.h_source, ETH_ALEN)) + return s; + } + return NULL; +} + +static void __bnep_link_session(struct bnep_session *s) +{ + MOD_INC_USE_COUNT; + list_add(&s->list, &bnep_session_list); +} + +static void __bnep_unlink_session(struct bnep_session *s) +{ + list_del(&s->list); + MOD_DEC_USE_COUNT; +} + +static int bnep_send(struct bnep_session *s, void *data, size_t len) +{ + struct socket *sock = s->sock; + struct iovec iv = { data, len }; + s->msg.msg_iov = &iv; + s->msg.msg_iovlen = 1; + return sock->ops->sendmsg(sock, &s->msg, len, NULL); +} + +static int bnep_send_rsp(struct bnep_session *s, u8 ctrl, u16 resp) +{ + struct bnep_control_rsp rsp; + rsp.type = BNEP_CONTROL; + rsp.ctrl = ctrl; + rsp.resp = htons(resp); + return bnep_send(s, &rsp, sizeof(rsp)); +} + +static int bnep_ctrl_set_netfilter(struct bnep_session *s, u16 *data, int len) +{ + int n; + + if (len < 2) + return -EILSEQ; + + n = ntohs(get_unaligned(data)); + data++; len -= 2; + + if (len < n) + return -EILSEQ; + + BT_DBG("filter len %d", n); + +#ifdef CONFIG_BLUEZ_BNEP_PROTO_FILTER + n /= 4; + if (n <= BNEP_MAX_PROTO_FILTERS) { + struct bnep_proto_filter *f = s->proto_filter; + int i; + + for (i = 0; i < n; i++) { + f[i].start = get_unaligned(data++); + f[i].end = get_unaligned(data++); + + BT_DBG("proto filter start %d end %d", + f[i].start, f[i].end); + } + if (i < BNEP_MAX_PROTO_FILTERS) + memset(f + i, 0, sizeof(*f)); + + bnep_send_rsp(s, BNEP_FILTER_NET_TYPE_RSP, BNEP_SUCCESS); + } else { + bnep_send_rsp(s, BNEP_FILTER_NET_TYPE_RSP, BNEP_FILTER_LIMIT_REACHED); + } +#else + bnep_send_rsp(s, BNEP_FILTER_NET_TYPE_RSP, BNEP_FILTER_UNSUPPORTED_REQ); +#endif + return 0; +} + +static int bnep_ctrl_set_mcfilter(struct bnep_session *s, u8 *data, int len) +{ + int n; + + if (len < 2) + return -EILSEQ; + + n = ntohs(get_unaligned((u16 *) data)); + data += 2; len -= 2; + + if (len < n) + return -EILSEQ; + + BT_DBG("filter len %d", n); + +#ifdef CONFIG_BLUEZ_BNEP_MC_FILTER + n /= (ETH_ALEN * 2); + + if (n > 0) { + s->mc_filter = 0; + + /* Always send broadcast */ + set_bit(bnep_mc_hash(s->dev.broadcast), &s->mc_filter); + + /* Add address ranges to the multicast hash */ + for (; n > 0; n--) { + u8 a1[6], *a2; + + memcpy(a1, data, ETH_ALEN); data += ETH_ALEN; + a2 = data; data += ETH_ALEN; + + BT_DBG("mc filter %s -> %s", + batostr((void *) a1), batostr((void *) a2)); + + #define INCA(a) { int i = 5; while (i >=0 && ++a[i--] == 0); } + + /* Iterate from a1 to a2 */ + set_bit(bnep_mc_hash(a1), &s->mc_filter); + while (memcmp(a1, a2, 6) < 0 && s->mc_filter != ~0LL) { + INCA(a1); + set_bit(bnep_mc_hash(a1), &s->mc_filter); + } + } + } + + BT_DBG("mc filter hash 0x%llx", s->mc_filter); + + bnep_send_rsp(s, BNEP_FILTER_MULTI_ADDR_RSP, BNEP_SUCCESS); +#else + bnep_send_rsp(s, BNEP_FILTER_MULTI_ADDR_RSP, BNEP_FILTER_UNSUPPORTED_REQ); +#endif + return 0; +} + +static int bnep_rx_control(struct bnep_session *s, void *data, int len) +{ + u8 cmd = *(u8 *)data; + int err = 0; + + data++; len--; + + switch (cmd) { + case BNEP_CMD_NOT_UNDERSTOOD: + case BNEP_SETUP_CONN_REQ: + case BNEP_SETUP_CONN_RSP: + case BNEP_FILTER_NET_TYPE_RSP: + case BNEP_FILTER_MULTI_ADDR_RSP: + /* Ignore these for now */ + break; + + case BNEP_FILTER_NET_TYPE_SET: + err = bnep_ctrl_set_netfilter(s, data, len); + break; + + case BNEP_FILTER_MULTI_ADDR_SET: + err = bnep_ctrl_set_mcfilter(s, data, len); + break; + + default: { + u8 pkt[3]; + pkt[0] = BNEP_CONTROL; + pkt[1] = BNEP_CMD_NOT_UNDERSTOOD; + pkt[2] = cmd; + bnep_send(s, pkt, sizeof(pkt)); + } + break; + } + + return err; +} + +static int bnep_rx_extension(struct bnep_session *s, struct sk_buff *skb) +{ + struct bnep_ext_hdr *h; + int err = 0; + + do { + h = (void *) skb->data; + if (!skb_pull(skb, sizeof(*h))) { + err = -EILSEQ; + break; + } + + BT_DBG("type 0x%x len %d", h->type, h->len); + + switch (h->type & BNEP_TYPE_MASK) { + case BNEP_EXT_CONTROL: + bnep_rx_control(s, skb->data, skb->len); + break; + + default: + /* Unknown extension, skip it. */ + break; + } + + if (!skb_pull(skb, h->len)) { + err = -EILSEQ; + break; + } + } while (!err && (h->type & BNEP_EXT_HEADER)); + + return err; +} + +static u8 __bnep_rx_hlen[] = { + ETH_HLEN, /* BNEP_GENERAL */ + 0, /* BNEP_CONTROL */ + 2, /* BNEP_COMPRESSED */ + ETH_ALEN + 2, /* BNEP_COMPRESSED_SRC_ONLY */ + ETH_ALEN + 2 /* BNEP_COMPRESSED_DST_ONLY */ +}; +#define BNEP_RX_TYPES (sizeof(__bnep_rx_hlen) - 1) + +static inline int bnep_rx_frame(struct bnep_session *s, struct sk_buff *skb) +{ + struct net_device *dev = &s->dev; + struct sk_buff *nskb; + u8 type; + + dev->last_rx = jiffies; + s->stats.rx_bytes += skb->len; + + type = *(u8 *) skb->data; skb_pull(skb, 1); + + if ((type & BNEP_TYPE_MASK) > BNEP_RX_TYPES) + goto badframe; + + if ((type & BNEP_TYPE_MASK) == BNEP_CONTROL) { + bnep_rx_control(s, skb->data, skb->len); + kfree_skb(skb); + return 0; + } + + skb->mac.raw = skb->data; + + /* Verify and pull out header */ + if (!skb_pull(skb, __bnep_rx_hlen[type & BNEP_TYPE_MASK])) + goto badframe; + + s->eh.h_proto = get_unaligned((u16 *) (skb->data - 2)); + + if (type & BNEP_EXT_HEADER) { + if (bnep_rx_extension(s, skb) < 0) + goto badframe; + } + + /* Strip 802.1p header */ + if (ntohs(s->eh.h_proto) == 0x8100) { + if (!skb_pull(skb, 4)) + goto badframe; + s->eh.h_proto = get_unaligned((u16 *) (skb->data - 2)); + } + + /* We have to alloc new skb and copy data here :(. Because original skb + * may not be modified and because of the alignment requirements. */ + nskb = alloc_skb(2 + ETH_HLEN + skb->len, GFP_KERNEL); + if (!nskb) { + s->stats.rx_dropped++; + kfree_skb(skb); + return -ENOMEM; + } + skb_reserve(nskb, 2); + + /* Decompress header and construct ether frame */ + switch (type & BNEP_TYPE_MASK) { + case BNEP_COMPRESSED: + memcpy(__skb_put(nskb, ETH_HLEN), &s->eh, ETH_HLEN); + break; + + case BNEP_COMPRESSED_SRC_ONLY: + memcpy(__skb_put(nskb, ETH_ALEN), s->eh.h_dest, ETH_ALEN); + memcpy(__skb_put(nskb, ETH_ALEN), skb->mac.raw, ETH_ALEN); + put_unaligned(s->eh.h_proto, (u16 *) __skb_put(nskb, 2)); + break; + + case BNEP_COMPRESSED_DST_ONLY: + memcpy(__skb_put(nskb, ETH_ALEN), skb->mac.raw, ETH_ALEN); + memcpy(__skb_put(nskb, ETH_ALEN + 2), s->eh.h_source, ETH_ALEN + 2); + break; + + case BNEP_GENERAL: + memcpy(__skb_put(nskb, ETH_ALEN * 2), skb->mac.raw, ETH_ALEN * 2); + put_unaligned(s->eh.h_proto, (u16 *) __skb_put(nskb, 2)); + break; + } + + memcpy(__skb_put(nskb, skb->len), skb->data, skb->len); + kfree_skb(skb); + + s->stats.rx_packets++; + nskb->dev = dev; + nskb->ip_summed = CHECKSUM_UNNECESSARY; + nskb->protocol = eth_type_trans(nskb, dev); + netif_rx_ni(nskb); + return 0; + +badframe: + s->stats.rx_errors++; + kfree_skb(skb); + return 0; +} + +static u8 __bnep_tx_types[] = { + BNEP_GENERAL, + BNEP_COMPRESSED_SRC_ONLY, + BNEP_COMPRESSED_DST_ONLY, + BNEP_COMPRESSED +}; + +static inline int bnep_tx_frame(struct bnep_session *s, struct sk_buff *skb) +{ + struct ethhdr *eh = (void *) skb->data; + struct socket *sock = s->sock; + struct iovec iv[3]; + int len = 0, il = 0; + u8 type = 0; + + BT_DBG("skb %p dev %p type %d", skb, skb->dev, skb->pkt_type); + + if (!skb->dev) { + /* Control frame sent by us */ + goto send; + } + + iv[il++] = (struct iovec) { &type, 1 }; + len++; + + if (!memcmp(eh->h_dest, s->eh.h_source, ETH_ALEN)) + type |= 0x01; + + if (!memcmp(eh->h_source, s->eh.h_dest, ETH_ALEN)) + type |= 0x02; + + if (type) + skb_pull(skb, ETH_ALEN * 2); + + type = __bnep_tx_types[type]; + switch (type) { + case BNEP_COMPRESSED_SRC_ONLY: + iv[il++] = (struct iovec) { eh->h_source, ETH_ALEN }; + len += ETH_ALEN; + break; + + case BNEP_COMPRESSED_DST_ONLY: + iv[il++] = (struct iovec) { eh->h_dest, ETH_ALEN }; + len += ETH_ALEN; + break; + } + +send: + iv[il++] = (struct iovec) { skb->data, skb->len }; + len += skb->len; + + /* FIXME: linearize skb */ + + s->msg.msg_iov = iv; + s->msg.msg_iovlen = il; + len = sock->ops->sendmsg(sock, &s->msg, len, NULL); + kfree_skb(skb); + + if (len > 0) { + s->stats.tx_bytes += len; + s->stats.tx_packets++; + return 0; + } + + return len; +} + +static int bnep_session(void *arg) +{ + struct bnep_session *s = arg; + struct net_device *dev = &s->dev; + struct sock *sk = s->sock->sk; + struct sk_buff *skb; + wait_queue_t wait; + + BT_DBG(""); + + daemonize(); reparent_to_init(); + + sprintf(current->comm, "kbnepd %s", dev->name); + + sigfillset(¤t->blocked); + flush_signals(current); + + current->nice = -15; + + set_fs(KERNEL_DS); + + init_waitqueue_entry(&wait, current); + add_wait_queue(sk->sleep, &wait); + while (!atomic_read(&s->killed)) { + set_current_state(TASK_INTERRUPTIBLE); + + // RX + while ((skb = skb_dequeue(&sk->receive_queue))) { + skb_orphan(skb); + bnep_rx_frame(s, skb); + } + + if (sk->state != BT_CONNECTED) + break; + + // TX + while ((skb = skb_dequeue(&sk->write_queue))) + if (bnep_tx_frame(s, skb)) + break; + netif_wake_queue(dev); + + schedule(); + } + set_current_state(TASK_RUNNING); + remove_wait_queue(sk->sleep, &wait); + + /* Cleanup session */ + down_write(&bnep_session_sem); + + /* Delete network device */ + unregister_netdev(dev); + + /* Release the socket */ + fput(s->sock->file); + + __bnep_unlink_session(s); + + up_write(&bnep_session_sem); + kfree(s); + return 0; +} + +int bnep_add_connection(struct bnep_connadd_req *req, struct socket *sock) +{ + struct net_device *dev; + struct bnep_session *s, *ss; + u8 dst[ETH_ALEN], src[ETH_ALEN]; + int err; + + BT_DBG(""); + + baswap((void *) dst, &bluez_pi(sock->sk)->dst); + baswap((void *) src, &bluez_pi(sock->sk)->src); + + s = kmalloc(sizeof(struct bnep_session), GFP_KERNEL); + if (!s) + return -ENOMEM; + memset(s, 0, sizeof(struct bnep_session)); + + down_write(&bnep_session_sem); + + ss = __bnep_get_session(dst); + if (ss && ss->state == BT_CONNECTED) { + err = -EEXIST; + goto failed; + } + + dev = &s->dev; + + if (*req->device) + strcpy(dev->name, req->device); + else + strcpy(dev->name, "bnep%d"); + + memset(dev->broadcast, 0xff, ETH_ALEN); + + /* This is rx header therefor addresses are swaped. + * ie eh.h_dest is our local address. */ + memcpy(s->eh.h_dest, &src, ETH_ALEN); + memcpy(s->eh.h_source, &dst, ETH_ALEN); + + s->sock = sock; + s->role = req->role; + s->state = BT_CONNECTED; + + s->msg.msg_flags = MSG_NOSIGNAL; + +#ifdef CONFIG_BLUEZ_BNEP_MC_FILTER + /* Set default mc filter */ + set_bit(bnep_mc_hash(dev->broadcast), &s->mc_filter); +#endif + +#ifdef CONFIG_BLUEZ_BNEP_PROTO_FILTER + /* Set default protocol filter */ + + /* (IPv4, ARP) */ + s->proto_filter[0].start = htons(0x0800); + s->proto_filter[0].end = htons(0x0806); + /* (RARP, AppleTalk) */ + s->proto_filter[1].start = htons(0x8035); + s->proto_filter[1].end = htons(0x80F3); + /* (IPX, IPv6) */ + s->proto_filter[2].start = htons(0x8137); + s->proto_filter[2].end = htons(0x86DD); +#endif + + dev->init = bnep_net_init; + dev->priv = s; + err = register_netdev(dev); + if (err) { + goto failed; + } + + __bnep_link_session(s); + + err = kernel_thread(bnep_session, s, CLONE_FS | CLONE_FILES | CLONE_SIGHAND); + if (err < 0) { + /* Session thread start failed, gotta cleanup. */ + unregister_netdev(dev); + __bnep_unlink_session(s); + goto failed; + } + + up_write(&bnep_session_sem); + strcpy(req->device, dev->name); + return 0; + +failed: + up_write(&bnep_session_sem); + kfree(s); + return err; +} + +int bnep_del_connection(struct bnep_conndel_req *req) +{ + struct bnep_session *s; + int err = 0; + + BT_DBG(""); + + down_read(&bnep_session_sem); + + s = __bnep_get_session(req->dst); + if (s) { + /* Wakeup user-space which is polling for socket errors. + * This is temporary hack untill we have shutdown in L2CAP */ + s->sock->sk->err = EUNATCH; + + /* Kill session thread */ + atomic_inc(&s->killed); + wake_up_interruptible(s->sock->sk->sleep); + } else + err = -ENOENT; + + up_read(&bnep_session_sem); + return err; +} + +static void __bnep_copy_ci(struct bnep_conninfo *ci, struct bnep_session *s) +{ + memcpy(ci->dst, s->eh.h_source, ETH_ALEN); + strcpy(ci->device, s->dev.name); + ci->flags = s->flags; + ci->state = s->state; + ci->role = s->role; +} + +int bnep_get_connlist(struct bnep_connlist_req *req) +{ + struct list_head *p; + int err = 0, n = 0; + + down_read(&bnep_session_sem); + + list_for_each(p, &bnep_session_list) { + struct bnep_session *s; + struct bnep_conninfo ci; + + s = list_entry(p, struct bnep_session, list); + + __bnep_copy_ci(&ci, s); + + if (copy_to_user(req->ci, &ci, sizeof(ci))) { + err = -EFAULT; + break; + } + + if (++n >= req->cnum) + break; + + req->ci++; + } + req->cnum = n; + + up_read(&bnep_session_sem); + return err; +} + +int bnep_get_conninfo(struct bnep_conninfo *ci) +{ + struct bnep_session *s; + int err = 0; + + down_read(&bnep_session_sem); + + s = __bnep_get_session(ci->dst); + if (s) + __bnep_copy_ci(ci, s); + else + err = -ENOENT; + + up_read(&bnep_session_sem); + return err; +} + +static int __init bnep_init_module(void) +{ + l2cap_load(); + + bnep_crc32_init(); + bnep_sock_init(); + + BT_INFO("BlueZ BNEP ver %s", VERSION); + BT_INFO("Copyright (C) 2001,2002 Inventel Systemes"); + BT_INFO("Written 2001,2002 by Clement Moreau "); + BT_INFO("Written 2001,2002 by David Libault "); + BT_INFO("Copyright (C) 2002 Maxim Krasnyanskiy "); + + return 0; +} + +static void __exit bnep_cleanup_module(void) +{ + bnep_sock_cleanup(); + bnep_crc32_cleanup(); +} + +module_init(bnep_init_module); +module_exit(bnep_cleanup_module); + +MODULE_DESCRIPTION("BlueZ BNEP ver " VERSION); +MODULE_AUTHOR("David Libault , Maxim Krasnyanskiy "); +MODULE_LICENSE("GPL"); --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/net/bluetooth/bnep/crc32.c 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1,59 @@ +/* + * Based on linux-2.5/lib/crc32 by Matt Domsch + * + * FIXME: Remove in 2.5 + */ + +#include +#include +#include +#include +#include +#include + +#include "crc32.h" + +#define CRCPOLY_BE 0x04c11db7 +#define CRC_BE_BITS 8 + +static u32 *bnep_crc32_table; + +/* + * This code is in the public domain; copyright abandoned. + * Liability for non-performance of this code is limited to the amount + * you paid for it. Since it is distributed for free, your refund will + * be very very small. If it breaks, you get to keep both pieces. + */ +u32 bnep_crc32(u32 crc, unsigned char const *p, size_t len) +{ + while (len--) + crc = (crc << 8) ^ bnep_crc32_table[(crc >> 24) ^ *p++]; + + return crc; +} + +int __init bnep_crc32_init(void) +{ + unsigned i, j; + u32 crc = 0x80000000; + + bnep_crc32_table = kmalloc((1 << CRC_BE_BITS) * sizeof(u32), GFP_KERNEL); + if (!bnep_crc32_table) + return -ENOMEM; + + bnep_crc32_table[0] = 0; + + for (i = 1; i < 1 << CRC_BE_BITS; i <<= 1) { + crc = (crc << 1) ^ ((crc & 0x80000000) ? CRCPOLY_BE : 0); + for (j = 0; j < i; j++) + bnep_crc32_table[i + j] = crc ^ bnep_crc32_table[j]; + } + return 0; +} + +void __exit bnep_crc32_cleanup(void) +{ + if (bnep_crc32_table) + kfree(bnep_crc32_table); + bnep_crc32_table = NULL; +} --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/net/bluetooth/bnep/crc32.h 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1,10 @@ +/* + * crc32.h + * See crc32.c for license and changes + * + * FIXME: Remove in 2.5 + */ + +int bnep_crc32_init(void); +void bnep_crc32_cleanup(void); +u32 bnep_crc32(u32 crc, unsigned char const *p, size_t len); --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/net/bluetooth/bnep/Makefile 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1,10 @@ +# +# Makefile for the Linux Bluetooth BNEP layer +# + +O_TARGET := bnep.o + +obj-y := core.o sock.o netdev.o crc32.o +obj-m += $(O_TARGET) + +include $(TOPDIR)/Rules.make --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/net/bluetooth/bnep/netdev.c 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1,254 @@ +/* + BNEP implementation for Linux Bluetooth stack (BlueZ). + Copyright (C) 2001-2002 Inventel Systemes + Written 2001-2002 by + Clément Moreau + David Libault + + Copyright (C) 2002 Maxim Krasnyanskiy + + 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: netdev.c,v 1.7 2002/07/14 05:39:26 maxk Exp $ + */ + +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "bnep.h" + +#ifndef CONFIG_BLUEZ_BNEP_DEBUG +#undef BT_DBG +#define BT_DBG( A... ) +#endif + +#define BNEP_TX_QUEUE_LEN 20 + +static int bnep_net_open(struct net_device *dev) +{ + netif_start_queue(dev); + return 0; +} + +static int bnep_net_close(struct net_device *dev) +{ + netif_stop_queue(dev); + return 0; +} + +static struct net_device_stats *bnep_net_get_stats(struct net_device *dev) +{ + struct bnep_session *s = dev->priv; + return &s->stats; +} + +static void bnep_net_set_mc_list(struct net_device *dev) +{ +#ifdef CONFIG_BLUEZ_BNEP_MC_FILTER + struct bnep_session *s = dev->priv; + struct sock *sk = s->sock->sk; + struct bnep_set_filter_req *r; + struct sk_buff *skb; + int size; + + BT_DBG("%s mc_count %d", dev->name, dev->mc_count); + + size = sizeof(*r) + (BNEP_MAX_MULTICAST_FILTERS + 1) * ETH_ALEN * 2; + skb = alloc_skb(size, GFP_ATOMIC); + if (!skb) { + BT_ERR("%s Multicast list allocation failed", dev->name); + return; + } + + r = (void *) skb->data; + __skb_put(skb, sizeof(*r)); + + r->type = BNEP_CONTROL; + r->ctrl = BNEP_FILTER_MULTI_ADDR_SET; + + if (dev->flags & (IFF_PROMISC | IFF_ALLMULTI)) { + u8 start[ETH_ALEN] = { 0x01 }; + + /* Request all addresses */ + memcpy(__skb_put(skb, ETH_ALEN), start, ETH_ALEN); + memcpy(__skb_put(skb, ETH_ALEN), dev->broadcast, ETH_ALEN); + r->len = htons(ETH_ALEN * 2); + } else { + struct dev_mc_list *dmi = dev->mc_list; + int i, len = skb->len; + + if (dev->flags & IFF_BROADCAST) { + memcpy(__skb_put(skb, ETH_ALEN), dev->broadcast, ETH_ALEN); + memcpy(__skb_put(skb, ETH_ALEN), dev->broadcast, ETH_ALEN); + } + + /* FIXME: We should group addresses here. */ + + for (i = 0; i < dev->mc_count && i < BNEP_MAX_MULTICAST_FILTERS; i++) { + memcpy(__skb_put(skb, ETH_ALEN), dmi->dmi_addr, ETH_ALEN); + memcpy(__skb_put(skb, ETH_ALEN), dmi->dmi_addr, ETH_ALEN); + dmi = dmi->next; + } + r->len = htons(skb->len - len); + } + + skb_queue_tail(&sk->write_queue, skb); + wake_up_interruptible(sk->sleep); +#endif +} + +static int bnep_net_set_mac_addr(struct net_device *dev, void *arg) +{ + BT_DBG("%s", dev->name); + return 0; +} + +static void bnep_net_timeout(struct net_device *dev) +{ + BT_DBG("net_timeout"); + netif_wake_queue(dev); +} + +static int bnep_net_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) +{ + return -EINVAL; +} + +#ifdef CONFIG_BLUEZ_BNEP_MC_FILTER +static inline int bnep_net_mc_filter(struct sk_buff *skb, struct bnep_session *s) +{ + struct ethhdr *eh = (void *) skb->data; + + if ((eh->h_dest[0] & 1) && !test_bit(bnep_mc_hash(eh->h_dest), &s->mc_filter)) { + BT_DBG("BNEP: filtered skb %p, dst %.2x:%.2x:%.2x:%.2x:%.2x:%.2x", skb, + eh->h_dest[0], eh->h_dest[1], eh->h_dest[2], + eh->h_dest[3], eh->h_dest[4], eh->h_dest[5]); + return 1; + } + return 0; +} +#endif + +#ifdef CONFIG_BLUEZ_BNEP_PROTO_FILTER +/* Determine ether protocol. Based on eth_type_trans. */ +static inline u16 bnep_net_eth_proto(struct sk_buff *skb) +{ + struct ethhdr *eh = (void *) skb->data; + + if (ntohs(eh->h_proto) >= 1536) + return eh->h_proto; + + if (get_unaligned((u16 *) skb->data) == 0xFFFF) + return htons(ETH_P_802_3); + + return htons(ETH_P_802_2); +} + +static inline int bnep_net_proto_filter(struct sk_buff *skb, struct bnep_session *s) +{ + u16 proto = bnep_net_eth_proto(skb); + struct bnep_proto_filter *f = s->proto_filter; + int i; + + for (i = 0; i < BNEP_MAX_PROTO_FILTERS && f[i].end; i++) { + if (proto >= f[i].start && proto <= f[i].end) + return 0; + } + + BT_DBG("BNEP: filtered skb %p, proto 0x%.4x", skb, proto); + return 1; +} +#endif + +static int bnep_net_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct bnep_session *s = dev->priv; + struct sock *sk = s->sock->sk; + + BT_DBG("skb %p, dev %p", skb, dev); + +#ifdef CONFIG_BLUEZ_BNEP_MC_FILTER + if (bnep_net_mc_filter(skb, s)) { + kfree_skb(skb); + return 0; + } +#endif + +#ifdef CONFIG_BLUEZ_BNEP_PROTO_FILTER + if (bnep_net_proto_filter(skb, s)) { + kfree_skb(skb); + return 0; + } +#endif + + /* + * We cannot send L2CAP packets from here as we are potentially in a bh. + * So we have to queue them and wake up session thread which is sleeping + * on the sk->sleep. + */ + dev->trans_start = jiffies; + skb_queue_tail(&sk->write_queue, skb); + wake_up_interruptible(sk->sleep); + + if (skb_queue_len(&sk->write_queue) >= BNEP_TX_QUEUE_LEN) { + BT_DBG("tx queue is full"); + + /* Stop queuing. + * Session thread will do netif_wake_queue() */ + netif_stop_queue(dev); + } + + return 0; +} + +int bnep_net_init(struct net_device *dev) +{ + struct bnep_session *s = dev->priv; + + memcpy(dev->dev_addr, s->eh.h_dest, ETH_ALEN); + dev->addr_len = ETH_ALEN; + + ether_setup(dev); + + dev->open = bnep_net_open; + dev->stop = bnep_net_close; + dev->hard_start_xmit = bnep_net_xmit; + dev->get_stats = bnep_net_get_stats; + dev->do_ioctl = bnep_net_ioctl; + dev->set_mac_address = bnep_net_set_mac_addr; + dev->set_multicast_list = bnep_net_set_mc_list; + + dev->watchdog_timeo = HZ * 2; + dev->tx_timeout = bnep_net_timeout; + + return 0; +} --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/net/bluetooth/bnep/sock.c 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1,238 @@ +/* + BNEP implementation for Linux Bluetooth stack (BlueZ). + Copyright (C) 2001-2002 Inventel Systemes + Written 2001-2002 by + David Libault + + Copyright (C) 2002 Maxim Krasnyanskiy + + 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: sock.c,v 1.3 2002/07/10 22:59:52 maxk Exp $ + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "bnep.h" + +#ifndef CONFIG_BLUEZ_BNEP_DEBUG +#undef BT_DBG +#define BT_DBG( A... ) +#endif + +static inline struct socket *socki_lookup(struct inode *inode) +{ + return &inode->u.socket_i; +} + +static struct socket *sockfd_lookup(int fd, int *err) +{ + struct file *file; + struct inode *inode; + struct socket *sock; + + if (!(file = fget(fd))) { + *err = -EBADF; + return NULL; + } + + inode = file->f_dentry->d_inode; + if (!inode->i_sock || !(sock = socki_lookup(inode))) { + *err = -ENOTSOCK; + fput(file); + return NULL; + } + + if (sock->file != file) { + printk(KERN_ERR "socki_lookup: socket file changed!\n"); + sock->file = file; + } + return sock; +} + +static int bnep_sock_release(struct socket *sock) +{ + struct sock *sk = sock->sk; + + BT_DBG("sock %p sk %p", sock, sk); + + if (!sk) + return 0; + + sock_orphan(sk); + sock_put(sk); + + MOD_DEC_USE_COUNT; + return 0; +} + +static int bnep_sock_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg) +{ + struct bnep_connlist_req cl; + struct bnep_connadd_req ca; + struct bnep_conndel_req cd; + struct bnep_conninfo ci; + struct socket *nsock; + int err; + + BT_DBG("cmd %x arg %lx", cmd, arg); + + switch (cmd) { + case BNEPCONNADD: + if (!capable(CAP_NET_ADMIN)) + return -EACCES; + + if (copy_from_user(&ca, (void *) arg, sizeof(ca))) + return -EFAULT; + + nsock = sockfd_lookup(ca.sock, &err); + if (!nsock) + return err; + + if (nsock->sk->state != BT_CONNECTED) + return -EBADFD; + + err = bnep_add_connection(&ca, nsock); + if (!err) { + if (copy_to_user((void *) arg, &ca, sizeof(ca))) + err = -EFAULT; + } else + fput(nsock->file); + + return err; + + case BNEPCONNDEL: + if (!capable(CAP_NET_ADMIN)) + return -EACCES; + + if (copy_from_user(&cd, (void *) arg, sizeof(cd))) + return -EFAULT; + + return bnep_del_connection(&cd); + + case BNEPGETCONNLIST: + if (copy_from_user(&cl, (void *) arg, sizeof(cl))) + return -EFAULT; + + if (cl.cnum <= 0) + return -EINVAL; + + err = bnep_get_connlist(&cl); + if (!err && copy_to_user((void *) arg, &cl, sizeof(cl))) + return -EFAULT; + + return err; + + case BNEPGETCONNINFO: + if (copy_from_user(&ci, (void *) arg, sizeof(ci))) + return -EFAULT; + + err = bnep_get_conninfo(&ci); + if (!err && copy_to_user((void *) arg, &ci, sizeof(ci))) + return -EFAULT; + + return err; + + default: + return -EINVAL; + } + + return 0; +} + +static struct proto_ops bnep_sock_ops = { + family: PF_BLUETOOTH, + release: bnep_sock_release, + ioctl: bnep_sock_ioctl, + bind: sock_no_bind, + getname: sock_no_getname, + sendmsg: sock_no_sendmsg, + recvmsg: sock_no_recvmsg, + poll: sock_no_poll, + listen: sock_no_listen, + shutdown: sock_no_shutdown, + setsockopt: sock_no_setsockopt, + getsockopt: sock_no_getsockopt, + connect: sock_no_connect, + socketpair: sock_no_socketpair, + accept: sock_no_accept, + mmap: sock_no_mmap +}; + +static int bnep_sock_create(struct socket *sock, int protocol) +{ + struct sock *sk; + + BT_DBG("sock %p", sock); + + if (sock->type != SOCK_RAW) + return -ESOCKTNOSUPPORT; + + sock->ops = &bnep_sock_ops; + + if (!(sk = sk_alloc(PF_BLUETOOTH, GFP_KERNEL, 1))) + return -ENOMEM; + + MOD_INC_USE_COUNT; + + sock->state = SS_UNCONNECTED; + sock_init_data(sock, sk); + + sk->destruct = NULL; + sk->protocol = protocol; + + return 0; +} + +static struct net_proto_family bnep_sock_family_ops = { + family: PF_BLUETOOTH, + create: bnep_sock_create +}; + +int bnep_sock_init(void) +{ + bluez_sock_register(BTPROTO_BNEP, &bnep_sock_family_ops); + return 0; +} + +int bnep_sock_cleanup(void) +{ + if (bluez_sock_unregister(BTPROTO_BNEP)) + BT_ERR("Can't unregister BNEP socket"); + return 0; +} --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/net/bluetooth/cmtp/capi.c 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1,707 @@ +/* + CMTP implementation for Linux Bluetooth stack (BlueZ). + Copyright (C) 2002-2003 Marcel Holtmann + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation; + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. + IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY + CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, + COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS + SOFTWARE IS DISCLAIMED. +*/ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "../drivers/isdn/avmb1/capilli.h" +#include "../drivers/isdn/avmb1/capicmd.h" +#include "../drivers/isdn/avmb1/capiutil.h" + +#include "cmtp.h" + +#ifndef CONFIG_BLUEZ_CMTP_DEBUG +#undef BT_DBG +#define BT_DBG(D...) +#endif + +#define REVISION "1.0" + +#define CAPI_INTEROPERABILITY 0x20 + +#define CAPI_INTEROPERABILITY_REQ CAPICMD(CAPI_INTEROPERABILITY, CAPI_REQ) +#define CAPI_INTEROPERABILITY_CONF CAPICMD(CAPI_INTEROPERABILITY, CAPI_CONF) +#define CAPI_INTEROPERABILITY_IND CAPICMD(CAPI_INTEROPERABILITY, CAPI_IND) +#define CAPI_INTEROPERABILITY_RESP CAPICMD(CAPI_INTEROPERABILITY, CAPI_RESP) + +#define CAPI_INTEROPERABILITY_REQ_LEN (CAPI_MSG_BASELEN + 2) +#define CAPI_INTEROPERABILITY_CONF_LEN (CAPI_MSG_BASELEN + 4) +#define CAPI_INTEROPERABILITY_IND_LEN (CAPI_MSG_BASELEN + 2) +#define CAPI_INTEROPERABILITY_RESP_LEN (CAPI_MSG_BASELEN + 2) + +#define CAPI_FUNCTION_REGISTER 0 +#define CAPI_FUNCTION_RELEASE 1 +#define CAPI_FUNCTION_GET_PROFILE 2 +#define CAPI_FUNCTION_GET_MANUFACTURER 3 +#define CAPI_FUNCTION_GET_VERSION 4 +#define CAPI_FUNCTION_GET_SERIAL_NUMBER 5 +#define CAPI_FUNCTION_MANUFACTURER 6 +#define CAPI_FUNCTION_LOOPBACK 7 + +static struct capi_driver_interface *di; + + +#define CMTP_MSGNUM 1 +#define CMTP_APPLID 2 +#define CMTP_MAPPING 3 + +static struct cmtp_application *cmtp_application_add(struct cmtp_session *session, __u16 appl) +{ + struct cmtp_application *app = kmalloc(sizeof(*app), GFP_KERNEL); + + BT_DBG("session %p application %p appl %d", session, app, appl); + + if (!app) + return NULL; + + memset(app, 0, sizeof(*app)); + + app->state = BT_OPEN; + app->appl = appl; + + list_add_tail(&app->list, &session->applications); + + return app; +} + +static void cmtp_application_del(struct cmtp_session *session, struct cmtp_application *app) +{ + BT_DBG("session %p application %p", session, app); + + if (app) { + list_del(&app->list); + kfree(app); + } +} + +static struct cmtp_application *cmtp_application_get(struct cmtp_session *session, int pattern, __u16 value) +{ + struct cmtp_application *app; + struct list_head *p, *n; + + list_for_each_safe(p, n, &session->applications) { + app = list_entry(p, struct cmtp_application, list); + switch (pattern) { + case CMTP_MSGNUM: + if (app->msgnum == value) + return app; + break; + case CMTP_APPLID: + if (app->appl == value) + return app; + break; + case CMTP_MAPPING: + if (app->mapping == value) + return app; + break; + } + } + + return NULL; +} + +static int cmtp_msgnum_get(struct cmtp_session *session) +{ + session->msgnum++; + + if ((session->msgnum & 0xff) > 200) + session->msgnum = CMTP_INITIAL_MSGNUM + 1; + + return session->msgnum; +} + + +static void cmtp_send_interopmsg(struct cmtp_session *session, + __u8 subcmd, __u16 appl, __u16 msgnum, + __u16 function, unsigned char *buf, int len) +{ + struct sk_buff *skb; + unsigned char *s; + + BT_DBG("session %p subcmd 0x%02x appl %d msgnum %d", session, subcmd, appl, msgnum); + + if (!(skb = alloc_skb(CAPI_MSG_BASELEN + 6 + len, GFP_ATOMIC))) { + BT_ERR("Can't allocate memory for interoperability packet"); + return; + } + + s = skb_put(skb, CAPI_MSG_BASELEN + 6 + len); + + capimsg_setu16(s, 0, CAPI_MSG_BASELEN + 6 + len); + capimsg_setu16(s, 2, appl); + capimsg_setu8 (s, 4, CAPI_INTEROPERABILITY); + capimsg_setu8 (s, 5, subcmd); + capimsg_setu16(s, 6, msgnum); + + /* Interoperability selector (Bluetooth Device Management) */ + capimsg_setu16(s, 8, 0x0001); + + capimsg_setu8 (s, 10, 3 + len); + capimsg_setu16(s, 11, function); + capimsg_setu8 (s, 13, len); + + if (len > 0) + memcpy(s + 14, buf, len); + + cmtp_send_capimsg(session, skb); +} + +static void cmtp_recv_interopmsg(struct cmtp_session *session, struct sk_buff *skb) +{ + struct capi_ctr *ctrl = session->ctrl; + struct cmtp_application *application; + __u16 appl, msgnum, func, info; + __u32 controller; + + BT_DBG("session %p skb %p len %d", session, skb, skb->len); + + switch (CAPIMSG_SUBCOMMAND(skb->data)) { + case CAPI_CONF: + func = CAPIMSG_U16(skb->data, CAPI_MSG_BASELEN + 5); + info = CAPIMSG_U16(skb->data, CAPI_MSG_BASELEN + 8); + + switch (func) { + case CAPI_FUNCTION_REGISTER: + msgnum = CAPIMSG_MSGID(skb->data); + + application = cmtp_application_get(session, CMTP_MSGNUM, msgnum); + if (application) { + application->state = BT_CONNECTED; + application->msgnum = 0; + application->mapping = CAPIMSG_APPID(skb->data); + wake_up_interruptible(&session->wait); + } + + break; + + case CAPI_FUNCTION_RELEASE: + appl = CAPIMSG_APPID(skb->data); + + application = cmtp_application_get(session, CMTP_MAPPING, appl); + if (application) { + application->state = BT_CLOSED; + application->msgnum = 0; + wake_up_interruptible(&session->wait); + } + + break; + + case CAPI_FUNCTION_GET_PROFILE: + controller = CAPIMSG_U16(skb->data, CAPI_MSG_BASELEN + 11); + msgnum = CAPIMSG_MSGID(skb->data); + + if (!info && (msgnum == CMTP_INITIAL_MSGNUM)) { + session->ncontroller = controller; + wake_up_interruptible(&session->wait); + break; + } + + if (!info && ctrl) { + memcpy(&ctrl->profile, + skb->data + CAPI_MSG_BASELEN + 11, + sizeof(capi_profile)); + session->state = BT_CONNECTED; + ctrl->ready(ctrl); + } + + break; + + case CAPI_FUNCTION_GET_MANUFACTURER: + controller = CAPIMSG_U32(skb->data, CAPI_MSG_BASELEN + 10); + + if (!info && ctrl) { + strncpy(ctrl->manu, + skb->data + CAPI_MSG_BASELEN + 15, + skb->data[CAPI_MSG_BASELEN + 14]); + } + + break; + + case CAPI_FUNCTION_GET_VERSION: + controller = CAPIMSG_U32(skb->data, CAPI_MSG_BASELEN + 12); + + if (!info && ctrl) { + ctrl->version.majorversion = CAPIMSG_U32(skb->data, CAPI_MSG_BASELEN + 16); + ctrl->version.minorversion = CAPIMSG_U32(skb->data, CAPI_MSG_BASELEN + 20); + ctrl->version.majormanuversion = CAPIMSG_U32(skb->data, CAPI_MSG_BASELEN + 24); + ctrl->version.minormanuversion = CAPIMSG_U32(skb->data, CAPI_MSG_BASELEN + 28); + } + + break; + + case CAPI_FUNCTION_GET_SERIAL_NUMBER: + controller = CAPIMSG_U32(skb->data, CAPI_MSG_BASELEN + 12); + + if (!info && ctrl) { + memset(ctrl->serial, 0, CAPI_SERIAL_LEN); + strncpy(ctrl->serial, + skb->data + CAPI_MSG_BASELEN + 17, + skb->data[CAPI_MSG_BASELEN + 16]); + } + + break; + } + + break; + + case CAPI_IND: + func = CAPIMSG_U16(skb->data, CAPI_MSG_BASELEN + 3); + + if (func == CAPI_FUNCTION_LOOPBACK) { + appl = CAPIMSG_APPID(skb->data); + msgnum = CAPIMSG_MSGID(skb->data); + cmtp_send_interopmsg(session, CAPI_RESP, appl, msgnum, func, + skb->data + CAPI_MSG_BASELEN + 6, + skb->data[CAPI_MSG_BASELEN + 5]); + } + + break; + } + + kfree_skb(skb); +} + +void cmtp_recv_capimsg(struct cmtp_session *session, struct sk_buff *skb) +{ + struct capi_ctr *ctrl = session->ctrl; + struct cmtp_application *application; + __u16 cmd, appl, info; + __u32 ncci, contr; + + BT_DBG("session %p skb %p len %d", session, skb, skb->len); + + if (CAPIMSG_COMMAND(skb->data) == CAPI_INTEROPERABILITY) { + cmtp_recv_interopmsg(session, skb); + return; + } + + if (session->flags & (1 << CMTP_LOOPBACK)) { + kfree_skb(skb); + return; + } + + cmd = CAPICMD(CAPIMSG_COMMAND(skb->data), CAPIMSG_SUBCOMMAND(skb->data)); + appl = CAPIMSG_APPID(skb->data); + contr = CAPIMSG_CONTROL(skb->data); + + application = cmtp_application_get(session, CMTP_MAPPING, appl); + if (application) { + appl = application->appl; + CAPIMSG_SETAPPID(skb->data, appl); + } else { + BT_ERR("Can't find application with id %d", appl); + kfree_skb(skb); + return; + } + + if ((contr & 0x7f) == 0x01) { + contr = (contr & 0xffffff80) | session->num; + CAPIMSG_SETCONTROL(skb->data, contr); + } + + if (!ctrl) { + BT_ERR("Can't find controller %d for message", session->num); + kfree_skb(skb); + return; + } + + switch (cmd) { + case CAPI_CONNECT_B3_CONF: + ncci = CAPIMSG_NCCI(skb->data); + info = CAPIMSG_U16(skb->data, 12); + + BT_DBG("CONNECT_B3_CONF ncci 0x%02x info 0x%02x", ncci, info); + + if (info == 0) + ctrl->new_ncci(ctrl, appl, ncci, 8); + + ctrl->handle_capimsg(ctrl, appl, skb); + break; + + case CAPI_CONNECT_B3_IND: + ncci = CAPIMSG_NCCI(skb->data); + + BT_DBG("CONNECT_B3_IND ncci 0x%02x", ncci); + + ctrl->new_ncci(ctrl, appl, ncci, 8); + ctrl->handle_capimsg(ctrl, appl, skb); + break; + + case CAPI_DISCONNECT_B3_IND: + ncci = CAPIMSG_NCCI(skb->data); + + BT_DBG("DISCONNECT_B3_IND ncci 0x%02x", ncci); + + if (ncci == 0xffffffff) + BT_ERR("DISCONNECT_B3_IND with ncci 0xffffffff"); + + ctrl->handle_capimsg(ctrl, appl, skb); + ctrl->free_ncci(ctrl, appl, ncci); + break; + + default: + ctrl->handle_capimsg(ctrl, appl, skb); + break; + } +} + +void cmtp_send_capimsg(struct cmtp_session *session, struct sk_buff *skb) +{ + struct cmtp_scb *scb = (void *) skb->cb; + + BT_DBG("session %p skb %p len %d", session, skb, skb->len); + + scb->id = -1; + scb->data = (CAPIMSG_COMMAND(skb->data) == CAPI_DATA_B3); + + skb_queue_tail(&session->transmit, skb); + + cmtp_schedule(session); +} + + +static int cmtp_load_firmware(struct capi_ctr *ctrl, capiloaddata *data) +{ + BT_DBG("ctrl %p data %p", ctrl, data); + + return -EIO; +} + +static void cmtp_reset_ctr(struct capi_ctr *ctrl) +{ + BT_DBG("ctrl %p", ctrl); + + ctrl->reseted(ctrl); +} + +static void cmtp_remove_ctr(struct capi_ctr *ctrl) +{ + struct cmtp_session *session = ctrl->driverdata; + + BT_DBG("ctrl %p", ctrl); + + ctrl->suspend_output(ctrl); + + atomic_inc(&session->terminate); + cmtp_schedule(session); +} + +static void cmtp_register_appl(struct capi_ctr *ctrl, __u16 appl, capi_register_params *rp) +{ + DECLARE_WAITQUEUE(wait, current); + struct cmtp_session *session = ctrl->driverdata; + struct cmtp_application *application; + unsigned long timeo = CMTP_INTEROP_TIMEOUT; + unsigned char buf[8]; + int err = 0, nconn, want = rp->level3cnt; + + BT_DBG("ctrl %p appl %d level3cnt %d datablkcnt %d datablklen %d", + ctrl, appl, rp->level3cnt, rp->datablkcnt, rp->datablklen); + + application = cmtp_application_add(session, appl); + if (!application) { + BT_ERR("Can't allocate memory for new application"); + ctrl->appl_released(ctrl, appl); + return; + } + + if (want < 0) + nconn = ctrl->profile.nbchannel * -want; + else + nconn = want; + + if (nconn == 0) + nconn = ctrl->profile.nbchannel; + + capimsg_setu16(buf, 0, nconn); + capimsg_setu16(buf, 2, rp->datablkcnt); + capimsg_setu16(buf, 4, rp->datablklen); + + application->state = BT_CONFIG; + application->msgnum = cmtp_msgnum_get(session); + + cmtp_send_interopmsg(session, CAPI_REQ, 0x0000, application->msgnum, + CAPI_FUNCTION_REGISTER, buf, 6); + + add_wait_queue(&session->wait, &wait); + while (1) { + set_current_state(TASK_INTERRUPTIBLE); + + if (!timeo) { + err = -EAGAIN; + break; + } + + if (application->state == BT_CLOSED) { + err = -application->err; + break; + } + + if (application->state == BT_CONNECTED) + break; + + if (signal_pending(current)) { + err = -EINTR; + break; + } + + timeo = schedule_timeout(timeo); + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&session->wait, &wait); + + if (err) { + ctrl->appl_released(ctrl, appl); + cmtp_application_del(session, application); + return; + } + + ctrl->appl_registered(ctrl, appl); +} + +static void cmtp_release_appl(struct capi_ctr *ctrl, __u16 appl) +{ + DECLARE_WAITQUEUE(wait, current); + struct cmtp_session *session = ctrl->driverdata; + struct cmtp_application *application; + unsigned long timeo = CMTP_INTEROP_TIMEOUT; + + BT_DBG("ctrl %p appl %d", ctrl, appl); + + application = cmtp_application_get(session, CMTP_APPLID, appl); + if (!application) { + BT_ERR("Can't find application"); + return; + } + + application->msgnum = cmtp_msgnum_get(session); + + cmtp_send_interopmsg(session, CAPI_REQ, application->mapping, application->msgnum, + CAPI_FUNCTION_RELEASE, NULL, 0); + + add_wait_queue(&session->wait, &wait); + while (timeo) { + set_current_state(TASK_INTERRUPTIBLE); + + if (application->state == BT_CLOSED) + break; + + if (signal_pending(current)) + break; + + timeo = schedule_timeout(timeo); + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&session->wait, &wait); + + cmtp_application_del(session, application); + ctrl->appl_released(ctrl, appl); +} + +static void cmtp_send_message(struct capi_ctr *ctrl, struct sk_buff *skb) +{ + struct cmtp_session *session = ctrl->driverdata; + struct cmtp_application *application; + __u16 appl; + __u32 contr; + + BT_DBG("ctrl %p skb %p", ctrl, skb); + + appl = CAPIMSG_APPID(skb->data); + contr = CAPIMSG_CONTROL(skb->data); + + application = cmtp_application_get(session, CMTP_APPLID, appl); + if ((!application) || (application->state != BT_CONNECTED)) { + BT_ERR("Can't find application with id %d", appl); + kfree_skb(skb); + return; + } + + CAPIMSG_SETAPPID(skb->data, application->mapping); + + if ((contr & 0x7f) == session->num) { + contr = (contr & 0xffffff80) | 0x01; + CAPIMSG_SETCONTROL(skb->data, contr); + } + + cmtp_send_capimsg(session, skb); +} + +static char *cmtp_procinfo(struct capi_ctr *ctrl) +{ + return "CAPI Message Transport Protocol"; +} + +static int cmtp_ctr_read_proc(char *page, char **start, off_t off, int count, int *eof, struct capi_ctr *ctrl) +{ + struct cmtp_session *session = ctrl->driverdata; + struct cmtp_application *app; + struct list_head *p, *n; + int len = 0; + + len += sprintf(page + len, "%s (Revision %s)\n\n", cmtp_procinfo(ctrl), REVISION); + len += sprintf(page + len, "addr %s\n", session->name); + len += sprintf(page + len, "ctrl %d\n", session->num); + + list_for_each_safe(p, n, &session->applications) { + app = list_entry(p, struct cmtp_application, list); + len += sprintf(page + len, "appl %d -> %d\n", app->appl, app->mapping); + } + + if (off + count >= len) + *eof = 1; + + if (len < off) + return 0; + + *start = page + off; + + return ((count < len - off) ? count : len - off); +} + +static struct capi_driver cmtp_driver = { + name: "cmtp", + revision: REVISION, + load_firmware: cmtp_load_firmware, + reset_ctr: cmtp_reset_ctr, + remove_ctr: cmtp_remove_ctr, + register_appl: cmtp_register_appl, + release_appl: cmtp_release_appl, + send_message: cmtp_send_message, + procinfo: cmtp_procinfo, + ctr_read_proc: cmtp_ctr_read_proc, + + driver_read_proc: 0, + add_card: 0, +}; + + +int cmtp_attach_device(struct cmtp_session *session) +{ + DECLARE_WAITQUEUE(wait, current); + unsigned long timeo = CMTP_INTEROP_TIMEOUT; + unsigned char buf[4]; + + BT_DBG("session %p", session); + + capimsg_setu32(buf, 0, 0); + + cmtp_send_interopmsg(session, CAPI_REQ, 0xffff, CMTP_INITIAL_MSGNUM, + CAPI_FUNCTION_GET_PROFILE, buf, 4); + + add_wait_queue(&session->wait, &wait); + while (timeo) { + set_current_state(TASK_INTERRUPTIBLE); + + if (session->ncontroller) + break; + + if (signal_pending(current)) + break; + + timeo = schedule_timeout(timeo); + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&session->wait, &wait); + + BT_INFO("Found %d CAPI controller(s) on device %s", session->ncontroller, session->name); + + if (!timeo) + return -ETIMEDOUT; + + if (!session->ncontroller) + return -ENODEV; + + + if (session->ncontroller > 1) + BT_INFO("Setting up only CAPI controller 1"); + + if (!(session->ctrl = di->attach_ctr(&cmtp_driver, session->name, session))) { + BT_ERR("Can't attach new controller"); + return -EBUSY; + } + + session->num = session->ctrl->cnr; + + BT_DBG("session %p ctrl %p num %d", session, session->ctrl, session->num); + + capimsg_setu32(buf, 0, 1); + + cmtp_send_interopmsg(session, CAPI_REQ, 0xffff, cmtp_msgnum_get(session), + CAPI_FUNCTION_GET_MANUFACTURER, buf, 4); + + cmtp_send_interopmsg(session, CAPI_REQ, 0xffff, cmtp_msgnum_get(session), + CAPI_FUNCTION_GET_VERSION, buf, 4); + + cmtp_send_interopmsg(session, CAPI_REQ, 0xffff, cmtp_msgnum_get(session), + CAPI_FUNCTION_GET_SERIAL_NUMBER, buf, 4); + + cmtp_send_interopmsg(session, CAPI_REQ, 0xffff, cmtp_msgnum_get(session), + CAPI_FUNCTION_GET_PROFILE, buf, 4); + + return 0; +} + +void cmtp_detach_device(struct cmtp_session *session) +{ + struct capi_ctr *ctrl = session->ctrl; + + BT_DBG("session %p ctrl %p", session, ctrl); + + if (!ctrl) + return; + + ctrl->reseted(ctrl); + + di->detach_ctr(ctrl); +} + +int cmtp_init_capi(void) +{ + if (!(di = attach_capi_driver(&cmtp_driver))) { + BT_ERR("Can't attach CAPI driver"); + return -EIO; + } + + return 0; +} + +void cmtp_cleanup_capi(void) +{ + detach_capi_driver(&cmtp_driver); +} --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/net/bluetooth/cmtp/cmtp.h 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1,138 @@ +/* + CMTP implementation for Linux Bluetooth stack (BlueZ). + Copyright (C) 2002-2003 Marcel Holtmann + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation; + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. + IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY + CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, + COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS + SOFTWARE IS DISCLAIMED. +*/ + +#ifndef __CMTP_H +#define __CMTP_H + +#include +#include + +#define BTNAMSIZ 18 + +/* CMTP ioctl defines */ +#define CMTPCONNADD _IOW('C', 200, int) +#define CMTPCONNDEL _IOW('C', 201, int) +#define CMTPGETCONNLIST _IOR('C', 210, int) +#define CMTPGETCONNINFO _IOR('C', 211, int) + +#define CMTP_LOOPBACK 0 + +struct cmtp_connadd_req { + int sock; // Connected socket + __u32 flags; +}; + +struct cmtp_conndel_req { + bdaddr_t bdaddr; + __u32 flags; +}; + +struct cmtp_conninfo { + bdaddr_t bdaddr; + __u32 flags; + __u16 state; + int num; +}; + +struct cmtp_connlist_req { + __u32 cnum; + struct cmtp_conninfo *ci; +}; + +int cmtp_add_connection(struct cmtp_connadd_req *req, struct socket *sock); +int cmtp_del_connection(struct cmtp_conndel_req *req); +int cmtp_get_connlist(struct cmtp_connlist_req *req); +int cmtp_get_conninfo(struct cmtp_conninfo *ci); + +/* CMTP session defines */ +#define CMTP_INTEROP_TIMEOUT (HZ * 5) +#define CMTP_INITIAL_MSGNUM 0xff00 + +struct cmtp_session { + struct list_head list; + + struct socket *sock; + + bdaddr_t bdaddr; + + unsigned long state; + unsigned long flags; + + uint mtu; + + char name[BTNAMSIZ]; + + atomic_t terminate; + + wait_queue_head_t wait; + + int ncontroller; + int num; + struct capi_ctr *ctrl; + + struct list_head applications; + + unsigned long blockids; + int msgnum; + + struct sk_buff_head transmit; + + struct sk_buff *reassembly[16]; +}; + +struct cmtp_application { + struct list_head list; + + unsigned long state; + int err; + + __u16 appl; + __u16 mapping; + + __u16 msgnum; +}; + +struct cmtp_scb { + int id; + int data; +}; + +int cmtp_attach_device(struct cmtp_session *session); +void cmtp_detach_device(struct cmtp_session *session); + +void cmtp_recv_capimsg(struct cmtp_session *session, struct sk_buff *skb); +void cmtp_send_capimsg(struct cmtp_session *session, struct sk_buff *skb); + +static inline void cmtp_schedule(struct cmtp_session *session) +{ + struct sock *sk = session->sock->sk; + + wake_up_interruptible(sk->sleep); +} + +/* CMTP init defines */ +int cmtp_init_capi(void); +int cmtp_init_sockets(void); +void cmtp_cleanup_capi(void); +void cmtp_cleanup_sockets(void); + +#endif /* __CMTP_H */ --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/net/bluetooth/cmtp/Config.in 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1,7 @@ +# +# Bluetooth CMTP layer configuration +# + +if [ "$CONFIG_ISDN" = "y" -o "$CONFIG_ISDN" = "m" ]; then + dep_tristate 'CMTP protocol support' CONFIG_BLUEZ_CMTP $CONFIG_ISDN_CAPI $CONFIG_BLUEZ_L2CAP +fi --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/net/bluetooth/cmtp/core.c 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1,515 @@ +/* + CMTP implementation for Linux Bluetooth stack (BlueZ). + Copyright (C) 2002-2003 Marcel Holtmann + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation; + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. + IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY + CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, + COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS + SOFTWARE IS DISCLAIMED. +*/ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "cmtp.h" + +#ifndef CONFIG_BLUEZ_CMTP_DEBUG +#undef BT_DBG +#define BT_DBG(D...) +#endif + +#define VERSION "1.0" + +static DECLARE_RWSEM(cmtp_session_sem); +static LIST_HEAD(cmtp_session_list); + +static struct cmtp_session *__cmtp_get_session(bdaddr_t *bdaddr) +{ + struct cmtp_session *session; + struct list_head *p; + + BT_DBG(""); + + list_for_each(p, &cmtp_session_list) { + session = list_entry(p, struct cmtp_session, list); + if (!bacmp(bdaddr, &session->bdaddr)) + return session; + } + return NULL; +} + +static void __cmtp_link_session(struct cmtp_session *session) +{ + MOD_INC_USE_COUNT; + list_add(&session->list, &cmtp_session_list); +} + +static void __cmtp_unlink_session(struct cmtp_session *session) +{ + list_del(&session->list); + MOD_DEC_USE_COUNT; +} + +static void __cmtp_copy_session(struct cmtp_session *session, struct cmtp_conninfo *ci) +{ + bacpy(&ci->bdaddr, &session->bdaddr); + + ci->flags = session->flags; + ci->state = session->state; + + ci->num = session->num; +} + + +static inline int cmtp_alloc_block_id(struct cmtp_session *session) +{ + int i, id = -1; + + for (i = 0; i < 16; i++) + if (!test_and_set_bit(i, &session->blockids)) { + id = i; + break; + } + + return id; +} + +static inline void cmtp_free_block_id(struct cmtp_session *session, int id) +{ + clear_bit(id, &session->blockids); +} + +static inline void cmtp_add_msgpart(struct cmtp_session *session, int id, const unsigned char *buf, int count) +{ + struct sk_buff *skb = session->reassembly[id], *nskb; + int size; + + BT_DBG("session %p buf %p count %d", session, buf, count); + + size = (skb) ? skb->len + count : count; + + if (!(nskb = alloc_skb(size, GFP_ATOMIC))) { + BT_ERR("Can't allocate memory for CAPI message"); + return; + } + + if (skb && (skb->len > 0)) + memcpy(skb_put(nskb, skb->len), skb->data, skb->len); + + memcpy(skb_put(nskb, count), buf, count); + + session->reassembly[id] = nskb; + + if (skb) + kfree_skb(skb); +} + +static inline int cmtp_recv_frame(struct cmtp_session *session, struct sk_buff *skb) +{ + __u8 hdr, hdrlen, id; + __u16 len; + + BT_DBG("session %p skb %p len %d", session, skb, skb->len); + + while (skb->len > 0) { + hdr = skb->data[0]; + + switch (hdr & 0xc0) { + case 0x40: + hdrlen = 2; + len = skb->data[1]; + break; + case 0x80: + hdrlen = 3; + len = skb->data[1] | (skb->data[2] << 8); + break; + default: + hdrlen = 1; + len = 0; + break; + } + + id = (hdr & 0x3c) >> 2; + + BT_DBG("hdr 0x%02x hdrlen %d len %d id %d", hdr, hdrlen, len, id); + + if (hdrlen + len > skb->len) { + BT_ERR("Wrong size or header information in CMTP frame"); + break; + } + + if (len == 0) { + skb_pull(skb, hdrlen); + continue; + } + + switch (hdr & 0x03) { + case 0x00: + cmtp_add_msgpart(session, id, skb->data + hdrlen, len); + cmtp_recv_capimsg(session, session->reassembly[id]); + session->reassembly[id] = NULL; + break; + case 0x01: + cmtp_add_msgpart(session, id, skb->data + hdrlen, len); + break; + default: + if (session->reassembly[id] != NULL) + kfree_skb(session->reassembly[id]); + session->reassembly[id] = NULL; + break; + } + + skb_pull(skb, hdrlen + len); + } + + kfree_skb(skb); + return 0; +} + +static int cmtp_send_frame(struct cmtp_session *session, unsigned char *data, int len) +{ + struct socket *sock = session->sock; + struct iovec iv = { data, len }; + struct msghdr msg; + int err; + + BT_DBG("session %p data %p len %d", session, data, len); + + if (!len) + return 0; + + memset(&msg, 0, sizeof(msg)); + msg.msg_iovlen = 1; + msg.msg_iov = &iv; + + err = sock->ops->sendmsg(sock, &msg, len, 0); + return err; +} + +static int cmtp_process_transmit(struct cmtp_session *session) +{ + struct sk_buff *skb, *nskb; + unsigned char *hdr; + unsigned int size, tail; + + BT_DBG("session %p", session); + + if (!(nskb = alloc_skb(session->mtu, GFP_ATOMIC))) { + BT_ERR("Can't allocate memory for new frame"); + return -ENOMEM; + } + + while ((skb = skb_dequeue(&session->transmit))) { + struct cmtp_scb *scb = (void *) skb->cb; + + if ((tail = (session->mtu - nskb->len)) < 5) { + cmtp_send_frame(session, nskb->data, nskb->len); + skb_trim(nskb, 0); + tail = session->mtu; + } + + size = min_t(uint, ((tail < 258) ? (tail - 2) : (tail - 3)), skb->len); + + if ((scb->id < 0) && ((scb->id = cmtp_alloc_block_id(session)) < 0)) { + skb_queue_head(&session->transmit, skb); + break; + } + + if (size < 256) { + hdr = skb_put(nskb, 2); + hdr[0] = 0x40 + | ((scb->id << 2) & 0x3c) + | ((skb->len == size) ? 0x00 : 0x01); + hdr[1] = size; + } else { + hdr = skb_put(nskb, 3); + hdr[0] = 0x80 + | ((scb->id << 2) & 0x3c) + | ((skb->len == size) ? 0x00 : 0x01); + hdr[1] = size & 0xff; + hdr[2] = size >> 8; + } + + memcpy(skb_put(nskb, size), skb->data, size); + skb_pull(skb, size); + + if (skb->len > 0) { + skb_queue_head(&session->transmit, skb); + } else { + cmtp_free_block_id(session, scb->id); + if (scb->data) { + cmtp_send_frame(session, nskb->data, nskb->len); + skb_trim(nskb, 0); + } + kfree_skb(skb); + } + } + + cmtp_send_frame(session, nskb->data, nskb->len); + + kfree_skb(nskb); + + return skb_queue_len(&session->transmit); +} + +static int cmtp_session(void *arg) +{ + struct cmtp_session *session = arg; + struct sock *sk = session->sock->sk; + struct sk_buff *skb; + wait_queue_t wait; + + BT_DBG("session %p", session); + + daemonize(); reparent_to_init(); + + sprintf(current->comm, "kcmtpd_ctr_%d", session->num); + + sigfillset(¤t->blocked); + flush_signals(current); + + current->nice = -15; + + set_fs(KERNEL_DS); + + init_waitqueue_entry(&wait, current); + add_wait_queue(sk->sleep, &wait); + while (!atomic_read(&session->terminate)) { + set_current_state(TASK_INTERRUPTIBLE); + + if (sk->state != BT_CONNECTED) + break; + + while ((skb = skb_dequeue(&sk->receive_queue))) { + skb_orphan(skb); + cmtp_recv_frame(session, skb); + } + + cmtp_process_transmit(session); + + schedule(); + } + set_current_state(TASK_RUNNING); + remove_wait_queue(sk->sleep, &wait); + + down_write(&cmtp_session_sem); + + if (!(session->flags & (1 << CMTP_LOOPBACK))) + cmtp_detach_device(session); + + fput(session->sock->file); + + __cmtp_unlink_session(session); + + up_write(&cmtp_session_sem); + + kfree(session); + return 0; +} + +int cmtp_add_connection(struct cmtp_connadd_req *req, struct socket *sock) +{ + struct cmtp_session *session, *s; + bdaddr_t src, dst; + int i, err; + + BT_DBG(""); + + baswap(&src, &bluez_pi(sock->sk)->src); + baswap(&dst, &bluez_pi(sock->sk)->dst); + + session = kmalloc(sizeof(struct cmtp_session), GFP_KERNEL); + if (!session) + return -ENOMEM; + memset(session, 0, sizeof(struct cmtp_session)); + + down_write(&cmtp_session_sem); + + s = __cmtp_get_session(&bluez_pi(sock->sk)->dst); + if (s && s->state == BT_CONNECTED) { + err = -EEXIST; + goto failed; + } + + bacpy(&session->bdaddr, &bluez_pi(sock->sk)->dst); + + session->mtu = min_t(uint, l2cap_pi(sock->sk)->omtu, l2cap_pi(sock->sk)->imtu); + + BT_DBG("mtu %d", session->mtu); + + sprintf(session->name, "%s", batostr(&dst)); + + session->sock = sock; + session->state = BT_CONFIG; + + init_waitqueue_head(&session->wait); + + session->ctrl = NULL; + session->msgnum = CMTP_INITIAL_MSGNUM; + + INIT_LIST_HEAD(&session->applications); + + skb_queue_head_init(&session->transmit); + + for (i = 0; i < 16; i++) + session->reassembly[i] = NULL; + + session->flags = req->flags; + + __cmtp_link_session(session); + + err = kernel_thread(cmtp_session, session, CLONE_FS | CLONE_FILES | CLONE_SIGHAND); + if (err < 0) + goto unlink; + + if (!(session->flags & (1 << CMTP_LOOPBACK))) { + err = cmtp_attach_device(session); + if (err < 0) + goto detach; + } + + up_write(&cmtp_session_sem); + return 0; + +detach: + cmtp_detach_device(session); + +unlink: + __cmtp_unlink_session(session); + +failed: + up_write(&cmtp_session_sem); + kfree(session); + return err; +} + +int cmtp_del_connection(struct cmtp_conndel_req *req) +{ + struct cmtp_session *session; + int err = 0; + + BT_DBG(""); + + down_read(&cmtp_session_sem); + + session = __cmtp_get_session(&req->bdaddr); + if (session) { + /* Flush the transmit queue */ + skb_queue_purge(&session->transmit); + + /* Kill session thread */ + atomic_inc(&session->terminate); + cmtp_schedule(session); + } else + err = -ENOENT; + + up_read(&cmtp_session_sem); + return err; +} + +int cmtp_get_connlist(struct cmtp_connlist_req *req) +{ + struct list_head *p; + int err = 0, n = 0; + + BT_DBG(""); + + down_read(&cmtp_session_sem); + + list_for_each(p, &cmtp_session_list) { + struct cmtp_session *session; + struct cmtp_conninfo ci; + + session = list_entry(p, struct cmtp_session, list); + + __cmtp_copy_session(session, &ci); + + if (copy_to_user(req->ci, &ci, sizeof(ci))) { + err = -EFAULT; + break; + } + + if (++n >= req->cnum) + break; + + req->ci++; + } + req->cnum = n; + + up_read(&cmtp_session_sem); + return err; +} + +int cmtp_get_conninfo(struct cmtp_conninfo *ci) +{ + struct cmtp_session *session; + int err = 0; + + down_read(&cmtp_session_sem); + + session = __cmtp_get_session(&ci->bdaddr); + if (session) + __cmtp_copy_session(session, ci); + else + err = -ENOENT; + + up_read(&cmtp_session_sem); + return err; +} + + +int __init init_cmtp(void) +{ + l2cap_load(); + + cmtp_init_capi(); + cmtp_init_sockets(); + + BT_INFO("BlueZ CMTP ver %s", VERSION); + BT_INFO("Copyright (C) 2002-2003 Marcel Holtmann "); + + return 0; +} + +void __exit exit_cmtp(void) +{ + cmtp_cleanup_sockets(); + cmtp_cleanup_capi(); +} + +module_init(init_cmtp); +module_exit(exit_cmtp); + +MODULE_AUTHOR("Marcel Holtmann "); +MODULE_DESCRIPTION("BlueZ CMTP ver " VERSION); +MODULE_LICENSE("GPL"); --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/net/bluetooth/cmtp/Makefile 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1,10 @@ +# +# Makefile for the Linux Bluetooth CMTP layer +# + +O_TARGET := cmtp.o + +obj-y := core.o sock.o capi.o +obj-m += $(O_TARGET) + +include $(TOPDIR)/Rules.make --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/net/bluetooth/cmtp/sock.c 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1,236 @@ +/* + CMTP implementation for Linux Bluetooth stack (BlueZ). + Copyright (C) 2002-2003 Marcel Holtmann + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation; + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. + IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY + CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, + COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS + SOFTWARE IS DISCLAIMED. +*/ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "cmtp.h" + +#ifndef CONFIG_BLUEZ_CMTP_DEBUG +#undef BT_DBG +#define BT_DBG(D...) +#endif + +static inline struct socket *socki_lookup(struct inode *inode) +{ + return &inode->u.socket_i; +} + +static struct socket *sockfd_lookup(int fd, int *err) +{ + struct file *file; + struct inode *inode; + struct socket *sock; + + if (!(file = fget(fd))) { + *err = -EBADF; + return NULL; + } + + inode = file->f_dentry->d_inode; + if (!inode->i_sock || !(sock = socki_lookup(inode))) { + *err = -ENOTSOCK; + fput(file); + return NULL; + } + + if (sock->file != file) { + printk(KERN_ERR "socki_lookup: socket file changed!\n"); + sock->file = file; + } + return sock; +} + +static int cmtp_sock_release(struct socket *sock) +{ + struct sock *sk = sock->sk; + + BT_DBG("sock %p sk %p", sock, sk); + + if (!sk) + return 0; + + sock_orphan(sk); + sock_put(sk); + + MOD_DEC_USE_COUNT; + return 0; +} + +static int cmtp_sock_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg) +{ + struct cmtp_connadd_req ca; + struct cmtp_conndel_req cd; + struct cmtp_connlist_req cl; + struct cmtp_conninfo ci; + struct socket *nsock; + int err; + + BT_DBG("cmd %x arg %lx", cmd, arg); + + switch (cmd) { + case CMTPCONNADD: + if (!capable(CAP_NET_ADMIN)) + return -EACCES; + + if (copy_from_user(&ca, (void *) arg, sizeof(ca))) + return -EFAULT; + + nsock = sockfd_lookup(ca.sock, &err); + if (!nsock) + return err; + + if (nsock->sk->state != BT_CONNECTED) + return -EBADFD; + + err = cmtp_add_connection(&ca, nsock); + if (!err) { + if (copy_to_user((void *) arg, &ca, sizeof(ca))) + err = -EFAULT; + } else + fput(nsock->file); + + return err; + + case CMTPCONNDEL: + if (!capable(CAP_NET_ADMIN)) + return -EACCES; + + if (copy_from_user(&cd, (void *) arg, sizeof(cd))) + return -EFAULT; + + return cmtp_del_connection(&cd); + + case CMTPGETCONNLIST: + if (copy_from_user(&cl, (void *) arg, sizeof(cl))) + return -EFAULT; + + if (cl.cnum <= 0) + return -EINVAL; + + err = cmtp_get_connlist(&cl); + if (!err && copy_to_user((void *) arg, &cl, sizeof(cl))) + return -EFAULT; + + return err; + + case CMTPGETCONNINFO: + if (copy_from_user(&ci, (void *) arg, sizeof(ci))) + return -EFAULT; + + err = cmtp_get_conninfo(&ci); + if (!err && copy_to_user((void *) arg, &ci, sizeof(ci))) + return -EFAULT; + + return err; + } + + return -EINVAL; +} + +static struct proto_ops cmtp_sock_ops = { + family: PF_BLUETOOTH, + release: cmtp_sock_release, + ioctl: cmtp_sock_ioctl, + bind: sock_no_bind, + getname: sock_no_getname, + sendmsg: sock_no_sendmsg, + recvmsg: sock_no_recvmsg, + poll: sock_no_poll, + listen: sock_no_listen, + shutdown: sock_no_shutdown, + setsockopt: sock_no_setsockopt, + getsockopt: sock_no_getsockopt, + connect: sock_no_connect, + socketpair: sock_no_socketpair, + accept: sock_no_accept, + mmap: sock_no_mmap +}; + +static int cmtp_sock_create(struct socket *sock, int protocol) +{ + struct sock *sk; + + BT_DBG("sock %p", sock); + + if (sock->type != SOCK_RAW) + return -ESOCKTNOSUPPORT; + + sock->ops = &cmtp_sock_ops; + + if (!(sk = sk_alloc(PF_BLUETOOTH, GFP_KERNEL, 1))) + return -ENOMEM; + + MOD_INC_USE_COUNT; + + sock->state = SS_UNCONNECTED; + sock_init_data(sock, sk); + + sk->destruct = NULL; + sk->protocol = protocol; + + return 0; +} + +static struct net_proto_family cmtp_sock_family_ops = { + family: PF_BLUETOOTH, + create: cmtp_sock_create +}; + +int cmtp_init_sockets(void) +{ + int err; + + if ((err = bluez_sock_register(BTPROTO_CMTP, &cmtp_sock_family_ops))) { + BT_ERR("Can't register CMTP socket layer (%d)", err); + return err; + } + + return 0; +} + +void cmtp_cleanup_sockets(void) +{ + int err; + + if ((err = bluez_sock_unregister(BTPROTO_CMTP))) + BT_ERR("Can't unregister CMTP socket layer (%d)", err); + + return; +} --- linux/net/bluetooth/Config.in~bluetooth-2.4.18-mh11 2001-06-12 04:15:27.000000000 +0200 +++ linux/net/bluetooth/Config.in 2004-01-25 23:37:39.000000000 +0100 @@ -1,16 +1,22 @@ # -# Bluetooth configuration +# Bluetooth subsystem configuration # if [ "$CONFIG_NET" != "n" ]; then + mainmenu_option next_comment comment 'Bluetooth support' dep_tristate 'Bluetooth subsystem support' CONFIG_BLUEZ $CONFIG_NET if [ "$CONFIG_BLUEZ" != "n" ]; then dep_tristate 'L2CAP protocol support' CONFIG_BLUEZ_L2CAP $CONFIG_BLUEZ + dep_tristate 'SCO links support' CONFIG_BLUEZ_SCO $CONFIG_BLUEZ + source net/bluetooth/rfcomm/Config.in + source net/bluetooth/bnep/Config.in + source net/bluetooth/cmtp/Config.in source drivers/bluetooth/Config.in fi + endmenu fi --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/net/bluetooth/hci_conn.c 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1,435 @@ +/* + BlueZ - Bluetooth protocol stack for Linux + Copyright (C) 2000-2001 Qualcomm Incorporated + + Written 2000,2001 by Maxim Krasnyansky + + 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. +*/ + +/* + * HCI Connection handling. + * + * $Id: hci_conn.c,v 1.5 2002/07/17 18:46:25 maxk Exp $ + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#ifndef HCI_CORE_DEBUG +#undef BT_DBG +#define BT_DBG( A... ) +#endif + +void hci_acl_connect(struct hci_conn *conn) +{ + struct hci_dev *hdev = conn->hdev; + struct inquiry_entry *ie; + create_conn_cp cp; + + BT_DBG("%p", conn); + + conn->state = BT_CONNECT; + conn->out = 1; + conn->link_mode = HCI_LM_MASTER; + + memset(&cp, 0, sizeof(cp)); + bacpy(&cp.bdaddr, &conn->dst); + cp.pscan_rep_mode = 0x02; + + if ((ie = inquiry_cache_lookup(hdev, &conn->dst)) && + inquiry_entry_age(ie) <= INQUIRY_ENTRY_AGE_MAX) { + cp.pscan_rep_mode = ie->info.pscan_rep_mode; + cp.pscan_mode = ie->info.pscan_mode; + cp.clock_offset = ie->info.clock_offset | __cpu_to_le16(0x8000); + } + + cp.pkt_type = __cpu_to_le16(hdev->pkt_type & ACL_PTYPE_MASK); + if (lmp_rswitch_capable(hdev) && !(hdev->link_mode & HCI_LM_MASTER)) + cp.role_switch = 0x01; + else + cp.role_switch = 0x00; + + hci_send_cmd(hdev, OGF_LINK_CTL, OCF_CREATE_CONN, + CREATE_CONN_CP_SIZE, &cp); +} + +void hci_acl_disconn(struct hci_conn *conn, __u8 reason) +{ + disconnect_cp cp; + + BT_DBG("%p", conn); + + conn->state = BT_DISCONN; + + cp.handle = __cpu_to_le16(conn->handle); + cp.reason = reason; + hci_send_cmd(conn->hdev, OGF_LINK_CTL, OCF_DISCONNECT, + DISCONNECT_CP_SIZE, &cp); +} + +void hci_add_sco(struct hci_conn *conn, __u16 handle) +{ + struct hci_dev *hdev = conn->hdev; + add_sco_cp cp; + + BT_DBG("%p", conn); + + conn->state = BT_CONNECT; + conn->out = 1; + + cp.pkt_type = __cpu_to_le16(hdev->pkt_type & SCO_PTYPE_MASK); + cp.handle = __cpu_to_le16(handle); + + hci_send_cmd(hdev, OGF_LINK_CTL, OCF_ADD_SCO, ADD_SCO_CP_SIZE, &cp); +} + +static void hci_conn_timeout(unsigned long arg) +{ + struct hci_conn *conn = (void *)arg; + struct hci_dev *hdev = conn->hdev; + + BT_DBG("conn %p state %d", conn, conn->state); + + if (atomic_read(&conn->refcnt)) + return; + + hci_dev_lock(hdev); + if (conn->state == BT_CONNECTED) + hci_acl_disconn(conn, 0x13); + else + conn->state = BT_CLOSED; + hci_dev_unlock(hdev); + return; +} + +static void hci_conn_init_timer(struct hci_conn *conn) +{ + init_timer(&conn->timer); + conn->timer.function = hci_conn_timeout; + conn->timer.data = (unsigned long)conn; +} + +struct hci_conn *hci_conn_add(struct hci_dev *hdev, int type, bdaddr_t *dst) +{ + struct hci_conn *conn; + + BT_DBG("%s dst %s", hdev->name, batostr(dst)); + + if (!(conn = kmalloc(sizeof(struct hci_conn), GFP_ATOMIC))) + return NULL; + memset(conn, 0, sizeof(struct hci_conn)); + + bacpy(&conn->dst, dst); + conn->type = type; + conn->hdev = hdev; + conn->state = BT_OPEN; + + skb_queue_head_init(&conn->data_q); + hci_conn_init_timer(conn); + + atomic_set(&conn->refcnt, 0); + + hci_dev_hold(hdev); + + tasklet_disable(&hdev->tx_task); + conn_hash_add(hdev, conn); + tasklet_enable(&hdev->tx_task); + + return conn; +} + +int hci_conn_del(struct hci_conn *conn) +{ + struct hci_dev *hdev = conn->hdev; + + BT_DBG("%s conn %p handle %d", hdev->name, conn, conn->handle); + + hci_conn_del_timer(conn); + + if (conn->type == SCO_LINK) { + struct hci_conn *acl = conn->link; + if (acl) { + acl->link = NULL; + hci_conn_put(acl); + } + } else { + struct hci_conn *sco = conn->link; + if (sco) + sco->link = NULL; + + /* Unacked frames */ + hdev->acl_cnt += conn->sent; + } + + tasklet_disable(&hdev->tx_task); + conn_hash_del(hdev, conn); + tasklet_enable(&hdev->tx_task); + + skb_queue_purge(&conn->data_q); + + hci_dev_put(hdev); + + kfree(conn); + return 0; +} + +struct hci_dev *hci_get_route(bdaddr_t *dst, bdaddr_t *src) +{ + int use_src = bacmp(src, BDADDR_ANY); + struct hci_dev *hdev = NULL; + struct list_head *p; + + BT_DBG("%s -> %s", batostr(src), batostr(dst)); + + read_lock_bh(&hdev_list_lock); + + list_for_each(p, &hdev_list) { + struct hci_dev *d; + d = list_entry(p, struct hci_dev, list); + + if (!test_bit(HCI_UP, &d->flags)) + continue; + + /* Simple routing: + * No source address - find interface with bdaddr != dst + * Source address - find interface with bdaddr == src + */ + + if (use_src) { + if (!bacmp(&d->bdaddr, src)) { + hdev = d; break; + } + } else { + if (bacmp(&d->bdaddr, dst)) { + hdev = d; break; + } + } + } + + if (hdev) + hci_dev_hold(hdev); + + read_unlock_bh(&hdev_list_lock); + return hdev; +} + +/* Create SCO or ACL connection. + * Device _must_ be locked */ +struct hci_conn * hci_connect(struct hci_dev *hdev, int type, bdaddr_t *dst) +{ + struct hci_conn *acl; + + BT_DBG("%s dst %s", hdev->name, batostr(dst)); + + if (!(acl = conn_hash_lookup_ba(hdev, ACL_LINK, dst))) { + if (!(acl = hci_conn_add(hdev, ACL_LINK, dst))) + return NULL; + } + + hci_conn_hold(acl); + + if (acl->state == BT_OPEN || acl->state == BT_CLOSED) + hci_acl_connect(acl); + + if (type == SCO_LINK) { + struct hci_conn *sco; + + if (!(sco = conn_hash_lookup_ba(hdev, SCO_LINK, dst))) { + if (!(sco = hci_conn_add(hdev, SCO_LINK, dst))) { + hci_conn_put(acl); + return NULL; + } + } + acl->link = sco; + sco->link = acl; + + hci_conn_hold(sco); + + if (acl->state == BT_CONNECTED && + (sco->state == BT_OPEN || sco->state == BT_CLOSED)) + hci_add_sco(sco, acl->handle); + + return sco; + } else { + return acl; + } +} + +/* Authenticate remote device */ +int hci_conn_auth(struct hci_conn *conn) +{ + BT_DBG("conn %p", conn); + + if (conn->link_mode & HCI_LM_AUTH) + return 1; + + if (!test_and_set_bit(HCI_CONN_AUTH_PEND, &conn->pend)) { + auth_requested_cp ar; + ar.handle = __cpu_to_le16(conn->handle); + hci_send_cmd(conn->hdev, OGF_LINK_CTL, OCF_AUTH_REQUESTED, + AUTH_REQUESTED_CP_SIZE, &ar); + } + return 0; +} + +/* Enable encryption */ +int hci_conn_encrypt(struct hci_conn *conn) +{ + BT_DBG("conn %p", conn); + + if (conn->link_mode & HCI_LM_ENCRYPT) + return 1; + + if (test_and_set_bit(HCI_CONN_ENCRYPT_PEND, &conn->pend)) + return 0; + + if (hci_conn_auth(conn)) { + set_conn_encrypt_cp ce; + ce.handle = __cpu_to_le16(conn->handle); + ce.encrypt = 1; + hci_send_cmd(conn->hdev, OGF_LINK_CTL, OCF_SET_CONN_ENCRYPT, + SET_CONN_ENCRYPT_CP_SIZE, &ce); + } + return 0; +} + +/* Drop all connection on the device */ +void hci_conn_hash_flush(struct hci_dev *hdev) +{ + struct conn_hash *h = &hdev->conn_hash; + struct list_head *p; + + BT_DBG("hdev %s", hdev->name); + + p = h->list.next; + while (p != &h->list) { + struct hci_conn *c; + + c = list_entry(p, struct hci_conn, list); + p = p->next; + + c->state = BT_CLOSED; + + hci_proto_disconn_ind(c, 0x16); + hci_conn_del(c); + } +} + +int hci_get_conn_list(unsigned long arg) +{ + struct hci_conn_list_req req, *cl; + struct hci_conn_info *ci; + struct hci_dev *hdev; + struct list_head *p; + int n = 0, size; + + if (copy_from_user(&req, (void *) arg, sizeof(req))) + return -EFAULT; + + if (!(hdev = hci_dev_get(req.dev_id))) + return -ENODEV; + + size = req.conn_num * sizeof(struct hci_conn_info) + sizeof(req); + + if (verify_area(VERIFY_WRITE, (void *)arg, size)) + return -EFAULT; + + if (!(cl = (void *) kmalloc(size, GFP_KERNEL))) + return -ENOMEM; + ci = cl->conn_info; + + hci_dev_lock_bh(hdev); + list_for_each(p, &hdev->conn_hash.list) { + register struct hci_conn *c; + c = list_entry(p, struct hci_conn, list); + + bacpy(&(ci + n)->bdaddr, &c->dst); + (ci + n)->handle = c->handle; + (ci + n)->type = c->type; + (ci + n)->out = c->out; + (ci + n)->state = c->state; + (ci + n)->link_mode = c->link_mode; + n++; + } + hci_dev_unlock_bh(hdev); + + cl->dev_id = hdev->id; + cl->conn_num = n; + size = n * sizeof(struct hci_conn_info) + sizeof(req); + + hci_dev_put(hdev); + + copy_to_user((void *) arg, cl, size); + kfree(cl); + + return 0; +} + +int hci_get_conn_info(struct hci_dev *hdev, unsigned long arg) +{ + struct hci_conn_info_req req; + struct hci_conn_info ci; + struct hci_conn *conn; + char *ptr = (void *) arg + sizeof(req); + + if (copy_from_user(&req, (void *) arg, sizeof(req))) + return -EFAULT; + + if (verify_area(VERIFY_WRITE, ptr, sizeof(ci))) + return -EFAULT; + + hci_dev_lock_bh(hdev); + conn = conn_hash_lookup_ba(hdev, req.type, &req.bdaddr); + if (conn) { + bacpy(&ci.bdaddr, &conn->dst); + ci.handle = conn->handle; + ci.type = conn->type; + ci.out = conn->out; + ci.state = conn->state; + ci.link_mode = conn->link_mode; + } + hci_dev_unlock_bh(hdev); + + if (!conn) + return -ENOENT; + + copy_to_user(ptr, &ci, sizeof(ci)); + return 0; +} --- linux/net/bluetooth/hci_core.c~bluetooth-2.4.18-mh11 2001-11-09 23:21:21.000000000 +0100 +++ linux/net/bluetooth/hci_core.c 2004-01-25 23:37:39.000000000 +0100 @@ -25,11 +25,12 @@ /* * BlueZ HCI Core. * - * $Id: hci_core.c,v 1.22 2001/08/03 04:19:50 maxk Exp $ + * $Id: hci_core.c,v 1.14 2002/08/26 16:57:57 maxk Exp $ */ #include #include +#include #include #include @@ -50,12 +51,11 @@ #include #include -#include #include #ifndef HCI_CORE_DEBUG -#undef DBG -#define DBG( A... ) +#undef BT_DBG +#define BT_DBG( A... ) #endif static void hci_cmd_task(unsigned long arg); @@ -63,279 +63,69 @@ static void hci_tx_task(unsigned long arg); static void hci_notify(struct hci_dev *hdev, int event); -static rwlock_t hci_task_lock = RW_LOCK_UNLOCKED; +rwlock_t hci_task_lock = RW_LOCK_UNLOCKED; /* HCI device list */ -struct hci_dev *hdev_list[HCI_MAX_DEV]; -spinlock_t hdev_list_lock; -#define GET_HDEV(a) (hdev_list[a]) +LIST_HEAD(hdev_list); +rwlock_t hdev_list_lock = RW_LOCK_UNLOCKED; -/* HCI protocol list */ -struct hci_proto *hproto_list[HCI_MAX_PROTO]; -#define GET_HPROTO(a) (hproto_list[a]) +/* HCI protocols */ +#define HCI_MAX_PROTO 2 +struct hci_proto *hci_proto[HCI_MAX_PROTO]; /* HCI notifiers list */ -struct notifier_block *hci_dev_notifier; - -/* HCI device notifications */ -int hci_register_notifier(struct notifier_block *nb) -{ - int err, i; - struct hci_dev *hdev; - - if ((err = notifier_chain_register(&hci_dev_notifier, nb))) - return err; - - /* Notify about already registered devices */ - spin_lock(&hdev_list_lock); - for (i = 0; i < HCI_MAX_DEV; i++) { - if (!(hdev = GET_HDEV(i))) - continue; - if (hdev->flags & HCI_UP) - (*nb->notifier_call)(nb, HCI_DEV_UP, hdev); - } - spin_unlock(&hdev_list_lock); - - return 0; -} - -int hci_unregister_notifier(struct notifier_block *nb) -{ - return notifier_chain_unregister(&hci_dev_notifier, nb); -} - -static inline void hci_notify(struct hci_dev *hdev, int event) -{ - notifier_call_chain(&hci_dev_notifier, event, hdev); -} - -/* Get HCI device by index (device is locked on return)*/ -struct hci_dev *hci_dev_get(int index) -{ - struct hci_dev *hdev; - DBG("%d", index); - - if (index < 0 || index >= HCI_MAX_DEV) - return NULL; - - spin_lock(&hdev_list_lock); - if ((hdev = GET_HDEV(index))) - hci_dev_hold(hdev); - spin_unlock(&hdev_list_lock); - - return hdev; -} - -/* Flush inquiry cache */ -void inquiry_cache_flush(struct inquiry_cache *cache) -{ - struct inquiry_entry *next = cache->list, *e; - - DBG("cache %p", cache); - - cache->list = NULL; - while ((e = next)) { - next = e->next; - kfree(e); - } -} - -/* Lookup by bdaddr. - * Cache must be locked. */ -static struct inquiry_entry * __inquiry_cache_lookup(struct inquiry_cache *cache, bdaddr_t *bdaddr) -{ - struct inquiry_entry *e; - - DBG("cache %p, %s", cache, batostr(bdaddr)); - - for (e = cache->list; e; e = e->next) - if (!bacmp(&e->info.bdaddr, bdaddr)) - break; - - return e; -} - -static void inquiry_cache_update(struct inquiry_cache *cache, inquiry_info *info) -{ - struct inquiry_entry *e; - - DBG("cache %p, %s", cache, batostr(&info->bdaddr)); - - inquiry_cache_lock(cache); - - if (!(e = __inquiry_cache_lookup(cache, &info->bdaddr))) { - /* Entry not in the cache. Add new one. */ - if (!(e = kmalloc(sizeof(struct inquiry_entry), GFP_ATOMIC))) - goto unlock; - memset(e, 0, sizeof(struct inquiry_entry)); - e->next = cache->list; - cache->list = e; - } - - memcpy(&e->info, info, sizeof(inquiry_info)); - e->timestamp = jiffies; - cache->timestamp = jiffies; -unlock: - inquiry_cache_unlock(cache); -} - -static int inquiry_cache_dump(struct inquiry_cache *cache, int num, __u8 *buf) -{ - inquiry_info *info = (inquiry_info *) buf; - struct inquiry_entry *e; - int copied = 0; - - inquiry_cache_lock(cache); - - for (e = cache->list; e && copied < num; e = e->next, copied++) - memcpy(info++, &e->info, sizeof(inquiry_info)); +static struct notifier_block *hci_notifier; - inquiry_cache_unlock(cache); - DBG("cache %p, copied %d", cache, copied); - return copied; -} +/* ---- HCI notifications ---- */ -/* --------- BaseBand connections --------- */ -static struct hci_conn *hci_conn_add(struct hci_dev *hdev, __u16 handle, __u8 type, bdaddr_t *dst) +int hci_register_notifier(struct notifier_block *nb) { - struct hci_conn *conn; - - DBG("%s handle %d dst %s", hdev->name, handle, batostr(dst)); - - if ( conn_hash_lookup(&hdev->conn_hash, handle)) { - ERR("%s handle 0x%x already exists", hdev->name, handle); - return NULL; - } - - if (!(conn = kmalloc(sizeof(struct hci_conn), GFP_ATOMIC))) - return NULL; - memset(conn, 0, sizeof(struct hci_conn)); - - bacpy(&conn->dst, dst); - conn->handle = handle; - conn->type = type; - conn->hdev = hdev; - - skb_queue_head_init(&conn->data_q); - - hci_dev_hold(hdev); - conn_hash_add(&hdev->conn_hash, handle, conn); - - return conn; + return notifier_chain_register(&hci_notifier, nb); } -static int hci_conn_del(struct hci_dev *hdev, struct hci_conn *conn) +int hci_unregister_notifier(struct notifier_block *nb) { - DBG("%s conn %p handle %d", hdev->name, conn, conn->handle); - - conn_hash_del(&hdev->conn_hash, conn); - hci_dev_put(hdev); - - /* Unacked frames */ - hdev->acl_cnt += conn->sent; - - skb_queue_purge(&conn->data_q); - - kfree(conn); - return 0; + return notifier_chain_unregister(&hci_notifier, nb); } -/* Drop all connection on the device */ -static void hci_conn_hash_flush(struct hci_dev *hdev) +void hci_notify(struct hci_dev *hdev, int event) { - struct conn_hash *h = &hdev->conn_hash; - struct hci_proto *hp; - struct list_head *p; - - DBG("hdev %s", hdev->name); - - p = h->list.next; - while (p != &h->list) { - struct hci_conn *c; - - c = list_entry(p, struct hci_conn, list); - p = p->next; - - if (c->type == ACL_LINK) { - /* ACL link notify L2CAP layer */ - if ((hp = GET_HPROTO(HCI_PROTO_L2CAP)) && hp->disconn_ind) - hp->disconn_ind(c, 0x16); - } else { - /* SCO link (no notification) */ - } - - hci_conn_del(hdev, c); - } + notifier_call_chain(&hci_notifier, event, hdev); } -int hci_connect(struct hci_dev *hdev, bdaddr_t *bdaddr) -{ - struct inquiry_cache *cache = &hdev->inq_cache; - struct inquiry_entry *e; - create_conn_cp cc; - __u16 clock_offset; - - DBG("%s bdaddr %s", hdev->name, batostr(bdaddr)); - - if (!(hdev->flags & HCI_UP)) - return -ENODEV; - - inquiry_cache_lock_bh(cache); - - if (!(e = __inquiry_cache_lookup(cache, bdaddr)) || inquiry_entry_age(e) > INQUIRY_ENTRY_AGE_MAX) { - cc.pscan_rep_mode = 0; - cc.pscan_mode = 0; - clock_offset = 0; - } else { - cc.pscan_rep_mode = e->info.pscan_rep_mode; - cc.pscan_mode = e->info.pscan_mode; - clock_offset = __le16_to_cpu(e->info.clock_offset) & 0x8000; - } - - inquiry_cache_unlock_bh(cache); - - bacpy(&cc.bdaddr, bdaddr); - cc.pkt_type = __cpu_to_le16(hdev->pkt_type); - cc.clock_offset = __cpu_to_le16(clock_offset); - - if (lmp_rswitch_capable(hdev)) - cc.role_switch = 0x01; - else - cc.role_switch = 0x00; - - hci_send_cmd(hdev, OGF_LINK_CTL, OCF_CREATE_CONN, CREATE_CONN_CP_SIZE, &cc); +/* ---- HCI hotplug support ---- */ - return 0; -} +#ifdef CONFIG_HOTPLUG -int hci_disconnect(struct hci_conn *conn, __u8 reason) +static int hci_run_hotplug(char *dev, char *action) { - disconnect_cp dc; - - DBG("conn %p handle %d", conn, conn->handle); + char *argv[3], *envp[5], dstr[20], astr[32]; - dc.handle = __cpu_to_le16(conn->handle); - dc.reason = reason; - hci_send_cmd(conn->hdev, OGF_LINK_CTL, OCF_DISCONNECT, DISCONNECT_CP_SIZE, &dc); + sprintf(dstr, "DEVICE=%s", dev); + sprintf(astr, "ACTION=%s", action); - return 0; -} + argv[0] = hotplug_path; + argv[1] = "bluetooth"; + argv[2] = NULL; -/* --------- HCI request handling ------------ */ -static inline void hci_req_lock(struct hci_dev *hdev) -{ - down(&hdev->req_lock); + envp[0] = "HOME=/"; + envp[1] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin"; + envp[2] = dstr; + envp[3] = astr; + envp[4] = NULL; + + return call_usermodehelper(argv[0], argv, envp); } +#else +#define hci_run_hotplug(A...) +#endif -static inline void hci_req_unlock(struct hci_dev *hdev) -{ - up(&hdev->req_lock); -} +/* ---- HCI requests ---- */ -static inline void hci_req_complete(struct hci_dev *hdev, int result) +void hci_req_complete(struct hci_dev *hdev, int result) { - DBG("%s result 0x%2.2x", hdev->name, result); + BT_DBG("%s result 0x%2.2x", hdev->name, result); if (hdev->req_status == HCI_REQ_PEND) { hdev->req_result = result; @@ -344,9 +134,9 @@ } } -static inline void hci_req_cancel(struct hci_dev *hdev, int err) +void hci_req_cancel(struct hci_dev *hdev, int err) { - DBG("%s err 0x%2.2x", hdev->name, err); + BT_DBG("%s err 0x%2.2x", hdev->name, err); if (hdev->req_status == HCI_REQ_PEND) { hdev->req_result = err; @@ -356,23 +146,22 @@ } /* Execute request and wait for completion. */ -static int __hci_request(struct hci_dev *hdev, void (*req)(struct hci_dev *hdev, unsigned long opt), - unsigned long opt, __u32 timeout) +static int __hci_request(struct hci_dev *hdev, void (*req)(struct hci_dev *hdev, unsigned long opt), unsigned long opt, __u32 timeout) { DECLARE_WAITQUEUE(wait, current); int err = 0; - DBG("%s start", hdev->name); + BT_DBG("%s start", hdev->name); hdev->req_status = HCI_REQ_PEND; add_wait_queue(&hdev->req_wait_q, &wait); - current->state = TASK_INTERRUPTIBLE; + set_current_state(TASK_INTERRUPTIBLE); req(hdev, opt); schedule_timeout(timeout); - current->state = TASK_RUNNING; + set_current_state(TASK_RUNNING); remove_wait_queue(&hdev->req_wait_q, &wait); if (signal_pending(current)) @@ -394,7 +183,7 @@ hdev->req_status = hdev->req_result = 0; - DBG("%s end: err %d", hdev->name, err); + BT_DBG("%s end: err %d", hdev->name, err); return err; } @@ -412,10 +201,9 @@ return ret; } -/* --------- HCI requests ---------- */ static void hci_reset_req(struct hci_dev *hdev, unsigned long opt) { - DBG("%s %ld", hdev->name, opt); + BT_DBG("%s %ld", hdev->name, opt); /* Reset device */ hci_send_cmd(hdev, OGF_HOST_CTL, OCF_RESET, 0, NULL); @@ -423,10 +211,10 @@ static void hci_init_req(struct hci_dev *hdev, unsigned long opt) { - set_event_flt_cp ec; + set_event_flt_cp ef; __u16 param; - DBG("%s %ld", hdev->name, opt); + BT_DBG("%s %ld", hdev->name, opt); /* Mandatory initialization */ @@ -436,14 +224,27 @@ /* Read Buffer Size (ACL mtu, max pkt, etc.) */ hci_send_cmd(hdev, OGF_INFO_PARAM, OCF_READ_BUFFER_SIZE, 0, NULL); +#if 0 + /* Host buffer size */ + { + host_buffer_size_cp bs; + bs.acl_mtu = __cpu_to_le16(HCI_MAX_ACL_SIZE); + bs.sco_mtu = HCI_MAX_SCO_SIZE; + bs.acl_max_pkt = __cpu_to_le16(0xffff); + bs.sco_max_pkt = __cpu_to_le16(0xffff); + hci_send_cmd(hdev, OGF_HOST_CTL, OCF_HOST_BUFFER_SIZE, + HOST_BUFFER_SIZE_CP_SIZE, &bs); + } +#endif + /* Read BD Address */ hci_send_cmd(hdev, OGF_INFO_PARAM, OCF_READ_BD_ADDR, 0, NULL); /* Optional initialization */ /* Clear Event Filters */ - ec.flt_type = FLT_CLEAR_ALL; - hci_send_cmd(hdev, OGF_HOST_CTL, OCF_SET_EVENT_FLT, 1, &ec); + ef.flt_type = FLT_CLEAR_ALL; + hci_send_cmd(hdev, OGF_HOST_CTL, OCF_SET_EVENT_FLT, 1, &ef); /* Page timeout ~20 secs */ param = __cpu_to_le16(0x8000); @@ -458,7 +259,7 @@ { __u8 scan = opt; - DBG("%s %x", hdev->name, scan); + BT_DBG("%s %x", hdev->name, scan); /* Inquiry and Page scans */ hci_send_cmd(hdev, OGF_HOST_CTL, OCF_WRITE_SCAN_ENABLE, 1, &scan); @@ -468,27 +269,190 @@ { __u8 auth = opt; - DBG("%s %x", hdev->name, auth); + BT_DBG("%s %x", hdev->name, auth); /* Authentication */ hci_send_cmd(hdev, OGF_HOST_CTL, OCF_WRITE_AUTH_ENABLE, 1, &auth); } +static void hci_encrypt_req(struct hci_dev *hdev, unsigned long opt) +{ + __u8 encrypt = opt; + + BT_DBG("%s %x", hdev->name, encrypt); + + /* Authentication */ + hci_send_cmd(hdev, OGF_HOST_CTL, OCF_WRITE_ENCRYPT_MODE, 1, &encrypt); +} + +/* Get HCI device by index. + * Device is locked on return. */ +struct hci_dev *hci_dev_get(int index) +{ + struct hci_dev *hdev; + struct list_head *p; + + BT_DBG("%d", index); + + if (index < 0) + return NULL; + + read_lock(&hdev_list_lock); + list_for_each(p, &hdev_list) { + hdev = list_entry(p, struct hci_dev, list); + if (hdev->id == index) { + hci_dev_hold(hdev); + goto done; + } + } + hdev = NULL; +done: + read_unlock(&hdev_list_lock); + return hdev; +} + +/* ---- Inquiry support ---- */ +void inquiry_cache_flush(struct hci_dev *hdev) +{ + struct inquiry_cache *cache = &hdev->inq_cache; + struct inquiry_entry *next = cache->list, *e; + + BT_DBG("cache %p", cache); + + cache->list = NULL; + while ((e = next)) { + next = e->next; + kfree(e); + } +} + +struct inquiry_entry *inquiry_cache_lookup(struct hci_dev *hdev, bdaddr_t *bdaddr) +{ + struct inquiry_cache *cache = &hdev->inq_cache; + struct inquiry_entry *e; + + BT_DBG("cache %p, %s", cache, batostr(bdaddr)); + + for (e = cache->list; e; e = e->next) + if (!bacmp(&e->info.bdaddr, bdaddr)) + break; + return e; +} + +void inquiry_cache_update(struct hci_dev *hdev, inquiry_info *info) +{ + struct inquiry_cache *cache = &hdev->inq_cache; + struct inquiry_entry *e; + + BT_DBG("cache %p, %s", cache, batostr(&info->bdaddr)); + + if (!(e = inquiry_cache_lookup(hdev, &info->bdaddr))) { + /* Entry not in the cache. Add new one. */ + if (!(e = kmalloc(sizeof(struct inquiry_entry), GFP_ATOMIC))) + return; + memset(e, 0, sizeof(struct inquiry_entry)); + e->next = cache->list; + cache->list = e; + } + + memcpy(&e->info, info, sizeof(inquiry_info)); + e->timestamp = jiffies; + cache->timestamp = jiffies; +} + +int inquiry_cache_dump(struct hci_dev *hdev, int num, __u8 *buf) +{ + struct inquiry_cache *cache = &hdev->inq_cache; + inquiry_info *info = (inquiry_info *) buf; + struct inquiry_entry *e; + int copied = 0; + + for (e = cache->list; e && copied < num; e = e->next, copied++) + memcpy(info++, &e->info, sizeof(inquiry_info)); + + BT_DBG("cache %p, copied %d", cache, copied); + return copied; +} + static void hci_inq_req(struct hci_dev *hdev, unsigned long opt) { struct hci_inquiry_req *ir = (struct hci_inquiry_req *) opt; inquiry_cp ic; - DBG("%s", hdev->name); + BT_DBG("%s", hdev->name); + + if (test_bit(HCI_INQUIRY, &hdev->flags)) + return; /* Start Inquiry */ memcpy(&ic.lap, &ir->lap, 3); - ic.lenght = ir->length; + ic.length = ir->length; ic.num_rsp = ir->num_rsp; hci_send_cmd(hdev, OGF_LINK_CTL, OCF_INQUIRY, INQUIRY_CP_SIZE, &ic); } -/* HCI ioctl helpers */ +int hci_inquiry(unsigned long arg) +{ + struct hci_inquiry_req ir; + struct hci_dev *hdev; + int err = 0, do_inquiry = 0, max_rsp; + long timeo; + __u8 *buf, *ptr; + + ptr = (void *) arg; + if (copy_from_user(&ir, ptr, sizeof(ir))) + return -EFAULT; + + if (!(hdev = hci_dev_get(ir.dev_id))) + return -ENODEV; + + hci_dev_lock_bh(hdev); + if (inquiry_cache_age(hdev) > INQUIRY_CACHE_AGE_MAX || + inquiry_cache_empty(hdev) || + ir.flags & IREQ_CACHE_FLUSH) { + inquiry_cache_flush(hdev); + do_inquiry = 1; + } + hci_dev_unlock_bh(hdev); + + timeo = ir.length * 2 * HZ; + if (do_inquiry && (err = hci_request(hdev, hci_inq_req, (unsigned long)&ir, timeo)) < 0) + goto done; + + /* for unlimited number of responses we will use buffer with 255 entries */ + max_rsp = (ir.num_rsp == 0) ? 255 : ir.num_rsp; + + /* cache_dump can't sleep. Therefore we allocate temp buffer and then + * copy it to the user space. + */ + if (!(buf = kmalloc(sizeof(inquiry_info) * max_rsp, GFP_KERNEL))) { + err = -ENOMEM; + goto done; + } + + hci_dev_lock_bh(hdev); + ir.num_rsp = inquiry_cache_dump(hdev, max_rsp, buf); + hci_dev_unlock_bh(hdev); + + BT_DBG("num_rsp %d", ir.num_rsp); + + if (!verify_area(VERIFY_WRITE, ptr, sizeof(ir) + + (sizeof(inquiry_info) * ir.num_rsp))) { + copy_to_user(ptr, &ir, sizeof(ir)); + ptr += sizeof(ir); + copy_to_user(ptr, buf, sizeof(inquiry_info) * ir.num_rsp); + } else + err = -EFAULT; + + kfree(buf); + +done: + hci_dev_put(hdev); + return err; +} + +/* ---- HCI ioctl helpers ---- */ + int hci_dev_open(__u16 dev) { struct hci_dev *hdev; @@ -497,11 +461,11 @@ if (!(hdev = hci_dev_get(dev))) return -ENODEV; - DBG("%s %p", hdev->name, hdev); + BT_DBG("%s %p", hdev->name, hdev); hci_req_lock(hdev); - if (hdev->flags & HCI_UP) { + if (test_bit(HCI_UP, &hdev->flags)) { ret = -EALREADY; goto done; } @@ -511,18 +475,18 @@ goto done; } - if (hdev->flags & HCI_NORMAL) { + if (!test_bit(HCI_RAW, &hdev->flags)) { atomic_set(&hdev->cmd_cnt, 1); - hdev->flags |= HCI_INIT; + set_bit(HCI_INIT, &hdev->flags); //__hci_request(hdev, hci_reset_req, 0, HZ); ret = __hci_request(hdev, hci_init_req, 0, HCI_INIT_TIMEOUT); - hdev->flags &= ~HCI_INIT; + clear_bit(HCI_INIT, &hdev->flags); } if (!ret) { - hdev->flags |= HCI_UP; + set_bit(HCI_UP, &hdev->flags); hci_notify(hdev, HCI_DEV_UP); } else { /* Init failed, cleanup */ @@ -542,42 +506,36 @@ } hdev->close(hdev); + hdev->flags = 0; } done: hci_req_unlock(hdev); hci_dev_put(hdev); - return ret; } -int hci_dev_close(__u16 dev) +static int hci_dev_do_close(struct hci_dev *hdev) { - struct hci_dev *hdev; - - if (!(hdev = hci_dev_get(dev))) - return -ENODEV; - - DBG("%s %p", hdev->name, hdev); + BT_DBG("%s %p", hdev->name, hdev); hci_req_cancel(hdev, ENODEV); hci_req_lock(hdev); - if (!(hdev->flags & HCI_UP)) - goto done; + if (!test_and_clear_bit(HCI_UP, &hdev->flags)) { + hci_req_unlock(hdev); + return 0; + } /* Kill RX and TX tasks */ tasklet_kill(&hdev->rx_task); tasklet_kill(&hdev->tx_task); - inquiry_cache_flush(&hdev->inq_cache); - + hci_dev_lock_bh(hdev); + inquiry_cache_flush(hdev); hci_conn_hash_flush(hdev); - - /* Clear flags */ - hdev->flags &= HCI_SOCK; - hdev->flags |= HCI_NORMAL; - + hci_dev_unlock_bh(hdev); + hci_notify(hdev, HCI_DEV_DOWN); if (hdev->flush) @@ -586,9 +544,9 @@ /* Reset device */ skb_queue_purge(&hdev->cmd_q); atomic_set(&hdev->cmd_cnt, 1); - hdev->flags |= HCI_INIT; - __hci_request(hdev, hci_reset_req, 0, HZ); - hdev->flags &= ~HCI_INIT; + set_bit(HCI_INIT, &hdev->flags); + __hci_request(hdev, hci_reset_req, 0, HZ/4); + clear_bit(HCI_INIT, &hdev->flags); /* Kill cmd task */ tasklet_kill(&hdev->cmd_task); @@ -605,17 +563,28 @@ } /* After this point our queues are empty - * and no tasks are scheduled. - */ + * and no tasks are scheduled. */ hdev->close(hdev); -done: - hci_req_unlock(hdev); - hci_dev_put(hdev); + /* Clear flags */ + hdev->flags = 0; + hci_req_unlock(hdev); return 0; } +int hci_dev_close(__u16 dev) +{ + struct hci_dev *hdev; + int err; + + if (!(hdev = hci_dev_get(dev))) + return -ENODEV; + err = hci_dev_do_close(hdev); + hci_dev_put(hdev); + return err; +} + int hci_dev_reset(__u16 dev) { struct hci_dev *hdev; @@ -627,16 +596,17 @@ hci_req_lock(hdev); tasklet_disable(&hdev->tx_task); - if (!(hdev->flags & HCI_UP)) + if (!test_bit(HCI_UP, &hdev->flags)) goto done; /* Drop queues */ skb_queue_purge(&hdev->rx_q); skb_queue_purge(&hdev->cmd_q); - inquiry_cache_flush(&hdev->inq_cache); - + hci_dev_lock_bh(hdev); + inquiry_cache_flush(hdev); hci_conn_hash_flush(hdev); + hci_dev_unlock_bh(hdev); if (hdev->flush) hdev->flush(hdev); @@ -650,7 +620,6 @@ tasklet_enable(&hdev->tx_task); hci_req_unlock(hdev); hci_dev_put(hdev); - return ret; } @@ -669,30 +638,11 @@ return ret; } -int hci_dev_setauth(unsigned long arg) -{ - struct hci_dev *hdev; - struct hci_dev_req dr; - int ret = 0; - - if (copy_from_user(&dr, (void *) arg, sizeof(dr))) - return -EFAULT; - - if (!(hdev = hci_dev_get(dr.dev_id))) - return -ENODEV; - - ret = hci_request(hdev, hci_auth_req, dr.dev_opt, HCI_INIT_TIMEOUT); - - hci_dev_put(hdev); - - return ret; -} - -int hci_dev_setscan(unsigned long arg) +int hci_dev_cmd(unsigned int cmd, unsigned long arg) { struct hci_dev *hdev; struct hci_dev_req dr; - int ret = 0; + int err = 0; if (copy_from_user(&dr, (void *) arg, sizeof(dr))) return -EFAULT; @@ -700,48 +650,78 @@ if (!(hdev = hci_dev_get(dr.dev_id))) return -ENODEV; - ret = hci_request(hdev, hci_scan_req, dr.dev_opt, HCI_INIT_TIMEOUT); - - hci_dev_put(hdev); + switch (cmd) { + case HCISETAUTH: + err = hci_request(hdev, hci_auth_req, dr.dev_opt, HCI_INIT_TIMEOUT); + break; - return ret; -} + case HCISETENCRYPT: + if (!lmp_encrypt_capable(hdev)) { + err = -EOPNOTSUPP; + break; + } -int hci_dev_setptype(unsigned long arg) -{ - struct hci_dev *hdev; - struct hci_dev_req dr; - int ret = 0; + if (!test_bit(HCI_AUTH, &hdev->flags)) { + /* Auth must be enabled first */ + err = hci_request(hdev, hci_auth_req, + dr.dev_opt, HCI_INIT_TIMEOUT); + if (err) + break; + } + + err = hci_request(hdev, hci_encrypt_req, + dr.dev_opt, HCI_INIT_TIMEOUT); + break; + + case HCISETSCAN: + err = hci_request(hdev, hci_scan_req, dr.dev_opt, HCI_INIT_TIMEOUT); + break; + + case HCISETPTYPE: + hdev->pkt_type = (__u16) dr.dev_opt; + break; + + case HCISETLINKPOL: + hdev->link_policy = (__u16) dr.dev_opt; + break; - if (copy_from_user(&dr, (void *) arg, sizeof(dr))) - return -EFAULT; + case HCISETLINKMODE: + hdev->link_mode = ((__u16) dr.dev_opt) & (HCI_LM_MASTER | HCI_LM_ACCEPT); + break; - if (!(hdev = hci_dev_get(dr.dev_id))) - return -ENODEV; + case HCISETACLMTU: + hdev->acl_mtu = *((__u16 *)&dr.dev_opt + 1); + hdev->acl_pkts = *((__u16 *)&dr.dev_opt + 0); + break; - hdev->pkt_type = (__u16) dr.dev_opt; + case HCISETSCOMTU: + hdev->sco_mtu = *((__u16 *)&dr.dev_opt + 1); + hdev->sco_pkts = *((__u16 *)&dr.dev_opt + 0); + break; + default: + err = -EINVAL; + break; + } hci_dev_put(hdev); - - return ret; + return err; } -int hci_dev_list(unsigned long arg) +int hci_get_dev_list(unsigned long arg) { struct hci_dev_list_req *dl; struct hci_dev_req *dr; - struct hci_dev *hdev; - int i, n, size; + struct list_head *p; + int n = 0, size; __u16 dev_num; if (get_user(dev_num, (__u16 *) arg)) return -EFAULT; - /* Avoid long loop, overflow */ - if (dev_num > 2048) + if (!dev_num) return -EINVAL; - size = dev_num * sizeof(struct hci_dev_req) + sizeof(__u16); + size = dev_num * sizeof(*dr) + sizeof(*dl); if (verify_area(VERIFY_WRITE, (void *) arg, size)) return -EFAULT; @@ -750,25 +730,27 @@ return -ENOMEM; dr = dl->dev_req; - spin_lock_bh(&hdev_list_lock); - for (i = 0, n = 0; i < HCI_MAX_DEV && n < dev_num; i++) { - if ((hdev = hdev_list[i])) { - (dr + n)->dev_id = hdev->id; - (dr + n)->dev_opt = hdev->flags; - n++; - } + read_lock_bh(&hdev_list_lock); + list_for_each(p, &hdev_list) { + struct hci_dev *hdev; + hdev = list_entry(p, struct hci_dev, list); + (dr + n)->dev_id = hdev->id; + (dr + n)->dev_opt = hdev->flags; + if (++n >= dev_num) + break; } - spin_unlock_bh(&hdev_list_lock); + read_unlock_bh(&hdev_list_lock); dl->dev_num = n; - size = n * sizeof(struct hci_dev_req) + sizeof(__u16); + size = n * sizeof(*dr) + sizeof(*dl); copy_to_user((void *) arg, dl, size); + kfree(dl); return 0; } -int hci_dev_info(unsigned long arg) +int hci_get_dev_info(unsigned long arg) { struct hci_dev *hdev; struct hci_dev_info di; @@ -786,9 +768,11 @@ di.flags = hdev->flags; di.pkt_type = hdev->pkt_type; di.acl_mtu = hdev->acl_mtu; - di.acl_max = hdev->acl_max; + di.acl_pkts = hdev->acl_pkts; di.sco_mtu = hdev->sco_mtu; - di.sco_max = hdev->sco_max; + di.sco_pkts = hdev->sco_pkts; + di.link_policy = hdev->link_policy; + di.link_mode = hdev->link_mode; memcpy(&di.stat, &hdev->stat, sizeof(di.stat)); memcpy(&di.features, &hdev->features, sizeof(di.features)); @@ -801,258 +785,168 @@ return err; } -__u32 hci_dev_setmode(struct hci_dev *hdev, __u32 mode) -{ - __u32 omode = hdev->flags & HCI_MODE_MASK; - - hdev->flags &= ~HCI_MODE_MASK; - hdev->flags |= (mode & HCI_MODE_MASK); - return omode; -} - -__u32 hci_dev_getmode(struct hci_dev *hdev) -{ - return hdev->flags & HCI_MODE_MASK; -} +/* ---- Interface to HCI drivers ---- */ -int hci_conn_list(unsigned long arg) +/* Register HCI device */ +int hci_register_dev(struct hci_dev *hdev) { - struct hci_conn_list_req req, *cl; - struct hci_conn_info *ci; - struct hci_dev *hdev; - struct list_head *p; - int n = 0, size; - - if (copy_from_user(&req, (void *) arg, sizeof(req))) - return -EFAULT; + struct list_head *head = &hdev_list, *p; + int id = 0; - if (!(hdev = hci_dev_get(req.dev_id))) - return -ENODEV; + BT_DBG("%p name %s type %d", hdev, hdev->name, hdev->type); - /* Set a limit to avoid overlong loops, and also numeric overflow - AC */ - if(req.conn_num < 2048) + if (!hdev->open || !hdev->close || !hdev->destruct) return -EINVAL; - - size = req.conn_num * sizeof(struct hci_conn_info) + sizeof(req); - if (!(cl = kmalloc(size, GFP_KERNEL))) - return -ENOMEM; - ci = cl->conn_info; - - local_bh_disable(); - conn_hash_lock(&hdev->conn_hash); - list_for_each(p, &hdev->conn_hash.list) { - register struct hci_conn *c; - c = list_entry(p, struct hci_conn, list); + write_lock_bh(&hdev_list_lock); - (ci + n)->handle = c->handle; - bacpy(&(ci + n)->bdaddr, &c->dst); - n++; + /* Find first available device id */ + list_for_each(p, &hdev_list) { + if (list_entry(p, struct hci_dev, list)->id != id) + break; + head = p; id++; } - conn_hash_unlock(&hdev->conn_hash); - local_bh_enable(); - - cl->dev_id = hdev->id; - cl->conn_num = n; - size = n * sizeof(struct hci_conn_info) + sizeof(req); - - hci_dev_put(hdev); - - if(copy_to_user((void *) arg, cl, size)) - return -EFAULT; - return 0; -} - -int hci_inquiry(unsigned long arg) -{ - struct inquiry_cache *cache; - struct hci_inquiry_req ir; - struct hci_dev *hdev; - int err = 0, do_inquiry = 0; - long timeo; - __u8 *buf, *ptr; - - ptr = (void *) arg; - if (copy_from_user(&ir, ptr, sizeof(ir))) - return -EFAULT; + + sprintf(hdev->name, "hci%d", id); + hdev->id = id; + list_add(&hdev->list, head); - if (!(hdev = hci_dev_get(ir.dev_id))) - return -ENODEV; + atomic_set(&hdev->refcnt, 1); + spin_lock_init(&hdev->lock); + + hdev->flags = 0; + hdev->pkt_type = (HCI_DM1 | HCI_DH1 | HCI_HV1); + hdev->link_mode = (HCI_LM_ACCEPT); - cache = &hdev->inq_cache; + tasklet_init(&hdev->cmd_task, hci_cmd_task,(unsigned long) hdev); + tasklet_init(&hdev->rx_task, hci_rx_task, (unsigned long) hdev); + tasklet_init(&hdev->tx_task, hci_tx_task, (unsigned long) hdev); - inquiry_cache_lock(cache); - if (inquiry_cache_age(cache) > INQUIRY_CACHE_AGE_MAX || ir.flags & IREQ_CACHE_FLUSH) { - inquiry_cache_flush(cache); - do_inquiry = 1; - } - inquiry_cache_unlock(cache); + skb_queue_head_init(&hdev->rx_q); + skb_queue_head_init(&hdev->cmd_q); + skb_queue_head_init(&hdev->raw_q); - /* Limit inquiry time, also avoid overflows */ + init_waitqueue_head(&hdev->req_wait_q); + init_MUTEX(&hdev->req_lock); - if(ir.length > 2048 || ir.num_rsp > 2048) - { - err = -EINVAL; - goto done; - } + inquiry_cache_init(hdev); - timeo = ir.length * 2 * HZ; - if (do_inquiry && (err = hci_request(hdev, hci_inq_req, (unsigned long)&ir, timeo)) < 0) - goto done; + conn_hash_init(hdev); - /* cache_dump can't sleep. Therefore we allocate temp buffer and then - * copy it to the user space. - */ - if (!(buf = kmalloc(sizeof(inquiry_info) * ir.num_rsp, GFP_KERNEL))) { - err = -ENOMEM; - goto done; - } - ir.num_rsp = inquiry_cache_dump(cache, ir.num_rsp, buf); + memset(&hdev->stat, 0, sizeof(struct hci_dev_stats)); - DBG("num_rsp %d", ir.num_rsp); + atomic_set(&hdev->promisc, 0); - if (!verify_area(VERIFY_WRITE, ptr, sizeof(ir) + (sizeof(inquiry_info) * ir.num_rsp))) { - copy_to_user(ptr, &ir, sizeof(ir)); - ptr += sizeof(ir); - copy_to_user(ptr, buf, sizeof(inquiry_info) * ir.num_rsp); - } else - err = -EFAULT; + MOD_INC_USE_COUNT; - kfree(buf); + write_unlock_bh(&hdev_list_lock); -done: - hci_dev_put(hdev); + hci_notify(hdev, HCI_DEV_REG); + hci_run_hotplug(hdev->name, "register"); - return err; + return id; } -/* Interface to HCI drivers */ - -/* Register HCI device */ -int hci_register_dev(struct hci_dev *hdev) +/* Unregister HCI device */ +int hci_unregister_dev(struct hci_dev *hdev) { - int i; - - DBG("%p name %s type %d", hdev, hdev->name, hdev->type); - - /* Find free slot */ - spin_lock_bh(&hdev_list_lock); - for (i = 0; i < HCI_MAX_DEV; i++) { - if (!hdev_list[i]) { - hdev_list[i] = hdev; - - sprintf(hdev->name, "hci%d", i); - atomic_set(&hdev->refcnt, 0); - hdev->id = i; - hdev->flags = HCI_NORMAL; - - hdev->pkt_type = (HCI_DM1 | HCI_DH1); - - tasklet_init(&hdev->cmd_task, hci_cmd_task, (unsigned long) hdev); - tasklet_init(&hdev->rx_task, hci_rx_task, (unsigned long) hdev); - tasklet_init(&hdev->tx_task, hci_tx_task, (unsigned long) hdev); - - skb_queue_head_init(&hdev->rx_q); - skb_queue_head_init(&hdev->cmd_q); - skb_queue_head_init(&hdev->raw_q); - - init_waitqueue_head(&hdev->req_wait_q); - init_MUTEX(&hdev->req_lock); - - inquiry_cache_init(&hdev->inq_cache); + BT_DBG("%p name %s type %d", hdev, hdev->name, hdev->type); - conn_hash_init(&hdev->conn_hash); + write_lock_bh(&hdev_list_lock); + list_del(&hdev->list); + write_unlock_bh(&hdev_list_lock); - memset(&hdev->stat, 0, sizeof(struct hci_dev_stats)); + hci_dev_do_close(hdev); - hci_notify(hdev, HCI_DEV_REG); + hci_notify(hdev, HCI_DEV_UNREG); + hci_run_hotplug(hdev->name, "unregister"); - MOD_INC_USE_COUNT; - break; - } - } - spin_unlock_bh(&hdev_list_lock); + hci_dev_put(hdev); - return (i == HCI_MAX_DEV) ? -1 : i; + MOD_DEC_USE_COUNT; + return 0; } -/* Unregister HCI device */ -int hci_unregister_dev(struct hci_dev *hdev) +/* Suspend HCI device */ +int hci_suspend_dev(struct hci_dev *hdev) { - int i; + hci_notify(hdev, HCI_DEV_SUSPEND); + hci_run_hotplug(hdev->name, "suspend"); + return 0; +} - DBG("%p name %s type %d", hdev, hdev->name, hdev->type); +/* Resume HCI device */ +int hci_resume_dev(struct hci_dev *hdev) +{ + hci_notify(hdev, HCI_DEV_RESUME); + hci_run_hotplug(hdev->name, "resume"); + return 0; +} - if (hdev->flags & HCI_UP) - hci_dev_close(hdev->id); +/* Receive frame from HCI drivers */ +int hci_recv_frame(struct sk_buff *skb) +{ + struct hci_dev *hdev = (struct hci_dev *) skb->dev; - /* Find device slot */ - spin_lock(&hdev_list_lock); - for (i = 0; i < HCI_MAX_DEV; i++) { - if (hdev_list[i] == hdev) { - hdev_list[i] = NULL; - MOD_DEC_USE_COUNT; - break; - } + if (!hdev || (!test_bit(HCI_UP, &hdev->flags) && + !test_bit(HCI_INIT, &hdev->flags)) ) { + kfree_skb(skb); + return -1; } - spin_unlock(&hdev_list_lock); - hci_notify(hdev, HCI_DEV_UNREG); - - /* Sleep while device is in use */ - while (atomic_read(&hdev->refcnt)) { - int sleep_cnt = 100; + BT_DBG("%s type %d len %d", hdev->name, skb->pkt_type, skb->len); - DBG("%s sleeping on lock %d", hdev->name, atomic_read(&hdev->refcnt)); + /* Incomming skb */ + bluez_cb(skb)->incomming = 1; - sleep_on_timeout(&hdev->req_wait_q, HZ*10); - if (!(--sleep_cnt)) - break; - } + /* Time stamp */ + do_gettimeofday(&skb->stamp); + /* Queue frame for rx task */ + skb_queue_tail(&hdev->rx_q, skb); + hci_sched_rx(hdev); return 0; } -/* Interface to upper protocols */ +/* ---- Interface to upper protocols ---- */ /* Register/Unregister protocols. - * hci_task_lock is used to ensure that no tasks are running. - */ -int hci_register_proto(struct hci_proto *hproto) + * hci_task_lock is used to ensure that no tasks are running. */ +int hci_register_proto(struct hci_proto *hp) { int err = 0; - DBG("%p name %s", hproto, hproto->name); + BT_DBG("%p name %s id %d", hp, hp->name, hp->id); - if (hproto->id >= HCI_MAX_PROTO) + if (hp->id >= HCI_MAX_PROTO) return -EINVAL; write_lock_bh(&hci_task_lock); - if (!hproto_list[hproto->id]) - hproto_list[hproto->id] = hproto; + if (!hci_proto[hp->id]) + hci_proto[hp->id] = hp; else - err = -1; + err = -EEXIST; write_unlock_bh(&hci_task_lock); return err; } -int hci_unregister_proto(struct hci_proto *hproto) +int hci_unregister_proto(struct hci_proto *hp) { int err = 0; - DBG("%p name %s", hproto, hproto->name); + BT_DBG("%p name %s id %d", hp, hp->name, hp->id); - if (hproto->id > HCI_MAX_PROTO) + if (hp->id >= HCI_MAX_PROTO) return -EINVAL; write_lock_bh(&hci_task_lock); - if (hproto_list[hproto->id]) - hproto_list[hproto->id] = NULL; + if (hci_proto[hp->id]) + hci_proto[hp->id] = NULL; else err = -ENOENT; @@ -1070,10 +964,14 @@ return -ENODEV; } - DBG("%s type %d len %d", hdev->name, skb->pkt_type, skb->len); + BT_DBG("%s type %d len %d", hdev->name, skb->pkt_type, skb->len); + + if (atomic_read(&hdev->promisc)) { + /* Time stamp */ + do_gettimeofday(&skb->stamp); - if (hdev->flags & HCI_SOCK) hci_send_to_sock(hdev, skb); + } /* Get rid of skb owner, prior to sending to the driver. */ skb_orphan(skb); @@ -1081,128 +979,6 @@ return hdev->send(skb); } -/* Connection scheduler */ -static inline struct hci_conn *hci_low_sent(struct hci_dev *hdev, __u8 type, int *quote) -{ - struct conn_hash *h = &hdev->conn_hash; - struct hci_conn *conn = NULL; - int num = 0, min = 0xffff; - struct list_head *p; - - conn_hash_lock(h); - list_for_each(p, &h->list) { - register struct hci_conn *c; - - c = list_entry(p, struct hci_conn, list); - - if (c->type != type || skb_queue_empty(&c->data_q)) - continue; - num++; - - if (c->sent < min) { - min = c->sent; - conn = c; - } - } - conn_hash_unlock(h); - - if (conn) { - int q = hdev->acl_cnt / num; - *quote = q ? q : 1; - } else - *quote = 0; - - DBG("conn %p quote %d", conn, *quote); - - return conn; -} - -static inline void hci_sched_acl(struct hci_dev *hdev) -{ - struct hci_conn *conn; - struct sk_buff *skb; - int quote; - - DBG("%s", hdev->name); - - while (hdev->acl_cnt && (conn = hci_low_sent(hdev, ACL_LINK, "e))) { - while (quote && (skb = skb_dequeue(&conn->data_q))) { - DBG("skb %p len %d", skb, skb->len); - - hci_send_frame(skb); - - conn->sent++; - hdev->acl_cnt--; - quote--; - } - } -} - -/* Schedule SCO */ -static inline void hci_sched_sco(struct hci_dev *hdev) -{ - /* FIXME: For now we queue SCO packets to the raw queue - - while (hdev->sco_cnt && (skb = skb_dequeue(&conn->data_q))) { - hci_send_frame(skb); - conn->sco_sent++; - hdev->sco_cnt--; - } - */ -} - -/* Get data from the previously sent command */ -static void * hci_sent_cmd_data(struct hci_dev *hdev, __u16 ogf, __u16 ocf) -{ - hci_command_hdr *hc; - - if (!hdev->sent_cmd) - return NULL; - - hc = (void *) hdev->sent_cmd->data; - - if (hc->opcode != __cpu_to_le16(cmd_opcode_pack(ogf, ocf))) - return NULL; - - DBG("%s ogf 0x%x ocf 0x%x", hdev->name, ogf, ocf); - - return hdev->sent_cmd->data + HCI_COMMAND_HDR_SIZE; -} - -/* Send raw HCI frame */ -int hci_send_raw(struct sk_buff *skb) -{ - struct hci_dev *hdev = (struct hci_dev *) skb->dev; - - if (!hdev) { - kfree_skb(skb); - return -ENODEV; - } - - DBG("%s type %d len %d", hdev->name, skb->pkt_type, skb->len); - - if (hdev->flags & HCI_NORMAL) { - /* Queue frame according it's type */ - switch (skb->pkt_type) { - case HCI_COMMAND_PKT: - skb_queue_tail(&hdev->cmd_q, skb); - hci_sched_cmd(hdev); - return 0; - - case HCI_ACLDATA_PKT: - case HCI_SCODATA_PKT: - /* FIXME: - * Check header here and queue to apropriate connection. - */ - break; - } - } - - skb_queue_tail(&hdev->raw_q, skb); - hci_sched_tx(hdev); - return 0; -} - /* Send HCI command */ int hci_send_cmd(struct hci_dev *hdev, __u16 ogf, __u16 ocf, __u32 plen, void *param) { @@ -1210,10 +986,10 @@ hci_command_hdr *hc; struct sk_buff *skb; - DBG("%s ogf 0x%x ocf 0x%x plen %d", hdev->name, ogf, ocf, plen); + BT_DBG("%s ogf 0x%x ocf 0x%x plen %d", hdev->name, ogf, ocf, plen); if (!(skb = bluez_skb_alloc(len, GFP_ATOMIC))) { - ERR("%s Can't allocate memory for HCI command", hdev->name); + BT_ERR("%s Can't allocate memory for HCI command", hdev->name); return -ENOMEM; } @@ -1224,7 +1000,7 @@ if (plen) memcpy(skb_put(skb, plen), param, plen); - DBG("skb len %d", skb->len); + BT_DBG("skb len %d", skb->len); skb->pkt_type = HCI_COMMAND_PKT; skb->dev = (void *) hdev; @@ -1234,10 +1010,28 @@ return 0; } +/* Get data from the previously sent command */ +void *hci_sent_cmd_data(struct hci_dev *hdev, __u16 ogf, __u16 ocf) +{ + hci_command_hdr *hc; + + if (!hdev->sent_cmd) + return NULL; + + hc = (void *) hdev->sent_cmd->data; + + if (hc->opcode != __cpu_to_le16(cmd_opcode_pack(ogf, ocf))) + return NULL; + + BT_DBG("%s ogf 0x%x ocf 0x%x", hdev->name, ogf, ocf); + + return hdev->sent_cmd->data + HCI_COMMAND_HDR_SIZE; +} + /* Send ACL data */ static void hci_add_acl_hdr(struct sk_buff *skb, __u16 handle, __u16 flags) { - int len = skb->len; + int len = skb->len; hci_acl_hdr *ah; ah = (hci_acl_hdr *) skb_push(skb, HCI_ACL_HDR_SIZE); @@ -1252,7 +1046,7 @@ struct hci_dev *hdev = conn->hdev; struct sk_buff *list; - DBG("%s conn %p flags 0x%x", hdev->name, conn, flags); + BT_DBG("%s conn %p flags 0x%x", hdev->name, conn, flags); skb->dev = (void *) hdev; skb->pkt_type = HCI_ACLDATA_PKT; @@ -1260,12 +1054,12 @@ if (!(list = skb_shinfo(skb)->frag_list)) { /* Non fragmented */ - DBG("%s nonfrag skb %p len %d", hdev->name, skb, skb->len); + BT_DBG("%s nonfrag skb %p len %d", hdev->name, skb, skb->len); skb_queue_tail(&conn->data_q, skb); } else { /* Fragmented */ - DBG("%s frag %p len %d", hdev->name, skb, skb->len); + BT_DBG("%s frag %p len %d", hdev->name, skb, skb->len); skb_shinfo(skb)->frag_list = NULL; @@ -1280,7 +1074,7 @@ skb->pkt_type = HCI_ACLDATA_PKT; hci_add_acl_hdr(skb, conn->handle, flags | ACL_CONT); - DBG("%s frag %p len %d", hdev->name, skb, skb->len); + BT_DBG("%s frag %p len %d", hdev->name, skb, skb->len); __skb_queue_tail(&conn->data_q, skb); } while (list); @@ -1298,7 +1092,7 @@ struct hci_dev *hdev = conn->hdev; hci_sco_hdr hs; - DBG("%s len %d", hdev->name, skb->len); + BT_DBG("%s len %d", hdev->name, skb->len); if (skb->len > hdev->sco_mtu) { kfree_skb(skb); @@ -1315,544 +1109,136 @@ skb->pkt_type = HCI_SCODATA_PKT; skb_queue_tail(&conn->data_q, skb); hci_sched_tx(hdev); - return 0; } -/* Handle HCI Event packets */ - -/* Command Complete OGF LINK_CTL */ -static void hci_cc_link_ctl(struct hci_dev *hdev, __u16 ocf, struct sk_buff *skb) -{ - DBG("%s ocf 0x%x", hdev->name, ocf); - - switch (ocf) { - default: - DBG("%s Command complete: ogf LINK_CTL ocf %x", hdev->name, ocf); - break; - }; -} - -/* Command Complete OGF LINK_POLICY */ -static void hci_cc_link_policy(struct hci_dev *hdev, __u16 ocf, struct sk_buff *skb) -{ - DBG("%s ocf 0x%x", hdev->name, ocf); - - switch (ocf) { - default: - DBG("%s: Command complete: ogf LINK_POLICY ocf %x", hdev->name, ocf); - break; - }; -} - -/* Command Complete OGF HOST_CTL */ -static void hci_cc_host_ctl(struct hci_dev *hdev, __u16 ocf, struct sk_buff *skb) -{ - __u8 status, param; - void *sent; - - - DBG("%s ocf 0x%x", hdev->name, ocf); - - switch (ocf) { - case OCF_RESET: - status = *((__u8 *) skb->data); - - hci_req_complete(hdev, status); - break; - - case OCF_SET_EVENT_FLT: - status = *((__u8 *) skb->data); - - if (status) { - DBG("%s SET_EVENT_FLT failed %d", hdev->name, status); - } else { - DBG("%s SET_EVENT_FLT succeseful", hdev->name); - } - break; - - case OCF_WRITE_AUTH_ENABLE: - if (!(sent = hci_sent_cmd_data(hdev, OGF_HOST_CTL, OCF_WRITE_AUTH_ENABLE))) - break; - - status = *((__u8 *) skb->data); - param = *((__u8 *) sent); - - if (!status) { - if (param == AUTH_ENABLED) - hdev->flags |= HCI_AUTH; - else - hdev->flags &= ~HCI_AUTH; - } - hci_req_complete(hdev, status); - break; - - case OCF_WRITE_CA_TIMEOUT: - status = *((__u8 *) skb->data); - - if (status) { - DBG("%s OCF_WRITE_CA_TIMEOUT failed %d", hdev->name, status); - } else { - DBG("%s OCF_WRITE_CA_TIMEOUT succeseful", hdev->name); - } - break; - - case OCF_WRITE_PG_TIMEOUT: - status = *((__u8 *) skb->data); - - if (status) { - DBG("%s OCF_WRITE_PG_TIMEOUT failed %d", hdev->name, status); - } else { - DBG("%s: OCF_WRITE_PG_TIMEOUT succeseful", hdev->name); - } - break; - - case OCF_WRITE_SCAN_ENABLE: - if (!(sent = hci_sent_cmd_data(hdev, OGF_HOST_CTL, OCF_WRITE_SCAN_ENABLE))) - break; - status = *((__u8 *) skb->data); - param = *((__u8 *) sent); - - DBG("param 0x%x", param); - - if (!status) { - switch (param) { - case IS_ENA_PS_ENA: - hdev->flags |= HCI_PSCAN | HCI_ISCAN; - break; - - case IS_ENA_PS_DIS: - hdev->flags &= ~HCI_PSCAN; - hdev->flags |= HCI_ISCAN; - break; - - case IS_DIS_PS_ENA: - hdev->flags &= ~HCI_ISCAN; - hdev->flags |= HCI_PSCAN; - break; - - default: - hdev->flags &= ~(HCI_ISCAN | HCI_PSCAN); - break; - }; - } - hci_req_complete(hdev, status); - break; - - default: - DBG("%s Command complete: ogf HOST_CTL ocf %x", hdev->name, ocf); - break; - }; -} - -/* Command Complete OGF INFO_PARAM */ -static void hci_cc_info_param(struct hci_dev *hdev, __u16 ocf, struct sk_buff *skb) -{ - read_local_features_rp *lf; - read_buffer_size_rp *bs; - read_bd_addr_rp *ba; - - DBG("%s ocf 0x%x", hdev->name, ocf); - - switch (ocf) { - case OCF_READ_LOCAL_FEATURES: - lf = (read_local_features_rp *) skb->data; - - if (lf->status) { - DBG("%s READ_LOCAL_FEATURES failed %d", hdev->name, lf->status); - break; - } - - memcpy(hdev->features, lf->features, sizeof(hdev->features)); - - /* Adjust default settings according to features - * supported by device. */ - if (hdev->features[0] & LMP_3SLOT) - hdev->pkt_type |= (HCI_DM3 | HCI_DH3); - - if (hdev->features[0] & LMP_5SLOT) - hdev->pkt_type |= (HCI_DM5 | HCI_DH5); - - DBG("%s: features 0x%x 0x%x 0x%x", hdev->name, lf->features[0], lf->features[1], lf->features[2]); - - break; - - case OCF_READ_BUFFER_SIZE: - bs = (read_buffer_size_rp *) skb->data; - - if (bs->status) { - DBG("%s READ_BUFFER_SIZE failed %d", hdev->name, bs->status); - break; - } - - hdev->acl_mtu = __le16_to_cpu(bs->acl_mtu); - hdev->sco_mtu = bs->sco_mtu; - hdev->acl_max = hdev->acl_cnt = __le16_to_cpu(bs->acl_max_pkt); - hdev->sco_max = hdev->sco_cnt = __le16_to_cpu(bs->sco_max_pkt); - - DBG("%s mtu: acl %d, sco %d max_pkt: acl %d, sco %d", hdev->name, - hdev->acl_mtu, hdev->sco_mtu, hdev->acl_max, hdev->sco_max); - - break; - - case OCF_READ_BD_ADDR: - ba = (read_bd_addr_rp *) skb->data; - - if (!ba->status) { - bacpy(&hdev->bdaddr, &ba->bdaddr); - } else { - DBG("%s: READ_BD_ADDR failed %d", hdev->name, ba->status); - } - - hci_req_complete(hdev, ba->status); - break; - - default: - DBG("%s Command complete: ogf INFO_PARAM ocf %x", hdev->name, ocf); - break; - }; -} +/* ---- HCI TX task (outgoing data) ---- */ -/* Command Status OGF LINK_CTL */ -static void hci_cs_link_ctl(struct hci_dev *hdev, __u16 ocf, __u8 status) +/* HCI Connection scheduler */ +static inline struct hci_conn *hci_low_sent(struct hci_dev *hdev, __u8 type, int *quote) { - struct hci_proto * hp; - - DBG("%s ocf 0x%x", hdev->name, ocf); - - switch (ocf) { - case OCF_CREATE_CONN: - if (status) { - create_conn_cp *cc = hci_sent_cmd_data(hdev, OGF_LINK_CTL, OCF_CREATE_CONN); - - if (!cc) - break; + struct conn_hash *h = &hdev->conn_hash; + struct hci_conn *conn = NULL; + int num = 0, min = ~0; + struct list_head *p; - DBG("%s Create connection error: status 0x%x %s", hdev->name, - status, batostr(&cc->bdaddr)); + /* We don't have to lock device here. Connections are always + * added and removed with TX task disabled. */ + list_for_each(p, &h->list) { + struct hci_conn *c; + c = list_entry(p, struct hci_conn, list); - /* Notify upper protocols */ - if ((hp = GET_HPROTO(HCI_PROTO_L2CAP)) && hp->connect_cfm) { - tasklet_disable(&hdev->tx_task); - hp->connect_cfm(hdev, &cc->bdaddr, status, NULL); - tasklet_enable(&hdev->tx_task); - } - } - break; + if (c->type != type || c->state != BT_CONNECTED + || skb_queue_empty(&c->data_q)) + continue; + num++; - case OCF_INQUIRY: - if (status) { - DBG("%s Inquiry error: status 0x%x", hdev->name, status); - hci_req_complete(hdev, status); + if (c->sent < min) { + min = c->sent; + conn = c; } - break; - - default: - DBG("%s Command status: ogf LINK_CTL ocf %x", hdev->name, ocf); - break; - }; -} - -/* Command Status OGF LINK_POLICY */ -static void hci_cs_link_policy(struct hci_dev *hdev, __u16 ocf, __u8 status) -{ - DBG("%s ocf 0x%x", hdev->name, ocf); - - switch (ocf) { - default: - DBG("%s Command status: ogf HOST_POLICY ocf %x", hdev->name, ocf); - break; - }; -} - -/* Command Status OGF HOST_CTL */ -static void hci_cs_host_ctl(struct hci_dev *hdev, __u16 ocf, __u8 status) -{ - DBG("%s ocf 0x%x", hdev->name, ocf); - - switch (ocf) { - default: - DBG("%s Command status: ogf HOST_CTL ocf %x", hdev->name, ocf); - break; - }; -} - -/* Command Status OGF INFO_PARAM */ -static void hci_cs_info_param(struct hci_dev *hdev, __u16 ocf, __u8 status) -{ - DBG("%s: hci_cs_info_param: ocf 0x%x", hdev->name, ocf); - - switch (ocf) { - default: - DBG("%s Command status: ogf INFO_PARAM ocf %x", hdev->name, ocf); - break; - }; -} - -/* Inquiry Complete */ -static void hci_inquiry_complete_evt(struct hci_dev *hdev, struct sk_buff *skb) -{ - __u8 status = *((__u8 *) skb->data); - - DBG("%s status %d", hdev->name, status); - - hci_req_complete(hdev, status); -} - -/* Inquiry Result */ -static void hci_inquiry_result_evt(struct hci_dev *hdev, struct sk_buff *skb) -{ - inquiry_info *info = (inquiry_info *) (skb->data + 1); - int num_rsp = *((__u8 *) skb->data); + } - DBG("%s num_rsp %d", hdev->name, num_rsp); + if (conn) { + int cnt = (type == ACL_LINK ? hdev->acl_cnt : hdev->sco_cnt); + int q = cnt / num; + *quote = q ? q : 1; + } else + *quote = 0; - for (; num_rsp; num_rsp--) - inquiry_cache_update(&hdev->inq_cache, info++); + BT_DBG("conn %p quote %d", conn, *quote); + return conn; } -/* Connect Request */ -static void hci_conn_request_evt(struct hci_dev *hdev, struct sk_buff *skb) +static inline void hci_acl_tx_to(struct hci_dev *hdev) { - evt_conn_request *cr = (evt_conn_request *) skb->data; - struct hci_proto *hp; - accept_conn_req_cp ac; - int accept = 0; + struct conn_hash *h = &hdev->conn_hash; + struct list_head *p; + struct hci_conn *c; - DBG("%s Connection request: %s type 0x%x", hdev->name, batostr(&cr->bdaddr), cr->link_type); + BT_ERR("%s ACL tx timeout", hdev->name); - /* Notify upper protocols */ - if (cr->link_type == ACL_LINK) { - /* ACL link notify L2CAP */ - if ((hp = GET_HPROTO(HCI_PROTO_L2CAP)) && hp->connect_ind) { - tasklet_disable(&hdev->tx_task); - accept = hp->connect_ind(hdev, &cr->bdaddr); - tasklet_enable(&hdev->tx_task); + /* Kill stalled connections */ + list_for_each(p, &h->list) { + c = list_entry(p, struct hci_conn, list); + if (c->type == ACL_LINK && c->sent) { + BT_ERR("%s killing stalled ACL connection %s", + hdev->name, batostr(&c->dst)); + hci_acl_disconn(c, 0x13); } - } else { - /* SCO link (no notification) */ - /* FIXME: Should be accept it here or let the requester (app) accept it ? */ - accept = 1; - } - - if (accept) { - /* Connection accepted by upper layer */ - bacpy(&ac.bdaddr, &cr->bdaddr); - ac.role = 0x01; /* Remain slave */ - hci_send_cmd(hdev, OGF_LINK_CTL, OCF_ACCEPT_CONN_REQ, ACCEPT_CONN_REQ_CP_SIZE, &ac); - } else { - /* Connection rejected by upper layer */ - /* FIXME: - * Should we use HCI reject here ? - */ - return; } } -/* Connect Complete */ -static void hci_conn_complete_evt(struct hci_dev *hdev, struct sk_buff *skb) +static inline void hci_sched_acl(struct hci_dev *hdev) { - evt_conn_complete *cc = (evt_conn_complete *) skb->data; - struct hci_conn *conn = NULL; - struct hci_proto *hp; + struct hci_conn *conn; + struct sk_buff *skb; + int quote; - DBG("%s", hdev->name); + BT_DBG("%s", hdev->name); - tasklet_disable(&hdev->tx_task); + /* ACL tx timeout must be longer than maximum + * link supervision timeout (40.9 seconds) */ + if (!hdev->acl_cnt && (jiffies - hdev->acl_last_tx) > (HZ * 45)) + hci_acl_tx_to(hdev); - if (!cc->status) - conn = hci_conn_add(hdev, __le16_to_cpu(cc->handle), cc->link_type, &cc->bdaddr); + while (hdev->acl_cnt && (conn = hci_low_sent(hdev, ACL_LINK, "e))) { + while (quote-- && (skb = skb_dequeue(&conn->data_q))) { + BT_DBG("skb %p len %d", skb, skb->len); + hci_send_frame(skb); + hdev->acl_last_tx = jiffies; - /* Notify upper protocols */ - if (cc->link_type == ACL_LINK) { - /* ACL link notify L2CAP layer */ - if ((hp = GET_HPROTO(HCI_PROTO_L2CAP)) && hp->connect_cfm) - hp->connect_cfm(hdev, &cc->bdaddr, cc->status, conn); - } else { - /* SCO link (no notification) */ + hdev->acl_cnt--; + conn->sent++; + } } - - tasklet_enable(&hdev->tx_task); } -/* Disconnect Complete */ -static void hci_disconn_complete_evt(struct hci_dev *hdev, struct sk_buff *skb) +/* Schedule SCO */ +static inline void hci_sched_sco(struct hci_dev *hdev) { - evt_disconn_complete *dc = (evt_disconn_complete *) skb->data; - struct hci_conn *conn = NULL; - struct hci_proto *hp; - __u16 handle = __le16_to_cpu(dc->handle); + struct hci_conn *conn; + struct sk_buff *skb; + int quote; - DBG("%s", hdev->name); + BT_DBG("%s", hdev->name); - if (!dc->status && (conn = conn_hash_lookup(&hdev->conn_hash, handle))) { - tasklet_disable(&hdev->tx_task); + while (hdev->sco_cnt && (conn = hci_low_sent(hdev, SCO_LINK, "e))) { + while (quote-- && (skb = skb_dequeue(&conn->data_q))) { + BT_DBG("skb %p len %d", skb, skb->len); + hci_send_frame(skb); - /* Notify upper protocols */ - if (conn->type == ACL_LINK) { - /* ACL link notify L2CAP layer */ - if ((hp = GET_HPROTO(HCI_PROTO_L2CAP)) && hp->disconn_ind) - hp->disconn_ind(conn, dc->reason); - } else { - /* SCO link (no notification) */ + conn->sent++; + if (conn->sent == ~0) + conn->sent = 0; } - - hci_conn_del(hdev, conn); - - tasklet_enable(&hdev->tx_task); } } -/* Number of completed packets */ -static void hci_num_comp_pkts_evt(struct hci_dev *hdev, struct sk_buff *skb) +static void hci_tx_task(unsigned long arg) { - evt_num_comp_pkts *nc = (evt_num_comp_pkts *) skb->data; - __u16 *ptr; - int i; - - skb_pull(skb, EVT_NUM_COMP_PKTS_SIZE); - - DBG("%s num_hndl %d", hdev->name, nc->num_hndl); + struct hci_dev *hdev = (struct hci_dev *) arg; + struct sk_buff *skb; - if (skb->len < nc->num_hndl * 4) { - DBG("%s bad parameters", hdev->name); - return; - } + read_lock(&hci_task_lock); - tasklet_disable(&hdev->tx_task); + BT_DBG("%s acl %d sco %d", hdev->name, hdev->acl_cnt, hdev->sco_cnt); - for (i = 0, ptr = (__u16 *) skb->data; i < nc->num_hndl; i++) { - struct hci_conn *conn; - __u16 handle, count; + /* Schedule queues and send stuff to HCI driver */ - handle = __le16_to_cpu(get_unaligned(ptr++)); - count = __le16_to_cpu(get_unaligned(ptr++)); + hci_sched_acl(hdev); - hdev->acl_cnt += count; + hci_sched_sco(hdev); - if ((conn = conn_hash_lookup(&hdev->conn_hash, handle))) - conn->sent -= count; - } + /* Send next queued raw (unknown type) packet */ + while ((skb = skb_dequeue(&hdev->raw_q))) + hci_send_frame(skb); - tasklet_enable(&hdev->tx_task); - - hci_sched_tx(hdev); + read_unlock(&hci_task_lock); } -static inline void hci_event_packet(struct hci_dev *hdev, struct sk_buff *skb) -{ - hci_event_hdr *he = (hci_event_hdr *) skb->data; - evt_cmd_status *cs; - evt_cmd_complete *ec; - __u16 opcode, ocf, ogf; - - skb_pull(skb, HCI_EVENT_HDR_SIZE); - - DBG("%s evt 0x%x", hdev->name, he->evt); - - switch (he->evt) { - case EVT_NUM_COMP_PKTS: - hci_num_comp_pkts_evt(hdev, skb); - break; - - case EVT_INQUIRY_COMPLETE: - hci_inquiry_complete_evt(hdev, skb); - break; - - case EVT_INQUIRY_RESULT: - hci_inquiry_result_evt(hdev, skb); - break; - - case EVT_CONN_REQUEST: - hci_conn_request_evt(hdev, skb); - break; - - case EVT_CONN_COMPLETE: - hci_conn_complete_evt(hdev, skb); - break; - - case EVT_DISCONN_COMPLETE: - hci_disconn_complete_evt(hdev, skb); - break; - - case EVT_CMD_STATUS: - cs = (evt_cmd_status *) skb->data; - skb_pull(skb, EVT_CMD_STATUS_SIZE); - - opcode = __le16_to_cpu(cs->opcode); - ogf = cmd_opcode_ogf(opcode); - ocf = cmd_opcode_ocf(opcode); - - switch (ogf) { - case OGF_INFO_PARAM: - hci_cs_info_param(hdev, ocf, cs->status); - break; - - case OGF_HOST_CTL: - hci_cs_host_ctl(hdev, ocf, cs->status); - break; - case OGF_LINK_CTL: - hci_cs_link_ctl(hdev, ocf, cs->status); - break; - - case OGF_LINK_POLICY: - hci_cs_link_policy(hdev, ocf, cs->status); - break; - - default: - DBG("%s Command Status OGF %x", hdev->name, ogf); - break; - }; - - if (cs->ncmd) { - atomic_set(&hdev->cmd_cnt, 1); - if (!skb_queue_empty(&hdev->cmd_q)) - hci_sched_cmd(hdev); - } - break; - - case EVT_CMD_COMPLETE: - ec = (evt_cmd_complete *) skb->data; - skb_pull(skb, EVT_CMD_COMPLETE_SIZE); - - opcode = __le16_to_cpu(ec->opcode); - ogf = cmd_opcode_ogf(opcode); - ocf = cmd_opcode_ocf(opcode); - - switch (ogf) { - case OGF_INFO_PARAM: - hci_cc_info_param(hdev, ocf, skb); - break; - - case OGF_HOST_CTL: - hci_cc_host_ctl(hdev, ocf, skb); - break; - - case OGF_LINK_CTL: - hci_cc_link_ctl(hdev, ocf, skb); - break; - - case OGF_LINK_POLICY: - hci_cc_link_policy(hdev, ocf, skb); - break; - - default: - DBG("%s Command Completed OGF %x", hdev->name, ogf); - break; - }; - - if (ec->ncmd) { - atomic_set(&hdev->cmd_cnt, 1); - if (!skb_queue_empty(&hdev->cmd_q)) - hci_sched_cmd(hdev); - } - break; - }; - - kfree_skb(skb); - hdev->stat.evt_rx++; -} +/* ----- HCI RX task (incomming data proccessing) ----- */ /* ACL data packet */ static inline void hci_acldata_packet(struct hci_dev *hdev, struct sk_buff *skb) @@ -1867,51 +1253,86 @@ flags = acl_flags(handle); handle = acl_handle(handle); - DBG("%s len %d handle 0x%x flags 0x%x", hdev->name, skb->len, handle, flags); + BT_DBG("%s len %d handle 0x%x flags 0x%x", hdev->name, skb->len, handle, flags); - if ((conn = conn_hash_lookup(&hdev->conn_hash, handle))) { + hdev->stat.acl_rx++; + + hci_dev_lock(hdev); + conn = conn_hash_lookup_handle(hdev, handle); + hci_dev_unlock(hdev); + + if (conn) { register struct hci_proto *hp; /* Send to upper protocol */ - if ((hp = GET_HPROTO(HCI_PROTO_L2CAP)) && hp->recv_acldata) { + if ((hp = hci_proto[HCI_PROTO_L2CAP]) && hp->recv_acldata) { hp->recv_acldata(conn, skb, flags); - goto sent; + return; } } else { - ERR("%s ACL packet for unknown connection handle %d", hdev->name, handle); + BT_ERR("%s ACL packet for unknown connection handle %d", + hdev->name, handle); } kfree_skb(skb); -sent: - hdev->stat.acl_rx++; } /* SCO data packet */ static inline void hci_scodata_packet(struct hci_dev *hdev, struct sk_buff *skb) { - DBG("%s len %d", hdev->name, skb->len); + hci_sco_hdr *sh = (void *) skb->data; + struct hci_conn *conn; + __u16 handle; + + skb_pull(skb, HCI_SCO_HDR_SIZE); + + handle = __le16_to_cpu(sh->handle); + + BT_DBG("%s len %d handle 0x%x", hdev->name, skb->len, handle); - kfree_skb(skb); hdev->stat.sco_rx++; + + hci_dev_lock(hdev); + conn = conn_hash_lookup_handle(hdev, handle); + hci_dev_unlock(hdev); + + if (conn) { + register struct hci_proto *hp; + + /* Send to upper protocol */ + if ((hp = hci_proto[HCI_PROTO_SCO]) && hp->recv_scodata) { + hp->recv_scodata(conn, skb); + return; + } + } else { + BT_ERR("%s SCO packet for unknown connection handle %d", + hdev->name, handle); + } + + kfree_skb(skb); } -/* ----- HCI tasks ----- */ void hci_rx_task(unsigned long arg) { struct hci_dev *hdev = (struct hci_dev *) arg; struct sk_buff *skb; - DBG("%s", hdev->name); + BT_DBG("%s", hdev->name); read_lock(&hci_task_lock); while ((skb = skb_dequeue(&hdev->rx_q))) { - if (hdev->flags & HCI_SOCK) { + if (atomic_read(&hdev->promisc)) { /* Send copy to the sockets */ hci_send_to_sock(hdev, skb); } - if (hdev->flags & HCI_INIT) { + if (test_bit(HCI_RAW, &hdev->flags)) { + kfree_skb(skb); + continue; + } + + if (test_bit(HCI_INIT, &hdev->flags)) { /* Don't process data packets in this states. */ switch (skb->pkt_type) { case HCI_ACLDATA_PKT: @@ -1921,64 +1342,43 @@ }; } - if (hdev->flags & HCI_NORMAL) { - /* Process frame */ - switch (skb->pkt_type) { - case HCI_EVENT_PKT: - hci_event_packet(hdev, skb); - break; + /* Process frame */ + switch (skb->pkt_type) { + case HCI_EVENT_PKT: + hci_event_packet(hdev, skb); + break; - case HCI_ACLDATA_PKT: - DBG("%s ACL data packet", hdev->name); - hci_acldata_packet(hdev, skb); - break; + case HCI_ACLDATA_PKT: + BT_DBG("%s ACL data packet", hdev->name); + hci_acldata_packet(hdev, skb); + break; - case HCI_SCODATA_PKT: - DBG("%s SCO data packet", hdev->name); - hci_scodata_packet(hdev, skb); - break; + case HCI_SCODATA_PKT: + BT_DBG("%s SCO data packet", hdev->name); + hci_scodata_packet(hdev, skb); + break; - default: - kfree_skb(skb); - break; - }; - } else { + default: kfree_skb(skb); + break; } } read_unlock(&hci_task_lock); } -static void hci_tx_task(unsigned long arg) -{ - struct hci_dev *hdev = (struct hci_dev *) arg; - struct sk_buff *skb; - - read_lock(&hci_task_lock); - - DBG("%s acl %d sco %d", hdev->name, hdev->acl_cnt, hdev->sco_cnt); - - /* Schedule queues and send stuff to HCI driver */ - - hci_sched_acl(hdev); - - hci_sched_sco(hdev); - - /* Send next queued raw (unknown type) packet */ - while ((skb = skb_dequeue(&hdev->raw_q))) - hci_send_frame(skb); - - read_unlock(&hci_task_lock); -} - static void hci_cmd_task(unsigned long arg) { struct hci_dev *hdev = (struct hci_dev *) arg; struct sk_buff *skb; - DBG("%s cmd %d", hdev->name, atomic_read(&hdev->cmd_cnt)); + BT_DBG("%s cmd %d", hdev->name, atomic_read(&hdev->cmd_cnt)); + if (!atomic_read(&hdev->cmd_cnt) && (jiffies - hdev->cmd_last_tx) > HZ) { + BT_ERR("%s command tx timeout", hdev->name); + atomic_set(&hdev->cmd_cnt, 1); + } + /* Send queued commands */ if (atomic_read(&hdev->cmd_cnt) && (skb = skb_dequeue(&hdev->cmd_q))) { if (hdev->sent_cmd) @@ -1987,6 +1387,7 @@ if ((hdev->sent_cmd = skb_clone(skb, GFP_ATOMIC))) { atomic_dec(&hdev->cmd_cnt); hci_send_frame(skb); + hdev->cmd_last_tx = jiffies; } else { skb_queue_head(&hdev->cmd_q, skb); hci_sched_cmd(hdev); @@ -1994,33 +1395,10 @@ } } -/* Receive frame from HCI drivers */ -int hci_recv_frame(struct sk_buff *skb) -{ - struct hci_dev *hdev = (struct hci_dev *) skb->dev; - - if (!hdev || !(hdev->flags & (HCI_UP | HCI_INIT))) { - kfree_skb(skb); - return -1; - } - - DBG("%s type %d len %d", hdev->name, skb->pkt_type, skb->len); - - /* Incomming skb */ - bluez_cb(skb)->incomming = 1; - - /* Queue frame for rx task */ - skb_queue_tail(&hdev->rx_q, skb); - hci_sched_rx(hdev); - - return 0; -} +/* ---- Initialization ---- */ int hci_core_init(void) { - /* Init locks */ - spin_lock_init(&hdev_list_lock); - return 0; } @@ -2028,5 +1406,3 @@ { return 0; } - -MODULE_LICENSE("GPL"); --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/net/bluetooth/hci_event.c 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1,910 @@ +/* + BlueZ - Bluetooth protocol stack for Linux + Copyright (C) 2000-2001 Qualcomm Incorporated + + Written 2000,2001 by Maxim Krasnyansky + + 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. +*/ + +/* + * HCI Events. + * + * $Id: hci_event.c,v 1.4 2002/07/27 18:14:38 maxk Exp $ + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#ifndef HCI_CORE_DEBUG +#undef BT_DBG +#define BT_DBG( A... ) +#endif + +/* Handle HCI Event packets */ + +/* Command Complete OGF LINK_CTL */ +static void hci_cc_link_ctl(struct hci_dev *hdev, __u16 ocf, struct sk_buff *skb) +{ + __u8 status; + + BT_DBG("%s ocf 0x%x", hdev->name, ocf); + + switch (ocf) { + case OCF_INQUIRY_CANCEL: + status = *((__u8 *) skb->data); + + if (status) { + BT_DBG("%s Inquiry cancel error: status 0x%x", hdev->name, status); + } else { + clear_bit(HCI_INQUIRY, &hdev->flags); + hci_req_complete(hdev, status); + } + break; + + default: + BT_DBG("%s Command complete: ogf LINK_CTL ocf %x", hdev->name, ocf); + break; + }; +} + +/* Command Complete OGF LINK_POLICY */ +static void hci_cc_link_policy(struct hci_dev *hdev, __u16 ocf, struct sk_buff *skb) +{ + struct hci_conn *conn; + role_discovery_rp *rd; + + BT_DBG("%s ocf 0x%x", hdev->name, ocf); + + switch (ocf) { + case OCF_ROLE_DISCOVERY: + rd = (void *) skb->data; + + if (rd->status) + break; + + hci_dev_lock(hdev); + + conn = conn_hash_lookup_handle(hdev, __le16_to_cpu(rd->handle)); + if (conn) { + if (rd->role) + conn->link_mode &= ~HCI_LM_MASTER; + else + conn->link_mode |= HCI_LM_MASTER; + } + + hci_dev_unlock(hdev); + break; + + default: + BT_DBG("%s: Command complete: ogf LINK_POLICY ocf %x", + hdev->name, ocf); + break; + }; +} + +/* Command Complete OGF HOST_CTL */ +static void hci_cc_host_ctl(struct hci_dev *hdev, __u16 ocf, struct sk_buff *skb) +{ + __u8 status, param; + void *sent; + + BT_DBG("%s ocf 0x%x", hdev->name, ocf); + + switch (ocf) { + case OCF_RESET: + status = *((__u8 *) skb->data); + hci_req_complete(hdev, status); + break; + + case OCF_SET_EVENT_FLT: + status = *((__u8 *) skb->data); + if (status) { + BT_DBG("%s SET_EVENT_FLT failed %d", hdev->name, status); + } else { + BT_DBG("%s SET_EVENT_FLT succeseful", hdev->name); + } + break; + + case OCF_WRITE_AUTH_ENABLE: + sent = hci_sent_cmd_data(hdev, OGF_HOST_CTL, OCF_WRITE_AUTH_ENABLE); + if (!sent) + break; + + status = *((__u8 *) skb->data); + param = *((__u8 *) sent); + + if (!status) { + if (param == AUTH_ENABLED) + set_bit(HCI_AUTH, &hdev->flags); + else + clear_bit(HCI_AUTH, &hdev->flags); + } + hci_req_complete(hdev, status); + break; + + case OCF_WRITE_ENCRYPT_MODE: + sent = hci_sent_cmd_data(hdev, OGF_HOST_CTL, OCF_WRITE_ENCRYPT_MODE); + if (!sent) + break; + + status = *((__u8 *) skb->data); + param = *((__u8 *) sent); + + if (!status) { + if (param) + set_bit(HCI_ENCRYPT, &hdev->flags); + else + clear_bit(HCI_ENCRYPT, &hdev->flags); + } + hci_req_complete(hdev, status); + break; + + case OCF_WRITE_CA_TIMEOUT: + status = *((__u8 *) skb->data); + if (status) { + BT_DBG("%s OCF_WRITE_CA_TIMEOUT failed %d", hdev->name, status); + } else { + BT_DBG("%s OCF_WRITE_CA_TIMEOUT succeseful", hdev->name); + } + break; + + case OCF_WRITE_PG_TIMEOUT: + status = *((__u8 *) skb->data); + if (status) { + BT_DBG("%s OCF_WRITE_PG_TIMEOUT failed %d", hdev->name, status); + } else { + BT_DBG("%s: OCF_WRITE_PG_TIMEOUT succeseful", hdev->name); + } + break; + + case OCF_WRITE_SCAN_ENABLE: + sent = hci_sent_cmd_data(hdev, OGF_HOST_CTL, OCF_WRITE_SCAN_ENABLE); + if (!sent) + break; + status = *((__u8 *) skb->data); + param = *((__u8 *) sent); + + BT_DBG("param 0x%x", param); + + if (!status) { + clear_bit(HCI_PSCAN, &hdev->flags); + clear_bit(HCI_ISCAN, &hdev->flags); + if (param & SCAN_INQUIRY) + set_bit(HCI_ISCAN, &hdev->flags); + + if (param & SCAN_PAGE) + set_bit(HCI_PSCAN, &hdev->flags); + } + hci_req_complete(hdev, status); + break; + + case OCF_HOST_BUFFER_SIZE: + status = *((__u8 *) skb->data); + if (status) { + BT_DBG("%s OCF_BUFFER_SIZE failed %d", hdev->name, status); + hci_req_complete(hdev, status); + } + break; + + default: + BT_DBG("%s Command complete: ogf HOST_CTL ocf %x", hdev->name, ocf); + break; + }; +} + +/* Command Complete OGF INFO_PARAM */ +static void hci_cc_info_param(struct hci_dev *hdev, __u16 ocf, struct sk_buff *skb) +{ + read_local_features_rp *lf; + read_buffer_size_rp *bs; + read_bd_addr_rp *ba; + + BT_DBG("%s ocf 0x%x", hdev->name, ocf); + + switch (ocf) { + case OCF_READ_LOCAL_FEATURES: + lf = (read_local_features_rp *) skb->data; + + if (lf->status) { + BT_DBG("%s READ_LOCAL_FEATURES failed %d", hdev->name, lf->status); + break; + } + + memcpy(hdev->features, lf->features, sizeof(hdev->features)); + + /* Adjust default settings according to features + * supported by device. */ + if (hdev->features[0] & LMP_3SLOT) + hdev->pkt_type |= (HCI_DM3 | HCI_DH3); + + if (hdev->features[0] & LMP_5SLOT) + hdev->pkt_type |= (HCI_DM5 | HCI_DH5); + + if (hdev->features[1] & LMP_HV2) + hdev->pkt_type |= (HCI_HV2); + + if (hdev->features[1] & LMP_HV3) + hdev->pkt_type |= (HCI_HV3); + + BT_DBG("%s: features 0x%x 0x%x 0x%x", hdev->name, lf->features[0], lf->features[1], lf->features[2]); + + break; + + case OCF_READ_BUFFER_SIZE: + bs = (read_buffer_size_rp *) skb->data; + + if (bs->status) { + BT_DBG("%s READ_BUFFER_SIZE failed %d", hdev->name, bs->status); + hci_req_complete(hdev, bs->status); + break; + } + + hdev->acl_mtu = __le16_to_cpu(bs->acl_mtu); + hdev->sco_mtu = bs->sco_mtu ? bs->sco_mtu : 64; + hdev->acl_pkts = hdev->acl_cnt = __le16_to_cpu(bs->acl_max_pkt); + hdev->sco_pkts = hdev->sco_cnt = __le16_to_cpu(bs->sco_max_pkt); + + BT_DBG("%s mtu: acl %d, sco %d max_pkt: acl %d, sco %d", hdev->name, + hdev->acl_mtu, hdev->sco_mtu, hdev->acl_pkts, hdev->sco_pkts); + break; + + case OCF_READ_BD_ADDR: + ba = (read_bd_addr_rp *) skb->data; + + if (!ba->status) { + bacpy(&hdev->bdaddr, &ba->bdaddr); + } else { + BT_DBG("%s: READ_BD_ADDR failed %d", hdev->name, ba->status); + } + + hci_req_complete(hdev, ba->status); + break; + + default: + BT_DBG("%s Command complete: ogf INFO_PARAM ocf %x", hdev->name, ocf); + break; + }; +} + +/* Command Status OGF LINK_CTL */ +static inline void hci_cs_create_conn(struct hci_dev *hdev, __u8 status) +{ + struct hci_conn *conn; + create_conn_cp *cc = hci_sent_cmd_data(hdev, OGF_LINK_CTL, OCF_CREATE_CONN); + + if (!cc) + return; + + hci_dev_lock(hdev); + + conn = conn_hash_lookup_ba(hdev, ACL_LINK, &cc->bdaddr); + + BT_DBG("%s status 0x%x bdaddr %s conn %p", hdev->name, + status, batostr(&cc->bdaddr), conn); + + if (status) { + if (conn) { + conn->state = BT_CLOSED; + hci_proto_connect_cfm(conn, status); + hci_conn_del(conn); + } + } else { + if (!conn) { + conn = hci_conn_add(hdev, ACL_LINK, &cc->bdaddr); + if (conn) { + conn->out = 1; + conn->link_mode |= HCI_LM_MASTER; + } else + BT_ERR("No memmory for new connection"); + } + } + + hci_dev_unlock(hdev); +} + +static void hci_cs_link_ctl(struct hci_dev *hdev, __u16 ocf, __u8 status) +{ + BT_DBG("%s ocf 0x%x", hdev->name, ocf); + + switch (ocf) { + case OCF_CREATE_CONN: + hci_cs_create_conn(hdev, status); + break; + + case OCF_ADD_SCO: + if (status) { + struct hci_conn *acl, *sco; + add_sco_cp *cp = hci_sent_cmd_data(hdev, + OGF_LINK_CTL, OCF_ADD_SCO); + __u16 handle; + + if (!cp) + break; + + handle = __le16_to_cpu(cp->handle); + + BT_DBG("%s Add SCO error: handle %d status 0x%x", hdev->name, handle, status); + + hci_dev_lock(hdev); + + acl = conn_hash_lookup_handle(hdev, handle); + if (acl && (sco = acl->link)) { + sco->state = BT_CLOSED; + hci_proto_connect_cfm(sco, status); + hci_conn_del(sco); + } + + hci_dev_unlock(hdev); + } + break; + + case OCF_INQUIRY: + if (status) { + BT_DBG("%s Inquiry error: status 0x%x", hdev->name, status); + hci_req_complete(hdev, status); + } else { + set_bit(HCI_INQUIRY, &hdev->flags); + } + break; + + default: + BT_DBG("%s Command status: ogf LINK_CTL ocf %x status %d", + hdev->name, ocf, status); + break; + }; +} + +/* Command Status OGF LINK_POLICY */ +static void hci_cs_link_policy(struct hci_dev *hdev, __u16 ocf, __u8 status) +{ + BT_DBG("%s ocf 0x%x", hdev->name, ocf); + + switch (ocf) { + default: + BT_DBG("%s Command status: ogf HOST_POLICY ocf %x", hdev->name, ocf); + break; + }; +} + +/* Command Status OGF HOST_CTL */ +static void hci_cs_host_ctl(struct hci_dev *hdev, __u16 ocf, __u8 status) +{ + BT_DBG("%s ocf 0x%x", hdev->name, ocf); + + switch (ocf) { + default: + BT_DBG("%s Command status: ogf HOST_CTL ocf %x", hdev->name, ocf); + break; + }; +} + +/* Command Status OGF INFO_PARAM */ +static void hci_cs_info_param(struct hci_dev *hdev, __u16 ocf, __u8 status) +{ + BT_DBG("%s: hci_cs_info_param: ocf 0x%x", hdev->name, ocf); + + switch (ocf) { + default: + BT_DBG("%s Command status: ogf INFO_PARAM ocf %x", hdev->name, ocf); + break; + }; +} + +/* Inquiry Complete */ +static inline void hci_inquiry_complete_evt(struct hci_dev *hdev, struct sk_buff *skb) +{ + __u8 status = *((__u8 *) skb->data); + + BT_DBG("%s status %d", hdev->name, status); + + clear_bit(HCI_INQUIRY, &hdev->flags); + hci_req_complete(hdev, status); +} + +/* Inquiry Result */ +static inline void hci_inquiry_result_evt(struct hci_dev *hdev, struct sk_buff *skb) +{ + inquiry_info *info = (inquiry_info *) (skb->data + 1); + int num_rsp = *((__u8 *) skb->data); + + BT_DBG("%s num_rsp %d", hdev->name, num_rsp); + + hci_dev_lock(hdev); + for (; num_rsp; num_rsp--) + inquiry_cache_update(hdev, info++); + hci_dev_unlock(hdev); +} + +/* Inquiry Result With RSSI */ +static inline void hci_inquiry_result_with_rssi_evt(struct hci_dev *hdev, struct sk_buff *skb) +{ + inquiry_info_with_rssi *info = (inquiry_info_with_rssi *) (skb->data + 1); + int num_rsp = *((__u8 *) skb->data); + + BT_DBG("%s num_rsp %d", hdev->name, num_rsp); + + hci_dev_lock(hdev); + for (; num_rsp; num_rsp--) { + inquiry_info tmp; + bacpy(&tmp.bdaddr, &info->bdaddr); + tmp.pscan_rep_mode = info->pscan_rep_mode; + tmp.pscan_period_mode = info->pscan_period_mode; + tmp.pscan_mode = 0x00; + memcpy(tmp.dev_class, &info->dev_class, 3); + tmp.clock_offset = info->clock_offset; + info++; + inquiry_cache_update(hdev, &tmp); + } + hci_dev_unlock(hdev); +} + +/* Connect Request */ +static inline void hci_conn_request_evt(struct hci_dev *hdev, struct sk_buff *skb) +{ + evt_conn_request *cr = (evt_conn_request *) skb->data; + int mask = hdev->link_mode; + + BT_DBG("%s Connection request: %s type 0x%x", hdev->name, + batostr(&cr->bdaddr), cr->link_type); + + mask |= hci_proto_connect_ind(hdev, &cr->bdaddr, cr->link_type); + + if (mask & HCI_LM_ACCEPT) { + /* Connection accepted */ + struct hci_conn *conn; + accept_conn_req_cp ac; + + hci_dev_lock(hdev); + conn = conn_hash_lookup_ba(hdev, cr->link_type, &cr->bdaddr); + if (!conn) { + if (!(conn = hci_conn_add(hdev, cr->link_type, &cr->bdaddr))) { + BT_ERR("No memmory for new connection"); + hci_dev_unlock(hdev); + return; + } + } + conn->state = BT_CONNECT; + hci_dev_unlock(hdev); + + bacpy(&ac.bdaddr, &cr->bdaddr); + + if (lmp_rswitch_capable(hdev) && (mask & HCI_LM_MASTER)) + ac.role = 0x00; /* Become master */ + else + ac.role = 0x01; /* Remain slave */ + + hci_send_cmd(hdev, OGF_LINK_CTL, OCF_ACCEPT_CONN_REQ, + ACCEPT_CONN_REQ_CP_SIZE, &ac); + } else { + /* Connection rejected */ + reject_conn_req_cp rc; + + bacpy(&rc.bdaddr, &cr->bdaddr); + rc.reason = 0x0f; + hci_send_cmd(hdev, OGF_LINK_CTL, OCF_REJECT_CONN_REQ, + REJECT_CONN_REQ_CP_SIZE, &rc); + } +} + +/* Connect Complete */ +static inline void hci_conn_complete_evt(struct hci_dev *hdev, struct sk_buff *skb) +{ + evt_conn_complete *cc = (evt_conn_complete *) skb->data; + struct hci_conn *conn = NULL; + + BT_DBG("%s", hdev->name); + + hci_dev_lock(hdev); + + conn = conn_hash_lookup_ba(hdev, cc->link_type, &cc->bdaddr); + if (!conn) { + hci_dev_unlock(hdev); + return; + } + + if (!cc->status) { + conn->handle = __le16_to_cpu(cc->handle); + conn->state = BT_CONNECTED; + + if (test_bit(HCI_AUTH, &hdev->flags)) + conn->link_mode |= HCI_LM_AUTH; + + if (test_bit(HCI_ENCRYPT, &hdev->flags)) + conn->link_mode |= HCI_LM_ENCRYPT; + + + /* Set link policy */ + if (conn->type == ACL_LINK && hdev->link_policy) { + write_link_policy_cp lp; + lp.handle = cc->handle; + lp.policy = __cpu_to_le16(hdev->link_policy); + hci_send_cmd(hdev, OGF_LINK_POLICY, OCF_WRITE_LINK_POLICY, + WRITE_LINK_POLICY_CP_SIZE, &lp); + } + + /* Set packet type for incomming connection */ + if (!conn->out) { + change_conn_ptype_cp cp; + cp.handle = cc->handle; + cp.pkt_type = (conn->type == ACL_LINK) ? + __cpu_to_le16(hdev->pkt_type & ACL_PTYPE_MASK): + __cpu_to_le16(hdev->pkt_type & SCO_PTYPE_MASK); + + hci_send_cmd(hdev, OGF_LINK_CTL, OCF_CHANGE_CONN_PTYPE, + CHANGE_CONN_PTYPE_CP_SIZE, &cp); + } + } else + conn->state = BT_CLOSED; + + if (conn->type == ACL_LINK) { + struct hci_conn *sco = conn->link; + if (sco) { + if (!cc->status) + hci_add_sco(sco, conn->handle); + else { + hci_proto_connect_cfm(sco, cc->status); + hci_conn_del(sco); + } + } + } + + hci_proto_connect_cfm(conn, cc->status); + if (cc->status) + hci_conn_del(conn); + + hci_dev_unlock(hdev); +} + +/* Disconnect Complete */ +static inline void hci_disconn_complete_evt(struct hci_dev *hdev, struct sk_buff *skb) +{ + evt_disconn_complete *dc = (evt_disconn_complete *) skb->data; + struct hci_conn *conn = NULL; + __u16 handle = __le16_to_cpu(dc->handle); + + BT_DBG("%s status %d", hdev->name, dc->status); + + if (dc->status) + return; + + hci_dev_lock(hdev); + + conn = conn_hash_lookup_handle(hdev, handle); + if (conn) { + conn->state = BT_CLOSED; + hci_proto_disconn_ind(conn, dc->reason); + hci_conn_del(conn); + } + + hci_dev_unlock(hdev); +} + +/* Number of completed packets */ +static inline void hci_num_comp_pkts_evt(struct hci_dev *hdev, struct sk_buff *skb) +{ + evt_num_comp_pkts *nc = (evt_num_comp_pkts *) skb->data; + __u16 *ptr; + int i; + + skb_pull(skb, EVT_NUM_COMP_PKTS_SIZE); + + BT_DBG("%s num_hndl %d", hdev->name, nc->num_hndl); + + if (skb->len < nc->num_hndl * 4) { + BT_DBG("%s bad parameters", hdev->name); + return; + } + + tasklet_disable(&hdev->tx_task); + + for (i = 0, ptr = (__u16 *) skb->data; i < nc->num_hndl; i++) { + struct hci_conn *conn; + __u16 handle, count; + + handle = __le16_to_cpu(get_unaligned(ptr++)); + count = __le16_to_cpu(get_unaligned(ptr++)); + + conn = conn_hash_lookup_handle(hdev, handle); + if (conn) { + conn->sent -= count; + + if (conn->type == SCO_LINK) { + if ((hdev->sco_cnt += count) > hdev->sco_pkts) + hdev->sco_cnt = hdev->sco_pkts; + } else { + if ((hdev->acl_cnt += count) > hdev->acl_pkts) + hdev->acl_cnt = hdev->acl_pkts; + } + } + } + hci_sched_tx(hdev); + + tasklet_enable(&hdev->tx_task); +} + +/* Role Change */ +static inline void hci_role_change_evt(struct hci_dev *hdev, struct sk_buff *skb) +{ + evt_role_change *rc = (evt_role_change *) skb->data; + struct hci_conn *conn = NULL; + + BT_DBG("%s status %d", hdev->name, rc->status); + + if (rc->status) + return; + + hci_dev_lock(hdev); + + conn = conn_hash_lookup_ba(hdev, ACL_LINK, &rc->bdaddr); + if (conn) { + if (rc->role) + conn->link_mode &= ~HCI_LM_MASTER; + else + conn->link_mode |= HCI_LM_MASTER; + } + + hci_dev_unlock(hdev); +} + +/* Authentication Complete */ +static inline void hci_auth_complete_evt(struct hci_dev *hdev, struct sk_buff *skb) +{ + evt_auth_complete *ac = (evt_auth_complete *) skb->data; + struct hci_conn *conn = NULL; + __u16 handle = __le16_to_cpu(ac->handle); + + BT_DBG("%s status %d", hdev->name, ac->status); + + hci_dev_lock(hdev); + + conn = conn_hash_lookup_handle(hdev, handle); + if (conn) { + if (!ac->status) + conn->link_mode |= HCI_LM_AUTH; + clear_bit(HCI_CONN_AUTH_PEND, &conn->pend); + + hci_proto_auth_cfm(conn, ac->status); + + if (test_bit(HCI_CONN_ENCRYPT_PEND, &conn->pend)) { + if (!ac->status) { + set_conn_encrypt_cp ce; + ce.handle = __cpu_to_le16(conn->handle); + ce.encrypt = 1; + hci_send_cmd(conn->hdev, OGF_LINK_CTL, + OCF_SET_CONN_ENCRYPT, + SET_CONN_ENCRYPT_CP_SIZE, &ce); + } else { + clear_bit(HCI_CONN_ENCRYPT_PEND, &conn->pend); + hci_proto_encrypt_cfm(conn, ac->status); + } + } + } + + hci_dev_unlock(hdev); +} + +/* Encryption Change */ +static inline void hci_encrypt_change_evt(struct hci_dev *hdev, struct sk_buff *skb) +{ + evt_encrypt_change *ec = (evt_encrypt_change *) skb->data; + struct hci_conn *conn = NULL; + __u16 handle = __le16_to_cpu(ec->handle); + + BT_DBG("%s status %d", hdev->name, ec->status); + + hci_dev_lock(hdev); + + conn = conn_hash_lookup_handle(hdev, handle); + if (conn) { + if (!ec->status) { + if (ec->encrypt) + conn->link_mode |= HCI_LM_ENCRYPT; + else + conn->link_mode &= ~HCI_LM_ENCRYPT; + } + clear_bit(HCI_CONN_ENCRYPT_PEND, &conn->pend); + + hci_proto_encrypt_cfm(conn, ec->status); + } + + hci_dev_unlock(hdev); +} + +void hci_event_packet(struct hci_dev *hdev, struct sk_buff *skb) +{ + hci_event_hdr *he = (hci_event_hdr *) skb->data; + evt_cmd_status *cs; + evt_cmd_complete *ec; + __u16 opcode, ocf, ogf; + + skb_pull(skb, HCI_EVENT_HDR_SIZE); + + BT_DBG("%s evt 0x%x", hdev->name, he->evt); + + switch (he->evt) { + case EVT_NUM_COMP_PKTS: + hci_num_comp_pkts_evt(hdev, skb); + break; + + case EVT_INQUIRY_COMPLETE: + hci_inquiry_complete_evt(hdev, skb); + break; + + case EVT_INQUIRY_RESULT: + hci_inquiry_result_evt(hdev, skb); + break; + + case EVT_INQUIRY_RESULT_WITH_RSSI: + hci_inquiry_result_with_rssi_evt(hdev, skb); + break; + + case EVT_CONN_REQUEST: + hci_conn_request_evt(hdev, skb); + break; + + case EVT_CONN_COMPLETE: + hci_conn_complete_evt(hdev, skb); + break; + + case EVT_DISCONN_COMPLETE: + hci_disconn_complete_evt(hdev, skb); + break; + + case EVT_ROLE_CHANGE: + hci_role_change_evt(hdev, skb); + break; + + case EVT_AUTH_COMPLETE: + hci_auth_complete_evt(hdev, skb); + break; + + case EVT_ENCRYPT_CHANGE: + hci_encrypt_change_evt(hdev, skb); + break; + + case EVT_CMD_STATUS: + cs = (evt_cmd_status *) skb->data; + skb_pull(skb, EVT_CMD_STATUS_SIZE); + + opcode = __le16_to_cpu(cs->opcode); + ogf = cmd_opcode_ogf(opcode); + ocf = cmd_opcode_ocf(opcode); + + switch (ogf) { + case OGF_INFO_PARAM: + hci_cs_info_param(hdev, ocf, cs->status); + break; + + case OGF_HOST_CTL: + hci_cs_host_ctl(hdev, ocf, cs->status); + break; + + case OGF_LINK_CTL: + hci_cs_link_ctl(hdev, ocf, cs->status); + break; + + case OGF_LINK_POLICY: + hci_cs_link_policy(hdev, ocf, cs->status); + break; + + default: + BT_DBG("%s Command Status OGF %x", hdev->name, ogf); + break; + }; + + if (cs->ncmd) { + atomic_set(&hdev->cmd_cnt, 1); + if (!skb_queue_empty(&hdev->cmd_q)) + hci_sched_cmd(hdev); + } + break; + + case EVT_CMD_COMPLETE: + ec = (evt_cmd_complete *) skb->data; + skb_pull(skb, EVT_CMD_COMPLETE_SIZE); + + opcode = __le16_to_cpu(ec->opcode); + ogf = cmd_opcode_ogf(opcode); + ocf = cmd_opcode_ocf(opcode); + + switch (ogf) { + case OGF_INFO_PARAM: + hci_cc_info_param(hdev, ocf, skb); + break; + + case OGF_HOST_CTL: + hci_cc_host_ctl(hdev, ocf, skb); + break; + + case OGF_LINK_CTL: + hci_cc_link_ctl(hdev, ocf, skb); + break; + + case OGF_LINK_POLICY: + hci_cc_link_policy(hdev, ocf, skb); + break; + + default: + BT_DBG("%s Command Completed OGF %x", hdev->name, ogf); + break; + }; + + if (ec->ncmd) { + atomic_set(&hdev->cmd_cnt, 1); + if (!skb_queue_empty(&hdev->cmd_q)) + hci_sched_cmd(hdev); + } + break; + }; + + kfree_skb(skb); + hdev->stat.evt_rx++; +} + +/* General internal stack event */ +void hci_si_event(struct hci_dev *hdev, int type, int dlen, void *data) +{ + hci_event_hdr *eh; + evt_stack_internal *si; + struct sk_buff *skb; + int size; + void *ptr; + + size = HCI_EVENT_HDR_SIZE + EVT_STACK_INTERNAL_SIZE + dlen; + skb = bluez_skb_alloc(size, GFP_ATOMIC); + if (!skb) + return; + + ptr = skb_put(skb, size); + + eh = ptr; + eh->evt = EVT_STACK_INTERNAL; + eh->plen = EVT_STACK_INTERNAL_SIZE + dlen; + ptr += HCI_EVENT_HDR_SIZE; + + si = ptr; + si->type = type; + memcpy(si->data, data, dlen); + + skb->pkt_type = HCI_EVENT_PKT; + skb->dev = (void *) hdev; + hci_send_to_sock(hdev, skb); + kfree_skb(skb); +} --- linux/net/bluetooth/hci_sock.c~bluetooth-2.4.18-mh11 2001-09-07 18:28:38.000000000 +0200 +++ linux/net/bluetooth/hci_sock.c 2004-01-25 23:37:39.000000000 +0100 @@ -25,7 +25,7 @@ /* * BlueZ HCI socket layer. * - * $Id: hci_sock.c,v 1.9 2001/08/05 06:02:16 maxk Exp $ + * $Id: hci_sock.c,v 1.5 2002/07/22 20:32:54 maxk Exp $ */ #include @@ -49,45 +49,54 @@ #include #include +#include #include -#include #include #ifndef HCI_SOCK_DEBUG -#undef DBG -#define DBG( A... ) +#undef BT_DBG +#define BT_DBG( A... ) #endif -/* HCI socket interface */ +/* ----- HCI socket interface ----- */ + +/* Security filter */ +static struct hci_sec_filter hci_sec_filter = { + /* Packet types */ + 0x10, + /* Events */ + { 0x1000d9fe, 0x0000300c }, + /* Commands */ + { + { 0x0 }, + /* OGF_LINK_CTL */ + { 0xbe000006, 0x00000001, 0x0000, 0x00 }, + /* OGF_LINK_POLICY */ + { 0x00005200, 0x00000000, 0x0000, 0x00 }, + /* OGF_HOST_CTL */ + { 0xaab00200, 0x2b402aaa, 0x0154, 0x00 }, + /* OGF_INFO_PARAM */ + { 0x000002be, 0x00000000, 0x0000, 0x00 }, + /* OGF_STATUS_PARAM */ + { 0x000000ea, 0x00000000, 0x0000, 0x00 } + } +}; static struct bluez_sock_list hci_sk_list = { lock: RW_LOCK_UNLOCKED }; -static struct sock *hci_sock_lookup(struct hci_dev *hdev) -{ - struct sock *sk; - - read_lock(&hci_sk_list.lock); - for (sk = hci_sk_list.head; sk; sk = sk->next) { - if (hci_pi(sk)->hdev == hdev) - break; - } - read_unlock(&hci_sk_list.lock); - return sk; -} - /* Send frame to RAW socket */ void hci_send_to_sock(struct hci_dev *hdev, struct sk_buff *skb) { struct sock * sk; - DBG("hdev %p len %d", hdev, skb->len); + BT_DBG("hdev %p len %d", hdev, skb->len); read_lock(&hci_sk_list.lock); for (sk = hci_sk_list.head; sk; sk = sk->next) { - struct hci_filter *flt; + struct hci_filter *flt; struct sk_buff *nskb; if (sk->state != BT_BOUND || hci_pi(sk)->hdev != hdev) @@ -100,13 +109,19 @@ /* Apply filter */ flt = &hci_pi(sk)->filter; - if (!test_bit(skb->pkt_type, &flt->type_mask)) + if (!hci_test_bit((skb->pkt_type & HCI_FLT_TYPE_BITS), &flt->type_mask)) continue; if (skb->pkt_type == HCI_EVENT_PKT) { - register int evt = (*(__u8 *)skb->data & 63); + register int evt = (*(__u8 *)skb->data & HCI_FLT_EVENT_BITS); + + if (!hci_test_bit(evt, &flt->event_mask)) + continue; - if (!test_bit(evt, &flt->event_mask)) + if (flt->opcode && ((evt == EVT_CMD_COMPLETE && + flt->opcode != *(__u16 *)(skb->data + 3)) || + (evt == EVT_CMD_STATUS && + flt->opcode != *(__u16 *)(skb->data + 4)))) continue; } @@ -116,8 +131,8 @@ /* Put type byte before the data */ memcpy(skb_push(nskb, 1), &nskb->pkt_type, 1); - skb_queue_tail(&sk->receive_queue, nskb); - sk->data_ready(sk, nskb->len); + if (sock_queue_rcv_skb(sk, nskb)) + kfree_skb(nskb); } read_unlock(&hci_sk_list.lock); } @@ -127,7 +142,7 @@ struct sock *sk = sock->sk; struct hci_dev *hdev = hci_pi(sk)->hdev; - DBG("sock %p sk %p", sock, sk); + BT_DBG("sock %p sk %p", sock, sk); if (!sk) return 0; @@ -135,9 +150,7 @@ bluez_sock_unlink(&hci_sk_list, sk); if (hdev) { - if (!hci_sock_lookup(hdev)) - hdev->flags &= ~HCI_SOCK; - + atomic_dec(&hdev->promisc); hci_dev_put(hdev); } @@ -149,24 +162,55 @@ sock_put(sk); MOD_DEC_USE_COUNT; - return 0; } -static int hci_sock_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg) +/* Ioctls that require bound socket */ +static inline int hci_sock_bound_ioctl(struct sock *sk, unsigned int cmd, unsigned long arg) { - struct sock *sk = sock->sk; struct hci_dev *hdev = hci_pi(sk)->hdev; - __u32 mode; - DBG("cmd %x arg %lx", cmd, arg); + if (!hdev) + return -EBADFD; switch (cmd) { - case HCIGETINFO: - return hci_dev_info(arg); + case HCISETRAW: + if (!capable(CAP_NET_ADMIN)) + return -EACCES; + + if (arg) + set_bit(HCI_RAW, &hdev->flags); + else + clear_bit(HCI_RAW, &hdev->flags); + + return 0; + + case HCIGETCONNINFO: + return hci_get_conn_info(hdev, arg); + default: + if (hdev->ioctl) + return hdev->ioctl(hdev, cmd, arg); + return -EINVAL; + } +} + +static int hci_sock_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg) +{ + struct sock *sk = sock->sk; + int err; + + BT_DBG("cmd %x arg %lx", cmd, arg); + + switch (cmd) { case HCIGETDEVLIST: - return hci_dev_list(arg); + return hci_get_dev_list(arg); + + case HCIGETDEVINFO: + return hci_get_dev_info(arg); + + case HCIGETCONNLIST: + return hci_get_conn_list(arg); case HCIDEVUP: if (!capable(CAP_NET_ADMIN)) @@ -183,48 +227,31 @@ return -EACCES; return hci_dev_reset(arg); - case HCIRESETSTAT: + case HCIDEVRESTAT: if (!capable(CAP_NET_ADMIN)) return -EACCES; return hci_dev_reset_stat(arg); case HCISETSCAN: - if (!capable(CAP_NET_ADMIN)) - return -EACCES; - return hci_dev_setscan(arg); - case HCISETAUTH: - if (!capable(CAP_NET_ADMIN)) - return -EACCES; - return hci_dev_setauth(arg); - - case HCISETRAW: - if (!capable(CAP_NET_ADMIN)) - return -EACCES; - - if (!hdev) - return -EBADFD; - - if (arg) - mode = HCI_RAW; - else - mode = HCI_NORMAL; - - return hci_dev_setmode(hdev, mode); - + case HCISETENCRYPT: case HCISETPTYPE: + case HCISETLINKPOL: + case HCISETLINKMODE: + case HCISETACLMTU: + case HCISETSCOMTU: if (!capable(CAP_NET_ADMIN)) return -EACCES; - return hci_dev_setptype(arg); + return hci_dev_cmd(cmd, arg); case HCIINQUIRY: return hci_inquiry(arg); - case HCIGETCONNLIST: - return hci_conn_list(arg); - default: - return -EINVAL; + lock_sock(sk); + err = hci_sock_bound_ioctl(sk, cmd, arg); + release_sock(sk); + return err; }; } @@ -233,28 +260,35 @@ struct sockaddr_hci *haddr = (struct sockaddr_hci *) addr; struct sock *sk = sock->sk; struct hci_dev *hdev = NULL; + int err = 0; - DBG("sock %p sk %p", sock, sk); + BT_DBG("sock %p sk %p", sock, sk); if (!haddr || haddr->hci_family != AF_BLUETOOTH) return -EINVAL; + lock_sock(sk); + if (hci_pi(sk)->hdev) { - /* Already bound */ - return 0; + err = -EALREADY; + goto done; } if (haddr->hci_dev != HCI_DEV_NONE) { - if (!(hdev = hci_dev_get(haddr->hci_dev))) - return -ENODEV; + if (!(hdev = hci_dev_get(haddr->hci_dev))) { + err = -ENODEV; + goto done; + } - hdev->flags |= HCI_SOCK; + atomic_inc(&hdev->promisc); } hci_pi(sk)->hdev = hdev; sk->state = BT_BOUND; - return 0; +done: + release_sock(sk); + return err; } static int hci_sock_getname(struct socket *sock, struct sockaddr *addr, int *addr_len, int peer) @@ -262,73 +296,44 @@ struct sockaddr_hci *haddr = (struct sockaddr_hci *) addr; struct sock *sk = sock->sk; - DBG("sock %p sk %p", sock, sk); + BT_DBG("sock %p sk %p", sock, sk); + + lock_sock(sk); *addr_len = sizeof(*haddr); haddr->hci_family = AF_BLUETOOTH; haddr->hci_dev = hci_pi(sk)->hdev->id; + release_sock(sk); return 0; } -static int hci_sock_sendmsg(struct socket *sock, struct msghdr *msg, int len, - struct scm_cookie *scm) -{ - struct sock *sk = sock->sk; - struct hci_dev *hdev = hci_pi(sk)->hdev; - struct sk_buff *skb; - int err; - - DBG("sock %p sk %p", sock, sk); - - if (msg->msg_flags & MSG_OOB) - return -EOPNOTSUPP; - - if (msg->msg_flags & ~(MSG_DONTWAIT|MSG_NOSIGNAL|MSG_ERRQUEUE)) - return -EINVAL; - - if (!hdev) - return -EBADFD; - - if (!(skb = bluez_skb_send_alloc(sk, len, msg->msg_flags & MSG_DONTWAIT, &err))) - return err; - - if (memcpy_fromiovec(skb_put(skb, len), msg->msg_iov, len)) { - kfree_skb(skb); - return -EFAULT; - } - - skb->dev = (void *) hdev; - skb->pkt_type = *((unsigned char *) skb->data); - skb_pull(skb, 1); - - /* Send frame to HCI core */ - hci_send_raw(skb); - - return len; -} - static inline void hci_sock_cmsg(struct sock *sk, struct msghdr *msg, struct sk_buff *skb) { __u32 mask = hci_pi(sk)->cmsg_mask; if (mask & HCI_CMSG_DIR) put_cmsg(msg, SOL_HCI, HCI_CMSG_DIR, sizeof(int), &bluez_cb(skb)->incomming); + + if (mask & HCI_CMSG_TSTAMP) + put_cmsg(msg, SOL_HCI, HCI_CMSG_TSTAMP, sizeof(skb->stamp), &skb->stamp); } -static int hci_sock_recvmsg(struct socket *sock, struct msghdr *msg, int len, - int flags, struct scm_cookie *scm) +static int hci_sock_recvmsg(struct socket *sock, struct msghdr *msg, int len, int flags, struct scm_cookie *scm) { int noblock = flags & MSG_DONTWAIT; struct sock *sk = sock->sk; struct sk_buff *skb; int copied, err; - DBG("sock %p sk %p", sock, sk); + BT_DBG("sock %p, sk %p", sock, sk); - if (flags & (MSG_OOB | MSG_PEEK)) + if (flags & (MSG_OOB)) return -EOPNOTSUPP; + if (sk->state == BT_CLOSED) + return 0; + if (!(skb = skb_recv_datagram(sk, flags, noblock, &err))) return err; @@ -343,28 +348,107 @@ skb->h.raw = skb->data; err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied); - if (hci_pi(sk)->cmsg_mask) - hci_sock_cmsg(sk, msg, skb); - + hci_sock_cmsg(sk, msg, skb); + skb_free_datagram(sk, skb); return err ? : copied; } +static int hci_sock_sendmsg(struct socket *sock, struct msghdr *msg, int len, + struct scm_cookie *scm) +{ + struct sock *sk = sock->sk; + struct hci_dev *hdev; + struct sk_buff *skb; + int err; + + BT_DBG("sock %p sk %p", sock, sk); + + if (msg->msg_flags & MSG_OOB) + return -EOPNOTSUPP; + + if (msg->msg_flags & ~(MSG_DONTWAIT|MSG_NOSIGNAL|MSG_ERRQUEUE)) + return -EINVAL; + + if (len < 4) + return -EINVAL; + + lock_sock(sk); + + if (!(hdev = hci_pi(sk)->hdev)) { + err = -EBADFD; + goto done; + } + + if (!(skb = bluez_skb_send_alloc(sk, len, msg->msg_flags & MSG_DONTWAIT, &err))) + goto done; + + if (memcpy_fromiovec(skb_put(skb, len), msg->msg_iov, len)) { + err = -EFAULT; + goto drop; + } + + skb->pkt_type = *((unsigned char *) skb->data); + skb_pull(skb, 1); + skb->dev = (void *) hdev; + + if (skb->pkt_type == HCI_COMMAND_PKT) { + u16 opcode = __le16_to_cpu(get_unaligned((u16 *)skb->data)); + u16 ogf = cmd_opcode_ogf(opcode); + u16 ocf = cmd_opcode_ocf(opcode); + + if (((ogf > HCI_SFLT_MAX_OGF) || + !hci_test_bit(ocf & HCI_FLT_OCF_BITS, &hci_sec_filter.ocf_mask[ogf])) && + !capable(CAP_NET_RAW)) { + err = -EPERM; + goto drop; + } + + if (test_bit(HCI_RAW, &hdev->flags) || (ogf == OGF_VENDOR_CMD)) { + skb_queue_tail(&hdev->raw_q, skb); + hci_sched_tx(hdev); + } else { + skb_queue_tail(&hdev->cmd_q, skb); + hci_sched_cmd(hdev); + } + } else { + if (!capable(CAP_NET_RAW)) { + err = -EPERM; + goto drop; + } + + skb_queue_tail(&hdev->raw_q, skb); + hci_sched_tx(hdev); + } + + err = len; + +done: + release_sock(sk); + return err; + +drop: + kfree_skb(skb); + goto done; +} + int hci_sock_setsockopt(struct socket *sock, int level, int optname, char *optval, int len) { struct sock *sk = sock->sk; - struct hci_filter flt; + struct hci_filter flt = { opcode: 0 }; int err = 0, opt = 0; - DBG("sk %p, opt %d", sk, optname); + BT_DBG("sk %p, opt %d", sk, optname); lock_sock(sk); switch (optname) { case HCI_DATA_DIR: - if (get_user(opt, (int *)optval)) - return -EFAULT; + if (get_user(opt, (int *)optval)) { + err = -EFAULT; + break; + } if (opt) hci_pi(sk)->cmsg_mask |= HCI_CMSG_DIR; @@ -372,12 +456,31 @@ hci_pi(sk)->cmsg_mask &= ~HCI_CMSG_DIR; break; + case HCI_TIME_STAMP: + if (get_user(opt, (int *)optval)) { + err = -EFAULT; + break; + } + + if (opt) + hci_pi(sk)->cmsg_mask |= HCI_CMSG_TSTAMP; + else + hci_pi(sk)->cmsg_mask &= ~HCI_CMSG_TSTAMP; + break; + case HCI_FILTER: len = MIN(len, sizeof(struct hci_filter)); if (copy_from_user(&flt, optval, len)) { err = -EFAULT; break; } + + if (!capable(CAP_NET_RAW)) { + flt.type_mask &= hci_sec_filter.type_mask; + flt.event_mask[0] &= hci_sec_filter.event_mask[0]; + flt.event_mask[1] &= hci_sec_filter.event_mask[1]; + } + memcpy(&hci_pi(sk)->filter, &flt, len); break; @@ -409,6 +512,16 @@ return -EFAULT; break; + case HCI_TIME_STAMP: + if (hci_pi(sk)->cmsg_mask & HCI_CMSG_TSTAMP) + opt = 1; + else + opt = 0; + + if (put_user(opt, optval)) + return -EFAULT; + break; + case HCI_FILTER: len = MIN(len, sizeof(struct hci_filter)); if (copy_to_user(optval, &hci_pi(sk)->filter, len)) @@ -446,7 +559,7 @@ { struct sock *sk; - DBG("sock %p", sock); + BT_DBG("sock %p", sock); if (sock->type != SOCK_RAW) return -ESOCKTNOSUPPORT; @@ -464,44 +577,31 @@ sk->protocol = protocol; sk->state = BT_OPEN; - /* Initialize filter */ - hci_pi(sk)->filter.type_mask = (1<filter.event_mask[0] = ~0L; - hci_pi(sk)->filter.event_mask[1] = ~0L; - bluez_sock_link(&hci_sk_list, sk); MOD_INC_USE_COUNT; - return 0; } static int hci_sock_dev_event(struct notifier_block *this, unsigned long event, void *ptr) { struct hci_dev *hdev = (struct hci_dev *) ptr; - struct sk_buff *skb; - - DBG("hdev %s event %ld", hdev->name, event); + evt_si_device sd; + + BT_DBG("hdev %s event %ld", hdev->name, event); /* Send event to sockets */ - if ((skb = bluez_skb_alloc(HCI_EVENT_HDR_SIZE + EVT_HCI_DEV_EVENT_SIZE, GFP_ATOMIC))) { - hci_event_hdr eh = { EVT_HCI_DEV_EVENT, EVT_HCI_DEV_EVENT_SIZE }; - evt_hci_dev_event he = { event, hdev->id }; - - skb->pkt_type = HCI_EVENT_PKT; - memcpy(skb_put(skb, HCI_EVENT_HDR_SIZE), &eh, HCI_EVENT_HDR_SIZE); - memcpy(skb_put(skb, EVT_HCI_DEV_EVENT_SIZE), &he, EVT_HCI_DEV_EVENT_SIZE); - - hci_send_to_sock(NULL, skb); - kfree_skb(skb); - } - + sd.event = event; + sd.dev_id = hdev->id; + hci_si_event(NULL, EVT_SI_DEVICE, EVT_SI_DEVICE_SIZE, &sd); + if (event == HCI_DEV_UNREG) { struct sock *sk; /* Detach sockets from device */ read_lock(&hci_sk_list.lock); for (sk = hci_sk_list.head; sk; sk = sk->next) { + bh_lock_sock(sk); if (hci_pi(sk)->hdev == hdev) { hci_pi(sk)->hdev = NULL; sk->err = EPIPE; @@ -510,6 +610,7 @@ hci_dev_put(hdev); } + bh_unlock_sock(sk); } read_unlock(&hci_sk_list.lock); } @@ -529,21 +630,19 @@ int hci_sock_init(void) { if (bluez_sock_register(BTPROTO_HCI, &hci_sock_family_ops)) { - ERR("Can't register HCI socket"); + BT_ERR("Can't register HCI socket"); return -EPROTO; } hci_register_notifier(&hci_sock_nblock); - return 0; } int hci_sock_cleanup(void) { if (bluez_sock_unregister(BTPROTO_HCI)) - ERR("Can't unregister HCI socket"); + BT_ERR("Can't unregister HCI socket"); hci_unregister_notifier(&hci_sock_nblock); - return 0; } --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/net/bluetooth/l2cap.c 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1,2187 @@ +/* + BlueZ - Bluetooth protocol stack for Linux + Copyright (C) 2000-2001 Qualcomm Incorporated + + Written 2000,2001 by Maxim Krasnyansky + + 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. +*/ + +/* + * BlueZ L2CAP core and sockets. + * + * $Id: l2cap.c,v 1.15 2002/09/09 01:14:52 maxk Exp $ + */ +#define VERSION "2.3" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#ifndef L2CAP_DEBUG +#undef BT_DBG +#define BT_DBG( A... ) +#endif + +static struct proto_ops l2cap_sock_ops; + +struct bluez_sock_list l2cap_sk_list = { + lock: RW_LOCK_UNLOCKED +}; + +static int l2cap_conn_del(struct hci_conn *conn, int err); + +static inline void l2cap_chan_add(struct l2cap_conn *conn, struct sock *sk, struct sock *parent); +static void l2cap_chan_del(struct sock *sk, int err); +static int l2cap_chan_send(struct sock *sk, struct msghdr *msg, int len); + +static void __l2cap_sock_close(struct sock *sk, int reason); +static void l2cap_sock_close(struct sock *sk); +static void l2cap_sock_kill(struct sock *sk); + +static int l2cap_send_req(struct l2cap_conn *conn, __u8 code, __u16 len, void *data); +static int l2cap_send_rsp(struct l2cap_conn *conn, __u8 ident, __u8 code, __u16 len, void *data); + +/* ----- L2CAP timers ------ */ +static void l2cap_sock_timeout(unsigned long arg) +{ + struct sock *sk = (struct sock *) arg; + + BT_DBG("sock %p state %d", sk, sk->state); + + bh_lock_sock(sk); + __l2cap_sock_close(sk, ETIMEDOUT); + bh_unlock_sock(sk); + + l2cap_sock_kill(sk); + sock_put(sk); +} + +static void l2cap_sock_set_timer(struct sock *sk, long timeout) +{ + BT_DBG("sk %p state %d timeout %ld", sk, sk->state, timeout); + + if (!mod_timer(&sk->timer, jiffies + timeout)) + sock_hold(sk); +} + +static void l2cap_sock_clear_timer(struct sock *sk) +{ + BT_DBG("sock %p state %d", sk, sk->state); + + if (timer_pending(&sk->timer) && del_timer(&sk->timer)) + __sock_put(sk); +} + +static void l2cap_sock_init_timer(struct sock *sk) +{ + init_timer(&sk->timer); + sk->timer.function = l2cap_sock_timeout; + sk->timer.data = (unsigned long)sk; +} + +/* -------- L2CAP connections --------- */ +static struct l2cap_conn *l2cap_conn_add(struct hci_conn *hcon, __u8 status) +{ + struct l2cap_conn *conn; + + if ((conn = hcon->l2cap_data)) + return conn; + + if (status) + return conn; + + if (!(conn = kmalloc(sizeof(struct l2cap_conn), GFP_ATOMIC))) + return NULL; + memset(conn, 0, sizeof(struct l2cap_conn)); + + hcon->l2cap_data = conn; + conn->hcon = hcon; + + conn->mtu = hcon->hdev->acl_mtu; + conn->src = &hcon->hdev->bdaddr; + conn->dst = &hcon->dst; + + spin_lock_init(&conn->lock); + conn->chan_list.lock = RW_LOCK_UNLOCKED; + + BT_DBG("hcon %p conn %p", hcon, conn); + + MOD_INC_USE_COUNT; + return conn; +} + +static int l2cap_conn_del(struct hci_conn *hcon, int err) +{ + struct l2cap_conn *conn; + struct sock *sk; + + if (!(conn = hcon->l2cap_data)) + return 0; + + BT_DBG("hcon %p conn %p, err %d", hcon, conn, err); + + if (conn->rx_skb) + kfree_skb(conn->rx_skb); + + /* Kill channels */ + while ((sk = conn->chan_list.head)) { + bh_lock_sock(sk); + l2cap_chan_del(sk, err); + bh_unlock_sock(sk); + l2cap_sock_kill(sk); + } + + hcon->l2cap_data = NULL; + kfree(conn); + + MOD_DEC_USE_COUNT; + return 0; +} + +/* -------- Socket interface ---------- */ +static struct sock *__l2cap_get_sock_by_addr(__u16 psm, bdaddr_t *src) +{ + struct sock *sk; + for (sk = l2cap_sk_list.head; sk; sk = sk->next) { + if (sk->sport == psm && !bacmp(&bluez_pi(sk)->src, src)) + break; + } + return sk; +} + +/* Find socket with psm and source bdaddr. + * Returns closest match. + */ +static struct sock *__l2cap_get_sock_by_psm(int state, __u16 psm, bdaddr_t *src) +{ + struct sock *sk, *sk1 = NULL; + + for (sk = l2cap_sk_list.head; sk; sk = sk->next) { + if (state && sk->state != state) + continue; + + if (l2cap_pi(sk)->psm == psm) { + /* Exact match. */ + if (!bacmp(&bluez_pi(sk)->src, src)) + break; + + /* Closest match */ + if (!bacmp(&bluez_pi(sk)->src, BDADDR_ANY)) + sk1 = sk; + } + } + return sk ? sk : sk1; +} + +/* Find socket with given address (psm, src). + * Returns locked socket */ +static inline struct sock *l2cap_get_sock_by_psm(int state, __u16 psm, bdaddr_t *src) +{ + struct sock *s; + read_lock(&l2cap_sk_list.lock); + s = __l2cap_get_sock_by_psm(state, psm, src); + if (s) bh_lock_sock(s); + read_unlock(&l2cap_sk_list.lock); + return s; +} + +static void l2cap_sock_destruct(struct sock *sk) +{ + BT_DBG("sk %p", sk); + + skb_queue_purge(&sk->receive_queue); + skb_queue_purge(&sk->write_queue); + + MOD_DEC_USE_COUNT; +} + +static void l2cap_sock_cleanup_listen(struct sock *parent) +{ + struct sock *sk; + + BT_DBG("parent %p", parent); + + /* Close not yet accepted channels */ + while ((sk = bluez_accept_dequeue(parent, NULL))) + l2cap_sock_close(sk); + + parent->state = BT_CLOSED; + parent->zapped = 1; +} + +/* Kill socket (only if zapped and orphan) + * Must be called on unlocked socket. + */ +static void l2cap_sock_kill(struct sock *sk) +{ + if (!sk->zapped || sk->socket) + return; + + BT_DBG("sk %p state %d", sk, sk->state); + + /* Kill poor orphan */ + bluez_sock_unlink(&l2cap_sk_list, sk); + sk->dead = 1; + sock_put(sk); +} + +/* Close socket. + */ +static void __l2cap_sock_close(struct sock *sk, int reason) +{ + BT_DBG("sk %p state %d socket %p", sk, sk->state, sk->socket); + + switch (sk->state) { + case BT_LISTEN: + l2cap_sock_cleanup_listen(sk); + break; + + case BT_CONNECTED: + case BT_CONFIG: + case BT_CONNECT2: + if (sk->type == SOCK_SEQPACKET) { + struct l2cap_conn *conn = l2cap_pi(sk)->conn; + l2cap_disconn_req req; + + sk->state = BT_DISCONN; + l2cap_sock_set_timer(sk, sk->sndtimeo); + + req.dcid = __cpu_to_le16(l2cap_pi(sk)->dcid); + req.scid = __cpu_to_le16(l2cap_pi(sk)->scid); + l2cap_send_req(conn, L2CAP_DISCONN_REQ, L2CAP_DISCONN_REQ_SIZE, &req); + } else { + l2cap_chan_del(sk, reason); + } + break; + + case BT_CONNECT: + case BT_DISCONN: + l2cap_chan_del(sk, reason); + break; + + default: + sk->zapped = 1; + break; + }; +} + +/* Must be called on unlocked socket. */ +static void l2cap_sock_close(struct sock *sk) +{ + l2cap_sock_clear_timer(sk); + lock_sock(sk); + __l2cap_sock_close(sk, ECONNRESET); + release_sock(sk); + l2cap_sock_kill(sk); +} + +static void l2cap_sock_init(struct sock *sk, struct sock *parent) +{ + struct l2cap_pinfo *pi = l2cap_pi(sk); + + BT_DBG("sk %p", sk); + + if (parent) { + sk->type = parent->type; + pi->imtu = l2cap_pi(parent)->imtu; + pi->omtu = l2cap_pi(parent)->omtu; + pi->link_mode = l2cap_pi(parent)->link_mode; + } else { + pi->imtu = L2CAP_DEFAULT_MTU; + pi->omtu = 0; + pi->link_mode = 0; + } + + /* Default config options */ + pi->conf_mtu = L2CAP_DEFAULT_MTU; + pi->flush_to = L2CAP_DEFAULT_FLUSH_TO; +} + +static struct sock *l2cap_sock_alloc(struct socket *sock, int proto, int prio) +{ + struct sock *sk; + + if (!(sk = sk_alloc(PF_BLUETOOTH, prio, 1))) + return NULL; + + bluez_sock_init(sock, sk); + + sk->zapped = 0; + + sk->destruct = l2cap_sock_destruct; + sk->sndtimeo = L2CAP_CONN_TIMEOUT; + + sk->protocol = proto; + sk->state = BT_OPEN; + + l2cap_sock_init_timer(sk); + + bluez_sock_link(&l2cap_sk_list, sk); + + MOD_INC_USE_COUNT; + return sk; +} + +static int l2cap_sock_create(struct socket *sock, int protocol) +{ + struct sock *sk; + + BT_DBG("sock %p", sock); + + sock->state = SS_UNCONNECTED; + + if (sock->type != SOCK_SEQPACKET && sock->type != SOCK_DGRAM && sock->type != SOCK_RAW) + return -ESOCKTNOSUPPORT; + + if (sock->type == SOCK_RAW && !capable(CAP_NET_RAW)) + return -EPERM; + + sock->ops = &l2cap_sock_ops; + + if (!(sk = l2cap_sock_alloc(sock, protocol, GFP_KERNEL))) + return -ENOMEM; + + l2cap_sock_init(sk, NULL); + return 0; +} + +static int l2cap_sock_bind(struct socket *sock, struct sockaddr *addr, int addr_len) +{ + struct sockaddr_l2 *la = (struct sockaddr_l2 *) addr; + struct sock *sk = sock->sk; + int err = 0; + + BT_DBG("sk %p, %s %d", sk, batostr(&la->l2_bdaddr), la->l2_psm); + + if (!addr || addr->sa_family != AF_BLUETOOTH) + return -EINVAL; + + lock_sock(sk); + + if (sk->state != BT_OPEN) { + err = -EBADFD; + goto done; + } + + write_lock_bh(&l2cap_sk_list.lock); + if (la->l2_psm && __l2cap_get_sock_by_addr(la->l2_psm, &la->l2_bdaddr)) { + err = -EADDRINUSE; + } else { + /* Save source address */ + bacpy(&bluez_pi(sk)->src, &la->l2_bdaddr); + l2cap_pi(sk)->psm = la->l2_psm; + sk->sport = la->l2_psm; + sk->state = BT_BOUND; + } + write_unlock_bh(&l2cap_sk_list.lock); + +done: + release_sock(sk); + return err; +} + +static int l2cap_do_connect(struct sock *sk) +{ + bdaddr_t *src = &bluez_pi(sk)->src; + bdaddr_t *dst = &bluez_pi(sk)->dst; + struct l2cap_conn *conn; + struct hci_conn *hcon; + struct hci_dev *hdev; + int err = 0; + + BT_DBG("%s -> %s psm 0x%2.2x", batostr(src), batostr(dst), l2cap_pi(sk)->psm); + + if (!(hdev = hci_get_route(dst, src))) + return -EHOSTUNREACH; + + hci_dev_lock_bh(hdev); + + err = -ENOMEM; + + hcon = hci_connect(hdev, ACL_LINK, dst); + if (!hcon) + goto done; + + conn = l2cap_conn_add(hcon, 0); + if (!conn) { + hci_conn_put(hcon); + goto done; + } + + err = 0; + + /* Update source addr of the socket */ + bacpy(src, conn->src); + + l2cap_chan_add(conn, sk, NULL); + + sk->state = BT_CONNECT; + l2cap_sock_set_timer(sk, sk->sndtimeo); + + if (hcon->state == BT_CONNECTED) { + if (sk->type == SOCK_SEQPACKET) { + l2cap_conn_req req; + req.scid = __cpu_to_le16(l2cap_pi(sk)->scid); + req.psm = l2cap_pi(sk)->psm; + l2cap_send_req(conn, L2CAP_CONN_REQ, L2CAP_CONN_REQ_SIZE, &req); + } else { + l2cap_sock_clear_timer(sk); + sk->state = BT_CONNECTED; + } + } + +done: + hci_dev_unlock_bh(hdev); + hci_dev_put(hdev); + return err; +} + +static int l2cap_sock_connect(struct socket *sock, struct sockaddr *addr, int alen, int flags) +{ + struct sockaddr_l2 *la = (struct sockaddr_l2 *) addr; + struct sock *sk = sock->sk; + int err = 0; + + lock_sock(sk); + + BT_DBG("sk %p", sk); + + if (addr->sa_family != AF_BLUETOOTH || alen < sizeof(struct sockaddr_l2)) { + err = -EINVAL; + goto done; + } + + if (sk->type == SOCK_SEQPACKET && !la->l2_psm) { + err = -EINVAL; + goto done; + } + + switch(sk->state) { + case BT_CONNECT: + case BT_CONNECT2: + case BT_CONFIG: + /* Already connecting */ + goto wait; + + case BT_CONNECTED: + /* Already connected */ + goto done; + + case BT_OPEN: + case BT_BOUND: + /* Can connect */ + break; + + default: + err = -EBADFD; + goto done; + } + + /* Set destination address and psm */ + bacpy(&bluez_pi(sk)->dst, &la->l2_bdaddr); + l2cap_pi(sk)->psm = la->l2_psm; + + if ((err = l2cap_do_connect(sk))) + goto done; + +wait: + err = bluez_sock_wait_state(sk, BT_CONNECTED, + sock_sndtimeo(sk, flags & O_NONBLOCK)); + +done: + release_sock(sk); + return err; +} + +int l2cap_sock_listen(struct socket *sock, int backlog) +{ + struct sock *sk = sock->sk; + int err = 0; + + BT_DBG("sk %p backlog %d", sk, backlog); + + lock_sock(sk); + + if (sk->state != BT_BOUND || sock->type != SOCK_SEQPACKET) { + err = -EBADFD; + goto done; + } + + if (!l2cap_pi(sk)->psm) { + err = -EINVAL; + goto done; + } + + sk->max_ack_backlog = backlog; + sk->ack_backlog = 0; + sk->state = BT_LISTEN; + +done: + release_sock(sk); + return err; +} + +int l2cap_sock_accept(struct socket *sock, struct socket *newsock, int flags) +{ + DECLARE_WAITQUEUE(wait, current); + struct sock *sk = sock->sk, *nsk; + long timeo; + int err = 0; + + lock_sock(sk); + + if (sk->state != BT_LISTEN) { + err = -EBADFD; + goto done; + } + + timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK); + + BT_DBG("sk %p timeo %ld", sk, timeo); + + /* Wait for an incoming connection. (wake-one). */ + add_wait_queue_exclusive(sk->sleep, &wait); + while (!(nsk = bluez_accept_dequeue(sk, newsock))) { + set_current_state(TASK_INTERRUPTIBLE); + if (!timeo) { + err = -EAGAIN; + break; + } + + release_sock(sk); + timeo = schedule_timeout(timeo); + lock_sock(sk); + + if (sk->state != BT_LISTEN) { + err = -EBADFD; + break; + } + + if (signal_pending(current)) { + err = sock_intr_errno(timeo); + break; + } + } + set_current_state(TASK_RUNNING); + remove_wait_queue(sk->sleep, &wait); + + if (err) + goto done; + + newsock->state = SS_CONNECTED; + + BT_DBG("new socket %p", nsk); + +done: + release_sock(sk); + return err; +} + +static int l2cap_sock_getname(struct socket *sock, struct sockaddr *addr, int *len, int peer) +{ + struct sockaddr_l2 *la = (struct sockaddr_l2 *) addr; + struct sock *sk = sock->sk; + + BT_DBG("sock %p, sk %p", sock, sk); + + addr->sa_family = AF_BLUETOOTH; + *len = sizeof(struct sockaddr_l2); + + if (peer) + bacpy(&la->l2_bdaddr, &bluez_pi(sk)->dst); + else + bacpy(&la->l2_bdaddr, &bluez_pi(sk)->src); + + la->l2_psm = l2cap_pi(sk)->psm; + return 0; +} + +static int l2cap_sock_sendmsg(struct socket *sock, struct msghdr *msg, int len, struct scm_cookie *scm) +{ + struct sock *sk = sock->sk; + int err = 0; + + BT_DBG("sock %p, sk %p", sock, sk); + + if (sk->err) + return sock_error(sk); + + if (msg->msg_flags & MSG_OOB) + return -EOPNOTSUPP; + + /* Check outgoing MTU */ + if (len > l2cap_pi(sk)->omtu) + return -EINVAL; + + lock_sock(sk); + + if (sk->state == BT_CONNECTED) + err = l2cap_chan_send(sk, msg, len); + else + err = -ENOTCONN; + + release_sock(sk); + return err; +} + +static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname, char *optval, int optlen) +{ + struct sock *sk = sock->sk; + struct l2cap_options opts; + int err = 0, len; + __u32 opt; + + BT_DBG("sk %p", sk); + + lock_sock(sk); + + switch (optname) { + case L2CAP_OPTIONS: + len = MIN(sizeof(opts), optlen); + if (copy_from_user((char *)&opts, optval, len)) { + err = -EFAULT; + break; + } + l2cap_pi(sk)->imtu = opts.imtu; + l2cap_pi(sk)->omtu = opts.omtu; + break; + + case L2CAP_LM: + if (get_user(opt, (__u32 *)optval)) { + err = -EFAULT; + break; + } + + l2cap_pi(sk)->link_mode = opt; + break; + + default: + err = -ENOPROTOOPT; + break; + } + + release_sock(sk); + return err; +} + +static int l2cap_sock_getsockopt(struct socket *sock, int level, int optname, char *optval, int *optlen) +{ + struct sock *sk = sock->sk; + struct l2cap_options opts; + struct l2cap_conninfo cinfo; + int len, err = 0; + + if (get_user(len, optlen)) + return -EFAULT; + + lock_sock(sk); + + switch (optname) { + case L2CAP_OPTIONS: + opts.imtu = l2cap_pi(sk)->imtu; + opts.omtu = l2cap_pi(sk)->omtu; + opts.flush_to = l2cap_pi(sk)->flush_to; + + len = MIN(len, sizeof(opts)); + if (copy_to_user(optval, (char *)&opts, len)) + err = -EFAULT; + + break; + + case L2CAP_LM: + if (put_user(l2cap_pi(sk)->link_mode, (__u32 *)optval)) + err = -EFAULT; + break; + + case L2CAP_CONNINFO: + if (sk->state != BT_CONNECTED) { + err = -ENOTCONN; + break; + } + + cinfo.hci_handle = l2cap_pi(sk)->conn->hcon->handle; + + len = MIN(len, sizeof(cinfo)); + if (copy_to_user(optval, (char *)&cinfo, len)) + err = -EFAULT; + + break; + + default: + err = -ENOPROTOOPT; + break; + } + + release_sock(sk); + return err; +} + +static int l2cap_sock_shutdown(struct socket *sock, int how) +{ + struct sock *sk = sock->sk; + int err = 0; + + BT_DBG("sock %p, sk %p", sock, sk); + + if (!sk) return 0; + + lock_sock(sk); + if (!sk->shutdown) { + sk->shutdown = SHUTDOWN_MASK; + l2cap_sock_clear_timer(sk); + __l2cap_sock_close(sk, 0); + + if (sk->linger) + err = bluez_sock_wait_state(sk, BT_CLOSED, sk->lingertime); + } + release_sock(sk); + return err; +} + +static int l2cap_sock_release(struct socket *sock) +{ + struct sock *sk = sock->sk; + int err; + + BT_DBG("sock %p, sk %p", sock, sk); + + if (!sk) return 0; + + err = l2cap_sock_shutdown(sock, 2); + + sock_orphan(sk); + l2cap_sock_kill(sk); + return err; +} + +/* --------- L2CAP channels --------- */ +static struct sock * __l2cap_get_chan_by_dcid(struct l2cap_chan_list *l, __u16 cid) +{ + struct sock *s; + for (s = l->head; s; s = l2cap_pi(s)->next_c) { + if (l2cap_pi(s)->dcid == cid) + break; + } + return s; +} + +static struct sock *__l2cap_get_chan_by_scid(struct l2cap_chan_list *l, __u16 cid) +{ + struct sock *s; + for (s = l->head; s; s = l2cap_pi(s)->next_c) { + if (l2cap_pi(s)->scid == cid) + break; + } + return s; +} + +/* Find channel with given SCID. + * Returns locked socket */ +static inline struct sock *l2cap_get_chan_by_scid(struct l2cap_chan_list *l, __u16 cid) +{ + struct sock *s; + read_lock(&l->lock); + s = __l2cap_get_chan_by_scid(l, cid); + if (s) bh_lock_sock(s); + read_unlock(&l->lock); + return s; +} + +static __u16 l2cap_alloc_cid(struct l2cap_chan_list *l) +{ + __u16 cid = 0x0040; + + for (; cid < 0xffff; cid++) { + if(!__l2cap_get_chan_by_scid(l, cid)) + return cid; + } + + return 0; +} + +static inline void __l2cap_chan_link(struct l2cap_chan_list *l, struct sock *sk) +{ + sock_hold(sk); + + if (l->head) + l2cap_pi(l->head)->prev_c = sk; + + l2cap_pi(sk)->next_c = l->head; + l2cap_pi(sk)->prev_c = NULL; + l->head = sk; +} + +static inline void l2cap_chan_unlink(struct l2cap_chan_list *l, struct sock *sk) +{ + struct sock *next = l2cap_pi(sk)->next_c, *prev = l2cap_pi(sk)->prev_c; + + write_lock(&l->lock); + if (sk == l->head) + l->head = next; + + if (next) + l2cap_pi(next)->prev_c = prev; + if (prev) + l2cap_pi(prev)->next_c = next; + write_unlock(&l->lock); + + __sock_put(sk); +} + +static void __l2cap_chan_add(struct l2cap_conn *conn, struct sock *sk, struct sock *parent) +{ + struct l2cap_chan_list *l = &conn->chan_list; + + BT_DBG("conn %p, psm 0x%2.2x, dcid 0x%4.4x", conn, l2cap_pi(sk)->psm, l2cap_pi(sk)->dcid); + + l2cap_pi(sk)->conn = conn; + + if (sk->type == SOCK_SEQPACKET) { + /* Alloc CID for connection-oriented socket */ + l2cap_pi(sk)->scid = l2cap_alloc_cid(l); + } else if (sk->type == SOCK_DGRAM) { + /* Connectionless socket */ + l2cap_pi(sk)->scid = 0x0002; + l2cap_pi(sk)->dcid = 0x0002; + l2cap_pi(sk)->omtu = L2CAP_DEFAULT_MTU; + } else { + /* Raw socket can send/recv signalling messages only */ + l2cap_pi(sk)->scid = 0x0001; + l2cap_pi(sk)->dcid = 0x0001; + l2cap_pi(sk)->omtu = L2CAP_DEFAULT_MTU; + } + + __l2cap_chan_link(l, sk); + + if (parent) + bluez_accept_enqueue(parent, sk); +} + +static inline void l2cap_chan_add(struct l2cap_conn *conn, struct sock *sk, struct sock *parent) +{ + struct l2cap_chan_list *l = &conn->chan_list; + write_lock(&l->lock); + __l2cap_chan_add(conn, sk, parent); + write_unlock(&l->lock); +} + +/* Delete channel. + * Must be called on the locked socket. */ +static void l2cap_chan_del(struct sock *sk, int err) +{ + struct l2cap_conn *conn = l2cap_pi(sk)->conn; + struct sock *parent = bluez_pi(sk)->parent; + + l2cap_sock_clear_timer(sk); + + BT_DBG("sk %p, conn %p, err %d", sk, conn, err); + + if (conn) { + /* Unlink from channel list */ + l2cap_chan_unlink(&conn->chan_list, sk); + l2cap_pi(sk)->conn = NULL; + hci_conn_put(conn->hcon); + } + + sk->state = BT_CLOSED; + sk->zapped = 1; + + if (err) + sk->err = err; + + if (parent) + parent->data_ready(parent, 0); + else + sk->state_change(sk); +} + +static void l2cap_conn_ready(struct l2cap_conn *conn) +{ + struct l2cap_chan_list *l = &conn->chan_list; + struct sock *sk; + + BT_DBG("conn %p", conn); + + read_lock(&l->lock); + + for (sk = l->head; sk; sk = l2cap_pi(sk)->next_c) { + bh_lock_sock(sk); + + if (sk->type != SOCK_SEQPACKET) { + l2cap_sock_clear_timer(sk); + sk->state = BT_CONNECTED; + sk->state_change(sk); + } else if (sk->state == BT_CONNECT) { + l2cap_conn_req req; + req.scid = __cpu_to_le16(l2cap_pi(sk)->scid); + req.psm = l2cap_pi(sk)->psm; + l2cap_send_req(conn, L2CAP_CONN_REQ, L2CAP_CONN_REQ_SIZE, &req); + } + + bh_unlock_sock(sk); + } + + read_unlock(&l->lock); +} + +/* Notify sockets that we cannot guaranty reliability anymore */ +static void l2cap_conn_unreliable(struct l2cap_conn *conn, int err) +{ + struct l2cap_chan_list *l = &conn->chan_list; + struct sock *sk; + + BT_DBG("conn %p", conn); + + read_lock(&l->lock); + for (sk = l->head; sk; sk = l2cap_pi(sk)->next_c) { + if (l2cap_pi(sk)->link_mode & L2CAP_LM_RELIABLE) + sk->err = err; + } + read_unlock(&l->lock); +} + +static void l2cap_chan_ready(struct sock *sk) +{ + struct sock *parent = bluez_pi(sk)->parent; + + BT_DBG("sk %p, parent %p", sk, parent); + + l2cap_pi(sk)->conf_state = 0; + l2cap_sock_clear_timer(sk); + + if (!parent) { + /* Outgoing channel. + * Wake up socket sleeping on connect. + */ + sk->state = BT_CONNECTED; + sk->state_change(sk); + } else { + /* Incomming channel. + * Wake up socket sleeping on accept. + */ + parent->data_ready(parent, 0); + } +} + +/* Copy frame to all raw sockets on that connection */ +void l2cap_raw_recv(struct l2cap_conn *conn, struct sk_buff *skb) +{ + struct l2cap_chan_list *l = &conn->chan_list; + struct sk_buff *nskb; + struct sock * sk; + + BT_DBG("conn %p", conn); + + read_lock(&l->lock); + for (sk = l->head; sk; sk = l2cap_pi(sk)->next_c) { + if (sk->type != SOCK_RAW) + continue; + + /* Don't send frame to the socket it came from */ + if (skb->sk == sk) + continue; + + if (!(nskb = skb_clone(skb, GFP_ATOMIC))) + continue; + + if (sock_queue_rcv_skb(sk, nskb)) + kfree_skb(nskb); + } + read_unlock(&l->lock); +} + +static int l2cap_chan_send(struct sock *sk, struct msghdr *msg, int len) +{ + struct l2cap_conn *conn = l2cap_pi(sk)->conn; + struct sk_buff *skb, **frag; + int err, hlen, count, sent=0; + l2cap_hdr *lh; + + BT_DBG("sk %p len %d", sk, len); + + /* First fragment (with L2CAP header) */ + if (sk->type == SOCK_DGRAM) + hlen = L2CAP_HDR_SIZE + 2; + else + hlen = L2CAP_HDR_SIZE; + + count = MIN(conn->mtu - hlen, len); + + skb = bluez_skb_send_alloc(sk, hlen + count, + msg->msg_flags & MSG_DONTWAIT, &err); + if (!skb) + return err; + + /* Create L2CAP header */ + lh = (l2cap_hdr *) skb_put(skb, L2CAP_HDR_SIZE); + lh->cid = __cpu_to_le16(l2cap_pi(sk)->dcid); + lh->len = __cpu_to_le16(len + (hlen - L2CAP_HDR_SIZE)); + + if (sk->type == SOCK_DGRAM) + put_unaligned(l2cap_pi(sk)->psm, (__u16 *) skb_put(skb, 2)); + + if (memcpy_fromiovec(skb_put(skb, count), msg->msg_iov, count)) { + err = -EFAULT; + goto fail; + } + + sent += count; + len -= count; + + /* Continuation fragments (no L2CAP header) */ + frag = &skb_shinfo(skb)->frag_list; + while (len) { + count = MIN(conn->mtu, len); + + *frag = bluez_skb_send_alloc(sk, count, msg->msg_flags & MSG_DONTWAIT, &err); + if (!*frag) + goto fail; + + if (memcpy_fromiovec(skb_put(*frag, count), msg->msg_iov, count)) { + err = -EFAULT; + goto fail; + } + + sent += count; + len -= count; + + frag = &(*frag)->next; + } + + if ((err = hci_send_acl(conn->hcon, skb, 0)) < 0) + goto fail; + + return sent; + +fail: + kfree_skb(skb); + return err; +} + +/* --------- L2CAP signalling commands --------- */ +static inline __u8 l2cap_get_ident(struct l2cap_conn *conn) +{ + __u8 id; + + /* Get next available identificator. + * 1 - 199 are used by kernel. + * 200 - 254 are used by utilities like l2ping, etc + */ + + spin_lock(&conn->lock); + + if (++conn->tx_ident > 199) + conn->tx_ident = 1; + + id = conn->tx_ident; + + spin_unlock(&conn->lock); + + return id; +} + +static struct sk_buff *l2cap_build_cmd(struct l2cap_conn *conn, + __u8 code, __u8 ident, __u16 dlen, void *data) +{ + struct sk_buff *skb, **frag; + l2cap_cmd_hdr *cmd; + l2cap_hdr *lh; + int len, count; + + BT_DBG("conn %p, code 0x%2.2x, ident 0x%2.2x, len %d", conn, code, ident, dlen); + + len = L2CAP_HDR_SIZE + L2CAP_CMD_HDR_SIZE + dlen; + count = MIN(conn->mtu, len); + + skb = bluez_skb_alloc(count, GFP_ATOMIC); + if (!skb) + return NULL; + + lh = (l2cap_hdr *) skb_put(skb, L2CAP_HDR_SIZE); + lh->len = __cpu_to_le16(L2CAP_CMD_HDR_SIZE + dlen); + lh->cid = __cpu_to_le16(0x0001); + + cmd = (l2cap_cmd_hdr *) skb_put(skb, L2CAP_CMD_HDR_SIZE); + cmd->code = code; + cmd->ident = ident; + cmd->len = __cpu_to_le16(dlen); + + if (dlen) { + count -= L2CAP_HDR_SIZE + L2CAP_CMD_HDR_SIZE; + memcpy(skb_put(skb, count), data, count); + data += count; + } + + len -= skb->len; + + /* Continuation fragments (no L2CAP header) */ + frag = &skb_shinfo(skb)->frag_list; + while (len) { + count = MIN(conn->mtu, len); + + *frag = bluez_skb_alloc(count, GFP_ATOMIC); + if (!*frag) + goto fail; + + memcpy(skb_put(*frag, count), data, count); + + len -= count; + data += count; + + frag = &(*frag)->next; + } + + return skb; + +fail: + kfree_skb(skb); + return NULL; +} + +static int l2cap_send_req(struct l2cap_conn *conn, __u8 code, __u16 len, void *data) +{ + __u8 ident = l2cap_get_ident(conn); + struct sk_buff *skb = l2cap_build_cmd(conn, code, ident, len, data); + + BT_DBG("code 0x%2.2x", code); + + if (!skb) + return -ENOMEM; + return hci_send_acl(conn->hcon, skb, 0); +} + +static int l2cap_send_rsp(struct l2cap_conn *conn, __u8 ident, __u8 code, __u16 len, void *data) +{ + struct sk_buff *skb = l2cap_build_cmd(conn, code, ident, len, data); + + BT_DBG("code 0x%2.2x", code); + + if (!skb) + return -ENOMEM; + return hci_send_acl(conn->hcon, skb, 0); +} + +static inline int l2cap_get_conf_opt(void **ptr, int *type, int *olen, unsigned long *val) +{ + l2cap_conf_opt *opt = *ptr; + int len; + + len = L2CAP_CONF_OPT_SIZE + opt->len; + *ptr += len; + + *type = opt->type; + *olen = opt->len; + + switch (opt->len) { + case 1: + *val = *((__u8 *) opt->val); + break; + + case 2: + *val = __le16_to_cpu(*((__u16 *)opt->val)); + break; + + case 4: + *val = __le32_to_cpu(*((__u32 *)opt->val)); + break; + + default: + *val = (unsigned long) opt->val; + break; + }; + + BT_DBG("type 0x%2.2x len %d val 0x%lx", *type, opt->len, *val); + return len; +} + +static inline void l2cap_parse_conf_req(struct sock *sk, void *data, int len) +{ + int type, hint, olen; + unsigned long val; + void *ptr = data; + + BT_DBG("sk %p len %d", sk, len); + + while (len >= L2CAP_CONF_OPT_SIZE) { + len -= l2cap_get_conf_opt(&ptr, &type, &olen, &val); + + hint = type & 0x80; + type &= 0x7f; + + switch (type) { + case L2CAP_CONF_MTU: + l2cap_pi(sk)->conf_mtu = val; + break; + + case L2CAP_CONF_FLUSH_TO: + l2cap_pi(sk)->flush_to = val; + break; + + case L2CAP_CONF_QOS: + break; + + default: + if (hint) + break; + + /* FIXME: Reject unknown option */ + break; + }; + } +} + +static void l2cap_add_conf_opt(void **ptr, __u8 type, __u8 len, unsigned long val) +{ + register l2cap_conf_opt *opt = *ptr; + + BT_DBG("type 0x%2.2x len %d val 0x%lx", type, len, val); + + opt->type = type; + opt->len = len; + + switch (len) { + case 1: + *((__u8 *) opt->val) = val; + break; + + case 2: + *((__u16 *) opt->val) = __cpu_to_le16(val); + break; + + case 4: + *((__u32 *) opt->val) = __cpu_to_le32(val); + break; + + default: + memcpy(opt->val, (void *) val, len); + break; + }; + + *ptr += L2CAP_CONF_OPT_SIZE + len; +} + +static int l2cap_build_conf_req(struct sock *sk, void *data) +{ + struct l2cap_pinfo *pi = l2cap_pi(sk); + l2cap_conf_req *req = (l2cap_conf_req *) data; + void *ptr = req->data; + + BT_DBG("sk %p", sk); + + if (pi->imtu != L2CAP_DEFAULT_MTU) + l2cap_add_conf_opt(&ptr, L2CAP_CONF_MTU, 2, pi->imtu); + + /* FIXME. Need actual value of the flush timeout */ + //if (flush_to != L2CAP_DEFAULT_FLUSH_TO) + // l2cap_add_conf_opt(&ptr, L2CAP_CONF_FLUSH_TO, 2, pi->flush_to); + + req->dcid = __cpu_to_le16(pi->dcid); + req->flags = __cpu_to_le16(0); + + return ptr - data; +} + +static inline int l2cap_conf_output(struct sock *sk, void **ptr) +{ + struct l2cap_pinfo *pi = l2cap_pi(sk); + int result = 0; + + /* Configure output options and let the other side know + * which ones we don't like. + */ + if (pi->conf_mtu < pi->omtu) { + l2cap_add_conf_opt(ptr, L2CAP_CONF_MTU, 2, pi->omtu); + result = L2CAP_CONF_UNACCEPT; + } else { + pi->omtu = pi->conf_mtu; + } + + BT_DBG("sk %p result %d", sk, result); + return result; +} + +static int l2cap_build_conf_rsp(struct sock *sk, void *data, int *result) +{ + l2cap_conf_rsp *rsp = (l2cap_conf_rsp *) data; + void *ptr = rsp->data; + u16 flags = 0; + + BT_DBG("sk %p complete %d", sk, result ? 1 : 0); + + if (result) + *result = l2cap_conf_output(sk, &ptr); + else + flags |= 0x0001; + + rsp->scid = __cpu_to_le16(l2cap_pi(sk)->dcid); + rsp->result = __cpu_to_le16(result ? *result : 0); + rsp->flags = __cpu_to_le16(flags); + + return ptr - data; +} + +static inline int l2cap_connect_req(struct l2cap_conn *conn, l2cap_cmd_hdr *cmd, __u8 *data) +{ + struct l2cap_chan_list *list = &conn->chan_list; + l2cap_conn_req *req = (l2cap_conn_req *) data; + l2cap_conn_rsp rsp; + struct sock *sk, *parent; + int result = 0, status = 0; + + __u16 dcid = 0, scid = __le16_to_cpu(req->scid); + __u16 psm = req->psm; + + BT_DBG("psm 0x%2.2x scid 0x%4.4x", psm, scid); + + /* Check if we have socket listening on psm */ + parent = l2cap_get_sock_by_psm(BT_LISTEN, psm, conn->src); + if (!parent) { + result = L2CAP_CR_BAD_PSM; + goto sendresp; + } + + result = L2CAP_CR_NO_MEM; + + /* Check for backlog size */ + if (parent->ack_backlog > parent->max_ack_backlog) { + BT_DBG("backlog full %d", parent->ack_backlog); + goto response; + } + + sk = l2cap_sock_alloc(NULL, BTPROTO_L2CAP, GFP_ATOMIC); + if (!sk) + goto response; + + write_lock(&list->lock); + + /* Check if we already have channel with that dcid */ + if (__l2cap_get_chan_by_dcid(list, scid)) { + write_unlock(&list->lock); + sk->zapped = 1; + l2cap_sock_kill(sk); + goto response; + } + + hci_conn_hold(conn->hcon); + + l2cap_sock_init(sk, parent); + bacpy(&bluez_pi(sk)->src, conn->src); + bacpy(&bluez_pi(sk)->dst, conn->dst); + l2cap_pi(sk)->psm = psm; + l2cap_pi(sk)->dcid = scid; + + __l2cap_chan_add(conn, sk, parent); + dcid = l2cap_pi(sk)->scid; + + l2cap_sock_set_timer(sk, sk->sndtimeo); + + /* Service level security */ + result = L2CAP_CR_PEND; + status = L2CAP_CS_AUTHEN_PEND; + sk->state = BT_CONNECT2; + l2cap_pi(sk)->ident = cmd->ident; + + if (l2cap_pi(sk)->link_mode & L2CAP_LM_ENCRYPT) { + if (!hci_conn_encrypt(conn->hcon)) + goto done; + } else if (l2cap_pi(sk)->link_mode & L2CAP_LM_AUTH) { + if (!hci_conn_auth(conn->hcon)) + goto done; + } + + sk->state = BT_CONFIG; + result = status = 0; + +done: + write_unlock(&list->lock); + +response: + bh_unlock_sock(parent); + +sendresp: + rsp.scid = __cpu_to_le16(scid); + rsp.dcid = __cpu_to_le16(dcid); + rsp.result = __cpu_to_le16(result); + rsp.status = __cpu_to_le16(status); + l2cap_send_rsp(conn, cmd->ident, L2CAP_CONN_RSP, L2CAP_CONN_RSP_SIZE, &rsp); + return 0; +} + +static inline int l2cap_connect_rsp(struct l2cap_conn *conn, l2cap_cmd_hdr *cmd, __u8 *data) +{ + l2cap_conn_rsp *rsp = (l2cap_conn_rsp *) data; + __u16 scid, dcid, result, status; + struct sock *sk; + char req[128]; + + scid = __le16_to_cpu(rsp->scid); + dcid = __le16_to_cpu(rsp->dcid); + result = __le16_to_cpu(rsp->result); + status = __le16_to_cpu(rsp->status); + + BT_DBG("dcid 0x%4.4x scid 0x%4.4x result 0x%2.2x status 0x%2.2x", dcid, scid, result, status); + + if (!(sk = l2cap_get_chan_by_scid(&conn->chan_list, scid))) + return -ENOENT; + + switch (result) { + case L2CAP_CR_SUCCESS: + sk->state = BT_CONFIG; + l2cap_pi(sk)->dcid = dcid; + l2cap_pi(sk)->conf_state |= L2CAP_CONF_REQ_SENT; + + l2cap_send_req(conn, L2CAP_CONF_REQ, l2cap_build_conf_req(sk, req), req); + break; + + case L2CAP_CR_PEND: + break; + + default: + l2cap_chan_del(sk, ECONNREFUSED); + break; + } + + bh_unlock_sock(sk); + return 0; +} + +static inline int l2cap_config_req(struct l2cap_conn *conn, l2cap_cmd_hdr *cmd, __u8 *data) +{ + l2cap_conf_req * req = (l2cap_conf_req *) data; + __u16 dcid, flags; + __u8 rsp[64]; + struct sock *sk; + int result; + + dcid = __le16_to_cpu(req->dcid); + flags = __le16_to_cpu(req->flags); + + BT_DBG("dcid 0x%4.4x flags 0x%2.2x", dcid, flags); + + if (!(sk = l2cap_get_chan_by_scid(&conn->chan_list, dcid))) + return -ENOENT; + + l2cap_parse_conf_req(sk, req->data, cmd->len - L2CAP_CONF_REQ_SIZE); + + if (flags & 0x0001) { + /* Incomplete config. Send empty response. */ + l2cap_send_rsp(conn, cmd->ident, L2CAP_CONF_RSP, l2cap_build_conf_rsp(sk, rsp, NULL), rsp); + goto unlock; + } + + /* Complete config. */ + l2cap_send_rsp(conn, cmd->ident, L2CAP_CONF_RSP, l2cap_build_conf_rsp(sk, rsp, &result), rsp); + + if (result) + goto unlock; + + /* Output config done */ + l2cap_pi(sk)->conf_state |= L2CAP_CONF_OUTPUT_DONE; + + if (l2cap_pi(sk)->conf_state & L2CAP_CONF_INPUT_DONE) { + sk->state = BT_CONNECTED; + l2cap_chan_ready(sk); + } else if (!(l2cap_pi(sk)->conf_state & L2CAP_CONF_REQ_SENT)) { + char req[64]; + l2cap_send_req(conn, L2CAP_CONF_REQ, l2cap_build_conf_req(sk, req), req); + } + +unlock: + bh_unlock_sock(sk); + return 0; +} + +static inline int l2cap_config_rsp(struct l2cap_conn *conn, l2cap_cmd_hdr *cmd, __u8 *data) +{ + l2cap_conf_rsp *rsp = (l2cap_conf_rsp *)data; + __u16 scid, flags, result; + struct sock *sk; + int err = 0; + + scid = __le16_to_cpu(rsp->scid); + flags = __le16_to_cpu(rsp->flags); + result = __le16_to_cpu(rsp->result); + + BT_DBG("scid 0x%4.4x flags 0x%2.2x result 0x%2.2x", scid, flags, result); + + if (!(sk = l2cap_get_chan_by_scid(&conn->chan_list, scid))) + return -ENOENT; + + switch (result) { + case L2CAP_CONF_SUCCESS: + break; + + case L2CAP_CONF_UNACCEPT: + if (++l2cap_pi(sk)->conf_retry < L2CAP_CONF_MAX_RETRIES) { + char req[128]; + /* + It does not make sense to adjust L2CAP parameters + that are currently defined in the spec. We simply + resend config request that we sent earlier. It is + stupid :) but it helps qualification testing + which expects at least some response from us. + */ + l2cap_send_req(conn, L2CAP_CONF_REQ, + l2cap_build_conf_req(sk, req), req); + goto done; + } + default: + sk->state = BT_DISCONN; + sk->err = ECONNRESET; + l2cap_sock_set_timer(sk, HZ * 5); + { + l2cap_disconn_req req; + req.dcid = __cpu_to_le16(l2cap_pi(sk)->dcid); + req.scid = __cpu_to_le16(l2cap_pi(sk)->scid); + l2cap_send_req(conn, L2CAP_DISCONN_REQ, L2CAP_DISCONN_REQ_SIZE, &req); + } + goto done; + } + + if (flags & 0x01) + goto done; + + /* Input config done */ + l2cap_pi(sk)->conf_state |= L2CAP_CONF_INPUT_DONE; + + if (l2cap_pi(sk)->conf_state & L2CAP_CONF_OUTPUT_DONE) { + sk->state = BT_CONNECTED; + l2cap_chan_ready(sk); + } + +done: + bh_unlock_sock(sk); + return err; +} + +static inline int l2cap_disconnect_req(struct l2cap_conn *conn, l2cap_cmd_hdr *cmd, __u8 *data) +{ + l2cap_disconn_req *req = (l2cap_disconn_req *) data; + l2cap_disconn_rsp rsp; + __u16 dcid, scid; + struct sock *sk; + + scid = __le16_to_cpu(req->scid); + dcid = __le16_to_cpu(req->dcid); + + BT_DBG("scid 0x%4.4x dcid 0x%4.4x", scid, dcid); + + if (!(sk = l2cap_get_chan_by_scid(&conn->chan_list, dcid))) + return 0; + + rsp.dcid = __cpu_to_le16(l2cap_pi(sk)->scid); + rsp.scid = __cpu_to_le16(l2cap_pi(sk)->dcid); + l2cap_send_rsp(conn, cmd->ident, L2CAP_DISCONN_RSP, L2CAP_DISCONN_RSP_SIZE, &rsp); + + sk->shutdown = SHUTDOWN_MASK; + + l2cap_chan_del(sk, ECONNRESET); + bh_unlock_sock(sk); + + l2cap_sock_kill(sk); + return 0; +} + +static inline int l2cap_disconnect_rsp(struct l2cap_conn *conn, l2cap_cmd_hdr *cmd, __u8 *data) +{ + l2cap_disconn_rsp *rsp = (l2cap_disconn_rsp *) data; + __u16 dcid, scid; + struct sock *sk; + + scid = __le16_to_cpu(rsp->scid); + dcid = __le16_to_cpu(rsp->dcid); + + BT_DBG("dcid 0x%4.4x scid 0x%4.4x", dcid, scid); + + if (!(sk = l2cap_get_chan_by_scid(&conn->chan_list, scid))) + return 0; + l2cap_chan_del(sk, 0); + bh_unlock_sock(sk); + + l2cap_sock_kill(sk); + return 0; +} + +static inline void l2cap_sig_channel(struct l2cap_conn *conn, struct sk_buff *skb) +{ + __u8 *data = skb->data; + int len = skb->len; + l2cap_cmd_hdr cmd; + int err = 0; + + while (len >= L2CAP_CMD_HDR_SIZE) { + memcpy(&cmd, data, L2CAP_CMD_HDR_SIZE); + data += L2CAP_CMD_HDR_SIZE; + len -= L2CAP_CMD_HDR_SIZE; + + cmd.len = __le16_to_cpu(cmd.len); + + BT_DBG("code 0x%2.2x len %d id 0x%2.2x", cmd.code, cmd.len, cmd.ident); + + if (cmd.len > len || !cmd.ident) { + BT_DBG("corrupted command"); + break; + } + + switch (cmd.code) { + case L2CAP_CONN_REQ: + err = l2cap_connect_req(conn, &cmd, data); + break; + + case L2CAP_CONN_RSP: + err = l2cap_connect_rsp(conn, &cmd, data); + break; + + case L2CAP_CONF_REQ: + err = l2cap_config_req(conn, &cmd, data); + break; + + case L2CAP_CONF_RSP: + err = l2cap_config_rsp(conn, &cmd, data); + break; + + case L2CAP_DISCONN_REQ: + err = l2cap_disconnect_req(conn, &cmd, data); + break; + + case L2CAP_DISCONN_RSP: + err = l2cap_disconnect_rsp(conn, &cmd, data); + break; + + case L2CAP_COMMAND_REJ: + /* FIXME: We should process this */ + l2cap_raw_recv(conn, skb); + break; + + case L2CAP_ECHO_REQ: + l2cap_send_rsp(conn, cmd.ident, L2CAP_ECHO_RSP, cmd.len, data); + break; + + case L2CAP_ECHO_RSP: + case L2CAP_INFO_REQ: + case L2CAP_INFO_RSP: + l2cap_raw_recv(conn, skb); + break; + + default: + BT_ERR("Uknown signaling command 0x%2.2x", cmd.code); + err = -EINVAL; + break; + }; + + if (err) { + l2cap_cmd_rej rej; + BT_DBG("error %d", err); + + /* FIXME: Map err to a valid reason. */ + rej.reason = __cpu_to_le16(0); + l2cap_send_rsp(conn, cmd.ident, L2CAP_COMMAND_REJ, L2CAP_CMD_REJ_SIZE, &rej); + } + + data += cmd.len; + len -= cmd.len; + } + + kfree_skb(skb); +} + +static inline int l2cap_data_channel(struct l2cap_conn *conn, __u16 cid, struct sk_buff *skb) +{ + struct sock *sk; + + sk = l2cap_get_chan_by_scid(&conn->chan_list, cid); + if (!sk) { + BT_DBG("unknown cid 0x%4.4x", cid); + goto drop; + } + + BT_DBG("sk %p, len %d", sk, skb->len); + + if (sk->state != BT_CONNECTED) + goto drop; + + if (l2cap_pi(sk)->imtu < skb->len) + goto drop; + + /* If socket recv buffers overflows we drop data here + * which is *bad* because L2CAP has to be reliable. + * But we don't have any other choice. L2CAP doesn't + * provide flow control mechanism */ + + if (!sock_queue_rcv_skb(sk, skb)) + goto done; + +drop: + kfree_skb(skb); + +done: + if (sk) bh_unlock_sock(sk); + return 0; +} + +static inline int l2cap_conless_channel(struct l2cap_conn *conn, __u16 psm, struct sk_buff *skb) +{ + struct sock *sk; + + sk = l2cap_get_sock_by_psm(0, psm, conn->src); + if (!sk) + goto drop; + + BT_DBG("sk %p, len %d", sk, skb->len); + + if (sk->state != BT_BOUND && sk->state != BT_CONNECTED) + goto drop; + + if (l2cap_pi(sk)->imtu < skb->len) + goto drop; + + if (!sock_queue_rcv_skb(sk, skb)) + goto done; + +drop: + kfree_skb(skb); + +done: + if (sk) bh_unlock_sock(sk); + return 0; +} + +static void l2cap_recv_frame(struct l2cap_conn *conn, struct sk_buff *skb) +{ + l2cap_hdr *lh = (l2cap_hdr *) skb->data; + __u16 cid, psm, len; + + skb_pull(skb, L2CAP_HDR_SIZE); + cid = __le16_to_cpu(lh->cid); + len = __le16_to_cpu(lh->len); + + BT_DBG("len %d, cid 0x%4.4x", len, cid); + + switch (cid) { + case 0x0001: + l2cap_sig_channel(conn, skb); + break; + + case 0x0002: + psm = get_unaligned((__u16 *) skb->data); + skb_pull(skb, 2); + l2cap_conless_channel(conn, psm, skb); + break; + + default: + l2cap_data_channel(conn, cid, skb); + break; + } +} + +/* ------------ L2CAP interface with lower layer (HCI) ------------- */ + +static int l2cap_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr, __u8 type) +{ + int exact = 0, lm1 = 0, lm2 = 0; + register struct sock *sk; + + if (type != ACL_LINK) + return 0; + + BT_DBG("hdev %s, bdaddr %s", hdev->name, batostr(bdaddr)); + + /* Find listening sockets and check their link_mode */ + read_lock(&l2cap_sk_list.lock); + for (sk = l2cap_sk_list.head; sk; sk = sk->next) { + if (sk->state != BT_LISTEN) + continue; + + if (!bacmp(&bluez_pi(sk)->src, &hdev->bdaddr)) { + lm1 |= (HCI_LM_ACCEPT | l2cap_pi(sk)->link_mode); + exact++; + } else if (!bacmp(&bluez_pi(sk)->src, BDADDR_ANY)) + lm2 |= (HCI_LM_ACCEPT | l2cap_pi(sk)->link_mode); + } + read_unlock(&l2cap_sk_list.lock); + + return exact ? lm1 : lm2; +} + +static int l2cap_connect_cfm(struct hci_conn *hcon, __u8 status) +{ + BT_DBG("hcon %p bdaddr %s status %d", hcon, batostr(&hcon->dst), status); + + if (hcon->type != ACL_LINK) + return 0; + + if (!status) { + struct l2cap_conn *conn; + + conn = l2cap_conn_add(hcon, status); + if (conn) + l2cap_conn_ready(conn); + } else + l2cap_conn_del(hcon, bterr(status)); + + return 0; +} + +static int l2cap_disconn_ind(struct hci_conn *hcon, __u8 reason) +{ + BT_DBG("hcon %p reason %d", hcon, reason); + + if (hcon->type != ACL_LINK) + return 0; + + l2cap_conn_del(hcon, bterr(reason)); + return 0; +} + +static int l2cap_auth_cfm(struct hci_conn *hcon, __u8 status) +{ + struct l2cap_chan_list *l; + struct l2cap_conn *conn; + l2cap_conn_rsp rsp; + struct sock *sk; + int result; + + if (!(conn = hcon->l2cap_data)) + return 0; + l = &conn->chan_list; + + BT_DBG("conn %p", conn); + + read_lock(&l->lock); + + for (sk = l->head; sk; sk = l2cap_pi(sk)->next_c) { + bh_lock_sock(sk); + + if (sk->state != BT_CONNECT2 || + (l2cap_pi(sk)->link_mode & L2CAP_LM_ENCRYPT)) { + bh_unlock_sock(sk); + continue; + } + + if (!status) { + sk->state = BT_CONFIG; + result = 0; + } else { + sk->state = BT_DISCONN; + l2cap_sock_set_timer(sk, HZ/10); + result = L2CAP_CR_SEC_BLOCK; + } + + rsp.scid = __cpu_to_le16(l2cap_pi(sk)->dcid); + rsp.dcid = __cpu_to_le16(l2cap_pi(sk)->scid); + rsp.result = __cpu_to_le16(result); + rsp.status = __cpu_to_le16(0); + l2cap_send_rsp(conn, l2cap_pi(sk)->ident, L2CAP_CONN_RSP, + L2CAP_CONN_RSP_SIZE, &rsp); + + bh_unlock_sock(sk); + } + + read_unlock(&l->lock); + return 0; +} + +static int l2cap_encrypt_cfm(struct hci_conn *hcon, __u8 status) +{ + struct l2cap_chan_list *l; + struct l2cap_conn *conn; + l2cap_conn_rsp rsp; + struct sock *sk; + int result; + + if (!(conn = hcon->l2cap_data)) + return 0; + l = &conn->chan_list; + + BT_DBG("conn %p", conn); + + read_lock(&l->lock); + + for (sk = l->head; sk; sk = l2cap_pi(sk)->next_c) { + bh_lock_sock(sk); + + if (sk->state != BT_CONNECT2) { + bh_unlock_sock(sk); + continue; + } + + if (!status) { + sk->state = BT_CONFIG; + result = 0; + } else { + sk->state = BT_DISCONN; + l2cap_sock_set_timer(sk, HZ/10); + result = L2CAP_CR_SEC_BLOCK; + } + + rsp.scid = __cpu_to_le16(l2cap_pi(sk)->dcid); + rsp.dcid = __cpu_to_le16(l2cap_pi(sk)->scid); + rsp.result = __cpu_to_le16(result); + rsp.status = __cpu_to_le16(0); + l2cap_send_rsp(conn, l2cap_pi(sk)->ident, L2CAP_CONN_RSP, + L2CAP_CONN_RSP_SIZE, &rsp); + + bh_unlock_sock(sk); + } + + read_unlock(&l->lock); + return 0; +} + +static int l2cap_recv_acldata(struct hci_conn *hcon, struct sk_buff *skb, __u16 flags) +{ + struct l2cap_conn *conn = hcon->l2cap_data; + + if (!conn && !(conn = l2cap_conn_add(hcon, 0))) + goto drop; + + BT_DBG("conn %p len %d flags 0x%x", conn, skb->len, flags); + + if (flags & ACL_START) { + l2cap_hdr *hdr; + int len; + + if (conn->rx_len) { + BT_ERR("Unexpected start frame (len %d)", skb->len); + kfree_skb(conn->rx_skb); + conn->rx_skb = NULL; + conn->rx_len = 0; + l2cap_conn_unreliable(conn, ECOMM); + } + + if (skb->len < 2) { + BT_ERR("Frame is too short (len %d)", skb->len); + l2cap_conn_unreliable(conn, ECOMM); + goto drop; + } + + hdr = (l2cap_hdr *) skb->data; + len = __le16_to_cpu(hdr->len) + L2CAP_HDR_SIZE; + + if (len == skb->len) { + /* Complete frame received */ + l2cap_recv_frame(conn, skb); + return 0; + } + + BT_DBG("Start: total len %d, frag len %d", len, skb->len); + + if (skb->len > len) { + BT_ERR("Frame is too long (len %d, expected len %d)", + skb->len, len); + l2cap_conn_unreliable(conn, ECOMM); + goto drop; + } + + /* Allocate skb for the complete frame including header */ + conn->rx_skb = bluez_skb_alloc(len, GFP_ATOMIC); + if (!conn->rx_skb) + goto drop; + + memcpy(skb_put(conn->rx_skb, skb->len), skb->data, skb->len); + conn->rx_len = len - skb->len; + } else { + BT_DBG("Cont: frag len %d (expecting %d)", skb->len, conn->rx_len); + + if (!conn->rx_len) { + BT_ERR("Unexpected continuation frame (len %d)", skb->len); + l2cap_conn_unreliable(conn, ECOMM); + goto drop; + } + + if (skb->len > conn->rx_len) { + BT_ERR("Fragment is too long (len %d, expected %d)", + skb->len, conn->rx_len); + kfree_skb(conn->rx_skb); + conn->rx_skb = NULL; + conn->rx_len = 0; + l2cap_conn_unreliable(conn, ECOMM); + goto drop; + } + + memcpy(skb_put(conn->rx_skb, skb->len), skb->data, skb->len); + conn->rx_len -= skb->len; + + if (!conn->rx_len) { + /* Complete frame received */ + l2cap_recv_frame(conn, conn->rx_skb); + conn->rx_skb = NULL; + } + } + +drop: + kfree_skb(skb); + return 0; +} + +/* ----- Proc fs support ------ */ +static int l2cap_sock_dump(char *buf, struct bluez_sock_list *list) +{ + struct l2cap_pinfo *pi; + struct sock *sk; + char *ptr = buf; + + read_lock_bh(&list->lock); + + for (sk = list->head; sk; sk = sk->next) { + pi = l2cap_pi(sk); + ptr += sprintf(ptr, "%s %s %d %d 0x%4.4x 0x%4.4x %d %d 0x%x\n", + batostr(&bluez_pi(sk)->src), batostr(&bluez_pi(sk)->dst), + sk->state, pi->psm, pi->scid, pi->dcid, pi->imtu, pi->omtu, + pi->link_mode); + } + + read_unlock_bh(&list->lock); + + ptr += sprintf(ptr, "\n"); + return ptr - buf; +} + +static int l2cap_read_proc(char *buf, char **start, off_t offset, int count, int *eof, void *priv) +{ + char *ptr = buf; + int len; + + BT_DBG("count %d, offset %ld", count, offset); + + ptr += l2cap_sock_dump(ptr, &l2cap_sk_list); + len = ptr - buf; + + if (len <= count + offset) + *eof = 1; + + *start = buf + offset; + len -= offset; + + if (len > count) + len = count; + if (len < 0) + len = 0; + + return len; +} + +static struct proto_ops l2cap_sock_ops = { + family: PF_BLUETOOTH, + release: l2cap_sock_release, + bind: l2cap_sock_bind, + connect: l2cap_sock_connect, + listen: l2cap_sock_listen, + accept: l2cap_sock_accept, + getname: l2cap_sock_getname, + sendmsg: l2cap_sock_sendmsg, + recvmsg: bluez_sock_recvmsg, + poll: bluez_sock_poll, + socketpair: sock_no_socketpair, + ioctl: sock_no_ioctl, + shutdown: l2cap_sock_shutdown, + setsockopt: l2cap_sock_setsockopt, + getsockopt: l2cap_sock_getsockopt, + mmap: sock_no_mmap +}; + +static struct net_proto_family l2cap_sock_family_ops = { + family: PF_BLUETOOTH, + create: l2cap_sock_create +}; + +static struct hci_proto l2cap_hci_proto = { + name: "L2CAP", + id: HCI_PROTO_L2CAP, + connect_ind: l2cap_connect_ind, + connect_cfm: l2cap_connect_cfm, + disconn_ind: l2cap_disconn_ind, + recv_acldata: l2cap_recv_acldata, + auth_cfm: l2cap_auth_cfm, + encrypt_cfm: l2cap_encrypt_cfm +}; + +int __init l2cap_init(void) +{ + int err; + + if ((err = bluez_sock_register(BTPROTO_L2CAP, &l2cap_sock_family_ops))) { + BT_ERR("Can't register L2CAP socket"); + return err; + } + + if ((err = hci_register_proto(&l2cap_hci_proto))) { + BT_ERR("Can't register L2CAP protocol"); + return err; + } + + create_proc_read_entry("bluetooth/l2cap", 0, 0, l2cap_read_proc, NULL); + + BT_INFO("BlueZ L2CAP ver %s Copyright (C) 2000,2001 Qualcomm Inc", VERSION); + BT_INFO("Written 2000,2001 by Maxim Krasnyansky "); + return 0; +} + +void l2cap_cleanup(void) +{ + remove_proc_entry("bluetooth/l2cap", NULL); + + /* Unregister socket and protocol */ + if (bluez_sock_unregister(BTPROTO_L2CAP)) + BT_ERR("Can't unregister L2CAP socket"); + + if (hci_unregister_proto(&l2cap_hci_proto)) + BT_ERR("Can't unregister L2CAP protocol"); +} + +void l2cap_load(void) +{ + /* Dummy function to trigger automatic L2CAP module loading by + other modules that use L2CAP sockets but do not use any other + symbols from it. */ + return; +} + +EXPORT_SYMBOL(l2cap_load); + +module_init(l2cap_init); +module_exit(l2cap_cleanup); + +MODULE_AUTHOR("Maxim Krasnyansky "); +MODULE_DESCRIPTION("BlueZ L2CAP ver " VERSION); +MODULE_LICENSE("GPL"); --- linux/net/bluetooth/l2cap_core.c~bluetooth-2.4.18-mh11 +++ linux/net/bluetooth/l2cap_core.c -/* - BlueZ - Bluetooth protocol stack for Linux - Copyright (C) 2000-2001 Qualcomm Incorporated - - Written 2000,2001 by Maxim Krasnyansky - - 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. -*/ - -/* - * BlueZ L2CAP core and sockets. - * - * $Id: l2cap_core.c,v 1.19 2001/08/03 04:19:50 maxk Exp $ - */ -#define VERSION "1.1" - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include - -#ifndef L2CAP_DEBUG -#undef DBG -#define DBG( A... ) -#endif - -struct proto_ops l2cap_sock_ops; - -struct bluez_sock_list l2cap_sk_list = { - lock: RW_LOCK_UNLOCKED -}; - -struct list_head l2cap_iff_list = LIST_HEAD_INIT(l2cap_iff_list); -rwlock_t l2cap_rt_lock = RW_LOCK_UNLOCKED; - -static int l2cap_conn_del(struct l2cap_conn *conn, int err); - -static inline void l2cap_chan_add(struct l2cap_conn *conn, struct sock *sk, struct sock *parent); -static void l2cap_chan_del(struct sock *sk, int err); -static int l2cap_chan_send(struct sock *sk, struct msghdr *msg, int len); - -static void l2cap_sock_close(struct sock *sk); -static void l2cap_sock_kill(struct sock *sk); - -static int l2cap_send_req(struct l2cap_conn *conn, __u8 code, __u16 len, void *data); -static int l2cap_send_rsp(struct l2cap_conn *conn, __u8 ident, __u8 code, __u16 len, void *data); - -/* -------- L2CAP interfaces & routing --------- */ -/* Add/delete L2CAP interface. - * Must be called with locked rt_lock - */ - -static void l2cap_iff_add(struct hci_dev *hdev) -{ - struct l2cap_iff *iff; - - DBG("%s", hdev->name); - - DBG("iff_list %p next %p prev %p", &l2cap_iff_list, l2cap_iff_list.next, l2cap_iff_list.prev); - - /* Allocate new interface and lock HCI device */ - if (!(iff = kmalloc(sizeof(struct l2cap_iff), GFP_KERNEL))) { - ERR("Can't allocate new interface %s", hdev->name); - return; - } - memset(iff, 0, sizeof(struct l2cap_iff)); - - hci_dev_hold(hdev); - hdev->l2cap_data = iff; - iff->hdev = hdev; - iff->mtu = hdev->acl_mtu - HCI_ACL_HDR_SIZE; - iff->bdaddr = &hdev->bdaddr; - - spin_lock_init(&iff->lock); - INIT_LIST_HEAD(&iff->conn_list); - - list_add(&iff->list, &l2cap_iff_list); -} - -static void l2cap_iff_del(struct hci_dev *hdev) -{ - struct l2cap_iff *iff; - - if (!(iff = hdev->l2cap_data)) - return; - - DBG("%s iff %p", hdev->name, iff); - - list_del(&iff->list); - - l2cap_iff_lock(iff); - - /* Drop connections */ - while (!list_empty(&iff->conn_list)) { - struct l2cap_conn *c; - - c = list_entry(iff->conn_list.next, struct l2cap_conn, list); - l2cap_conn_del(c, ENODEV); - } - - l2cap_iff_unlock(iff); - - /* Unlock HCI device */ - hdev->l2cap_data = NULL; - hci_dev_put(hdev); - - kfree(iff); -} - -/* Get route. Returns L2CAP interface. - * Must be called with locked rt_lock - */ -static struct l2cap_iff *l2cap_get_route(bdaddr_t *src, bdaddr_t *dst) -{ - struct list_head *p; - int use_src; - - DBG("%s -> %s", batostr(src), batostr(dst)); - - use_src = bacmp(src, BDADDR_ANY) ? 0 : 1; - - /* Simple routing: - * No source address - find interface with bdaddr != dst - * Source address - find interface with bdaddr == src - */ - - list_for_each(p, &l2cap_iff_list) { - struct l2cap_iff *iff; - - iff = list_entry(p, struct l2cap_iff, list); - - if (use_src && !bacmp(iff->bdaddr, src)) - return iff; - else if (bacmp(iff->bdaddr, dst)) - return iff; - } - return NULL; -} - -/* ----- L2CAP timers ------ */ -static void l2cap_sock_timeout(unsigned long arg) -{ - struct sock *sk = (struct sock *) arg; - - DBG("sock %p state %d", sk, sk->state); - - bh_lock_sock(sk); - switch (sk->state) { - case BT_DISCONN: - l2cap_chan_del(sk, ETIMEDOUT); - break; - - default: - sk->err = ETIMEDOUT; - sk->state_change(sk); - break; - }; - bh_unlock_sock(sk); - - l2cap_sock_kill(sk); - sock_put(sk); -} - -static void l2cap_sock_set_timer(struct sock *sk, long timeout) -{ - DBG("sock %p state %d timeout %ld", sk, sk->state, timeout); - - if (!mod_timer(&sk->timer, jiffies + timeout)) - sock_hold(sk); -} - -static void l2cap_sock_clear_timer(struct sock *sk) -{ - DBG("sock %p state %d", sk, sk->state); - - if (timer_pending(&sk->timer) && del_timer(&sk->timer)) - __sock_put(sk); -} - -static void l2cap_sock_init_timer(struct sock *sk) -{ - init_timer(&sk->timer); - sk->timer.function = l2cap_sock_timeout; - sk->timer.data = (unsigned long)sk; -} - -static void l2cap_conn_timeout(unsigned long arg) -{ - struct l2cap_conn *conn = (void *)arg; - - DBG("conn %p state %d", conn, conn->state); - - if (conn->state == BT_CONNECTED) { - hci_disconnect(conn->hconn, 0x13); - } - - return; -} - -static void l2cap_conn_set_timer(struct l2cap_conn *conn, long timeout) -{ - DBG("conn %p state %d timeout %ld", conn, conn->state, timeout); - - mod_timer(&conn->timer, jiffies + timeout); -} - -static void l2cap_conn_clear_timer(struct l2cap_conn *conn) -{ - DBG("conn %p state %d", conn, conn->state); - - del_timer(&conn->timer); -} - -static void l2cap_conn_init_timer(struct l2cap_conn *conn) -{ - init_timer(&conn->timer); - conn->timer.function = l2cap_conn_timeout; - conn->timer.data = (unsigned long)conn; -} - -/* -------- L2CAP connections --------- */ -/* Add new connection to the interface. - * Interface must be locked - */ -static struct l2cap_conn *l2cap_conn_add(struct l2cap_iff *iff, bdaddr_t *dst) -{ - struct l2cap_conn *conn; - bdaddr_t *src = iff->bdaddr; - - if (!(conn = kmalloc(sizeof(struct l2cap_conn), GFP_KERNEL))) - return NULL; - - memset(conn, 0, sizeof(struct l2cap_conn)); - - conn->state = BT_OPEN; - conn->iff = iff; - bacpy(&conn->src, src); - bacpy(&conn->dst, dst); - - spin_lock_init(&conn->lock); - conn->chan_list.lock = RW_LOCK_UNLOCKED; - - l2cap_conn_init_timer(conn); - - __l2cap_conn_link(iff, conn); - - DBG("%s -> %s, %p", batostr(src), batostr(dst), conn); - - MOD_INC_USE_COUNT; - - return conn; -} - -/* Delete connection on the interface. - * Interface must be locked - */ -static int l2cap_conn_del(struct l2cap_conn *conn, int err) -{ - struct sock *sk; - - DBG("conn %p, state %d, err %d", conn, conn->state, err); - - l2cap_conn_clear_timer(conn); - __l2cap_conn_unlink(conn->iff, conn); - - conn->state = BT_CLOSED; - - if (conn->rx_skb) - kfree_skb(conn->rx_skb); - - /* Kill channels */ - while ((sk = conn->chan_list.head)) { - bh_lock_sock(sk); - l2cap_sock_clear_timer(sk); - l2cap_chan_del(sk, err); - bh_unlock_sock(sk); - - l2cap_sock_kill(sk); - } - - kfree(conn); - - MOD_DEC_USE_COUNT; - return 0; -} - -static inline struct l2cap_conn *l2cap_get_conn_by_addr(struct l2cap_iff *iff, bdaddr_t *dst) -{ - struct list_head *p; - - list_for_each(p, &iff->conn_list) { - struct l2cap_conn *c; - - c = list_entry(p, struct l2cap_conn, list); - if (!bacmp(&c->dst, dst)) - return c; - } - return NULL; -} - -int l2cap_connect(struct sock *sk) -{ - bdaddr_t *src = &l2cap_pi(sk)->src; - bdaddr_t *dst = &l2cap_pi(sk)->dst; - struct l2cap_conn *conn; - struct l2cap_iff *iff; - int err = 0; - - DBG("%s -> %s psm 0x%2.2x", batostr(src), batostr(dst), l2cap_pi(sk)->psm); - - read_lock_bh(&l2cap_rt_lock); - - /* Get route to remote BD address */ - if (!(iff = l2cap_get_route(src, dst))) { - err = -EHOSTUNREACH; - goto done; - } - - /* Update source addr of the socket */ - bacpy(src, iff->bdaddr); - - l2cap_iff_lock(iff); - - if (!(conn = l2cap_get_conn_by_addr(iff, dst))) { - /* Connection doesn't exist */ - if (!(conn = l2cap_conn_add(iff, dst))) { - l2cap_iff_unlock(iff); - err = -ENOMEM; - goto done; - } - conn->out = 1; - } - - l2cap_iff_unlock(iff); - - l2cap_chan_add(conn, sk, NULL); - - sk->state = BT_CONNECT; - l2cap_sock_set_timer(sk, sk->sndtimeo); - - switch (conn->state) { - case BT_CONNECTED: - if (sk->type == SOCK_SEQPACKET) { - l2cap_conn_req req; - req.scid = __cpu_to_le16(l2cap_pi(sk)->scid); - req.psm = l2cap_pi(sk)->psm; - l2cap_send_req(conn, L2CAP_CONN_REQ, L2CAP_CONN_REQ_SIZE, &req); - } else { - l2cap_sock_clear_timer(sk); - sk->state = BT_CONNECTED; - } - break; - - case BT_CONNECT: - break; - - default: - /* Create ACL connection */ - conn->state = BT_CONNECT; - hci_connect(iff->hdev, dst); - break; - }; - -done: - read_unlock_bh(&l2cap_rt_lock); - return err; -} - -/* ------ Channel queues for listening sockets ------ */ -void l2cap_accept_queue(struct sock *parent, struct sock *sk) -{ - struct l2cap_accept_q *q = &l2cap_pi(parent)->accept_q; - - DBG("parent %p, sk %p", parent, sk); - - sock_hold(sk); - l2cap_pi(sk)->parent = parent; - l2cap_pi(sk)->next_q = NULL; - - if (!q->head) { - q->head = q->tail = sk; - } else { - struct sock *tail = q->tail; - - l2cap_pi(sk)->prev_q = tail; - l2cap_pi(tail)->next_q = sk; - q->tail = sk; - } - - parent->ack_backlog++; -} - -void l2cap_accept_unlink(struct sock *sk) -{ - struct sock *parent = l2cap_pi(sk)->parent; - struct l2cap_accept_q *q = &l2cap_pi(parent)->accept_q; - struct sock *next, *prev; - - DBG("sk %p", sk); - - next = l2cap_pi(sk)->next_q; - prev = l2cap_pi(sk)->prev_q; - - if (sk == q->head) - q->head = next; - if (sk == q->tail) - q->tail = prev; - - if (next) - l2cap_pi(next)->prev_q = prev; - if (prev) - l2cap_pi(prev)->next_q = next; - - l2cap_pi(sk)->parent = NULL; - - parent->ack_backlog--; - __sock_put(sk); -} - -/* Get next connected channel in queue. */ -struct sock *l2cap_accept_dequeue(struct sock *parent, int state) -{ - struct l2cap_accept_q *q = &l2cap_pi(parent)->accept_q; - struct sock *sk; - - for (sk = q->head; sk; sk = l2cap_pi(sk)->next_q) { - if (!state || sk->state == state) { - l2cap_accept_unlink(sk); - break; - } - } - - DBG("parent %p, sk %p", parent, sk); - - return sk; -} - -/* -------- Socket interface ---------- */ -static struct sock *__l2cap_get_sock_by_addr(struct sockaddr_l2 *addr) -{ - bdaddr_t *src = &addr->l2_bdaddr; - __u16 psm = addr->l2_psm; - struct sock *sk; - - for (sk = l2cap_sk_list.head; sk; sk = sk->next) { - if (l2cap_pi(sk)->psm == psm && - !bacmp(&l2cap_pi(sk)->src, src)) - break; - } - - return sk; -} - -/* Find socket listening on psm and source bdaddr. - * Returns closest match. - */ -static struct sock *l2cap_get_sock_listen(bdaddr_t *src, __u16 psm) -{ - struct sock *sk, *sk1 = NULL; - - read_lock(&l2cap_sk_list.lock); - - for (sk = l2cap_sk_list.head; sk; sk = sk->next) { - struct l2cap_pinfo *pi; - - if (sk->state != BT_LISTEN) - continue; - - pi = l2cap_pi(sk); - - if (pi->psm == psm) { - /* Exact match. */ - if (!bacmp(&pi->src, src)) - break; - - /* Closest match */ - if (!bacmp(&pi->src, BDADDR_ANY)) - sk1 = sk; - } - } - - read_unlock(&l2cap_sk_list.lock); - - return sk ? sk : sk1; -} - -static void l2cap_sock_destruct(struct sock *sk) -{ - DBG("sk %p", sk); - - skb_queue_purge(&sk->receive_queue); - skb_queue_purge(&sk->write_queue); - - MOD_DEC_USE_COUNT; -} - -static void l2cap_sock_cleanup_listen(struct sock *parent) -{ - struct sock *sk; - - DBG("parent %p", parent); - - /* Close not yet accepted channels */ - while ((sk = l2cap_accept_dequeue(parent, 0))) - l2cap_sock_close(sk); - - parent->state = BT_CLOSED; - parent->zapped = 1; -} - -/* Kill socket (only if zapped and orphan) - * Must be called on unlocked socket. - */ -static void l2cap_sock_kill(struct sock *sk) -{ - if (!sk->zapped || sk->socket) - return; - - DBG("sk %p state %d", sk, sk->state); - - /* Kill poor orphan */ - bluez_sock_unlink(&l2cap_sk_list, sk); - sk->dead = 1; - sock_put(sk); -} - -/* Close socket. - * Must be called on unlocked socket. - */ -static void l2cap_sock_close(struct sock *sk) -{ - struct l2cap_conn *conn; - - l2cap_sock_clear_timer(sk); - - lock_sock(sk); - - conn = l2cap_pi(sk)->conn; - - DBG("sk %p state %d conn %p socket %p", sk, sk->state, conn, sk->socket); - - switch (sk->state) { - case BT_LISTEN: - l2cap_sock_cleanup_listen(sk); - break; - - case BT_CONNECTED: - case BT_CONFIG: - if (sk->type == SOCK_SEQPACKET) { - l2cap_disconn_req req; - - sk->state = BT_DISCONN; - - req.dcid = __cpu_to_le16(l2cap_pi(sk)->dcid); - req.scid = __cpu_to_le16(l2cap_pi(sk)->scid); - l2cap_send_req(conn, L2CAP_DISCONN_REQ, L2CAP_DISCONN_REQ_SIZE, &req); - - l2cap_sock_set_timer(sk, sk->sndtimeo); - } else { - l2cap_chan_del(sk, ECONNRESET); - } - break; - - case BT_CONNECT: - case BT_DISCONN: - l2cap_chan_del(sk, ECONNRESET); - break; - - default: - sk->zapped = 1; - break; - }; - - release_sock(sk); - - l2cap_sock_kill(sk); -} - -static void l2cap_sock_init(struct sock *sk, struct sock *parent) -{ - struct l2cap_pinfo *pi = l2cap_pi(sk); - - DBG("sk %p", sk); - - if (parent) { - sk->type = parent->type; - - pi->imtu = l2cap_pi(parent)->imtu; - pi->omtu = l2cap_pi(parent)->omtu; - } else { - pi->imtu = L2CAP_DEFAULT_MTU; - pi->omtu = 0; - } - - /* Default config options */ - pi->conf_mtu = L2CAP_DEFAULT_MTU; - pi->flush_to = L2CAP_DEFAULT_FLUSH_TO; -} - -static struct sock *l2cap_sock_alloc(struct socket *sock, int proto, int prio) -{ - struct sock *sk; - - if (!(sk = sk_alloc(PF_BLUETOOTH, prio, 1))) - return NULL; - - sock_init_data(sock, sk); - - sk->zapped = 0; - - sk->destruct = l2cap_sock_destruct; - sk->sndtimeo = L2CAP_CONN_TIMEOUT; - - sk->protocol = proto; - sk->state = BT_OPEN; - - l2cap_sock_init_timer(sk); - - bluez_sock_link(&l2cap_sk_list, sk); - - MOD_INC_USE_COUNT; - - return sk; -} - -static int l2cap_sock_create(struct socket *sock, int protocol) -{ - struct sock *sk; - - DBG("sock %p", sock); - - sock->state = SS_UNCONNECTED; - - if (sock->type != SOCK_SEQPACKET && sock->type != SOCK_RAW) - return -ESOCKTNOSUPPORT; - - sock->ops = &l2cap_sock_ops; - - if (!(sk = l2cap_sock_alloc(sock, protocol, GFP_KERNEL))) - return -ENOMEM; - - l2cap_sock_init(sk, NULL); - - return 0; -} - -static int l2cap_sock_bind(struct socket *sock, struct sockaddr *addr, int addr_len) -{ - struct sockaddr_l2 *la = (struct sockaddr_l2 *) addr; - struct sock *sk = sock->sk; - int err = 0; - - DBG("sk %p, %s %d", sk, batostr(&la->l2_bdaddr), la->l2_psm); - - if (!addr || addr->sa_family != AF_BLUETOOTH) - return -EINVAL; - - lock_sock(sk); - - if (sk->state != BT_OPEN) { - err = -EBADFD; - goto done; - } - - write_lock(&l2cap_sk_list.lock); - - if (la->l2_psm && __l2cap_get_sock_by_addr(la)) { - err = -EADDRINUSE; - goto unlock; - } - - /* Save source address */ - bacpy(&l2cap_pi(sk)->src, &la->l2_bdaddr); - l2cap_pi(sk)->psm = la->l2_psm; - sk->state = BT_BOUND; - -unlock: - write_unlock(&l2cap_sk_list.lock); - -done: - release_sock(sk); - - return err; -} - -static int l2cap_sock_w4_connect(struct sock *sk, int flags) -{ - DECLARE_WAITQUEUE(wait, current); - long timeo = sock_sndtimeo(sk, flags & O_NONBLOCK); - int err = 0; - - DBG("sk %p", sk); - - add_wait_queue(sk->sleep, &wait); - current->state = TASK_INTERRUPTIBLE; - - while (sk->state != BT_CONNECTED) { - if (!timeo) { - err = -EAGAIN; - break; - } - - release_sock(sk); - timeo = schedule_timeout(timeo); - lock_sock(sk); - - err = 0; - if (sk->state == BT_CONNECTED) - break; - - if (sk->err) { - err = sock_error(sk); - break; - } - - if (signal_pending(current)) { - err = sock_intr_errno(timeo); - break; - } - } - current->state = TASK_RUNNING; - remove_wait_queue(sk->sleep, &wait); - - return err; -} - -static int l2cap_sock_connect(struct socket *sock, struct sockaddr *addr, int alen, int flags) -{ - struct sockaddr_l2 *la = (struct sockaddr_l2 *) addr; - struct sock *sk = sock->sk; - int err = 0; - - lock_sock(sk); - - DBG("sk %p", sk); - - if (addr->sa_family != AF_BLUETOOTH || alen < sizeof(struct sockaddr_l2)) { - err = -EINVAL; - goto done; - } - - if (sk->state != BT_OPEN && sk->state != BT_BOUND) { - err = -EBADFD; - goto done; - } - - if (sk->type == SOCK_SEQPACKET && !la->l2_psm) { - err = -EINVAL; - goto done; - } - - /* Set destination address and psm */ - bacpy(&l2cap_pi(sk)->dst, &la->l2_bdaddr); - l2cap_pi(sk)->psm = la->l2_psm; - - if ((err = l2cap_connect(sk))) - goto done; - - err = l2cap_sock_w4_connect(sk, flags); - -done: - release_sock(sk); - return err; -} - -int l2cap_sock_listen(struct socket *sock, int backlog) -{ - struct sock *sk = sock->sk; - int err = 0; - - DBG("sk %p backlog %d", sk, backlog); - - lock_sock(sk); - - if (sk->state != BT_BOUND || sock->type != SOCK_SEQPACKET) { - err = -EBADFD; - goto done; - } - - if (!l2cap_pi(sk)->psm) { - err = -EINVAL; - goto done; - } - - sk->max_ack_backlog = backlog; - sk->ack_backlog = 0; - sk->state = BT_LISTEN; - -done: - release_sock(sk); - return err; -} - -int l2cap_sock_accept(struct socket *sock, struct socket *newsock, int flags) -{ - DECLARE_WAITQUEUE(wait, current); - struct sock *sk = sock->sk, *ch; - long timeo; - int err = 0; - - lock_sock(sk); - - if (sk->state != BT_LISTEN) { - err = -EBADFD; - goto done; - } - - timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK); - - DBG("sk %p timeo %ld", sk, timeo); - - /* Wait for an incoming connection. (wake-one). */ - add_wait_queue_exclusive(sk->sleep, &wait); - current->state = TASK_INTERRUPTIBLE; - while (!(ch = l2cap_accept_dequeue(sk, BT_CONNECTED))) { - if (!timeo) { - err = -EAGAIN; - break; - } - - release_sock(sk); - timeo = schedule_timeout(timeo); - lock_sock(sk); - - if (sk->state != BT_LISTEN) { - err = -EBADFD; - break; - } - - if (signal_pending(current)) { - err = sock_intr_errno(timeo); - break; - } - } - current->state = TASK_RUNNING; - remove_wait_queue(sk->sleep, &wait); - - if (err) - goto done; - - sock_graft(ch, newsock); - newsock->state = SS_CONNECTED; - - DBG("new socket %p", ch); - -done: - release_sock(sk); - - return err; -} - -static int l2cap_sock_getname(struct socket *sock, struct sockaddr *addr, int *len, int peer) -{ - struct sockaddr_l2 *la = (struct sockaddr_l2 *) addr; - struct sock *sk = sock->sk; - - DBG("sock %p, sk %p", sock, sk); - - addr->sa_family = AF_BLUETOOTH; - *len = sizeof(struct sockaddr_l2); - - if (peer) - bacpy(&la->l2_bdaddr, &l2cap_pi(sk)->dst); - else - bacpy(&la->l2_bdaddr, &l2cap_pi(sk)->src); - - la->l2_psm = l2cap_pi(sk)->psm; - - return 0; -} - -static int l2cap_sock_sendmsg(struct socket *sock, struct msghdr *msg, int len, struct scm_cookie *scm) -{ - struct sock *sk = sock->sk; - int err = 0; - - DBG("sock %p, sk %p", sock, sk); - - if (sk->err) - return sock_error(sk); - - if (msg->msg_flags & MSG_OOB) - return -EOPNOTSUPP; - - lock_sock(sk); - - if (sk->state == BT_CONNECTED) - err = l2cap_chan_send(sk, msg, len); - else - err = -ENOTCONN; - - release_sock(sk); - return err; -} - -static int l2cap_sock_recvmsg(struct socket *sock, struct msghdr *msg, int len, int flags, struct scm_cookie *scm) -{ - struct sock *sk = sock->sk; - int noblock = flags & MSG_DONTWAIT; - int copied, err; - struct sk_buff *skb; - - DBG("sock %p, sk %p", sock, sk); - - if (flags & (MSG_OOB)) - return -EOPNOTSUPP; - - if (sk->state == BT_CLOSED) - return 0; - - if (!(skb = skb_recv_datagram(sk, flags, noblock, &err))) - return err; - - msg->msg_namelen = 0; - - copied = skb->len; - if (len < copied) { - msg->msg_flags |= MSG_TRUNC; - copied = len; - } - - skb->h.raw = skb->data; - err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied); - - skb_free_datagram(sk, skb); - - return err ? : copied; -} - -int l2cap_sock_setsockopt(struct socket *sock, int level, int optname, char *optval, int optlen) -{ - struct sock *sk = sock->sk; - struct l2cap_options opts; - int err = 0; - - DBG("sk %p", sk); - - lock_sock(sk); - - switch (optname) { - case L2CAP_OPTIONS: - if (copy_from_user((char *)&opts, optval, optlen)) { - err = -EFAULT; - break; - } - l2cap_pi(sk)->imtu = opts.imtu; - l2cap_pi(sk)->omtu = opts.omtu; - break; - - default: - err = -ENOPROTOOPT; - break; - }; - - release_sock(sk); - return err; -} - -int l2cap_sock_getsockopt(struct socket *sock, int level, int optname, char *optval, int *optlen) -{ - struct sock *sk = sock->sk; - struct l2cap_options opts; - struct l2cap_conninfo cinfo; - int len, err = 0; - - if (get_user(len, optlen)) - return -EFAULT; - - lock_sock(sk); - - switch (optname) { - case L2CAP_OPTIONS: - opts.imtu = l2cap_pi(sk)->imtu; - opts.omtu = l2cap_pi(sk)->omtu; - opts.flush_to = l2cap_pi(sk)->flush_to; - - len = MIN(len, sizeof(opts)); - if (copy_to_user(optval, (char *)&opts, len)) - err = -EFAULT; - - break; - - case L2CAP_CONNINFO: - if (sk->state != BT_CONNECTED) { - err = -ENOTCONN; - break; - } - - cinfo.hci_handle = l2cap_pi(sk)->conn->hconn->handle; - - len = MIN(len, sizeof(cinfo)); - if (copy_to_user(optval, (char *)&cinfo, len)) - err = -EFAULT; - - break; - - default: - err = -ENOPROTOOPT; - break; - }; - - release_sock(sk); - return err; -} - -static unsigned int l2cap_sock_poll(struct file * file, struct socket *sock, poll_table *wait) -{ - struct sock *sk = sock->sk; - struct l2cap_accept_q *aq; - unsigned int mask; - - DBG("sock %p, sk %p", sock, sk); - - poll_wait(file, sk->sleep, wait); - mask = 0; - - if (sk->err || !skb_queue_empty(&sk->error_queue)) - mask |= POLLERR; - - if (sk->shutdown == SHUTDOWN_MASK) - mask |= POLLHUP; - - aq = &l2cap_pi(sk)->accept_q; - if (!skb_queue_empty(&sk->receive_queue) || aq->head || (sk->shutdown & RCV_SHUTDOWN)) - mask |= POLLIN | POLLRDNORM; - - if (sk->state == BT_CLOSED) - mask |= POLLHUP; - - if (sock_writeable(sk)) - mask |= POLLOUT | POLLWRNORM | POLLWRBAND; - else - set_bit(SOCK_ASYNC_NOSPACE, &sk->socket->flags); - - return mask; -} - -static int l2cap_sock_release(struct socket *sock) -{ - struct sock *sk = sock->sk; - - DBG("sock %p, sk %p", sock, sk); - - if (!sk) - return 0; - - sock_orphan(sk); - - l2cap_sock_close(sk); - - return 0; -} - -/* --------- L2CAP channels --------- */ -static struct sock * __l2cap_get_chan_by_dcid(struct l2cap_chan_list *l, __u16 cid) -{ - struct sock *s; - - for (s = l->head; s; s = l2cap_pi(s)->next_c) { - if (l2cap_pi(s)->dcid == cid) - break; - } - - return s; -} - -static inline struct sock *l2cap_get_chan_by_dcid(struct l2cap_chan_list *l, __u16 cid) -{ - struct sock *s; - - read_lock(&l->lock); - s = __l2cap_get_chan_by_dcid(l, cid); - read_unlock(&l->lock); - - return s; -} - -static struct sock *__l2cap_get_chan_by_scid(struct l2cap_chan_list *l, __u16 cid) -{ - struct sock *s; - - for (s = l->head; s; s = l2cap_pi(s)->next_c) { - if (l2cap_pi(s)->scid == cid) - break; - } - - return s; -} -static inline struct sock *l2cap_get_chan_by_scid(struct l2cap_chan_list *l, __u16 cid) -{ - struct sock *s; - - read_lock(&l->lock); - s = __l2cap_get_chan_by_scid(l, cid); - read_unlock(&l->lock); - - return s; -} - -static struct sock *__l2cap_get_chan_by_ident(struct l2cap_chan_list *l, __u8 ident) -{ - struct sock *s; - - for (s = l->head; s; s = l2cap_pi(s)->next_c) { - if (l2cap_pi(s)->ident == ident) - break; - } - - return s; -} - -static inline struct sock *l2cap_get_chan_by_ident(struct l2cap_chan_list *l, __u8 ident) -{ - struct sock *s; - - read_lock(&l->lock); - s = __l2cap_get_chan_by_ident(l, ident); - read_unlock(&l->lock); - - return s; -} - -static __u16 l2cap_alloc_cid(struct l2cap_chan_list *l) -{ - __u16 cid = 0x0040; - - for (; cid < 0xffff; cid++) { - if(!__l2cap_get_chan_by_scid(l, cid)) - return cid; - } - - return 0; -} - -static inline void __l2cap_chan_link(struct l2cap_chan_list *l, struct sock *sk) -{ - sock_hold(sk); - - if (l->head) - l2cap_pi(l->head)->prev_c = sk; - - l2cap_pi(sk)->next_c = l->head; - l2cap_pi(sk)->prev_c = NULL; - l->head = sk; -} - -static inline void l2cap_chan_unlink(struct l2cap_chan_list *l, struct sock *sk) -{ - struct sock *next = l2cap_pi(sk)->next_c, *prev = l2cap_pi(sk)->prev_c; - - write_lock(&l->lock); - if (sk == l->head) - l->head = next; - - if (next) - l2cap_pi(next)->prev_c = prev; - if (prev) - l2cap_pi(prev)->next_c = next; - write_unlock(&l->lock); - - __sock_put(sk); -} - -static void __l2cap_chan_add(struct l2cap_conn *conn, struct sock *sk, struct sock *parent) -{ - struct l2cap_chan_list *l = &conn->chan_list; - - DBG("conn %p, psm 0x%2.2x, dcid 0x%4.4x", conn, l2cap_pi(sk)->psm, l2cap_pi(sk)->dcid); - - l2cap_conn_clear_timer(conn); - - atomic_inc(&conn->refcnt); - l2cap_pi(sk)->conn = conn; - - if (sk->type == SOCK_SEQPACKET) { - /* Alloc CID for normal socket */ - l2cap_pi(sk)->scid = l2cap_alloc_cid(l); - } else { - /* Raw socket can send only signalling messages */ - l2cap_pi(sk)->scid = 0x0001; - l2cap_pi(sk)->dcid = 0x0001; - l2cap_pi(sk)->omtu = L2CAP_DEFAULT_MTU; - } - - __l2cap_chan_link(l, sk); - - if (parent) - l2cap_accept_queue(parent, sk); -} - -static inline void l2cap_chan_add(struct l2cap_conn *conn, struct sock *sk, struct sock *parent) -{ - struct l2cap_chan_list *l = &conn->chan_list; - - write_lock(&l->lock); - __l2cap_chan_add(conn, sk, parent); - write_unlock(&l->lock); -} - -/* Delete channel. - * Must be called on the locked socket. */ -static void l2cap_chan_del(struct sock *sk, int err) -{ - struct l2cap_conn *conn; - struct sock *parent; - - conn = l2cap_pi(sk)->conn; - parent = l2cap_pi(sk)->parent; - - DBG("sk %p, conn %p, err %d", sk, conn, err); - - if (parent) { - /* Unlink from parent accept queue */ - bh_lock_sock(parent); - l2cap_accept_unlink(sk); - bh_unlock_sock(parent); - } - - if (conn) { - long timeout; - - /* Unlink from channel list */ - l2cap_chan_unlink(&conn->chan_list, sk); - l2cap_pi(sk)->conn = NULL; - - if (conn->out) - timeout = L2CAP_DISCONN_TIMEOUT; - else - timeout = L2CAP_CONN_IDLE_TIMEOUT; - - if (atomic_dec_and_test(&conn->refcnt) && conn->state == BT_CONNECTED) { - /* Schedule Baseband disconnect */ - l2cap_conn_set_timer(conn, timeout); - } - } - - sk->state = BT_CLOSED; - sk->err = err; - sk->state_change(sk); - - sk->zapped = 1; -} - -static void l2cap_conn_ready(struct l2cap_conn *conn) -{ - struct l2cap_chan_list *l = &conn->chan_list; - struct sock *sk; - - DBG("conn %p", conn); - - read_lock(&l->lock); - - for (sk = l->head; sk; sk = l2cap_pi(sk)->next_c) { - bh_lock_sock(sk); - - if (sk->type != SOCK_SEQPACKET) { - sk->state = BT_CONNECTED; - sk->state_change(sk); - l2cap_sock_clear_timer(sk); - } else if (sk->state == BT_CONNECT) { - l2cap_conn_req req; - req.scid = __cpu_to_le16(l2cap_pi(sk)->scid); - req.psm = l2cap_pi(sk)->psm; - l2cap_send_req(conn, L2CAP_CONN_REQ, L2CAP_CONN_REQ_SIZE, &req); - - l2cap_sock_set_timer(sk, sk->sndtimeo); - } - - bh_unlock_sock(sk); - } - - read_unlock(&l->lock); -} - -static void l2cap_chan_ready(struct sock *sk) -{ - struct sock *parent = l2cap_pi(sk)->parent; - - DBG("sk %p, parent %p", sk, parent); - - l2cap_pi(sk)->conf_state = 0; - l2cap_sock_clear_timer(sk); - - if (!parent) { - /* Outgoing channel. - * Wake up socket sleeping on connect. - */ - sk->state = BT_CONNECTED; - sk->state_change(sk); - } else { - /* Incomming channel. - * Wake up socket sleeping on accept. - */ - parent->data_ready(parent, 1); - } -} - -/* Copy frame to all raw sockets on that connection */ -void l2cap_raw_recv(struct l2cap_conn *conn, struct sk_buff *skb) -{ - struct l2cap_chan_list *l = &conn->chan_list; - struct sk_buff *nskb; - struct sock * sk; - - DBG("conn %p", conn); - - read_lock(&l->lock); - for (sk = l->head; sk; sk = l2cap_pi(sk)->next_c) { - if (sk->type != SOCK_RAW) - continue; - - /* Don't send frame to the socket it came from */ - if (skb->sk == sk) - continue; - - if (!(nskb = skb_clone(skb, GFP_ATOMIC))) - continue; - - skb_queue_tail(&sk->receive_queue, nskb); - sk->data_ready(sk, nskb->len); - } - read_unlock(&l->lock); -} - -static int l2cap_chan_send(struct sock *sk, struct msghdr *msg, int len) -{ - struct l2cap_conn *conn = l2cap_pi(sk)->conn; - struct sk_buff *skb, **frag; - int err, size, count, sent=0; - l2cap_hdr *lh; - - /* Check outgoing MTU */ - if (len > l2cap_pi(sk)->omtu) - return -EINVAL; - - DBG("sk %p len %d", sk, len); - - /* First fragment (with L2CAP header) */ - count = MIN(conn->iff->mtu - L2CAP_HDR_SIZE, len); - size = L2CAP_HDR_SIZE + count; - if (!(skb = bluez_skb_send_alloc(sk, size, msg->msg_flags & MSG_DONTWAIT, &err))) - return err; - - /* Create L2CAP header */ - lh = (l2cap_hdr *) skb_put(skb, L2CAP_HDR_SIZE); - lh->len = __cpu_to_le16(len); - lh->cid = __cpu_to_le16(l2cap_pi(sk)->dcid); - - if (memcpy_fromiovec(skb_put(skb, count), msg->msg_iov, count)) { - err = -EFAULT; - goto fail; - } - - sent += count; - len -= count; - - /* Continuation fragments (no L2CAP header) */ - frag = &skb_shinfo(skb)->frag_list; - while (len) { - count = MIN(conn->iff->mtu, len); - - *frag = bluez_skb_send_alloc(sk, count, msg->msg_flags & MSG_DONTWAIT, &err); - if (!*frag) - goto fail; - - if (memcpy_fromiovec(skb_put(*frag, count), msg->msg_iov, count)) { - err = -EFAULT; - goto fail; - } - - sent += count; - len -= count; - - frag = &(*frag)->next; - } - - if ((err = hci_send_acl(conn->hconn, skb, 0)) < 0) - goto fail; - - return sent; - -fail: - kfree_skb(skb); - return err; -} - -/* --------- L2CAP signalling commands --------- */ -static inline __u8 l2cap_get_ident(struct l2cap_conn *conn) -{ - __u8 id; - - /* Get next available identificator. - * 1 - 199 are used by kernel. - * 200 - 254 are used by utilities like l2ping, etc - */ - - spin_lock(&conn->lock); - - if (++conn->tx_ident > 199) - conn->tx_ident = 1; - - id = conn->tx_ident; - - spin_unlock(&conn->lock); - - return id; -} - -static inline struct sk_buff *l2cap_build_cmd(__u8 code, __u8 ident, __u16 len, void *data) -{ - struct sk_buff *skb; - l2cap_cmd_hdr *cmd; - l2cap_hdr *lh; - int size; - - DBG("code 0x%2.2x, ident 0x%2.2x, len %d", code, ident, len); - - size = L2CAP_HDR_SIZE + L2CAP_CMD_HDR_SIZE + len; - if (!(skb = bluez_skb_alloc(size, GFP_ATOMIC))) - return NULL; - - lh = (l2cap_hdr *) skb_put(skb, L2CAP_HDR_SIZE); - lh->len = __cpu_to_le16(L2CAP_CMD_HDR_SIZE + len); - lh->cid = __cpu_to_le16(0x0001); - - cmd = (l2cap_cmd_hdr *) skb_put(skb, L2CAP_CMD_HDR_SIZE); - cmd->code = code; - cmd->ident = ident; - cmd->len = __cpu_to_le16(len); - - if (len) - memcpy(skb_put(skb, len), data, len); - - return skb; -} - -static int l2cap_send_req(struct l2cap_conn *conn, __u8 code, __u16 len, void *data) -{ - struct sk_buff *skb; - __u8 ident; - - DBG("code 0x%2.2x", code); - - ident = l2cap_get_ident(conn); - if (!(skb = l2cap_build_cmd(code, ident, len, data))) - return -ENOMEM; - return hci_send_acl(conn->hconn, skb, 0); -} - -static int l2cap_send_rsp(struct l2cap_conn *conn, __u8 ident, __u8 code, __u16 len, void *data) -{ - struct sk_buff *skb; - - DBG("code 0x%2.2x", code); - - if (!(skb = l2cap_build_cmd(code, ident, len, data))) - return -ENOMEM; - return hci_send_acl(conn->hconn, skb, 0); -} - -static inline int l2cap_get_conf_opt(__u8 **ptr, __u8 *type, __u32 *val) -{ - l2cap_conf_opt *opt = (l2cap_conf_opt *) (*ptr); - int len; - - *type = opt->type; - switch (opt->len) { - case 1: - *val = *((__u8 *) opt->val); - break; - - case 2: - *val = __le16_to_cpu(*((__u16 *)opt->val)); - break; - - case 4: - *val = __le32_to_cpu(*((__u32 *)opt->val)); - break; - - default: - *val = 0L; - break; - }; - - DBG("type 0x%2.2x len %d val 0x%8.8x", *type, opt->len, *val); - - len = L2CAP_CONF_OPT_SIZE + opt->len; - - *ptr += len; - - return len; -} - -static inline void l2cap_parse_conf_req(struct sock *sk, char *data, int len) -{ - __u8 type, hint; __u32 val; - __u8 *ptr = data; - - DBG("sk %p len %d", sk, len); - - while (len >= L2CAP_CONF_OPT_SIZE) { - len -= l2cap_get_conf_opt(&ptr, &type, &val); - - hint = type & 0x80; - type &= 0x7f; - - switch (type) { - case L2CAP_CONF_MTU: - l2cap_pi(sk)->conf_mtu = val; - break; - - case L2CAP_CONF_FLUSH_TO: - l2cap_pi(sk)->flush_to = val; - break; - - case L2CAP_CONF_QOS: - break; - - default: - if (hint) - break; - - /* FIXME: Reject unknon option */ - break; - }; - } -} - -static inline void l2cap_add_conf_opt(__u8 **ptr, __u8 type, __u8 len, __u32 val) -{ - register l2cap_conf_opt *opt = (l2cap_conf_opt *) (*ptr); - - DBG("type 0x%2.2x len %d val 0x%8.8x", type, len, val); - - opt->type = type; - opt->len = len; - switch (len) { - case 1: - *((__u8 *) opt->val) = val; - break; - - case 2: - *((__u16 *) opt->val) = __cpu_to_le16(val); - break; - - case 4: - *((__u32 *) opt->val) = __cpu_to_le32(val); - break; - }; - - *ptr += L2CAP_CONF_OPT_SIZE + len; -} - -static int l2cap_build_conf_req(struct sock *sk, __u8 *data) -{ - struct l2cap_pinfo *pi = l2cap_pi(sk); - l2cap_conf_req *req = (l2cap_conf_req *) data; - __u8 *ptr = req->data; - - DBG("sk %p", sk); - - if (pi->imtu != L2CAP_DEFAULT_MTU) - l2cap_add_conf_opt(&ptr, L2CAP_CONF_MTU, 2, pi->imtu); - - /* FIXME. Need actual value of the flush timeout */ - //if (flush_to != L2CAP_DEFAULT_FLUSH_TO) - // l2cap_add_conf_opt(&ptr, L2CAP_CONF_FLUSH_TO, 2, pi->flush_to); - - req->dcid = __cpu_to_le16(pi->dcid); - req->flags = __cpu_to_le16(0); - - return ptr - data; -} - -static int l2cap_conf_output(struct sock *sk, __u8 **ptr) -{ - struct l2cap_pinfo *pi = l2cap_pi(sk); - int result = 0; - - /* Configure output options and let other side know - * which ones we don't like. - */ - if (pi->conf_mtu < pi->omtu) { - l2cap_add_conf_opt(ptr, L2CAP_CONF_MTU, 2, l2cap_pi(sk)->omtu); - result = L2CAP_CONF_UNACCEPT; - } else { - pi->omtu = pi->conf_mtu; - } - - DBG("sk %p result %d", sk, result); - return result; -} - -static int l2cap_build_conf_rsp(struct sock *sk, __u8 *data, int *result) -{ - l2cap_conf_rsp *rsp = (l2cap_conf_rsp *) data; - __u8 *ptr = rsp->data; - - DBG("sk %p complete %d", sk, result ? 1 : 0); - - if (result) - *result = l2cap_conf_output(sk, &ptr); - - rsp->scid = __cpu_to_le16(l2cap_pi(sk)->dcid); - rsp->result = __cpu_to_le16(result ? *result : 0); - rsp->flags = __cpu_to_le16(0); - - return ptr - data; -} - -static inline int l2cap_connect_req(struct l2cap_conn *conn, l2cap_cmd_hdr *cmd, __u8 *data) -{ - struct l2cap_chan_list *list = &conn->chan_list; - l2cap_conn_req *req = (l2cap_conn_req *) data; - l2cap_conn_rsp rsp; - struct sock *sk, *parent; - - __u16 scid = __le16_to_cpu(req->scid); - __u16 psm = req->psm; - - DBG("psm 0x%2.2x scid 0x%4.4x", psm, scid); - - /* Check if we have socket listening on psm */ - if (!(parent = l2cap_get_sock_listen(&conn->src, psm))) - goto reject; - - bh_lock_sock(parent); - write_lock(&list->lock); - - /* Check if we already have channel with that dcid */ - if (__l2cap_get_chan_by_dcid(list, scid)) - goto unlock; - - /* Check for backlog size */ - if (parent->ack_backlog > parent->max_ack_backlog) - goto unlock; - - if (!(sk = l2cap_sock_alloc(NULL, BTPROTO_L2CAP, GFP_ATOMIC))) - goto unlock; - - l2cap_sock_init(sk, parent); - - bacpy(&l2cap_pi(sk)->src, &conn->src); - bacpy(&l2cap_pi(sk)->dst, &conn->dst); - l2cap_pi(sk)->psm = psm; - l2cap_pi(sk)->dcid = scid; - - __l2cap_chan_add(conn, sk, parent); - sk->state = BT_CONFIG; - - write_unlock(&list->lock); - bh_unlock_sock(parent); - - rsp.dcid = __cpu_to_le16(l2cap_pi(sk)->scid); - rsp.scid = __cpu_to_le16(l2cap_pi(sk)->dcid); - rsp.result = __cpu_to_le16(0); - rsp.status = __cpu_to_le16(0); - l2cap_send_rsp(conn, cmd->ident, L2CAP_CONN_RSP, L2CAP_CONN_RSP_SIZE, &rsp); - - return 0; - -unlock: - write_unlock(&list->lock); - bh_unlock_sock(parent); - -reject: - rsp.scid = __cpu_to_le16(scid); - rsp.dcid = __cpu_to_le16(0); - rsp.status = __cpu_to_le16(0); - rsp.result = __cpu_to_le16(L2CAP_CONN_NO_MEM); - l2cap_send_rsp(conn, cmd->ident, L2CAP_CONN_RSP, L2CAP_CONN_RSP_SIZE, &rsp); - - return 0; -} - -static inline int l2cap_connect_rsp(struct l2cap_conn *conn, l2cap_cmd_hdr *cmd, __u8 *data) -{ - l2cap_conn_rsp *rsp = (l2cap_conn_rsp *) data; - __u16 scid, dcid, result, status; - struct sock *sk; - - scid = __le16_to_cpu(rsp->scid); - dcid = __le16_to_cpu(rsp->dcid); - result = __le16_to_cpu(rsp->result); - status = __le16_to_cpu(rsp->status); - - DBG("dcid 0x%4.4x scid 0x%4.4x result 0x%2.2x status 0x%2.2x", dcid, scid, result, status); - - if (!(sk = l2cap_get_chan_by_scid(&conn->chan_list, scid))) - return -ENOENT; - - bh_lock_sock(sk); - - if (!result) { - char req[64]; - - sk->state = BT_CONFIG; - l2cap_pi(sk)->dcid = dcid; - l2cap_pi(sk)->conf_state |= CONF_REQ_SENT; - - l2cap_send_req(conn, L2CAP_CONF_REQ, l2cap_build_conf_req(sk, req), req); - } else { - l2cap_chan_del(sk, ECONNREFUSED); - } - - bh_unlock_sock(sk); - return 0; -} - -static inline int l2cap_config_req(struct l2cap_conn *conn, l2cap_cmd_hdr *cmd, __u8 *data) -{ - l2cap_conf_req * req = (l2cap_conf_req *) data; - __u16 dcid, flags; - __u8 rsp[64]; - struct sock *sk; - int result; - - dcid = __le16_to_cpu(req->dcid); - flags = __le16_to_cpu(req->flags); - - DBG("dcid 0x%4.4x flags 0x%2.2x", dcid, flags); - - if (!(sk = l2cap_get_chan_by_scid(&conn->chan_list, dcid))) - return -ENOENT; - - bh_lock_sock(sk); - - l2cap_parse_conf_req(sk, req->data, cmd->len - L2CAP_CONF_REQ_SIZE); - - if (flags & 0x01) { - /* Incomplete config. Send empty response. */ - l2cap_send_rsp(conn, cmd->ident, L2CAP_CONF_RSP, l2cap_build_conf_rsp(sk, rsp, NULL), rsp); - goto unlock; - } - - /* Complete config. */ - l2cap_send_rsp(conn, cmd->ident, L2CAP_CONF_RSP, l2cap_build_conf_rsp(sk, rsp, &result), rsp); - - if (result) - goto unlock; - - /* Output config done */ - l2cap_pi(sk)->conf_state |= CONF_OUTPUT_DONE; - - if (l2cap_pi(sk)->conf_state & CONF_INPUT_DONE) { - sk->state = BT_CONNECTED; - l2cap_chan_ready(sk); - } else if (!(l2cap_pi(sk)->conf_state & CONF_REQ_SENT)) { - char req[64]; - l2cap_send_req(conn, L2CAP_CONF_REQ, l2cap_build_conf_req(sk, req), req); - } - -unlock: - bh_unlock_sock(sk); - - return 0; -} - -static inline int l2cap_config_rsp(struct l2cap_conn *conn, l2cap_cmd_hdr *cmd, __u8 *data) -{ - l2cap_conf_rsp *rsp = (l2cap_conf_rsp *)data; - __u16 scid, flags, result; - struct sock *sk; - int err = 0; - - scid = __le16_to_cpu(rsp->scid); - flags = __le16_to_cpu(rsp->flags); - result = __le16_to_cpu(rsp->result); - - DBG("scid 0x%4.4x flags 0x%2.2x result 0x%2.2x", scid, flags, result); - - if (!(sk = l2cap_get_chan_by_scid(&conn->chan_list, scid))) - return -ENOENT; - - bh_lock_sock(sk); - - if (result) { - l2cap_disconn_req req; - - /* They didn't like our options. Well... we do not negotiate. - * Close channel. - */ - sk->state = BT_DISCONN; - - req.dcid = __cpu_to_le16(l2cap_pi(sk)->dcid); - req.scid = __cpu_to_le16(l2cap_pi(sk)->scid); - l2cap_send_req(conn, L2CAP_DISCONN_REQ, L2CAP_DISCONN_REQ_SIZE, &req); - - l2cap_sock_set_timer(sk, sk->sndtimeo); - goto done; - } - - if (flags & 0x01) - goto done; - - /* Input config done */ - l2cap_pi(sk)->conf_state |= CONF_INPUT_DONE; - - if (l2cap_pi(sk)->conf_state & CONF_OUTPUT_DONE) { - sk->state = BT_CONNECTED; - l2cap_chan_ready(sk); - } - -done: - bh_unlock_sock(sk); - - return err; -} - -static inline int l2cap_disconnect_req(struct l2cap_conn *conn, l2cap_cmd_hdr *cmd, __u8 *data) -{ - l2cap_disconn_req *req = (l2cap_disconn_req *) data; - l2cap_disconn_rsp rsp; - __u16 dcid, scid; - struct sock *sk; - - scid = __le16_to_cpu(req->scid); - dcid = __le16_to_cpu(req->dcid); - - DBG("scid 0x%4.4x dcid 0x%4.4x", scid, dcid); - - if (!(sk = l2cap_get_chan_by_scid(&conn->chan_list, dcid))) - return 0; - - bh_lock_sock(sk); - - rsp.dcid = __cpu_to_le16(l2cap_pi(sk)->scid); - rsp.scid = __cpu_to_le16(l2cap_pi(sk)->dcid); - l2cap_send_rsp(conn, cmd->ident, L2CAP_DISCONN_RSP, L2CAP_DISCONN_RSP_SIZE, &rsp); - - l2cap_chan_del(sk, ECONNRESET); - - bh_unlock_sock(sk); - - l2cap_sock_kill(sk); - - return 0; -} - -static inline int l2cap_disconnect_rsp(struct l2cap_conn *conn, l2cap_cmd_hdr *cmd, __u8 *data) -{ - l2cap_disconn_rsp *rsp = (l2cap_disconn_rsp *) data; - __u16 dcid, scid; - struct sock *sk; - - scid = __le16_to_cpu(rsp->scid); - dcid = __le16_to_cpu(rsp->dcid); - - DBG("dcid 0x%4.4x scid 0x%4.4x", dcid, scid); - - if (!(sk = l2cap_get_chan_by_scid(&conn->chan_list, scid))) - return -ENOENT; - - bh_lock_sock(sk); - l2cap_sock_clear_timer(sk); - l2cap_chan_del(sk, ECONNABORTED); - bh_unlock_sock(sk); - - l2cap_sock_kill(sk); - - return 0; -} - -static inline void l2cap_sig_channel(struct l2cap_conn *conn, struct sk_buff *skb) -{ - __u8 *data = skb->data; - int len = skb->len; - l2cap_cmd_hdr cmd; - int err = 0; - - while (len >= L2CAP_CMD_HDR_SIZE) { - memcpy(&cmd, data, L2CAP_CMD_HDR_SIZE); - data += L2CAP_CMD_HDR_SIZE; - len -= L2CAP_CMD_HDR_SIZE; - - cmd.len = __le16_to_cpu(cmd.len); - - DBG("code 0x%2.2x len %d id 0x%2.2x", cmd.code, cmd.len, cmd.ident); - - if (cmd.len > len || !cmd.ident) { - DBG("corrupted command"); - break; - } - - switch (cmd.code) { - case L2CAP_CONN_REQ: - err = l2cap_connect_req(conn, &cmd, data); - break; - - case L2CAP_CONN_RSP: - err = l2cap_connect_rsp(conn, &cmd, data); - break; - - case L2CAP_CONF_REQ: - err = l2cap_config_req(conn, &cmd, data); - break; - - case L2CAP_CONF_RSP: - err = l2cap_config_rsp(conn, &cmd, data); - break; - - case L2CAP_DISCONN_REQ: - err = l2cap_disconnect_req(conn, &cmd, data); - break; - - case L2CAP_DISCONN_RSP: - err = l2cap_disconnect_rsp(conn, &cmd, data); - break; - - case L2CAP_COMMAND_REJ: - /* FIXME: We should process this */ - l2cap_raw_recv(conn, skb); - break; - - case L2CAP_ECHO_REQ: - l2cap_send_rsp(conn, cmd.ident, L2CAP_ECHO_RSP, cmd.len, data); - break; - - case L2CAP_ECHO_RSP: - case L2CAP_INFO_REQ: - case L2CAP_INFO_RSP: - l2cap_raw_recv(conn, skb); - break; - - default: - ERR("Uknown signaling command 0x%2.2x", cmd.code); - err = -EINVAL; - break; - }; - - if (err) { - l2cap_cmd_rej rej; - DBG("error %d", err); - - /* FIXME: Map err to a valid reason. */ - rej.reason = __cpu_to_le16(0); - l2cap_send_rsp(conn, cmd.ident, L2CAP_COMMAND_REJ, L2CAP_CMD_REJ_SIZE, &rej); - } - - data += cmd.len; - len -= cmd.len; - } - - kfree_skb(skb); -} - -static inline int l2cap_data_channel(struct l2cap_conn *conn, __u16 cid, struct sk_buff *skb) -{ - struct sock *sk; - - if (!(sk = l2cap_get_chan_by_scid(&conn->chan_list, cid))) { - DBG("unknown cid 0x%4.4x", cid); - goto drop; - } - - DBG("sk %p, len %d", sk, skb->len); - - if (sk->state != BT_CONNECTED) - goto drop; - - if (l2cap_pi(sk)->imtu < skb->len) - goto drop; - - skb_queue_tail(&sk->receive_queue, skb); - sk->data_ready(sk, skb->len); - - return 0; - -drop: - kfree_skb(skb); - - return 0; -} - -static void l2cap_recv_frame(struct l2cap_conn *conn, struct sk_buff *skb) -{ - l2cap_hdr *lh = (l2cap_hdr *) skb->data; - __u16 cid, len; - - skb_pull(skb, L2CAP_HDR_SIZE); - cid = __le16_to_cpu(lh->cid); - len = __le16_to_cpu(lh->len); - - DBG("len %d, cid 0x%4.4x", len, cid); - - if (cid == 0x0001) - l2cap_sig_channel(conn, skb); - else - l2cap_data_channel(conn, cid, skb); -} - -/* ------------ L2CAP interface with lower layer (HCI) ------------- */ -static int l2cap_dev_event(struct notifier_block *this, unsigned long event, void *ptr) -{ - struct hci_dev *hdev = (struct hci_dev *) ptr; - - DBG("hdev %s, event %ld", hdev->name, event); - - write_lock(&l2cap_rt_lock); - - switch (event) { - case HCI_DEV_UP: - l2cap_iff_add(hdev); - break; - - case HCI_DEV_DOWN: - l2cap_iff_del(hdev); - break; - }; - - write_unlock(&l2cap_rt_lock); - - return NOTIFY_DONE; -} - -int l2cap_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr) -{ - struct l2cap_iff *iff; - - DBG("hdev %s, bdaddr %s", hdev->name, batostr(bdaddr)); - - if (!(iff = hdev->l2cap_data)) { - ERR("unknown interface"); - return 0; - } - - /* Always accept connection */ - return 1; -} - -int l2cap_connect_cfm(struct hci_dev *hdev, bdaddr_t *bdaddr, __u8 status, struct hci_conn *hconn) -{ - struct l2cap_conn *conn; - struct l2cap_iff *iff; - int err = 0; - - DBG("hdev %s bdaddr %s hconn %p", hdev->name, batostr(bdaddr), hconn); - - if (!(iff = hdev->l2cap_data)) { - ERR("unknown interface"); - return 0; - } - - l2cap_iff_lock(iff); - - conn = l2cap_get_conn_by_addr(iff, bdaddr); - - if (conn) { - /* Outgoing connection */ - DBG("Outgoing connection: %s -> %s, %p, %2.2x", batostr(iff->bdaddr), batostr(bdaddr), conn, status); - - if (!status && hconn) { - conn->state = BT_CONNECTED; - conn->hconn = hconn; - - hconn->l2cap_data = (void *)conn; - - /* Establish channels */ - l2cap_conn_ready(conn); - } else { - l2cap_conn_del(conn, bterr(status)); - } - } else { - /* Incomming connection */ - DBG("Incomming connection: %s -> %s, %2.2x", batostr(iff->bdaddr), batostr(bdaddr), status); - - if (status || !hconn) - goto done; - - if (!(conn = l2cap_conn_add(iff, bdaddr))) { - err = -ENOMEM; - goto done; - } - - conn->hconn = hconn; - hconn->l2cap_data = (void *)conn; - - conn->state = BT_CONNECTED; - } - -done: - l2cap_iff_unlock(iff); - - return err; -} - -int l2cap_disconn_ind(struct hci_conn *hconn, __u8 reason) -{ - struct l2cap_conn *conn = hconn->l2cap_data; - - DBG("hconn %p reason %d", hconn, reason); - - if (!conn) { - ERR("unknown connection"); - return 0; - } - conn->hconn = NULL; - - l2cap_iff_lock(conn->iff); - l2cap_conn_del(conn, bterr(reason)); - l2cap_iff_unlock(conn->iff); - - return 0; -} - -int l2cap_recv_acldata(struct hci_conn *hconn, struct sk_buff *skb, __u16 flags) -{ - struct l2cap_conn *conn = hconn->l2cap_data; - - if (!conn) { - ERR("unknown connection %p", hconn); - goto drop; - } - - DBG("conn %p len %d flags 0x%x", conn, skb->len, flags); - - if (flags & ACL_START) { - int flen, tlen, size; - l2cap_hdr *lh; - - if (conn->rx_len) { - ERR("Unexpected start frame (len %d)", skb->len); - kfree_skb(conn->rx_skb); conn->rx_skb = NULL; - conn->rx_len = 0; - } - - if (skb->len < L2CAP_HDR_SIZE) { - ERR("Frame is too small (len %d)", skb->len); - goto drop; - } - - lh = (l2cap_hdr *)skb->data; - tlen = __le16_to_cpu(lh->len); - flen = skb->len - L2CAP_HDR_SIZE; - - DBG("Start: total len %d, frag len %d", tlen, flen); - - if (flen == tlen) { - /* Complete frame received */ - l2cap_recv_frame(conn, skb); - return 0; - } - - /* Allocate skb for the complete frame (with header) */ - size = L2CAP_HDR_SIZE + tlen; - if (!(conn->rx_skb = bluez_skb_alloc(size, GFP_ATOMIC))) - goto drop; - - memcpy(skb_put(conn->rx_skb, skb->len), skb->data, skb->len); - - conn->rx_len = tlen - flen; - } else { - DBG("Cont: frag len %d (expecting %d)", skb->len, conn->rx_len); - - if (!conn->rx_len) { - ERR("Unexpected continuation frame (len %d)", skb->len); - goto drop; - } - - if (skb->len > conn->rx_len) { - ERR("Fragment is too large (len %d)", skb->len); - kfree_skb(conn->rx_skb); conn->rx_skb = NULL; - goto drop; - } - - memcpy(skb_put(conn->rx_skb, skb->len), skb->data, skb->len); - conn->rx_len -= skb->len; - - if (!conn->rx_len) { - /* Complete frame received */ - l2cap_recv_frame(conn, conn->rx_skb); - conn->rx_skb = NULL; - } - } - -drop: - kfree_skb(skb); - return 0; -} - -struct proto_ops l2cap_sock_ops = { - family: PF_BLUETOOTH, - release: l2cap_sock_release, - bind: l2cap_sock_bind, - connect: l2cap_sock_connect, - listen: l2cap_sock_listen, - accept: l2cap_sock_accept, - getname: l2cap_sock_getname, - sendmsg: l2cap_sock_sendmsg, - recvmsg: l2cap_sock_recvmsg, - poll: l2cap_sock_poll, - socketpair: sock_no_socketpair, - ioctl: sock_no_ioctl, - shutdown: sock_no_shutdown, - setsockopt: l2cap_sock_setsockopt, - getsockopt: l2cap_sock_getsockopt, - mmap: sock_no_mmap -}; - -struct net_proto_family l2cap_sock_family_ops = { - family: PF_BLUETOOTH, - create: l2cap_sock_create -}; - -struct hci_proto l2cap_hci_proto = { - name: "L2CAP", - id: HCI_PROTO_L2CAP, - connect_ind: l2cap_connect_ind, - connect_cfm: l2cap_connect_cfm, - disconn_ind: l2cap_disconn_ind, - recv_acldata: l2cap_recv_acldata, -}; - -struct notifier_block l2cap_nblock = { - notifier_call: l2cap_dev_event -}; - -int __init l2cap_init(void) -{ - INF("BlueZ L2CAP ver %s Copyright (C) 2000,2001 Qualcomm Inc", - VERSION); - INF("Written 2000,2001 by Maxim Krasnyansky "); - - if (bluez_sock_register(BTPROTO_L2CAP, &l2cap_sock_family_ops)) { - ERR("Can't register L2CAP socket"); - return -EPROTO; - } - - if (hci_register_proto(&l2cap_hci_proto) < 0) { - ERR("Can't register L2CAP protocol"); - return -EPROTO; - } - - hci_register_notifier(&l2cap_nblock); - - l2cap_register_proc(); - - return 0; -} - -void l2cap_cleanup(void) -{ - l2cap_unregister_proc(); - - /* Unregister socket, protocol and notifier */ - if (bluez_sock_unregister(BTPROTO_L2CAP)) - ERR("Can't unregister L2CAP socket"); - - if (hci_unregister_proto(&l2cap_hci_proto) < 0) - ERR("Can't unregister L2CAP protocol"); - - hci_unregister_notifier(&l2cap_nblock); - - /* We _must_ not have any sockets and/or connections - * at this stage. - */ - - /* Free interface list and unlock HCI devices */ - { - struct list_head *list = &l2cap_iff_list; - - while (!list_empty(list)) { - struct l2cap_iff *iff; - - iff = list_entry(list->next, struct l2cap_iff, list); - l2cap_iff_del(iff->hdev); - } - } -} - -module_init(l2cap_init); -module_exit(l2cap_cleanup); - -MODULE_AUTHOR("Maxim Krasnyansky "); -MODULE_DESCRIPTION("BlueZ L2CAP ver " VERSION); -MODULE_LICENSE("GPL"); - --- linux/net/bluetooth/l2cap_proc.c~bluetooth-2.4.18-mh11 +++ linux/net/bluetooth/l2cap_proc.c -/* - BlueZ - Bluetooth protocol stack for Linux - Copyright (C) 2000-2001 Qualcomm Incorporated - - Written 2000,2001 by Maxim Krasnyansky - - 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. -*/ - -/* - * BlueZ L2CAP proc fs support. - * - * $Id: l2cap_proc.c,v 1.2 2001/06/02 01:40:09 maxk Exp $ - */ - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include - -#ifndef L2CAP_DEBUG -#undef DBG -#define DBG( A... ) -#endif - -/* ----- PROC fs support ----- */ -static int l2cap_conn_dump(char *buf, struct l2cap_iff *iff) -{ - struct list_head *p; - char *ptr = buf; - - list_for_each(p, &iff->conn_list) { - struct l2cap_conn *c; - - c = list_entry(p, struct l2cap_conn, list); - ptr += sprintf(ptr, " %p %d %p %p %s %s\n", - c, c->state, c->iff, c->hconn, batostr(&c->src), batostr(&c->dst)); - } - - return ptr - buf; -} - -static int l2cap_iff_dump(char *buf) -{ - struct list_head *p; - char *ptr = buf; - - ptr += sprintf(ptr, "Interfaces:\n"); - - write_lock(&l2cap_rt_lock); - - list_for_each(p, &l2cap_iff_list) { - struct l2cap_iff *iff; - - iff = list_entry(p, struct l2cap_iff, list); - - ptr += sprintf(ptr, " %s %p %p\n", iff->hdev->name, iff, iff->hdev); - - l2cap_iff_lock(iff); - ptr += l2cap_conn_dump(ptr, iff); - l2cap_iff_unlock(iff); - } - - write_unlock(&l2cap_rt_lock); - - ptr += sprintf(ptr, "\n"); - - return ptr - buf; -} - -static int l2cap_sock_dump(char *buf, struct bluez_sock_list *list) -{ - struct l2cap_pinfo *pi; - struct sock *sk; - char *ptr = buf; - - ptr += sprintf(ptr, "Sockets:\n"); - - write_lock(&list->lock); - - for (sk = list->head; sk; sk = sk->next) { - pi = l2cap_pi(sk); - ptr += sprintf(ptr, " %p %d %p %d %s %s 0x%4.4x 0x%4.4x %d %d\n", sk, sk->state, pi->conn, pi->psm, - batostr(&pi->src), batostr(&pi->dst), pi->scid, pi->dcid, pi->imtu, pi->omtu ); - } - - write_unlock(&list->lock); - - ptr += sprintf(ptr, "\n"); - - return ptr - buf; -} - -static int l2cap_read_proc(char *buf, char **start, off_t offset, int count, int *eof, void *priv) -{ - char *ptr = buf; - int len; - - DBG("count %d, offset %ld", count, offset); - - ptr += l2cap_iff_dump(ptr); - ptr += l2cap_sock_dump(ptr, &l2cap_sk_list); - len = ptr - buf; - - if (len <= count + offset) - *eof = 1; - - *start = buf + offset; - len -= offset; - - if (len > count) - len = count; - if (len < 0) - len = 0; - - return len; -} - -void l2cap_register_proc(void) -{ - create_proc_read_entry("bluetooth/l2cap", 0, 0, l2cap_read_proc, NULL); -} - -void l2cap_unregister_proc(void) -{ - remove_proc_entry("bluetooth/l2cap", NULL); -} --- linux/net/bluetooth/lib.c~bluetooth-2.4.18-mh11 2001-09-07 18:28:38.000000000 +0200 +++ linux/net/bluetooth/lib.c 2004-01-25 23:37:39.000000000 +0100 @@ -25,7 +25,7 @@ /* * BlueZ kernel library. * - * $Id: lib.c,v 1.3 2001/06/22 23:14:23 maxk Exp $ + * $Id: lib.c,v 1.2 2002/06/20 19:55:08 maxk Exp $ */ #include @@ -105,7 +105,7 @@ return EACCES; case 0x06: - return EINVAL; + return EBADE; case 0x07: return ENOMEM; --- linux/net/bluetooth/Makefile~bluetooth-2.4.18-mh11 2001-06-12 04:15:27.000000000 +0200 +++ linux/net/bluetooth/Makefile 2004-01-25 23:37:39.000000000 +0100 @@ -1,20 +1,31 @@ # -# Makefile for the Bluetooth subsystem +# Makefile for the Linux Bluetooth subsystem # -O_TARGET := bluetooth.o -list-multi := hci.o l2cap.o -export-objs := syms.o -hci-objs := af_bluetooth.o hci_core.o hci_sock.o lib.o syms.o -l2cap-objs := l2cap_core.o l2cap_proc.o +O_TARGET := bluetooth.o -obj-$(CONFIG_BLUEZ) += hci.o +list-multi := bluez.o +export-objs := syms.o l2cap.o + +bluez-objs := af_bluetooth.o hci_core.o hci_conn.o hci_event.o hci_sock.o lib.o syms.o + +obj-$(CONFIG_BLUEZ) += bluez.o obj-$(CONFIG_BLUEZ_L2CAP) += l2cap.o +obj-$(CONFIG_BLUEZ_SCO) += sco.o -include $(TOPDIR)/Rules.make +subdir-$(CONFIG_BLUEZ_RFCOMM) += rfcomm +subdir-$(CONFIG_BLUEZ_BNEP) += bnep +subdir-$(CONFIG_BLUEZ_CMTP) += cmtp -hci.o: $(hci-objs) - $(LD) -r -o $@ $(hci-objs) +ifeq ($(CONFIG_BLUEZ_RFCOMM),y) +obj-y += rfcomm/rfcomm.o +endif -l2cap.o: $(l2cap-objs) - $(LD) -r -o $@ $(l2cap-objs) +ifeq ($(CONFIG_BLUEZ_BNEP),y) +obj-y += bnep/bnep.o +endif + +include $(TOPDIR)/Rules.make + +bluez.o: $(bluez-objs) + $(LD) -r -o $@ $(bluez-objs) --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/net/bluetooth/rfcomm/Config.in 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1,10 @@ +# +# Bluetooth RFCOMM layer configuration +# + +dep_tristate 'RFCOMM protocol support' CONFIG_BLUEZ_RFCOMM $CONFIG_BLUEZ_L2CAP + +if [ "$CONFIG_BLUEZ_RFCOMM" != "n" ]; then + bool ' RFCOMM TTY support' CONFIG_BLUEZ_RFCOMM_TTY +fi + --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/net/bluetooth/rfcomm/core.c 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1,1939 @@ +/* + RFCOMM implementation for Linux Bluetooth stack (BlueZ). + Copyright (C) 2002 Maxim Krasnyansky + Copyright (C) 2002 Marcel Holtmann + + 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. +*/ + +/* + RPN support - Dirk Husemann +*/ + +/* + * RFCOMM core. + * + * $Id: core.c,v 1.46 2002/10/18 20:12:12 maxk Exp $ + */ + +#define __KERNEL_SYSCALLS__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define VERSION "1.1" + +#ifndef CONFIG_BLUEZ_RFCOMM_DEBUG +#undef BT_DBG +#define BT_DBG(D...) +#endif + +struct task_struct *rfcomm_thread; +DECLARE_MUTEX(rfcomm_sem); +unsigned long rfcomm_event; + +static LIST_HEAD(session_list); +static atomic_t terminate, running; + +static int rfcomm_send_frame(struct rfcomm_session *s, u8 *data, int len); +static int rfcomm_send_sabm(struct rfcomm_session *s, u8 dlci); +static int rfcomm_send_disc(struct rfcomm_session *s, u8 dlci); +static int rfcomm_queue_disc(struct rfcomm_dlc *d); +static int rfcomm_send_nsc(struct rfcomm_session *s, int cr, u8 type); +static int rfcomm_send_pn(struct rfcomm_session *s, int cr, struct rfcomm_dlc *d); +static int rfcomm_send_msc(struct rfcomm_session *s, int cr, u8 dlci, u8 v24_sig); +static int rfcomm_send_test(struct rfcomm_session *s, int cr, u8 *pattern, int len); +static int rfcomm_send_credits(struct rfcomm_session *s, u8 addr, u8 credits); +static void rfcomm_make_uih(struct sk_buff *skb, u8 addr); + +static void rfcomm_process_connect(struct rfcomm_session *s); + +/* ---- RFCOMM frame parsing macros ---- */ +#define __get_dlci(b) ((b & 0xfc) >> 2) +#define __get_channel(b) ((b & 0xf8) >> 3) +#define __get_dir(b) ((b & 0x04) >> 2) +#define __get_type(b) ((b & 0xef)) + +#define __test_ea(b) ((b & 0x01)) +#define __test_cr(b) ((b & 0x02)) +#define __test_pf(b) ((b & 0x10)) + +#define __addr(cr, dlci) (((dlci & 0x3f) << 2) | (cr << 1) | 0x01) +#define __ctrl(type, pf) (((type & 0xef) | (pf << 4))) +#define __dlci(dir, chn) (((chn & 0x1f) << 1) | dir) +#define __srv_channel(dlci) (dlci >> 1) +#define __dir(dlci) (dlci & 0x01) + +#define __len8(len) (((len) << 1) | 1) +#define __len16(len) ((len) << 1) + +/* MCC macros */ +#define __mcc_type(cr, type) (((type << 2) | (cr << 1) | 0x01)) +#define __get_mcc_type(b) ((b & 0xfc) >> 2) +#define __get_mcc_len(b) ((b & 0xfe) >> 1) + +/* RPN macros */ +#define __rpn_line_settings(data, stop, parity) ((data & 0x3) | ((stop & 0x1) << 2) | ((parity & 0x3) << 3)) +#define __get_rpn_data_bits(line) ((line) & 0x3) +#define __get_rpn_stop_bits(line) (((line) >> 2) & 0x1) +#define __get_rpn_parity(line) (((line) >> 3) & 0x3) + +/* ---- RFCOMM FCS computation ---- */ + +/* CRC on 2 bytes */ +#define __crc(data) (rfcomm_crc_table[rfcomm_crc_table[0xff ^ data[0]] ^ data[1]]) + +/* FCS on 2 bytes */ +static inline u8 __fcs(u8 *data) +{ + return (0xff - __crc(data)); +} + +/* FCS on 3 bytes */ +static inline u8 __fcs2(u8 *data) +{ + return (0xff - rfcomm_crc_table[__crc(data) ^ data[2]]); +} + +/* Check FCS */ +static inline int __check_fcs(u8 *data, int type, u8 fcs) +{ + u8 f = __crc(data); + + if (type != RFCOMM_UIH) + f = rfcomm_crc_table[f ^ data[2]]; + + return rfcomm_crc_table[f ^ fcs] != 0xcf; +} + +/* ---- L2CAP callbacks ---- */ +static void rfcomm_l2state_change(struct sock *sk) +{ + BT_DBG("%p state %d", sk, sk->state); + rfcomm_schedule(RFCOMM_SCHED_STATE); +} + +static void rfcomm_l2data_ready(struct sock *sk, int bytes) +{ + BT_DBG("%p bytes %d", sk, bytes); + rfcomm_schedule(RFCOMM_SCHED_RX); +} + +static int rfcomm_l2sock_create(struct socket **sock) +{ + int err; + + BT_DBG(""); + + err = sock_create(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP, sock); + if (!err) { + struct sock *sk = (*sock)->sk; + sk->data_ready = rfcomm_l2data_ready; + sk->state_change = rfcomm_l2state_change; + } + return err; +} + +/* ---- RFCOMM DLCs ---- */ +static void rfcomm_dlc_timeout(unsigned long arg) +{ + struct rfcomm_dlc *d = (void *) arg; + + BT_DBG("dlc %p state %ld", d, d->state); + + set_bit(RFCOMM_TIMED_OUT, &d->flags); + rfcomm_dlc_put(d); + rfcomm_schedule(RFCOMM_SCHED_TIMEO); +} + +static void rfcomm_dlc_set_timer(struct rfcomm_dlc *d, long timeout) +{ + BT_DBG("dlc %p state %ld timeout %ld", d, d->state, timeout); + + if (!mod_timer(&d->timer, jiffies + timeout)) + rfcomm_dlc_hold(d); +} + +static void rfcomm_dlc_clear_timer(struct rfcomm_dlc *d) +{ + BT_DBG("dlc %p state %ld", d, d->state); + + if (timer_pending(&d->timer) && del_timer(&d->timer)) + rfcomm_dlc_put(d); +} + +static void rfcomm_dlc_clear_state(struct rfcomm_dlc *d) +{ + BT_DBG("%p", d); + + d->state = BT_OPEN; + d->flags = 0; + d->mscex = 0; + d->mtu = RFCOMM_DEFAULT_MTU; + d->v24_sig = RFCOMM_V24_RTC | RFCOMM_V24_RTR | RFCOMM_V24_DV; + + d->cfc = RFCOMM_CFC_DISABLED; + d->rx_credits = RFCOMM_DEFAULT_CREDITS; +} + +struct rfcomm_dlc *rfcomm_dlc_alloc(int prio) +{ + struct rfcomm_dlc *d = kmalloc(sizeof(*d), prio); + if (!d) + return NULL; + memset(d, 0, sizeof(*d)); + + init_timer(&d->timer); + d->timer.function = rfcomm_dlc_timeout; + d->timer.data = (unsigned long) d; + + skb_queue_head_init(&d->tx_queue); + spin_lock_init(&d->lock); + atomic_set(&d->refcnt, 1); + + rfcomm_dlc_clear_state(d); + + BT_DBG("%p", d); + return d; +} + +void rfcomm_dlc_free(struct rfcomm_dlc *d) +{ + BT_DBG("%p", d); + + skb_queue_purge(&d->tx_queue); + kfree(d); +} + +static void rfcomm_dlc_link(struct rfcomm_session *s, struct rfcomm_dlc *d) +{ + BT_DBG("dlc %p session %p", d, s); + + rfcomm_session_hold(s); + + rfcomm_dlc_hold(d); + list_add(&d->list, &s->dlcs); + d->session = s; +} + +static void rfcomm_dlc_unlink(struct rfcomm_dlc *d) +{ + struct rfcomm_session *s = d->session; + + BT_DBG("dlc %p refcnt %d session %p", d, atomic_read(&d->refcnt), s); + + list_del(&d->list); + d->session = NULL; + rfcomm_dlc_put(d); + + rfcomm_session_put(s); +} + +static struct rfcomm_dlc *rfcomm_dlc_get(struct rfcomm_session *s, u8 dlci) +{ + struct rfcomm_dlc *d; + struct list_head *p; + + list_for_each(p, &s->dlcs) { + d = list_entry(p, struct rfcomm_dlc, list); + if (d->dlci == dlci) + return d; + } + return NULL; +} + +static int __rfcomm_dlc_open(struct rfcomm_dlc *d, bdaddr_t *src, bdaddr_t *dst, u8 channel) +{ + struct rfcomm_session *s; + int err = 0; + u8 dlci; + + BT_DBG("dlc %p state %ld %s %s channel %d", + d, d->state, batostr(src), batostr(dst), channel); + + if (channel < 1 || channel > 30) + return -EINVAL; + + if (d->state != BT_OPEN && d->state != BT_CLOSED) + return 0; + + s = rfcomm_session_get(src, dst); + if (!s) { + s = rfcomm_session_create(src, dst, &err); + if (!s) + return err; + } + + dlci = __dlci(!s->initiator, channel); + + /* Check if DLCI already exists */ + if (rfcomm_dlc_get(s, dlci)) + return -EBUSY; + + rfcomm_dlc_clear_state(d); + + d->dlci = dlci; + d->addr = __addr(s->initiator, dlci); + d->priority = 7; + + d->state = BT_CONFIG; + rfcomm_dlc_link(s, d); + + d->mtu = s->mtu; + d->cfc = (s->cfc == RFCOMM_CFC_UNKNOWN) ? 0 : s->cfc; + + if (s->state == BT_CONNECTED) + rfcomm_send_pn(s, 1, d); + rfcomm_dlc_set_timer(d, RFCOMM_CONN_TIMEOUT); + return 0; +} + +int rfcomm_dlc_open(struct rfcomm_dlc *d, bdaddr_t *src, bdaddr_t *dst, u8 channel) +{ + mm_segment_t fs; + int r; + + rfcomm_lock(); + + fs = get_fs(); set_fs(KERNEL_DS); + r = __rfcomm_dlc_open(d, src, dst, channel); + set_fs(fs); + + rfcomm_unlock(); + return r; +} + +static int __rfcomm_dlc_close(struct rfcomm_dlc *d, int err) +{ + struct rfcomm_session *s = d->session; + if (!s) + return 0; + + BT_DBG("dlc %p state %ld dlci %d err %d session %p", + d, d->state, d->dlci, err, s); + + switch (d->state) { + case BT_CONNECTED: + case BT_CONFIG: + case BT_CONNECT: + d->state = BT_DISCONN; + if (skb_queue_empty(&d->tx_queue)) { + rfcomm_send_disc(s, d->dlci); + rfcomm_dlc_set_timer(d, RFCOMM_DISC_TIMEOUT); + } else { + rfcomm_queue_disc(d); + rfcomm_dlc_set_timer(d, RFCOMM_DISC_TIMEOUT * 2); + } + break; + + default: + rfcomm_dlc_clear_timer(d); + + rfcomm_dlc_lock(d); + d->state = BT_CLOSED; + d->state_change(d, err); + rfcomm_dlc_unlock(d); + + skb_queue_purge(&d->tx_queue); + rfcomm_dlc_unlink(d); + } + + return 0; +} + +int rfcomm_dlc_close(struct rfcomm_dlc *d, int err) +{ + mm_segment_t fs; + int r; + + rfcomm_lock(); + + fs = get_fs(); set_fs(KERNEL_DS); + r = __rfcomm_dlc_close(d, err); + set_fs(fs); + + rfcomm_unlock(); + return r; +} + +int rfcomm_dlc_send(struct rfcomm_dlc *d, struct sk_buff *skb) +{ + int len = skb->len; + + if (d->state != BT_CONNECTED) + return -ENOTCONN; + + BT_DBG("dlc %p mtu %d len %d", d, d->mtu, len); + + if (len > d->mtu) + return -EINVAL; + + rfcomm_make_uih(skb, d->addr); + skb_queue_tail(&d->tx_queue, skb); + + if (!test_bit(RFCOMM_TX_THROTTLED, &d->flags)) + rfcomm_schedule(RFCOMM_SCHED_TX); + return len; +} + +void __rfcomm_dlc_throttle(struct rfcomm_dlc *d) +{ + BT_DBG("dlc %p state %ld", d, d->state); + + if (!d->cfc) { + d->v24_sig |= RFCOMM_V24_FC; + set_bit(RFCOMM_MSC_PENDING, &d->flags); + } + rfcomm_schedule(RFCOMM_SCHED_TX); +} + +void __rfcomm_dlc_unthrottle(struct rfcomm_dlc *d) +{ + BT_DBG("dlc %p state %ld", d, d->state); + + if (!d->cfc) { + d->v24_sig &= ~RFCOMM_V24_FC; + set_bit(RFCOMM_MSC_PENDING, &d->flags); + } + rfcomm_schedule(RFCOMM_SCHED_TX); +} + +/* + Set/get modem status functions use _local_ status i.e. what we report + to the other side. + Remote status is provided by dlc->modem_status() callback. + */ +int rfcomm_dlc_set_modem_status(struct rfcomm_dlc *d, u8 v24_sig) +{ + BT_DBG("dlc %p state %ld v24_sig 0x%x", + d, d->state, v24_sig); + + if (test_bit(RFCOMM_RX_THROTTLED, &d->flags)) + v24_sig |= RFCOMM_V24_FC; + else + v24_sig &= ~RFCOMM_V24_FC; + + d->v24_sig = v24_sig; + + if (!test_and_set_bit(RFCOMM_MSC_PENDING, &d->flags)) + rfcomm_schedule(RFCOMM_SCHED_TX); + + return 0; +} + +int rfcomm_dlc_get_modem_status(struct rfcomm_dlc *d, u8 *v24_sig) +{ + BT_DBG("dlc %p state %ld v24_sig 0x%x", + d, d->state, d->v24_sig); + + *v24_sig = d->v24_sig; + return 0; +} + +/* ---- RFCOMM sessions ---- */ +struct rfcomm_session *rfcomm_session_add(struct socket *sock, int state) +{ + struct rfcomm_session *s = kmalloc(sizeof(*s), GFP_KERNEL); + if (!s) + return NULL; + memset(s, 0, sizeof(*s)); + + BT_DBG("session %p sock %p", s, sock); + + INIT_LIST_HEAD(&s->dlcs); + s->state = state; + s->sock = sock; + + s->mtu = RFCOMM_DEFAULT_MTU; + s->cfc = RFCOMM_CFC_UNKNOWN; + + list_add(&s->list, &session_list); + + /* Do not increment module usage count for listeting sessions. + * Otherwise we won't be able to unload the module. */ + if (state != BT_LISTEN) + MOD_INC_USE_COUNT; + return s; +} + +void rfcomm_session_del(struct rfcomm_session *s) +{ + int state = s->state; + + BT_DBG("session %p state %ld", s, s->state); + + list_del(&s->list); + + if (state == BT_CONNECTED) + rfcomm_send_disc(s, 0); + + sock_release(s->sock); + kfree(s); + + if (state != BT_LISTEN) + MOD_DEC_USE_COUNT; +} + +struct rfcomm_session *rfcomm_session_get(bdaddr_t *src, bdaddr_t *dst) +{ + struct rfcomm_session *s; + struct list_head *p, *n; + struct bluez_pinfo *pi; + list_for_each_safe(p, n, &session_list) { + s = list_entry(p, struct rfcomm_session, list); + pi = bluez_pi(s->sock->sk); + + if ((!bacmp(src, BDADDR_ANY) || !bacmp(&pi->src, src)) && + !bacmp(&pi->dst, dst)) + return s; + } + return NULL; +} + +void rfcomm_session_close(struct rfcomm_session *s, int err) +{ + struct rfcomm_dlc *d; + struct list_head *p, *n; + + BT_DBG("session %p state %ld err %d", s, s->state, err); + + rfcomm_session_hold(s); + + s->state = BT_CLOSED; + + /* Close all dlcs */ + list_for_each_safe(p, n, &s->dlcs) { + d = list_entry(p, struct rfcomm_dlc, list); + d->state = BT_CLOSED; + __rfcomm_dlc_close(d, err); + } + + rfcomm_session_put(s); +} + +struct rfcomm_session *rfcomm_session_create(bdaddr_t *src, bdaddr_t *dst, int *err) +{ + struct rfcomm_session *s = NULL; + struct sockaddr_l2 addr; + struct l2cap_options opts; + struct socket *sock; + int size; + + BT_DBG("%s %s", batostr(src), batostr(dst)); + + *err = rfcomm_l2sock_create(&sock); + if (*err < 0) + return NULL; + + bacpy(&addr.l2_bdaddr, src); + addr.l2_family = AF_BLUETOOTH; + addr.l2_psm = 0; + *err = sock->ops->bind(sock, (struct sockaddr *) &addr, sizeof(addr)); + if (*err < 0) + goto failed; + + /* Set L2CAP options */ + size = sizeof(opts); + sock->ops->getsockopt(sock, SOL_L2CAP, L2CAP_OPTIONS, (void *)&opts, &size); + + opts.imtu = RFCOMM_MAX_L2CAP_MTU; + sock->ops->setsockopt(sock, SOL_L2CAP, L2CAP_OPTIONS, (void *)&opts, size); + + s = rfcomm_session_add(sock, BT_BOUND); + if (!s) { + *err = -ENOMEM; + goto failed; + } + + s->initiator = 1; + + bacpy(&addr.l2_bdaddr, dst); + addr.l2_family = AF_BLUETOOTH; + addr.l2_psm = htobs(RFCOMM_PSM); + *err = sock->ops->connect(sock, (struct sockaddr *) &addr, sizeof(addr), O_NONBLOCK); + if (*err == 0 || *err == -EAGAIN) + return s; + + rfcomm_session_del(s); + return NULL; + +failed: + sock_release(sock); + return NULL; +} + +void rfcomm_session_getaddr(struct rfcomm_session *s, bdaddr_t *src, bdaddr_t *dst) +{ + struct sock *sk = s->sock->sk; + if (src) + bacpy(src, &bluez_pi(sk)->src); + if (dst) + bacpy(dst, &bluez_pi(sk)->dst); +} + +/* ---- RFCOMM frame sending ---- */ +static int rfcomm_send_frame(struct rfcomm_session *s, u8 *data, int len) +{ + struct socket *sock = s->sock; + struct iovec iv = { data, len }; + struct msghdr msg; + int err; + + BT_DBG("session %p len %d", s, len); + + memset(&msg, 0, sizeof(msg)); + msg.msg_iovlen = 1; + msg.msg_iov = &iv; + + err = sock->ops->sendmsg(sock, &msg, len, 0); + return err; +} + +static int rfcomm_send_sabm(struct rfcomm_session *s, u8 dlci) +{ + struct rfcomm_cmd cmd; + + BT_DBG("%p dlci %d", s, dlci); + + cmd.addr = __addr(s->initiator, dlci); + cmd.ctrl = __ctrl(RFCOMM_SABM, 1); + cmd.len = __len8(0); + cmd.fcs = __fcs2((u8 *) &cmd); + + return rfcomm_send_frame(s, (void *) &cmd, sizeof(cmd)); +} + +static int rfcomm_send_ua(struct rfcomm_session *s, u8 dlci) +{ + struct rfcomm_cmd cmd; + + BT_DBG("%p dlci %d", s, dlci); + + cmd.addr = __addr(!s->initiator, dlci); + cmd.ctrl = __ctrl(RFCOMM_UA, 1); + cmd.len = __len8(0); + cmd.fcs = __fcs2((u8 *) &cmd); + + return rfcomm_send_frame(s, (void *) &cmd, sizeof(cmd)); +} + +static int rfcomm_send_disc(struct rfcomm_session *s, u8 dlci) +{ + struct rfcomm_cmd cmd; + + BT_DBG("%p dlci %d", s, dlci); + + cmd.addr = __addr(s->initiator, dlci); + cmd.ctrl = __ctrl(RFCOMM_DISC, 1); + cmd.len = __len8(0); + cmd.fcs = __fcs2((u8 *) &cmd); + + return rfcomm_send_frame(s, (void *) &cmd, sizeof(cmd)); +} + +static int rfcomm_queue_disc(struct rfcomm_dlc *d) +{ + struct rfcomm_cmd *cmd; + struct sk_buff *skb; + + BT_DBG("dlc %p dlci %d", d, d->dlci); + + skb = alloc_skb(sizeof(*cmd), GFP_KERNEL); + if (!skb) + return -ENOMEM; + + cmd = (void *) __skb_put(skb, sizeof(*cmd)); + cmd->addr = d->addr; + cmd->ctrl = __ctrl(RFCOMM_DISC, 1); + cmd->len = __len8(0); + cmd->fcs = __fcs2((u8 *) cmd); + + skb_queue_tail(&d->tx_queue, skb); + rfcomm_schedule(RFCOMM_SCHED_TX); + return 0; +} + +static int rfcomm_send_dm(struct rfcomm_session *s, u8 dlci) +{ + struct rfcomm_cmd cmd; + + BT_DBG("%p dlci %d", s, dlci); + + cmd.addr = __addr(!s->initiator, dlci); + cmd.ctrl = __ctrl(RFCOMM_DM, 1); + cmd.len = __len8(0); + cmd.fcs = __fcs2((u8 *) &cmd); + + return rfcomm_send_frame(s, (void *) &cmd, sizeof(cmd)); +} + +static int rfcomm_send_nsc(struct rfcomm_session *s, int cr, u8 type) +{ + struct rfcomm_hdr *hdr; + struct rfcomm_mcc *mcc; + u8 buf[16], *ptr = buf; + + BT_DBG("%p cr %d type %d", s, cr, type); + + hdr = (void *) ptr; ptr += sizeof(*hdr); + hdr->addr = __addr(s->initiator, 0); + hdr->ctrl = __ctrl(RFCOMM_UIH, 0); + hdr->len = __len8(sizeof(*mcc) + 1); + + mcc = (void *) ptr; ptr += sizeof(*mcc); + mcc->type = __mcc_type(cr, RFCOMM_NSC); + mcc->len = __len8(1); + + /* Type that we didn't like */ + *ptr = __mcc_type(cr, type); ptr++; + + *ptr = __fcs(buf); ptr++; + + return rfcomm_send_frame(s, buf, ptr - buf); +} + +static int rfcomm_send_pn(struct rfcomm_session *s, int cr, struct rfcomm_dlc *d) +{ + struct rfcomm_hdr *hdr; + struct rfcomm_mcc *mcc; + struct rfcomm_pn *pn; + u8 buf[16], *ptr = buf; + + BT_DBG("%p cr %d dlci %d mtu %d", s, cr, d->dlci, d->mtu); + + hdr = (void *) ptr; ptr += sizeof(*hdr); + hdr->addr = __addr(s->initiator, 0); + hdr->ctrl = __ctrl(RFCOMM_UIH, 0); + hdr->len = __len8(sizeof(*mcc) + sizeof(*pn)); + + mcc = (void *) ptr; ptr += sizeof(*mcc); + mcc->type = __mcc_type(cr, RFCOMM_PN); + mcc->len = __len8(sizeof(*pn)); + + pn = (void *) ptr; ptr += sizeof(*pn); + pn->dlci = d->dlci; + pn->priority = d->priority; + pn->ack_timer = 0; + pn->max_retrans = 0; + + if (s->cfc) { + pn->flow_ctrl = cr ? 0xf0 : 0xe0; + pn->credits = RFCOMM_DEFAULT_CREDITS; + } else { + pn->flow_ctrl = 0; + pn->credits = 0; + } + + pn->mtu = htobs(d->mtu); + + *ptr = __fcs(buf); ptr++; + + return rfcomm_send_frame(s, buf, ptr - buf); +} + +static int rfcomm_send_rpn(struct rfcomm_session *s, int cr, u8 dlci, + u8 bit_rate, u8 data_bits, u8 stop_bits, + u8 parity, u8 flow_ctrl_settings, + u8 xon_char, u8 xoff_char, u16 param_mask) +{ + struct rfcomm_hdr *hdr; + struct rfcomm_mcc *mcc; + struct rfcomm_rpn *rpn; + u8 buf[16], *ptr = buf; + + BT_DBG("%p cr %d dlci %d bit_r 0x%x data_b 0x%x stop_b 0x%x parity 0x%x" + "flwc_s 0x%x xon_c 0x%x xoff_c 0x%x p_mask 0x%x", + s, cr, dlci, bit_rate, data_bits, stop_bits, parity, + flow_ctrl_settings, xon_char, xoff_char, param_mask); + + hdr = (void *) ptr; ptr += sizeof(*hdr); + hdr->addr = __addr(s->initiator, 0); + hdr->ctrl = __ctrl(RFCOMM_UIH, 0); + hdr->len = __len8(sizeof(*mcc) + sizeof(*rpn)); + + mcc = (void *) ptr; ptr += sizeof(*mcc); + mcc->type = __mcc_type(cr, RFCOMM_RPN); + mcc->len = __len8(sizeof(*rpn)); + + rpn = (void *) ptr; ptr += sizeof(*rpn); + rpn->dlci = __addr(1, dlci); + rpn->bit_rate = bit_rate; + rpn->line_settings = __rpn_line_settings(data_bits, stop_bits, parity); + rpn->flow_ctrl = flow_ctrl_settings; + rpn->xon_char = xon_char; + rpn->xoff_char = xoff_char; + rpn->param_mask = param_mask; + + *ptr = __fcs(buf); ptr++; + + return rfcomm_send_frame(s, buf, ptr - buf); +} + +static int rfcomm_send_rls(struct rfcomm_session *s, int cr, u8 dlci, u8 status) +{ + struct rfcomm_hdr *hdr; + struct rfcomm_mcc *mcc; + struct rfcomm_rls *rls; + u8 buf[16], *ptr = buf; + + BT_DBG("%p cr %d status 0x%x", s, cr, status); + + hdr = (void *) ptr; ptr += sizeof(*hdr); + hdr->addr = __addr(s->initiator, 0); + hdr->ctrl = __ctrl(RFCOMM_UIH, 0); + hdr->len = __len8(sizeof(*mcc) + sizeof(*rls)); + + mcc = (void *) ptr; ptr += sizeof(*mcc); + mcc->type = __mcc_type(cr, RFCOMM_RLS); + mcc->len = __len8(sizeof(*rls)); + + rls = (void *) ptr; ptr += sizeof(*rls); + rls->dlci = __addr(1, dlci); + rls->status = status; + + *ptr = __fcs(buf); ptr++; + + return rfcomm_send_frame(s, buf, ptr - buf); +} + +static int rfcomm_send_msc(struct rfcomm_session *s, int cr, u8 dlci, u8 v24_sig) +{ + struct rfcomm_hdr *hdr; + struct rfcomm_mcc *mcc; + struct rfcomm_msc *msc; + u8 buf[16], *ptr = buf; + + BT_DBG("%p cr %d v24 0x%x", s, cr, v24_sig); + + hdr = (void *) ptr; ptr += sizeof(*hdr); + hdr->addr = __addr(s->initiator, 0); + hdr->ctrl = __ctrl(RFCOMM_UIH, 0); + hdr->len = __len8(sizeof(*mcc) + sizeof(*msc)); + + mcc = (void *) ptr; ptr += sizeof(*mcc); + mcc->type = __mcc_type(cr, RFCOMM_MSC); + mcc->len = __len8(sizeof(*msc)); + + msc = (void *) ptr; ptr += sizeof(*msc); + msc->dlci = __addr(1, dlci); + msc->v24_sig = v24_sig | 0x01; + + *ptr = __fcs(buf); ptr++; + + return rfcomm_send_frame(s, buf, ptr - buf); +} + +static int rfcomm_send_fcoff(struct rfcomm_session *s, int cr) +{ + struct rfcomm_hdr *hdr; + struct rfcomm_mcc *mcc; + u8 buf[16], *ptr = buf; + + BT_DBG("%p cr %d", s, cr); + + hdr = (void *) ptr; ptr += sizeof(*hdr); + hdr->addr = __addr(s->initiator, 0); + hdr->ctrl = __ctrl(RFCOMM_UIH, 0); + hdr->len = __len8(sizeof(*mcc)); + + mcc = (void *) ptr; ptr += sizeof(*mcc); + mcc->type = __mcc_type(cr, RFCOMM_FCOFF); + mcc->len = __len8(0); + + *ptr = __fcs(buf); ptr++; + + return rfcomm_send_frame(s, buf, ptr - buf); +} + +static int rfcomm_send_fcon(struct rfcomm_session *s, int cr) +{ + struct rfcomm_hdr *hdr; + struct rfcomm_mcc *mcc; + u8 buf[16], *ptr = buf; + + BT_DBG("%p cr %d", s, cr); + + hdr = (void *) ptr; ptr += sizeof(*hdr); + hdr->addr = __addr(s->initiator, 0); + hdr->ctrl = __ctrl(RFCOMM_UIH, 0); + hdr->len = __len8(sizeof(*mcc)); + + mcc = (void *) ptr; ptr += sizeof(*mcc); + mcc->type = __mcc_type(cr, RFCOMM_FCON); + mcc->len = __len8(0); + + *ptr = __fcs(buf); ptr++; + + return rfcomm_send_frame(s, buf, ptr - buf); +} + +static int rfcomm_send_test(struct rfcomm_session *s, int cr, u8 *pattern, int len) +{ + struct socket *sock = s->sock; + struct iovec iv[3]; + struct msghdr msg; + unsigned char hdr[5], crc[1]; + + if (len > 125) + return -EINVAL; + + BT_DBG("%p cr %d", s, cr); + + hdr[0] = __addr(s->initiator, 0); + hdr[1] = __ctrl(RFCOMM_UIH, 0); + hdr[2] = 0x01 | ((len + 2) << 1); + hdr[3] = 0x01 | ((cr & 0x01) << 1) | (RFCOMM_TEST << 2); + hdr[4] = 0x01 | (len << 1); + + crc[0] = __fcs(hdr); + + iv[0].iov_base = hdr; + iv[0].iov_len = 5; + iv[1].iov_base = pattern; + iv[1].iov_len = len; + iv[2].iov_base = crc; + iv[2].iov_len = 1; + + memset(&msg, 0, sizeof(msg)); + msg.msg_iovlen = 3; + msg.msg_iov = iv; + return sock->ops->sendmsg(sock, &msg, 6 + len, 0); +} + +static int rfcomm_send_credits(struct rfcomm_session *s, u8 addr, u8 credits) +{ + struct rfcomm_hdr *hdr; + u8 buf[16], *ptr = buf; + + BT_DBG("%p addr %d credits %d", s, addr, credits); + + hdr = (void *) ptr; ptr += sizeof(*hdr); + hdr->addr = addr; + hdr->ctrl = __ctrl(RFCOMM_UIH, 1); + hdr->len = __len8(0); + + *ptr = credits; ptr++; + + *ptr = __fcs(buf); ptr++; + + return rfcomm_send_frame(s, buf, ptr - buf); +} + +static void rfcomm_make_uih(struct sk_buff *skb, u8 addr) +{ + struct rfcomm_hdr *hdr; + int len = skb->len; + u8 *crc; + + if (len > 127) { + hdr = (void *) skb_push(skb, 4); + put_unaligned(htobs(__len16(len)), (u16 *) &hdr->len); + } else { + hdr = (void *) skb_push(skb, 3); + hdr->len = __len8(len); + } + hdr->addr = addr; + hdr->ctrl = __ctrl(RFCOMM_UIH, 0); + + crc = skb_put(skb, 1); + *crc = __fcs((void *) hdr); +} + +/* ---- RFCOMM frame reception ---- */ +static int rfcomm_recv_ua(struct rfcomm_session *s, u8 dlci) +{ + BT_DBG("session %p state %ld dlci %d", s, s->state, dlci); + + if (dlci) { + /* Data channel */ + struct rfcomm_dlc *d = rfcomm_dlc_get(s, dlci); + if (!d) { + rfcomm_send_dm(s, dlci); + return 0; + } + + switch (d->state) { + case BT_CONNECT: + rfcomm_dlc_clear_timer(d); + + rfcomm_dlc_lock(d); + d->state = BT_CONNECTED; + d->state_change(d, 0); + rfcomm_dlc_unlock(d); + + rfcomm_send_msc(s, 1, dlci, d->v24_sig); + break; + + case BT_DISCONN: + d->state = BT_CLOSED; + __rfcomm_dlc_close(d, 0); + break; + } + } else { + /* Control channel */ + switch (s->state) { + case BT_CONNECT: + s->state = BT_CONNECTED; + rfcomm_process_connect(s); + break; + } + } + return 0; +} + +static int rfcomm_recv_dm(struct rfcomm_session *s, u8 dlci) +{ + int err = 0; + + BT_DBG("session %p state %ld dlci %d", s, s->state, dlci); + + if (dlci) { + /* Data DLC */ + struct rfcomm_dlc *d = rfcomm_dlc_get(s, dlci); + if (d) { + if (d->state == BT_CONNECT || d->state == BT_CONFIG) + err = ECONNREFUSED; + else + err = ECONNRESET; + + d->state = BT_CLOSED; + __rfcomm_dlc_close(d, err); + } + } else { + if (s->state == BT_CONNECT) + err = ECONNREFUSED; + else + err = ECONNRESET; + + s->state = BT_CLOSED; + rfcomm_session_close(s, err); + } + return 0; +} + +static int rfcomm_recv_disc(struct rfcomm_session *s, u8 dlci) +{ + int err = 0; + + BT_DBG("session %p state %ld dlci %d", s, s->state, dlci); + + if (dlci) { + struct rfcomm_dlc *d = rfcomm_dlc_get(s, dlci); + if (d) { + rfcomm_send_ua(s, dlci); + + if (d->state == BT_CONNECT || d->state == BT_CONFIG) + err = ECONNREFUSED; + else + err = ECONNRESET; + + d->state = BT_CLOSED; + __rfcomm_dlc_close(d, err); + } else + rfcomm_send_dm(s, dlci); + + } else { + rfcomm_send_ua(s, 0); + + if (s->state == BT_CONNECT) + err = ECONNREFUSED; + else + err = ECONNRESET; + + s->state = BT_CLOSED; + rfcomm_session_close(s, err); + } + + return 0; +} + +static int rfcomm_recv_sabm(struct rfcomm_session *s, u8 dlci) +{ + struct rfcomm_dlc *d; + u8 channel; + + BT_DBG("session %p state %ld dlci %d", s, s->state, dlci); + + if (!dlci) { + rfcomm_send_ua(s, 0); + + if (s->state == BT_OPEN) { + s->state = BT_CONNECTED; + rfcomm_process_connect(s); + } + return 0; + } + + /* Check if DLC exists */ + d = rfcomm_dlc_get(s, dlci); + if (d) { + if (d->state == BT_OPEN) { + /* DLC was previously opened by PN request */ + rfcomm_send_ua(s, dlci); + + rfcomm_dlc_lock(d); + d->state = BT_CONNECTED; + d->state_change(d, 0); + rfcomm_dlc_unlock(d); + + rfcomm_send_msc(s, 1, dlci, d->v24_sig); + } + return 0; + } + + /* Notify socket layer about incomming connection */ + channel = __srv_channel(dlci); + if (rfcomm_connect_ind(s, channel, &d)) { + d->dlci = dlci; + d->addr = __addr(s->initiator, dlci); + rfcomm_dlc_link(s, d); + + rfcomm_send_ua(s, dlci); + + rfcomm_dlc_lock(d); + d->state = BT_CONNECTED; + d->state_change(d, 0); + rfcomm_dlc_unlock(d); + + rfcomm_send_msc(s, 1, dlci, d->v24_sig); + } else { + rfcomm_send_dm(s, dlci); + } + + return 0; +} + +static int rfcomm_apply_pn(struct rfcomm_dlc *d, int cr, struct rfcomm_pn *pn) +{ + struct rfcomm_session *s = d->session; + + BT_DBG("dlc %p state %ld dlci %d mtu %d fc 0x%x credits %d", + d, d->state, d->dlci, pn->mtu, pn->flow_ctrl, pn->credits); + + if (pn->flow_ctrl == 0xf0 || pn->flow_ctrl == 0xe0) { + d->cfc = s->cfc = RFCOMM_CFC_ENABLED; + d->tx_credits = pn->credits; + } else { + d->cfc = s->cfc = RFCOMM_CFC_DISABLED; + set_bit(RFCOMM_TX_THROTTLED, &d->flags); + } + + d->priority = pn->priority; + + d->mtu = s->mtu = btohs(pn->mtu); + + return 0; +} + +static int rfcomm_recv_pn(struct rfcomm_session *s, int cr, struct sk_buff *skb) +{ + struct rfcomm_pn *pn = (void *) skb->data; + struct rfcomm_dlc *d; + u8 dlci = pn->dlci; + + BT_DBG("session %p state %ld dlci %d", s, s->state, dlci); + + if (!dlci) + return 0; + + d = rfcomm_dlc_get(s, dlci); + if (d) { + if (cr) { + /* PN request */ + rfcomm_apply_pn(d, cr, pn); + rfcomm_send_pn(s, 0, d); + } else { + /* PN response */ + switch (d->state) { + case BT_CONFIG: + rfcomm_apply_pn(d, cr, pn); + + d->state = BT_CONNECT; + rfcomm_send_sabm(s, d->dlci); + break; + } + } + } else { + u8 channel = __srv_channel(dlci); + + if (!cr) + return 0; + + /* PN request for non existing DLC. + * Assume incomming connection. */ + if (rfcomm_connect_ind(s, channel, &d)) { + d->dlci = dlci; + d->addr = __addr(s->initiator, dlci); + rfcomm_dlc_link(s, d); + + rfcomm_apply_pn(d, cr, pn); + + d->state = BT_OPEN; + rfcomm_send_pn(s, 0, d); + } else { + rfcomm_send_dm(s, dlci); + } + } + return 0; +} + +static int rfcomm_recv_rpn(struct rfcomm_session *s, int cr, int len, struct sk_buff *skb) +{ + struct rfcomm_rpn *rpn = (void *) skb->data; + u8 dlci = __get_dlci(rpn->dlci); + + u8 bit_rate = 0; + u8 data_bits = 0; + u8 stop_bits = 0; + u8 parity = 0; + u8 flow_ctrl = 0; + u8 xon_char = 0; + u8 xoff_char = 0; + u16 rpn_mask = RFCOMM_RPN_PM_ALL; + + BT_DBG("dlci %d cr %d len 0x%x bitr 0x%x line 0x%x flow 0x%x xonc 0x%x xoffc 0x%x pm 0x%x", + dlci, cr, len, rpn->bit_rate, rpn->line_settings, rpn->flow_ctrl, + rpn->xon_char, rpn->xoff_char, rpn->param_mask); + + if (!cr) + return 0; + + if (len == 1) { + /* request: return default setting */ + bit_rate = RFCOMM_RPN_BR_115200; + data_bits = RFCOMM_RPN_DATA_8; + stop_bits = RFCOMM_RPN_STOP_1; + parity = RFCOMM_RPN_PARITY_NONE; + flow_ctrl = RFCOMM_RPN_FLOW_NONE; + xon_char = RFCOMM_RPN_XON_CHAR; + xoff_char = RFCOMM_RPN_XOFF_CHAR; + + goto rpn_out; + } + /* check for sane values: ignore/accept bit_rate, 8 bits, 1 stop bit, no parity, + no flow control lines, normal XON/XOFF chars */ + if (rpn->param_mask & RFCOMM_RPN_PM_BITRATE) { + bit_rate = rpn->bit_rate; + if (bit_rate != RFCOMM_RPN_BR_115200) { + BT_DBG("RPN bit rate mismatch 0x%x", bit_rate); + bit_rate = RFCOMM_RPN_BR_115200; + rpn_mask ^= RFCOMM_RPN_PM_BITRATE; + } + } + if (rpn->param_mask & RFCOMM_RPN_PM_DATA) { + data_bits = __get_rpn_data_bits(rpn->line_settings); + if (data_bits != RFCOMM_RPN_DATA_8) { + BT_DBG("RPN data bits mismatch 0x%x", data_bits); + data_bits = RFCOMM_RPN_DATA_8; + rpn_mask ^= RFCOMM_RPN_PM_DATA; + } + } + if (rpn->param_mask & RFCOMM_RPN_PM_STOP) { + stop_bits = __get_rpn_stop_bits(rpn->line_settings); + if (stop_bits != RFCOMM_RPN_STOP_1) { + BT_DBG("RPN stop bits mismatch 0x%x", stop_bits); + stop_bits = RFCOMM_RPN_STOP_1; + rpn_mask ^= RFCOMM_RPN_PM_STOP; + } + } + if (rpn->param_mask & RFCOMM_RPN_PM_PARITY) { + parity = __get_rpn_parity(rpn->line_settings); + if (parity != RFCOMM_RPN_PARITY_NONE) { + BT_DBG("RPN parity mismatch 0x%x", parity); + parity = RFCOMM_RPN_PARITY_NONE; + rpn_mask ^= RFCOMM_RPN_PM_PARITY; + } + } + if (rpn->param_mask & RFCOMM_RPN_PM_FLOW) { + flow_ctrl = rpn->flow_ctrl; + if (flow_ctrl != RFCOMM_RPN_FLOW_NONE) { + BT_DBG("RPN flow ctrl mismatch 0x%x", flow_ctrl); + flow_ctrl = RFCOMM_RPN_FLOW_NONE; + rpn_mask ^= RFCOMM_RPN_PM_FLOW; + } + } + if (rpn->param_mask & RFCOMM_RPN_PM_XON) { + xon_char = rpn->xon_char; + if (xon_char != RFCOMM_RPN_XON_CHAR) { + BT_DBG("RPN XON char mismatch 0x%x", xon_char); + xon_char = RFCOMM_RPN_XON_CHAR; + rpn_mask ^= RFCOMM_RPN_PM_XON; + } + } + if (rpn->param_mask & RFCOMM_RPN_PM_XOFF) { + xoff_char = rpn->xoff_char; + if (xoff_char != RFCOMM_RPN_XOFF_CHAR) { + BT_DBG("RPN XOFF char mismatch 0x%x", xoff_char); + xoff_char = RFCOMM_RPN_XOFF_CHAR; + rpn_mask ^= RFCOMM_RPN_PM_XOFF; + } + } + +rpn_out: + rfcomm_send_rpn(s, 0, dlci, + bit_rate, data_bits, stop_bits, parity, flow_ctrl, + xon_char, xoff_char, rpn_mask); + + return 0; +} + +static int rfcomm_recv_rls(struct rfcomm_session *s, int cr, struct sk_buff *skb) +{ + struct rfcomm_rls *rls = (void *) skb->data; + u8 dlci = __get_dlci(rls->dlci); + + BT_DBG("dlci %d cr %d status 0x%x", dlci, cr, rls->status); + + if (!cr) + return 0; + + /* FIXME: We should probably do something with this + information here. But for now it's sufficient just + to reply -- Bluetooth 1.1 says it's mandatory to + recognise and respond to RLS */ + + rfcomm_send_rls(s, 0, dlci, rls->status); + + return 0; +} + +static int rfcomm_recv_msc(struct rfcomm_session *s, int cr, struct sk_buff *skb) +{ + struct rfcomm_msc *msc = (void *) skb->data; + struct rfcomm_dlc *d; + u8 dlci = __get_dlci(msc->dlci); + + BT_DBG("dlci %d cr %d v24 0x%x", dlci, cr, msc->v24_sig); + + d = rfcomm_dlc_get(s, dlci); + if (!d) + return 0; + + if (cr) { + if (msc->v24_sig & RFCOMM_V24_FC && !d->cfc) + set_bit(RFCOMM_TX_THROTTLED, &d->flags); + else + clear_bit(RFCOMM_TX_THROTTLED, &d->flags); + + rfcomm_dlc_lock(d); + if (d->modem_status) + d->modem_status(d, msc->v24_sig); + rfcomm_dlc_unlock(d); + + rfcomm_send_msc(s, 0, dlci, msc->v24_sig); + + d->mscex |= RFCOMM_MSCEX_RX; + } else + d->mscex |= RFCOMM_MSCEX_TX; + + return 0; +} + +static int rfcomm_recv_mcc(struct rfcomm_session *s, struct sk_buff *skb) +{ + struct rfcomm_mcc *mcc = (void *) skb->data; + u8 type, cr, len; + + cr = __test_cr(mcc->type); + type = __get_mcc_type(mcc->type); + len = __get_mcc_len(mcc->len); + + BT_DBG("%p type 0x%x cr %d", s, type, cr); + + skb_pull(skb, 2); + + switch (type) { + case RFCOMM_PN: + rfcomm_recv_pn(s, cr, skb); + break; + + case RFCOMM_RPN: + rfcomm_recv_rpn(s, cr, len, skb); + break; + + case RFCOMM_RLS: + rfcomm_recv_rls(s, cr, skb); + break; + + case RFCOMM_MSC: + rfcomm_recv_msc(s, cr, skb); + break; + + case RFCOMM_FCOFF: + if (cr) { + set_bit(RFCOMM_TX_THROTTLED, &s->flags); + rfcomm_send_fcoff(s, 0); + } + break; + + case RFCOMM_FCON: + if (cr) { + clear_bit(RFCOMM_TX_THROTTLED, &s->flags); + rfcomm_send_fcon(s, 0); + } + break; + + case RFCOMM_TEST: + if (cr) + rfcomm_send_test(s, 0, skb->data, skb->len); + break; + + case RFCOMM_NSC: + break; + + default: + BT_ERR("Unknown control type 0x%02x", type); + rfcomm_send_nsc(s, cr, type); + break; + } + return 0; +} + +static int rfcomm_recv_data(struct rfcomm_session *s, u8 dlci, int pf, struct sk_buff *skb) +{ + struct rfcomm_dlc *d; + + BT_DBG("session %p state %ld dlci %d pf %d", s, s->state, dlci, pf); + + d = rfcomm_dlc_get(s, dlci); + if (!d) { + rfcomm_send_dm(s, dlci); + goto drop; + } + + if (pf && d->cfc) { + u8 credits = *(u8 *) skb->data; skb_pull(skb, 1); + + d->tx_credits += credits; + if (d->tx_credits) + clear_bit(RFCOMM_TX_THROTTLED, &d->flags); + } + + if (skb->len && d->state == BT_CONNECTED) { + rfcomm_dlc_lock(d); + d->rx_credits--; + d->data_ready(d, skb); + rfcomm_dlc_unlock(d); + return 0; + } + +drop: + kfree_skb(skb); + return 0; +} + +static int rfcomm_recv_frame(struct rfcomm_session *s, struct sk_buff *skb) +{ + struct rfcomm_hdr *hdr = (void *) skb->data; + u8 type, dlci, fcs; + + dlci = __get_dlci(hdr->addr); + type = __get_type(hdr->ctrl); + + /* Trim FCS */ + skb->len--; skb->tail--; + fcs = *(u8 *) skb->tail; + + if (__check_fcs(skb->data, type, fcs)) { + BT_ERR("bad checksum in packet"); + kfree_skb(skb); + return -EILSEQ; + } + + if (__test_ea(hdr->len)) + skb_pull(skb, 3); + else + skb_pull(skb, 4); + + switch (type) { + case RFCOMM_SABM: + if (__test_pf(hdr->ctrl)) + rfcomm_recv_sabm(s, dlci); + break; + + case RFCOMM_DISC: + if (__test_pf(hdr->ctrl)) + rfcomm_recv_disc(s, dlci); + break; + + case RFCOMM_UA: + if (__test_pf(hdr->ctrl)) + rfcomm_recv_ua(s, dlci); + break; + + case RFCOMM_DM: + rfcomm_recv_dm(s, dlci); + break; + + case RFCOMM_UIH: + if (dlci) + return rfcomm_recv_data(s, dlci, __test_pf(hdr->ctrl), skb); + + rfcomm_recv_mcc(s, skb); + break; + + default: + BT_ERR("Unknown packet type 0x%02x\n", type); + break; + } + kfree_skb(skb); + return 0; +} + +/* ---- Connection and data processing ---- */ + +static void rfcomm_process_connect(struct rfcomm_session *s) +{ + struct rfcomm_dlc *d; + struct list_head *p, *n; + + BT_DBG("session %p state %ld", s, s->state); + + list_for_each_safe(p, n, &s->dlcs) { + d = list_entry(p, struct rfcomm_dlc, list); + if (d->state == BT_CONFIG) { + d->mtu = s->mtu; + rfcomm_send_pn(s, 1, d); + } + } +} + +/* Send data queued for the DLC. + * Return number of frames left in the queue. + */ +static inline int rfcomm_process_tx(struct rfcomm_dlc *d) +{ + struct sk_buff *skb; + int err; + + BT_DBG("dlc %p state %ld cfc %d rx_credits %d tx_credits %d", + d, d->state, d->cfc, d->rx_credits, d->tx_credits); + + /* Send pending MSC */ + if (test_and_clear_bit(RFCOMM_MSC_PENDING, &d->flags)) + rfcomm_send_msc(d->session, 1, d->dlci, d->v24_sig); + + if (d->cfc) { + /* CFC enabled. + * Give them some credits */ + if (!test_bit(RFCOMM_RX_THROTTLED, &d->flags) && + d->rx_credits <= (d->cfc >> 2)) { + rfcomm_send_credits(d->session, d->addr, d->cfc - d->rx_credits); + d->rx_credits = d->cfc; + } + } else { + /* CFC disabled. + * Give ourselves some credits */ + d->tx_credits = 5; + } + + if (test_bit(RFCOMM_TX_THROTTLED, &d->flags)) + return skb_queue_len(&d->tx_queue); + + while (d->tx_credits && (skb = skb_dequeue(&d->tx_queue))) { + err = rfcomm_send_frame(d->session, skb->data, skb->len); + if (err < 0) { + skb_queue_head(&d->tx_queue, skb); + break; + } + kfree_skb(skb); + d->tx_credits--; + } + + if (d->cfc && !d->tx_credits) { + /* We're out of TX credits. + * Set TX_THROTTLED flag to avoid unnesary wakeups by dlc_send. */ + set_bit(RFCOMM_TX_THROTTLED, &d->flags); + } + + return skb_queue_len(&d->tx_queue); +} + +static inline void rfcomm_process_dlcs(struct rfcomm_session *s) +{ + struct rfcomm_dlc *d; + struct list_head *p, *n; + + BT_DBG("session %p state %ld", s, s->state); + + list_for_each_safe(p, n, &s->dlcs) { + d = list_entry(p, struct rfcomm_dlc, list); + if (test_bit(RFCOMM_TIMED_OUT, &d->flags)) { + __rfcomm_dlc_close(d, ETIMEDOUT); + continue; + } + + if (test_bit(RFCOMM_TX_THROTTLED, &s->flags)) + continue; + + if ((d->state == BT_CONNECTED || d->state == BT_DISCONN) && + d->mscex == RFCOMM_MSCEX_OK) + rfcomm_process_tx(d); + } +} + +static inline void rfcomm_process_rx(struct rfcomm_session *s) +{ + struct socket *sock = s->sock; + struct sock *sk = sock->sk; + struct sk_buff *skb; + + BT_DBG("session %p state %ld qlen %d", s, s->state, skb_queue_len(&sk->receive_queue)); + + /* Get data directly from socket receive queue without copying it. */ + while ((skb = skb_dequeue(&sk->receive_queue))) { + skb_orphan(skb); + rfcomm_recv_frame(s, skb); + } + + if (sk->state == BT_CLOSED) { + if (!s->initiator) + rfcomm_session_put(s); + + rfcomm_session_close(s, sk->err); + } +} + +static inline void rfcomm_accept_connection(struct rfcomm_session *s) +{ + struct socket *sock = s->sock, *nsock; + int err; + + /* Fast check for a new connection. + * Avoids unnesesary socket allocations. */ + if (list_empty(&bluez_pi(sock->sk)->accept_q)) + return; + + BT_DBG("session %p", s); + + nsock = sock_alloc(); + if (!nsock) + return; + + nsock->type = sock->type; + nsock->ops = sock->ops; + + err = sock->ops->accept(sock, nsock, O_NONBLOCK); + if (err < 0) { + sock_release(nsock); + return; + } + + /* Set our callbacks */ + nsock->sk->data_ready = rfcomm_l2data_ready; + nsock->sk->state_change = rfcomm_l2state_change; + + s = rfcomm_session_add(nsock, BT_OPEN); + if (s) + rfcomm_session_hold(s); + else + sock_release(nsock); +} + +static inline void rfcomm_check_connection(struct rfcomm_session *s) +{ + struct sock *sk = s->sock->sk; + + BT_DBG("%p state %ld", s, s->state); + + switch(sk->state) { + case BT_CONNECTED: + s->state = BT_CONNECT; + + /* We can adjust MTU on outgoing sessions. + * L2CAP MTU minus UIH header and FCS. */ + s->mtu = min(l2cap_pi(sk)->omtu, l2cap_pi(sk)->imtu) - 5; + + rfcomm_send_sabm(s, 0); + break; + + case BT_CLOSED: + s->state = BT_CLOSED; + rfcomm_session_close(s, sk->err); + break; + } +} + +static inline void rfcomm_process_sessions(void) +{ + struct list_head *p, *n; + + rfcomm_lock(); + + list_for_each_safe(p, n, &session_list) { + struct rfcomm_session *s; + s = list_entry(p, struct rfcomm_session, list); + + if (s->state == BT_LISTEN) { + rfcomm_accept_connection(s); + continue; + } + + rfcomm_session_hold(s); + + switch (s->state) { + case BT_BOUND: + rfcomm_check_connection(s); + break; + + default: + rfcomm_process_rx(s); + break; + } + + rfcomm_process_dlcs(s); + + rfcomm_session_put(s); + } + + rfcomm_unlock(); +} + +static void rfcomm_worker(void) +{ + BT_DBG(""); + + daemonize(); reparent_to_init(); + set_fs(KERNEL_DS); + + while (!atomic_read(&terminate)) { + BT_DBG("worker loop event 0x%lx", rfcomm_event); + + if (!test_bit(RFCOMM_SCHED_WAKEUP, &rfcomm_event)) { + /* No pending events. Let's sleep. + * Incomming connections and data will wake us up. */ + set_current_state(TASK_INTERRUPTIBLE); + schedule(); + } + + /* Process stuff */ + clear_bit(RFCOMM_SCHED_WAKEUP, &rfcomm_event); + rfcomm_process_sessions(); + } + set_current_state(TASK_RUNNING); + return; +} + +static int rfcomm_add_listener(bdaddr_t *ba) +{ + struct sockaddr_l2 addr; + struct l2cap_options opts; + struct socket *sock; + struct rfcomm_session *s; + int size, err = 0; + + /* Create socket */ + err = rfcomm_l2sock_create(&sock); + if (err < 0) { + BT_ERR("Create socket failed %d", err); + return err; + } + + /* Bind socket */ + bacpy(&addr.l2_bdaddr, ba); + addr.l2_family = AF_BLUETOOTH; + addr.l2_psm = htobs(RFCOMM_PSM); + err = sock->ops->bind(sock, (struct sockaddr *) &addr, sizeof(addr)); + if (err < 0) { + BT_ERR("Bind failed %d", err); + goto failed; + } + + /* Set L2CAP options */ + size = sizeof(opts); + sock->ops->getsockopt(sock, SOL_L2CAP, L2CAP_OPTIONS, (void *)&opts, &size); + + opts.imtu = RFCOMM_MAX_L2CAP_MTU; + sock->ops->setsockopt(sock, SOL_L2CAP, L2CAP_OPTIONS, (void *)&opts, size); + + /* Start listening on the socket */ + err = sock->ops->listen(sock, 10); + if (err) { + BT_ERR("Listen failed %d", err); + goto failed; + } + + /* Add listening session */ + s = rfcomm_session_add(sock, BT_LISTEN); + if (!s) + goto failed; + + rfcomm_session_hold(s); + return 0; +failed: + sock_release(sock); + return err; +} + +static void rfcomm_kill_listener(void) +{ + struct rfcomm_session *s; + struct list_head *p, *n; + + BT_DBG(""); + + list_for_each_safe(p, n, &session_list) { + s = list_entry(p, struct rfcomm_session, list); + rfcomm_session_del(s); + } +} + +static int rfcomm_run(void *unused) +{ + rfcomm_thread = current; + + atomic_inc(&running); + + daemonize(); reparent_to_init(); + + sigfillset(¤t->blocked); + set_fs(KERNEL_DS); + + sprintf(current->comm, "krfcommd"); + + BT_DBG(""); + + rfcomm_add_listener(BDADDR_ANY); + + rfcomm_worker(); + + rfcomm_kill_listener(); + + atomic_dec(&running); + return 0; +} + +/* ---- Proc fs support ---- */ +static int rfcomm_dlc_dump(char *buf) +{ + struct rfcomm_session *s; + struct sock *sk; + struct list_head *p, *pp; + char *ptr = buf; + + rfcomm_lock(); + + list_for_each(p, &session_list) { + s = list_entry(p, struct rfcomm_session, list); + sk = s->sock->sk; + + list_for_each(pp, &s->dlcs) { + struct rfcomm_dlc *d; + d = list_entry(pp, struct rfcomm_dlc, list); + + ptr += sprintf(ptr, "dlc %s %s %ld %d %d %d %d\n", + batostr(&bluez_pi(sk)->src), batostr(&bluez_pi(sk)->dst), + d->state, d->dlci, d->mtu, d->rx_credits, d->tx_credits); + } + } + + rfcomm_unlock(); + + return ptr - buf; +} + +extern int rfcomm_sock_dump(char *buf); + +static int rfcomm_read_proc(char *buf, char **start, off_t offset, int count, int *eof, void *priv) +{ + char *ptr = buf; + int len; + + BT_DBG("count %d, offset %ld", count, offset); + + ptr += rfcomm_dlc_dump(ptr); + ptr += rfcomm_sock_dump(ptr); + len = ptr - buf; + + if (len <= count + offset) + *eof = 1; + + *start = buf + offset; + len -= offset; + + if (len > count) + len = count; + if (len < 0) + len = 0; + + return len; +} + +/* ---- Initialization ---- */ +int __init rfcomm_init(void) +{ + l2cap_load(); + + kernel_thread(rfcomm_run, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGHAND); + + rfcomm_init_sockets(); + +#ifdef CONFIG_BLUEZ_RFCOMM_TTY + rfcomm_init_ttys(); +#endif + + create_proc_read_entry("bluetooth/rfcomm", 0, 0, rfcomm_read_proc, NULL); + + BT_INFO("BlueZ RFCOMM ver %s", VERSION); + BT_INFO("Copyright (C) 2002 Maxim Krasnyansky "); + BT_INFO("Copyright (C) 2002 Marcel Holtmann "); + return 0; +} + +void rfcomm_cleanup(void) +{ + /* Terminate working thread. + * ie. Set terminate flag and wake it up */ + atomic_inc(&terminate); + rfcomm_schedule(RFCOMM_SCHED_STATE); + + /* Wait until thread is running */ + while (atomic_read(&running)) + schedule(); + + remove_proc_entry("bluetooth/rfcomm", NULL); + +#ifdef CONFIG_BLUEZ_RFCOMM_TTY + rfcomm_cleanup_ttys(); +#endif + + rfcomm_cleanup_sockets(); + return; +} + +module_init(rfcomm_init); +module_exit(rfcomm_cleanup); + +MODULE_AUTHOR("Maxim Krasnyansky , Marcel Holtmann "); +MODULE_DESCRIPTION("BlueZ RFCOMM ver " VERSION); +MODULE_LICENSE("GPL"); --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/net/bluetooth/rfcomm/crc.c 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1,71 @@ +/* + RFCOMM implementation for Linux Bluetooth stack (BlueZ). + Copyright (C) 2002 Maxim Krasnyansky + Copyright (C) 2002 Marcel Holtmann + + 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. +*/ + +/* + * RFCOMM FCS calculation. + * + * $Id: crc.c,v 1.2 2002/09/21 09:54:32 holtmann Exp $ + */ + +/* reversed, 8-bit, poly=0x07 */ +unsigned char rfcomm_crc_table[256] = { + 0x00, 0x91, 0xe3, 0x72, 0x07, 0x96, 0xe4, 0x75, + 0x0e, 0x9f, 0xed, 0x7c, 0x09, 0x98, 0xea, 0x7b, + 0x1c, 0x8d, 0xff, 0x6e, 0x1b, 0x8a, 0xf8, 0x69, + 0x12, 0x83, 0xf1, 0x60, 0x15, 0x84, 0xf6, 0x67, + + 0x38, 0xa9, 0xdb, 0x4a, 0x3f, 0xae, 0xdc, 0x4d, + 0x36, 0xa7, 0xd5, 0x44, 0x31, 0xa0, 0xd2, 0x43, + 0x24, 0xb5, 0xc7, 0x56, 0x23, 0xb2, 0xc0, 0x51, + 0x2a, 0xbb, 0xc9, 0x58, 0x2d, 0xbc, 0xce, 0x5f, + + 0x70, 0xe1, 0x93, 0x02, 0x77, 0xe6, 0x94, 0x05, + 0x7e, 0xef, 0x9d, 0x0c, 0x79, 0xe8, 0x9a, 0x0b, + 0x6c, 0xfd, 0x8f, 0x1e, 0x6b, 0xfa, 0x88, 0x19, + 0x62, 0xf3, 0x81, 0x10, 0x65, 0xf4, 0x86, 0x17, + + 0x48, 0xd9, 0xab, 0x3a, 0x4f, 0xde, 0xac, 0x3d, + 0x46, 0xd7, 0xa5, 0x34, 0x41, 0xd0, 0xa2, 0x33, + 0x54, 0xc5, 0xb7, 0x26, 0x53, 0xc2, 0xb0, 0x21, + 0x5a, 0xcb, 0xb9, 0x28, 0x5d, 0xcc, 0xbe, 0x2f, + + 0xe0, 0x71, 0x03, 0x92, 0xe7, 0x76, 0x04, 0x95, + 0xee, 0x7f, 0x0d, 0x9c, 0xe9, 0x78, 0x0a, 0x9b, + 0xfc, 0x6d, 0x1f, 0x8e, 0xfb, 0x6a, 0x18, 0x89, + 0xf2, 0x63, 0x11, 0x80, 0xf5, 0x64, 0x16, 0x87, + + 0xd8, 0x49, 0x3b, 0xaa, 0xdf, 0x4e, 0x3c, 0xad, + 0xd6, 0x47, 0x35, 0xa4, 0xd1, 0x40, 0x32, 0xa3, + 0xc4, 0x55, 0x27, 0xb6, 0xc3, 0x52, 0x20, 0xb1, + 0xca, 0x5b, 0x29, 0xb8, 0xcd, 0x5c, 0x2e, 0xbf, + + 0x90, 0x01, 0x73, 0xe2, 0x97, 0x06, 0x74, 0xe5, + 0x9e, 0x0f, 0x7d, 0xec, 0x99, 0x08, 0x7a, 0xeb, + 0x8c, 0x1d, 0x6f, 0xfe, 0x8b, 0x1a, 0x68, 0xf9, + 0x82, 0x13, 0x61, 0xf0, 0x85, 0x14, 0x66, 0xf7, + + 0xa8, 0x39, 0x4b, 0xda, 0xaf, 0x3e, 0x4c, 0xdd, + 0xa6, 0x37, 0x45, 0xd4, 0xa1, 0x30, 0x42, 0xd3, + 0xb4, 0x25, 0x57, 0xc6, 0xb3, 0x22, 0x50, 0xc1, + 0xba, 0x2b, 0x59, 0xc8, 0xbd, 0x2c, 0x5e, 0xcf +}; --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/net/bluetooth/rfcomm/Makefile 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1,11 @@ +# +# Makefile for the Linux Bluetooth RFCOMM layer +# + +O_TARGET := rfcomm.o + +obj-y := core.o sock.o crc.o +obj-$(CONFIG_BLUEZ_RFCOMM_TTY) += tty.o +obj-m += $(O_TARGET) + +include $(TOPDIR)/Rules.make --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/net/bluetooth/rfcomm/sock.c 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1,847 @@ +/* + RFCOMM implementation for Linux Bluetooth stack (BlueZ). + Copyright (C) 2002 Maxim Krasnyansky + Copyright (C) 2002 Marcel Holtmann + + 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. +*/ + +/* + * RFCOMM sockets. + * + * $Id: sock.c,v 1.30 2002/10/18 20:12:12 maxk Exp $ + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#ifndef CONFIG_BLUEZ_RFCOMM_DEBUG +#undef BT_DBG +#define BT_DBG(D...) +#endif + +static struct proto_ops rfcomm_sock_ops; + +static struct bluez_sock_list rfcomm_sk_list = { + lock: RW_LOCK_UNLOCKED +}; + +static void rfcomm_sock_close(struct sock *sk); +static void rfcomm_sock_kill(struct sock *sk); + +/* ---- DLC callbacks ---- + * + * called under rfcomm_dlc_lock() + */ +static void rfcomm_sk_data_ready(struct rfcomm_dlc *d, struct sk_buff *skb) +{ + struct sock *sk = d->owner; + if (!sk) + return; + + atomic_add(skb->len, &sk->rmem_alloc); + skb_queue_tail(&sk->receive_queue, skb); + sk->data_ready(sk, skb->len); + + if (atomic_read(&sk->rmem_alloc) >= sk->rcvbuf) + rfcomm_dlc_throttle(d); +} + +static void rfcomm_sk_state_change(struct rfcomm_dlc *d, int err) +{ + struct sock *sk = d->owner, *parent; + if (!sk) + return; + + BT_DBG("dlc %p state %ld err %d", d, d->state, err); + + bh_lock_sock(sk); + + if (err) + sk->err = err; + sk->state = d->state; + + parent = bluez_pi(sk)->parent; + if (!parent) { + if (d->state == BT_CONNECTED) + rfcomm_session_getaddr(d->session, &bluez_pi(sk)->src, NULL); + sk->state_change(sk); + } else + parent->data_ready(parent, 0); + + bh_unlock_sock(sk); +} + +/* ---- Socket functions ---- */ +static struct sock *__rfcomm_get_sock_by_addr(u8 channel, bdaddr_t *src) +{ + struct sock *sk; + + for (sk = rfcomm_sk_list.head; sk; sk = sk->next) { + if (rfcomm_pi(sk)->channel == channel && + !bacmp(&bluez_pi(sk)->src, src)) + break; + } + + return sk; +} + +/* Find socket with channel and source bdaddr. + * Returns closest match. + */ +static struct sock *__rfcomm_get_sock_by_channel(int state, u8 channel, bdaddr_t *src) +{ + struct sock *sk, *sk1 = NULL; + + for (sk = rfcomm_sk_list.head; sk; sk = sk->next) { + if (state && sk->state != state) + continue; + + if (rfcomm_pi(sk)->channel == channel) { + /* Exact match. */ + if (!bacmp(&bluez_pi(sk)->src, src)) + break; + + /* Closest match */ + if (!bacmp(&bluez_pi(sk)->src, BDADDR_ANY)) + sk1 = sk; + } + } + return sk ? sk : sk1; +} + +/* Find socket with given address (channel, src). + * Returns locked socket */ +static inline struct sock *rfcomm_get_sock_by_channel(int state, u8 channel, bdaddr_t *src) +{ + struct sock *s; + read_lock(&rfcomm_sk_list.lock); + s = __rfcomm_get_sock_by_channel(state, channel, src); + if (s) bh_lock_sock(s); + read_unlock(&rfcomm_sk_list.lock); + return s; +} + +static void rfcomm_sock_destruct(struct sock *sk) +{ + struct rfcomm_dlc *d = rfcomm_pi(sk)->dlc; + + BT_DBG("sk %p dlc %p", sk, d); + + skb_queue_purge(&sk->receive_queue); + skb_queue_purge(&sk->write_queue); + + rfcomm_dlc_lock(d); + rfcomm_pi(sk)->dlc = NULL; + + /* Detach DLC if it's owned by this socket */ + if (d->owner == sk) + d->owner = NULL; + rfcomm_dlc_unlock(d); + + rfcomm_dlc_put(d); + + MOD_DEC_USE_COUNT; +} + +static void rfcomm_sock_cleanup_listen(struct sock *parent) +{ + struct sock *sk; + + BT_DBG("parent %p", parent); + + /* Close not yet accepted dlcs */ + while ((sk = bluez_accept_dequeue(parent, NULL))) { + rfcomm_sock_close(sk); + rfcomm_sock_kill(sk); + } + + parent->state = BT_CLOSED; + parent->zapped = 1; +} + +/* Kill socket (only if zapped and orphan) + * Must be called on unlocked socket. + */ +static void rfcomm_sock_kill(struct sock *sk) +{ + if (!sk->zapped || sk->socket) + return; + + BT_DBG("sk %p state %d refcnt %d", sk, sk->state, atomic_read(&sk->refcnt)); + + /* Kill poor orphan */ + bluez_sock_unlink(&rfcomm_sk_list, sk); + sk->dead = 1; + sock_put(sk); +} + +static void __rfcomm_sock_close(struct sock *sk) +{ + struct rfcomm_dlc *d = rfcomm_pi(sk)->dlc; + + BT_DBG("sk %p state %d socket %p", sk, sk->state, sk->socket); + + switch (sk->state) { + case BT_LISTEN: + rfcomm_sock_cleanup_listen(sk); + break; + + case BT_CONNECT: + case BT_CONNECT2: + case BT_CONFIG: + case BT_CONNECTED: + rfcomm_dlc_close(d, 0); + + default: + sk->zapped = 1; + break; + } +} + +/* Close socket. + * Must be called on unlocked socket. + */ +static void rfcomm_sock_close(struct sock *sk) +{ + lock_sock(sk); + __rfcomm_sock_close(sk); + release_sock(sk); +} + +static void rfcomm_sock_init(struct sock *sk, struct sock *parent) +{ + BT_DBG("sk %p", sk); + + if (parent) + sk->type = parent->type; +} + +static struct sock *rfcomm_sock_alloc(struct socket *sock, int proto, int prio) +{ + struct rfcomm_dlc *d; + struct sock *sk; + + sk = sk_alloc(PF_BLUETOOTH, prio, 1); + if (!sk) + return NULL; + + d = rfcomm_dlc_alloc(prio); + if (!d) { + sk_free(sk); + return NULL; + } + d->data_ready = rfcomm_sk_data_ready; + d->state_change = rfcomm_sk_state_change; + + rfcomm_pi(sk)->dlc = d; + d->owner = sk; + + bluez_sock_init(sock, sk); + + sk->zapped = 0; + + sk->destruct = rfcomm_sock_destruct; + sk->sndtimeo = RFCOMM_CONN_TIMEOUT; + + sk->sndbuf = RFCOMM_MAX_CREDITS * RFCOMM_DEFAULT_MTU * 10; + sk->rcvbuf = RFCOMM_MAX_CREDITS * RFCOMM_DEFAULT_MTU * 10; + + sk->protocol = proto; + sk->state = BT_OPEN; + + bluez_sock_link(&rfcomm_sk_list, sk); + + BT_DBG("sk %p", sk); + + MOD_INC_USE_COUNT; + return sk; +} + +static int rfcomm_sock_create(struct socket *sock, int protocol) +{ + struct sock *sk; + + BT_DBG("sock %p", sock); + + sock->state = SS_UNCONNECTED; + + if (sock->type != SOCK_STREAM && sock->type != SOCK_RAW) + return -ESOCKTNOSUPPORT; + + sock->ops = &rfcomm_sock_ops; + + if (!(sk = rfcomm_sock_alloc(sock, protocol, GFP_KERNEL))) + return -ENOMEM; + + rfcomm_sock_init(sk, NULL); + return 0; +} + +static int rfcomm_sock_bind(struct socket *sock, struct sockaddr *addr, int addr_len) +{ + struct sockaddr_rc *sa = (struct sockaddr_rc *) addr; + struct sock *sk = sock->sk; + int err = 0; + + BT_DBG("sk %p %s", sk, batostr(&sa->rc_bdaddr)); + + if (!addr || addr->sa_family != AF_BLUETOOTH) + return -EINVAL; + + lock_sock(sk); + + if (sk->state != BT_OPEN) { + err = -EBADFD; + goto done; + } + + write_lock_bh(&rfcomm_sk_list.lock); + + if (sa->rc_channel && __rfcomm_get_sock_by_addr(sa->rc_channel, &sa->rc_bdaddr)) { + err = -EADDRINUSE; + } else { + /* Save source address */ + bacpy(&bluez_pi(sk)->src, &sa->rc_bdaddr); + rfcomm_pi(sk)->channel = sa->rc_channel; + sk->state = BT_BOUND; + } + + write_unlock_bh(&rfcomm_sk_list.lock); + +done: + release_sock(sk); + return err; +} + +static int rfcomm_sock_connect(struct socket *sock, struct sockaddr *addr, int alen, int flags) +{ + struct sockaddr_rc *sa = (struct sockaddr_rc *) addr; + struct sock *sk = sock->sk; + struct rfcomm_dlc *d = rfcomm_pi(sk)->dlc; + int err = 0; + + BT_DBG("sk %p", sk); + + if (addr->sa_family != AF_BLUETOOTH || alen < sizeof(struct sockaddr_rc)) + return -EINVAL; + + if (sk->state != BT_OPEN && sk->state != BT_BOUND) + return -EBADFD; + + if (sk->type != SOCK_STREAM) + return -EINVAL; + + lock_sock(sk); + + sk->state = BT_CONNECT; + bacpy(&bluez_pi(sk)->dst, &sa->rc_bdaddr); + rfcomm_pi(sk)->channel = sa->rc_channel; + + err = rfcomm_dlc_open(d, &bluez_pi(sk)->src, &sa->rc_bdaddr, sa->rc_channel); + if (!err) + err = bluez_sock_wait_state(sk, BT_CONNECTED, + sock_sndtimeo(sk, flags & O_NONBLOCK)); + + release_sock(sk); + return err; +} + +int rfcomm_sock_listen(struct socket *sock, int backlog) +{ + struct sock *sk = sock->sk; + int err = 0; + + BT_DBG("sk %p backlog %d", sk, backlog); + + lock_sock(sk); + + if (sk->state != BT_BOUND) { + err = -EBADFD; + goto done; + } + + sk->max_ack_backlog = backlog; + sk->ack_backlog = 0; + sk->state = BT_LISTEN; + +done: + release_sock(sk); + return err; +} + +int rfcomm_sock_accept(struct socket *sock, struct socket *newsock, int flags) +{ + DECLARE_WAITQUEUE(wait, current); + struct sock *sk = sock->sk, *nsk; + long timeo; + int err = 0; + + lock_sock(sk); + + if (sk->state != BT_LISTEN) { + err = -EBADFD; + goto done; + } + + timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK); + + BT_DBG("sk %p timeo %ld", sk, timeo); + + /* Wait for an incoming connection. (wake-one). */ + add_wait_queue_exclusive(sk->sleep, &wait); + while (!(nsk = bluez_accept_dequeue(sk, newsock))) { + set_current_state(TASK_INTERRUPTIBLE); + if (!timeo) { + err = -EAGAIN; + break; + } + + release_sock(sk); + timeo = schedule_timeout(timeo); + lock_sock(sk); + + if (sk->state != BT_LISTEN) { + err = -EBADFD; + break; + } + + if (signal_pending(current)) { + err = sock_intr_errno(timeo); + break; + } + } + set_current_state(TASK_RUNNING); + remove_wait_queue(sk->sleep, &wait); + + if (err) + goto done; + + newsock->state = SS_CONNECTED; + + BT_DBG("new socket %p", nsk); + +done: + release_sock(sk); + return err; +} + +static int rfcomm_sock_getname(struct socket *sock, struct sockaddr *addr, int *len, int peer) +{ + struct sockaddr_rc *sa = (struct sockaddr_rc *) addr; + struct sock *sk = sock->sk; + + BT_DBG("sock %p, sk %p", sock, sk); + + sa->rc_family = AF_BLUETOOTH; + sa->rc_channel = rfcomm_pi(sk)->channel; + if (peer) + bacpy(&sa->rc_bdaddr, &bluez_pi(sk)->dst); + else + bacpy(&sa->rc_bdaddr, &bluez_pi(sk)->src); + + *len = sizeof(struct sockaddr_rc); + return 0; +} + +static int rfcomm_sock_sendmsg(struct socket *sock, struct msghdr *msg, int len, + struct scm_cookie *scm) +{ + struct sock *sk = sock->sk; + struct rfcomm_dlc *d = rfcomm_pi(sk)->dlc; + struct sk_buff *skb; + int err, size; + int sent = 0; + + if (msg->msg_flags & MSG_OOB) + return -EOPNOTSUPP; + + if (sk->shutdown & SEND_SHUTDOWN) + return -EPIPE; + + BT_DBG("sock %p, sk %p", sock, sk); + + lock_sock(sk); + + while (len) { + size = min_t(uint, len, d->mtu); + + skb = sock_alloc_send_skb(sk, size + RFCOMM_SKB_RESERVE, + msg->msg_flags & MSG_DONTWAIT, &err); + if (!skb) + break; + skb_reserve(skb, RFCOMM_SKB_HEAD_RESERVE); + + err = memcpy_fromiovec(skb_put(skb, size), msg->msg_iov, size); + if (err) { + kfree_skb(skb); + sent = err; + break; + } + + err = rfcomm_dlc_send(d, skb); + if (err < 0) { + kfree_skb(skb); + break; + } + + sent += size; + len -= size; + } + + release_sock(sk); + + return sent ? sent : err; +} + +static long rfcomm_sock_data_wait(struct sock *sk, long timeo) +{ + DECLARE_WAITQUEUE(wait, current); + + add_wait_queue(sk->sleep, &wait); + for (;;) { + set_current_state(TASK_INTERRUPTIBLE); + + if (skb_queue_len(&sk->receive_queue) || sk->err || (sk->shutdown & RCV_SHUTDOWN) || + signal_pending(current) || !timeo) + break; + + set_bit(SOCK_ASYNC_WAITDATA, &sk->socket->flags); + release_sock(sk); + timeo = schedule_timeout(timeo); + lock_sock(sk); + clear_bit(SOCK_ASYNC_WAITDATA, &sk->socket->flags); + } + + __set_current_state(TASK_RUNNING); + remove_wait_queue(sk->sleep, &wait); + return timeo; +} + +static int rfcomm_sock_recvmsg(struct socket *sock, struct msghdr *msg, int size, + int flags, struct scm_cookie *scm) +{ + struct sock *sk = sock->sk; + int target, err = 0, copied = 0; + long timeo; + + if (flags & MSG_OOB) + return -EOPNOTSUPP; + + msg->msg_namelen = 0; + + BT_DBG("sk %p size %d", sk, size); + + lock_sock(sk); + + target = sock_rcvlowat(sk, flags & MSG_WAITALL, size); + timeo = sock_rcvtimeo(sk, flags & MSG_DONTWAIT); + + do { + struct sk_buff *skb; + int chunk; + + skb = skb_dequeue(&sk->receive_queue); + if (!skb) { + if (copied >= target) + break; + + if ((err = sock_error(sk)) != 0) + break; + if (sk->shutdown & RCV_SHUTDOWN) + break; + + err = -EAGAIN; + if (!timeo) + break; + + timeo = rfcomm_sock_data_wait(sk, timeo); + + if (signal_pending(current)) { + err = sock_intr_errno(timeo); + goto out; + } + continue; + } + + chunk = min_t(unsigned int, skb->len, size); + if (memcpy_toiovec(msg->msg_iov, skb->data, chunk)) { + skb_queue_head(&sk->receive_queue, skb); + if (!copied) + copied = -EFAULT; + break; + } + copied += chunk; + size -= chunk; + + if (!(flags & MSG_PEEK)) { + atomic_sub(chunk, &sk->rmem_alloc); + + skb_pull(skb, chunk); + if (skb->len) { + skb_queue_head(&sk->receive_queue, skb); + break; + } + kfree_skb(skb); + + } else { + /* put message back and return */ + skb_queue_head(&sk->receive_queue, skb); + break; + } + } while (size); + +out: + if (atomic_read(&sk->rmem_alloc) <= (sk->rcvbuf >> 2)) + rfcomm_dlc_unthrottle(rfcomm_pi(sk)->dlc); + + release_sock(sk); + return copied ? : err; +} + +static int rfcomm_sock_setsockopt(struct socket *sock, int level, int optname, char *optval, int optlen) +{ + struct sock *sk = sock->sk; + int err = 0; + + BT_DBG("sk %p", sk); + + lock_sock(sk); + + switch (optname) { + default: + err = -ENOPROTOOPT; + break; + }; + + release_sock(sk); + return err; +} + +static int rfcomm_sock_getsockopt(struct socket *sock, int level, int optname, char *optval, int *optlen) +{ + struct sock *sk = sock->sk; + int len, err = 0; + + BT_DBG("sk %p", sk); + + if (get_user(len, optlen)) + return -EFAULT; + + lock_sock(sk); + + switch (optname) { + default: + err = -ENOPROTOOPT; + break; + }; + + release_sock(sk); + return err; +} + +static int rfcomm_sock_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg) +{ + struct sock *sk = sock->sk; + int err; + + lock_sock(sk); + +#ifdef CONFIG_BLUEZ_RFCOMM_TTY + err = rfcomm_dev_ioctl(sk, cmd, arg); +#else + err = -EOPNOTSUPP; +#endif + + release_sock(sk); + + return err; +} + +static int rfcomm_sock_shutdown(struct socket *sock, int how) +{ + struct sock *sk = sock->sk; + int err = 0; + + BT_DBG("sock %p, sk %p", sock, sk); + + if (!sk) return 0; + + lock_sock(sk); + if (!sk->shutdown) { + sk->shutdown = SHUTDOWN_MASK; + __rfcomm_sock_close(sk); + + if (sk->linger) + err = bluez_sock_wait_state(sk, BT_CLOSED, sk->lingertime); + } + release_sock(sk); + return err; +} + +static int rfcomm_sock_release(struct socket *sock) +{ + struct sock *sk = sock->sk; + int err = 0; + + BT_DBG("sock %p, sk %p", sock, sk); + + if (!sk) + return 0; + + err = rfcomm_sock_shutdown(sock, 2); + + sock_orphan(sk); + rfcomm_sock_kill(sk); + return err; +} + +/* ---- RFCOMM core layer callbacks ---- + * + * called under rfcomm_lock() + */ +int rfcomm_connect_ind(struct rfcomm_session *s, u8 channel, struct rfcomm_dlc **d) +{ + struct sock *sk, *parent; + bdaddr_t src, dst; + int result = 0; + + BT_DBG("session %p channel %d", s, channel); + + rfcomm_session_getaddr(s, &src, &dst); + + /* Check if we have socket listening on this channel */ + parent = rfcomm_get_sock_by_channel(BT_LISTEN, channel, &src); + if (!parent) + return 0; + + /* Check for backlog size */ + if (parent->ack_backlog > parent->max_ack_backlog) { + BT_DBG("backlog full %d", parent->ack_backlog); + goto done; + } + + sk = rfcomm_sock_alloc(NULL, BTPROTO_RFCOMM, GFP_ATOMIC); + if (!sk) + goto done; + + rfcomm_sock_init(sk, parent); + bacpy(&bluez_pi(sk)->src, &src); + bacpy(&bluez_pi(sk)->dst, &dst); + rfcomm_pi(sk)->channel = channel; + + sk->state = BT_CONFIG; + bluez_accept_enqueue(parent, sk); + + /* Accept connection and return socket DLC */ + *d = rfcomm_pi(sk)->dlc; + result = 1; + +done: + bh_unlock_sock(parent); + return result; +} + +/* ---- Proc fs support ---- */ +int rfcomm_sock_dump(char *buf) +{ + struct bluez_sock_list *list = &rfcomm_sk_list; + struct rfcomm_pinfo *pi; + struct sock *sk; + char *ptr = buf; + + write_lock_bh(&list->lock); + + for (sk = list->head; sk; sk = sk->next) { + pi = rfcomm_pi(sk); + ptr += sprintf(ptr, "sk %s %s %d %d\n", + batostr(&bluez_pi(sk)->src), batostr(&bluez_pi(sk)->dst), + sk->state, rfcomm_pi(sk)->channel); + } + + write_unlock_bh(&list->lock); + + return ptr - buf; +} + +static struct proto_ops rfcomm_sock_ops = { + family: PF_BLUETOOTH, + release: rfcomm_sock_release, + bind: rfcomm_sock_bind, + connect: rfcomm_sock_connect, + listen: rfcomm_sock_listen, + accept: rfcomm_sock_accept, + getname: rfcomm_sock_getname, + sendmsg: rfcomm_sock_sendmsg, + recvmsg: rfcomm_sock_recvmsg, + shutdown: rfcomm_sock_shutdown, + setsockopt: rfcomm_sock_setsockopt, + getsockopt: rfcomm_sock_getsockopt, + ioctl: rfcomm_sock_ioctl, + poll: bluez_sock_poll, + socketpair: sock_no_socketpair, + mmap: sock_no_mmap +}; + +static struct net_proto_family rfcomm_sock_family_ops = { + family: PF_BLUETOOTH, + create: rfcomm_sock_create +}; + +int rfcomm_init_sockets(void) +{ + int err; + + if ((err = bluez_sock_register(BTPROTO_RFCOMM, &rfcomm_sock_family_ops))) { + BT_ERR("Can't register RFCOMM socket layer"); + return err; + } + + return 0; +} + +void rfcomm_cleanup_sockets(void) +{ + int err; + + /* Unregister socket, protocol and notifier */ + if ((err = bluez_sock_unregister(BTPROTO_RFCOMM))) + BT_ERR("Can't unregister RFCOMM socket layer %d", err); +} --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/net/bluetooth/rfcomm/tty.c 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1,945 @@ +/* + RFCOMM implementation for Linux Bluetooth stack (BlueZ). + Copyright (C) 2002 Maxim Krasnyansky + Copyright (C) 2002 Marcel Holtmann + + 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. +*/ + +/* + * RFCOMM TTY. + * + * $Id: tty.c,v 1.26 2002/10/18 20:12:12 maxk Exp $ + */ + +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include + +#ifndef CONFIG_BLUEZ_RFCOMM_DEBUG +#undef BT_DBG +#define BT_DBG(D...) +#endif + +#define RFCOMM_TTY_MAGIC 0x6d02 /* magic number for rfcomm struct */ +#define RFCOMM_TTY_PORTS RFCOMM_MAX_DEV /* whole lotta rfcomm devices */ +#define RFCOMM_TTY_MAJOR 216 /* device node major id of the usb/bluetooth.c driver */ +#define RFCOMM_TTY_MINOR 0 + +struct rfcomm_dev { + struct list_head list; + atomic_t refcnt; + + char name[12]; + int id; + unsigned long flags; + int opened; + int err; + + bdaddr_t src; + bdaddr_t dst; + u8 channel; + + uint modem_status; + + struct rfcomm_dlc *dlc; + struct tty_struct *tty; + wait_queue_head_t wait; + struct tasklet_struct wakeup_task; + + atomic_t wmem_alloc; +}; + +static LIST_HEAD(rfcomm_dev_list); +static rwlock_t rfcomm_dev_lock = RW_LOCK_UNLOCKED; + +static void rfcomm_dev_data_ready(struct rfcomm_dlc *dlc, struct sk_buff *skb); +static void rfcomm_dev_state_change(struct rfcomm_dlc *dlc, int err); +static void rfcomm_dev_modem_status(struct rfcomm_dlc *dlc, u8 v24_sig); + +static void rfcomm_tty_wakeup(unsigned long arg); + +/* ---- Device functions ---- */ +static void rfcomm_dev_destruct(struct rfcomm_dev *dev) +{ + struct rfcomm_dlc *dlc = dev->dlc; + + BT_DBG("dev %p dlc %p", dev, dlc); + + rfcomm_dlc_lock(dlc); + /* Detach DLC if it's owned by this dev */ + if (dlc->owner == dev) + dlc->owner = NULL; + rfcomm_dlc_unlock(dlc); + + rfcomm_dlc_put(dlc); + kfree(dev); + + MOD_DEC_USE_COUNT; +} + +static inline void rfcomm_dev_hold(struct rfcomm_dev *dev) +{ + atomic_inc(&dev->refcnt); +} + +static inline void rfcomm_dev_put(struct rfcomm_dev *dev) +{ + if (atomic_dec_and_test(&dev->refcnt)) + rfcomm_dev_destruct(dev); +} + +static struct rfcomm_dev *__rfcomm_dev_get(int id) +{ + struct rfcomm_dev *dev; + struct list_head *p; + + list_for_each(p, &rfcomm_dev_list) { + dev = list_entry(p, struct rfcomm_dev, list); + if (dev->id == id) + return dev; + } + + return NULL; +} + +static inline struct rfcomm_dev *rfcomm_dev_get(int id) +{ + struct rfcomm_dev *dev; + + read_lock(&rfcomm_dev_lock); + dev = __rfcomm_dev_get(id); + read_unlock(&rfcomm_dev_lock); + + if (dev) rfcomm_dev_hold(dev); + return dev; +} + +static int rfcomm_dev_add(struct rfcomm_dev_req *req, struct rfcomm_dlc *dlc) +{ + struct rfcomm_dev *dev; + struct list_head *head = &rfcomm_dev_list, *p; + int err = 0; + + BT_DBG("id %d channel %d", req->dev_id, req->channel); + + dev = kmalloc(sizeof(struct rfcomm_dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + memset(dev, 0, sizeof(struct rfcomm_dev)); + + write_lock_bh(&rfcomm_dev_lock); + + if (req->dev_id < 0) { + dev->id = 0; + + list_for_each(p, &rfcomm_dev_list) { + if (list_entry(p, struct rfcomm_dev, list)->id != dev->id) + break; + + dev->id++; + head = p; + } + } else { + dev->id = req->dev_id; + + list_for_each(p, &rfcomm_dev_list) { + struct rfcomm_dev *entry = list_entry(p, struct rfcomm_dev, list); + + if (entry->id == dev->id) { + err = -EADDRINUSE; + goto out; + } + + if (entry->id > dev->id - 1) + break; + + head = p; + } + } + + if ((dev->id < 0) || (dev->id > RFCOMM_MAX_DEV - 1)) { + err = -ENFILE; + goto out; + } + + sprintf(dev->name, "rfcomm%d", dev->id); + + list_add(&dev->list, head); + atomic_set(&dev->refcnt, 1); + + bacpy(&dev->src, &req->src); + bacpy(&dev->dst, &req->dst); + dev->channel = req->channel; + + dev->flags = req->flags & + ((1 << RFCOMM_RELEASE_ONHUP) | (1 << RFCOMM_REUSE_DLC)); + + init_waitqueue_head(&dev->wait); + tasklet_init(&dev->wakeup_task, rfcomm_tty_wakeup, (unsigned long) dev); + + rfcomm_dlc_lock(dlc); + dlc->data_ready = rfcomm_dev_data_ready; + dlc->state_change = rfcomm_dev_state_change; + dlc->modem_status = rfcomm_dev_modem_status; + + dlc->owner = dev; + dev->dlc = dlc; + rfcomm_dlc_unlock(dlc); + + MOD_INC_USE_COUNT; + +out: + write_unlock_bh(&rfcomm_dev_lock); + + if (err) { + kfree(dev); + return err; + } else + return dev->id; +} + +static void rfcomm_dev_del(struct rfcomm_dev *dev) +{ + BT_DBG("dev %p", dev); + + write_lock_bh(&rfcomm_dev_lock); + list_del_init(&dev->list); + write_unlock_bh(&rfcomm_dev_lock); + + rfcomm_dev_put(dev); +} + +/* ---- Send buffer ---- */ + +static inline unsigned int rfcomm_room(struct rfcomm_dlc *dlc) +{ + /* We can't let it be zero, because we don't get a callback + when tx_credits becomes nonzero, hence we'd never wake up */ + return dlc->mtu * (dlc->tx_credits?:1); +} + +static void rfcomm_wfree(struct sk_buff *skb) +{ + struct rfcomm_dev *dev = (void *) skb->sk; + atomic_sub(skb->truesize, &dev->wmem_alloc); + if (test_bit(RFCOMM_TTY_ATTACHED, &dev->flags)) + tasklet_schedule(&dev->wakeup_task); + rfcomm_dev_put(dev); +} + +static inline void rfcomm_set_owner_w(struct sk_buff *skb, struct rfcomm_dev *dev) +{ + rfcomm_dev_hold(dev); + atomic_add(skb->truesize, &dev->wmem_alloc); + skb->sk = (void *) dev; + skb->destructor = rfcomm_wfree; +} + +static struct sk_buff *rfcomm_wmalloc(struct rfcomm_dev *dev, unsigned long size, int force, int priority) +{ + if (force || atomic_read(&dev->wmem_alloc) < rfcomm_room(dev->dlc)) { + struct sk_buff *skb = alloc_skb(size, priority); + if (skb) { + rfcomm_set_owner_w(skb, dev); + return skb; + } + } + return NULL; +} + +/* ---- Device IOCTLs ---- */ + +#define NOCAP_FLAGS ((1 << RFCOMM_REUSE_DLC) | (1 << RFCOMM_RELEASE_ONHUP)) + +static int rfcomm_create_dev(struct sock *sk, unsigned long arg) +{ + struct rfcomm_dev_req req; + struct rfcomm_dlc *dlc; + int id; + + if (copy_from_user(&req, (void *) arg, sizeof(req))) + return -EFAULT; + + BT_DBG("sk %p dev_id %id flags 0x%x", sk, req.dev_id, req.flags); + + if (req.flags != NOCAP_FLAGS && !capable(CAP_NET_ADMIN)) + return -EPERM; + + if (req.flags & (1 << RFCOMM_REUSE_DLC)) { + /* Socket must be connected */ + if (sk->state != BT_CONNECTED) + return -EBADFD; + + dlc = rfcomm_pi(sk)->dlc; + rfcomm_dlc_hold(dlc); + } else { + dlc = rfcomm_dlc_alloc(GFP_KERNEL); + if (!dlc) + return -ENOMEM; + } + + id = rfcomm_dev_add(&req, dlc); + if (id < 0) { + rfcomm_dlc_put(dlc); + return id; + } + + if (req.flags & (1 << RFCOMM_REUSE_DLC)) { + /* DLC is now used by device. + * Socket must be disconnected */ + sk->state = BT_CLOSED; + } + + return id; +} + +static int rfcomm_release_dev(unsigned long arg) +{ + struct rfcomm_dev_req req; + struct rfcomm_dev *dev; + + if (copy_from_user(&req, (void *) arg, sizeof(req))) + return -EFAULT; + + BT_DBG("dev_id %id flags 0x%x", req.dev_id, req.flags); + + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + + if (!(dev = rfcomm_dev_get(req.dev_id))) + return -ENODEV; + + if (req.flags & (1 << RFCOMM_HANGUP_NOW)) + rfcomm_dlc_close(dev->dlc, 0); + + rfcomm_dev_del(dev); + rfcomm_dev_put(dev); + return 0; +} + +static int rfcomm_get_dev_list(unsigned long arg) +{ + struct rfcomm_dev_list_req *dl; + struct rfcomm_dev_info *di; + struct list_head *p; + int n = 0, size; + u16 dev_num; + + BT_DBG(""); + + if (get_user(dev_num, (u16 *) arg)) + return -EFAULT; + + if (!dev_num) + return -EINVAL; + + size = sizeof(*dl) + dev_num * sizeof(*di); + + if (verify_area(VERIFY_WRITE, (void *)arg, size)) + return -EFAULT; + + if (!(dl = kmalloc(size, GFP_KERNEL))) + return -ENOMEM; + + di = dl->dev_info; + + read_lock_bh(&rfcomm_dev_lock); + + list_for_each(p, &rfcomm_dev_list) { + struct rfcomm_dev *dev = list_entry(p, struct rfcomm_dev, list); + (di + n)->id = dev->id; + (di + n)->flags = dev->flags; + (di + n)->state = dev->dlc->state; + (di + n)->channel = dev->channel; + bacpy(&(di + n)->src, &dev->src); + bacpy(&(di + n)->dst, &dev->dst); + if (++n >= dev_num) + break; + } + + read_unlock_bh(&rfcomm_dev_lock); + + dl->dev_num = n; + size = sizeof(*dl) + n * sizeof(*di); + + copy_to_user((void *) arg, dl, size); + kfree(dl); + return 0; +} + +static int rfcomm_get_dev_info(unsigned long arg) +{ + struct rfcomm_dev *dev; + struct rfcomm_dev_info di; + int err = 0; + + BT_DBG(""); + + if (copy_from_user(&di, (void *)arg, sizeof(di))) + return -EFAULT; + + if (!(dev = rfcomm_dev_get(di.id))) + return -ENODEV; + + di.flags = dev->flags; + di.channel = dev->channel; + di.state = dev->dlc->state; + bacpy(&di.src, &dev->src); + bacpy(&di.dst, &dev->dst); + + if (copy_to_user((void *)arg, &di, sizeof(di))) + err = -EFAULT; + + rfcomm_dev_put(dev); + return err; +} + +int rfcomm_dev_ioctl(struct sock *sk, unsigned int cmd, unsigned long arg) +{ + BT_DBG("cmd %d arg %ld", cmd, arg); + + switch (cmd) { + case RFCOMMCREATEDEV: + return rfcomm_create_dev(sk, arg); + + case RFCOMMRELEASEDEV: + return rfcomm_release_dev(arg); + + case RFCOMMGETDEVLIST: + return rfcomm_get_dev_list(arg); + + case RFCOMMGETDEVINFO: + return rfcomm_get_dev_info(arg); + } + + return -EINVAL; +} + +/* ---- DLC callbacks ---- */ +static void rfcomm_dev_data_ready(struct rfcomm_dlc *dlc, struct sk_buff *skb) +{ + struct rfcomm_dev *dev = dlc->owner; + struct tty_struct *tty; + + if (!dev || !(tty = dev->tty)) { + kfree_skb(skb); + return; + } + + BT_DBG("dlc %p tty %p len %d", dlc, tty, skb->len); + + if (test_bit(TTY_DONT_FLIP, &tty->flags)) { + register int i; + for (i = 0; i < skb->len; i++) { + if (tty->flip.count >= TTY_FLIPBUF_SIZE) + tty_flip_buffer_push(tty); + + tty_insert_flip_char(tty, skb->data[i], 0); + } + tty_flip_buffer_push(tty); + } else + tty->ldisc.receive_buf(tty, skb->data, NULL, skb->len); + + kfree_skb(skb); +} + +static void rfcomm_dev_state_change(struct rfcomm_dlc *dlc, int err) +{ + struct rfcomm_dev *dev = dlc->owner; + if (!dev) + return; + + BT_DBG("dlc %p dev %p err %d", dlc, dev, err); + + dev->err = err; + wake_up_interruptible(&dev->wait); + + if (dlc->state == BT_CLOSED) { + if (!dev->tty) { + if (test_bit(RFCOMM_RELEASE_ONHUP, &dev->flags)) { + rfcomm_dev_hold(dev); + rfcomm_dev_del(dev); + + /* We have to drop DLC lock here, otherwise + * rfcomm_dev_put() will dead lock if it's the last refference */ + rfcomm_dlc_unlock(dlc); + rfcomm_dev_put(dev); + rfcomm_dlc_lock(dlc); + } + } else + tty_hangup(dev->tty); + } +} + +static void rfcomm_dev_modem_status(struct rfcomm_dlc *dlc, u8 v24_sig) +{ + struct rfcomm_dev *dev = dlc->owner; + if (!dev) + return; + + BT_DBG("dlc %p dev %p v24_sig 0x%02x", dlc, dev, v24_sig); + + dev->modem_status = + ((v24_sig & RFCOMM_V24_RTC) ? (TIOCM_DSR | TIOCM_DTR) : 0) | + ((v24_sig & RFCOMM_V24_RTR) ? (TIOCM_RTS | TIOCM_CTS) : 0) | + ((v24_sig & RFCOMM_V24_IC) ? TIOCM_RI : 0) | + ((v24_sig & RFCOMM_V24_DV) ? TIOCM_CD : 0); +} + +/* ---- TTY functions ---- */ +static void rfcomm_tty_wakeup(unsigned long arg) +{ + struct rfcomm_dev *dev = (void *) arg; + struct tty_struct *tty = dev->tty; + if (!tty) + return; + + BT_DBG("dev %p tty %p", dev, tty); + + if (test_bit(TTY_DO_WRITE_WAKEUP, &tty->flags) && tty->ldisc.write_wakeup) + (tty->ldisc.write_wakeup)(tty); + + wake_up_interruptible(&tty->write_wait); +#ifdef SERIAL_HAVE_POLL_WAIT + wake_up_interruptible(&tty->poll_wait); +#endif +} + +static int rfcomm_tty_open(struct tty_struct *tty, struct file *filp) +{ + DECLARE_WAITQUEUE(wait, current); + struct rfcomm_dev *dev; + struct rfcomm_dlc *dlc; + int err, id; + + id = MINOR(tty->device) - tty->driver.minor_start; + + BT_DBG("tty %p id %d", tty, id); + + dev = rfcomm_dev_get(id); + if (!dev) + return -ENODEV; + + BT_DBG("dev %p dst %s channel %d opened %d", dev, batostr(&dev->dst), dev->channel, dev->opened); + + if (dev->opened++ != 0) + return 0; + + dlc = dev->dlc; + + /* Attach TTY and open DLC */ + + rfcomm_dlc_lock(dlc); + tty->driver_data = dev; + dev->tty = tty; + rfcomm_dlc_unlock(dlc); + set_bit(RFCOMM_TTY_ATTACHED, &dev->flags); + + err = rfcomm_dlc_open(dlc, &dev->src, &dev->dst, dev->channel); + if (err < 0) + return err; + + /* Wait for DLC to connect */ + add_wait_queue(&dev->wait, &wait); + while (1) { + set_current_state(TASK_INTERRUPTIBLE); + + if (dlc->state == BT_CLOSED) { + err = -dev->err; + break; + } + + if (dlc->state == BT_CONNECTED) + break; + + if (signal_pending(current)) { + err = -EINTR; + break; + } + + schedule(); + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&dev->wait, &wait); + + return err; +} + +static void rfcomm_tty_close(struct tty_struct *tty, struct file *filp) +{ + struct rfcomm_dev *dev = (struct rfcomm_dev *) tty->driver_data; + if (!dev) + return; + + BT_DBG("tty %p dev %p dlc %p opened %d", tty, dev, dev->dlc, dev->opened); + + if (--dev->opened == 0) { + /* Close DLC and dettach TTY */ + rfcomm_dlc_close(dev->dlc, 0); + + clear_bit(RFCOMM_TTY_ATTACHED, &dev->flags); + tasklet_kill(&dev->wakeup_task); + + rfcomm_dlc_lock(dev->dlc); + tty->driver_data = NULL; + dev->tty = NULL; + rfcomm_dlc_unlock(dev->dlc); + } + + rfcomm_dev_put(dev); +} + +static int rfcomm_tty_write(struct tty_struct *tty, int from_user, const unsigned char *buf, int count) +{ + struct rfcomm_dev *dev = (struct rfcomm_dev *) tty->driver_data; + struct rfcomm_dlc *dlc = dev->dlc; + struct sk_buff *skb; + int err = 0, sent = 0, size; + + BT_DBG("tty %p from_user %d count %d", tty, from_user, count); + + while (count) { + size = min_t(uint, count, dlc->mtu); + + if (from_user) + skb = rfcomm_wmalloc(dev, size + RFCOMM_SKB_RESERVE, 0, GFP_KERNEL); + else + skb = rfcomm_wmalloc(dev, size + RFCOMM_SKB_RESERVE, 0, GFP_ATOMIC); + + if (!skb) + break; + + skb_reserve(skb, RFCOMM_SKB_HEAD_RESERVE); + + if (from_user) + copy_from_user(skb_put(skb, size), buf + sent, size); + else + memcpy(skb_put(skb, size), buf + sent, size); + + if ((err = rfcomm_dlc_send(dlc, skb)) < 0) { + kfree_skb(skb); + break; + } + + sent += size; + count -= size; + } + + return sent ? sent : err; +} + +static void rfcomm_tty_put_char(struct tty_struct *tty, unsigned char ch) +{ + struct rfcomm_dev *dev = (struct rfcomm_dev *) tty->driver_data; + struct rfcomm_dlc *dlc = dev->dlc; + struct sk_buff *skb; + + BT_DBG("tty %p char %x", tty, ch); + + skb = rfcomm_wmalloc(dev, 1 + RFCOMM_SKB_RESERVE, 1, GFP_ATOMIC); + + if (!skb) + return; + + skb_reserve(skb, RFCOMM_SKB_HEAD_RESERVE); + + *(char *)skb_put(skb, 1) = ch; + + if ((rfcomm_dlc_send(dlc, skb)) < 0) + kfree_skb(skb); +} + +static int rfcomm_tty_write_room(struct tty_struct *tty) +{ + struct rfcomm_dev *dev = (struct rfcomm_dev *) tty->driver_data; + int room; + + BT_DBG("tty %p", tty); + + room = rfcomm_room(dev->dlc) - atomic_read(&dev->wmem_alloc); + if (room < 0) + room = 0; + + return room; +} + +static int rfcomm_tty_set_modem_status(uint cmd, struct rfcomm_dlc *dlc, uint status) +{ + u8 v24_sig, mask; + + BT_DBG("dlc %p cmd 0x%02x", dlc, cmd); + + if (cmd == TIOCMSET) + v24_sig = 0; + else + rfcomm_dlc_get_modem_status(dlc, &v24_sig); + + mask = ((status & TIOCM_DSR) ? RFCOMM_V24_RTC : 0) | + ((status & TIOCM_DTR) ? RFCOMM_V24_RTC : 0) | + ((status & TIOCM_RTS) ? RFCOMM_V24_RTR : 0) | + ((status & TIOCM_CTS) ? RFCOMM_V24_RTR : 0) | + ((status & TIOCM_RI) ? RFCOMM_V24_IC : 0) | + ((status & TIOCM_CD) ? RFCOMM_V24_DV : 0); + + if (cmd == TIOCMBIC) + v24_sig &= ~mask; + else + v24_sig |= mask; + + rfcomm_dlc_set_modem_status(dlc, v24_sig); + return 0; +} + +static int rfcomm_tty_ioctl(struct tty_struct *tty, struct file *filp, unsigned int cmd, unsigned long arg) +{ + struct rfcomm_dev *dev = (struct rfcomm_dev *) tty->driver_data; + struct rfcomm_dlc *dlc = dev->dlc; + uint status; + int err; + + BT_DBG("tty %p cmd 0x%02x", tty, cmd); + + switch (cmd) { + case TCGETS: + BT_DBG("TCGETS is not supported"); + return -ENOIOCTLCMD; + + case TCSETS: + BT_DBG("TCSETS is not supported"); + return -ENOIOCTLCMD; + + case TIOCMGET: + BT_DBG("TIOCMGET"); + + return put_user(dev->modem_status, (unsigned int *)arg); + + case TIOCMSET: /* Turns on and off the lines as specified by the mask */ + case TIOCMBIS: /* Turns on the lines as specified by the mask */ + case TIOCMBIC: /* Turns off the lines as specified by the mask */ + if ((err = get_user(status, (unsigned int *)arg))) + return err; + return rfcomm_tty_set_modem_status(cmd, dlc, status); + + case TIOCMIWAIT: + BT_DBG("TIOCMIWAIT"); + break; + + case TIOCGICOUNT: + BT_DBG("TIOCGICOUNT"); + break; + + case TIOCGSERIAL: + BT_ERR("TIOCGSERIAL is not supported"); + return -ENOIOCTLCMD; + + case TIOCSSERIAL: + BT_ERR("TIOCSSERIAL is not supported"); + return -ENOIOCTLCMD; + + case TIOCSERGSTRUCT: + BT_ERR("TIOCSERGSTRUCT is not supported"); + return -ENOIOCTLCMD; + + case TIOCSERGETLSR: + BT_ERR("TIOCSERGETLSR is not supported"); + return -ENOIOCTLCMD; + + case TIOCSERCONFIG: + BT_ERR("TIOCSERCONFIG is not supported"); + return -ENOIOCTLCMD; + + default: + return -ENOIOCTLCMD; /* ioctls which we must ignore */ + + } + + return -ENOIOCTLCMD; +} + +#define RELEVANT_IFLAG(iflag) (iflag & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK)) + +static void rfcomm_tty_set_termios(struct tty_struct *tty, struct termios *old) +{ + BT_DBG("tty %p", tty); + + if ((tty->termios->c_cflag == old->c_cflag) && + (RELEVANT_IFLAG(tty->termios->c_iflag) == RELEVANT_IFLAG(old->c_iflag))) + return; + + /* handle turning off CRTSCTS */ + if ((old->c_cflag & CRTSCTS) && !(tty->termios->c_cflag & CRTSCTS)) { + BT_DBG("turning off CRTSCTS"); + } +} + +static void rfcomm_tty_throttle(struct tty_struct *tty) +{ + struct rfcomm_dev *dev = (struct rfcomm_dev *) tty->driver_data; + + BT_DBG("tty %p dev %p", tty, dev); + + rfcomm_dlc_throttle(dev->dlc); +} + +static void rfcomm_tty_unthrottle(struct tty_struct *tty) +{ + struct rfcomm_dev *dev = (struct rfcomm_dev *) tty->driver_data; + + BT_DBG("tty %p dev %p", tty, dev); + + rfcomm_dlc_unthrottle(dev->dlc); +} + +static int rfcomm_tty_chars_in_buffer(struct tty_struct *tty) +{ + struct rfcomm_dev *dev = (struct rfcomm_dev *) tty->driver_data; + struct rfcomm_dlc *dlc = dev->dlc; + + BT_DBG("tty %p dev %p", tty, dev); + + if (skb_queue_len(&dlc->tx_queue)) + return dlc->mtu; + + return 0; +} + +static void rfcomm_tty_flush_buffer(struct tty_struct *tty) +{ + struct rfcomm_dev *dev = (struct rfcomm_dev *) tty->driver_data; + if (!dev) + return; + + BT_DBG("tty %p dev %p", tty, dev); + + skb_queue_purge(&dev->dlc->tx_queue); + + if (test_bit(TTY_DO_WRITE_WAKEUP, &tty->flags) && tty->ldisc.write_wakeup) + tty->ldisc.write_wakeup(tty); +} + +static void rfcomm_tty_send_xchar(struct tty_struct *tty, char ch) +{ + BT_DBG("tty %p ch %c", tty, ch); +} + +static void rfcomm_tty_wait_until_sent(struct tty_struct *tty, int timeout) +{ + BT_DBG("tty %p timeout %d", tty, timeout); +} + +static void rfcomm_tty_hangup(struct tty_struct *tty) +{ + struct rfcomm_dev *dev = (struct rfcomm_dev *) tty->driver_data; + if (!dev) + return; + + BT_DBG("tty %p dev %p", tty, dev); + + rfcomm_tty_flush_buffer(tty); + + if (test_bit(RFCOMM_RELEASE_ONHUP, &dev->flags)) + rfcomm_dev_del(dev); +} + +static int rfcomm_tty_read_proc(char *buf, char **start, off_t offset, int len, int *eof, void *unused) +{ + return 0; +} + +/* ---- TTY structure ---- */ +static int rfcomm_tty_refcount; /* If we manage several devices */ + +static struct tty_struct *rfcomm_tty_table[RFCOMM_TTY_PORTS]; +static struct termios *rfcomm_tty_termios[RFCOMM_TTY_PORTS]; +static struct termios *rfcomm_tty_termios_locked[RFCOMM_TTY_PORTS]; + +static struct tty_driver rfcomm_tty_driver = { + magic: TTY_DRIVER_MAGIC, + driver_name: "rfcomm", +#ifdef CONFIG_DEVFS_FS + name: "bluetooth/rfcomm/%d", +#else + name: "rfcomm", +#endif + major: RFCOMM_TTY_MAJOR, + minor_start: RFCOMM_TTY_MINOR, + num: RFCOMM_TTY_PORTS, + type: TTY_DRIVER_TYPE_SERIAL, + subtype: SERIAL_TYPE_NORMAL, + flags: TTY_DRIVER_REAL_RAW, + + refcount: &rfcomm_tty_refcount, + table: rfcomm_tty_table, + termios: rfcomm_tty_termios, + termios_locked: rfcomm_tty_termios_locked, + + open: rfcomm_tty_open, + close: rfcomm_tty_close, + put_char: rfcomm_tty_put_char, + write: rfcomm_tty_write, + write_room: rfcomm_tty_write_room, + chars_in_buffer: rfcomm_tty_chars_in_buffer, + flush_buffer: rfcomm_tty_flush_buffer, + ioctl: rfcomm_tty_ioctl, + throttle: rfcomm_tty_throttle, + unthrottle: rfcomm_tty_unthrottle, + set_termios: rfcomm_tty_set_termios, + send_xchar: rfcomm_tty_send_xchar, + stop: NULL, + start: NULL, + hangup: rfcomm_tty_hangup, + wait_until_sent: rfcomm_tty_wait_until_sent, + read_proc: rfcomm_tty_read_proc, +}; + +int rfcomm_init_ttys(void) +{ + int i; + + /* Initalize our global data */ + for (i = 0; i < RFCOMM_TTY_PORTS; i++) + rfcomm_tty_table[i] = NULL; + + /* Register the TTY driver */ + rfcomm_tty_driver.init_termios = tty_std_termios; + rfcomm_tty_driver.init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL; + rfcomm_tty_driver.flags = TTY_DRIVER_REAL_RAW; + + if (tty_register_driver(&rfcomm_tty_driver)) { + BT_ERR("Can't register RFCOMM TTY driver"); + return -1; + } + + return 0; +} + +void rfcomm_cleanup_ttys(void) +{ + tty_unregister_driver(&rfcomm_tty_driver); + return; +} --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ linux/net/bluetooth/sco.c 2004-01-25 23:37:39.000000000 +0100 @@ -0,0 +1,1019 @@ +/* + BlueZ - Bluetooth protocol stack for Linux + Copyright (C) 2000-2001 Qualcomm Incorporated + + Written 2000,2001 by Maxim Krasnyansky + + 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. +*/ + +/* + * BlueZ SCO sockets. + * + * $Id: sco.c,v 1.4 2002/07/22 20:32:54 maxk Exp $ + */ +#define VERSION "0.3" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#ifndef SCO_DEBUG +#undef BT_DBG +#define BT_DBG( A... ) +#endif + +static struct proto_ops sco_sock_ops; + +static struct bluez_sock_list sco_sk_list = { + lock: RW_LOCK_UNLOCKED +}; + +static inline int sco_chan_add(struct sco_conn *conn, struct sock *sk, struct sock *parent); +static void sco_chan_del(struct sock *sk, int err); +static inline struct sock * sco_chan_get(struct sco_conn *conn); + +static int sco_conn_del(struct hci_conn *conn, int err); + +static void sco_sock_close(struct sock *sk); +static void sco_sock_kill(struct sock *sk); + +/* ----- SCO timers ------ */ +static void sco_sock_timeout(unsigned long arg) +{ + struct sock *sk = (struct sock *) arg; + + BT_DBG("sock %p state %d", sk, sk->state); + + bh_lock_sock(sk); + sk->err = ETIMEDOUT; + sk->state_change(sk); + bh_unlock_sock(sk); + + sco_sock_kill(sk); + sock_put(sk); +} + +static void sco_sock_set_timer(struct sock *sk, long timeout) +{ + BT_DBG("sock %p state %d timeout %ld", sk, sk->state, timeout); + + if (!mod_timer(&sk->timer, jiffies + timeout)) + sock_hold(sk); +} + +static void sco_sock_clear_timer(struct sock *sk) +{ + BT_DBG("sock %p state %d", sk, sk->state); + + if (timer_pending(&sk->timer) && del_timer(&sk->timer)) + __sock_put(sk); +} + +static void sco_sock_init_timer(struct sock *sk) +{ + init_timer(&sk->timer); + sk->timer.function = sco_sock_timeout; + sk->timer.data = (unsigned long)sk; +} + +/* -------- SCO connections --------- */ +static struct sco_conn *sco_conn_add(struct hci_conn *hcon, __u8 status) +{ + struct hci_dev *hdev = hcon->hdev; + struct sco_conn *conn; + + if ((conn = hcon->sco_data)) + return conn; + + if (status) + return conn; + + if (!(conn = kmalloc(sizeof(struct sco_conn), GFP_ATOMIC))) + return NULL; + memset(conn, 0, sizeof(struct sco_conn)); + + spin_lock_init(&conn->lock); + + hcon->sco_data = conn; + conn->hcon = hcon; + + conn->src = &hdev->bdaddr; + conn->dst = &hcon->dst; + + if (hdev->sco_mtu > 0) + conn->mtu = hdev->sco_mtu; + else + conn->mtu = 60; + + BT_DBG("hcon %p conn %p", hcon, conn); + + MOD_INC_USE_COUNT; + return conn; +} + +static int sco_conn_del(struct hci_conn *hcon, int err) +{ + struct sco_conn *conn; + struct sock *sk; + + if (!(conn = hcon->sco_data)) + return 0; + + BT_DBG("hcon %p conn %p, err %d", hcon, conn, err); + + /* Kill socket */ + if ((sk = sco_chan_get(conn))) { + bh_lock_sock(sk); + sco_sock_clear_timer(sk); + sco_chan_del(sk, err); + bh_unlock_sock(sk); + sco_sock_kill(sk); + } + + hcon->sco_data = NULL; + kfree(conn); + + MOD_DEC_USE_COUNT; + return 0; +} + +int sco_connect(struct sock *sk) +{ + bdaddr_t *src = &bluez_pi(sk)->src; + bdaddr_t *dst = &bluez_pi(sk)->dst; + struct sco_conn *conn; + struct hci_conn *hcon; + struct hci_dev *hdev; + int err = 0; + + BT_DBG("%s -> %s", batostr(src), batostr(dst)); + + if (!(hdev = hci_get_route(dst, src))) + return -EHOSTUNREACH; + + hci_dev_lock_bh(hdev); + + err = -ENOMEM; + + hcon = hci_connect(hdev, SCO_LINK, dst); + if (!hcon) + goto done; + + conn = sco_conn_add(hcon, 0); + if (!conn) { + hci_conn_put(hcon); + goto done; + } + + /* Update source addr of the socket */ + bacpy(src, conn->src); + + err = sco_chan_add(conn, sk, NULL); + if (err) + goto done; + + if (hcon->state == BT_CONNECTED) { + sco_sock_clear_timer(sk); + sk->state = BT_CONNECTED; + } else { + sk->state = BT_CONNECT; + sco_sock_set_timer(sk, sk->sndtimeo); + } +done: + hci_dev_unlock_bh(hdev); + hci_dev_put(hdev); + return err; +} + +static inline int sco_send_frame(struct sock *sk, struct msghdr *msg, int len) +{ + struct sco_conn *conn = sco_pi(sk)->conn; + struct sk_buff *skb; + int err, count; + + /* Check outgoing MTU */ + if (len > conn->mtu) + return -EINVAL; + + BT_DBG("sk %p len %d", sk, len); + + count = MIN(conn->mtu, len); + if (!(skb = bluez_skb_send_alloc(sk, count, msg->msg_flags & MSG_DONTWAIT, &err))) + return err; + + if (memcpy_fromiovec(skb_put(skb, count), msg->msg_iov, count)) { + err = -EFAULT; + goto fail; + } + + if ((err = hci_send_sco(conn->hcon, skb)) < 0) + goto fail; + + return count; + +fail: + kfree_skb(skb); + return err; +} + +static inline void sco_recv_frame(struct sco_conn *conn, struct sk_buff *skb) +{ + struct sock *sk = sco_chan_get(conn); + + if (!sk) + goto drop; + + BT_DBG("sk %p len %d", sk, skb->len); + + if (sk->state != BT_CONNECTED) + goto drop; + + if (!sock_queue_rcv_skb(sk, skb)) + return; + +drop: + kfree_skb(skb); + return; +} + +/* -------- Socket interface ---------- */ +static struct sock *__sco_get_sock_by_addr(bdaddr_t *ba) +{ + struct sock *sk; + + for (sk = sco_sk_list.head; sk; sk = sk->next) { + if (!bacmp(&bluez_pi(sk)->src, ba)) + break; + } + + return sk; +} + +/* Find socket listening on source bdaddr. + * Returns closest match. + */ +static struct sock *sco_get_sock_listen(bdaddr_t *src) +{ + struct sock *sk, *sk1 = NULL; + + read_lock(&sco_sk_list.lock); + + for (sk = sco_sk_list.head; sk; sk = sk->next) { + if (sk->state != BT_LISTEN) + continue; + + /* Exact match. */ + if (!bacmp(&bluez_pi(sk)->src, src)) + break; + + /* Closest match */ + if (!bacmp(&bluez_pi(sk)->src, BDADDR_ANY)) + sk1 = sk; + } + + read_unlock(&sco_sk_list.lock); + + return sk ? sk : sk1; +} + +static void sco_sock_destruct(struct sock *sk) +{ + BT_DBG("sk %p", sk); + + skb_queue_purge(&sk->receive_queue); + skb_queue_purge(&sk->write_queue); + + MOD_DEC_USE_COUNT; +} + +static void sco_sock_cleanup_listen(struct sock *parent) +{ + struct sock *sk; + + BT_DBG("parent %p", parent); + + /* Close not yet accepted channels */ + while ((sk = bluez_accept_dequeue(parent, NULL))) { + sco_sock_close(sk); + sco_sock_kill(sk); + } + + parent->state = BT_CLOSED; + parent->zapped = 1; +} + +/* Kill socket (only if zapped and orphan) + * Must be called on unlocked socket. + */ +static void sco_sock_kill(struct sock *sk) +{ + if (!sk->zapped || sk->socket) + return; + + BT_DBG("sk %p state %d", sk, sk->state); + + /* Kill poor orphan */ + bluez_sock_unlink(&sco_sk_list, sk); + sk->dead = 1; + sock_put(sk); +} + +/* Close socket. + * Must be called on unlocked socket. + */ +static void sco_sock_close(struct sock *sk) +{ + struct sco_conn *conn; + + sco_sock_clear_timer(sk); + + lock_sock(sk); + + conn = sco_pi(sk)->conn; + + BT_DBG("sk %p state %d conn %p socket %p", sk, sk->state, conn, sk->socket); + + switch (sk->state) { + case BT_LISTEN: + sco_sock_cleanup_listen(sk); + break; + + case BT_CONNECTED: + case BT_CONFIG: + case BT_CONNECT: + case BT_DISCONN: + sco_chan_del(sk, ECONNRESET); + break; + + default: + sk->zapped = 1; + break; + }; + + release_sock(sk); +} + +static void sco_sock_init(struct sock *sk, struct sock *parent) +{ + BT_DBG("sk %p", sk); + + if (parent) + sk->type = parent->type; +} + +static struct sock *sco_sock_alloc(struct socket *sock, int proto, int prio) +{ + struct sock *sk; + + if (!(sk = sk_alloc(PF_BLUETOOTH, prio, 1))) + return NULL; + + bluez_sock_init(sock, sk); + + sk->zapped = 0; + + sk->destruct = sco_sock_destruct; + sk->sndtimeo = SCO_CONN_TIMEOUT; + + sk->protocol = proto; + sk->state = BT_OPEN; + + sco_sock_init_timer(sk); + + bluez_sock_link(&sco_sk_list, sk); + + MOD_INC_USE_COUNT; + return sk; +} + +static int sco_sock_create(struct socket *sock, int protocol) +{ + struct sock *sk; + + BT_DBG("sock %p", sock); + + sock->state = SS_UNCONNECTED; + + if (sock->type != SOCK_SEQPACKET) + return -ESOCKTNOSUPPORT; + + sock->ops = &sco_sock_ops; + + if (!(sk = sco_sock_alloc(sock, protocol, GFP_KERNEL))) + return -ENOMEM; + + sco_sock_init(sk, NULL); + return 0; +} + +static int sco_sock_bind(struct socket *sock, struct sockaddr *addr, int addr_len) +{ + struct sockaddr_sco *sa = (struct sockaddr_sco *) addr; + struct sock *sk = sock->sk; + bdaddr_t *src = &sa->sco_bdaddr; + int err = 0; + + BT_DBG("sk %p %s", sk, batostr(&sa->sco_bdaddr)); + + if (!addr || addr->sa_family != AF_BLUETOOTH) + return -EINVAL; + + lock_sock(sk); + + if (sk->state != BT_OPEN) { + err = -EBADFD; + goto done; + } + + write_lock_bh(&sco_sk_list.lock); + + if (bacmp(src, BDADDR_ANY) && __sco_get_sock_by_addr(src)) { + err = -EADDRINUSE; + } else { + /* Save source address */ + bacpy(&bluez_pi(sk)->src, &sa->sco_bdaddr); + sk->state = BT_BOUND; + } + + write_unlock_bh(&sco_sk_list.lock); + +done: + release_sock(sk); + + return err; +} + +static int sco_sock_connect(struct socket *sock, struct sockaddr *addr, int alen, int flags) +{ + struct sockaddr_sco *sa = (struct sockaddr_sco *) addr; + struct sock *sk = sock->sk; + int err = 0; + + + BT_DBG("sk %p", sk); + + if (addr->sa_family != AF_BLUETOOTH || alen < sizeof(struct sockaddr_sco)) + return -EINVAL; + + if (sk->state != BT_OPEN && sk->state != BT_BOUND) + return -EBADFD; + + if (sk->type != SOCK_SEQPACKET) + return -EINVAL; + + lock_sock(sk); + + /* Set destination address and psm */ + bacpy(&bluez_pi(sk)->dst, &sa->sco_bdaddr); + + if ((err = sco_connect(sk))) + goto done; + + err = bluez_sock_wait_state(sk, BT_CONNECTED, + sock_sndtimeo(sk, flags & O_NONBLOCK)); + +done: + release_sock(sk); + return err; +} + +int sco_sock_listen(struct socket *sock, int backlog) +{ + struct sock *sk = sock->sk; + int err = 0; + + BT_DBG("sk %p backlog %d", sk, backlog); + + lock_sock(sk); + + if (sk->state != BT_BOUND || sock->type != SOCK_SEQPACKET) { + err = -EBADFD; + goto done; + } + + sk->max_ack_backlog = backlog; + sk->ack_backlog = 0; + sk->state = BT_LISTEN; + +done: + release_sock(sk); + return err; +} + +int sco_sock_accept(struct socket *sock, struct socket *newsock, int flags) +{ + DECLARE_WAITQUEUE(wait, current); + struct sock *sk = sock->sk, *ch; + long timeo; + int err = 0; + + lock_sock(sk); + + if (sk->state != BT_LISTEN) { + err = -EBADFD; + goto done; + } + + timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK); + + BT_DBG("sk %p timeo %ld", sk, timeo); + + /* Wait for an incoming connection. (wake-one). */ + add_wait_queue_exclusive(sk->sleep, &wait); + while (!(ch = bluez_accept_dequeue(sk, newsock))) { + set_current_state(TASK_INTERRUPTIBLE); + if (!timeo) { + err = -EAGAIN; + break; + } + + release_sock(sk); + timeo = schedule_timeout(timeo); + lock_sock(sk); + + if (sk->state != BT_LISTEN) { + err = -EBADFD; + break; + } + + if (signal_pending(current)) { + err = sock_intr_errno(timeo); + break; + } + } + set_current_state(TASK_RUNNING); + remove_wait_queue(sk->sleep, &wait); + + if (err) + goto done; + + newsock->state = SS_CONNECTED; + + BT_DBG("new socket %p", ch); + +done: + release_sock(sk); + return err; +} + +static int sco_sock_getname(struct socket *sock, struct sockaddr *addr, int *len, int peer) +{ + struct sockaddr_sco *sa = (struct sockaddr_sco *) addr; + struct sock *sk = sock->sk; + + BT_DBG("sock %p, sk %p", sock, sk); + + addr->sa_family = AF_BLUETOOTH; + *len = sizeof(struct sockaddr_sco); + + if (peer) + bacpy(&sa->sco_bdaddr, &bluez_pi(sk)->dst); + else + bacpy(&sa->sco_bdaddr, &bluez_pi(sk)->src); + + return 0; +} + +static int sco_sock_sendmsg(struct socket *sock, struct msghdr *msg, int len, struct scm_cookie *scm) +{ + struct sock *sk = sock->sk; + int err = 0; + + BT_DBG("sock %p, sk %p", sock, sk); + + if (sk->err) + return sock_error(sk); + + if (msg->msg_flags & MSG_OOB) + return -EOPNOTSUPP; + + lock_sock(sk); + + if (sk->state == BT_CONNECTED) + err = sco_send_frame(sk, msg, len); + else + err = -ENOTCONN; + + release_sock(sk); + return err; +} + +int sco_sock_setsockopt(struct socket *sock, int level, int optname, char *optval, int optlen) +{ + struct sock *sk = sock->sk; + int err = 0; + + BT_DBG("sk %p", sk); + + lock_sock(sk); + + switch (optname) { + default: + err = -ENOPROTOOPT; + break; + }; + + release_sock(sk); + return err; +} + +int sco_sock_getsockopt(struct socket *sock, int level, int optname, char *optval, int *optlen) +{ + struct sock *sk = sock->sk; + struct sco_options opts; + struct sco_conninfo cinfo; + int len, err = 0; + + BT_DBG("sk %p", sk); + + if (get_user(len, optlen)) + return -EFAULT; + + lock_sock(sk); + + switch (optname) { + case SCO_OPTIONS: + if (sk->state != BT_CONNECTED) { + err = -ENOTCONN; + break; + } + + opts.mtu = sco_pi(sk)->conn->mtu; + + BT_DBG("mtu %d", opts.mtu); + + len = MIN(len, sizeof(opts)); + if (copy_to_user(optval, (char *)&opts, len)) + err = -EFAULT; + + break; + + case SCO_CONNINFO: + if (sk->state != BT_CONNECTED) { + err = -ENOTCONN; + break; + } + + cinfo.hci_handle = sco_pi(sk)->conn->hcon->handle; + + len = MIN(len, sizeof(cinfo)); + if (copy_to_user(optval, (char *)&cinfo, len)) + err = -EFAULT; + + break; + + default: + err = -ENOPROTOOPT; + break; + }; + + release_sock(sk); + return err; +} + +static int sco_sock_release(struct socket *sock) +{ + struct sock *sk = sock->sk; + int err = 0; + + BT_DBG("sock %p, sk %p", sock, sk); + + if (!sk) + return 0; + + sco_sock_close(sk); + if (sk->linger) { + lock_sock(sk); + err = bluez_sock_wait_state(sk, BT_CLOSED, sk->lingertime); + release_sock(sk); + } + + sock_orphan(sk); + sco_sock_kill(sk); + return err; +} + +static void __sco_chan_add(struct sco_conn *conn, struct sock *sk, struct sock *parent) +{ + BT_DBG("conn %p", conn); + + sco_pi(sk)->conn = conn; + conn->sk = sk; + + if (parent) + bluez_accept_enqueue(parent, sk); +} + +static inline int sco_chan_add(struct sco_conn *conn, struct sock *sk, struct sock *parent) +{ + int err = 0; + + sco_conn_lock(conn); + if (conn->sk) { + err = -EBUSY; + } else { + __sco_chan_add(conn, sk, parent); + } + sco_conn_unlock(conn); + return err; +} + +static inline struct sock * sco_chan_get(struct sco_conn *conn) +{ + struct sock *sk = NULL; + sco_conn_lock(conn); + sk = conn->sk; + sco_conn_unlock(conn); + return sk; +} + +/* Delete channel. + * Must be called on the locked socket. */ +static void sco_chan_del(struct sock *sk, int err) +{ + struct sco_conn *conn; + + conn = sco_pi(sk)->conn; + + BT_DBG("sk %p, conn %p, err %d", sk, conn, err); + + if (conn) { + sco_conn_lock(conn); + conn->sk = NULL; + sco_pi(sk)->conn = NULL; + sco_conn_unlock(conn); + hci_conn_put(conn->hcon); + } + + sk->state = BT_CLOSED; + sk->err = err; + sk->state_change(sk); + + sk->zapped = 1; +} + +static void sco_conn_ready(struct sco_conn *conn) +{ + struct sock *parent, *sk; + + BT_DBG("conn %p", conn); + + sco_conn_lock(conn); + + if ((sk = conn->sk)) { + sco_sock_clear_timer(sk); + bh_lock_sock(sk); + sk->state = BT_CONNECTED; + sk->state_change(sk); + bh_unlock_sock(sk); + } else { + parent = sco_get_sock_listen(conn->src); + if (!parent) + goto done; + + bh_lock_sock(parent); + + sk = sco_sock_alloc(NULL, BTPROTO_SCO, GFP_ATOMIC); + if (!sk) { + bh_unlock_sock(parent); + goto done; + } + + sco_sock_init(sk, parent); + + bacpy(&bluez_pi(sk)->src, conn->src); + bacpy(&bluez_pi(sk)->dst, conn->dst); + + hci_conn_hold(conn->hcon); + __sco_chan_add(conn, sk, parent); + + sk->state = BT_CONNECTED; + + /* Wake up parent */ + parent->data_ready(parent, 1); + + bh_unlock_sock(parent); + } + +done: + sco_conn_unlock(conn); +} + +/* ----- SCO interface with lower layer (HCI) ----- */ +int sco_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr, __u8 type) +{ + BT_DBG("hdev %s, bdaddr %s", hdev->name, batostr(bdaddr)); + + /* Always accept connection */ + return HCI_LM_ACCEPT; +} + +int sco_connect_cfm(struct hci_conn *hcon, __u8 status) +{ + BT_DBG("hcon %p bdaddr %s status %d", hcon, batostr(&hcon->dst), status); + + if (hcon->type != SCO_LINK) + return 0; + + if (!status) { + struct sco_conn *conn; + + conn = sco_conn_add(hcon, status); + if (conn) + sco_conn_ready(conn); + } else + sco_conn_del(hcon, bterr(status)); + + return 0; +} + +int sco_disconn_ind(struct hci_conn *hcon, __u8 reason) +{ + BT_DBG("hcon %p reason %d", hcon, reason); + + if (hcon->type != SCO_LINK) + return 0; + + sco_conn_del(hcon, bterr(reason)); + return 0; +} + +int sco_recv_scodata(struct hci_conn *hcon, struct sk_buff *skb) +{ + struct sco_conn *conn = hcon->sco_data; + + if (!conn) + goto drop; + + BT_DBG("conn %p len %d", conn, skb->len); + + if (skb->len) { + sco_recv_frame(conn, skb); + return 0; + } + +drop: + kfree_skb(skb); + return 0; +} + +/* ----- Proc fs support ------ */ +static int sco_sock_dump(char *buf, struct bluez_sock_list *list) +{ + struct sco_pinfo *pi; + struct sock *sk; + char *ptr = buf; + + write_lock_bh(&list->lock); + + for (sk = list->head; sk; sk = sk->next) { + pi = sco_pi(sk); + ptr += sprintf(ptr, "%s %s %d\n", + batostr(&bluez_pi(sk)->src), batostr(&bluez_pi(sk)->dst), + sk->state); + } + + write_unlock_bh(&list->lock); + + ptr += sprintf(ptr, "\n"); + + return ptr - buf; +} + +static int sco_read_proc(char *buf, char **start, off_t offset, int count, int *eof, void *priv) +{ + char *ptr = buf; + int len; + + BT_DBG("count %d, offset %ld", count, offset); + + ptr += sco_sock_dump(ptr, &sco_sk_list); + len = ptr - buf; + + if (len <= count + offset) + *eof = 1; + + *start = buf + offset; + len -= offset; + + if (len > count) + len = count; + if (len < 0) + len = 0; + + return len; +} + +static struct proto_ops sco_sock_ops = { + family: PF_BLUETOOTH, + release: sco_sock_release, + bind: sco_sock_bind, + connect: sco_sock_connect, + listen: sco_sock_listen, + accept: sco_sock_accept, + getname: sco_sock_getname, + sendmsg: sco_sock_sendmsg, + recvmsg: bluez_sock_recvmsg, + poll: bluez_sock_poll, + socketpair: sock_no_socketpair, + ioctl: sock_no_ioctl, + shutdown: sock_no_shutdown, + setsockopt: sco_sock_setsockopt, + getsockopt: sco_sock_getsockopt, + mmap: sock_no_mmap +}; + +static struct net_proto_family sco_sock_family_ops = { + family: PF_BLUETOOTH, + create: sco_sock_create +}; + +static struct hci_proto sco_hci_proto = { + name: "SCO", + id: HCI_PROTO_SCO, + connect_ind: sco_connect_ind, + connect_cfm: sco_connect_cfm, + disconn_ind: sco_disconn_ind, + recv_scodata: sco_recv_scodata, +}; + +int __init sco_init(void) +{ + int err; + + if ((err = bluez_sock_register(BTPROTO_SCO, &sco_sock_family_ops))) { + BT_ERR("Can't register SCO socket layer"); + return err; + } + + if ((err = hci_register_proto(&sco_hci_proto))) { + BT_ERR("Can't register SCO protocol"); + return err; + } + + create_proc_read_entry("bluetooth/sco", 0, 0, sco_read_proc, NULL); + + BT_INFO("BlueZ SCO ver %s Copyright (C) 2000,2001 Qualcomm Inc", VERSION); + BT_INFO("Written 2000,2001 by Maxim Krasnyansky "); + return 0; +} + +void sco_cleanup(void) +{ + int err; + + remove_proc_entry("bluetooth/sco", NULL); + + /* Unregister socket, protocol and notifier */ + if ((err = bluez_sock_unregister(BTPROTO_SCO))) + BT_ERR("Can't unregister SCO socket layer %d", err); + + if ((err = hci_unregister_proto(&sco_hci_proto))) + BT_ERR("Can't unregister SCO protocol %d", err); +} + +module_init(sco_init); +module_exit(sco_cleanup); + +MODULE_AUTHOR("Maxim Krasnyansky "); +MODULE_DESCRIPTION("BlueZ SCO ver " VERSION); +MODULE_LICENSE("GPL"); --- linux/net/bluetooth/syms.c~bluetooth-2.4.18-mh11 2001-09-07 18:28:38.000000000 +0200 +++ linux/net/bluetooth/syms.c 2004-01-25 23:37:39.000000000 +0100 @@ -25,7 +25,7 @@ /* * BlueZ symbols. * - * $Id: syms.c,v 1.1 2001/07/12 19:31:24 maxk Exp $ + * $Id: syms.c,v 1.1 2002/03/08 21:06:59 maxk Exp $ */ #include @@ -39,25 +39,28 @@ #include #include -#include #include /* HCI Core */ EXPORT_SYMBOL(hci_register_dev); EXPORT_SYMBOL(hci_unregister_dev); +EXPORT_SYMBOL(hci_suspend_dev); +EXPORT_SYMBOL(hci_resume_dev); + EXPORT_SYMBOL(hci_register_proto); EXPORT_SYMBOL(hci_unregister_proto); -EXPORT_SYMBOL(hci_register_notifier); -EXPORT_SYMBOL(hci_unregister_notifier); +EXPORT_SYMBOL(hci_get_route); EXPORT_SYMBOL(hci_connect); -EXPORT_SYMBOL(hci_disconnect); EXPORT_SYMBOL(hci_dev_get); +EXPORT_SYMBOL(hci_conn_auth); +EXPORT_SYMBOL(hci_conn_encrypt); EXPORT_SYMBOL(hci_recv_frame); EXPORT_SYMBOL(hci_send_acl); EXPORT_SYMBOL(hci_send_sco); -EXPORT_SYMBOL(hci_send_raw); +EXPORT_SYMBOL(hci_send_cmd); +EXPORT_SYMBOL(hci_si_event); /* BlueZ lib */ EXPORT_SYMBOL(bluez_dump); @@ -68,5 +71,11 @@ /* BlueZ sockets */ EXPORT_SYMBOL(bluez_sock_register); EXPORT_SYMBOL(bluez_sock_unregister); +EXPORT_SYMBOL(bluez_sock_init); EXPORT_SYMBOL(bluez_sock_link); EXPORT_SYMBOL(bluez_sock_unlink); +EXPORT_SYMBOL(bluez_sock_recvmsg); +EXPORT_SYMBOL(bluez_sock_poll); +EXPORT_SYMBOL(bluez_accept_enqueue); +EXPORT_SYMBOL(bluez_accept_dequeue); +EXPORT_SYMBOL(bluez_sock_wait_state);