aboutsummaryrefslogtreecommitdiffstats
path: root/net/bluetooth/mgmt.c
diff options
context:
space:
mode:
authorJohan Hedberg <johan.hedberg@intel.com>2014-02-24 07:52:22 -0500
committerMarcel Holtmann <marcel@holtmann.org>2014-02-24 14:10:36 -0500
commit8b064a3ad377c016a17e74f676e7a204c2b8c9f2 (patch)
treee7f68ab10e78959fd48965f13481927fb42a3ece /net/bluetooth/mgmt.c
parent7c4cfab8082f1398dc7bc091166dd302a44b015b (diff)
Bluetooth: Clean up HCI state when doing power off
To be friendly to user space and to behave well with controllers that lack a proper internal power off procedure we should try to clean up as much state as possible before requesting the HCI driver to power off. This patch updates the power off procedure that's triggered by mgmt_set_powered to clean any scan modes, stop LE scanning and advertising and to disconnect any open connections. The asynchronous cleanup procedure uses the HCI request framework, however since HCI_Disconnect is only covered until its Command Status event we need some extra tracking/waiting of disconnections. This is done by monitoring when hci_conn_count() indicates that there are no more connections. Signed-off-by: Johan Hedberg <johan.hedberg@intel.com> Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
Diffstat (limited to 'net/bluetooth/mgmt.c')
-rw-r--r--net/bluetooth/mgmt.c70
1 files changed, 66 insertions, 4 deletions
diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c
index 610ac32e797b..25b8b278debd 100644
--- a/net/bluetooth/mgmt.c
+++ b/net/bluetooth/mgmt.c
@@ -1026,6 +1026,49 @@ static int send_settings_rsp(struct sock *sk, u16 opcode, struct hci_dev *hdev)
1026 sizeof(settings)); 1026 sizeof(settings));
1027} 1027}
1028 1028
1029static void clean_up_hci_complete(struct hci_dev *hdev, u8 status)
1030{
1031 BT_DBG("%s status 0x%02x", hdev->name, status);
1032
1033 if (hci_conn_count(hdev) == 0)
1034 queue_work(hdev->req_workqueue, &hdev->power_off.work);
1035}
1036
1037static int clean_up_hci_state(struct hci_dev *hdev)
1038{
1039 struct hci_request req;
1040 struct hci_conn *conn;
1041
1042 hci_req_init(&req, hdev);
1043
1044 if (test_bit(HCI_ISCAN, &hdev->flags) ||
1045 test_bit(HCI_PSCAN, &hdev->flags)) {
1046 u8 scan = 0x00;
1047 hci_req_add(&req, HCI_OP_WRITE_SCAN_ENABLE, 1, &scan);
1048 }
1049
1050 if (test_bit(HCI_ADVERTISING, &hdev->dev_flags))
1051 disable_advertising(&req);
1052
1053 if (test_bit(HCI_LE_SCAN, &hdev->dev_flags)) {
1054 struct hci_cp_le_set_scan_enable cp;
1055
1056 memset(&cp, 0, sizeof(cp));
1057 cp.enable = LE_SCAN_DISABLE;
1058 hci_req_add(&req, HCI_OP_LE_SET_SCAN_ENABLE, sizeof(cp), &cp);
1059 }
1060
1061 list_for_each_entry(conn, &hdev->conn_hash.list, list) {
1062 struct hci_cp_disconnect dc;
1063
1064 dc.handle = cpu_to_le16(conn->handle);
1065 dc.reason = 0x15; /* Terminated due to Power Off */
1066 hci_req_add(&req, HCI_OP_DISCONNECT, sizeof(dc), &dc);
1067 }
1068
1069 return hci_req_run(&req, clean_up_hci_complete);
1070}
1071
1029static int set_powered(struct sock *sk, struct hci_dev *hdev, void *data, 1072static int set_powered(struct sock *sk, struct hci_dev *hdev, void *data,
1030 u16 len) 1073 u16 len)
1031{ 1074{
@@ -1069,12 +1112,19 @@ static int set_powered(struct sock *sk, struct hci_dev *hdev, void *data,
1069 goto failed; 1112 goto failed;
1070 } 1113 }
1071 1114
1072 if (cp->val) 1115 if (cp->val) {
1073 queue_work(hdev->req_workqueue, &hdev->power_on); 1116 queue_work(hdev->req_workqueue, &hdev->power_on);
1074 else 1117 err = 0;
1075 queue_work(hdev->req_workqueue, &hdev->power_off.work); 1118 } else {
1119 /* Disconnect connections, stop scans, etc */
1120 err = clean_up_hci_state(hdev);
1076 1121
1077 err = 0; 1122 /* ENODATA means there were no HCI commands queued */
1123 if (err == -ENODATA) {
1124 queue_work(hdev->req_workqueue, &hdev->power_off.work);
1125 err = 0;
1126 }
1127 }
1078 1128
1079failed: 1129failed:
1080 hci_dev_unlock(hdev); 1130 hci_dev_unlock(hdev);
@@ -5028,8 +5078,20 @@ void mgmt_device_disconnected(struct hci_dev *hdev, bdaddr_t *bdaddr,
5028 bool mgmt_connected) 5078 bool mgmt_connected)
5029{ 5079{
5030 struct mgmt_ev_device_disconnected ev; 5080 struct mgmt_ev_device_disconnected ev;
5081 struct pending_cmd *power_off;
5031 struct sock *sk = NULL; 5082 struct sock *sk = NULL;
5032 5083
5084 power_off = mgmt_pending_find(MGMT_OP_SET_POWERED, hdev);
5085 if (power_off) {
5086 struct mgmt_mode *cp = power_off->param;
5087
5088 /* The connection is still in hci_conn_hash so test for 1
5089 * instead of 0 to know if this is the last one.
5090 */
5091 if (!cp->val && hci_conn_count(hdev) == 1)
5092 queue_work(hdev->req_workqueue, &hdev->power_off.work);
5093 }
5094
5033 if (!mgmt_connected) 5095 if (!mgmt_connected)
5034 return; 5096 return;
5035 5097