@@ -2845,12 +2845,7 @@ u32 hci_conn_get_phy(struct hci_conn *conn)
static int abort_conn_sync(struct hci_dev *hdev, void *data)
{
- struct hci_conn *conn;
- u16 handle = PTR_ERR(data);
-
- conn = hci_conn_hash_lookup_handle(hdev, handle);
- if (!conn)
- return 0;
+ struct hci_conn *conn = data;
return hci_abort_conn_sync(hdev, conn, conn->abort_reason);
}
@@ -2886,8 +2881,7 @@ int hci_abort_conn(struct hci_conn *conn, u8 reason)
}
}
- return hci_cmd_sync_queue(hdev, abort_conn_sync, ERR_PTR(conn->handle),
- NULL);
+ return hci_conn_sync_queue(conn, abort_conn_sync, conn, NULL);
}
struct hci_conn_sync_work_entry {
@@ -5407,6 +5407,8 @@ int hci_abort_conn_sync(struct hci_dev *hdev, struct hci_conn *conn, u8 reason)
{
int err;
+ lockdep_assert_held(&hdev->lock);
+
switch (conn->state) {
case BT_CONNECTED:
case BT_CONFIG:
@@ -5418,21 +5420,15 @@ int hci_abort_conn_sync(struct hci_dev *hdev, struct hci_conn *conn, u8 reason)
* or in case of LE it was still scanning so it can be cleanup
* safely.
*/
- if (err) {
- hci_dev_lock(hdev);
+ if (err && hci_conn_is_alive(hdev, conn))
hci_conn_failed(conn, err);
- hci_dev_unlock(hdev);
- }
return err;
case BT_CONNECT2:
return hci_reject_conn_sync(hdev, conn, reason);
case BT_OPEN:
/* Cleanup bises that failed to be established */
- if (test_and_clear_bit(HCI_CONN_BIG_SYNC_FAILED, &conn->flags)) {
- hci_dev_lock(hdev);
+ if (test_and_clear_bit(HCI_CONN_BIG_SYNC_FAILED, &conn->flags))
hci_conn_failed(conn, reason);
- hci_dev_unlock(hdev);
- }
break;
default:
conn->state = BT_CLOSED;
@@ -5444,16 +5440,42 @@ int hci_abort_conn_sync(struct hci_dev *hdev, struct hci_conn *conn, u8 reason)
static int hci_disconnect_all_sync(struct hci_dev *hdev, u8 reason)
{
- struct hci_conn *conn, *tmp;
- int err;
+ struct hci_conn *conn;
+ struct hci_conn **conns;
+ int err = 0;
+ size_t i, n;
- list_for_each_entry_safe(conn, tmp, &hdev->conn_hash.list, list) {
- err = hci_abort_conn_sync(hdev, conn, reason);
- if (err)
- return err;
+ hci_cmd_sync_dev_lock(hdev);
+
+ /* Make a copy of conn_hash, because hci_abort_conn_sync may release the
+ * lock and wait for events, during which the list may be mutated
+ * arbitrarily.
+ */
+ n = 0;
+ list_for_each_entry(conn, &hdev->conn_hash.list, list)
+ ++n;
+
+ conns = kvcalloc(n, sizeof(*conns), GFP_KERNEL);
+ if (!conns) {
+ err = -ENOMEM;
+ goto unlock;
}
- return 0;
+ i = 0;
+ list_for_each_entry(conn, &hdev->conn_hash.list, list)
+ conns[i++] = hci_conn_get(conn);
+
+ for (i = 0; i < n; ++i) {
+ if (!err)
+ err = hci_abort_conn_sync(hdev, conns[i], reason);
+ hci_conn_put(conns[i]);
+ }
+
+ kvfree(conns);
+
+unlock:
+ hci_cmd_sync_dev_unlock(hdev);
+ return err;
}
/* This function perform power off HCI command sequence as follows:
hci_conn_abort may run concurrently with operations that e.g. change conn handle or delete it. Similarly, hci_disconnect_all_sync iterates conn_hash without locks/RCU which is not OK. To make it correct vs locking, use hci_conn_sync_queue to hold hdev->lock and check hci_conn aliveness after waiting for events. Make iteration in hci_disconnect_all_sync safe. Since we don't have a flag for indicating whether a connection was aborted, just make a copy of the conn_hash and iterate the copy. Signed-off-by: Pauli Virtanen <pav@iki.fi> --- Notes: The iterate-over-copy is ugly, could add another hci_conn flag. Or change things so that hci_conn_get keeps the hci_conn in conn_hash so we can always continue the iteration safely as long as a reference was held. net/bluetooth/hci_conn.c | 10 ++------ net/bluetooth/hci_sync.c | 52 ++++++++++++++++++++++++++++------------ 2 files changed, 39 insertions(+), 23 deletions(-)