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 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> Written 2000,2001 by Maxim Krasnyansky <maxk@qualcomm.com>
@@ -240,8 +240,11 @@ struct hci_dev {
rwlock_t adv_entries_lock; rwlock_t adv_entries_lock;
struct timer_list adv_timer; struct timer_list adv_timer;
struct timer_list disc_timer; struct timer_list disco_timer;
struct timer_list disc_le_timer; struct timer_list disco_le_timer;
__u8 disco_state;
int disco_int_phase;
int disco_int_count;
struct hci_dev_stats stat; 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); int mgmt_remote_name(u16 index, bdaddr_t *bdaddr, u8 status, u8 *name);
void mgmt_inquiry_started(u16 index); void mgmt_inquiry_started(u16 index);
void mgmt_inquiry_complete_evt(u16 index, u8 status); 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); int mgmt_encrypt_change(u16 index, bdaddr_t *bdaddr, u8 status);
/* LE SMP Management interface */ /* 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); skb_queue_head_init(&hdev->raw_q);
setup_timer(&hdev->cmd_timer, hci_cmd_timer, (unsigned long) hdev); 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++) for (i = 0; i < NUM_REASSEMBLY; i++)
hdev->reassembly[i] = NULL; hdev->reassembly[i] = NULL;
@@ -1575,8 +1579,8 @@ int hci_unregister_dev(struct hci_dev *hdev)
hci_del_off_timer(hdev); hci_del_off_timer(hdev);
del_timer(&hdev->adv_timer); del_timer(&hdev->adv_timer);
del_timer(&hdev->cmd_timer); del_timer(&hdev->cmd_timer);
del_timer(&hdev->disc_timer); del_timer(&hdev->disco_timer);
del_timer(&hdev->disc_le_timer); del_timer(&hdev->disco_le_timer);
destroy_workqueue(hdev->workqueue); destroy_workqueue(hdev->workqueue);

View File

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