Bluetooth: Handle PM_SUSPEND_PREPARE and PM_POST_SUSPEND
Register for PM_SUSPEND_PREPARE and PM_POST_SUSPEND to make sure the Bluetooth controller is prepared correctly for suspend/resume. Implement the registration, scheduling and task handling portions only in this patch. Signed-off-by: Abhishek Pandit-Subedi <abhishekpandit@chromium.org> Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
This commit is contained in:
		
							parent
							
								
									72da7b2cca
								
							
						
					
					
						commit
						9952d90ea2
					
				| @ -88,6 +88,20 @@ struct discovery_state { | ||||
| 	unsigned long		scan_duration; | ||||
| }; | ||||
| 
 | ||||
| #define SUSPEND_NOTIFIER_TIMEOUT	msecs_to_jiffies(2000) /* 2 seconds */ | ||||
| 
 | ||||
| enum suspend_tasks { | ||||
| 	SUSPEND_POWERING_DOWN, | ||||
| 
 | ||||
| 	SUSPEND_PREPARE_NOTIFIER, | ||||
| 	__SUSPEND_NUM_TASKS | ||||
| }; | ||||
| 
 | ||||
| enum suspended_state { | ||||
| 	BT_RUNNING = 0, | ||||
| 	BT_SUSPENDED, | ||||
| }; | ||||
| 
 | ||||
