Commit 182ee45d authored by Luiz Augusto von Dentz's avatar Luiz Augusto von Dentz Committed by Marcel Holtmann
Browse files

Bluetooth: hci_sync: Rework hci_suspend_notifier



This makes hci_suspend_notifier use the hci_*_sync which can be
executed synchronously which is allowed in the suspend_notifier and
simplifies a lot of the handling since the status of each command can
be checked inline so no other work need to be scheduled thus can be
performed without using of a state machine.

Signed-off-by: default avatarLuiz Augusto von Dentz <luiz.von.dentz@intel.com>
Signed-off-by: default avatarMarcel Holtmann <marcel@holtmann.org>
parent d0b13706
Loading
Loading
Loading
Loading
+0 −4
Original line number Diff line number Diff line
@@ -523,7 +523,6 @@ struct hci_dev {
	bool			advertising_paused;

	struct notifier_block	suspend_notifier;
	struct work_struct	suspend_prepare;
	enum suspended_state	suspend_state_next;
	enum suspended_state	suspend_state;
	bool			scanning_paused;
@@ -532,9 +531,6 @@ struct hci_dev {
	bdaddr_t		wake_addr;
	u8			wake_addr_type;

	wait_queue_head_t	suspend_wait_q;
	DECLARE_BITMAP(suspend_tasks, __SUSPEND_NUM_TASKS);

	struct hci_conn_hash	conn_hash;

	struct list_head	mgmt_pending;
+3 −0
Original line number Diff line number Diff line
@@ -92,3 +92,6 @@ int hci_set_powered_sync(struct hci_dev *hdev, u8 val);

int hci_start_discovery_sync(struct hci_dev *hdev);
int hci_stop_discovery_sync(struct hci_dev *hdev);

int hci_suspend_sync(struct hci_dev *hdev);
int hci_resume_sync(struct hci_dev *hdev);
+0 −10
Original line number Diff line number Diff line
@@ -900,16 +900,6 @@ void hci_le_conn_failed(struct hci_conn *conn, u8 status)

	hci_conn_del(conn);

	/* The suspend notifier is waiting for all devices to disconnect and an
	 * LE connect cancel will result in an hci_le_conn_failed. Once the last
	 * connection is deleted, we should also wake the suspend queue to
	 * complete suspend operations.
	 */
	if (list_empty(&hdev->conn_hash.list) &&
	    test_and_clear_bit(SUSPEND_DISCONNECTING, hdev->suspend_tasks)) {
		wake_up(&hdev->suspend_wait_q);
	}

	/* Since we may have temporarily stopped the background scanning in
	 * favor of connection establishment, we should restart it.
	 */
+12 −94
Original line number Diff line number Diff line
@@ -2374,61 +2374,6 @@ void hci_copy_identity_address(struct hci_dev *hdev, bdaddr_t *bdaddr,
	}
}

static void hci_suspend_clear_tasks(struct hci_dev *hdev)
{
	int i;

	for (i = 0; i < __SUSPEND_NUM_TASKS; i++)
		clear_bit(i, hdev->suspend_tasks);

	wake_up(&hdev->suspend_wait_q);
}

static int hci_suspend_wait_event(struct hci_dev *hdev)
{
#define WAKE_COND                                                              \
	(find_first_bit(hdev->suspend_tasks, __SUSPEND_NUM_TASKS) ==           \
	 __SUSPEND_NUM_TASKS)

	int i;
	int ret = wait_event_timeout(hdev->suspend_wait_q,
				     WAKE_COND, SUSPEND_NOTIFIER_TIMEOUT);

	if (ret == 0) {
		bt_dev_err(hdev, "Timed out waiting for suspend events");
		for (i = 0; i < __SUSPEND_NUM_TASKS; ++i) {
			if (test_bit(i, hdev->suspend_tasks))
				bt_dev_err(hdev, "Suspend timeout bit: %d", i);
			clear_bit(i, hdev->suspend_tasks);
		}

		ret = -ETIMEDOUT;
	} else {
		ret = 0;
	}

	return ret;
}

static void hci_prepare_suspend(struct work_struct *work)
{
	struct hci_dev *hdev =
		container_of(work, struct hci_dev, suspend_prepare);

	hci_dev_lock(hdev);
	hci_req_prepare_suspend(hdev, hdev->suspend_state_next);
	hci_dev_unlock(hdev);
}

static int hci_change_suspend_state(struct hci_dev *hdev,
				    enum suspended_state next)
{
	hdev->suspend_state_next = next;
	set_bit(SUSPEND_PREPARE_NOTIFIER, hdev->suspend_tasks);
	queue_work(hdev->req_workqueue, &hdev->suspend_prepare);
	return hci_suspend_wait_event(hdev);
}

static void hci_clear_wake_reason(struct hci_dev *hdev)
{
	hci_dev_lock(hdev);
@@ -2565,7 +2510,6 @@ struct hci_dev *hci_alloc_dev_priv(int sizeof_priv)
	INIT_WORK(&hdev->tx_work, hci_tx_work);
	INIT_WORK(&hdev->power_on, hci_power_on);
	INIT_WORK(&hdev->error_reset, hci_error_reset);
	INIT_WORK(&hdev->suspend_prepare, hci_prepare_suspend);

	hci_cmd_sync_init(hdev);

@@ -2576,7 +2520,6 @@ struct hci_dev *hci_alloc_dev_priv(int sizeof_priv)
	skb_queue_head_init(&hdev->raw_q);

	init_waitqueue_head(&hdev->req_wait_q);
	init_waitqueue_head(&hdev->suspend_wait_q);

	INIT_DELAYED_WORK(&hdev->cmd_timer, hci_cmd_timeout);
	INIT_DELAYED_WORK(&hdev->ncmd_timer, hci_ncmd_timeout);
@@ -2729,11 +2672,8 @@ void hci_unregister_dev(struct hci_dev *hdev)

	hci_cmd_sync_clear(hdev);

	if (!test_bit(HCI_QUIRK_NO_SUSPEND_NOTIFIER, &hdev->quirks)) {
		hci_suspend_clear_tasks(hdev);
	if (!test_bit(HCI_QUIRK_NO_SUSPEND_NOTIFIER, &hdev->quirks))
		unregister_pm_notifier(&hdev->suspend_notifier);
		cancel_work_sync(&hdev->suspend_prepare);
	}

	msft_unregister(hdev);

@@ -2800,7 +2740,6 @@ EXPORT_SYMBOL(hci_release_dev);
int hci_suspend_dev(struct hci_dev *hdev)
{
	int ret;
	u8 state = BT_RUNNING;

	bt_dev_dbg(hdev, "");

@@ -2809,40 +2748,17 @@ int hci_suspend_dev(struct hci_dev *hdev)
	    hci_dev_test_flag(hdev, HCI_UNREGISTER))
		return 0;

	/* If powering down, wait for completion. */
	if (mgmt_powering_down(hdev)) {
		set_bit(SUSPEND_POWERING_DOWN, hdev->suspend_tasks);
		ret = hci_suspend_wait_event(hdev);
		if (ret)
			goto done;
	}

	/* Suspend consists of two actions:
	 *  - First, disconnect everything and make the controller not
	 *    connectable (disabling scanning)
	 *  - Second, program event filter/accept list and enable scan
	 */
	ret = hci_change_suspend_state(hdev, BT_SUSPEND_DISCONNECT);
	if (ret)
		goto clear;

	state = BT_SUSPEND_DISCONNECT;
	/* If powering down don't attempt to suspend */
	if (mgmt_powering_down(hdev))
		return 0;

	/* Only configure accept list if device may wakeup. */
	if (hdev->wakeup && hdev->wakeup(hdev)) {
		ret = hci_change_suspend_state(hdev, BT_SUSPEND_CONFIGURE_WAKE);
		if (!ret)
			state = BT_SUSPEND_CONFIGURE_WAKE;
	}
	hci_req_sync_lock(hdev);
	ret = hci_suspend_sync(hdev);
	hci_req_sync_unlock(hdev);

clear:
	hci_clear_wake_reason(hdev);
	mgmt_suspending(hdev, state);
	mgmt_suspending(hdev, hdev->suspend_state);

done:
	/* We always allow suspend even if suspend preparation failed and
	 * attempt to recover in resume.
	 */
	hci_sock_dev_event(hdev, HCI_DEV_SUSPEND);
	return ret;
}
@@ -2864,7 +2780,9 @@ int hci_resume_dev(struct hci_dev *hdev)
	if (mgmt_powering_down(hdev))
		return 0;

	ret = hci_change_suspend_state(hdev, BT_RUNNING);
	hci_req_sync_lock(hdev);
	ret = hci_resume_sync(hdev);
	hci_req_sync_unlock(hdev);

	mgmt_resuming(hdev, hdev->wake_reason, &hdev->wake_addr,
		      hdev->wake_addr_type);
+53 −18
Original line number Diff line number Diff line
@@ -2414,9 +2414,14 @@ static void hci_cs_exit_sniff_mode(struct hci_dev *hdev, __u8 status)
static void hci_cs_disconnect(struct hci_dev *hdev, u8 status)
{
	struct hci_cp_disconnect *cp;
	struct hci_conn_params *params;
	struct hci_conn *conn;
	bool mgmt_conn;

	if (!status)
	/* Wait for HCI_EV_DISCONN_COMPLETE if status 0x00 and not suspended
	 * otherwise cleanup the connection immediately.
	 */
	if (!status && !hdev->suspended)
		return;

	cp = hci_sent_cmd_data(hdev, HCI_OP_DISCONNECT);
@@ -2426,7 +2431,10 @@ static void hci_cs_disconnect(struct hci_dev *hdev, u8 status)
	hci_dev_lock(hdev);

	conn = hci_conn_hash_lookup_handle(hdev, __le16_to_cpu(cp->handle));
	if (conn) {
	if (!conn)
		goto unlock;

	if (status) {
		mgmt_disconnect_failed(hdev, &conn->dst, conn->type,
				       conn->dst_type, status);

@@ -2435,14 +2443,48 @@ static void hci_cs_disconnect(struct hci_dev *hdev, u8 status)
			hci_enable_advertising(hdev);
		}

		goto done;
	}

	mgmt_conn = test_and_clear_bit(HCI_CONN_MGMT_CONNECTED, &conn->flags);

	if (conn->type == ACL_LINK) {
		if (test_bit(HCI_CONN_FLUSH_KEY, &conn->flags))
			hci_remove_link_key(hdev, &conn->dst);
	}

	params = hci_conn_params_lookup(hdev, &conn->dst, conn->dst_type);
	if (params) {
		switch (params->auto_connect) {
		case HCI_AUTO_CONN_LINK_LOSS:
			if (cp->reason != HCI_ERROR_CONNECTION_TIMEOUT)
				break;
			fallthrough;

		case HCI_AUTO_CONN_DIRECT:
		case HCI_AUTO_CONN_ALWAYS:
			list_del_init(&params->action);
			list_add(&params->action, &hdev->pend_le_conns);
			break;

		default:
			break;
		}
	}

	mgmt_device_disconnected(hdev, &conn->dst, conn->type, conn->dst_type,
				 cp->reason, mgmt_conn);

	hci_disconn_cfm(conn, cp->reason);

done:
	/* If the disconnection failed for any reason, the upper layer
	 * does not retry to disconnect in current implementation.
	 * Hence, we need to do some basic cleanup here and re-enable
	 * advertising if necessary.
	 */
	hci_conn_del(conn);
	}

unlock:
	hci_dev_unlock(hdev);
}

@@ -3047,14 +3089,6 @@ static void hci_disconn_complete_evt(struct hci_dev *hdev, struct sk_buff *skb)

	hci_conn_del(conn);

	/* The suspend notifier is waiting for all devices to disconnect so
	 * clear the bit from pending tasks and inform the wait queue.
	 */
	if (list_empty(&hdev->conn_hash.list) &&
	    test_and_clear_bit(SUSPEND_DISCONNECTING, hdev->suspend_tasks)) {
		wake_up(&hdev->suspend_wait_q);
	}

unlock:
	hci_dev_unlock(hdev);
}
@@ -5575,8 +5609,9 @@ static struct hci_conn *check_pending_le_conn(struct hci_dev *hdev,
	if (adv_type != LE_ADV_IND && adv_type != LE_ADV_DIRECT_IND)
		return NULL;

	/* Ignore if the device is blocked */
	if (hci_bdaddr_list_lookup(&hdev->reject_list, addr, addr_type))
	/* Ignore if the device is blocked or hdev is suspended */
	if (hci_bdaddr_list_lookup(&hdev->reject_list, addr, addr_type) ||
	    hdev->suspended)
		return NULL;

	/* Most controller will fail if we try to create new connections
Loading