From: Hsin-chen Chuang <chharry@xxxxxxxxxxxx> Automatically configure the altsetting for USER_CHANNEL when a SCO is connected or disconnected. This adds support for the USER_CHANNEL to transfer SCO data over USB transport. Fixes: b16b327edb4d ("Bluetooth: btusb: add sysfs attribute to control USB alt setting") Signed-off-by: Hsin-chen Chuang <chharry@xxxxxxxxxxxx> --- drivers/bluetooth/btusb.c | 224 +++++++++++++++++++++++++++++++------- 1 file changed, 186 insertions(+), 38 deletions(-) diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index de3fa725d210..dfb0918dfe98 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -854,6 +854,11 @@ struct qca_dump_info { #define BTUSB_ALT6_CONTINUOUS_TX 16 #define BTUSB_HW_SSR_ACTIVE 17 +struct sco_handle_list { + struct list_head list; + u16 handle; +}; + struct btusb_data { struct hci_dev *hdev; struct usb_device *udev; @@ -920,6 +925,9 @@ struct btusb_data { int oob_wake_irq; /* irq for out-of-band wake-on-bt */ struct qca_dump_info qca_dump; + + /* Records the exsiting SCO handles for HCI_USER_CHANNEL */ + struct list_head sco_handle_list; }; static void btusb_reset(struct hci_dev *hdev) @@ -1113,6 +1121,131 @@ static inline void btusb_free_frags(struct btusb_data *data) spin_unlock_irqrestore(&data->rxlock, flags); } +static void btusb_sco_handle_list_clear(struct list_head *list) +{ + struct sco_handle_list *entry, *n; + + list_for_each_entry_safe(entry, n, list, list) { + list_del(&entry->list); + kfree(entry); + } +} + +static struct sco_handle_list *btusb_sco_handle_list_find( + struct list_head *list, + u16 handle) +{ + struct sco_handle_list *entry; + + list_for_each_entry(entry, list, list) + if (entry->handle == handle) + return entry; + + return NULL; +} + +static int btusb_sco_handle_list_add(struct list_head *list, u16 handle) +{ + struct sco_handle_list *entry; + + if (btusb_sco_handle_list_find(list, handle)) + return -EEXIST; + + entry = kzalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) + return -ENOMEM; + + entry->handle = handle; + list_add(&entry->list, list); + + return 0; +} + +static int btusb_sco_handle_list_del(struct list_head *list, u16 handle) +{ + struct sco_handle_list *entry; + + entry = btusb_sco_handle_list_find(list, handle); + if (!entry) + return -ENOENT; + + list_del(&entry->list); + kfree(entry); + + return 0; +} + +static void btusb_sco_conn_change(struct btusb_data *data, struct sk_buff *skb) +{ + struct hci_event_hdr *hdr = (void *) skb->data; + struct hci_dev *hdev = data->hdev; + + if (hci_skb_pkt_type(skb) != HCI_EVENT_PKT || skb->len < sizeof(*hdr)) + return; + + switch (hdr->evt) { + case HCI_EV_DISCONN_COMPLETE: { + struct hci_ev_disconn_complete *ev = + (void *) skb->data + sizeof(*hdr); + u16 handle; + + if (skb->len != sizeof(*hdr) + sizeof(*ev) || ev->status) + return; + + handle = __le16_to_cpu(ev->handle); + if (btusb_sco_handle_list_del(&data->sco_handle_list, + handle) < 0) + return; + + bt_dev_info(hdev, "disabling SCO"); + data->sco_num--; + data->air_mode = HCI_NOTIFY_DISABLE_SCO; + schedule_work(&data->work); + + break; + } + case HCI_EV_SYNC_CONN_COMPLETE: { + struct hci_ev_sync_conn_complete *ev = + (void *) skb->data + sizeof(*hdr); + unsigned int notify_air_mode; + u16 handle; + + if (skb->len != sizeof(*hdr) + sizeof(*ev) || ev->status) + return; + + switch (ev->air_mode) { + case BT_CODEC_CVSD: + notify_air_mode = HCI_NOTIFY_ENABLE_SCO_CVSD; + break; + + case BT_CODEC_TRANSPARENT: + notify_air_mode = HCI_NOTIFY_ENABLE_SCO_TRANSP; + break; + + default: + return; + } + + handle = __le16_to_cpu(ev->handle); + if (btusb_sco_handle_list_add(&data->sco_handle_list, + handle) < 0) { + bt_dev_err(hdev, "failed to add the new SCO handle"); + return; + } + + bt_dev_info(hdev, "enabling SCO with air mode %u", + ev->air_mode); + data->sco_num++; + data->air_mode = notify_air_mode; + schedule_work(&data->work); + + break; + } + default: + break; + } +} + static int btusb_recv_event(struct btusb_data *data, struct sk_buff *skb) { if (data->intr_interval) { @@ -1120,6 +1253,10 @@ static int btusb_recv_event(struct btusb_data *data, struct sk_buff *skb) schedule_delayed_work(&data->rx_work, 0); } + /* Configure altsetting for HCI_USER_CHANNEL on SCO dis/connected */ + if (hci_dev_test_flag(data->hdev, HCI_USER_CHANNEL)) + btusb_sco_conn_change(data, skb); + return data->recv_event(data->hdev, skb); } @@ -1919,44 +2056,6 @@ static void btusb_stop_traffic(struct btusb_data *data) usb_kill_anchored_urbs(&data->ctrl_anchor); } -static int btusb_close(struct hci_dev *hdev) -{ - struct btusb_data *data = hci_get_drvdata(hdev); - int err; - - BT_DBG("%s", hdev->name); - - cancel_delayed_work(&data->rx_work); - cancel_work_sync(&data->work); - cancel_work_sync(&data->waker); - - skb_queue_purge(&data->acl_q); - - clear_bit(BTUSB_ISOC_RUNNING, &data->flags); - clear_bit(BTUSB_BULK_RUNNING, &data->flags); - clear_bit(BTUSB_INTR_RUNNING, &data->flags); - clear_bit(BTUSB_DIAG_RUNNING, &data->flags); - - btusb_stop_traffic(data); - btusb_free_frags(data); - - err = usb_autopm_get_interface(data->intf); - if (err < 0) - goto failed; - - data->intf->needs_remote_wakeup = 0; - - /* Enable remote wake up for auto-suspend */ - if (test_bit(BTUSB_WAKEUP_AUTOSUSPEND, &data->flags)) - data->intf->needs_remote_wakeup = 1; - - usb_autopm_put_interface(data->intf); - -failed: - usb_scuttle_anchored_urbs(&data->deferred); - return 0; -} - static int btusb_flush(struct hci_dev *hdev) { struct btusb_data *data = hci_get_drvdata(hdev); @@ -2327,6 +2426,51 @@ static void btusb_work(struct work_struct *work) } } +static int btusb_close(struct hci_dev *hdev) +{ + struct btusb_data *data = hci_get_drvdata(hdev); + int err; + + BT_DBG("%s", hdev->name); + + if (hci_dev_test_flag(hdev, HCI_USER_CHANNEL)) { + btusb_sco_handle_list_clear(&data->sco_handle_list); + data->sco_num = 0; + if (data->isoc_altsetting != 0) + btusb_switch_alt_setting(hdev, 0); + } + + cancel_delayed_work(&data->rx_work); + cancel_work_sync(&data->work); + cancel_work_sync(&data->waker); + + skb_queue_purge(&data->acl_q); + + clear_bit(BTUSB_ISOC_RUNNING, &data->flags); + clear_bit(BTUSB_BULK_RUNNING, &data->flags); + clear_bit(BTUSB_INTR_RUNNING, &data->flags); + clear_bit(BTUSB_DIAG_RUNNING, &data->flags); + + btusb_stop_traffic(data); + btusb_free_frags(data); + + err = usb_autopm_get_interface(data->intf); + if (err < 0) + goto failed; + + data->intf->needs_remote_wakeup = 0; + + /* Enable remote wake up for auto-suspend */ + if (test_bit(BTUSB_WAKEUP_AUTOSUSPEND, &data->flags)) + data->intf->needs_remote_wakeup = 1; + + usb_autopm_put_interface(data->intf); + +failed: + usb_scuttle_anchored_urbs(&data->deferred); + return 0; +} + static void btusb_waker(struct work_struct *work) { struct btusb_data *data = container_of(work, struct btusb_data, waker); @@ -3782,6 +3926,8 @@ static int btusb_probe(struct usb_interface *intf, data->udev = interface_to_usbdev(intf); data->intf = intf; + INIT_LIST_HEAD(&data->sco_handle_list); + INIT_WORK(&data->work, btusb_work); INIT_WORK(&data->waker, btusb_waker); INIT_DELAYED_WORK(&data->rx_work, btusb_rx_work); @@ -4117,6 +4263,8 @@ static void btusb_disconnect(struct usb_interface *intf) hdev = data->hdev; usb_set_intfdata(data->intf, NULL); + btusb_sco_handle_list_clear(&data->sco_handle_list); + if (data->isoc) { device_remove_file(&intf->dev, &dev_attr_isoc_alt); usb_set_intfdata(data->isoc, NULL); -- 2.48.1.601.g30ceb7b040-goog