[PATCH v3 2/4] Bluetooth: Add generic support for vendor packets
From: Zijun Hu
Date: Mon Sep 07 2026 - 05:02:40 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 packets, 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
There are also BT ACL packets with vendor-reserved handles found in
existing device drivers, as shown below:
+--------+--------+---------------------------+
| Vendor | Handle | Purpose |
+--------+--------+---------------------------+
| QCOM | 0xEDD | Firmware coredump |
| | 0xEDC | Firmware enhanced logging |
+--------+--------+---------------------------+
| MTK | 0xFC6F | Firmware coredump |
| | 0x05FF | Firmware debug logging 1 |
| | 0x05FE | Firmware debug logging 2 |
+--------+--------+---------------------------+
| NXP | 0xFFF | Firmware coredump |
+--------+--------+---------------------------+
Implement HCI_VENDOR_PKT to generically support such vendor packets,
which don't follow BT SIG's vendor-extension framework:
- Log them in btmon as they appear on the wire.
- Add hdev->recv_vendor_pkt() to handle them in hci_rx_work().
- Add hci_send_vendor_frame() to send them to the device driver.
- Allow them to flow over HCI_CHANNEL_USER, nested inside HCI_VENDOR_PKT.
Also solve the same issues that the commit below fixes for
HCI_EV_VENDOR, which follows BT SIG's vendor-extension framework:
commit 0bd606b31d40 ("Bluetooth: hci_event: Introduce
handle_ev_vendor() for HCI_EV_VENDOR")
Signed-off-by: Zijun Hu <zijun.hu@xxxxxxxxxxxxxxxx>
---
include/net/bluetooth/hci_core.h | 5 +++++
include/net/bluetooth/hci_mon.h | 2 ++
net/bluetooth/hci_core.c | 45 ++++++++++++++++++++++++++++++++++++++++
net/bluetooth/hci_sock.c | 8 +++++++
4 files changed, 60 insertions(+)
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 66840df8c020..40a225d41cc3 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,51 @@ 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;
+ unsigned int len;
+
+ if (WARN_ON(!iov_iter_is_kvec(iter)))
+ return -EINVAL;
+
+ /* Vendor frames are opaque, the caller guarantees the size. */
+ len = (unsigned int)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 +4088,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..406b70ecaf33 100644
--- a/net/bluetooth/hci_sock.c
+++ b/net/bluetooth/hci_sock.c
@@ -228,16 +228,17 @@ void hci_send_to_sock(struct hci_dev *hdev, struct sk_buff *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_VENDOR_PKT &&
hci_skb_pkt_type(skb) != HCI_DRV_PKT)
continue;
} else {
/* Don't send frame to other channel types */
continue;
}
if (!skb_copy) {
@@ -385,16 +386,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 +1870,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) {
--
2.34.1