From 568dde90e74e9e8a27538b9a3c82cf77ae60fe9c Mon Sep 17 00:00:00 2001 From: Brian Gix Date: Wed, 11 Jan 2012 16:18:04 -0800 Subject: [PATCH] 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 --- include/net/bluetooth/hci_core.h | 11 +- net/bluetooth/hci_core.c | 8 +- net/bluetooth/mgmt.c | 289 ++++++++++++++----------------- 3 files changed, 147 insertions(+), 161 deletions(-) diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index 53397851a68..857f65406f7 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -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 @@ -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 */ diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index 97f0775a125..47bfde5f019 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -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); diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index 7f2cff89fd6..67a2900e922 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -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) { - hci_dev_lock_bh(hdev); - del_timer_sync(&hdev->disc_le_timer); + if (!hdev) + return; - cmd = mgmt_pending_find(MGMT_OP_STOP_DISCOVERY, ilp->index); + hci_dev_lock_bh(hdev); + del_timer(&hdev->disco_le_timer); - if (ilp->mode != SCAN_IDLE) { - struct hci_cp_le_set_scan_enable le_cp = {0, 0}; + if (hdev->disco_state != SCAN_IDLE) { + struct hci_cp_le_set_scan_enable le_cp = {0, 0}; - if (ilp->mode == 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); - - ilp->mode = SCAN_IDLE; - } - - if (cmd) { - struct mgmt_mode cp = {0}; - - mgmt_event(MGMT_EV_DISCOVERING, ilp->index, &cp, - sizeof(cp), NULL); - mgmt_pending_remove(cmd); - } - - hci_dev_unlock_bh(hdev); - hci_dev_put(hdev); - } -} - -static void disco_le_to(unsigned long data) -{ - struct disco_interleave *ilp = (void *)data; - struct hci_dev *hdev; - struct pending_cmd *cmd; - struct hci_cp_le_set_scan_enable le_cp = {0, 0}; - - BT_DBG("hci%d", ilp->index); - - hdev = hci_dev_get(ilp->index); - - 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); + else + hci_send_cmd(hdev, HCI_OP_INQUIRY_CANCEL, 0, NULL); - /* re-start BR scan */ - if (cmd) { - 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; - hci_send_cmd(hdev, HCI_OP_INQUIRY, sizeof(cp), &cp); - ilp->mode = SCAN_BR; - } else - ilp->mode = SCAN_IDLE; - - hci_dev_unlock_bh(hdev); - hci_dev_put(hdev); + hdev->disco_state = SCAN_IDLE; } + + mgmt_event(MGMT_EV_DISCOVERING, hdev->id, &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); +} + +void mgmt_disco_le_timeout(unsigned long data) +{ + struct hci_dev *hdev = (void *)data; + struct hci_cp_le_set_scan_enable le_cp = {0, 0}; + + BT_DBG("hci%d", hdev->id); + + hdev = hci_dev_get(hdev->id); + + if (!hdev) + return; + + hci_dev_lock_bh(hdev); + + 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 (hdev->disco_state != SCAN_IDLE) { + struct hci_cp_inquiry cp = {{0x33, 0x8b, 0x9e}, 4, 0}; + 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); + 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, - jiffies + msecs_to_jiffies(20000)); - } + 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) { - /* 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}; + if (!hdev) + return 0; - ilp->int_phase *= 2; - ilp->int_count = 0; - if (ilp->mode == 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; - hci_send_cmd(hdev, HCI_OP_INQUIRY, - sizeof(cp), &cp); - ilp->mode = SCAN_BR; - del_timer_sync(&hdev->disc_le_timer); - } + 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}; + + 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) hdev->disco_int_phase; + hci_send_cmd(hdev, HCI_OP_INQUIRY, + sizeof(cp), &cp); + hdev->disco_state = SCAN_BR; + del_timer_sync(&hdev->disco_le_timer); } - - if (hdev) - hci_dev_put(hdev); } +done: + hci_dev_put(hdev); return 0; }