diff --git a/include/net/bluetooth/bluetooth.h b/include/net/bluetooth/bluetooth.h index bbefde319f95..cee6be23acc8 100644 --- a/include/net/bluetooth/bluetooth.h +++ b/include/net/bluetooth/bluetooth.h @@ -655,6 +655,7 @@ static inline bool iso_enabled(void) int mgmt_init(void); void mgmt_exit(void); void mgmt_cleanup(struct sock *sk); +void mgmt_pending_cleanup(struct hci_dev *hdev); void bt_sock_reclassify_lock(struct sock *sk, int proto); diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index 54bfeeaa0995..a9449b9e86fc 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -1683,6 +1683,7 @@ static inline struct hci_dev *hci_dev_hold(struct hci_dev *d) #define hci_dev_lock(d) mutex_lock(&d->lock) #define hci_dev_unlock(d) mutex_unlock(&d->lock) +#define hci_dev_is_locked(d) mutex_is_locked(&d->lock) #define to_hci_dev(d) container_of(d, struct hci_dev, dev) #define to_hci_conn(c) container_of(c, struct hci_conn, dev) diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index 5eb0600bbd03..d7d2e17d2aec 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -2685,10 +2685,7 @@ void hci_unregister_dev(struct hci_dev *hdev) hci_dev_unlock(hdev); } - /* mgmt_index_removed should take care of emptying the - * pending list */ - BUG_ON(!list_empty(&hdev->mgmt_pending)); - + mgmt_pending_cleanup(hdev); hci_sock_dev_event(hdev, HCI_DEV_UNREG); if (hdev->rfkill) { diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index 46b22708dfbd..9126235a1a82 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -1447,14 +1447,12 @@ static void settings_rsp(struct mgmt_pending_cmd *cmd, void *data) send_settings_rsp(cmd->sk, cmd->opcode, match->hdev); - list_del(&cmd->list); - if (match->sk == NULL) { match->sk = cmd->sk; sock_hold(match->sk); } - mgmt_pending_free(cmd); + mgmt_pending_remove(cmd); } static void cmd_status_rsp(struct mgmt_pending_cmd *cmd, void *data) @@ -2598,18 +2596,25 @@ static int mgmt_hci_cmd_sync(struct sock *sk, struct hci_dev *hdev, static bool pending_eir_or_class(struct hci_dev *hdev) { struct mgmt_pending_cmd *cmd; + bool found = false; - list_for_each_entry(cmd, &hdev->mgmt_pending, list) { + rcu_read_lock(); + + list_for_each_entry_rcu(cmd, &hdev->mgmt_pending, list) { + if (atomic_read(&cmd->deleted)) + continue; switch (cmd->opcode) { case MGMT_OP_ADD_UUID: case MGMT_OP_REMOVE_UUID: case MGMT_OP_SET_DEV_CLASS: case MGMT_OP_SET_POWERED: - return true; + found = true; + break; } } - return false; + rcu_read_unlock(); + return found; } static const u8 bluetooth_base_uuid[] = { @@ -3401,19 +3406,23 @@ static int set_io_capability(struct sock *sk, struct hci_dev *hdev, void *data, static struct mgmt_pending_cmd *find_pairing(struct hci_conn *conn) { struct hci_dev *hdev = conn->hdev; - struct mgmt_pending_cmd *cmd; + struct mgmt_pending_cmd *tmp, *cmd = NULL; - list_for_each_entry(cmd, &hdev->mgmt_pending, list) { - if (cmd->opcode != MGMT_OP_PAIR_DEVICE) - continue; + rcu_read_lock(); - if (cmd->user_data != conn) + list_for_each_entry_rcu(tmp, &hdev->mgmt_pending, list) { + if (atomic_read(&tmp->deleted)) continue; - - return cmd; + if (tmp->opcode != MGMT_OP_PAIR_DEVICE) + continue; + if (tmp->user_data != conn) + continue; + cmd = tmp; + break; } - return NULL; + rcu_read_unlock(); + return cmd; } static int pairing_complete(struct mgmt_pending_cmd *cmd, u8 status) @@ -10476,3 +10485,14 @@ void mgmt_cleanup(struct sock *sk) read_unlock(&hci_dev_list_lock); } + +void mgmt_pending_cleanup(struct hci_dev *hdev) +{ + struct mgmt_pending_cmd *cmd, *tmp; + + list_for_each_entry_safe(cmd, tmp, &hdev->mgmt_pending, list) { + BUG_ON(atomic_read(&cmd->deleted) == 0); + list_del_rcu(&cmd->list); + mgmt_pending_free(cmd); + } +} diff --git a/net/bluetooth/mgmt_util.c b/net/bluetooth/mgmt_util.c index e5ff65e424b5..978e12db969c 100644 --- a/net/bluetooth/mgmt_util.c +++ b/net/bluetooth/mgmt_util.c @@ -217,30 +217,44 @@ int mgmt_cmd_complete(struct sock *sk, u16 index, u16 cmd, u8 status, struct mgmt_pending_cmd *mgmt_pending_find(unsigned short channel, u16 opcode, struct hci_dev *hdev) { - struct mgmt_pending_cmd *cmd; + struct mgmt_pending_cmd *tmp, *cmd = NULL; + + rcu_read_lock(); - list_for_each_entry(cmd, &hdev->mgmt_pending, list) { - if (hci_sock_get_channel(cmd->sk) != channel) + list_for_each_entry_rcu(tmp, &hdev->mgmt_pending, list) { + if (atomic_read(&tmp->deleted)) + continue; + if (hci_sock_get_channel(tmp->sk) != channel) continue; - if (cmd->opcode == opcode) - return cmd; + if (tmp->opcode == opcode) { + cmd = tmp; + break; + } } - return NULL; + rcu_read_unlock(); + return cmd; } void mgmt_pending_foreach(u16 opcode, struct hci_dev *hdev, void (*cb)(struct mgmt_pending_cmd *cmd, void *data), void *data) { - struct mgmt_pending_cmd *cmd, *tmp; + struct mgmt_pending_cmd *cmd; + + rcu_read_lock(); - list_for_each_entry_safe(cmd, tmp, &hdev->mgmt_pending, list) { + list_for_each_entry_rcu(cmd, &hdev->mgmt_pending, list) { + if (atomic_read(&cmd->deleted)) + continue; if (opcode > 0 && cmd->opcode != opcode) continue; - + rcu_read_unlock(); cb(cmd, data); + rcu_read_lock(); } + + rcu_read_unlock(); } struct mgmt_pending_cmd *mgmt_pending_new(struct sock *sk, u16 opcode, @@ -270,17 +284,34 @@ struct mgmt_pending_cmd *mgmt_pending_new(struct sock *sk, u16 opcode, return cmd; } +static void mgmt_pending_delayed_free(struct rcu_head *rcu) +{ + struct mgmt_pending_cmd *cmd = + container_of(rcu, struct mgmt_pending_cmd, head); + kfree(cmd->param); + kfree(cmd); +} + struct mgmt_pending_cmd *mgmt_pending_add(struct sock *sk, u16 opcode, struct hci_dev *hdev, void *data, u16 len) { - struct mgmt_pending_cmd *cmd; + struct mgmt_pending_cmd *cmd, *old, *tmp; cmd = mgmt_pending_new(sk, opcode, hdev, data, len); if (!cmd) return NULL; - list_add_tail(&cmd->list, &hdev->mgmt_pending); + BUG_ON(!hci_dev_is_locked(hdev)); + + list_for_each_entry_safe(old, tmp, &hdev->mgmt_pending, list) + if (atomic_read(&old->deleted)) { + list_del_rcu(&old->list); + sock_put(old->sk); + call_rcu(&old->head, mgmt_pending_delayed_free); + } + + list_add_tail_rcu(&cmd->list, &hdev->mgmt_pending); return cmd; } @@ -294,8 +325,7 @@ void mgmt_pending_free(struct mgmt_pending_cmd *cmd) void mgmt_pending_remove(struct mgmt_pending_cmd *cmd) { - list_del(&cmd->list); - mgmt_pending_free(cmd); + atomic_set(&cmd->deleted, 1); } void mgmt_mesh_foreach(struct hci_dev *hdev, diff --git a/net/bluetooth/mgmt_util.h b/net/bluetooth/mgmt_util.h index f2ba994ab1d8..5e681bc74220 100644 --- a/net/bluetooth/mgmt_util.h +++ b/net/bluetooth/mgmt_util.h @@ -32,6 +32,8 @@ struct mgmt_mesh_tx { struct mgmt_pending_cmd { struct list_head list; + struct rcu_head head; + atomic_t deleted; u16 opcode; int index; void *param; @@ -65,6 +67,7 @@ struct mgmt_pending_cmd *mgmt_pending_new(struct sock *sk, u16 opcode, void *data, u16 len); void mgmt_pending_free(struct mgmt_pending_cmd *cmd); void mgmt_pending_remove(struct mgmt_pending_cmd *cmd); +void mgmt_pending_cleanup(struct hci_dev *hdev); void mgmt_mesh_foreach(struct hci_dev *hdev, void (*cb)(struct mgmt_mesh_tx *mesh_tx, void *data), void *data, struct sock *sk);