From: Luiz Augusto von Dentz <luiz.dentz@gmail.com>
To: linux-bluetooth@vger.kernel.org
Subject: [PATCH v2 1/2] Bluetooth: Move H:4 reassembly into the Bluetooth core
Date: Fri, 28 Aug 2026 16:13:05 -0400 [thread overview]
Message-ID: <20260828201306.593974-1-luiz.dentz@gmail.com> (raw)
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
next reply other threads:[~2026-08-28 20:13 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-28 20:13 Luiz Augusto von Dentz [this message]
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
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=20260828201306.593974-1-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