[PATCH v2 2/4] Bluetooth: Add generic support for vendor HCI packets
From: Zijun Hu
Date: Mon Aug 31 2026 - 01:05:28 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, where PERI is a subsystem in the chip, take
the upcoming QCC2072 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
Implement HCI_VENDOR_PKT to generically support vendor HCI packets:
- Log them in btmon as they appear on the wire.
- Allow userspace to send/receive them over HCI_CHANNEL_USER, gate RX
with the new socket option BT_RECV_VENDOR_PKT, off by default to
avoid regressions for existing applications.
- Add hdev->recv_vendor_pkt() to handle them in hci_rx_work().
- Add hci_send_vendor_frame() to send an HCI_VENDOR_PKT frame.
Signed-off-by: Zijun Hu <zijun.hu@xxxxxxxxxxxxxxxx>
---
Previous version:
https://lore.kernel.org/all/20260722-support_vendor_hci-v2-2-132b506460a4@xxxxxxxxxxxxxxxx
Changes since previous version:
- Change BT_RECV_VENDOR_PKT type from int to u32
- Rename macro and enum from *_RCV_* to *_RECV_*
- Improve the commit title and message
---
include/net/bluetooth/bluetooth.h | 6 +++++
include/net/bluetooth/hci.h | 1 +
include/net/bluetooth/hci_core.h | 5 ++++
include/net/bluetooth/hci_mon.h | 2 ++
net/bluetooth/hci_core.c | 44 ++++++++++++++++++++++++++++++++
net/bluetooth/hci_sock.c | 53 +++++++++++++++++++++++++++++++++++----
6 files changed, 106 insertions(+), 5 deletions(-)
diff --git a/include/net/bluetooth/bluetooth.h b/include/net/bluetooth/bluetooth.h
index b624da5026f5..f9b380060976 100644
--- a/include/net/bluetooth/bluetooth.h
+++ b/include/net/bluetooth/bluetooth.h
@@ -251,16 +251,22 @@ struct bt_codecs {
#define BT_ISO_BASE 20
/* Socket option value 21 reserved */
#define BT_PKT_SEQNUM 22
#define BT_SCM_PKT_SEQNUM 0x05
+/*
+ * Control receiving HCI_VENDOR_PKT over HCI_CHANNEL_USER, off
+ * by default to avoid regressions for existing applications.
+ */
+#define BT_RECV_VENDOR_PKT 23
+
__printf(1, 2)
void bt_info(const char *fmt, ...);
__printf(1, 2)
void bt_warn(const char *fmt, ...);
__printf(1, 2)
void bt_err(const char *fmt, ...);
#if IS_ENABLED(CONFIG_BT_FEATURE_DEBUG)
void bt_dbg_set(bool enable);
diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h
index 1641d879dbda..0145f629cdd5 100644
--- a/include/net/bluetooth/hci.h
+++ b/include/net/bluetooth/hci.h
@@ -393,16 +393,17 @@ enum {
HCI_RAW,
HCI_RESET,
};
/* HCI socket flags */
enum {
HCI_SOCK_TRUSTED,
+ HCI_SOCK_RECV_VENDOR_PKT,
HCI_MGMT_INDEX_EVENTS,
HCI_MGMT_UNCONF_INDEX_EVENTS,
HCI_MGMT_EXT_INDEX_EVENTS,
HCI_MGMT_EXT_INFO_EVENTS,
HCI_MGMT_OPTION_EVENTS,
HCI_MGMT_SETTING_EVENTS,
HCI_MGMT_DEV_CLASS_EVENTS,
HCI_MGMT_LOCAL_NAME_EVENTS,
diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h
index c12cd6873f65..b26004a05368 100644
--- a/include/net/bluetooth/hci_core.h
+++ b/include/net/bluetooth/hci_core.h
@@ -23,16 +23,17 @@
#ifndef __HCI_CORE_H
#define __HCI_CORE_H
#include <linux/idr.h>
#include <linux/leds.h>
#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>
#include <net/bluetooth/hci_sync.h>
#include <net/bluetooth/hci_sock.h>
#include <net/bluetooth/coredump.h>
/* HCI priority */
@@ -641,16 +642,18 @@ struct hci_dev {
#endif
int (*open)(struct hci_dev *hdev);
int (*close)(struct hci_dev *hdev);
int (*flush)(struct hci_dev *hdev);
int (*setup)(struct hci_dev *hdev);
int (*shutdown)(struct hci_dev *hdev);
int (*send)(struct hci_dev *hdev, struct sk_buff *skb);
+ /* Receive HCI_VENDOR_PKT */
+ void (*recv_vendor_pkt)(struct hci_dev *hdev, struct sk_buff *skb);
/* Handle HCI_EV_VENDOR; return true if handled, false otherwise */
bool (*handle_ev_vendor)(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);
int (*set_diag)(struct hci_dev *hdev, bool enable);
int (*set_bdaddr)(struct hci_dev *hdev, const bdaddr_t *bdaddr);
void (*reset)(struct hci_dev *hdev);
@@ -2395,16 +2398,18 @@ static inline int hci_check_conn_params(u16 min, u16 max, u16 latency,
}
return 0;
}
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);
int hci_send_cmd(struct hci_dev *hdev, __u16 opcode, __u32 plen,
const void *param);
void hci_send_acl(struct hci_chan *chan, struct sk_buff *skb, __u16 flags);
void hci_send_sco(struct hci_conn *conn, struct sk_buff *skb);
void hci_send_iso(struct hci_conn *conn, struct sk_buff *skb);
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
@@ -45,16 +45,18 @@ struct hci_mon_hdr {
#define HCI_MON_CTRL_OPEN 14
#define HCI_MON_CTRL_CLOSE 15
#define HCI_MON_CTRL_COMMAND 16
#define HCI_MON_CTRL_EVENT 17
#define HCI_MON_ISO_TX_PKT 18
#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;
__u8 bus;
bdaddr_t bdaddr;
char name[8] __nonstring;
} __packed;
#define HCI_MON_NEW_INDEX_SIZE 16
diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c
index 88df159d3393..2663654cdf60 100644
--- a/net/bluetooth/hci_core.c
+++ b/net/bluetooth/hci_core.c
@@ -2911,16 +2911,18 @@ int hci_recv_frame(struct hci_dev *hdev, struct sk_buff *skb)
type == PA_LINK)
hci_skb_pkt_type(skb) = HCI_ISODATA_PKT;
}
break;
case HCI_SCODATA_PKT:
break;
case HCI_ISODATA_PKT:
break;
+ case HCI_VENDOR_PKT:
+ break;
case HCI_DRV_PKT:
break;
default:
kfree_skb(skb);
return -EINVAL;
}
/* Incoming skb */
@@ -3047,16 +3049,50 @@ static int hci_send_frame(struct hci_dev *hdev, struct sk_buff *skb)
static int hci_send_conn_frame(struct hci_dev *hdev, struct hci_conn *conn,
struct sk_buff *skb)
{
hci_conn_tx_queue(conn, skb);
return hci_send_frame(hdev, skb);
}
+/**
+ * hci_send_vendor_frame - Send an HCI_VENDOR_PKT frame to the HCI driver
+ * @hdev: The HCI device
+ * @iter: iov_iter carrying the frame
+ *
+ * Return: 0 on success, or a negative errno on failure.
+ */
+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)
{
struct sk_buff *skb;
BT_DBG("%s opcode 0x%4.4x plen %d", hdev->name, opcode, plen);
@@ -4051,16 +4087,24 @@ static void hci_rx_work(struct work_struct *work)
hci_scodata_packet(hdev, skb);
break;
case HCI_ISODATA_PKT:
BT_DBG("%s ISO data packet", hdev->name);
hci_isodata_packet(hdev, skb);
break;
+ case HCI_VENDOR_PKT:
+ BT_DBG("%s Vendor packet", hdev->name);
+ if (hdev->recv_vendor_pkt)
+ hdev->recv_vendor_pkt(hdev, skb);
+ else
+ kfree_skb(skb);
+ break;
+
default:
kfree_skb(skb);
break;
}
}
}
static int hci_send_cmd_sync(struct hci_dev *hdev, struct sk_buff *skb)
diff --git a/net/bluetooth/hci_sock.c b/net/bluetooth/hci_sock.c
index 070ca388f9ac..4b328fa8dff4 100644
--- a/net/bluetooth/hci_sock.c
+++ b/net/bluetooth/hci_sock.c
@@ -224,22 +224,26 @@ void hci_send_to_sock(struct hci_dev *hdev, struct sk_buff *skb)
hci_skb_pkt_type(skb) != HCI_SCODATA_PKT &&
hci_skb_pkt_type(skb) != HCI_ISODATA_PKT)
continue;
if (is_filtered_packet(sk, skb))
continue;
} 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_RECV_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;
}
if (!skb_copy) {
/* Create a private copy with headroom */
skb_copy = __pskb_copy_fclone(skb, 1, GFP_ATOMIC, true);
@@ -385,16 +389,22 @@ void hci_send_to_monitor(struct hci_dev *hdev, struct sk_buff *skb)
opcode = cpu_to_le16(HCI_MON_SCO_TX_PKT);
break;
case HCI_ISODATA_PKT:
if (bt_cb(skb)->incoming)
opcode = cpu_to_le16(HCI_MON_ISO_RX_PKT);
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);
else
opcode = cpu_to_le16(HCI_MON_DRV_TX_PKT);
break;
case HCI_DIAG_PKT:
opcode = cpu_to_le16(HCI_MON_VENDOR_DIAG);
@@ -1863,16 +1873,17 @@ static int hci_sock_sendmsg(struct socket *sock, struct msghdr *msg,
* since that gets enforced when binding the socket.
*
* However check that the packet type is valid.
*/
if (hci_skb_pkt_type(skb) != HCI_COMMAND_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_VENDOR_PKT &&
hci_skb_pkt_type(skb) != HCI_DRV_PKT) {
err = -EINVAL;
goto drop;
}
skb_queue_tail(&hdev->raw_q, skb);
queue_work(hdev->workqueue, &hdev->tx_work);
} else if (hci_skb_pkt_type(skb) == HCI_COMMAND_PKT) {
@@ -2012,16 +2023,17 @@ static int hci_sock_setsockopt_old(struct socket *sock, int level, int optname,
return err;
}
static int hci_sock_setsockopt(struct socket *sock, int level, int optname,
sockptr_t optval, unsigned int optlen)
{
struct sock *sk = sock->sk;
int err = 0;
+ u32 opt_u32;
u16 opt;
BT_DBG("sk %p, opt %d", sk, optname);
if (level == SOL_HCI)
return hci_sock_setsockopt_old(sock, level, optname, optval,
optlen);
@@ -2045,16 +2057,33 @@ static int hci_sock_setsockopt(struct socket *sock, int level, int optname,
err = copy_safe_from_sockptr(&opt, sizeof(opt), optval, optlen);
if (err)
break;
hci_pi(sk)->mtu = opt;
break;
+ case BT_RECV_VENDOR_PKT:
+ if (hci_pi(sk)->channel != HCI_CHANNEL_USER) {
+ err = -ENOPROTOOPT;
+ break;
+ }
+
+ err = copy_safe_from_sockptr(&opt_u32, sizeof(opt_u32),
+ optval, optlen);
+ if (err)
+ break;
+
+ if (opt_u32)
+ hci_sock_set_flag(sk, HCI_SOCK_RECV_VENDOR_PKT);
+ else
+ hci_sock_clear_flag(sk, HCI_SOCK_RECV_VENDOR_PKT);
+ break;
+
default:
err = -ENOPROTOOPT;
break;
}
done:
release_sock(sk);
return err;
@@ -2127,16 +2156,17 @@ static int hci_sock_getsockopt_old(struct socket *sock, int level, int optname,
return err;
}
static int hci_sock_getsockopt(struct socket *sock, int level, int optname,
sockopt_t *sopt)
{
struct sock *sk = sock->sk;
int err = 0;
+ u32 opt_u32;
u16 mtu;
BT_DBG("sk %p, opt %d", sk, optname);
if (level == SOL_HCI)
return hci_sock_getsockopt_old(sock, level, optname, sopt);
if (level != SOL_BLUETOOTH)
@@ -2148,16 +2178,29 @@ static int hci_sock_getsockopt(struct socket *sock, int level, int optname,
case BT_SNDMTU:
case BT_RCVMTU:
mtu = hci_pi(sk)->mtu;
if (copy_to_iter(&mtu, sizeof(mtu), &sopt->iter_out) !=
sizeof(mtu))
err = -EFAULT;
break;
+ case BT_RECV_VENDOR_PKT:
+ if (hci_pi(sk)->channel != HCI_CHANNEL_USER) {
+ err = -ENOPROTOOPT;
+ break;
+ }
+
+ opt_u32 = hci_sock_test_flag(sk, HCI_SOCK_RECV_VENDOR_PKT) ?
+ 1 : 0;
+ if (copy_to_iter(&opt_u32, sizeof(opt_u32),
+ &sopt->iter_out) != sizeof(opt_u32))
+ err = -EFAULT;
+ break;
+
default:
err = -ENOPROTOOPT;
break;
}
release_sock(sk);
return err;
}
--
2.34.1