Skip to content

Commit b3fdb8c

Browse files
Nicolas CavallariVudentz
authored andcommitted
Bluetooth: Work around SCO over USB HCI design defect
The USB interface between the host and the bluetooth adapter used for SCO packets uses an USB isochronous endpoint with a fragmentation scheme that does not tolerate errors. Except USB isochronous transfers do not provide a reliable stream with guaranteed delivery. (There is no retry on error, see USB spec v2.0 5.6 and 8.5.5.) To fragment a packet, the bluetooth HCI simply splits it in parts and transfer them as-is. The receiver is expected to reconstruct the packet by assuming the first fragment contains the header and parsing its size field. There is no error detection either. If a fragment is lost, the end result is that the kernel is no longer synchronized and will pass malformed data to the upper layers, since it has no way to tell if the first fragment is an actual first fragment or a continuation fragment. Resynchronization can only happen by luck and requires an unbounded amount of time. The typical symptom for a HSP/HFP bluetooth headset is that the microphone stops working and dmesg contains piles of rate-limited "Bluetooth: hci0: SCO packet for unknown connection handle XXXX" errors for an indeterminate amount of time, until the kernel accidentally resynchronize. A workaround is to ask the upper layer to prevalidate the first fragment header. This is not possible with user channels so this workaround is disabled in this case. This problem is the most severe when using an ath3k adapter on an i.MX 6 board, where packet loss occur regularly, possibly because it is an USB1 device connected on an USB2 hub and this is a special case requiring split transactions. Signed-off-by: Nicolas Cavallari <[email protected]> Signed-off-by: Luiz Augusto von Dentz <[email protected]>
1 parent c9209b2 commit b3fdb8c

File tree

1 file changed

+33
-2
lines changed

1 file changed

+33
-2
lines changed

drivers/bluetooth/btusb.c

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -977,6 +977,34 @@ static int btusb_recv_bulk(struct btusb_data *data, void *buffer, int count)
977977
return err;
978978
}
979979

980+
static bool btusb_validate_sco_handle(struct hci_dev *hdev,
981+
struct hci_sco_hdr *hdr)
982+
{
983+
__u16 handle;
984+
985+
if (hci_dev_test_flag(hdev, HCI_USER_CHANNEL))
986+
// Can't validate, userspace controls everything.
987+
return true;
988+
989+
/*
990+
* USB isochronous transfers are not designed to be reliable and may
991+
* lose fragments. When this happens, the next first fragment
992+
* encountered might actually be a continuation fragment.
993+
* Validate the handle to detect it and drop it, or else the upper
994+
* layer will get garbage for a while.
995+
*/
996+
997+
handle = hci_handle(__le16_to_cpu(hdr->handle));
998+
999+
switch (hci_conn_lookup_type(hdev, handle)) {
1000+
case SCO_LINK:
1001+
case ESCO_LINK:
1002+
return true;
1003+
default:
1004+
return false;
1005+
}
1006+
}
1007+
9801008
static int btusb_recv_isoc(struct btusb_data *data, void *buffer, int count)
9811009
{
9821010
struct sk_buff *skb;
@@ -1009,9 +1037,12 @@ static int btusb_recv_isoc(struct btusb_data *data, void *buffer, int count)
10091037

10101038
if (skb->len == HCI_SCO_HDR_SIZE) {
10111039
/* Complete SCO header */
1012-
hci_skb_expect(skb) = hci_sco_hdr(skb)->dlen;
1040+
struct hci_sco_hdr *hdr = hci_sco_hdr(skb);
10131041

1014-
if (skb_tailroom(skb) < hci_skb_expect(skb)) {
1042+
hci_skb_expect(skb) = hdr->dlen;
1043+
1044+
if (skb_tailroom(skb) < hci_skb_expect(skb) ||
1045+
!btusb_validate_sco_handle(data->hdev, hdr)) {
10151046
kfree_skb(skb);
10161047
skb = NULL;
10171048

0 commit comments

Comments
 (0)