mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-10-26 10:57:38 +08:00
bt_dev_err_ratelimited() and bt_dev_warn_ratelimited() pass the format string to bt_err_ratelimited()/bt_warn_ratelimited() without appending a newline, unlike bt_dev_err() and bt_dev_warn(), which go through BT_ERR()/BT_WARN() and add one. Only 2 of the 22 in-tree callers (both in virtio_bt) put the newline in their own format string. A printk record without a trailing newline is stored with prb_commit() instead of prb_final_commit(), and readers of the log buffer (/dev/kmsg, syslog) do not see it until it is finalized, which only happens when the next record is reserved. These messages therefore stay invisible until some unrelated message is printed, and journald, which timestamps a record when it reads it, logs them with the time of that later message. This was observed with a MediaTek MT7921 controller (USB 04ca:3802): when an eSCO link is closed, the host still receives a few SCO packets for the handle after Disconnection Complete, so hci_scodata_packet() logs "SCO packet for unknown connection handle". Comparing the record's kernel timestamp (_SOURCE_MONOTONIC_TIMESTAMP) with the time journald received it, over one capture: emitted logged delay 15:36:52.896 15:37:00.214 7.3 s 15:37:03.410 15:39:05.574 122.2 s 15:39:05.574 15:39:12.506 6.9 s 15:39:19.791 15:40:54.109 94.3 s 15:41:22.021 15:41:24.647 2.6 s Each message showed up together with the next kernel message; the 94 s one looked as if it had been logged in the middle of a later, healthy eSCO connection. bt_dev_err_ratelimited() lost its newline in commit657cc64647("Bluetooth: Remove usage of BT_ERR_RATELIMITED macro"), which replaced BT_ERR_RATELIMITED(), the variant that appended "\n", with a direct call to bt_err_ratelimited(). bt_dev_warn_ratelimited() never had one. Append the newline in both macros, as BT_ERR() and BT_WARN() do, and drop it from the two virtio_bt format strings so that their messages do not gain an empty line. Fixes:36278a5d4d("Bluetooth: Adding a bt_dev_warn_ratelimited macro.") Fixes:657cc64647("Bluetooth: Remove usage of BT_ERR_RATELIMITED macro") Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Hitalo Souza <enghitalo@gmail.com> Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
460 lines
9.5 KiB
C
460 lines
9.5 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/virtio.h>
|
|
#include <linux/virtio_config.h>
|
|
#include <linux/skbuff.h>
|
|
|
|
#include <uapi/linux/virtio_ids.h>
|
|
#include <uapi/linux/virtio_bt.h>
|
|
|
|
#include <net/bluetooth/bluetooth.h>
|
|
#include <net/bluetooth/hci_core.h>
|
|
|
|
#define VERSION "0.1"
|
|
#define VIRTBT_RX_BUF_SIZE 1000
|
|
|
|
enum {
|
|
VIRTBT_VQ_TX,
|
|
VIRTBT_VQ_RX,
|
|
VIRTBT_NUM_VQS,
|
|
};
|
|
|
|
struct virtio_bluetooth {
|
|
struct virtio_device *vdev;
|
|
struct virtqueue *vqs[VIRTBT_NUM_VQS];
|
|
struct work_struct rx;
|
|
struct hci_dev *hdev;
|
|
};
|
|
|
|
static int virtbt_add_inbuf(struct virtio_bluetooth *vbt)
|
|
{
|
|
struct virtqueue *vq = vbt->vqs[VIRTBT_VQ_RX];
|
|
struct scatterlist sg[1];
|
|
struct sk_buff *skb;
|
|
int err;
|
|
|
|
skb = alloc_skb(VIRTBT_RX_BUF_SIZE, GFP_KERNEL);
|
|
if (!skb)
|
|
return -ENOMEM;
|
|
|
|
sg_init_one(sg, skb->data, VIRTBT_RX_BUF_SIZE);
|
|
|
|
err = virtqueue_add_inbuf(vq, sg, 1, skb, GFP_KERNEL);
|
|
if (err < 0) {
|
|
kfree_skb(skb);
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int virtbt_open(struct hci_dev *hdev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int virtbt_open_vdev(struct virtio_bluetooth *vbt)
|
|
{
|
|
if (virtbt_add_inbuf(vbt) < 0)
|
|
return -EIO;
|
|
|
|
virtqueue_kick(vbt->vqs[VIRTBT_VQ_RX]);
|
|
return 0;
|
|
}
|
|
|
|
static int virtbt_close(struct hci_dev *hdev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int virtbt_close_vdev(struct virtio_bluetooth *vbt)
|
|
{
|
|
int i;
|
|
|
|
cancel_work_sync(&vbt->rx);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(vbt->vqs); i++) {
|
|
struct virtqueue *vq = vbt->vqs[i];
|
|
struct sk_buff *skb;
|
|
|
|
while ((skb = virtqueue_detach_unused_buf(vq)))
|
|
kfree_skb(skb);
|
|
cond_resched();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int virtbt_flush(struct hci_dev *hdev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int virtbt_send_frame(struct hci_dev *hdev, struct sk_buff *skb)
|
|
{
|
|
struct virtio_bluetooth *vbt = hci_get_drvdata(hdev);
|
|
struct scatterlist sg[1];
|
|
int err;
|
|
|
|
memcpy(skb_push(skb, 1), &hci_skb_pkt_type(skb), 1);
|
|
|
|
sg_init_one(sg, skb->data, skb->len);
|
|
err = virtqueue_add_outbuf(vbt->vqs[VIRTBT_VQ_TX], sg, 1, skb,
|
|
GFP_KERNEL);
|
|
if (err) {
|
|
kfree_skb(skb);
|
|
return err;
|
|
}
|
|
|
|
virtqueue_kick(vbt->vqs[VIRTBT_VQ_TX]);
|
|
return 0;
|
|
}
|
|
|
|
static int virtbt_setup_zephyr(struct hci_dev *hdev)
|
|
{
|
|
struct sk_buff *skb;
|
|
|
|
/* Read Build Information */
|
|
skb = __hci_cmd_sync(hdev, 0xfc08, 0, NULL, HCI_INIT_TIMEOUT);
|
|
if (IS_ERR(skb))
|
|
return PTR_ERR(skb);
|
|
|
|
/* Bounded print: the backend controls skb->len. */
|
|
if (skb->len > 1) {
|
|
int len = skb->len - 1;
|
|
|
|
bt_dev_info(hdev, "%.*s", len, (char *)(skb->data + 1));
|
|
hci_set_fw_info(hdev, "%.*s", len, skb->data + 1);
|
|
}
|
|
|
|
kfree_skb(skb);
|
|
return 0;
|
|
}
|
|
|
|
static int virtbt_set_bdaddr_zephyr(struct hci_dev *hdev,
|
|
const bdaddr_t *bdaddr)
|
|
{
|
|
struct sk_buff *skb;
|
|
|
|
/* Write BD_ADDR */
|
|
skb = __hci_cmd_sync(hdev, 0xfc06, 6, bdaddr, HCI_INIT_TIMEOUT);
|
|
if (IS_ERR(skb))
|
|
return PTR_ERR(skb);
|
|
|
|
kfree_skb(skb);
|
|
return 0;
|
|
}
|
|
|
|
static int virtbt_setup_intel(struct hci_dev *hdev)
|
|
{
|
|
struct sk_buff *skb;
|
|
|
|
/* Intel Read Version */
|
|
skb = __hci_cmd_sync(hdev, 0xfc05, 0, NULL, HCI_CMD_TIMEOUT);
|
|
if (IS_ERR(skb))
|
|
return PTR_ERR(skb);
|
|
|
|
kfree_skb(skb);
|
|
return 0;
|
|
}
|
|
|
|
static int virtbt_set_bdaddr_intel(struct hci_dev *hdev, const bdaddr_t *bdaddr)
|
|
{
|
|
struct sk_buff *skb;
|
|
|
|
/* Intel Write BD Address */
|
|
skb = __hci_cmd_sync(hdev, 0xfc31, 6, bdaddr, HCI_INIT_TIMEOUT);
|
|
if (IS_ERR(skb))
|
|
return PTR_ERR(skb);
|
|
|
|
kfree_skb(skb);
|
|
return 0;
|
|
}
|
|
|
|
static int virtbt_setup_realtek(struct hci_dev *hdev)
|
|
{
|
|
struct sk_buff *skb;
|
|
|
|
/* Read ROM Version */
|
|
skb = __hci_cmd_sync(hdev, 0xfc6d, 0, NULL, HCI_INIT_TIMEOUT);
|
|
if (IS_ERR(skb))
|
|
return PTR_ERR(skb);
|
|
|
|
bt_dev_info(hdev, "ROM version %u", *((__u8 *) (skb->data + 1)));
|
|
|
|
kfree_skb(skb);
|
|
return 0;
|
|
}
|
|
|
|
static int virtbt_shutdown_generic(struct hci_dev *hdev)
|
|
{
|
|
struct sk_buff *skb;
|
|
|
|
/* Reset */
|
|
skb = __hci_cmd_sync(hdev, HCI_OP_RESET, 0, NULL, HCI_INIT_TIMEOUT);
|
|
if (IS_ERR(skb))
|
|
return PTR_ERR(skb);
|
|
|
|
kfree_skb(skb);
|
|
return 0;
|
|
}
|
|
|
|
static void virtbt_rx_handle(struct virtio_bluetooth *vbt, struct sk_buff *skb)
|
|
{
|
|
size_t min_hdr;
|
|
__u8 pkt_type;
|
|
|
|
pkt_type = *((__u8 *) skb->data);
|
|
skb_pull(skb, 1);
|
|
|
|
switch (pkt_type) {
|
|
case HCI_EVENT_PKT:
|
|
min_hdr = sizeof(struct hci_event_hdr);
|
|
break;
|
|
case HCI_ACLDATA_PKT:
|
|
min_hdr = sizeof(struct hci_acl_hdr);
|
|
break;
|
|
case HCI_SCODATA_PKT:
|
|
min_hdr = sizeof(struct hci_sco_hdr);
|
|
break;
|
|
case HCI_ISODATA_PKT:
|
|
min_hdr = sizeof(struct hci_iso_hdr);
|
|
break;
|
|
default:
|
|
kfree_skb(skb);
|
|
return;
|
|
}
|
|
|
|
if (skb->len < min_hdr) {
|
|
bt_dev_err_ratelimited(vbt->hdev,
|
|
"rx pkt_type 0x%02x payload %u < hdr %zu",
|
|
pkt_type, skb->len, min_hdr);
|
|
kfree_skb(skb);
|
|
return;
|
|
}
|
|
|
|
hci_skb_pkt_type(skb) = pkt_type;
|
|
hci_recv_frame(vbt->hdev, skb);
|
|
}
|
|
|
|
static void virtbt_rx_work(struct work_struct *work)
|
|
{
|
|
struct virtio_bluetooth *vbt = container_of(work,
|
|
struct virtio_bluetooth, rx);
|
|
struct sk_buff *skb;
|
|
unsigned int len;
|
|
|
|
skb = virtqueue_get_buf(vbt->vqs[VIRTBT_VQ_RX], &len);
|
|
if (!skb)
|
|
return;
|
|
|
|
if (!len || len > VIRTBT_RX_BUF_SIZE) {
|
|
bt_dev_err_ratelimited(vbt->hdev,
|
|
"rx reply len %u outside [1, %u]",
|
|
len, VIRTBT_RX_BUF_SIZE);
|
|
kfree_skb(skb);
|
|
} else {
|
|
skb_put(skb, len);
|
|
virtbt_rx_handle(vbt, skb);
|
|
}
|
|
|
|
if (virtbt_add_inbuf(vbt) < 0)
|
|
return;
|
|
|
|
virtqueue_kick(vbt->vqs[VIRTBT_VQ_RX]);
|
|
}
|
|
|
|
static void virtbt_tx_done(struct virtqueue *vq)
|
|
{
|
|
struct sk_buff *skb;
|
|
unsigned int len;
|
|
|
|
while ((skb = virtqueue_get_buf(vq, &len)))
|
|
kfree_skb(skb);
|
|
}
|
|
|
|
static void virtbt_rx_done(struct virtqueue *vq)
|
|
{
|
|
struct virtio_bluetooth *vbt = vq->vdev->priv;
|
|
|
|
schedule_work(&vbt->rx);
|
|
}
|
|
|
|
static int virtbt_probe(struct virtio_device *vdev)
|
|
{
|
|
struct virtqueue_info vqs_info[VIRTBT_NUM_VQS] = {
|
|
[VIRTBT_VQ_TX] = { "tx", virtbt_tx_done },
|
|
[VIRTBT_VQ_RX] = { "rx", virtbt_rx_done },
|
|
};
|
|
struct virtio_bluetooth *vbt;
|
|
struct hci_dev *hdev;
|
|
int err;
|
|
__u8 type;
|
|
|
|
if (!virtio_has_feature(vdev, VIRTIO_F_VERSION_1))
|
|
return -ENODEV;
|
|
|
|
type = virtio_cread8(vdev, offsetof(struct virtio_bt_config, type));
|
|
|
|
switch (type) {
|
|
case VIRTIO_BT_CONFIG_TYPE_PRIMARY:
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
vbt = kzalloc_obj(*vbt);
|
|
if (!vbt)
|
|
return -ENOMEM;
|
|
|
|
vdev->priv = vbt;
|
|
vbt->vdev = vdev;
|
|
|
|
INIT_WORK(&vbt->rx, virtbt_rx_work);
|
|
|
|
err = virtio_find_vqs(vdev, VIRTBT_NUM_VQS, vbt->vqs, vqs_info, NULL);
|
|
if (err)
|
|
goto err_free_vbt;
|
|
|
|
hdev = hci_alloc_dev();
|
|
if (!hdev) {
|
|
err = -ENOMEM;
|
|
goto err_del_vqs;
|
|
}
|
|
|
|
vbt->hdev = hdev;
|
|
|
|
hdev->bus = HCI_VIRTIO;
|
|
hci_set_drvdata(hdev, vbt);
|
|
|
|
hdev->open = virtbt_open;
|
|
hdev->close = virtbt_close;
|
|
hdev->flush = virtbt_flush;
|
|
hdev->send = virtbt_send_frame;
|
|
|
|
if (virtio_has_feature(vdev, VIRTIO_BT_F_VND_HCI)) {
|
|
__u16 vendor;
|
|
|
|
if (virtio_has_feature(vdev, VIRTIO_BT_F_CONFIG_V2))
|
|
virtio_cread(vdev, struct virtio_bt_config_v2,
|
|
vendor, &vendor);
|
|
else
|
|
virtio_cread(vdev, struct virtio_bt_config,
|
|
vendor, &vendor);
|
|
|
|
switch (vendor) {
|
|
case VIRTIO_BT_CONFIG_VENDOR_ZEPHYR:
|
|
hdev->manufacturer = 1521;
|
|
hdev->setup = virtbt_setup_zephyr;
|
|
hdev->shutdown = virtbt_shutdown_generic;
|
|
hdev->set_bdaddr = virtbt_set_bdaddr_zephyr;
|
|
break;
|
|
|
|
case VIRTIO_BT_CONFIG_VENDOR_INTEL:
|
|
hdev->manufacturer = 2;
|
|
hdev->setup = virtbt_setup_intel;
|
|
hdev->shutdown = virtbt_shutdown_generic;
|
|
hdev->set_bdaddr = virtbt_set_bdaddr_intel;
|
|
hci_set_quirk(hdev, HCI_QUIRK_STRICT_DUPLICATE_FILTER);
|
|
hci_set_quirk(hdev, HCI_QUIRK_SIMULTANEOUS_DISCOVERY);
|
|
hci_set_quirk(hdev, HCI_QUIRK_WIDEBAND_SPEECH_SUPPORTED);
|
|
break;
|
|
|
|
case VIRTIO_BT_CONFIG_VENDOR_REALTEK:
|
|
hdev->manufacturer = 93;
|
|
hdev->setup = virtbt_setup_realtek;
|
|
hdev->shutdown = virtbt_shutdown_generic;
|
|
hci_set_quirk(hdev, HCI_QUIRK_SIMULTANEOUS_DISCOVERY);
|
|
hci_set_quirk(hdev, HCI_QUIRK_WIDEBAND_SPEECH_SUPPORTED);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (virtio_has_feature(vdev, VIRTIO_BT_F_MSFT_EXT)) {
|
|
__u16 msft_opcode;
|
|
|
|
if (virtio_has_feature(vdev, VIRTIO_BT_F_CONFIG_V2))
|
|
virtio_cread(vdev, struct virtio_bt_config_v2,
|
|
msft_opcode, &msft_opcode);
|
|
else
|
|
virtio_cread(vdev, struct virtio_bt_config,
|
|
msft_opcode, &msft_opcode);
|
|
|
|
hci_set_msft_opcode(hdev, msft_opcode);
|
|
}
|
|
|
|
if (virtio_has_feature(vdev, VIRTIO_BT_F_AOSP_EXT))
|
|
hci_set_aosp_capable(hdev);
|
|
|
|
if (hci_register_dev(hdev) < 0) {
|
|
hci_free_dev(hdev);
|
|
err = -EBUSY;
|
|
goto err_del_vqs;
|
|
}
|
|
|
|
virtio_device_ready(vdev);
|
|
err = virtbt_open_vdev(vbt);
|
|
if (err) {
|
|
hci_unregister_dev(hdev);
|
|
virtio_reset_device(vdev);
|
|
virtbt_close_vdev(vbt);
|
|
hci_free_dev(hdev);
|
|
goto err_del_vqs;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_del_vqs:
|
|
vdev->config->del_vqs(vdev);
|
|
err_free_vbt:
|
|
kfree(vbt);
|
|
return err;
|
|
}
|
|
|
|
static void virtbt_remove(struct virtio_device *vdev)
|
|
{
|
|
struct virtio_bluetooth *vbt = vdev->priv;
|
|
struct hci_dev *hdev = vbt->hdev;
|
|
|
|
hci_unregister_dev(hdev);
|
|
virtio_reset_device(vdev);
|
|
virtbt_close_vdev(vbt);
|
|
|
|
hci_free_dev(hdev);
|
|
vbt->hdev = NULL;
|
|
|
|
vdev->config->del_vqs(vdev);
|
|
kfree(vbt);
|
|
}
|
|
|
|
static struct virtio_device_id virtbt_table[] = {
|
|
{ VIRTIO_ID_BT, VIRTIO_DEV_ANY_ID },
|
|
{ 0 },
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(virtio, virtbt_table);
|
|
|
|
static const unsigned int virtbt_features[] = {
|
|
VIRTIO_BT_F_VND_HCI,
|
|
VIRTIO_BT_F_MSFT_EXT,
|
|
VIRTIO_BT_F_AOSP_EXT,
|
|
VIRTIO_BT_F_CONFIG_V2,
|
|
};
|
|
|
|
static struct virtio_driver virtbt_driver = {
|
|
.driver.name = KBUILD_MODNAME,
|
|
.feature_table = virtbt_features,
|
|
.feature_table_size = ARRAY_SIZE(virtbt_features),
|
|
.id_table = virtbt_table,
|
|
.probe = virtbt_probe,
|
|
.remove = virtbt_remove,
|
|
};
|
|
|
|
module_virtio_driver(virtbt_driver);
|
|
|
|
MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>");
|
|
MODULE_DESCRIPTION("Generic Bluetooth VIRTIO driver ver " VERSION);
|
|
MODULE_VERSION(VERSION);
|
|
MODULE_LICENSE("GPL");
|