Re: [PATCH v2 2/4] Bluetooth: Add generic support for vendor HCI packets

From: Luiz Augusto von Dentz

Date: Mon Aug 31 2026 - 15:21:20 EST


Hi Zijun,

On Mon, Aug 31, 2026 at 1:04 AM Zijun Hu <zijun.hu@xxxxxxxxxxxxxxxx> wrote:
>
> 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.

I don't find this one very useful, as btmon already supports handling
vendor events:

{ 0xff, "Vendor", vendor_evt, 0, false },

This will complicate things because some drivers might label packets
as vendor while others might inline them as an HCI vendor event. From
the spec's point of view there is no generic HCI vendor packet, so the
PERI approach really stands out as a non-compliant solution because it
uses reserved opcodes.

> 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
>


--
Luiz Augusto von Dentz