Bluetooth: hci_conn: Consolidate code for aborting connections
This consolidates code for aborting connections using hci_cmd_sync_queue so it is synchronized with other threads, but because of the fact that some commands may block the cmd_sync_queue while waiting specific events this attempt to cancel those requests by using hci_cmd_sync_cancel. Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
This commit is contained in:
parent
0b70151328
commit
a13f316e90
4 changed files with 50 additions and 150 deletions
|
@ -739,6 +739,7 @@ struct hci_conn {
|
||||||
unsigned long flags;
|
unsigned long flags;
|
||||||
|
|
||||||
enum conn_reasons conn_reason;
|
enum conn_reasons conn_reason;
|
||||||
|
__u8 abort_reason;
|
||||||
|
|
||||||
__u32 clock;
|
__u32 clock;
|
||||||
__u16 clock_accuracy;
|
__u16 clock_accuracy;
|
||||||
|
@ -758,7 +759,6 @@ struct hci_conn {
|
||||||
struct delayed_work auto_accept_work;
|
struct delayed_work auto_accept_work;
|
||||||
struct delayed_work idle_work;
|
struct delayed_work idle_work;
|
||||||
struct delayed_work le_conn_timeout;
|
struct delayed_work le_conn_timeout;
|
||||||
struct work_struct le_scan_cleanup;
|
|
||||||
|
|
||||||
struct device dev;
|
struct device dev;
|
||||||
struct dentry *debugfs;
|
struct dentry *debugfs;
|
||||||
|
|
|
@ -178,57 +178,6 @@ static void hci_conn_cleanup(struct hci_conn *conn)
|
||||||
hci_conn_put(conn);
|
hci_conn_put(conn);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void le_scan_cleanup(struct work_struct *work)
|
|
||||||
{
|
|
||||||
struct hci_conn *conn = container_of(work, struct hci_conn,
|
|
||||||
le_scan_cleanup);
|
|
||||||
struct hci_dev *hdev = conn->hdev;
|
|
||||||
struct hci_conn *c = NULL;
|
|
||||||
|
|
||||||
BT_DBG("%s hcon %p", hdev->name, conn);
|
|
||||||
|
|
||||||
hci_dev_lock(hdev);
|
|
||||||
|
|
||||||
/* Check that the hci_conn is still around */
|
|
||||||
rcu_read_lock();
|
|
||||||
list_for_each_entry_rcu(c, &hdev->conn_hash.list, list) {
|
|
||||||
if (c == conn)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
rcu_read_unlock();
|
|
||||||
|
|
||||||
if (c == conn) {
|
|
||||||
hci_connect_le_scan_cleanup(conn, 0x00);
|
|
||||||
hci_conn_cleanup(conn);
|
|
||||||
}
|
|
||||||
|
|
||||||
hci_dev_unlock(hdev);
|
|
||||||
hci_dev_put(hdev);
|
|
||||||
hci_conn_put(conn);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void hci_connect_le_scan_remove(struct hci_conn *conn)
|
|
||||||
{
|
|
||||||
BT_DBG("%s hcon %p", conn->hdev->name, conn);
|
|
||||||
|
|
||||||
/* We can't call hci_conn_del/hci_conn_cleanup here since that
|
|
||||||
* could deadlock with another hci_conn_del() call that's holding
|
|
||||||
* hci_dev_lock and doing cancel_delayed_work_sync(&conn->disc_work).
|
|
||||||
* Instead, grab temporary extra references to the hci_dev and
|
|
||||||
* hci_conn and perform the necessary cleanup in a separate work
|
|
||||||
* callback.
|
|
||||||
*/
|
|
||||||
|
|
||||||
hci_dev_hold(conn->hdev);
|
|
||||||
hci_conn_get(conn);
|
|
||||||
|
|
||||||
/* Even though we hold a reference to the hdev, many other
|
|
||||||
* things might get cleaned up meanwhile, including the hdev's
|
|
||||||
* own workqueue, so we can't use that for scheduling.
|
|
||||||
*/
|
|
||||||
schedule_work(&conn->le_scan_cleanup);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void hci_acl_create_connection(struct hci_conn *conn)
|
static void hci_acl_create_connection(struct hci_conn *conn)
|
||||||
{
|
{
|
||||||
struct hci_dev *hdev = conn->hdev;
|
struct hci_dev *hdev = conn->hdev;
|
||||||
|
@ -679,13 +628,6 @@ static void hci_conn_timeout(struct work_struct *work)
|
||||||
if (refcnt > 0)
|
if (refcnt > 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
/* LE connections in scanning state need special handling */
|
|
||||||
if (conn->state == BT_CONNECT && conn->type == LE_LINK &&
|
|
||||||
test_bit(HCI_CONN_SCANNING, &conn->flags)) {
|
|
||||||
hci_connect_le_scan_remove(conn);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
hci_abort_conn(conn, hci_proto_disconn_ind(conn));
|
hci_abort_conn(conn, hci_proto_disconn_ind(conn));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1066,7 +1008,6 @@ struct hci_conn *hci_conn_add(struct hci_dev *hdev, int type, bdaddr_t *dst,
|
||||||
INIT_DELAYED_WORK(&conn->auto_accept_work, hci_conn_auto_accept);
|
INIT_DELAYED_WORK(&conn->auto_accept_work, hci_conn_auto_accept);
|
||||||
INIT_DELAYED_WORK(&conn->idle_work, hci_conn_idle);
|
INIT_DELAYED_WORK(&conn->idle_work, hci_conn_idle);
|
||||||
INIT_DELAYED_WORK(&conn->le_conn_timeout, le_conn_timeout);
|
INIT_DELAYED_WORK(&conn->le_conn_timeout, le_conn_timeout);
|
||||||
INIT_WORK(&conn->le_scan_cleanup, le_scan_cleanup);
|
|
||||||
|
|
||||||
atomic_set(&conn->refcnt, 0);
|
atomic_set(&conn->refcnt, 0);
|
||||||
|
|
||||||
|
@ -2888,81 +2829,46 @@ u32 hci_conn_get_phy(struct hci_conn *conn)
|
||||||
return phys;
|
return phys;
|
||||||
}
|
}
|
||||||
|
|
||||||
int hci_abort_conn(struct hci_conn *conn, u8 reason)
|
static int abort_conn_sync(struct hci_dev *hdev, void *data)
|
||||||
{
|
{
|
||||||
int r = 0;
|
struct hci_conn *conn;
|
||||||
|
u16 handle = PTR_ERR(data);
|
||||||
|
|
||||||
if (test_and_set_bit(HCI_CONN_CANCEL, &conn->flags))
|
conn = hci_conn_hash_lookup_handle(hdev, handle);
|
||||||
|
if (!conn)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
switch (conn->state) {
|
return hci_abort_conn_sync(hdev, conn, conn->abort_reason);
|
||||||
case BT_CONNECTED:
|
}
|
||||||
case BT_CONFIG:
|
|
||||||
if (conn->type == AMP_LINK) {
|
|
||||||
struct hci_cp_disconn_phy_link cp;
|
|
||||||
|
|
||||||
cp.phy_handle = HCI_PHY_HANDLE(conn->handle);
|
int hci_abort_conn(struct hci_conn *conn, u8 reason)
|
||||||
cp.reason = reason;
|
{
|
||||||
r = hci_send_cmd(conn->hdev, HCI_OP_DISCONN_PHY_LINK,
|
struct hci_dev *hdev = conn->hdev;
|
||||||
sizeof(cp), &cp);
|
|
||||||
} else {
|
|
||||||
struct hci_cp_disconnect dc;
|
|
||||||
|
|
||||||
dc.handle = cpu_to_le16(conn->handle);
|
/* If abort_reason has already been set it means the connection is
|
||||||
dc.reason = reason;
|
* already being aborted so don't attempt to overwrite it.
|
||||||
r = hci_send_cmd(conn->hdev, HCI_OP_DISCONNECT,
|
*/
|
||||||
sizeof(dc), &dc);
|
if (conn->abort_reason)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
bt_dev_dbg(hdev, "handle 0x%2.2x reason 0x%2.2x", conn->handle, reason);
|
||||||
|
|
||||||
|
conn->abort_reason = reason;
|
||||||
|
|
||||||
|
/* If the connection is pending check the command opcode since that
|
||||||
|
* might be blocking on hci_cmd_sync_work while waiting its respective
|
||||||
|
* event so we need to hci_cmd_sync_cancel to cancel it.
|
||||||
|
*/
|
||||||
|
if (conn->state == BT_CONNECT && hdev->req_status == HCI_REQ_PEND) {
|
||||||
|
switch (hci_skb_event(hdev->sent_cmd)) {
|
||||||
|
case HCI_EV_LE_CONN_COMPLETE:
|
||||||
|
case HCI_EV_LE_ENHANCED_CONN_COMPLETE:
|
||||||
|
case HCI_EVT_LE_CIS_ESTABLISHED:
|
||||||
|
hci_cmd_sync_cancel(hdev, -ECANCELED);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
conn->state = BT_DISCONN;
|
|
||||||
|
|
||||||
break;
|
|
||||||
case BT_CONNECT:
|
|
||||||
if (conn->type == LE_LINK) {
|
|
||||||
if (test_bit(HCI_CONN_SCANNING, &conn->flags))
|
|
||||||
break;
|
|
||||||
r = hci_send_cmd(conn->hdev,
|
|
||||||
HCI_OP_LE_CREATE_CONN_CANCEL, 0, NULL);
|
|
||||||
} else if (conn->type == ACL_LINK) {
|
|
||||||
if (conn->hdev->hci_ver < BLUETOOTH_VER_1_2)
|
|
||||||
break;
|
|
||||||
r = hci_send_cmd(conn->hdev,
|
|
||||||
HCI_OP_CREATE_CONN_CANCEL,
|
|
||||||
6, &conn->dst);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case BT_CONNECT2:
|
|
||||||
if (conn->type == ACL_LINK) {
|
|
||||||
struct hci_cp_reject_conn_req rej;
|
|
||||||
|
|
||||||
bacpy(&rej.bdaddr, &conn->dst);
|
|
||||||
rej.reason = reason;
|
|
||||||
|
|
||||||
r = hci_send_cmd(conn->hdev,
|
|
||||||
HCI_OP_REJECT_CONN_REQ,
|
|
||||||
sizeof(rej), &rej);
|
|
||||||
} else if (conn->type == SCO_LINK || conn->type == ESCO_LINK) {
|
|
||||||
struct hci_cp_reject_sync_conn_req rej;
|
|
||||||
|
|
||||||
bacpy(&rej.bdaddr, &conn->dst);
|
|
||||||
|
|
||||||
/* SCO rejection has its own limited set of
|
|
||||||
* allowed error values (0x0D-0x0F) which isn't
|
|
||||||
* compatible with most values passed to this
|
|
||||||
* function. To be safe hard-code one of the
|
|
||||||
* values that's suitable for SCO.
|
|
||||||
*/
|
|
||||||
rej.reason = HCI_ERROR_REJ_LIMITED_RESOURCES;
|
|
||||||
|
|
||||||
r = hci_send_cmd(conn->hdev,
|
|
||||||
HCI_OP_REJECT_SYNC_CONN_REQ,
|
|
||||||
sizeof(rej), &rej);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
conn->state = BT_CLOSED;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return r;
|
return hci_cmd_sync_queue(hdev, abort_conn_sync, ERR_PTR(conn->handle),
|
||||||
|
NULL);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5274,22 +5274,27 @@ static int hci_disconnect_sync(struct hci_dev *hdev, struct hci_conn *conn,
|
||||||
}
|
}
|
||||||
|
|
||||||
static int hci_le_connect_cancel_sync(struct hci_dev *hdev,
|
static int hci_le_connect_cancel_sync(struct hci_dev *hdev,
|
||||||
struct hci_conn *conn)
|
struct hci_conn *conn, u8 reason)
|
||||||
{
|
{
|
||||||
|
/* Return reason if scanning since the connection shall probably be
|
||||||
|
* cleanup directly.
|
||||||
|
*/
|
||||||
if (test_bit(HCI_CONN_SCANNING, &conn->flags))
|
if (test_bit(HCI_CONN_SCANNING, &conn->flags))
|
||||||
return 0;
|
return reason;
|
||||||
|
|
||||||
if (test_and_set_bit(HCI_CONN_CANCEL, &conn->flags))
|
if (conn->role == HCI_ROLE_SLAVE ||
|
||||||
|
test_and_set_bit(HCI_CONN_CANCEL, &conn->flags))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
return __hci_cmd_sync_status(hdev, HCI_OP_LE_CREATE_CONN_CANCEL,
|
return __hci_cmd_sync_status(hdev, HCI_OP_LE_CREATE_CONN_CANCEL,
|
||||||
0, NULL, HCI_CMD_TIMEOUT);
|
0, NULL, HCI_CMD_TIMEOUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int hci_connect_cancel_sync(struct hci_dev *hdev, struct hci_conn *conn)
|
static int hci_connect_cancel_sync(struct hci_dev *hdev, struct hci_conn *conn,
|
||||||
|
u8 reason)
|
||||||
{
|
{
|
||||||
if (conn->type == LE_LINK)
|
if (conn->type == LE_LINK)
|
||||||
return hci_le_connect_cancel_sync(hdev, conn);
|
return hci_le_connect_cancel_sync(hdev, conn, reason);
|
||||||
|
|
||||||
if (hdev->hci_ver < BLUETOOTH_VER_1_2)
|
if (hdev->hci_ver < BLUETOOTH_VER_1_2)
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -5342,9 +5347,11 @@ int hci_abort_conn_sync(struct hci_dev *hdev, struct hci_conn *conn, u8 reason)
|
||||||
case BT_CONFIG:
|
case BT_CONFIG:
|
||||||
return hci_disconnect_sync(hdev, conn, reason);
|
return hci_disconnect_sync(hdev, conn, reason);
|
||||||
case BT_CONNECT:
|
case BT_CONNECT:
|
||||||
err = hci_connect_cancel_sync(hdev, conn);
|
err = hci_connect_cancel_sync(hdev, conn, reason);
|
||||||
/* Cleanup hci_conn object if it cannot be cancelled as it
|
/* Cleanup hci_conn object if it cannot be cancelled as it
|
||||||
* likelly means the controller and host stack are out of sync.
|
* likelly means the controller and host stack are out of sync
|
||||||
|
* or in case of LE it was still scanning so it can be cleanup
|
||||||
|
* safely.
|
||||||
*/
|
*/
|
||||||
if (err) {
|
if (err) {
|
||||||
hci_dev_lock(hdev);
|
hci_dev_lock(hdev);
|
||||||
|
@ -6258,7 +6265,7 @@ int hci_le_create_conn_sync(struct hci_dev *hdev, struct hci_conn *conn)
|
||||||
|
|
||||||
done:
|
done:
|
||||||
if (err == -ETIMEDOUT)
|
if (err == -ETIMEDOUT)
|
||||||
hci_le_connect_cancel_sync(hdev, conn);
|
hci_le_connect_cancel_sync(hdev, conn, 0x00);
|
||||||
|
|
||||||
/* Re-enable advertising after the connection attempt is finished. */
|
/* Re-enable advertising after the connection attempt is finished. */
|
||||||
hci_resume_advertising_sync(hdev);
|
hci_resume_advertising_sync(hdev);
|
||||||
|
|
|
@ -3586,18 +3586,6 @@ unlock:
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
return hci_abort_conn_sync(hdev, conn, HCI_ERROR_REMOTE_USER_TERM);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int cancel_pair_device(struct sock *sk, struct hci_dev *hdev, void *data,
|
static int cancel_pair_device(struct sock *sk, struct hci_dev *hdev, void *data,
|
||||||
u16 len)
|
u16 len)
|
||||||
{
|
{
|
||||||
|
@ -3648,8 +3636,7 @@ static int cancel_pair_device(struct sock *sk, struct hci_dev *hdev, void *data,
|
||||||
le_addr_type(addr->type));
|
le_addr_type(addr->type));
|
||||||
|
|
||||||
if (conn->conn_reason == CONN_REASON_PAIR_DEVICE)
|
if (conn->conn_reason == CONN_REASON_PAIR_DEVICE)
|
||||||
hci_cmd_sync_queue(hdev, abort_conn_sync, ERR_PTR(conn->handle),
|
hci_abort_conn(conn, HCI_ERROR_REMOTE_USER_TERM);
|
||||||
NULL);
|
|
||||||
|
|
||||||
unlock:
|
unlock:
|
||||||
hci_dev_unlock(hdev);
|
hci_dev_unlock(hdev);
|
||||||
|
|
Loading…
Add table
Reference in a new issue