diff --git a/include/net/bluetooth/hci_sync.h b/include/net/bluetooth/hci_sync.h index 6efbc2152146..ffa1481fb2e4 100644 --- a/include/net/bluetooth/hci_sync.h +++ b/include/net/bluetooth/hci_sync.h @@ -19,6 +19,10 @@ struct hci_cmd_sync_work_entry { hci_cmd_sync_work_destroy_t destroy; }; +typedef int (*hci_cmd_sync_select_t)(struct hci_dev *hdev, + const struct hci_cmd_sync_work_entry *item, + void *data); + struct adv_info; /* Function with sync suffix shall not be called with hdev->lock held as they * wait the command to complete and in the meantime an event could be received @@ -48,6 +52,8 @@ int hci_cmd_sync_submit(struct hci_dev *hdev, hci_cmd_sync_work_func_t func, void *data, hci_cmd_sync_work_destroy_t destroy); int hci_cmd_sync_queue(struct hci_dev *hdev, hci_cmd_sync_work_func_t func, void *data, hci_cmd_sync_work_destroy_t destroy); +int hci_cmd_sync_remove(struct hci_dev *hdev, + hci_cmd_sync_select_t func, void *data); int hci_update_eir_sync(struct hci_dev *hdev); int hci_update_class_sync(struct hci_dev *hdev); diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c index 3735764b3169..d66d80bc6757 100644 --- a/net/bluetooth/hci_conn.c +++ b/net/bluetooth/hci_conn.c @@ -68,6 +68,9 @@ static const struct sco_param esco_param_msbc[] = { { EDR_ESCO_MASK | ESCO_EV3, 0x0008, 0x02 }, /* T1 */ }; +static void hci_abort_conn_cancel(struct hci_conn *conn); +static int __hci_abort_conn(struct hci_conn *conn, u8 reason); + /* This function requires the caller holds hdev->lock */ static void hci_connect_le_scan_cleanup(struct hci_conn *conn, u8 status) { @@ -631,7 +634,7 @@ static void hci_conn_timeout(struct work_struct *work) if (refcnt > 0) return; - hci_abort_conn(conn, hci_proto_disconn_ind(conn)); + __hci_abort_conn(conn, hci_proto_disconn_ind(conn)); } /* Enter sniff mode */ @@ -717,7 +720,7 @@ static void le_conn_timeout(struct work_struct *work) return; } - hci_abort_conn(conn, HCI_ERROR_REMOTE_USER_TERM); + __hci_abort_conn(conn, HCI_ERROR_REMOTE_USER_TERM); } struct iso_cig_params { @@ -1176,6 +1179,8 @@ void hci_conn_del(struct hci_conn *conn) skb_queue_purge(&conn->data_q); + hci_abort_conn_cancel(conn); + /* Remove the connection from the list and cleanup its remaining * state. This is a separate function since for some cases like * BT_CONNECT_SCAN we *only* want the cleanup part without the @@ -2959,7 +2964,7 @@ static int abort_conn_sync(struct hci_dev *hdev, void *data) return hci_abort_conn_sync(hdev, conn, conn->abort_reason); } -int hci_abort_conn(struct hci_conn *conn, u8 reason) +static int __hci_abort_conn(struct hci_conn *conn, u8 reason) { struct hci_dev *hdev = conn->hdev; @@ -2993,3 +2998,34 @@ int hci_abort_conn(struct hci_conn *conn, u8 reason) return hci_cmd_sync_queue(hdev, abort_conn_sync, UINT_PTR(conn->handle), NULL); } + +int hci_abort_conn(struct hci_conn *conn, u8 reason) +{ + struct hci_dev *hdev = conn->hdev; + + lockdep_assert_held(&hdev->lock); + return __hci_abort_conn(conn, reason); +} + +static int abort_conn_remove(struct hci_dev *hdev, + const struct hci_cmd_sync_work_entry *entry, + void *data) +{ + if (entry->func == abort_conn_sync && entry->data == data) + return -ECANCELED; + + return 0; +} + +static void hci_abort_conn_cancel(struct hci_conn *conn) +{ + struct hci_dev *hdev = conn->hdev; + + lockdep_assert_held(&hdev->lock); + + /* Disallow queuing more aborts and remove entries from queue. */ + if (!conn->abort_reason) + conn->abort_reason = HCI_ERROR_UNSPECIFIED; + + hci_cmd_sync_remove(hdev, abort_conn_remove, UINT_PTR(conn->handle)); +} diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index 65601aa52e0d..5a26150f0fc4 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -3417,23 +3417,18 @@ static void hci_link_tx_to(struct hci_dev *hdev, __u8 type) bt_dev_err(hdev, "link tx timeout"); - rcu_read_lock(); + hci_dev_lock(hdev); /* Kill stalled connections */ - list_for_each_entry_rcu(c, &h->list, list) { + list_for_each_entry(c, &h->list, list) { if (c->type == type && c->sent) { bt_dev_err(hdev, "killing stalled connection %pMR", &c->dst); - /* hci_disconnect might sleep, so, we have to release - * the RCU read lock before calling it. - */ - rcu_read_unlock(); hci_disconnect(c, HCI_ERROR_REMOTE_USER_TERM); - rcu_read_lock(); } } - rcu_read_unlock(); + hci_dev_unlock(hdev); } static struct hci_chan *hci_chan_sent(struct hci_dev *hdev, __u8 type, diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index c6f57af88bfd..9bfdb6307137 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -634,6 +634,17 @@ void hci_cmd_sync_init(struct hci_dev *hdev) INIT_DELAYED_WORK(&hdev->adv_instance_expire, adv_timeout_expire); } +static void __hci_cmd_sync_cancel_entry(struct hci_dev *hdev, + struct hci_cmd_sync_work_entry *entry, + int err) +{ + if (entry->destroy) + entry->destroy(hdev, entry->data, err); + + list_del(&entry->list); + kfree(entry); +} + void hci_cmd_sync_clear(struct hci_dev *hdev) { struct hci_cmd_sync_work_entry *entry, *tmp; @@ -642,13 +653,8 @@ void hci_cmd_sync_clear(struct hci_dev *hdev) cancel_work_sync(&hdev->reenable_adv_work); mutex_lock(&hdev->cmd_sync_work_lock); - list_for_each_entry_safe(entry, tmp, &hdev->cmd_sync_work_list, list) { - if (entry->destroy) - entry->destroy(hdev, entry->data, -ECANCELED); - - list_del(&entry->list); - kfree(entry); - } + list_for_each_entry_safe(entry, tmp, &hdev->cmd_sync_work_list, list) + __hci_cmd_sync_cancel_entry(hdev, entry, -ECANCELED); mutex_unlock(&hdev->cmd_sync_work_lock); } @@ -681,6 +687,42 @@ void hci_cmd_sync_cancel(struct hci_dev *hdev, int err) } EXPORT_SYMBOL(hci_cmd_sync_cancel); +int __hci_cmd_sync_foreach(struct hci_dev *hdev, bool cancel, + hci_cmd_sync_select_t func, void *data) +{ + struct hci_cmd_sync_work_entry *entry, *tmp; + int ret = 0; + + list_for_each_entry_safe(entry, tmp, &hdev->cmd_sync_work_list, list) { + int err; + + err = func(hdev, entry, data); + if (!err) + continue; + + ret = err; + if (cancel) + __hci_cmd_sync_cancel_entry(hdev, entry, err); + else + break; + } + + return ret; +} + +int hci_cmd_sync_remove(struct hci_dev *hdev, hci_cmd_sync_select_t func, + void *data) +{ + int err; + + mutex_lock(&hdev->cmd_sync_work_lock); + err = __hci_cmd_sync_foreach(hdev, true, func, data); + mutex_unlock(&hdev->cmd_sync_work_lock); + + return err; +} +EXPORT_SYMBOL(hci_cmd_sync_remove); + /* Submit HCI command to be run in as cmd_sync_work: * * - hdev must _not_ be unregistered @@ -5383,6 +5425,9 @@ int hci_abort_conn_sync(struct hci_dev *hdev, struct hci_conn *conn, u8 reason) bool disconnect = false; struct hci_conn *c; + if (WARN_ON(!reason)) + return -EINVAL; + switch (conn->state) { case BT_CONNECTED: case BT_CONFIG: @@ -5434,6 +5479,9 @@ static int hci_disconnect_all_sync(struct hci_dev *hdev, u8 reason) struct list_head *head = &hdev->conn_hash.list; struct hci_conn *conn; + if (!reason) + return -EINVAL; + rcu_read_lock(); while ((conn = list_first_or_null_rcu(head, struct hci_conn, list))) { /* Make sure the connection is not freed while unlocking */