| struct hci_conn_hash { | ||||
| 	struct list_head list; | ||||
| 	unsigned int     acl_num; | ||||
| @ -390,6 +404,15 @@ struct hci_dev { | ||||
| 	void			*smp_bredr_data; | ||||
| 
 | ||||
| 	struct discovery_state	discovery; | ||||
| 
 | ||||
| 	struct notifier_block	suspend_notifier; | ||||
| 	struct work_struct	suspend_prepare; | ||||
| 	enum suspended_state	suspend_state_next; | ||||
| 	enum suspended_state	suspend_state; | ||||
| 
 | ||||
| 	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; | ||||
|  | ||||
| @ -31,6 +31,8 @@ | ||||
| #include <linux/debugfs.h> | ||||
| #include <linux/crypto.h> | ||||
| #include <linux/property.h> | ||||
| #include <linux/suspend.h> | ||||
| #include <linux/wait.h> | ||||
| #include <asm/unaligned.h> | ||||
| 
 | ||||
| #include <net/bluetooth/bluetooth.h> | ||||
| @ -1787,6 +1789,9 @@ int hci_dev_do_close(struct hci_dev *hdev) | ||||
| 	clear_bit(HCI_RUNNING, &hdev->flags); | ||||
| 	hci_sock_dev_event(hdev, HCI_DEV_CLOSE); | ||||
| 
 | ||||
| 	if (test_and_clear_bit(SUSPEND_POWERING_DOWN, hdev->suspend_tasks)) | ||||
| 		wake_up(&hdev->suspend_wait_q); | ||||
| 
 | ||||
| 	/* After this point our queues are empty
 | ||||
| 	 * and no tasks are scheduled. */ | ||||
| 	hdev->close(hdev); | ||||
| @ -3264,6 +3269,78 @@ void hci_copy_identity_address(struct hci_dev *hdev, bdaddr_t *bdaddr, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| 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_dbg(hdev, "Timed out waiting for suspend"); | ||||
| 		for (i = 0; i < __SUSPEND_NUM_TASKS; ++i) { | ||||
| 			if (test_bit(i, hdev->suspend_tasks)) | ||||
| 				bt_dev_dbg(hdev, "Bit %d is set", 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_suspend_notifier(struct notifier_block *nb, unsigned long action, | ||||
| 				void *data) | ||||
| { | ||||
| 	struct hci_dev *hdev = | ||||
| 		container_of(nb, struct hci_dev, suspend_notifier); | ||||
| 	int ret = 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 notifier should only act on events when powered. */ | ||||
| 	if (!hdev_is_powered(hdev)) | ||||
| 		goto done; | ||||
| 
 | ||||
| 	if (action == PM_SUSPEND_PREPARE) { | ||||
| 		hdev->suspend_state_next = BT_SUSPENDED; | ||||
| 		set_bit(SUSPEND_PREPARE_NOTIFIER, hdev->suspend_tasks); | ||||
| 		queue_work(hdev->req_workqueue, &hdev->suspend_prepare); | ||||
| 
 | ||||
| 		ret = hci_suspend_wait_event(hdev); | ||||
| 	} else if (action == PM_POST_SUSPEND) { | ||||
| 		hdev->suspend_state_next = BT_RUNNING; | ||||
| 		set_bit(SUSPEND_PREPARE_NOTIFIER, hdev->suspend_tasks); | ||||
| 		queue_work(hdev->req_workqueue, &hdev->suspend_prepare); | ||||
| 
 | ||||
| 		ret = hci_suspend_wait_event(hdev); | ||||
| 	} | ||||
| 
 | ||||
| done: | ||||
| 	return ret ? notifier_from_errno(-EBUSY) : NOTIFY_STOP; | ||||
| } | ||||
| /* Alloc HCI device */ | ||||
| struct hci_dev *hci_alloc_dev(void) | ||||
| { | ||||
| @ -3341,6 +3418,7 @@ struct hci_dev *hci_alloc_dev(void) | ||||
| 	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); | ||||
| 
 | ||||
| 	INIT_DELAYED_WORK(&hdev->power_off, hci_power_off); | ||||
| 
 | ||||
| @ -3349,6 +3427,7 @@ struct hci_dev *hci_alloc_dev(void) | ||||
| 	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); | ||||
| 
 | ||||
| @ -3460,6 +3539,11 @@ int hci_register_dev(struct hci_dev *hdev) | ||||
| 	hci_sock_dev_event(hdev, HCI_DEV_REG); | ||||
| 	hci_dev_hold(hdev); | ||||
| 
 | ||||
| 	hdev->suspend_notifier.notifier_call = hci_suspend_notifier; | ||||
| 	error = register_pm_notifier(&hdev->suspend_notifier); | ||||
| 	if (error) | ||||
| 		goto err_wqueue; | ||||
| 
 | ||||
| 	queue_work(hdev->req_workqueue, &hdev->power_on); | ||||
| 
 | ||||
| 	return id; | ||||
| @ -3493,6 +3577,8 @@ void hci_unregister_dev(struct hci_dev *hdev) | ||||
| 
 | ||||
| 	hci_dev_do_close(hdev); | ||||
| 
 | ||||
| 	unregister_pm_notifier(&hdev->suspend_notifier); | ||||
| 
 | ||||
| 	if (!test_bit(HCI_INIT, &hdev->flags) && | ||||
| 	    !hci_dev_test_flag(hdev, HCI_SETUP) && | ||||
| 	    !hci_dev_test_flag(hdev, HCI_CONFIG)) { | ||||
|  | ||||
| @ -918,6 +918,21 @@ static u8 get_adv_instance_scan_rsp_len(struct hci_dev *hdev, u8 instance) | ||||
| 	return adv_instance->scan_rsp_len; | ||||
| } | ||||
| 
 | ||||
| /* Call with hci_dev_lock */ | ||||
| void hci_req_prepare_suspend(struct hci_dev *hdev, enum suspended_state next) | ||||
| { | ||||
| 	if (next == hdev->suspend_state) { | ||||
| 		bt_dev_dbg(hdev, "Same state before and after: %d", next); | ||||
| 		goto done; | ||||
| 	} | ||||
| 
 | ||||
| 	hdev->suspend_state = next; | ||||
| 
 | ||||
| done: | ||||
| 	clear_bit(SUSPEND_PREPARE_NOTIFIER, hdev->suspend_tasks); | ||||
| 	wake_up(&hdev->suspend_wait_q); | ||||
| } | ||||
| 
 | ||||
| static u8 get_cur_adv_instance_scan_rsp_len(struct hci_dev *hdev) | ||||
| { | ||||
| 	u8 instance = hdev->cur_adv_instance; | ||||
|  | ||||
| @ -68,6 +68,8 @@ void __hci_req_update_eir(struct hci_request *req); | ||||
| void hci_req_add_le_scan_disable(struct hci_request *req); | ||||
| void hci_req_add_le_passive_scan(struct hci_request *req); | ||||
| 
 | ||||
| void hci_req_prepare_suspend(struct hci_dev *hdev, enum suspended_state next); | ||||
| 
 | ||||
| void hci_req_reenable_advertising(struct hci_dev *hdev); | ||||
| void __hci_req_enable_advertising(struct hci_request *req); | ||||
| void __hci_req_disable_advertising(struct hci_request *req); | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user