/*
|
/*
|
BlueZ - Bluetooth protocol stack for Linux
|
BlueZ - Bluetooth protocol stack for Linux
|
Copyright (C) 2000-2001 Qualcomm Incorporated
|
Copyright (C) 2000-2001 Qualcomm Incorporated
|
|
|
Written 2000,2001 by Maxim Krasnyansky <maxk@qualcomm.com>
|
Written 2000,2001 by Maxim Krasnyansky <maxk@qualcomm.com>
|
|
|
This program is free software; you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License version 2 as
|
published by the Free Software Foundation;
|
published by the Free Software Foundation;
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
|
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
|
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
|
CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS,
|
ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS,
|
COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS
|
COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS
|
SOFTWARE IS DISCLAIMED.
|
SOFTWARE IS DISCLAIMED.
|
*/
|
*/
|
|
|
/*
|
/*
|
* BlueZ HCI Core.
|
* BlueZ HCI Core.
|
*
|
*
|
* $Id: hci_core.c,v 1.1.1.1 2004-04-15 01:17:05 phoenix Exp $
|
* $Id: hci_core.c,v 1.1.1.1 2004-04-15 01:17:05 phoenix Exp $
|
*/
|
*/
|
|
|
#include <linux/config.h>
|
#include <linux/config.h>
|
#include <linux/module.h>
|
#include <linux/module.h>
|
#include <linux/kmod.h>
|
#include <linux/kmod.h>
|
|
|
#include <linux/types.h>
|
#include <linux/types.h>
|
#include <linux/errno.h>
|
#include <linux/errno.h>
|
#include <linux/kernel.h>
|
#include <linux/kernel.h>
|
#include <linux/major.h>
|
#include <linux/major.h>
|
#include <linux/sched.h>
|
#include <linux/sched.h>
|
#include <linux/slab.h>
|
#include <linux/slab.h>
|
#include <linux/poll.h>
|
#include <linux/poll.h>
|
#include <linux/fcntl.h>
|
#include <linux/fcntl.h>
|
#include <linux/init.h>
|
#include <linux/init.h>
|
#include <linux/skbuff.h>
|
#include <linux/skbuff.h>
|
#include <linux/interrupt.h>
|
#include <linux/interrupt.h>
|
#include <linux/notifier.h>
|
#include <linux/notifier.h>
|
#include <net/sock.h>
|
#include <net/sock.h>
|
|
|
#include <asm/system.h>
|
#include <asm/system.h>
|
#include <asm/uaccess.h>
|
#include <asm/uaccess.h>
|
#include <asm/unaligned.h>
|
#include <asm/unaligned.h>
|
|
|
#include <net/bluetooth/bluetooth.h>
|
#include <net/bluetooth/bluetooth.h>
|
#include <net/bluetooth/hci_core.h>
|
#include <net/bluetooth/hci_core.h>
|
|
|
#ifndef HCI_CORE_DEBUG
|
#ifndef HCI_CORE_DEBUG
|
#undef BT_DBG
|
#undef BT_DBG
|
#define BT_DBG( A... )
|
#define BT_DBG( A... )
|
#endif
|
#endif
|
|
|
static void hci_cmd_task(unsigned long arg);
|
static void hci_cmd_task(unsigned long arg);
|
static void hci_rx_task(unsigned long arg);
|
static void hci_rx_task(unsigned long arg);
|
static void hci_tx_task(unsigned long arg);
|
static void hci_tx_task(unsigned long arg);
|
static void hci_notify(struct hci_dev *hdev, int event);
|
static void hci_notify(struct hci_dev *hdev, int event);
|
|
|
rwlock_t hci_task_lock = RW_LOCK_UNLOCKED;
|
rwlock_t hci_task_lock = RW_LOCK_UNLOCKED;
|
|
|
/* HCI device list */
|
/* HCI device list */
|
LIST_HEAD(hdev_list);
|
LIST_HEAD(hdev_list);
|
rwlock_t hdev_list_lock = RW_LOCK_UNLOCKED;
|
rwlock_t hdev_list_lock = RW_LOCK_UNLOCKED;
|
|
|
/* HCI protocols */
|
/* HCI protocols */
|
#define HCI_MAX_PROTO 2
|
#define HCI_MAX_PROTO 2
|
struct hci_proto *hci_proto[HCI_MAX_PROTO];
|
struct hci_proto *hci_proto[HCI_MAX_PROTO];
|
|
|
/* HCI notifiers list */
|
/* HCI notifiers list */
|
static struct notifier_block *hci_notifier;
|
static struct notifier_block *hci_notifier;
|
|
|
|
|
/* ---- HCI notifications ---- */
|
/* ---- HCI notifications ---- */
|
|
|
int hci_register_notifier(struct notifier_block *nb)
|
int hci_register_notifier(struct notifier_block *nb)
|
{
|
{
|
return notifier_chain_register(&hci_notifier, nb);
|
return notifier_chain_register(&hci_notifier, nb);
|
}
|
}
|
|
|
int hci_unregister_notifier(struct notifier_block *nb)
|
int hci_unregister_notifier(struct notifier_block *nb)
|
{
|
{
|
return notifier_chain_unregister(&hci_notifier, nb);
|
return notifier_chain_unregister(&hci_notifier, nb);
|
}
|
}
|
|
|
void hci_notify(struct hci_dev *hdev, int event)
|
void hci_notify(struct hci_dev *hdev, int event)
|
{
|
{
|
notifier_call_chain(&hci_notifier, event, hdev);
|
notifier_call_chain(&hci_notifier, event, hdev);
|
}
|
}
|
|
|
/* ---- HCI hotplug support ---- */
|
/* ---- HCI hotplug support ---- */
|
|
|
#ifdef CONFIG_HOTPLUG
|
#ifdef CONFIG_HOTPLUG
|
|
|
static int hci_run_hotplug(char *dev, char *action)
|
static int hci_run_hotplug(char *dev, char *action)
|
{
|
{
|
char *argv[3], *envp[5], dstr[20], astr[32];
|
char *argv[3], *envp[5], dstr[20], astr[32];
|
|
|
sprintf(dstr, "DEVICE=%s", dev);
|
sprintf(dstr, "DEVICE=%s", dev);
|
sprintf(astr, "ACTION=%s", action);
|
sprintf(astr, "ACTION=%s", action);
|
|
|
argv[0] = hotplug_path;
|
argv[0] = hotplug_path;
|
argv[1] = "bluetooth";
|
argv[1] = "bluetooth";
|
argv[2] = NULL;
|
argv[2] = NULL;
|
|
|
envp[0] = "HOME=/";
|
envp[0] = "HOME=/";
|
envp[1] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin";
|
envp[1] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin";
|
envp[2] = dstr;
|
envp[2] = dstr;
|
envp[3] = astr;
|
envp[3] = astr;
|
envp[4] = NULL;
|
envp[4] = NULL;
|
|
|
return call_usermodehelper(argv[0], argv, envp);
|
return call_usermodehelper(argv[0], argv, envp);
|
}
|
}
|
#else
|
#else
|
#define hci_run_hotplug(A...)
|
#define hci_run_hotplug(A...)
|
#endif
|
#endif
|
|
|
/* ---- HCI requests ---- */
|
/* ---- HCI requests ---- */
|
|
|
void hci_req_complete(struct hci_dev *hdev, int result)
|
void hci_req_complete(struct hci_dev *hdev, int result)
|
{
|
{
|
BT_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) {
|
if (hdev->req_status == HCI_REQ_PEND) {
|
hdev->req_result = result;
|
hdev->req_result = result;
|
hdev->req_status = HCI_REQ_DONE;
|
hdev->req_status = HCI_REQ_DONE;
|
wake_up_interruptible(&hdev->req_wait_q);
|
wake_up_interruptible(&hdev->req_wait_q);
|
}
|
}
|
}
|
}
|
|
|
void hci_req_cancel(struct hci_dev *hdev, int err)
|
void hci_req_cancel(struct hci_dev *hdev, int err)
|
{
|
{
|
BT_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) {
|
if (hdev->req_status == HCI_REQ_PEND) {
|
hdev->req_result = err;
|
hdev->req_result = err;
|
hdev->req_status = HCI_REQ_CANCELED;
|
hdev->req_status = HCI_REQ_CANCELED;
|
wake_up_interruptible(&hdev->req_wait_q);
|
wake_up_interruptible(&hdev->req_wait_q);
|
}
|
}
|
}
|
}
|
|
|
/* Execute request and wait for completion. */
|
/* 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);
|
DECLARE_WAITQUEUE(wait, current);
|
int err = 0;
|
int err = 0;
|
|
|
BT_DBG("%s start", hdev->name);
|
BT_DBG("%s start", hdev->name);
|
|
|
hdev->req_status = HCI_REQ_PEND;
|
hdev->req_status = HCI_REQ_PEND;
|
|
|
add_wait_queue(&hdev->req_wait_q, &wait);
|
add_wait_queue(&hdev->req_wait_q, &wait);
|
set_current_state(TASK_INTERRUPTIBLE);
|
set_current_state(TASK_INTERRUPTIBLE);
|
|
|
req(hdev, opt);
|
req(hdev, opt);
|
schedule_timeout(timeout);
|
schedule_timeout(timeout);
|
|
|
set_current_state(TASK_RUNNING);
|
set_current_state(TASK_RUNNING);
|
remove_wait_queue(&hdev->req_wait_q, &wait);
|
remove_wait_queue(&hdev->req_wait_q, &wait);
|
|
|
if (signal_pending(current))
|
if (signal_pending(current))
|
return -EINTR;
|
return -EINTR;
|
|
|
switch (hdev->req_status) {
|
switch (hdev->req_status) {
|
case HCI_REQ_DONE:
|
case HCI_REQ_DONE:
|
err = -bterr(hdev->req_result);
|
err = -bterr(hdev->req_result);
|
break;
|
break;
|
|
|
case HCI_REQ_CANCELED:
|
case HCI_REQ_CANCELED:
|
err = -hdev->req_result;
|
err = -hdev->req_result;
|
break;
|
break;
|
|
|
default:
|
default:
|
err = -ETIMEDOUT;
|
err = -ETIMEDOUT;
|
break;
|
break;
|
};
|
};
|
|
|
hdev->req_status = hdev->req_result = 0;
|
hdev->req_status = hdev->req_result = 0;
|
|
|
BT_DBG("%s end: err %d", hdev->name, err);
|
BT_DBG("%s end: err %d", hdev->name, err);
|
|
|
return err;
|
return err;
|
}
|
}
|
|
|
static inline int hci_request(struct hci_dev *hdev, void (*req)(struct hci_dev *hdev, unsigned long opt),
|
static inline int hci_request(struct hci_dev *hdev, void (*req)(struct hci_dev *hdev, unsigned long opt),
|
unsigned long opt, __u32 timeout)
|
unsigned long opt, __u32 timeout)
|
{
|
{
|
int ret;
|
int ret;
|
|
|
/* Serialize all requests */
|
/* Serialize all requests */
|
hci_req_lock(hdev);
|
hci_req_lock(hdev);
|
ret = __hci_request(hdev, req, opt, timeout);
|
ret = __hci_request(hdev, req, opt, timeout);
|
hci_req_unlock(hdev);
|
hci_req_unlock(hdev);
|
|
|
return ret;
|
return ret;
|
}
|
}
|
|
|
static void hci_reset_req(struct hci_dev *hdev, unsigned long opt)
|
static void hci_reset_req(struct hci_dev *hdev, unsigned long opt)
|
{
|
{
|
BT_DBG("%s %ld", hdev->name, opt);
|
BT_DBG("%s %ld", hdev->name, opt);
|
|
|
/* Reset device */
|
/* Reset device */
|
hci_send_cmd(hdev, OGF_HOST_CTL, OCF_RESET, 0, NULL);
|
hci_send_cmd(hdev, OGF_HOST_CTL, OCF_RESET, 0, NULL);
|
}
|
}
|
|
|
static void hci_init_req(struct hci_dev *hdev, unsigned long opt)
|
static void hci_init_req(struct hci_dev *hdev, unsigned long opt)
|
{
|
{
|
set_event_flt_cp ef;
|
set_event_flt_cp ef;
|
__u16 param;
|
__u16 param;
|
|
|
BT_DBG("%s %ld", hdev->name, opt);
|
BT_DBG("%s %ld", hdev->name, opt);
|
|
|
/* Mandatory initialization */
|
/* Mandatory initialization */
|
|
|
/* Reset */
|
/* Reset */
|
if (test_bit(HCI_QUIRK_RESET_ON_INIT, &hdev->quirks))
|
if (test_bit(HCI_QUIRK_RESET_ON_INIT, &hdev->quirks))
|
hci_send_cmd(hdev, OGF_HOST_CTL, OCF_RESET, 0, NULL);
|
hci_send_cmd(hdev, OGF_HOST_CTL, OCF_RESET, 0, NULL);
|
|
|
/* Read Local Supported Features */
|
/* Read Local Supported Features */
|
hci_send_cmd(hdev, OGF_INFO_PARAM, OCF_READ_LOCAL_FEATURES, 0, NULL);
|
hci_send_cmd(hdev, OGF_INFO_PARAM, OCF_READ_LOCAL_FEATURES, 0, NULL);
|
|
|
/* Read Buffer Size (ACL mtu, max pkt, etc.) */
|
/* Read Buffer Size (ACL mtu, max pkt, etc.) */
|
hci_send_cmd(hdev, OGF_INFO_PARAM, OCF_READ_BUFFER_SIZE, 0, NULL);
|
hci_send_cmd(hdev, OGF_INFO_PARAM, OCF_READ_BUFFER_SIZE, 0, NULL);
|
|
|
#if 0
|
#if 0
|
/* Host buffer size */
|
/* Host buffer size */
|
{
|
{
|
host_buffer_size_cp bs;
|
host_buffer_size_cp bs;
|
bs.acl_mtu = __cpu_to_le16(HCI_MAX_ACL_SIZE);
|
bs.acl_mtu = __cpu_to_le16(HCI_MAX_ACL_SIZE);
|
bs.sco_mtu = HCI_MAX_SCO_SIZE;
|
bs.sco_mtu = HCI_MAX_SCO_SIZE;
|
bs.acl_max_pkt = __cpu_to_le16(0xffff);
|
bs.acl_max_pkt = __cpu_to_le16(0xffff);
|
bs.sco_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,
|
hci_send_cmd(hdev, OGF_HOST_CTL, OCF_HOST_BUFFER_SIZE,
|
HOST_BUFFER_SIZE_CP_SIZE, &bs);
|
HOST_BUFFER_SIZE_CP_SIZE, &bs);
|
}
|
}
|
#endif
|
#endif
|
|
|
/* Read BD Address */
|
/* Read BD Address */
|
hci_send_cmd(hdev, OGF_INFO_PARAM, OCF_READ_BD_ADDR, 0, NULL);
|
hci_send_cmd(hdev, OGF_INFO_PARAM, OCF_READ_BD_ADDR, 0, NULL);
|
|
|
/* Optional initialization */
|
/* Optional initialization */
|
|
|
/* Clear Event Filters */
|
/* Clear Event Filters */
|
ef.flt_type = FLT_CLEAR_ALL;
|
ef.flt_type = FLT_CLEAR_ALL;
|
hci_send_cmd(hdev, OGF_HOST_CTL, OCF_SET_EVENT_FLT, 1, &ef);
|
hci_send_cmd(hdev, OGF_HOST_CTL, OCF_SET_EVENT_FLT, 1, &ef);
|
|
|
/* Page timeout ~20 secs */
|
/* Page timeout ~20 secs */
|
param = __cpu_to_le16(0x8000);
|
param = __cpu_to_le16(0x8000);
|
hci_send_cmd(hdev, OGF_HOST_CTL, OCF_WRITE_PG_TIMEOUT, 2, ¶m);
|
hci_send_cmd(hdev, OGF_HOST_CTL, OCF_WRITE_PG_TIMEOUT, 2, ¶m);
|
|
|
/* Connection accept timeout ~20 secs */
|
/* Connection accept timeout ~20 secs */
|
param = __cpu_to_le16(0x7d00);
|
param = __cpu_to_le16(0x7d00);
|
hci_send_cmd(hdev, OGF_HOST_CTL, OCF_WRITE_CA_TIMEOUT, 2, ¶m);
|
hci_send_cmd(hdev, OGF_HOST_CTL, OCF_WRITE_CA_TIMEOUT, 2, ¶m);
|
}
|
}
|
|
|
static void hci_scan_req(struct hci_dev *hdev, unsigned long opt)
|
static void hci_scan_req(struct hci_dev *hdev, unsigned long opt)
|
{
|
{
|
__u8 scan = opt;
|
__u8 scan = opt;
|
|
|
BT_DBG("%s %x", hdev->name, scan);
|
BT_DBG("%s %x", hdev->name, scan);
|
|
|
/* Inquiry and Page scans */
|
/* Inquiry and Page scans */
|
hci_send_cmd(hdev, OGF_HOST_CTL, OCF_WRITE_SCAN_ENABLE, 1, &scan);
|
hci_send_cmd(hdev, OGF_HOST_CTL, OCF_WRITE_SCAN_ENABLE, 1, &scan);
|
}
|
}
|
|
|
static void hci_auth_req(struct hci_dev *hdev, unsigned long opt)
|
static void hci_auth_req(struct hci_dev *hdev, unsigned long opt)
|
{
|
{
|
__u8 auth = opt;
|
__u8 auth = opt;
|
|
|
BT_DBG("%s %x", hdev->name, auth);
|
BT_DBG("%s %x", hdev->name, auth);
|
|
|
/* Authentication */
|
/* Authentication */
|
hci_send_cmd(hdev, OGF_HOST_CTL, OCF_WRITE_AUTH_ENABLE, 1, &auth);
|
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)
|
static void hci_encrypt_req(struct hci_dev *hdev, unsigned long opt)
|
{
|
{
|
__u8 encrypt = opt;
|
__u8 encrypt = opt;
|
|
|
BT_DBG("%s %x", hdev->name, encrypt);
|
BT_DBG("%s %x", hdev->name, encrypt);
|
|
|
/* Authentication */
|
/* Authentication */
|
hci_send_cmd(hdev, OGF_HOST_CTL, OCF_WRITE_ENCRYPT_MODE, 1, &encrypt);
|
hci_send_cmd(hdev, OGF_HOST_CTL, OCF_WRITE_ENCRYPT_MODE, 1, &encrypt);
|
}
|
}
|
|
|
/* Get HCI device by index.
|
/* Get HCI device by index.
|
* Device is locked on return. */
|
* Device is locked on return. */
|
struct hci_dev *hci_dev_get(int index)
|
struct hci_dev *hci_dev_get(int index)
|
{
|
{
|
struct hci_dev *hdev;
|
struct hci_dev *hdev;
|
struct list_head *p;
|
struct list_head *p;
|
|
|
BT_DBG("%d", index);
|
BT_DBG("%d", index);
|
|
|
if (index < 0)
|
if (index < 0)
|
return NULL;
|
return NULL;
|
|
|
read_lock(&hdev_list_lock);
|
read_lock(&hdev_list_lock);
|
list_for_each(p, &hdev_list) {
|
list_for_each(p, &hdev_list) {
|
hdev = list_entry(p, struct hci_dev, list);
|
hdev = list_entry(p, struct hci_dev, list);
|
if (hdev->id == index) {
|
if (hdev->id == index) {
|
hci_dev_hold(hdev);
|
hci_dev_hold(hdev);
|
goto done;
|
goto done;
|
}
|
}
|
}
|
}
|
hdev = NULL;
|
hdev = NULL;
|
done:
|
done:
|
read_unlock(&hdev_list_lock);
|
read_unlock(&hdev_list_lock);
|
return hdev;
|
return hdev;
|
}
|
}
|
|
|
/* ---- Inquiry support ---- */
|
/* ---- Inquiry support ---- */
|
void inquiry_cache_flush(struct hci_dev *hdev)
|
void inquiry_cache_flush(struct hci_dev *hdev)
|
{
|
{
|
struct inquiry_cache *cache = &hdev->inq_cache;
|
struct inquiry_cache *cache = &hdev->inq_cache;
|
struct inquiry_entry *next = cache->list, *e;
|
struct inquiry_entry *next = cache->list, *e;
|
|
|
BT_DBG("cache %p", cache);
|
BT_DBG("cache %p", cache);
|
|
|
cache->list = NULL;
|
cache->list = NULL;
|
while ((e = next)) {
|
while ((e = next)) {
|
next = e->next;
|
next = e->next;
|
kfree(e);
|
kfree(e);
|
}
|
}
|
}
|
}
|
|
|
struct inquiry_entry *inquiry_cache_lookup(struct hci_dev *hdev, bdaddr_t *bdaddr)
|
struct inquiry_entry *inquiry_cache_lookup(struct hci_dev *hdev, bdaddr_t *bdaddr)
|
{
|
{
|
struct inquiry_cache *cache = &hdev->inq_cache;
|
struct inquiry_cache *cache = &hdev->inq_cache;
|
struct inquiry_entry *e;
|
struct inquiry_entry *e;
|
|
|
BT_DBG("cache %p, %s", cache, batostr(bdaddr));
|
BT_DBG("cache %p, %s", cache, batostr(bdaddr));
|
|
|
for (e = cache->list; e; e = e->next)
|
for (e = cache->list; e; e = e->next)
|
if (!bacmp(&e->info.bdaddr, bdaddr))
|
if (!bacmp(&e->info.bdaddr, bdaddr))
|
break;
|
break;
|
return e;
|
return e;
|
}
|
}
|
|
|
void inquiry_cache_update(struct hci_dev *hdev, inquiry_info *info)
|
void inquiry_cache_update(struct hci_dev *hdev, inquiry_info *info)
|
{
|
{
|
struct inquiry_cache *cache = &hdev->inq_cache;
|
struct inquiry_cache *cache = &hdev->inq_cache;
|
struct inquiry_entry *e;
|
struct inquiry_entry *e;
|
|
|
BT_DBG("cache %p, %s", cache, batostr(&info->bdaddr));
|
BT_DBG("cache %p, %s", cache, batostr(&info->bdaddr));
|
|
|
if (!(e = inquiry_cache_lookup(hdev, &info->bdaddr))) {
|
if (!(e = inquiry_cache_lookup(hdev, &info->bdaddr))) {
|
/* Entry not in the cache. Add new one. */
|
/* Entry not in the cache. Add new one. */
|
if (!(e = kmalloc(sizeof(struct inquiry_entry), GFP_ATOMIC)))
|
if (!(e = kmalloc(sizeof(struct inquiry_entry), GFP_ATOMIC)))
|
return;
|
return;
|
memset(e, 0, sizeof(struct inquiry_entry));
|
memset(e, 0, sizeof(struct inquiry_entry));
|
e->next = cache->list;
|
e->next = cache->list;
|
cache->list = e;
|
cache->list = e;
|
}
|
}
|
|
|
memcpy(&e->info, info, sizeof(inquiry_info));
|
memcpy(&e->info, info, sizeof(inquiry_info));
|
e->timestamp = jiffies;
|
e->timestamp = jiffies;
|
cache->timestamp = jiffies;
|
cache->timestamp = jiffies;
|
}
|
}
|
|
|
int inquiry_cache_dump(struct hci_dev *hdev, int num, __u8 *buf)
|
int inquiry_cache_dump(struct hci_dev *hdev, int num, __u8 *buf)
|
{
|
{
|
struct inquiry_cache *cache = &hdev->inq_cache;
|
struct inquiry_cache *cache = &hdev->inq_cache;
|
inquiry_info *info = (inquiry_info *) buf;
|
inquiry_info *info = (inquiry_info *) buf;
|
struct inquiry_entry *e;
|
struct inquiry_entry *e;
|
int copied = 0;
|
int copied = 0;
|
|
|
for (e = cache->list; e && copied < num; e = e->next, copied++)
|
for (e = cache->list; e && copied < num; e = e->next, copied++)
|
memcpy(info++, &e->info, sizeof(inquiry_info));
|
memcpy(info++, &e->info, sizeof(inquiry_info));
|
|
|
BT_DBG("cache %p, copied %d", cache, copied);
|
BT_DBG("cache %p, copied %d", cache, copied);
|
return copied;
|
return copied;
|
}
|
}
|
|
|
static void hci_inq_req(struct hci_dev *hdev, unsigned long opt)
|
static void hci_inq_req(struct hci_dev *hdev, unsigned long opt)
|
{
|
{
|
struct hci_inquiry_req *ir = (struct hci_inquiry_req *) opt;
|
struct hci_inquiry_req *ir = (struct hci_inquiry_req *) opt;
|
inquiry_cp ic;
|
inquiry_cp ic;
|
|
|
BT_DBG("%s", hdev->name);
|
BT_DBG("%s", hdev->name);
|
|
|
if (test_bit(HCI_INQUIRY, &hdev->flags))
|
if (test_bit(HCI_INQUIRY, &hdev->flags))
|
return;
|
return;
|
|
|
/* Start Inquiry */
|
/* Start Inquiry */
|
memcpy(&ic.lap, &ir->lap, 3);
|
memcpy(&ic.lap, &ir->lap, 3);
|
ic.length = ir->length;
|
ic.length = ir->length;
|
ic.num_rsp = ir->num_rsp;
|
ic.num_rsp = ir->num_rsp;
|
hci_send_cmd(hdev, OGF_LINK_CTL, OCF_INQUIRY, INQUIRY_CP_SIZE, &ic);
|
hci_send_cmd(hdev, OGF_LINK_CTL, OCF_INQUIRY, INQUIRY_CP_SIZE, &ic);
|
}
|
}
|
|
|
int hci_inquiry(unsigned long arg)
|
int hci_inquiry(unsigned long arg)
|
{
|
{
|
struct hci_inquiry_req ir;
|
struct hci_inquiry_req ir;
|
struct hci_dev *hdev;
|
struct hci_dev *hdev;
|
int err = 0, do_inquiry = 0, max_rsp;
|
int err = 0, do_inquiry = 0, max_rsp;
|
long timeo;
|
long timeo;
|
__u8 *buf, *ptr;
|
__u8 *buf, *ptr;
|
|
|
ptr = (void *) arg;
|
ptr = (void *) arg;
|
if (copy_from_user(&ir, ptr, sizeof(ir)))
|
if (copy_from_user(&ir, ptr, sizeof(ir)))
|
return -EFAULT;
|
return -EFAULT;
|
|
|
if (!(hdev = hci_dev_get(ir.dev_id)))
|
if (!(hdev = hci_dev_get(ir.dev_id)))
|
return -ENODEV;
|
return -ENODEV;
|
|
|
hci_dev_lock_bh(hdev);
|
hci_dev_lock_bh(hdev);
|
if (inquiry_cache_age(hdev) > INQUIRY_CACHE_AGE_MAX ||
|
if (inquiry_cache_age(hdev) > INQUIRY_CACHE_AGE_MAX ||
|
inquiry_cache_empty(hdev) ||
|
inquiry_cache_empty(hdev) ||
|
ir.flags & IREQ_CACHE_FLUSH) {
|
ir.flags & IREQ_CACHE_FLUSH) {
|
inquiry_cache_flush(hdev);
|
inquiry_cache_flush(hdev);
|
do_inquiry = 1;
|
do_inquiry = 1;
|
}
|
}
|
hci_dev_unlock_bh(hdev);
|
hci_dev_unlock_bh(hdev);
|
|
|
timeo = ir.length * 2 * HZ;
|
timeo = ir.length * 2 * HZ;
|
if (do_inquiry && (err = hci_request(hdev, hci_inq_req, (unsigned long)&ir, timeo)) < 0)
|
if (do_inquiry && (err = hci_request(hdev, hci_inq_req, (unsigned long)&ir, timeo)) < 0)
|
goto done;
|
goto done;
|
|
|
/* for unlimited number of responses we will use buffer with 255 entries */
|
/* for unlimited number of responses we will use buffer with 255 entries */
|
max_rsp = (ir.num_rsp == 0) ? 255 : ir.num_rsp;
|
max_rsp = (ir.num_rsp == 0) ? 255 : ir.num_rsp;
|
|
|
/* cache_dump can't sleep. Therefore we allocate temp buffer and then
|
/* cache_dump can't sleep. Therefore we allocate temp buffer and then
|
* copy it to the user space.
|
* copy it to the user space.
|
*/
|
*/
|
if (!(buf = kmalloc(sizeof(inquiry_info) * max_rsp, GFP_KERNEL))) {
|
if (!(buf = kmalloc(sizeof(inquiry_info) * max_rsp, GFP_KERNEL))) {
|
err = -ENOMEM;
|
err = -ENOMEM;
|
goto done;
|
goto done;
|
}
|
}
|
|
|
hci_dev_lock_bh(hdev);
|
hci_dev_lock_bh(hdev);
|
ir.num_rsp = inquiry_cache_dump(hdev, max_rsp, buf);
|
ir.num_rsp = inquiry_cache_dump(hdev, max_rsp, buf);
|
hci_dev_unlock_bh(hdev);
|
hci_dev_unlock_bh(hdev);
|
|
|
BT_DBG("num_rsp %d", ir.num_rsp);
|
BT_DBG("num_rsp %d", ir.num_rsp);
|
|
|
if (!verify_area(VERIFY_WRITE, ptr, sizeof(ir) +
|
if (!verify_area(VERIFY_WRITE, ptr, sizeof(ir) +
|
(sizeof(inquiry_info) * ir.num_rsp))) {
|
(sizeof(inquiry_info) * ir.num_rsp))) {
|
copy_to_user(ptr, &ir, sizeof(ir));
|
copy_to_user(ptr, &ir, sizeof(ir));
|
ptr += sizeof(ir);
|
ptr += sizeof(ir);
|
copy_to_user(ptr, buf, sizeof(inquiry_info) * ir.num_rsp);
|
copy_to_user(ptr, buf, sizeof(inquiry_info) * ir.num_rsp);
|
} else
|
} else
|
err = -EFAULT;
|
err = -EFAULT;
|
|
|
kfree(buf);
|
kfree(buf);
|
|
|
done:
|
done:
|
hci_dev_put(hdev);
|
hci_dev_put(hdev);
|
return err;
|
return err;
|
}
|
}
|
|
|
/* ---- HCI ioctl helpers ---- */
|
/* ---- HCI ioctl helpers ---- */
|
|
|
int hci_dev_open(__u16 dev)
|
int hci_dev_open(__u16 dev)
|
{
|
{
|
struct hci_dev *hdev;
|
struct hci_dev *hdev;
|
int ret = 0;
|
int ret = 0;
|
|
|
if (!(hdev = hci_dev_get(dev)))
|
if (!(hdev = hci_dev_get(dev)))
|
return -ENODEV;
|
return -ENODEV;
|
|
|
BT_DBG("%s %p", hdev->name, hdev);
|
BT_DBG("%s %p", hdev->name, hdev);
|
|
|
hci_req_lock(hdev);
|
hci_req_lock(hdev);
|
|
|
if (test_bit(HCI_UP, &hdev->flags)) {
|
if (test_bit(HCI_UP, &hdev->flags)) {
|
ret = -EALREADY;
|
ret = -EALREADY;
|
goto done;
|
goto done;
|
}
|
}
|
|
|
if (hdev->open(hdev)) {
|
if (hdev->open(hdev)) {
|
ret = -EIO;
|
ret = -EIO;
|
goto done;
|
goto done;
|
}
|
}
|
|
|
if (!test_bit(HCI_RAW, &hdev->flags)) {
|
if (!test_bit(HCI_RAW, &hdev->flags)) {
|
atomic_set(&hdev->cmd_cnt, 1);
|
atomic_set(&hdev->cmd_cnt, 1);
|
set_bit(HCI_INIT, &hdev->flags);
|
set_bit(HCI_INIT, &hdev->flags);
|
|
|
//__hci_request(hdev, hci_reset_req, 0, HZ);
|
//__hci_request(hdev, hci_reset_req, 0, HZ);
|
ret = __hci_request(hdev, hci_init_req, 0, HCI_INIT_TIMEOUT);
|
ret = __hci_request(hdev, hci_init_req, 0, HCI_INIT_TIMEOUT);
|
|
|
clear_bit(HCI_INIT, &hdev->flags);
|
clear_bit(HCI_INIT, &hdev->flags);
|
}
|
}
|
|
|
if (!ret) {
|
if (!ret) {
|
set_bit(HCI_UP, &hdev->flags);
|
set_bit(HCI_UP, &hdev->flags);
|
hci_notify(hdev, HCI_DEV_UP);
|
hci_notify(hdev, HCI_DEV_UP);
|
} else {
|
} else {
|
/* Init failed, cleanup */
|
/* Init failed, cleanup */
|
tasklet_kill(&hdev->rx_task);
|
tasklet_kill(&hdev->rx_task);
|
tasklet_kill(&hdev->tx_task);
|
tasklet_kill(&hdev->tx_task);
|
tasklet_kill(&hdev->cmd_task);
|
tasklet_kill(&hdev->cmd_task);
|
|
|
skb_queue_purge(&hdev->cmd_q);
|
skb_queue_purge(&hdev->cmd_q);
|
skb_queue_purge(&hdev->rx_q);
|
skb_queue_purge(&hdev->rx_q);
|
|
|
if (hdev->flush)
|
if (hdev->flush)
|
hdev->flush(hdev);
|
hdev->flush(hdev);
|
|
|
if (hdev->sent_cmd) {
|
if (hdev->sent_cmd) {
|
kfree_skb(hdev->sent_cmd);
|
kfree_skb(hdev->sent_cmd);
|
hdev->sent_cmd = NULL;
|
hdev->sent_cmd = NULL;
|
}
|
}
|
|
|
hdev->close(hdev);
|
hdev->close(hdev);
|
hdev->flags = 0;
|
hdev->flags = 0;
|
}
|
}
|
|
|
done:
|
done:
|
hci_req_unlock(hdev);
|
hci_req_unlock(hdev);
|
hci_dev_put(hdev);
|
hci_dev_put(hdev);
|
return ret;
|
return ret;
|
}
|
}
|
|
|
static int hci_dev_do_close(struct hci_dev *hdev)
|
static int hci_dev_do_close(struct hci_dev *hdev)
|
{
|
{
|
BT_DBG("%s %p", hdev->name, hdev);
|
BT_DBG("%s %p", hdev->name, hdev);
|
|
|
hci_req_cancel(hdev, ENODEV);
|
hci_req_cancel(hdev, ENODEV);
|
hci_req_lock(hdev);
|
hci_req_lock(hdev);
|
|
|
if (!test_and_clear_bit(HCI_UP, &hdev->flags)) {
|
if (!test_and_clear_bit(HCI_UP, &hdev->flags)) {
|
hci_req_unlock(hdev);
|
hci_req_unlock(hdev);
|
return 0;
|
return 0;
|
}
|
}
|
|
|
/* Kill RX and TX tasks */
|
/* Kill RX and TX tasks */
|
tasklet_kill(&hdev->rx_task);
|
tasklet_kill(&hdev->rx_task);
|
tasklet_kill(&hdev->tx_task);
|
tasklet_kill(&hdev->tx_task);
|
|
|
hci_dev_lock_bh(hdev);
|
hci_dev_lock_bh(hdev);
|
inquiry_cache_flush(hdev);
|
inquiry_cache_flush(hdev);
|
hci_conn_hash_flush(hdev);
|
hci_conn_hash_flush(hdev);
|
hci_dev_unlock_bh(hdev);
|
hci_dev_unlock_bh(hdev);
|
|
|
hci_notify(hdev, HCI_DEV_DOWN);
|
hci_notify(hdev, HCI_DEV_DOWN);
|
|
|
if (hdev->flush)
|
if (hdev->flush)
|
hdev->flush(hdev);
|
hdev->flush(hdev);
|
|
|
/* Reset device */
|
/* Reset device */
|
skb_queue_purge(&hdev->cmd_q);
|
skb_queue_purge(&hdev->cmd_q);
|
atomic_set(&hdev->cmd_cnt, 1);
|
atomic_set(&hdev->cmd_cnt, 1);
|
set_bit(HCI_INIT, &hdev->flags);
|
set_bit(HCI_INIT, &hdev->flags);
|
__hci_request(hdev, hci_reset_req, 0, HZ/4);
|
__hci_request(hdev, hci_reset_req, 0, HZ/4);
|
clear_bit(HCI_INIT, &hdev->flags);
|
clear_bit(HCI_INIT, &hdev->flags);
|
|
|
/* Kill cmd task */
|
/* Kill cmd task */
|
tasklet_kill(&hdev->cmd_task);
|
tasklet_kill(&hdev->cmd_task);
|
|
|
/* Drop queues */
|
/* Drop queues */
|
skb_queue_purge(&hdev->rx_q);
|
skb_queue_purge(&hdev->rx_q);
|
skb_queue_purge(&hdev->cmd_q);
|
skb_queue_purge(&hdev->cmd_q);
|
skb_queue_purge(&hdev->raw_q);
|
skb_queue_purge(&hdev->raw_q);
|
|
|
/* Drop last sent command */
|
/* Drop last sent command */
|
if (hdev->sent_cmd) {
|
if (hdev->sent_cmd) {
|
kfree_skb(hdev->sent_cmd);
|
kfree_skb(hdev->sent_cmd);
|
hdev->sent_cmd = NULL;
|
hdev->sent_cmd = NULL;
|
}
|
}
|
|
|
/* After this point our queues are empty
|
/* After this point our queues are empty
|
* and no tasks are scheduled. */
|
* and no tasks are scheduled. */
|
hdev->close(hdev);
|
hdev->close(hdev);
|
|
|
/* Clear flags */
|
/* Clear flags */
|
hdev->flags = 0;
|
hdev->flags = 0;
|
|
|
hci_req_unlock(hdev);
|
hci_req_unlock(hdev);
|
return 0;
|
return 0;
|
}
|
}
|
|
|
int hci_dev_close(__u16 dev)
|
int hci_dev_close(__u16 dev)
|
{
|
{
|
struct hci_dev *hdev;
|
struct hci_dev *hdev;
|
int err;
|
int err;
|
|
|
if (!(hdev = hci_dev_get(dev)))
|
if (!(hdev = hci_dev_get(dev)))
|
return -ENODEV;
|
return -ENODEV;
|
err = hci_dev_do_close(hdev);
|
err = hci_dev_do_close(hdev);
|
hci_dev_put(hdev);
|
hci_dev_put(hdev);
|
return err;
|
return err;
|
}
|
}
|
|
|
int hci_dev_reset(__u16 dev)
|
int hci_dev_reset(__u16 dev)
|
{
|
{
|
struct hci_dev *hdev;
|
struct hci_dev *hdev;
|
int ret = 0;
|
int ret = 0;
|
|
|
if (!(hdev = hci_dev_get(dev)))
|
if (!(hdev = hci_dev_get(dev)))
|
return -ENODEV;
|
return -ENODEV;
|
|
|
hci_req_lock(hdev);
|
hci_req_lock(hdev);
|
tasklet_disable(&hdev->tx_task);
|
tasklet_disable(&hdev->tx_task);
|
|
|
if (!test_bit(HCI_UP, &hdev->flags))
|
if (!test_bit(HCI_UP, &hdev->flags))
|
goto done;
|
goto done;
|
|
|
/* Drop queues */
|
/* Drop queues */
|
skb_queue_purge(&hdev->rx_q);
|
skb_queue_purge(&hdev->rx_q);
|
skb_queue_purge(&hdev->cmd_q);
|
skb_queue_purge(&hdev->cmd_q);
|
|
|
hci_dev_lock_bh(hdev);
|
hci_dev_lock_bh(hdev);
|
inquiry_cache_flush(hdev);
|
inquiry_cache_flush(hdev);
|
hci_conn_hash_flush(hdev);
|
hci_conn_hash_flush(hdev);
|
hci_dev_unlock_bh(hdev);
|
hci_dev_unlock_bh(hdev);
|
|
|
if (hdev->flush)
|
if (hdev->flush)
|
hdev->flush(hdev);
|
hdev->flush(hdev);
|
|
|
atomic_set(&hdev->cmd_cnt, 1);
|
atomic_set(&hdev->cmd_cnt, 1);
|
hdev->acl_cnt = 0; hdev->sco_cnt = 0;
|
hdev->acl_cnt = 0; hdev->sco_cnt = 0;
|
|
|
ret = __hci_request(hdev, hci_reset_req, 0, HCI_INIT_TIMEOUT);
|
ret = __hci_request(hdev, hci_reset_req, 0, HCI_INIT_TIMEOUT);
|
|
|
done:
|
done:
|
tasklet_enable(&hdev->tx_task);
|
tasklet_enable(&hdev->tx_task);
|
hci_req_unlock(hdev);
|
hci_req_unlock(hdev);
|
hci_dev_put(hdev);
|
hci_dev_put(hdev);
|
return ret;
|
return ret;
|
}
|
}
|
|
|
int hci_dev_reset_stat(__u16 dev)
|
int hci_dev_reset_stat(__u16 dev)
|
{
|
{
|
struct hci_dev *hdev;
|
struct hci_dev *hdev;
|
int ret = 0;
|
int ret = 0;
|
|
|
if (!(hdev = hci_dev_get(dev)))
|
if (!(hdev = hci_dev_get(dev)))
|
return -ENODEV;
|
return -ENODEV;
|
|
|
memset(&hdev->stat, 0, sizeof(struct hci_dev_stats));
|
memset(&hdev->stat, 0, sizeof(struct hci_dev_stats));
|
|
|
hci_dev_put(hdev);
|
hci_dev_put(hdev);
|
|
|
return ret;
|
return ret;
|
}
|
}
|
|
|
int hci_dev_cmd(unsigned int cmd, unsigned long arg)
|
int hci_dev_cmd(unsigned int cmd, unsigned long arg)
|
{
|
{
|
struct hci_dev *hdev;
|
struct hci_dev *hdev;
|
struct hci_dev_req dr;
|
struct hci_dev_req dr;
|
int err = 0;
|
int err = 0;
|
|
|
if (copy_from_user(&dr, (void *) arg, sizeof(dr)))
|
if (copy_from_user(&dr, (void *) arg, sizeof(dr)))
|
return -EFAULT;
|
return -EFAULT;
|
|
|
if (!(hdev = hci_dev_get(dr.dev_id)))
|
if (!(hdev = hci_dev_get(dr.dev_id)))
|
return -ENODEV;
|
return -ENODEV;
|
|
|
switch (cmd) {
|
switch (cmd) {
|
case HCISETAUTH:
|
case HCISETAUTH:
|
err = hci_request(hdev, hci_auth_req, dr.dev_opt, HCI_INIT_TIMEOUT);
|
err = hci_request(hdev, hci_auth_req, dr.dev_opt, HCI_INIT_TIMEOUT);
|
break;
|
break;
|
|
|
case HCISETENCRYPT:
|
case HCISETENCRYPT:
|
if (!lmp_encrypt_capable(hdev)) {
|
if (!lmp_encrypt_capable(hdev)) {
|
err = -EOPNOTSUPP;
|
err = -EOPNOTSUPP;
|
break;
|
break;
|
}
|
}
|
|
|
if (!test_bit(HCI_AUTH, &hdev->flags)) {
|
if (!test_bit(HCI_AUTH, &hdev->flags)) {
|
/* Auth must be enabled first */
|
/* Auth must be enabled first */
|
err = hci_request(hdev, hci_auth_req,
|
err = hci_request(hdev, hci_auth_req,
|
dr.dev_opt, HCI_INIT_TIMEOUT);
|
dr.dev_opt, HCI_INIT_TIMEOUT);
|
if (err)
|
if (err)
|
break;
|
break;
|
}
|
}
|
|
|
err = hci_request(hdev, hci_encrypt_req,
|
err = hci_request(hdev, hci_encrypt_req,
|
dr.dev_opt, HCI_INIT_TIMEOUT);
|
dr.dev_opt, HCI_INIT_TIMEOUT);
|
break;
|
break;
|
|
|
case HCISETSCAN:
|
case HCISETSCAN:
|
err = hci_request(hdev, hci_scan_req, dr.dev_opt, HCI_INIT_TIMEOUT);
|
err = hci_request(hdev, hci_scan_req, dr.dev_opt, HCI_INIT_TIMEOUT);
|
break;
|
break;
|
|
|
case HCISETPTYPE:
|
case HCISETPTYPE:
|
hdev->pkt_type = (__u16) dr.dev_opt;
|
hdev->pkt_type = (__u16) dr.dev_opt;
|
break;
|
break;
|
|
|
case HCISETLINKPOL:
|
case HCISETLINKPOL:
|
hdev->link_policy = (__u16) dr.dev_opt;
|
hdev->link_policy = (__u16) dr.dev_opt;
|
break;
|
break;
|
|
|
case HCISETLINKMODE:
|
case HCISETLINKMODE:
|
hdev->link_mode = ((__u16) dr.dev_opt) & (HCI_LM_MASTER | HCI_LM_ACCEPT);
|
hdev->link_mode = ((__u16) dr.dev_opt) & (HCI_LM_MASTER | HCI_LM_ACCEPT);
|
break;
|
break;
|
|
|
case HCISETACLMTU:
|
case HCISETACLMTU:
|
hdev->acl_mtu = *((__u16 *)&dr.dev_opt + 1);
|
hdev->acl_mtu = *((__u16 *)&dr.dev_opt + 1);
|
hdev->acl_pkts = *((__u16 *)&dr.dev_opt + 0);
|
hdev->acl_pkts = *((__u16 *)&dr.dev_opt + 0);
|
break;
|
break;
|
|
|
case HCISETSCOMTU:
|
case HCISETSCOMTU:
|
hdev->sco_mtu = *((__u16 *)&dr.dev_opt + 1);
|
hdev->sco_mtu = *((__u16 *)&dr.dev_opt + 1);
|
hdev->sco_pkts = *((__u16 *)&dr.dev_opt + 0);
|
hdev->sco_pkts = *((__u16 *)&dr.dev_opt + 0);
|
break;
|
break;
|
|
|
default:
|
default:
|
err = -EINVAL;
|
err = -EINVAL;
|
break;
|
break;
|
}
|
}
|
hci_dev_put(hdev);
|
hci_dev_put(hdev);
|
return err;
|
return err;
|
}
|
}
|
|
|
int hci_get_dev_list(unsigned long arg)
|
int hci_get_dev_list(unsigned long arg)
|
{
|
{
|
struct hci_dev_list_req *dl;
|
struct hci_dev_list_req *dl;
|
struct hci_dev_req *dr;
|
struct hci_dev_req *dr;
|
struct list_head *p;
|
struct list_head *p;
|
int n = 0, size, err;
|
int n = 0, size, err;
|
__u16 dev_num;
|
__u16 dev_num;
|
|
|
if (get_user(dev_num, (__u16 *) arg))
|
if (get_user(dev_num, (__u16 *) arg))
|
return -EFAULT;
|
return -EFAULT;
|
|
|
if (!dev_num || dev_num > (PAGE_SIZE * 2) / sizeof(*dr))
|
if (!dev_num || dev_num > (PAGE_SIZE * 2) / sizeof(*dr))
|
return -EINVAL;
|
return -EINVAL;
|
|
|
size = sizeof(*dl) + dev_num * sizeof(*dr);
|
size = sizeof(*dl) + dev_num * sizeof(*dr);
|
|
|
if (!(dl = kmalloc(size, GFP_KERNEL)))
|
if (!(dl = kmalloc(size, GFP_KERNEL)))
|
return -ENOMEM;
|
return -ENOMEM;
|
|
|
dr = dl->dev_req;
|
dr = dl->dev_req;
|
|
|
read_lock_bh(&hdev_list_lock);
|
read_lock_bh(&hdev_list_lock);
|
list_for_each(p, &hdev_list) {
|
list_for_each(p, &hdev_list) {
|
struct hci_dev *hdev;
|
struct hci_dev *hdev;
|
hdev = list_entry(p, struct hci_dev, list);
|
hdev = list_entry(p, struct hci_dev, list);
|
(dr + n)->dev_id = hdev->id;
|
(dr + n)->dev_id = hdev->id;
|
(dr + n)->dev_opt = hdev->flags;
|
(dr + n)->dev_opt = hdev->flags;
|
if (++n >= dev_num)
|
if (++n >= dev_num)
|
break;
|
break;
|
}
|
}
|
read_unlock_bh(&hdev_list_lock);
|
read_unlock_bh(&hdev_list_lock);
|
|
|
dl->dev_num = n;
|
dl->dev_num = n;
|
size = sizeof(*dl) + n * sizeof(*dr);
|
size = sizeof(*dl) + n * sizeof(*dr);
|
|
|
err = copy_to_user((void *) arg, dl, size);
|
err = copy_to_user((void *) arg, dl, size);
|
kfree(dl);
|
kfree(dl);
|
|
|
return err ? -EFAULT : 0;
|
return err ? -EFAULT : 0;
|
}
|
}
|
|
|
int hci_get_dev_info(unsigned long arg)
|
int hci_get_dev_info(unsigned long arg)
|
{
|
{
|
struct hci_dev *hdev;
|
struct hci_dev *hdev;
|
struct hci_dev_info di;
|
struct hci_dev_info di;
|
int err = 0;
|
int err = 0;
|
|
|
if (copy_from_user(&di, (void *) arg, sizeof(di)))
|
if (copy_from_user(&di, (void *) arg, sizeof(di)))
|
return -EFAULT;
|
return -EFAULT;
|
|
|
if (!(hdev = hci_dev_get(di.dev_id)))
|
if (!(hdev = hci_dev_get(di.dev_id)))
|
return -ENODEV;
|
return -ENODEV;
|
|
|
strcpy(di.name, hdev->name);
|
strcpy(di.name, hdev->name);
|
di.bdaddr = hdev->bdaddr;
|
di.bdaddr = hdev->bdaddr;
|
di.type = hdev->type;
|
di.type = hdev->type;
|
di.flags = hdev->flags;
|
di.flags = hdev->flags;
|
di.pkt_type = hdev->pkt_type;
|
di.pkt_type = hdev->pkt_type;
|
di.acl_mtu = hdev->acl_mtu;
|
di.acl_mtu = hdev->acl_mtu;
|
di.acl_pkts = hdev->acl_pkts;
|
di.acl_pkts = hdev->acl_pkts;
|
di.sco_mtu = hdev->sco_mtu;
|
di.sco_mtu = hdev->sco_mtu;
|
di.sco_pkts = hdev->sco_pkts;
|
di.sco_pkts = hdev->sco_pkts;
|
di.link_policy = hdev->link_policy;
|
di.link_policy = hdev->link_policy;
|
di.link_mode = hdev->link_mode;
|
di.link_mode = hdev->link_mode;
|
|
|
memcpy(&di.stat, &hdev->stat, sizeof(di.stat));
|
memcpy(&di.stat, &hdev->stat, sizeof(di.stat));
|
memcpy(&di.features, &hdev->features, sizeof(di.features));
|
memcpy(&di.features, &hdev->features, sizeof(di.features));
|
|
|
if (copy_to_user((void *) arg, &di, sizeof(di)))
|
if (copy_to_user((void *) arg, &di, sizeof(di)))
|
err = -EFAULT;
|
err = -EFAULT;
|
|
|
hci_dev_put(hdev);
|
hci_dev_put(hdev);
|
|
|
return err;
|
return err;
|
}
|
}
|
|
|
|
|
/* ---- Interface to HCI drivers ---- */
|
/* ---- Interface to HCI drivers ---- */
|
|
|
/* Register HCI device */
|
/* Register HCI device */
|
int hci_register_dev(struct hci_dev *hdev)
|
int hci_register_dev(struct hci_dev *hdev)
|
{
|
{
|
struct list_head *head = &hdev_list, *p;
|
struct list_head *head = &hdev_list, *p;
|
int id = 0;
|
int id = 0;
|
|
|
BT_DBG("%p name %s type %d", hdev, hdev->name, hdev->type);
|
BT_DBG("%p name %s type %d", hdev, hdev->name, hdev->type);
|
|
|
if (!hdev->open || !hdev->close || !hdev->destruct)
|
if (!hdev->open || !hdev->close || !hdev->destruct)
|
return -EINVAL;
|
return -EINVAL;
|
|
|
write_lock_bh(&hdev_list_lock);
|
write_lock_bh(&hdev_list_lock);
|
|
|
/* Find first available device id */
|
/* Find first available device id */
|
list_for_each(p, &hdev_list) {
|
list_for_each(p, &hdev_list) {
|
if (list_entry(p, struct hci_dev, list)->id != id)
|
if (list_entry(p, struct hci_dev, list)->id != id)
|
break;
|
break;
|
head = p; id++;
|
head = p; id++;
|
}
|
}
|
|
|
sprintf(hdev->name, "hci%d", id);
|
sprintf(hdev->name, "hci%d", id);
|
hdev->id = id;
|
hdev->id = id;
|
list_add(&hdev->list, head);
|
list_add(&hdev->list, head);
|
|
|
atomic_set(&hdev->refcnt, 1);
|
atomic_set(&hdev->refcnt, 1);
|
spin_lock_init(&hdev->lock);
|
spin_lock_init(&hdev->lock);
|
|
|
hdev->flags = 0;
|
hdev->flags = 0;
|
hdev->pkt_type = (HCI_DM1 | HCI_DH1 | HCI_HV1);
|
hdev->pkt_type = (HCI_DM1 | HCI_DH1 | HCI_HV1);
|
hdev->link_mode = (HCI_LM_ACCEPT);
|
hdev->link_mode = (HCI_LM_ACCEPT);
|
|
|
tasklet_init(&hdev->cmd_task, hci_cmd_task,(unsigned long) hdev);
|
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->rx_task, hci_rx_task, (unsigned long) hdev);
|
tasklet_init(&hdev->tx_task, hci_tx_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->rx_q);
|
skb_queue_head_init(&hdev->cmd_q);
|
skb_queue_head_init(&hdev->cmd_q);
|
skb_queue_head_init(&hdev->raw_q);
|
skb_queue_head_init(&hdev->raw_q);
|
|
|
init_waitqueue_head(&hdev->req_wait_q);
|
init_waitqueue_head(&hdev->req_wait_q);
|
init_MUTEX(&hdev->req_lock);
|
init_MUTEX(&hdev->req_lock);
|
|
|
inquiry_cache_init(hdev);
|
inquiry_cache_init(hdev);
|
|
|
conn_hash_init(hdev);
|
conn_hash_init(hdev);
|
|
|
memset(&hdev->stat, 0, sizeof(struct hci_dev_stats));
|
memset(&hdev->stat, 0, sizeof(struct hci_dev_stats));
|
|
|
atomic_set(&hdev->promisc, 0);
|
atomic_set(&hdev->promisc, 0);
|
|
|
MOD_INC_USE_COUNT;
|
MOD_INC_USE_COUNT;
|
|
|
write_unlock_bh(&hdev_list_lock);
|
write_unlock_bh(&hdev_list_lock);
|
|
|
hci_notify(hdev, HCI_DEV_REG);
|
hci_notify(hdev, HCI_DEV_REG);
|
hci_run_hotplug(hdev->name, "register");
|
hci_run_hotplug(hdev->name, "register");
|
|
|
return id;
|
return id;
|
}
|
}
|
|
|
/* Unregister HCI device */
|
/* Unregister HCI device */
|
int hci_unregister_dev(struct hci_dev *hdev)
|
int hci_unregister_dev(struct hci_dev *hdev)
|
{
|
{
|
BT_DBG("%p name %s type %d", hdev, hdev->name, hdev->type);
|
BT_DBG("%p name %s type %d", hdev, hdev->name, hdev->type);
|
|
|
write_lock_bh(&hdev_list_lock);
|
write_lock_bh(&hdev_list_lock);
|
list_del(&hdev->list);
|
list_del(&hdev->list);
|
write_unlock_bh(&hdev_list_lock);
|
write_unlock_bh(&hdev_list_lock);
|
|
|
hci_dev_do_close(hdev);
|
hci_dev_do_close(hdev);
|
|
|
hci_notify(hdev, HCI_DEV_UNREG);
|
hci_notify(hdev, HCI_DEV_UNREG);
|
hci_run_hotplug(hdev->name, "unregister");
|
hci_run_hotplug(hdev->name, "unregister");
|
|
|
hci_dev_put(hdev);
|
hci_dev_put(hdev);
|
|
|
MOD_DEC_USE_COUNT;
|
MOD_DEC_USE_COUNT;
|
return 0;
|
return 0;
|
}
|
}
|
|
|
/* Suspend HCI device */
|
/* Suspend HCI device */
|
int hci_suspend_dev(struct hci_dev *hdev)
|
int hci_suspend_dev(struct hci_dev *hdev)
|
{
|
{
|
hci_notify(hdev, HCI_DEV_SUSPEND);
|
hci_notify(hdev, HCI_DEV_SUSPEND);
|
hci_run_hotplug(hdev->name, "suspend");
|
hci_run_hotplug(hdev->name, "suspend");
|
return 0;
|
return 0;
|
}
|
}
|
|
|
/* Resume HCI device */
|
/* Resume HCI device */
|
int hci_resume_dev(struct hci_dev *hdev)
|
int hci_resume_dev(struct hci_dev *hdev)
|
{
|
{
|
hci_notify(hdev, HCI_DEV_RESUME);
|
hci_notify(hdev, HCI_DEV_RESUME);
|
hci_run_hotplug(hdev->name, "resume");
|
hci_run_hotplug(hdev->name, "resume");
|
return 0;
|
return 0;
|
}
|
}
|
|
|
/* Receive frame from HCI drivers */
|
/* Receive frame from HCI drivers */
|
int hci_recv_frame(struct sk_buff *skb)
|
int hci_recv_frame(struct sk_buff *skb)
|
{
|
{
|
struct hci_dev *hdev = (struct hci_dev *) skb->dev;
|
struct hci_dev *hdev = (struct hci_dev *) skb->dev;
|
|
|
if (!hdev || (!test_bit(HCI_UP, &hdev->flags) &&
|
if (!hdev || (!test_bit(HCI_UP, &hdev->flags) &&
|
!test_bit(HCI_INIT, &hdev->flags)) ) {
|
!test_bit(HCI_INIT, &hdev->flags)) ) {
|
kfree_skb(skb);
|
kfree_skb(skb);
|
return -1;
|
return -1;
|
}
|
}
|
|
|
BT_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);
|
|
|
/* Incomming skb */
|
/* Incomming skb */
|
bluez_cb(skb)->incomming = 1;
|
bluez_cb(skb)->incomming = 1;
|
|
|
/* Time stamp */
|
/* Time stamp */
|
do_gettimeofday(&skb->stamp);
|
do_gettimeofday(&skb->stamp);
|
|
|
/* Queue frame for rx task */
|
/* Queue frame for rx task */
|
skb_queue_tail(&hdev->rx_q, skb);
|
skb_queue_tail(&hdev->rx_q, skb);
|
hci_sched_rx(hdev);
|
hci_sched_rx(hdev);
|
return 0;
|
return 0;
|
}
|
}
|
|
|
/* ---- Interface to upper protocols ---- */
|
/* ---- Interface to upper protocols ---- */
|
|
|
/* Register/Unregister protocols.
|
/* Register/Unregister protocols.
|
* hci_task_lock is used to ensure that no tasks are running. */
|
* hci_task_lock is used to ensure that no tasks are running. */
|
int hci_register_proto(struct hci_proto *hp)
|
int hci_register_proto(struct hci_proto *hp)
|
{
|
{
|
int err = 0;
|
int err = 0;
|
|
|
BT_DBG("%p name %s id %d", hp, hp->name, hp->id);
|
BT_DBG("%p name %s id %d", hp, hp->name, hp->id);
|
|
|
if (hp->id >= HCI_MAX_PROTO)
|
if (hp->id >= HCI_MAX_PROTO)
|
return -EINVAL;
|
return -EINVAL;
|
|
|
write_lock_bh(&hci_task_lock);
|
write_lock_bh(&hci_task_lock);
|
|
|
if (!hci_proto[hp->id])
|
if (!hci_proto[hp->id])
|
hci_proto[hp->id] = hp;
|
hci_proto[hp->id] = hp;
|
else
|
else
|
err = -EEXIST;
|
err = -EEXIST;
|
|
|
write_unlock_bh(&hci_task_lock);
|
write_unlock_bh(&hci_task_lock);
|
|
|
return err;
|
return err;
|
}
|
}
|
|
|
int hci_unregister_proto(struct hci_proto *hp)
|
int hci_unregister_proto(struct hci_proto *hp)
|
{
|
{
|
int err = 0;
|
int err = 0;
|
|
|
BT_DBG("%p name %s id %d", hp, hp->name, hp->id);
|
BT_DBG("%p name %s id %d", hp, hp->name, hp->id);
|
|
|
if (hp->id >= HCI_MAX_PROTO)
|
if (hp->id >= HCI_MAX_PROTO)
|
return -EINVAL;
|
return -EINVAL;
|
|
|
write_lock_bh(&hci_task_lock);
|
write_lock_bh(&hci_task_lock);
|
|
|
if (hci_proto[hp->id])
|
if (hci_proto[hp->id])
|
hci_proto[hp->id] = NULL;
|
hci_proto[hp->id] = NULL;
|
else
|
else
|
err = -ENOENT;
|
err = -ENOENT;
|
|
|
write_unlock_bh(&hci_task_lock);
|
write_unlock_bh(&hci_task_lock);
|
|
|
return err;
|
return err;
|
}
|
}
|
|
|
static int hci_send_frame(struct sk_buff *skb)
|
static int hci_send_frame(struct sk_buff *skb)
|
{
|
{
|
struct hci_dev *hdev = (struct hci_dev *) skb->dev;
|
struct hci_dev *hdev = (struct hci_dev *) skb->dev;
|
|
|
if (!hdev) {
|
if (!hdev) {
|
kfree_skb(skb);
|
kfree_skb(skb);
|
return -ENODEV;
|
return -ENODEV;
|
}
|
}
|
|
|
BT_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)) {
|
if (atomic_read(&hdev->promisc)) {
|
/* Time stamp */
|
/* Time stamp */
|
do_gettimeofday(&skb->stamp);
|
do_gettimeofday(&skb->stamp);
|
|
|
hci_send_to_sock(hdev, skb);
|
hci_send_to_sock(hdev, skb);
|
}
|
}
|
|
|
/* Get rid of skb owner, prior to sending to the driver. */
|
/* Get rid of skb owner, prior to sending to the driver. */
|
skb_orphan(skb);
|
skb_orphan(skb);
|
|
|
return hdev->send(skb);
|
return hdev->send(skb);
|
}
|
}
|
|
|
/* Send HCI command */
|
/* Send HCI command */
|
int hci_send_cmd(struct hci_dev *hdev, __u16 ogf, __u16 ocf, __u32 plen, void *param)
|
int hci_send_cmd(struct hci_dev *hdev, __u16 ogf, __u16 ocf, __u32 plen, void *param)
|
{
|
{
|
int len = HCI_COMMAND_HDR_SIZE + plen;
|
int len = HCI_COMMAND_HDR_SIZE + plen;
|
hci_command_hdr *hc;
|
hci_command_hdr *hc;
|
struct sk_buff *skb;
|
struct sk_buff *skb;
|
|
|
BT_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))) {
|
if (!(skb = bluez_skb_alloc(len, GFP_ATOMIC))) {
|
BT_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;
|
return -ENOMEM;
|
}
|
}
|
|
|
hc = (hci_command_hdr *) skb_put(skb, HCI_COMMAND_HDR_SIZE);
|
hc = (hci_command_hdr *) skb_put(skb, HCI_COMMAND_HDR_SIZE);
|
hc->opcode = __cpu_to_le16(cmd_opcode_pack(ogf, ocf));
|
hc->opcode = __cpu_to_le16(cmd_opcode_pack(ogf, ocf));
|
hc->plen = plen;
|
hc->plen = plen;
|
|
|
if (plen)
|
if (plen)
|
memcpy(skb_put(skb, plen), param, plen);
|
memcpy(skb_put(skb, plen), param, plen);
|
|
|
BT_DBG("skb len %d", skb->len);
|
BT_DBG("skb len %d", skb->len);
|
|
|
skb->pkt_type = HCI_COMMAND_PKT;
|
skb->pkt_type = HCI_COMMAND_PKT;
|
skb->dev = (void *) hdev;
|
skb->dev = (void *) hdev;
|
skb_queue_tail(&hdev->cmd_q, skb);
|
skb_queue_tail(&hdev->cmd_q, skb);
|
hci_sched_cmd(hdev);
|
hci_sched_cmd(hdev);
|
|
|
return 0;
|
return 0;
|
}
|
}
|
|
|
/* Get data from the previously sent command */
|
/* Get data from the previously sent command */
|
void *hci_sent_cmd_data(struct hci_dev *hdev, __u16 ogf, __u16 ocf)
|
void *hci_sent_cmd_data(struct hci_dev *hdev, __u16 ogf, __u16 ocf)
|
{
|
{
|
hci_command_hdr *hc;
|
hci_command_hdr *hc;
|
|
|
if (!hdev->sent_cmd)
|
if (!hdev->sent_cmd)
|
return NULL;
|
return NULL;
|
|
|
hc = (void *) hdev->sent_cmd->data;
|
hc = (void *) hdev->sent_cmd->data;
|
|
|
if (hc->opcode != __cpu_to_le16(cmd_opcode_pack(ogf, ocf)))
|
if (hc->opcode != __cpu_to_le16(cmd_opcode_pack(ogf, ocf)))
|
return NULL;
|
return NULL;
|
|
|
BT_DBG("%s ogf 0x%x ocf 0x%x", hdev->name, ogf, ocf);
|
BT_DBG("%s ogf 0x%x ocf 0x%x", hdev->name, ogf, ocf);
|
|
|
return hdev->sent_cmd->data + HCI_COMMAND_HDR_SIZE;
|
return hdev->sent_cmd->data + HCI_COMMAND_HDR_SIZE;
|
}
|
}
|
|
|
/* Send ACL data */
|
/* Send ACL data */
|
static void hci_add_acl_hdr(struct sk_buff *skb, __u16 handle, __u16 flags)
|
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;
|
hci_acl_hdr *ah;
|
|
|
ah = (hci_acl_hdr *) skb_push(skb, HCI_ACL_HDR_SIZE);
|
ah = (hci_acl_hdr *) skb_push(skb, HCI_ACL_HDR_SIZE);
|
ah->handle = __cpu_to_le16(acl_handle_pack(handle, flags));
|
ah->handle = __cpu_to_le16(acl_handle_pack(handle, flags));
|
ah->dlen = __cpu_to_le16(len);
|
ah->dlen = __cpu_to_le16(len);
|
|
|
skb->h.raw = (void *) ah;
|
skb->h.raw = (void *) ah;
|
}
|
}
|
|
|
int hci_send_acl(struct hci_conn *conn, struct sk_buff *skb, __u16 flags)
|
int hci_send_acl(struct hci_conn *conn, struct sk_buff *skb, __u16 flags)
|
{
|
{
|
struct hci_dev *hdev = conn->hdev;
|
struct hci_dev *hdev = conn->hdev;
|
struct sk_buff *list;
|
struct sk_buff *list;
|
|
|
BT_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->dev = (void *) hdev;
|
skb->pkt_type = HCI_ACLDATA_PKT;
|
skb->pkt_type = HCI_ACLDATA_PKT;
|
hci_add_acl_hdr(skb, conn->handle, flags | ACL_START);
|
hci_add_acl_hdr(skb, conn->handle, flags | ACL_START);
|
|
|
if (!(list = skb_shinfo(skb)->frag_list)) {
|
if (!(list = skb_shinfo(skb)->frag_list)) {
|
/* Non fragmented */
|
/* Non fragmented */
|
BT_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);
|
skb_queue_tail(&conn->data_q, skb);
|
} else {
|
} else {
|
/* Fragmented */
|
/* Fragmented */
|
BT_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;
|
skb_shinfo(skb)->frag_list = NULL;
|
|
|
/* Queue all fragments atomically */
|
/* Queue all fragments atomically */
|
spin_lock_bh(&conn->data_q.lock);
|
spin_lock_bh(&conn->data_q.lock);
|
|
|
__skb_queue_tail(&conn->data_q, skb);
|
__skb_queue_tail(&conn->data_q, skb);
|
do {
|
do {
|
skb = list; list = list->next;
|
skb = list; list = list->next;
|
|
|
skb->dev = (void *) hdev;
|
skb->dev = (void *) hdev;
|
skb->pkt_type = HCI_ACLDATA_PKT;
|
skb->pkt_type = HCI_ACLDATA_PKT;
|
hci_add_acl_hdr(skb, conn->handle, flags | ACL_CONT);
|
hci_add_acl_hdr(skb, conn->handle, flags | ACL_CONT);
|
|
|
BT_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);
|
__skb_queue_tail(&conn->data_q, skb);
|
} while (list);
|
} while (list);
|
|
|
spin_unlock_bh(&conn->data_q.lock);
|
spin_unlock_bh(&conn->data_q.lock);
|
}
|
}
|
|
|
hci_sched_tx(hdev);
|
hci_sched_tx(hdev);
|
return 0;
|
return 0;
|
}
|
}
|
|
|
/* Send SCO data */
|
/* Send SCO data */
|
int hci_send_sco(struct hci_conn *conn, struct sk_buff *skb)
|
int hci_send_sco(struct hci_conn *conn, struct sk_buff *skb)
|
{
|
{
|
struct hci_dev *hdev = conn->hdev;
|
struct hci_dev *hdev = conn->hdev;
|
hci_sco_hdr hs;
|
hci_sco_hdr hs;
|
|
|
BT_DBG("%s len %d", hdev->name, skb->len);
|
BT_DBG("%s len %d", hdev->name, skb->len);
|
|
|
if (skb->len > hdev->sco_mtu) {
|
if (skb->len > hdev->sco_mtu) {
|
kfree_skb(skb);
|
kfree_skb(skb);
|
return -EINVAL;
|
return -EINVAL;
|
}
|
}
|
|
|
hs.handle = __cpu_to_le16(conn->handle);
|
hs.handle = __cpu_to_le16(conn->handle);
|
hs.dlen = skb->len;
|
hs.dlen = skb->len;
|
|
|
skb->h.raw = skb_push(skb, HCI_SCO_HDR_SIZE);
|
skb->h.raw = skb_push(skb, HCI_SCO_HDR_SIZE);
|
memcpy(skb->h.raw, &hs, HCI_SCO_HDR_SIZE);
|
memcpy(skb->h.raw, &hs, HCI_SCO_HDR_SIZE);
|
|
|
skb->dev = (void *) hdev;
|
skb->dev = (void *) hdev;
|
skb->pkt_type = HCI_SCODATA_PKT;
|
skb->pkt_type = HCI_SCODATA_PKT;
|
skb_queue_tail(&conn->data_q, skb);
|
skb_queue_tail(&conn->data_q, skb);
|
hci_sched_tx(hdev);
|
hci_sched_tx(hdev);
|
return 0;
|
return 0;
|
}
|
}
|
|
|
/* ---- HCI TX task (outgoing data) ---- */
|
/* ---- HCI TX task (outgoing data) ---- */
|
|
|
/* HCI Connection scheduler */
|
/* HCI Connection scheduler */
|
static inline struct hci_conn *hci_low_sent(struct hci_dev *hdev, __u8 type, int *quote)
|
static inline struct hci_conn *hci_low_sent(struct hci_dev *hdev, __u8 type, int *quote)
|
{
|
{
|
struct conn_hash *h = &hdev->conn_hash;
|
struct conn_hash *h = &hdev->conn_hash;
|
struct hci_conn *conn = NULL;
|
struct hci_conn *conn = NULL;
|
int num = 0, min = ~0;
|
int num = 0, min = ~0;
|
struct list_head *p;
|
struct list_head *p;
|
|
|
/* We don't have to lock device here. Connections are always
|
/* We don't have to lock device here. Connections are always
|
* added and removed with TX task disabled. */
|
* added and removed with TX task disabled. */
|
list_for_each(p, &h->list) {
|
list_for_each(p, &h->list) {
|
struct hci_conn *c;
|
struct hci_conn *c;
|
c = list_entry(p, struct hci_conn, list);
|
c = list_entry(p, struct hci_conn, list);
|
|
|
if (c->type != type || c->state != BT_CONNECTED
|
if (c->type != type || c->state != BT_CONNECTED
|
|| skb_queue_empty(&c->data_q))
|
|| skb_queue_empty(&c->data_q))
|
continue;
|
continue;
|
num++;
|
num++;
|
|
|
if (c->sent < min) {
|
if (c->sent < min) {
|
min = c->sent;
|
min = c->sent;
|
conn = c;
|
conn = c;
|
}
|
}
|
}
|
}
|
|
|
if (conn) {
|
if (conn) {
|
int cnt = (type == ACL_LINK ? hdev->acl_cnt : hdev->sco_cnt);
|
int cnt = (type == ACL_LINK ? hdev->acl_cnt : hdev->sco_cnt);
|
int q = cnt / num;
|
int q = cnt / num;
|
*quote = q ? q : 1;
|
*quote = q ? q : 1;
|
} else
|
} else
|
*quote = 0;
|
*quote = 0;
|
|
|
BT_DBG("conn %p quote %d", conn, *quote);
|
BT_DBG("conn %p quote %d", conn, *quote);
|
return conn;
|
return conn;
|
}
|
}
|
|
|
static inline void hci_acl_tx_to(struct hci_dev *hdev)
|
static inline void hci_acl_tx_to(struct hci_dev *hdev)
|
{
|
{
|
struct conn_hash *h = &hdev->conn_hash;
|
struct conn_hash *h = &hdev->conn_hash;
|
struct list_head *p;
|
struct list_head *p;
|
struct hci_conn *c;
|
struct hci_conn *c;
|
|
|
BT_ERR("%s ACL tx timeout", hdev->name);
|
BT_ERR("%s ACL tx timeout", hdev->name);
|
|
|
/* Kill stalled connections */
|
/* Kill stalled connections */
|
list_for_each(p, &h->list) {
|
list_for_each(p, &h->list) {
|
c = list_entry(p, struct hci_conn, list);
|
c = list_entry(p, struct hci_conn, list);
|
if (c->type == ACL_LINK && c->sent) {
|
if (c->type == ACL_LINK && c->sent) {
|
BT_ERR("%s killing stalled ACL connection %s",
|
BT_ERR("%s killing stalled ACL connection %s",
|
hdev->name, batostr(&c->dst));
|
hdev->name, batostr(&c->dst));
|
hci_acl_disconn(c, 0x13);
|
hci_acl_disconn(c, 0x13);
|
}
|
}
|
}
|
}
|
}
|
}
|
|
|
static inline void hci_sched_acl(struct hci_dev *hdev)
|
static inline void hci_sched_acl(struct hci_dev *hdev)
|
{
|
{
|
struct hci_conn *conn;
|
struct hci_conn *conn;
|
struct sk_buff *skb;
|
struct sk_buff *skb;
|
int quote;
|
int quote;
|
|
|
BT_DBG("%s", hdev->name);
|
BT_DBG("%s", hdev->name);
|
|
|
/* ACL tx timeout must be longer than maximum
|
/* ACL tx timeout must be longer than maximum
|
* link supervision timeout (40.9 seconds) */
|
* link supervision timeout (40.9 seconds) */
|
if (!hdev->acl_cnt && (jiffies - hdev->acl_last_tx) > (HZ * 45))
|
if (!hdev->acl_cnt && (jiffies - hdev->acl_last_tx) > (HZ * 45))
|
hci_acl_tx_to(hdev);
|
hci_acl_tx_to(hdev);
|
|
|
while (hdev->acl_cnt && (conn = hci_low_sent(hdev, ACL_LINK, "e))) {
|
while (hdev->acl_cnt && (conn = hci_low_sent(hdev, ACL_LINK, "e))) {
|
while (quote-- && (skb = skb_dequeue(&conn->data_q))) {
|
while (quote-- && (skb = skb_dequeue(&conn->data_q))) {
|
BT_DBG("skb %p len %d", skb, skb->len);
|
BT_DBG("skb %p len %d", skb, skb->len);
|
hci_send_frame(skb);
|
hci_send_frame(skb);
|
hdev->acl_last_tx = jiffies;
|
hdev->acl_last_tx = jiffies;
|
|
|
hdev->acl_cnt--;
|
hdev->acl_cnt--;
|
conn->sent++;
|
conn->sent++;
|
}
|
}
|
}
|
}
|
}
|
}
|
|
|
/* Schedule SCO */
|
/* Schedule SCO */
|
static inline void hci_sched_sco(struct hci_dev *hdev)
|
static inline void hci_sched_sco(struct hci_dev *hdev)
|
{
|
{
|
struct hci_conn *conn;
|
struct hci_conn *conn;
|
struct sk_buff *skb;
|
struct sk_buff *skb;
|
int quote;
|
int quote;
|
|
|
BT_DBG("%s", hdev->name);
|
BT_DBG("%s", hdev->name);
|
|
|
while (hdev->sco_cnt && (conn = hci_low_sent(hdev, SCO_LINK, "e))) {
|
while (hdev->sco_cnt && (conn = hci_low_sent(hdev, SCO_LINK, "e))) {
|
while (quote-- && (skb = skb_dequeue(&conn->data_q))) {
|
while (quote-- && (skb = skb_dequeue(&conn->data_q))) {
|
BT_DBG("skb %p len %d", skb, skb->len);
|
BT_DBG("skb %p len %d", skb, skb->len);
|
hci_send_frame(skb);
|
hci_send_frame(skb);
|
|
|
conn->sent++;
|
conn->sent++;
|
if (conn->sent == ~0)
|
if (conn->sent == ~0)
|
conn->sent = 0;
|
conn->sent = 0;
|
}
|
}
|
}
|
}
|
}
|
}
|
|
|
static void hci_tx_task(unsigned long arg)
|
static void hci_tx_task(unsigned long arg)
|
{
|
{
|
struct hci_dev *hdev = (struct hci_dev *) arg;
|
struct hci_dev *hdev = (struct hci_dev *) arg;
|
struct sk_buff *skb;
|
struct sk_buff *skb;
|
|
|
read_lock(&hci_task_lock);
|
read_lock(&hci_task_lock);
|
|
|
BT_DBG("%s acl %d sco %d", hdev->name, hdev->acl_cnt, hdev->sco_cnt);
|
BT_DBG("%s acl %d sco %d", hdev->name, hdev->acl_cnt, hdev->sco_cnt);
|
|
|
/* Schedule queues and send stuff to HCI driver */
|
/* Schedule queues and send stuff to HCI driver */
|
|
|
hci_sched_acl(hdev);
|
hci_sched_acl(hdev);
|
|
|
hci_sched_sco(hdev);
|
hci_sched_sco(hdev);
|
|
|
/* Send next queued raw (unknown type) packet */
|
/* Send next queued raw (unknown type) packet */
|
while ((skb = skb_dequeue(&hdev->raw_q)))
|
while ((skb = skb_dequeue(&hdev->raw_q)))
|
hci_send_frame(skb);
|
hci_send_frame(skb);
|
|
|
read_unlock(&hci_task_lock);
|
read_unlock(&hci_task_lock);
|
}
|
}
|
|
|
|
|
/* ----- HCI RX task (incomming data proccessing) ----- */
|
/* ----- HCI RX task (incomming data proccessing) ----- */
|
|
|
/* ACL data packet */
|
/* ACL data packet */
|
static inline void hci_acldata_packet(struct hci_dev *hdev, struct sk_buff *skb)
|
static inline void hci_acldata_packet(struct hci_dev *hdev, struct sk_buff *skb)
|
{
|
{
|
hci_acl_hdr *ah = (void *) skb->data;
|
hci_acl_hdr *ah = (void *) skb->data;
|
struct hci_conn *conn;
|
struct hci_conn *conn;
|
__u16 handle, flags;
|
__u16 handle, flags;
|
|
|
skb_pull(skb, HCI_ACL_HDR_SIZE);
|
skb_pull(skb, HCI_ACL_HDR_SIZE);
|
|
|
handle = __le16_to_cpu(ah->handle);
|
handle = __le16_to_cpu(ah->handle);
|
flags = acl_flags(handle);
|
flags = acl_flags(handle);
|
handle = acl_handle(handle);
|
handle = acl_handle(handle);
|
|
|
BT_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);
|
|
|
hdev->stat.acl_rx++;
|
hdev->stat.acl_rx++;
|
|
|
hci_dev_lock(hdev);
|
hci_dev_lock(hdev);
|
conn = conn_hash_lookup_handle(hdev, handle);
|
conn = conn_hash_lookup_handle(hdev, handle);
|
hci_dev_unlock(hdev);
|
hci_dev_unlock(hdev);
|
|
|
if (conn) {
|
if (conn) {
|
register struct hci_proto *hp;
|
register struct hci_proto *hp;
|
|
|
/* Send to upper protocol */
|
/* Send to upper protocol */
|
if ((hp = hci_proto[HCI_PROTO_L2CAP]) && hp->recv_acldata) {
|
if ((hp = hci_proto[HCI_PROTO_L2CAP]) && hp->recv_acldata) {
|
hp->recv_acldata(conn, skb, flags);
|
hp->recv_acldata(conn, skb, flags);
|
return;
|
return;
|
}
|
}
|
} else {
|
} else {
|
BT_ERR("%s ACL packet for unknown connection handle %d",
|
BT_ERR("%s ACL packet for unknown connection handle %d",
|
hdev->name, handle);
|
hdev->name, handle);
|
}
|
}
|
|
|
kfree_skb(skb);
|
kfree_skb(skb);
|
}
|
}
|
|
|
/* SCO data packet */
|
/* SCO data packet */
|
static inline void hci_scodata_packet(struct hci_dev *hdev, struct sk_buff *skb)
|
static inline void hci_scodata_packet(struct hci_dev *hdev, struct sk_buff *skb)
|
{
|
{
|
hci_sco_hdr *sh = (void *) skb->data;
|
hci_sco_hdr *sh = (void *) skb->data;
|
struct hci_conn *conn;
|
struct hci_conn *conn;
|
__u16 handle;
|
__u16 handle;
|
|
|
skb_pull(skb, HCI_SCO_HDR_SIZE);
|
skb_pull(skb, HCI_SCO_HDR_SIZE);
|
|
|
handle = __le16_to_cpu(sh->handle);
|
handle = __le16_to_cpu(sh->handle);
|
|
|
BT_DBG("%s len %d handle 0x%x", hdev->name, skb->len, handle);
|
BT_DBG("%s len %d handle 0x%x", hdev->name, skb->len, handle);
|
|
|
hdev->stat.sco_rx++;
|
hdev->stat.sco_rx++;
|
|
|
hci_dev_lock(hdev);
|
hci_dev_lock(hdev);
|
conn = conn_hash_lookup_handle(hdev, handle);
|
conn = conn_hash_lookup_handle(hdev, handle);
|
hci_dev_unlock(hdev);
|
hci_dev_unlock(hdev);
|
|
|
if (conn) {
|
if (conn) {
|
register struct hci_proto *hp;
|
register struct hci_proto *hp;
|
|
|
/* Send to upper protocol */
|
/* Send to upper protocol */
|
if ((hp = hci_proto[HCI_PROTO_SCO]) && hp->recv_scodata) {
|
if ((hp = hci_proto[HCI_PROTO_SCO]) && hp->recv_scodata) {
|
hp->recv_scodata(conn, skb);
|
hp->recv_scodata(conn, skb);
|
return;
|
return;
|
}
|
}
|
} else {
|
} else {
|
BT_ERR("%s SCO packet for unknown connection handle %d",
|
BT_ERR("%s SCO packet for unknown connection handle %d",
|
hdev->name, handle);
|
hdev->name, handle);
|
}
|
}
|
|
|
kfree_skb(skb);
|
kfree_skb(skb);
|
}
|
}
|
|
|
void hci_rx_task(unsigned long arg)
|
void hci_rx_task(unsigned long arg)
|
{
|
{
|
struct hci_dev *hdev = (struct hci_dev *) arg;
|
struct hci_dev *hdev = (struct hci_dev *) arg;
|
struct sk_buff *skb;
|
struct sk_buff *skb;
|
|
|
BT_DBG("%s", hdev->name);
|
BT_DBG("%s", hdev->name);
|
|
|
read_lock(&hci_task_lock);
|
read_lock(&hci_task_lock);
|
|
|
while ((skb = skb_dequeue(&hdev->rx_q))) {
|
while ((skb = skb_dequeue(&hdev->rx_q))) {
|
if (atomic_read(&hdev->promisc)) {
|
if (atomic_read(&hdev->promisc)) {
|
/* Send copy to the sockets */
|
/* Send copy to the sockets */
|
hci_send_to_sock(hdev, skb);
|
hci_send_to_sock(hdev, skb);
|
}
|
}
|
|
|
if (test_bit(HCI_RAW, &hdev->flags)) {
|
if (test_bit(HCI_RAW, &hdev->flags)) {
|
kfree_skb(skb);
|
kfree_skb(skb);
|
continue;
|
continue;
|
}
|
}
|
|
|
if (test_bit(HCI_INIT, &hdev->flags)) {
|
if (test_bit(HCI_INIT, &hdev->flags)) {
|
/* Don't process data packets in this states. */
|
/* Don't process data packets in this states. */
|
switch (skb->pkt_type) {
|
switch (skb->pkt_type) {
|
case HCI_ACLDATA_PKT:
|
case HCI_ACLDATA_PKT:
|
case HCI_SCODATA_PKT:
|
case HCI_SCODATA_PKT:
|
kfree_skb(skb);
|
kfree_skb(skb);
|
continue;
|
continue;
|
};
|
};
|
}
|
}
|
|
|
/* Process frame */
|
/* Process frame */
|
switch (skb->pkt_type) {
|
switch (skb->pkt_type) {
|
case HCI_EVENT_PKT:
|
case HCI_EVENT_PKT:
|
hci_event_packet(hdev, skb);
|
hci_event_packet(hdev, skb);
|
break;
|
break;
|
|
|
case HCI_ACLDATA_PKT:
|
case HCI_ACLDATA_PKT:
|
BT_DBG("%s ACL data packet", hdev->name);
|
BT_DBG("%s ACL data packet", hdev->name);
|
hci_acldata_packet(hdev, skb);
|
hci_acldata_packet(hdev, skb);
|
break;
|
break;
|
|
|
case HCI_SCODATA_PKT:
|
case HCI_SCODATA_PKT:
|
BT_DBG("%s SCO data packet", hdev->name);
|
BT_DBG("%s SCO data packet", hdev->name);
|
hci_scodata_packet(hdev, skb);
|
hci_scodata_packet(hdev, skb);
|
break;
|
break;
|
|
|
default:
|
default:
|
kfree_skb(skb);
|
kfree_skb(skb);
|
break;
|
break;
|
}
|
}
|
}
|
}
|
|
|
read_unlock(&hci_task_lock);
|
read_unlock(&hci_task_lock);
|
}
|
}
|
|
|
static void hci_cmd_task(unsigned long arg)
|
static void hci_cmd_task(unsigned long arg)
|
{
|
{
|
struct hci_dev *hdev = (struct hci_dev *) arg;
|
struct hci_dev *hdev = (struct hci_dev *) arg;
|
struct sk_buff *skb;
|
struct sk_buff *skb;
|
|
|
BT_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) {
|
if (!atomic_read(&hdev->cmd_cnt) && (jiffies - hdev->cmd_last_tx) > HZ) {
|
BT_ERR("%s command tx timeout", hdev->name);
|
BT_ERR("%s command tx timeout", hdev->name);
|
atomic_set(&hdev->cmd_cnt, 1);
|
atomic_set(&hdev->cmd_cnt, 1);
|
}
|
}
|
|
|
/* Send queued commands */
|
/* Send queued commands */
|
if (atomic_read(&hdev->cmd_cnt) && (skb = skb_dequeue(&hdev->cmd_q))) {
|
if (atomic_read(&hdev->cmd_cnt) && (skb = skb_dequeue(&hdev->cmd_q))) {
|
if (hdev->sent_cmd)
|
if (hdev->sent_cmd)
|
kfree_skb(hdev->sent_cmd);
|
kfree_skb(hdev->sent_cmd);
|
|
|
if ((hdev->sent_cmd = skb_clone(skb, GFP_ATOMIC))) {
|
if ((hdev->sent_cmd = skb_clone(skb, GFP_ATOMIC))) {
|
atomic_dec(&hdev->cmd_cnt);
|
atomic_dec(&hdev->cmd_cnt);
|
hci_send_frame(skb);
|
hci_send_frame(skb);
|
hdev->cmd_last_tx = jiffies;
|
hdev->cmd_last_tx = jiffies;
|
} else {
|
} else {
|
skb_queue_head(&hdev->cmd_q, skb);
|
skb_queue_head(&hdev->cmd_q, skb);
|
hci_sched_cmd(hdev);
|
hci_sched_cmd(hdev);
|
}
|
}
|
}
|
}
|
}
|
}
|
|
|
/* ---- Initialization ---- */
|
/* ---- Initialization ---- */
|
|
|
int hci_core_init(void)
|
int hci_core_init(void)
|
{
|
{
|
return 0;
|
return 0;
|
}
|
}
|
|
|
int hci_core_cleanup(void)
|
int hci_core_cleanup(void)
|
{
|
{
|
return 0;
|
return 0;
|
}
|
}
|
|
|