Bluetooth: Discovery Timer stability fixes

This change prevents running timers from being re-initialized
which causes system instablility. Also prevents starting a
discovery operation when already in progress.

CRs-fixed: 328673
Change-Id: Icda36a25fdcb40dab4f95f9cc39ca124b299e308
Signed-off-by: Brian Gix <bgix@codeaurora.org>
This commit is contained in:
Brian Gix
2012-01-11 16:18:04 -08:00
parent 3cd62049b5
commit 568dde90e7
3 changed files with 147 additions and 161 deletions

View File

@@ -1,6 +1,6 @@
/*
BlueZ - Bluetooth protocol stack for Linux
Copyright (c) 2000-2001, 2010-2011, Code Aurora Forum. All rights reserved.
Copyright (c) 2000-2001, 2010-2012, Code Aurora Forum. All rights reserved.
Written 2000,2001 by Maxim Krasnyansky <maxk@qualcomm.com>
@@ -240,8 +240,11 @@ struct hci_dev {
rwlock_t adv_entries_lock;
struct timer_list adv_timer;
struct timer_list disc_timer;
struct timer_list disc_le_timer;
struct timer_list disco_timer;
struct timer_list disco_le_timer;
__u8 disco_state;
int disco_int_phase;
int disco_int_count;
struct hci_dev_stats stat;
@@ -1037,6 +1040,8 @@ int mgmt_device_found(u16 index, bdaddr_t *bdaddr, u8 type, u8 le,
int mgmt_remote_name(u16 index, bdaddr_t *bdaddr, u8 status, u8 *name);
void mgmt_inquiry_started(u16 index);
void mgmt_inquiry_complete_evt(u16 index, u8 status);
void mgmt_disco_timeout(unsigned long data);
void mgmt_disco_le_timeout(unsigned long data);
int mgmt_encrypt_change(u16 index, bdaddr_t *bdaddr, u8 status);
/* LE SMP Management interface */

View File

@@ -1464,6 +1464,10 @@ int hci_register_dev(struct hci_dev *hdev)
skb_queue_head_init(&hdev->raw_q);
setup_timer(&hdev->cmd_timer, hci_cmd_timer, (unsigned long) hdev);
setup_timer(&hdev->disco_timer, mgmt_disco_timeout,
(unsigned long) hdev);
setup_timer(&hdev->disco_le_timer, mgmt_disco_le_timeout,
(unsigned long) hdev);
for (i = 0; i < NUM_REASSEMBLY; i++)
hdev->reassembly[i] = NULL;
@@ -1575,8 +1579,8 @@ int hci_unregister_dev(struct hci_dev *hdev)
hci_del_off_timer(hdev);
del_timer(&hdev->adv_timer);
del_timer(&hdev->cmd_timer);
del_timer(&hdev->disc_timer);
del_timer(&hdev->disc_le_timer);
del_timer(&hdev->disco_timer);
del_timer(&hdev->disco_le_timer);
destroy_workqueue(hdev->workqueue);

View File

