* [PATCH v2 1/2] Bluetooth: Move H:4 reassembly into the Bluetooth core
@ 2026-08-28 20:13 Luiz Augusto von Dentz
2026-08-28 20:13 ` [PATCH v2 2/2] Bluetooth: btusb: Add support for Bulk Serialization Mode Luiz Augusto von Dentz
` (2 more replies)
0 siblings, 3 replies; 4+ messages in thread
From: Luiz Augusto von Dentz @ 2026-08-28 20:13 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
h4_recv_buf() is currently implemented in hci_h4.c which is only built as
part of the hci_uart module, and only when CONFIG_BT_HCIUART_H4 is
enabled. That makes the H:4 reassembly logic unusable by drivers which do
not depend on hci_uart, e.g. btusb which needs it to implement Bulk
Serialization Mode.
Move the transport agnostic part into the Bluetooth core as
h4_recv_skb(), which takes a struct hci_dev instead of a struct hci_uart,
along with struct h4_recv_pkt and the H4_RECV_* helpers, and keep
h4_recv_buf() as a thin wrapper for the hci_uart protocols.
Since every Bluetooth driver already depends on the bluetooth module this
introduces no new module dependency and no new Kconfig symbol.
Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
---
drivers/bluetooth/hci_h4.c | 123 ++-----------------------
drivers/bluetooth/hci_uart.h | 39 +-------
include/net/bluetooth/hci_h4.h | 60 +++++++++++++
net/bluetooth/Makefile | 2 +-
net/bluetooth/hci_h4.c | 160 +++++++++++++++++++++++++++++++++
5 files changed, 229 insertions(+), 155 deletions(-)
create mode 100644 include/net/bluetooth/hci_h4.h
create mode 100644 net/bluetooth/hci_h4.c
diff --git a/drivers/bluetooth/hci_h4.c b/drivers/bluetooth/hci_h4.c
index 767372707498..cbdf51458ec3 100644
--- a/drivers/bluetooth/hci_h4.c
+++ b/drivers/bluetooth/hci_h4.c
@@ -29,6 +29,7 @@
#include <net/bluetooth/bluetooth.h>
#include <net/bluetooth/hci_core.h>
+#include <net/bluetooth/hci_h4.h>
#include "hci_uart.h"
@@ -112,8 +113,9 @@ static int h4_recv(struct hci_uart *hu, const void *data, int count)
if (!h4)
return -ENODEV;
- h4->rx_skb = h4_recv_buf(hu, h4->rx_skb, data, count,
- h4_recv_pkts, ARRAY_SIZE(h4_recv_pkts));
+ h4->rx_skb = h4_recv_skb(hu->hdev, &hu->alignment, &hu->padding,
+ h4->rx_skb, data, count, h4_recv_pkts,
+ ARRAY_SIZE(h4_recv_pkts));
if (IS_ERR(h4->rx_skb)) {
int err = PTR_ERR(h4->rx_skb);
bt_dev_err(hu->hdev, "Frame reassembly failed (%d)", err);
@@ -155,120 +157,7 @@ struct sk_buff *h4_recv_buf(struct hci_uart *hu, struct sk_buff *skb,
const unsigned char *buffer, int count,
const struct h4_recv_pkt *pkts, int pkts_count)
{
- u8 alignment = hu->alignment ? hu->alignment : 1;
- struct hci_dev *hdev = hu->hdev;
-
- /* Check for error from previous call */
- if (IS_ERR(skb))
- skb = NULL;
-
- while (count) {
- int i, len;
-
- /* remove padding bytes from buffer */
- for (; hu->padding && count > 0; hu->padding--) {
- count--;
- buffer++;
- }
- if (!count)
- break;
-
- if (!skb) {
- for (i = 0; i < pkts_count; i++) {
- if (buffer[0] != (&pkts[i])->type)
- continue;
-
- skb = bt_skb_alloc((&pkts[i])->maxlen,
- GFP_ATOMIC);
- if (!skb)
- return ERR_PTR(-ENOMEM);
-
- hci_skb_pkt_type(skb) = (&pkts[i])->type;
- hci_skb_expect(skb) = (&pkts[i])->hlen;
- break;
- }
-
- /* Check for invalid packet type */
- if (!skb)
- return ERR_PTR(-EILSEQ);
-
- count -= 1;
- buffer += 1;
- }
-
- len = min_t(uint, hci_skb_expect(skb) - skb->len, count);
- skb_put_data(skb, buffer, len);
-
- count -= len;
- buffer += len;
-
- /* Check for partial packet */
- if (skb->len < hci_skb_expect(skb))
- continue;
-
- for (i = 0; i < pkts_count; i++) {
- if (hci_skb_pkt_type(skb) == (&pkts[i])->type)
- break;
- }
-
- if (i >= pkts_count) {
- kfree_skb(skb);
- return ERR_PTR(-EILSEQ);
- }
-
- if (skb->len == (&pkts[i])->hlen) {
- u16 dlen;
-
- switch ((&pkts[i])->lsize) {
- case 0:
- /* No variable data length */
- dlen = 0;
- break;
- case 1:
- /* Single octet variable length */
- dlen = skb->data[(&pkts[i])->loff];
- hci_skb_expect(skb) += dlen;
-
- if (skb_tailroom(skb) < dlen) {
- kfree_skb(skb);
- return ERR_PTR(-EMSGSIZE);
- }
- break;
- case 2:
- /* Double octet variable length */
- dlen = get_unaligned_le16(skb->data +
- (&pkts[i])->loff);
- hci_skb_expect(skb) += dlen;
-
- if (skb_tailroom(skb) < dlen) {
- kfree_skb(skb);
- return ERR_PTR(-EMSGSIZE);
- }
- break;
- default:
- /* Unsupported variable length */
- kfree_skb(skb);
- return ERR_PTR(-EILSEQ);
- }
-
- if (!dlen) {
- hu->padding = (skb->len + 1) % alignment;
- hu->padding = (alignment - hu->padding) % alignment;
-
- /* No more data, complete frame */
- (&pkts[i])->recv(hdev, skb);
- skb = NULL;
- }
- } else {
- hu->padding = (skb->len + 1) % alignment;
- hu->padding = (alignment - hu->padding) % alignment;
-
- /* Complete frame */
- (&pkts[i])->recv(hdev, skb);
- skb = NULL;
- }
- }
-
- return skb;
+ return h4_recv_skb(hu->hdev, &hu->alignment, &hu->padding, skb, buffer,
+ count, pkts, pkts_count);
}
EXPORT_SYMBOL_GPL(h4_recv_buf);
diff --git a/drivers/bluetooth/hci_uart.h b/drivers/bluetooth/hci_uart.h
index 48ac7ca9334e..7fbe8dffab98 100644
--- a/drivers/bluetooth/hci_uart.h
+++ b/drivers/bluetooth/hci_uart.h
@@ -8,6 +8,8 @@
* Copyright (C) 2004-2005 Marcel Holtmann <marcel@holtmann.org>
*/
+#include <net/bluetooth/hci_h4.h>
+
#ifndef N_HCI
#define N_HCI 15
#endif
@@ -121,43 +123,6 @@ void hci_uart_set_flow_control(struct hci_uart *hu, bool enable);
void hci_uart_set_speeds(struct hci_uart *hu, unsigned int init_speed,
unsigned int oper_speed);
-struct h4_recv_pkt {
- u8 type; /* Packet type */
- u8 hlen; /* Header length */
- u8 loff; /* Data length offset in header */
- u8 lsize; /* Data length field size */
- u16 maxlen; /* Max overall packet length */
- int (*recv)(struct hci_dev *hdev, struct sk_buff *skb);
-};
-
-#define H4_RECV_ACL \
- .type = HCI_ACLDATA_PKT, \
- .hlen = HCI_ACL_HDR_SIZE, \
- .loff = 2, \
- .lsize = 2, \
- .maxlen = HCI_MAX_FRAME_SIZE \
-
-#define H4_RECV_SCO \
- .type = HCI_SCODATA_PKT, \
- .hlen = HCI_SCO_HDR_SIZE, \
- .loff = 2, \
- .lsize = 1, \
- .maxlen = HCI_MAX_SCO_SIZE
-
-#define H4_RECV_EVENT \
- .type = HCI_EVENT_PKT, \
- .hlen = HCI_EVENT_HDR_SIZE, \
- .loff = 1, \
- .lsize = 1, \
- .maxlen = HCI_MAX_EVENT_SIZE
-
-#define H4_RECV_ISO \
- .type = HCI_ISODATA_PKT, \
- .hlen = HCI_ISO_HDR_SIZE, \
- .loff = 2, \
- .lsize = 2, \
- .maxlen = HCI_MAX_FRAME_SIZE \
-
#ifdef CONFIG_BT_HCIUART_H4
int h4_init(void);
int h4_deinit(void);
diff --git a/include/net/bluetooth/hci_h4.h b/include/net/bluetooth/hci_h4.h
new file mode 100644
index 000000000000..a37e7df8c9ce
--- /dev/null
+++ b/include/net/bluetooth/hci_h4.h
@@ -0,0 +1,60 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Bluetooth HCI H:4 packet reassembly
+ *
+ * Copyright (C) 2000-2001 Qualcomm Incorporated
+ * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com>
+ * Copyright (C) 2004-2005 Marcel Holtmann <marcel@holtmann.org>
+ */
+
+#ifndef __HCI_H4_H
+#define __HCI_H4_H
+
+#include <linux/skbuff.h>
+#include <linux/types.h>
+
+struct hci_dev;
+
+struct h4_recv_pkt {
+ u8 type; /* Packet type */
+ u8 hlen; /* Header length */
+ u8 loff; /* Data length offset in header */
+ u8 lsize; /* Data length field size */
+ u16 maxlen; /* Max overall packet length */
+ int (*recv)(struct hci_dev *hdev, struct sk_buff *skb);
+};
+
+#define H4_RECV_ACL \
+ .type = HCI_ACLDATA_PKT, \
+ .hlen = HCI_ACL_HDR_SIZE, \
+ .loff = 2, \
+ .lsize = 2, \
+ .maxlen = HCI_MAX_FRAME_SIZE \
+
+#define H4_RECV_SCO \
+ .type = HCI_SCODATA_PKT, \
+ .hlen = HCI_SCO_HDR_SIZE, \
+ .loff = 2, \
+ .lsize = 1, \
+ .maxlen = HCI_MAX_SCO_SIZE
+
+#define H4_RECV_EVENT \
+ .type = HCI_EVENT_PKT, \
+ .hlen = HCI_EVENT_HDR_SIZE, \
+ .loff = 1, \
+ .lsize = 1, \
+ .maxlen = HCI_MAX_EVENT_SIZE
+
+#define H4_RECV_ISO \
+ .type = HCI_ISODATA_PKT, \
+ .hlen = HCI_ISO_HDR_SIZE, \
+ .loff = 2, \
+ .lsize = 2, \
+ .maxlen = HCI_MAX_FRAME_SIZE \
+
+struct sk_buff *h4_recv_skb(struct hci_dev *hdev, u8 *alignment, u8 *padding,
+ struct sk_buff *skb, const unsigned char *buffer,
+ int count, const struct h4_recv_pkt *pkts,
+ int pkts_count);
+
+#endif /* __HCI_H4_H */
diff --git a/net/bluetooth/Makefile b/net/bluetooth/Makefile
index ff466ea97436..b78ad98864d4 100644
--- a/net/bluetooth/Makefile
+++ b/net/bluetooth/Makefile
@@ -14,7 +14,7 @@ bluetooth_6lowpan-y := 6lowpan.o
bluetooth-y := af_bluetooth.o hci_core.o hci_conn.o hci_event.o mgmt.o \
hci_sock.o hci_sysfs.o l2cap_core.o l2cap_sock.o smp.o lib.o \
ecdh_helper.o mgmt_util.o mgmt_config.o hci_codec.o eir.o hci_sync.o \
- hci_drv.o
+ hci_drv.o hci_h4.o
bluetooth-$(CONFIG_DEV_COREDUMP) += coredump.o
diff --git a/net/bluetooth/hci_h4.c b/net/bluetooth/hci_h4.c
new file mode 100644
index 000000000000..86f809018062
--- /dev/null
+++ b/net/bluetooth/hci_h4.c
@@ -0,0 +1,160 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Bluetooth HCI H:4 packet reassembly
+ *
+ * Copyright (C) 2000-2001 Qualcomm Incorporated
+ * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com>
+ * Copyright (C) 2004-2005 Marcel Holtmann <marcel@holtmann.org>
+ */
+
+#include <linux/export.h>
+#include <linux/skbuff.h>
+#include <linux/unaligned.h>
+
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci_core.h>
+#include <net/bluetooth/hci_h4.h>
+
+/* h4_recv_skb - Reassemble H:4 framed packets
+ * @hdev: HCI device the packets are received on
+ * @alignment: optional packet alignment, NULL or 0 means no alignment
+ * @padding: optional padding state carried over between calls
+ * @skb: partially received packet from a previous call, may be NULL or an
+ * ERR_PTR returned by a previous call
+ * @buffer: buffer holding the received data
+ * @count: number of bytes in @buffer
+ * @pkts: table of supported packet types
+ * @pkts_count: number of entries in @pkts
+ *
+ * Returns the partially received packet to be passed to the next call, or an
+ * ERR_PTR on error. The returned value can be fed back into this function as
+ * is, but must be checked with IS_ERR() before being freed.
+ */
+struct sk_buff *h4_recv_skb(struct hci_dev *hdev, u8 *alignment, u8 *padding,
+ struct sk_buff *skb, const unsigned char *buffer,
+ int count, const struct h4_recv_pkt *pkts,
+ int pkts_count)
+{
+ u8 align = alignment && *alignment ? *alignment : 1;
+
+ /* Check for error from previous call */
+ if (IS_ERR(skb))
+ skb = NULL;
+
+ while (count) {
+ int i, len;
+
+ /* remove padding bytes from buffer */
+ if (padding) {
+ for (; (*padding) && count > 0; (*padding)--) {
+ count--;
+ buffer++;
+ }
+ }
+
+ if (!count)
+ break;
+
+ if (!skb) {
+ for (i = 0; i < pkts_count; i++) {
+ if (buffer[0] != pkts[i].type)
+ continue;
+
+ skb = bt_skb_alloc(pkts[i].maxlen,
+ GFP_ATOMIC);
+ if (!skb)
+ return ERR_PTR(-ENOMEM);
+
+ hci_skb_pkt_type(skb) = pkts[i].type;
+ hci_skb_expect(skb) = pkts[i].hlen;
+ break;
+ }
+
+ /* Check for invalid packet type */
+ if (!skb)
+ return ERR_PTR(-EILSEQ);
+
+ count -= 1;
+ buffer += 1;
+ }
+
+ len = min_t(uint, hci_skb_expect(skb) - skb->len, count);
+ skb_put_data(skb, buffer, len);
+
+ count -= len;
+ buffer += len;
+
+ /* Check for partial packet */
+ if (skb->len < hci_skb_expect(skb))
+ continue;
+
+ for (i = 0; i < pkts_count; i++) {
+ if (hci_skb_pkt_type(skb) == pkts[i].type)
+ break;
+ }
+
+ if (i >= pkts_count) {
+ kfree_skb(skb);
+ return ERR_PTR(-EILSEQ);
+ }
+
+ if (skb->len == pkts[i].hlen) {
+ u16 dlen;
+
+ switch (pkts[i].lsize) {
+ case 0:
+ /* No variable data length */
+ dlen = 0;
+ break;
+ case 1:
+ /* Single octet variable length */
+ dlen = skb->data[pkts[i].loff];
+ hci_skb_expect(skb) += dlen;
+
+ if (skb_tailroom(skb) < dlen) {
+ kfree_skb(skb);
+ return ERR_PTR(-EMSGSIZE);
+ }
+ break;
+ case 2:
+ /* Double octet variable length */
+ dlen = get_unaligned_le16(skb->data +
+ pkts[i].loff);
+ hci_skb_expect(skb) += dlen;
+
+ if (skb_tailroom(skb) < dlen) {
+ kfree_skb(skb);
+ return ERR_PTR(-EMSGSIZE);
+ }
+ break;
+ default:
+ /* Unsupported variable length */
+ kfree_skb(skb);
+ return ERR_PTR(-EILSEQ);
+ }
+
+ if (!dlen) {
+ if (padding) {
+ *padding = (skb->len + 1) % align;
+ *padding = (align - *padding) % align;
+ }
+
+ /* No more data, complete frame */
+ pkts[i].recv(hdev, skb);
+ skb = NULL;
+ }
+ } else {
+ if (padding) {
+ *padding = (skb->len + 1) % align;
+ *padding = (align - *padding) % align;
+ }
+
+ /* Complete frame */
+ pkts[i].recv(hdev, skb);
+ skb = NULL;
+ }
+ }
+
+ return skb;
+}
+EXPORT_SYMBOL_GPL(h4_recv_skb);
--
2.54.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH v2 2/2] Bluetooth: btusb: Add support for Bulk Serialization Mode
2026-08-28 20:13 [PATCH v2 1/2] Bluetooth: Move H:4 reassembly into the Bluetooth core Luiz Augusto von Dentz
@ 2026-08-28 20:13 ` Luiz Augusto von Dentz
2026-08-28 22:23 ` [v2,1/2] Bluetooth: Move H:4 reassembly into the Bluetooth core bluez.test.bot
2026-09-03 20:00 ` [PATCH v2 1/2] " patchwork-bot+bluetooth
2 siblings, 0 replies; 4+ messages in thread
From: Luiz Augusto von Dentz @ 2026-08-28 20:13 UTC (permalink / raw)
To: linux-bluetooth
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 | 170 +++++++++++++++++++++++++++++---------
1 file changed, 130 insertions(+), 40 deletions(-)
diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c
index 84614e60d142..b9c62d6acab5 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) {
@@ -2141,6 +2183,34 @@ 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);
+
+ 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);
@@ -2148,6 +2218,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);
@@ -2174,35 +2247,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);
@@ -2416,10 +2469,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);
@@ -2475,9 +2527,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;
@@ -3951,8 +4003,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)
@@ -4051,7 +4106,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;
}
@@ -4105,6 +4160,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 *ep;
struct btusb_data *data;
struct hci_dev *hdev;
unsigned ifnum_base;
@@ -4146,10 +4203,36 @@ static int btusb_probe(struct usb_interface *intf,
return -ENOMEM;
data->match_id = id;
+
+ /* Alternate setting 1 with a single pair of bulk endpoints 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, &ep) &&
+ !usb_find_common_endpoints(alt, &ep, &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;
@@ -4361,6 +4444,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);
@@ -4470,7 +4559,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
^ permalink raw reply related [flat|nested] 4+ messages in thread
* RE: [v2,1/2] Bluetooth: Move H:4 reassembly into the Bluetooth core
2026-08-28 20:13 [PATCH v2 1/2] Bluetooth: Move H:4 reassembly into the Bluetooth core Luiz Augusto von Dentz
2026-08-28 20:13 ` [PATCH v2 2/2] Bluetooth: btusb: Add support for Bulk Serialization Mode Luiz Augusto von Dentz
@ 2026-08-28 22:23 ` bluez.test.bot
2026-09-03 20:00 ` [PATCH v2 1/2] " patchwork-bot+bluetooth
2 siblings, 0 replies; 4+ messages in thread
From: bluez.test.bot @ 2026-08-28 22:23 UTC (permalink / raw)
To: linux-bluetooth, luiz.dentz
[-- Attachment #1: Type: text/plain, Size: 3106 bytes --]
This is automated email and please do not reply to this email!
Dear submitter,
Thank you for submitting the patches to the linux bluetooth mailing list.
This is a CI test results with your patch series:
PW Link:https://patchwork.kernel.org/project/bluetooth/list/?series=1153424
---Test result---
Test Summary:
CheckPatch FAIL 2.44 seconds
VerifyFixes PASS 0.07 seconds
VerifySignedoff PASS 0.07 seconds
GitLint PASS 0.42 seconds
SubjectPrefix PASS 0.13 seconds
BuildKernel PASS 27.53 seconds
CheckAllWarning PASS 30.43 seconds
CheckSparse PASS 28.92 seconds
BuildKernel32 PASS 26.63 seconds
CheckKernelLLVM SKIP 0.00 seconds
TestRunnerSetup PASS 503.25 seconds
TestRunner_l2cap-tester PASS 64.88 seconds
TestRunner_iso-tester PASS 114.18 seconds
TestRunner_bnep-tester PASS 19.21 seconds
TestRunner_mgmt-tester FAIL 220.15 seconds
TestRunner_rfcomm-tester PASS 25.35 seconds
TestRunner_sco-tester PASS 31.40 seconds
TestRunner_ioctl-tester PASS 26.35 seconds
TestRunner_mesh-tester FAIL 25.91 seconds
TestRunner_smp-tester PASS 24.79 seconds
TestRunner_userchan-tester PASS 20.76 seconds
TestRunner_6lowpan-tester PASS 23.14 seconds
IncrementalBuild PASS 27.63 seconds
Details
##############################
Test: CheckPatch - FAIL
Desc: Run checkpatch.pl script
Output:
[v2,1/2] Bluetooth: Move H:4 reassembly into the Bluetooth core
WARNING: added, moved or deleted file(s), does MAINTAINERS need updating?
#327:
new file mode 100644
total: 0 errors, 1 warnings, 0 checks, 419 lines checked
NOTE: For some of the reported defects, checkpatch may be able to
mechanically convert to the typical style using --fix or --fix-inplace.
/github/workspace/src/patch/14774101.patch has style problems, please review.
NOTE: Ignored message types: UNKNOWN_COMMIT_ID
NOTE: If any of the errors are false positives, please report
them to the maintainer, see CHECKPATCH in MAINTAINERS.
##############################
Test: CheckKernelLLVM - SKIP
Desc: Build kernel with LLVM + context analysis
Output:
Clang not found
##############################
Test: TestRunner_mgmt-tester - FAIL
Desc: Run mgmt-tester with test-runner
Output:
Total: 501, Passed: 496 (99.0%), Failed: 1, Not Run: 4
Failed Test Cases
Read Exp Feature - Success Failed 0.243 seconds
##############################
Test: TestRunner_mesh-tester - FAIL
Desc: Run mesh-tester with test-runner
Output:
Total: 10, Passed: 8 (80.0%), Failed: 2, Not Run: 0
Failed Test Cases
Mesh - Send cancel - 1 Timed out 1.967 seconds
Mesh - Send cancel - 2 Timed out 1.989 seconds
https://github.com/bluez/bluetooth-next/pull/662
---
Regards,
Linux Bluetooth
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH v2 1/2] Bluetooth: Move H:4 reassembly into the Bluetooth core
2026-08-28 20:13 [PATCH v2 1/2] Bluetooth: Move H:4 reassembly into the Bluetooth core Luiz Augusto von Dentz
2026-08-28 20:13 ` [PATCH v2 2/2] Bluetooth: btusb: Add support for Bulk Serialization Mode Luiz Augusto von Dentz
2026-08-28 22:23 ` [v2,1/2] Bluetooth: Move H:4 reassembly into the Bluetooth core bluez.test.bot
@ 2026-09-03 20:00 ` patchwork-bot+bluetooth
2 siblings, 0 replies; 4+ messages in thread
From: patchwork-bot+bluetooth @ 2026-09-03 20:00 UTC (permalink / raw)
To: Luiz Augusto von Dentz; +Cc: linux-bluetooth
Hello:
This series was applied to bluetooth/bluetooth-next.git (master)
by Luiz Augusto von Dentz <luiz.von.dentz@intel.com>:
On Fri, 28 Aug 2026 16:13:05 -0400 you wrote:
> From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
>
> h4_recv_buf() is currently implemented in hci_h4.c which is only built as
> part of the hci_uart module, and only when CONFIG_BT_HCIUART_H4 is
> enabled. That makes the H:4 reassembly logic unusable by drivers which do
> not depend on hci_uart, e.g. btusb which needs it to implement Bulk
> Serialization Mode.
>
> [...]
Here is the summary with links:
- [v2,1/2] Bluetooth: Move H:4 reassembly into the Bluetooth core
https://git.kernel.org/bluetooth/bluetooth-next/c/37521ec2f55f
- [v2,2/2] Bluetooth: btusb: Add support for Bulk Serialization Mode
(no matching commit)
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-09-03 20:01 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-28 20:13 [PATCH v2 1/2] Bluetooth: Move H:4 reassembly into the Bluetooth core Luiz Augusto von Dentz
2026-08-28 20:13 ` [PATCH v2 2/2] Bluetooth: btusb: Add support for Bulk Serialization Mode Luiz Augusto von Dentz
2026-08-28 22:23 ` [v2,1/2] Bluetooth: Move H:4 reassembly into the Bluetooth core bluez.test.bot
2026-09-03 20:00 ` [PATCH v2 1/2] " patchwork-bot+bluetooth
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox