Skip to content

Commit

Permalink
Bluetooth: Defer SCO setup if mode change is pending
Browse files Browse the repository at this point in the history
Certain headsets such as the Motorola H350 will reject SCO and eSCO
connection requests while the ACL is transitioning from sniff mode
to active mode. Add synchronization so that SCO and eSCO connection
requests will wait until the ACL has fully transitioned to active mode.

< HCI Command: Exit Sniff Mode (0x02|0x0004) plen 2
    handle 12
> HCI Event: Command Status (0x0f) plen 4
    Exit Sniff Mode (0x02|0x0004) status 0x00 ncmd 1
< HCI Command:  Setup Synchronous Connection (0x01|0x0028) plen 17
    handle 12 voice setting 0x0040
> HCI Event: Command Status (0x0f) plen 4
    Setup Synchronous Connection (0x01|0x0028) status 0x00 ncmd 1
> HCI Event: Number of Completed Packets (0x13) plen 5
    handle 12 packets 1
> HCI Event: Mode Change (0x14) plen 6
    status 0x00 handle 12 mode 0x00 interval 0
    Mode: Active
> HCI Event: Synchronous Connect Complete (0x2c) plen 17
    status 0x10 handle 14 bdaddr 00:1A:0E:50:28:A4 type SCO
    Error: Connection Accept Timeout Exceeded

Signed-off-by: Ron Shaffer <rshaffer@codeaurora.org>
Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
  • Loading branch information
Marcel Holtmann committed Jul 27, 2010
1 parent 800f65b commit e73439d
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 20 deletions.
2 changes: 2 additions & 0 deletions include/net/bluetooth/hci_core.h
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ enum {
HCI_CONN_ENCRYPT_PEND,
HCI_CONN_RSWITCH_PEND,
HCI_CONN_MODE_CHANGE_PEND,
HCI_CONN_SCO_SETUP_PEND,
};

static inline void hci_conn_hash_init(struct hci_dev *hdev)
Expand Down Expand Up @@ -336,6 +337,7 @@ void hci_acl_connect(struct hci_conn *conn);
void hci_acl_disconn(struct hci_conn *conn, __u8 reason);
void hci_add_sco(struct hci_conn *conn, __u16 handle);
void hci_setup_sync(struct hci_conn *conn, __u16 handle);
void hci_sco_setup(struct hci_conn *conn, __u8 status);

struct hci_conn *hci_conn_add(struct hci_dev *hdev, int type, bdaddr_t *dst);
int hci_conn_del(struct hci_conn *conn);
Expand Down
32 changes: 28 additions & 4 deletions net/bluetooth/hci_conn.c
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,27 @@ void hci_setup_sync(struct hci_conn *conn, __u16 handle)
hci_send_cmd(hdev, HCI_OP_SETUP_SYNC_CONN, sizeof(cp), &cp);
}

/* Device _must_ be locked */
void hci_sco_setup(struct hci_conn *conn, __u8 status)
{
struct hci_conn *sco = conn->link;

BT_DBG("%p", conn);

if (!sco)
return;

if (!status) {
if (lmp_esco_capable(conn->hdev))
hci_setup_sync(sco, conn->handle);
else
hci_add_sco(sco, conn->handle);
} else {
hci_proto_connect_cfm(sco, status);
hci_conn_del(sco);
}
}

static void hci_conn_timeout(unsigned long arg)
{
struct hci_conn *conn = (void *) arg;
Expand Down Expand Up @@ -385,10 +406,13 @@ struct hci_conn *hci_connect(struct hci_dev *hdev, int type, bdaddr_t *dst, __u8
acl->power_save = 1;
hci_conn_enter_active_mode(acl);

if (lmp_esco_capable(hdev))
hci_setup_sync(sco, acl->handle);
else
hci_add_sco(sco, acl->handle);
if (test_bit(HCI_CONN_MODE_CHANGE_PEND, &acl->pend)) {
/* defer SCO setup until mode change completed */
set_bit(HCI_CONN_SCO_SETUP_PEND, &acl->pend);
return sco;
}

hci_sco_setup(acl, 0x00);
}

return sco;
Expand Down
31 changes: 15 additions & 16 deletions net/bluetooth/hci_event.c
Original file line number Diff line number Diff line change
Expand Up @@ -785,9 +785,13 @@ static void hci_cs_sniff_mode(struct hci_dev *hdev, __u8 status)
hci_dev_lock(hdev);

conn = hci_conn_hash_lookup_handle(hdev, __le16_to_cpu(cp->handle));
if (conn)
if (conn) {
clear_bit(HCI_CONN_MODE_CHANGE_PEND, &conn->pend);

if (test_and_clear_bit(HCI_CONN_SCO_SETUP_PEND, &conn->pend))
hci_sco_setup(conn, status);
}

hci_dev_unlock(hdev);
}

Expand All @@ -808,9 +812,13 @@ static void hci_cs_exit_sniff_mode(struct hci_dev *hdev, __u8 status)
hci_dev_lock(hdev);

conn = hci_conn_hash_lookup_handle(hdev, __le16_to_cpu(cp->handle));
if (conn)
if (conn) {
clear_bit(HCI_CONN_MODE_CHANGE_PEND, &conn->pend);

if (test_and_clear_bit(HCI_CONN_SCO_SETUP_PEND, &conn->pend))
hci_sco_setup(conn, status);
}

hci_dev_unlock(hdev);
}

Expand Down Expand Up @@ -915,20 +923,8 @@ static inline void hci_conn_complete_evt(struct hci_dev *hdev, struct sk_buff *s
} else
conn->state = BT_CLOSED;

if (conn->type == ACL_LINK) {
struct hci_conn *sco = conn->link;
if (sco) {
if (!ev->status) {
if (lmp_esco_capable(hdev))
hci_setup_sync(sco, conn->handle);
else
hci_add_sco(sco, conn->handle);
} else {
hci_proto_connect_cfm(sco, ev->status);
hci_conn_del(sco);
}
}
}
if (conn->type == ACL_LINK)
hci_sco_setup(conn, ev->status);

if (ev->status) {
hci_proto_connect_cfm(conn, ev->status);
Expand Down Expand Up @@ -1481,6 +1477,9 @@ static inline void hci_mode_change_evt(struct hci_dev *hdev, struct sk_buff *skb
else
conn->power_save = 0;
}

if (test_and_clear_bit(HCI_CONN_SCO_SETUP_PEND, &conn->pend))
hci_sco_setup(conn, ev->status);
}

hci_dev_unlock(hdev);
Expand Down

0 comments on commit e73439d

Please sign in to comment.