@@ -1,6 +1,7 @@
/*
BlueZ - Bluetooth protocol stack for Linux
Copyright (C) 2010 Nokia Corporation
Copyright (c) 2011-2012 Code Aurora Forum. All rights reserved.
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
@@ -34,18 +35,9 @@
#define MGMT_VERSION 0
#define MGMT_REVISION 1
enum scan_mode {
SCAN_IDLE,
SCAN_LE,
SCAN_BR
};
struct disco_interleave {
u16 index;
enum scan_mode mode;
int int_phase;
int int_count;
};
#define SCAN_IDLE 0x00
#define SCAN_LE 0x01
#define SCAN_BR 0x02
struct pending_cmd {
struct list_head list;
@@ -1571,8 +1563,8 @@ static void discovery_terminated(struct pending_cmd *cmd, void *data)
if (!hdev)
goto not_found;
del_timer_sync(&hdev->disc_le_timer);
del_timer_sync(&hdev->disc_timer);
del_timer(&hdev->disco_le_timer);
del_timer(&hdev->disco_timer);
hci_dev_put(hdev);
not_found:
@@ -1858,8 +1850,8 @@ static void discovery_rsp(struct pending_cmd *cmd, void *data)
if (cmd->opcode == MGMT_OP_STOP_DISCOVERY) {
struct hci_dev *hdev = hci_dev_get(cmd->index);
if (hdev) {
del_timer_sync(&hdev->disc_le_timer);
del_timer_sync(&hdev->disc_timer);
del_timer(&hdev->disco_le_timer);
del_timer(&hdev->disco_timer);
hci_dev_put(hdev);
}
}
@@ -1883,7 +1875,7 @@ void mgmt_inquiry_complete_evt(u16 index, u8 status)
{
struct hci_dev *hdev;
struct hci_cp_le_set_scan_enable le_cp = {1, 0};
struct pending_cmd *cmd;
struct mgmt_mode cp = {0};
int err = -1;
BT_DBG("");
@@ -1891,7 +1883,6 @@ void mgmt_inquiry_complete_evt(u16 index, u8 status)
hdev = hci_dev_get(index);
if (!hdev || !lmp_le_capable(hdev)) {
struct mgmt_mode cp = {0};
mgmt_pending_foreach(MGMT_OP_STOP_DISCOVERY, index,
discovery_terminated, NULL);
@@ -1904,20 +1895,20 @@ void mgmt_inquiry_complete_evt(u16 index, u8 status)
return;
}
cmd = mgmt_pending_find(MGMT_OP_STOP_DISCOVERY, index);
if (cmd && cmd->param) {
struct disco_interleave *ilp = cmd->param;
if (hdev->disco_state != SCAN_IDLE) {
err = hci_send_cmd(hdev, HCI_OP_LE_SET_SCAN_ENABLE,
sizeof(le_cp), &le_cp);
if (err >= 0 && hdev) {
mod_timer(&hdev->disc_le_timer, jiffies +
msecs_to_jiffies(ilp->int_phase * 1000));
ilp->mode = SCAN_LE;
if (err >= 0) {
mod_timer(&hdev->disco_le_timer, jiffies +
msecs_to_jiffies(hdev->disco_int_phase * 1000));
hdev->disco_state = SCAN_LE;
} else
ilp->mode = SCAN_IDLE;
hdev->disco_state = SCAN_IDLE;
}
if (hdev->disco_state == SCAN_IDLE)
mgmt_event(MGMT_EV_DISCOVERING, index, &cp, sizeof(cp), NULL);
if (err < 0)
mgmt_pending_foreach(MGMT_OP_STOP_DISCOVERY, index,
discovery_terminated, NULL);
@@ -1926,82 +1917,74 @@ done:
hci_dev_put(hdev);
}
static void disco_to(unsigned long data)
void mgmt_disco_timeout(unsigned long data)
{
struct disco_interleave *ilp = (void *)data;
struct hci_dev *hdev;
struct hci_dev *hdev = (void *) data;
struct pending_cmd *cmd;
struct mgmt_mode cp = {0};
BT_DBG("hci%d", ilp->index);
BT_DBG("hci%d", hdev->id);
hdev = hci_dev_get(ilp->index);
hdev = hci_dev_get(hdev->id);
if (!hdev)
return;
if (hdev) {
hci_dev_lock_bh(hdev);
del_timer_sync(&hdev->disc_le_timer);
del_timer(&hdev->disco_le_timer);
cmd = mgmt_pending_find(MGMT_OP_STOP_DISCOVERY, ilp->index);
if (ilp->mode != SCAN_IDLE) {
if (hdev->disco_state != SCAN_IDLE) {
struct hci_cp_le_set_scan_enable le_cp = {0, 0};
if (ilp->mode == SCAN_LE)
if (hdev->disco_state == SCAN_LE)
hci_send_cmd(hdev, HCI_OP_LE_SET_SCAN_ENABLE,
sizeof(le_cp), &le_cp);
else
hci_send_cmd(hdev, HCI_OP_INQUIRY_CANCEL,
0, NULL);
hci_send_cmd(hdev, HCI_OP_INQUIRY_CANCEL, 0, NULL);
ilp->mode = SCAN_IDLE;
hdev->disco_state = SCAN_IDLE;
}
if (cmd) {
struct mgmt_mode cp = {0};
mgmt_event(MGMT_EV_DISCOVERING, hdev->id, &cp, sizeof(cp), NULL);
mgmt_event(MGMT_EV_DISCOVERING, ilp->index, &cp,
sizeof(cp), NULL);
cmd = mgmt_pending_find(MGMT_OP_STOP_DISCOVERY, hdev->id);
if (cmd)
mgmt_pending_remove(cmd);
}
hci_dev_unlock_bh(hdev);
hci_dev_put(hdev);
}
}
static void disco_le_to(unsigned long data)
void mgmt_disco_le_timeout(unsigned long data)
{
struct disco_interleave *ilp = (void *)data;
struct hci_dev *hdev;
struct pending_cmd *cmd;
struct hci_dev *hdev = (void *)data;
struct hci_cp_le_set_scan_enable le_cp = {0, 0};
BT_DBG("hci%d", ilp->index);
BT_DBG("hci%d", hdev->id);
hdev = hci_dev_get(ilp->index);
hdev = hci_dev_get(hdev->id);
if (!hdev)
return;
if (hdev) {
hci_dev_lock_bh(hdev);
cmd = mgmt_pending_find(MGMT_OP_STOP_DISCOVERY, ilp->index);
if (ilp->mode == SCAN_LE)
if (hdev->disco_state == SCAN_LE)
hci_send_cmd(hdev, HCI_OP_LE_SET_SCAN_ENABLE,
sizeof(le_cp), &le_cp);
/* re-start BR scan */
if (cmd) {
if (hdev->disco_state != SCAN_IDLE) {
struct hci_cp_inquiry cp = {{0x33, 0x8b, 0x9e}, 4, 0};
ilp->int_phase *= 2;
ilp->int_count = 0;
cp.num_rsp = (u8) ilp->int_phase;
hdev->disco_int_phase *= 2;
hdev->disco_int_count = 0;
cp.num_rsp = (u8) hdev->disco_int_phase;
hci_send_cmd(hdev, HCI_OP_INQUIRY, sizeof(cp), &cp);
ilp->mode = SCAN_BR;
} else
ilp->mode = SCAN_IDLE;
hdev->disco_state = SCAN_BR;
}
hci_dev_unlock_bh(hdev);
hci_dev_put(hdev);
}
}
static int start_discovery(struct sock *sk, u16 index)
@@ -2019,6 +2002,11 @@ static int start_discovery(struct sock *sk, u16 index)
hci_dev_lock_bh(hdev);
if (hdev->disco_state && timer_pending(&hdev->disco_timer)) {
err = -EBUSY;
goto failed;
}
cmd = mgmt_pending_add(sk, MGMT_OP_START_DISCOVERY, index, NULL, 0);
if (!cmd) {
err = -ENOMEM;
@@ -2052,30 +2040,23 @@ static int start_discovery(struct sock *sk, u16 index)
if (err < 0)
mgmt_pending_remove(cmd);
else if (lmp_le_capable(hdev)) {
struct disco_interleave il, *ilp;
il.int_phase = 1;
il.int_count = 0;
il.index = index;
il.mode = SCAN_BR;
mgmt_pending_add(sk, MGMT_OP_STOP_DISCOVERY, index, &il,
sizeof(struct disco_interleave));
cmd = mgmt_pending_find(MGMT_OP_STOP_DISCOVERY, index);
if (cmd) {
ilp = cmd->param;
setup_timer(&hdev->disc_le_timer, disco_le_to,
(unsigned long) ilp);
setup_timer(&hdev->disc_timer, disco_to,
(unsigned long) ilp);
mod_timer(&hdev->disc_timer,
hdev->disco_int_phase = 1;
hdev->disco_int_count = 0;
hdev->disco_state = SCAN_BR;
mgmt_pending_add(sk, MGMT_OP_STOP_DISCOVERY, index, NULL, 0);
del_timer(&hdev->disco_le_timer);
del_timer(&hdev->disco_timer);
mod_timer(&hdev->disco_timer,
jiffies + msecs_to_jiffies(20000));
}
}
failed:
hci_dev_unlock_bh(hdev);
hci_dev_put(hdev);
if (err < 0)
return cmd_status(sk, index, MGMT_OP_START_DISCOVERY, -err);
return err;
}
@@ -2083,10 +2064,10 @@ static int stop_discovery(struct sock *sk, u16 index)
{
struct hci_cp_le_set_scan_enable le_cp = {0, 0};
struct mgmt_mode mode_cp = {0};
struct disco_interleave *ilp = NULL;
struct hci_dev *hdev;
struct pending_cmd *cmd = NULL;
int err = -EPERM;
u8 state;
BT_DBG("");
@@ -2096,38 +2077,32 @@ static int stop_discovery(struct sock *sk, u16 index)
hci_dev_lock_bh(hdev);
if (lmp_le_capable(hdev)) {
cmd = mgmt_pending_find(MGMT_OP_STOP_DISCOVERY, index);
if (!cmd) {
err = -ENOMEM;
goto failed;
}
state = hdev->disco_state;
hdev->disco_state = SCAN_IDLE;
del_timer(&hdev->disco_le_timer);
del_timer(&hdev->disco_timer);
ilp = cmd->param;
}
if (lmp_le_capable(hdev) && ilp && (ilp->mode == SCAN_LE))
if (state == SCAN_LE) {
err = hci_send_cmd(hdev, HCI_OP_LE_SET_SCAN_ENABLE,
sizeof(le_cp), &le_cp);
if (err >= 0) {
mgmt_pending_foreach(MGMT_OP_STOP_DISCOVERY, index,
discovery_terminated, NULL);
if (err < 0) {
if (!ilp || (ilp->mode == SCAN_BR))
err = hci_send_cmd(hdev, HCI_OP_INQUIRY_CANCEL,
0, NULL);
err = cmd_complete(sk, index, MGMT_OP_STOP_DISCOVERY,
NULL, 0);
}
}
if (ilp) {
ilp->mode = SCAN_IDLE;
del_timer_sync(&hdev->disc_le_timer);
del_timer_sync(&hdev->disc_timer);
}
if (err < 0)
err = hci_send_cmd(hdev, HCI_OP_INQUIRY_CANCEL, 0, NULL);
cmd = mgmt_pending_find(MGMT_OP_STOP_DISCOVERY, index);
if (err < 0 && cmd)
mgmt_pending_remove(cmd);
mgmt_event(MGMT_EV_DISCOVERING, index, &mode_cp, sizeof(mode_cp), NULL);
failed:
hci_dev_unlock_bh(hdev);
hci_dev_put(hdev);
@@ -2822,7 +2797,7 @@ int mgmt_device_found(u16 index, bdaddr_t *bdaddr, u8 type, u8 le,
u8 *dev_class, s8 rssi, u8 eir_len, u8 *eir)
{
struct mgmt_ev_device_found ev;
struct pending_cmd *cmd;
struct hci_dev *hdev;
int err;
BT_DBG("le: %d", le);
@@ -2845,36 +2820,38 @@ int mgmt_device_found(u16 index, bdaddr_t *bdaddr, u8 type, u8 le,
if (err < 0)
return err;
cmd = mgmt_pending_find(MGMT_OP_STOP_DISCOVERY, index);
if (cmd) {
struct disco_interleave *ilp = cmd->param;
struct hci_dev *hdev = hci_dev_get(index);
hdev = hci_dev_get(index);
ilp->int_count++;
if (hdev && ilp->int_count >= ilp->int_phase) {
if (!hdev)
return 0;
if (hdev->disco_state == SCAN_IDLE)
goto done;
hdev->disco_int_count++;
if (hdev->disco_int_count >= hdev->disco_int_phase) {
/* Inquiry scan for General Discovery LAP */
struct hci_cp_inquiry cp = {{0x33, 0x8b, 0x9e}, 4, 0};
struct hci_cp_le_set_scan_enable le_cp = {0, 0};
ilp->int_phase *= 2;
ilp->int_count = 0;
if (ilp->mode == SCAN_LE) {
hdev->disco_int_phase *= 2;
hdev->disco_int_count = 0;
if (hdev->disco_state == SCAN_LE) {
/* cancel LE scan */
hci_send_cmd(hdev, HCI_OP_LE_SET_SCAN_ENABLE,
sizeof(le_cp), &le_cp);
/* start BR scan */
cp.num_rsp = (u8) ilp->int_phase;
cp.num_rsp = (u8) hdev->disco_int_phase;
hci_send_cmd(hdev, HCI_OP_INQUIRY,
sizeof(cp), &cp);
ilp->mode = SCAN_BR;
del_timer_sync(&hdev->disc_le_timer);
hdev->disco_state = SCAN_BR;
del_timer_sync(&hdev->disco_le_timer);
}
}
if (hdev)
done:
hci_dev_put(hdev);
}
return 0;
}