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>
diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c
index 7f2cff8..67a2900 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 @@
 	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 @@
 		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 @@
 {
 	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 @@
 	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 @@
 			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 @@
 	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 @@
 
 	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 @@
 	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 @@
 {
 	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 @@
 
 	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 @@
 			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 @@
 	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;
 }