[PATCH RFC v2 2/3] Bluetooth: Add generic support for vendor HCI frames using HCI_VENDOR_PKT

From: Zijun Hu

Date: Wed Jul 22 2026 - 12:31:22 EST


The virtual HCI_VENDOR_PKT (0xff) has been defined but never used by
the BT core. Now, there is a requirement to implement it, as follows:

For Qualcomm multi-subsystem BT chips, the transport wire carries both
BT-HCI and PERI-HCI frames. PERI is a subsystem on the chip. Take the
upcoming QCC2072 support as an example:

Packet type BT-HCI indicator PERI-HCI indicator
-----------------------------------------------------------------
CMD (Host -> Controller) 0x01 0x31
ACL Data (bidirectional) 0x02 0x32
EVENT (Controller -> Host) 0x04 0x34

To generically support vendor HCI frames:

- Show them in btmon logs as they appear on the wire.
- Allow userspace to send/receive them via HCI_CHANNEL_USER, with a
socket option to control RX, defaulting off to eliminate regression
risk for existing applications.
- Add hdev->recv_vendor_pkt() to handle vendor frames in hci_rx_work()
like BT frame handlers.
- Add hci_send_vendor_frame() API similar to existing __hci_cmd_send().

Signed-off-by: Zijun Hu <zijun.hu@xxxxxxxxxxxxxxxx>
---
include/net/bluetooth/bluetooth.h | 2 ++
include/net/bluetooth/hci.h | 1 +
include/net/bluetooth/hci_core.h | 4 +++
include/net/bluetooth/hci_mon.h | 2 ++
net/bluetooth/hci_core.c | 38 ++++++++++++++++++++++++++++
net/bluetooth/hci_sock.c | 53 +++++++++++++++++++++++++++++++++++----
6 files changed, 95 insertions(+), 5 deletions(-)

diff --git a/include/net/bluetooth/bluetooth.h b/include/net/bluetooth/bluetooth.h
index b624da5026f5..d9870a43a2c4 100644
--- a/include/net/bluetooth/bluetooth.h
+++ b/include/net/bluetooth/bluetooth.h
@@ -256,6 +256,8 @@ struct bt_codecs {

#define BT_SCM_PKT_SEQNUM 0x05

+#define BT_RCV_VENDOR_PKT 23
+
__printf(1, 2)
void bt_info(const char *fmt, ...);
__printf(1, 2)
diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h
index 50f0eef71fb1..ac97550cbdba 100644
--- a/include/net/bluetooth/hci.h
+++ b/include/net/bluetooth/hci.h
@@ -398,6 +398,7 @@ enum {
/* HCI socket flags */
enum {
HCI_SOCK_TRUSTED,
+ HCI_SOCK_RCV_VENDOR_PKT,
HCI_MGMT_INDEX_EVENTS,
HCI_MGMT_UNCONF_INDEX_EVENTS,
HCI_MGMT_EXT_INDEX_EVENTS,
diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h
index e7133ff87fbf..428261591288 100644
--- a/include/net/bluetooth/hci_core.h
+++ b/include/net/bluetooth/hci_core.h
@@ -28,6 +28,7 @@
#include <linux/rculist.h>
#include <linux/spinlock.h>
#include <linux/srcu.h>
+#include <linux/uio.h>

#include <net/bluetooth/hci.h>
#include <net/bluetooth/hci_drv.h>
@@ -645,6 +646,7 @@ struct hci_dev {
int (*setup)(struct hci_dev *hdev);
int (*shutdown)(struct hci_dev *hdev);
int (*send)(struct hci_dev *hdev, struct sk_buff *skb);
+ void (*recv_vendor_pkt)(struct hci_dev *hdev, struct sk_buff *skb);
void (*notify)(struct hci_dev *hdev, unsigned int evt);
void (*hw_error)(struct hci_dev *hdev, u8 code);
int (*post_init)(struct hci_dev *hdev);
@@ -2327,6 +2329,8 @@ static inline int hci_check_conn_params(u16 min, u16 max, u16 latency,
int hci_register_cb(struct hci_cb *hcb);
int hci_unregister_cb(struct hci_cb *hcb);

+int hci_send_vendor_frame(struct hci_dev *hdev, struct iov_iter *iter);
+
int __hci_cmd_send(struct hci_dev *hdev, u16 opcode, u32 plen,
const void *param);

diff --git a/include/net/bluetooth/hci_mon.h b/include/net/bluetooth/hci_mon.h
index 4b2a0af4ed58..7710688c0d30 100644
--- a/include/net/bluetooth/hci_mon.h
+++ b/include/net/bluetooth/hci_mon.h
@@ -50,6 +50,8 @@ struct hci_mon_hdr {
#define HCI_MON_ISO_RX_PKT 19
#define HCI_MON_DRV_TX_PKT 20
#define HCI_MON_DRV_RX_PKT 21
+#define HCI_MON_VENDOR_TX_PKT 22
+#define HCI_MON_VENDOR_RX_PKT 23

struct hci_mon_new_index {
__u8 type;
diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c
index 1e324c05af24..90807562e8ed 100644
--- a/net/bluetooth/hci_core.c
+++ b/net/bluetooth/hci_core.c
@@ -2917,6 +2917,8 @@ int hci_recv_frame(struct hci_dev *hdev, struct sk_buff *skb)
break;
case HCI_ISODATA_PKT:
break;
+ case HCI_VENDOR_PKT:
+ break;
case HCI_DRV_PKT:
break;
default:
@@ -3053,6 +3055,33 @@ static int hci_send_conn_frame(struct hci_dev *hdev, struct hci_conn *conn,
return hci_send_frame(hdev, skb);
}

+int hci_send_vendor_frame(struct hci_dev *hdev, struct iov_iter *iter)
+{
+ struct sk_buff *skb;
+ size_t len;
+
+ if (WARN_ON(!iov_iter_is_kvec(iter)))
+ return -EINVAL;
+
+ len = iov_iter_count(iter);
+ if (!len)
+ return -EINVAL;
+
+ skb = bt_skb_alloc(len, GFP_KERNEL);
+ if (!skb)
+ return -ENOMEM;
+
+ if (!copy_from_iter_full(skb_put(skb, len), len, iter)) {
+ kfree_skb(skb);
+ return -EFAULT;
+ }
+
+ hci_skb_pkt_type(skb) = HCI_VENDOR_PKT;
+
+ return hci_send_frame(hdev, skb);
+}
+EXPORT_SYMBOL(hci_send_vendor_frame);
+
/* Send HCI command */
int hci_send_cmd(struct hci_dev *hdev, __u16 opcode, __u32 plen,
const void *param)
@@ -4057,6 +4086,15 @@ static void hci_rx_work(struct work_struct *work)
hci_isodata_packet(hdev, skb);
break;

+ case HCI_VENDOR_PKT:
+ BT_DBG("%s Vendor packet with type 0x%2.2x",
+ hdev->name, hci_skb_pkt_type(skb));
+ if (hdev->recv_vendor_pkt)
+ hdev->recv_vendor_pkt(hdev, skb);
+ else
+ kfree_skb(skb);
+ break;
+
case HCI_DRV_PKT:
kfree_skb(skb);
break;
diff --git a/net/bluetooth/hci_sock.c b/net/bluetooth/hci_sock.c
index 070ca388f9ac..060c265e2c53 100644
--- a/net/bluetooth/hci_sock.c
+++ b/net/bluetooth/hci_sock.c
@@ -229,12 +229,16 @@ void hci_send_to_sock(struct hci_dev *hdev, struct sk_buff *skb)
} else if (hci_pi(sk)->channel == HCI_CHANNEL_USER) {
if (!bt_cb(skb)->incoming)
continue;
- if (hci_skb_pkt_type(skb) != HCI_EVENT_PKT &&
- hci_skb_pkt_type(skb) != HCI_ACLDATA_PKT &&
- hci_skb_pkt_type(skb) != HCI_SCODATA_PKT &&
- hci_skb_pkt_type(skb) != HCI_ISODATA_PKT &&
- hci_skb_pkt_type(skb) != HCI_DRV_PKT)
+ if (hci_skb_pkt_type(skb) == HCI_VENDOR_PKT) {
+ if (!hci_sock_test_flag(sk, HCI_SOCK_RCV_VENDOR_PKT))
+ continue;
+ } else if (hci_skb_pkt_type(skb) != HCI_EVENT_PKT &&
+ hci_skb_pkt_type(skb) != HCI_ACLDATA_PKT &&
+ hci_skb_pkt_type(skb) != HCI_SCODATA_PKT &&
+ hci_skb_pkt_type(skb) != HCI_ISODATA_PKT &&
+ hci_skb_pkt_type(skb) != HCI_DRV_PKT) {
continue;
+ }
} else {
/* Don't send frame to other channel types */
continue;
@@ -390,6 +394,12 @@ void hci_send_to_monitor(struct hci_dev *hdev, struct sk_buff *skb)
else
opcode = cpu_to_le16(HCI_MON_ISO_TX_PKT);
break;
+ case HCI_VENDOR_PKT:
+ if (bt_cb(skb)->incoming)
+ opcode = cpu_to_le16(HCI_MON_VENDOR_RX_PKT);
+ else
+ opcode = cpu_to_le16(HCI_MON_VENDOR_TX_PKT);
+ break;
case HCI_DRV_PKT:
if (bt_cb(skb)->incoming)
opcode = cpu_to_le16(HCI_MON_DRV_RX_PKT);
@@ -1868,6 +1878,7 @@ static int hci_sock_sendmsg(struct socket *sock, struct msghdr *msg,
hci_skb_pkt_type(skb) != HCI_ACLDATA_PKT &&
hci_skb_pkt_type(skb) != HCI_SCODATA_PKT &&
hci_skb_pkt_type(skb) != HCI_ISODATA_PKT &&
+ hci_skb_pkt_type(skb) != HCI_VENDOR_PKT &&
hci_skb_pkt_type(skb) != HCI_DRV_PKT) {
err = -EINVAL;
goto drop;
@@ -2017,6 +2028,7 @@ static int hci_sock_setsockopt(struct socket *sock, int level, int optname,
{
struct sock *sk = sock->sk;
int err = 0;
+ int opt_int;
u16 opt;

BT_DBG("sk %p, opt %d", sk, optname);
@@ -2050,6 +2062,23 @@ static int hci_sock_setsockopt(struct socket *sock, int level, int optname,
hci_pi(sk)->mtu = opt;
break;

+ case BT_RCV_VENDOR_PKT:
+ if (hci_pi(sk)->channel != HCI_CHANNEL_USER) {
+ err = -ENOPROTOOPT;
+ break;
+ }
+
+ err = copy_safe_from_sockptr(&opt_int, sizeof(opt_int),
+ optval, optlen);
+ if (err)
+ break;
+
+ if (opt_int)
+ hci_sock_set_flag(sk, HCI_SOCK_RCV_VENDOR_PKT);
+ else
+ hci_sock_clear_flag(sk, HCI_SOCK_RCV_VENDOR_PKT);
+ break;
+
default:
err = -ENOPROTOOPT;
break;
@@ -2132,6 +2161,7 @@ static int hci_sock_getsockopt(struct socket *sock, int level, int optname,
{
struct sock *sk = sock->sk;
int err = 0;
+ int opt_int;
u16 mtu;

BT_DBG("sk %p, opt %d", sk, optname);
@@ -2153,6 +2183,19 @@ static int hci_sock_getsockopt(struct socket *sock, int level, int optname,
err = -EFAULT;
break;

+ case BT_RCV_VENDOR_PKT:
+ if (hci_pi(sk)->channel != HCI_CHANNEL_USER) {
+ err = -ENOPROTOOPT;
+ break;
+ }
+
+ opt_int = hci_sock_test_flag(sk, HCI_SOCK_RCV_VENDOR_PKT) ?
+ 1 : 0;
+ if (copy_to_iter(&opt_int, sizeof(opt_int),
+ &sopt->iter_out) != sizeof(opt_int))
+ err = -EFAULT;
+ break;
+
default:
err = -ENOPROTOOPT;
break;

--
2.34.1