From: Luiz Augusto von Dentz <luiz.dentz@gmail.com>
To: linux-bluetooth@vger.kernel.org
Subject: [PATCH v3 2/2] Bluetooth: btusb: Add support for Bulk Serialization Mode
Date: Mon, 31 Aug 2026 12:41:11 -0400 [thread overview]
Message-ID: <20260831164112.778064-3-luiz.dentz@gmail.com> (raw)
In-Reply-To: <20260831164112.778064-1-luiz.dentz@gmail.com>
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
This adds support for Bulk Serialization Mode introduced in 6.2:
https://www.bluetooth.com/bluetooth-core-6-2-feature-overview/#5-bluetooth-hci-usb-le-isochronous-support
https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-62/out/en/host-controller-interface/usb-transport-layer.html#UUID-c1a65395-29e9-87d3-2981-8bed625d0459
It works by detecting if alternate setting 1 is supported for the
interface and then switches to use it as it serializes all the frames
in a single Bulk endpoint using H4 headers and it considerable more
robust then legacy one while allowing the transport of ISO packets:
'In addition to enabling Bluetooth® LE Audio, the new mode resolves a
persistent race condition in the legacy USB transport layer. In Legacy
Mode, different endpoint types are serviced in a specific order within
a USB frame, which can result in out-of-order delivery of data and
events. For example, a Host might receive a data packet before the
event signaling its arrival. This behavior can disrupt critical
processes such as connection setup, disconnection, and data encryption,
adversely affecting the user experience.'
Signed-off-by: Johann Fischer <johann.fischer@nordicsemi.no>
Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
---
drivers/bluetooth/btusb.c | 187 ++++++++++++++++++++++++++++++--------
1 file changed, 147 insertions(+), 40 deletions(-)
diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c
index ce870ec5ab43..ffa2d6053b32 100644
--- a/drivers/bluetooth/btusb.c
+++ b/drivers/bluetooth/btusb.c
@@ -27,8 +27,9 @@
#include "btbcm.h"
#include "btrtl.h"
#include "btmtk.h"
+#include "hci_uart.h"
-#define VERSION "0.8"
+#define VERSION "1.0"
static bool disable_scofix;
static bool force_scofix;
@@ -983,6 +984,9 @@ struct btqca_data {
#define BTUSB_ALT6_CONTINUOUS_TX 16
#define BTUSB_HW_SSR_ACTIVE 17
+#define BTUSB_PROTO_LEGACY 0x00
+#define BTUSB_PROTO_H4 0x01
+
struct btusb_data {
struct hci_dev *hdev;
struct usb_device *udev;
@@ -1016,6 +1020,7 @@ struct btusb_data {
struct sk_buff *evt_skb;
struct sk_buff *acl_skb;
struct sk_buff *sco_skb;
+ struct sk_buff *rx_skb;
struct usb_endpoint_descriptor *intr_ep;
struct usb_endpoint_descriptor *bulk_tx_ep;
@@ -1029,6 +1034,7 @@ struct btusb_data {
__u8 cmdreq_type;
__u8 cmdreq;
+ __u8 proto;
unsigned int sco_num;
unsigned int air_mode;
@@ -1256,6 +1262,11 @@ static inline void btusb_free_frags(struct btusb_data *data)
dev_kfree_skb_irq(data->sco_skb);
data->sco_skb = NULL;
+ /* rx_skb may hold an ERR_PTR from a previous h4_recv_skb() call */
+ if (!IS_ERR(data->rx_skb))
+ dev_kfree_skb_irq(data->rx_skb);
+ data->rx_skb = NULL;
+
spin_unlock_irqrestore(&data->rxlock, flags);
}
@@ -1355,12 +1366,41 @@ static int btusb_recv_acl(struct hci_dev *hdev, struct sk_buff *skb)
return 0;
}
+/* Dispatch through the btusb_recv_* wrappers so that vendor specific
+ * handling (data->recv_event, data->recv_acl) is preserved in H:4 mode.
+ */
+static const struct h4_recv_pkt btusb_recv_pkts[] = {
+ { H4_RECV_ACL, .recv = btusb_recv_acl },
+ { H4_RECV_SCO, .recv = hci_recv_frame },
+ { H4_RECV_EVENT, .recv = btusb_recv_event },
+ { H4_RECV_ISO, .recv = hci_recv_frame },
+};
+
+static int btusb_recv_h4(struct btusb_data *data, void *buffer, int count)
+{
+ unsigned long flags;
+ int err = 0;
+
+ spin_lock_irqsave(&data->rxlock, flags);
+ data->rx_skb = h4_recv_skb(data->hdev, NULL, NULL, data->rx_skb, buffer,
+ count, btusb_recv_pkts,
+ ARRAY_SIZE(btusb_recv_pkts));
+ if (IS_ERR(data->rx_skb))
+ err = PTR_ERR(data->rx_skb);
+ spin_unlock_irqrestore(&data->rxlock, flags);
+
+ return err;
+}
+
static int btusb_recv_bulk(struct btusb_data *data, void *buffer, int count)
{
struct sk_buff *skb;
unsigned long flags;
int err = 0;
+ if (data->proto == BTUSB_PROTO_H4)
+ return btusb_recv_h4(data, buffer, count);
+
spin_lock_irqsave(&data->rxlock, flags);
skb = data->acl_skb;
@@ -2038,12 +2078,14 @@ static int btusb_open(struct hci_dev *hdev)
data->intf->needs_remote_wakeup = 1;
- if (test_and_set_bit(BTUSB_INTR_RUNNING, &data->flags))
- goto done;
+ if (data->proto == BTUSB_PROTO_LEGACY) {
+ if (test_and_set_bit(BTUSB_INTR_RUNNING, &data->flags))
+ goto done;
- err = btusb_submit_intr_urb(hdev, GFP_KERNEL);
- if (err < 0)
- goto failed;
+ err = btusb_submit_intr_urb(hdev, GFP_KERNEL);
+ if (err < 0)
+ goto failed;
+ }
err = btusb_submit_bulk_urb(hdev, GFP_KERNEL);
if (err < 0) {
@@ -2147,6 +2189,42 @@ static int btusb_flush(struct hci_dev *hdev)
return 0;
}
+static struct urb *alloc_bulk_urb(struct hci_dev *hdev, struct sk_buff *skb)
+{
+ struct btusb_data *data = hci_get_drvdata(hdev);
+ struct urb *urb;
+ unsigned int pipe;
+
+ if (!data->bulk_tx_ep)
+ return ERR_PTR(-ENODEV);
+
+ if (data->proto == BTUSB_PROTO_H4) {
+ /* The frame type is prepended in place, so the buffer must not
+ * be shared with anyone else.
+ */
+ if (skb_cow_head(skb, 1))
+ return ERR_PTR(-ENOMEM);
+ }
+
+ urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!urb)
+ return ERR_PTR(-ENOMEM);
+
+ pipe = usb_sndbulkpipe(data->udev, data->bulk_tx_ep->bEndpointAddress);
+
+ if (data->proto == BTUSB_PROTO_H4) {
+ /* Prepend skb with frame type */
+ memcpy(skb_push(skb, 1), &hci_skb_pkt_type(skb), 1);
+ }
+
+ usb_fill_bulk_urb(urb, data->udev, pipe,
+ skb->data, skb->len, btusb_tx_complete, skb);
+
+ skb->dev = (void *)hdev;
+
+ return urb;
+}
+
static struct urb *alloc_ctrl_urb(struct hci_dev *hdev, struct sk_buff *skb)
{
struct btusb_data *data = hci_get_drvdata(hdev);
@@ -2154,6 +2232,9 @@ static struct urb *alloc_ctrl_urb(struct hci_dev *hdev, struct sk_buff *skb)
struct urb *urb;
unsigned int pipe;
+ if (data->proto == BTUSB_PROTO_H4)
+ return alloc_bulk_urb(hdev, skb);
+
urb = usb_alloc_urb(0, GFP_KERNEL);
if (!urb)
return ERR_PTR(-ENOMEM);
@@ -2180,35 +2261,15 @@ static struct urb *alloc_ctrl_urb(struct hci_dev *hdev, struct sk_buff *skb)
return urb;
}
-static struct urb *alloc_bulk_urb(struct hci_dev *hdev, struct sk_buff *skb)
-{
- struct btusb_data *data = hci_get_drvdata(hdev);
- struct urb *urb;
- unsigned int pipe;
-
- if (!data->bulk_tx_ep)
- return ERR_PTR(-ENODEV);
-
- urb = usb_alloc_urb(0, GFP_KERNEL);
- if (!urb)
- return ERR_PTR(-ENOMEM);
-
- pipe = usb_sndbulkpipe(data->udev, data->bulk_tx_ep->bEndpointAddress);
-
- usb_fill_bulk_urb(urb, data->udev, pipe,
- skb->data, skb->len, btusb_tx_complete, skb);
-
- skb->dev = (void *)hdev;
-
- return urb;
-}
-
static struct urb *alloc_isoc_urb(struct hci_dev *hdev, struct sk_buff *skb)
{
struct btusb_data *data = hci_get_drvdata(hdev);
struct urb *urb;
unsigned int pipe;
+ if (data->proto == BTUSB_PROTO_H4)
+ return alloc_bulk_urb(hdev, skb);
+
if (!data->isoc_tx_ep)
return ERR_PTR(-ENODEV);
@@ -2422,10 +2483,9 @@ static int btusb_switch_alt_setting(struct hci_dev *hdev, int new_alts)
return 0;
}
-static struct usb_host_interface *btusb_find_altsetting(struct btusb_data *data,
- int alt)
+static struct usb_host_interface *
+btusb_find_altsetting(struct usb_interface *intf, int alt)
{
- struct usb_interface *intf = data->isoc;
int i;
BT_DBG("Looking for Alt no :%d", alt);
@@ -2448,6 +2508,13 @@ static void btusb_work(struct work_struct *work)
int new_alts = 0;
int err;
+ /* In H:4 mode SCO/ISO data is carried over the bulk endpoints, so
+ * there is no isochronous interface to resume or to switch alternate
+ * settings on.
+ */
+ if (data->proto == BTUSB_PROTO_H4)
+ return;
+
if (data->sco_num > 0) {
if (!test_bit(BTUSB_DID_ISO_RESUME, &data->flags)) {
err = usb_autopm_get_interface(data->isoc ? data->isoc : data->intf);
@@ -2481,9 +2548,9 @@ static void btusb_work(struct work_struct *work)
* MTU >= 3 (packets) * 25 (size) - 3 (headers) = 72
* see also Core spec 5, vol 4, B 2.1.1 & Table 2.1.
*/
- if (btusb_find_altsetting(data, 6))
+ if (btusb_find_altsetting(data->isoc, 6))
new_alts = 6;
- else if (btusb_find_altsetting(data, 3) &&
+ else if (btusb_find_altsetting(data->isoc, 3) &&
hdev->sco_mtu >= 72 &&
test_bit(BTUSB_USE_ALT3_FOR_WBS, &data->flags))
new_alts = 3;
@@ -3957,8 +4024,11 @@ static ssize_t force_poll_sync_write(struct file *file,
if (err)
return err;
- /* Only allow changes while the adapter is down */
- if (test_bit(HCI_UP, &data->hdev->flags))
+ /* Only allow changes while the adapter is down and it is using legacy
+ * protocol.
+ */
+ if (test_bit(HCI_UP, &data->hdev->flags) ||
+ data->proto != BTUSB_PROTO_LEGACY)
return -EPERM;
if (data->poll_sync == enable)
@@ -4057,7 +4127,7 @@ static int btusb_hci_drv_supported_altsettings(struct hci_dev *hdev, void *data,
goto done;
for (i = 0; i <= 6; i++) {
- if (btusb_find_altsetting(drvdata, i))
+ if (btusb_find_altsetting(drvdata->isoc, i))
rp->altsettings[rp->num++] = i;
}
@@ -4111,6 +4181,8 @@ static int btusb_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
struct gpio_desc *reset_gpio;
+ struct usb_host_interface *alt;
+ struct usb_endpoint_descriptor *bulk_rx_ep, *bulk_tx_ep, *intr_ep;
struct btusb_data *data;
struct hci_dev *hdev;
unsigned ifnum_base;
@@ -4152,10 +4224,38 @@ static int btusb_probe(struct usb_interface *intf,
return -ENOMEM;
data->match_id = id;
+
+ /* Alternate setting 1 with a single pair of bulk endpoints and no
+ * interrupt endpoint means the controller supports Bulk Serialization
+ * Mode, in which every packet is prefixed with an H:4 header and
+ * carried over the bulk endpoints.
+ */
+ alt = btusb_find_altsetting(intf, 1);
+ if (alt && usb_find_int_in_endpoint(alt, &intr_ep) &&
+ !usb_find_common_endpoints(alt, &bulk_rx_ep, &bulk_tx_ep, NULL,
+ NULL)) {
+ err = usb_set_interface(interface_to_usbdev(intf), ifnum_base, 1);
+ if (!err)
+ data->proto = BTUSB_PROTO_H4;
+ else
+ dev_warn(&intf->dev,
+ "failed to select alt setting 1 (%d), using legacy mode",
+ err);
+ }
+
+ /* Check if all endpoints could be enumerated, legacy mode requires
+ * interrupt and bulk endpoints while H4 mode only requires bulk
+ * endpoints.
+ */
err = usb_find_common_endpoints(intf->cur_altsetting, &data->bulk_rx_ep,
- &data->bulk_tx_ep, &data->intr_ep, NULL);
- if (err)
+ &data->bulk_tx_ep,
+ data->proto == BTUSB_PROTO_LEGACY ?
+ &data->intr_ep : NULL,
+ NULL);
+ if (err) {
+ dev_err(&intf->dev, "failed to enumerate endpoints\n");
goto err_free_data;
+ }
if (id->driver_info & BTUSB_AMP) {
data->cmdreq_type = USB_TYPE_CLASS | 0x01;
@@ -4367,6 +4467,12 @@ static int btusb_probe(struct usb_interface *intf,
if (id->driver_info & BTUSB_AMP) {
/* AMP controllers do not support SCO packets */
data->isoc = NULL;
+ } else if (data->proto == BTUSB_PROTO_H4) {
+ /* In H:4 mode every packet, including SCO/ISO, is carried over
+ * the bulk endpoints, so the isochronous interface must not be
+ * claimed nor have its alternate settings switched.
+ */
+ data->isoc = NULL;
} else {
/* Interface orders are hardcoded in the specification */
data->isoc = usb_ifnum_to_if(data->udev, ifnum_base + 1);
@@ -4476,7 +4582,8 @@ static int btusb_probe(struct usb_interface *intf,
if (enable_autosuspend)
usb_enable_autosuspend(data->udev);
- data->poll_sync = enable_poll_sync;
+ if (data->proto == BTUSB_PROTO_LEGACY)
+ data->poll_sync = enable_poll_sync;
err = hci_register_dev(hdev);
if (err < 0)
--
2.54.0
next prev parent reply other threads:[~2026-08-31 16:41 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-31 16:41 [PATCH v1] Bluetooth: btusb: Fix UAF of btusb_data by rx_work Luiz Augusto von Dentz
2026-08-31 16:41 ` [PATCH v3 1/2] Bluetooth: Move H:4 reassembly into the Bluetooth core Luiz Augusto von Dentz
2026-08-31 19:06 ` [v3,1/2] " bluez.test.bot
2026-09-03 20:00 ` [PATCH v3 1/2] " patchwork-bot+bluetooth
2026-08-31 16:41 ` Luiz Augusto von Dentz [this message]
2026-08-31 18:56 ` [v1] Bluetooth: btusb: Fix UAF of btusb_data by rx_work bluez.test.bot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260831164112.778064-3-luiz.dentz@gmail.com \
--to=luiz.dentz@gmail.com \
--cc=linux-bluetooth@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox