mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 02:30:34 +02:00 
			
		
		
		
	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
					
				
					 4 changed files with 126 additions and 0 deletions
				
			
		| 
						 | 
					@ -88,6 +88,20 @@ struct discovery_state {
 | 
				
			||||||
	unsigned long		scan_duration;
 | 
						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 hci_conn_hash {
 | 
				
			||||||
	struct list_head list;
 | 
						struct list_head list;
 | 
				
			||||||
	unsigned int     acl_num;
 | 
						unsigned int     acl_num;
 | 
				
			||||||
| 
						 | 
					@ -390,6 +404,15 @@ struct hci_dev {
 | 
				
			||||||
	void			*smp_bredr_data;
 | 
						void			*smp_bredr_data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	struct discovery_state	discovery;
 | 
						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 hci_conn_hash	conn_hash;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	struct list_head	mgmt_pending;
 | 
						struct list_head	mgmt_pending;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -31,6 +31,8 @@
 | 
				
			||||||
#include <linux/debugfs.h>
 | 
					#include <linux/debugfs.h>
 | 
				
			||||||
#include <linux/crypto.h>
 | 
					#include <linux/crypto.h>
 | 
				
			||||||
#include <linux/property.h>
 | 
					#include <linux/property.h>
 | 
				
			||||||
 | 
					#include <linux/suspend.h>
 | 
				
			||||||
 | 
					#include <linux/wait.h>
 | 
				
			||||||
#include <asm/unaligned.h>
 | 
					#include <asm/unaligned.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <net/bluetooth/bluetooth.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);
 | 
						clear_bit(HCI_RUNNING, &hdev->flags);
 | 
				
			||||||
	hci_sock_dev_event(hdev, HCI_DEV_CLOSE);
 | 
						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
 | 
						/* After this point our queues are empty
 | 
				
			||||||
	 * and no tasks are scheduled. */
 | 
						 * and no tasks are scheduled. */
 | 
				
			||||||
	hdev->close(hdev);
 | 
						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 */
 | 
					/* Alloc HCI device */
 | 
				
			||||||
struct hci_dev *hci_alloc_dev(void)
 | 
					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->tx_work, hci_tx_work);
 | 
				
			||||||
	INIT_WORK(&hdev->power_on, hci_power_on);
 | 
						INIT_WORK(&hdev->power_on, hci_power_on);
 | 
				
			||||||
	INIT_WORK(&hdev->error_reset, hci_error_reset);
 | 
						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);
 | 
						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);
 | 
						skb_queue_head_init(&hdev->raw_q);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	init_waitqueue_head(&hdev->req_wait_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->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_sock_dev_event(hdev, HCI_DEV_REG);
 | 
				
			||||||
	hci_dev_hold(hdev);
 | 
						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);
 | 
						queue_work(hdev->req_workqueue, &hdev->power_on);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return id;
 | 
						return id;
 | 
				
			||||||
| 
						 | 
					@ -3493,6 +3577,8 @@ void hci_unregister_dev(struct hci_dev *hdev)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	hci_dev_do_close(hdev);
 | 
						hci_dev_do_close(hdev);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						unregister_pm_notifier(&hdev->suspend_notifier);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (!test_bit(HCI_INIT, &hdev->flags) &&
 | 
						if (!test_bit(HCI_INIT, &hdev->flags) &&
 | 
				
			||||||
	    !hci_dev_test_flag(hdev, HCI_SETUP) &&
 | 
						    !hci_dev_test_flag(hdev, HCI_SETUP) &&
 | 
				
			||||||
	    !hci_dev_test_flag(hdev, HCI_CONFIG)) {
 | 
						    !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;
 | 
						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)
 | 
					static u8 get_cur_adv_instance_scan_rsp_len(struct hci_dev *hdev)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	u8 instance = hdev->cur_adv_instance;
 | 
						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_scan_disable(struct hci_request *req);
 | 
				
			||||||
void hci_req_add_le_passive_scan(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_reenable_advertising(struct hci_dev *hdev);
 | 
				
			||||||
void __hci_req_enable_advertising(struct hci_request *req);
 | 
					void __hci_req_enable_advertising(struct hci_request *req);
 | 
				
			||||||
void __hci_req_disable_advertising(struct hci_request *req);
 | 
					void __hci_req_disable_advertising(struct hci_request *req);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue