* [PATCH 2/2] Bluetooth: virtio: process RX buffers in virtqueue callback
2026-08-07 16:03 [PATCH 0/2] Bluetooth: virtio: improve RX handling Igor Skalkin
2026-08-07 16:03 ` [PATCH 1/2] Bluetooth: virtio: fix RX buffer size Igor Skalkin
@ 2026-08-07 16:03 ` Igor Skalkin
1 sibling, 0 replies; 4+ messages in thread
From: Igor Skalkin @ 2026-08-07 16:03 UTC (permalink / raw)
To: Marcel Holtmann, Luiz Augusto von Dentz
Cc: linux-bluetooth, linux-kernel, Trilok Soni, Igor Skalkin
The RX virtqueue is sized for many in-flight descriptors (64 on
virtio-bt-pci), but the driver only ever posts a single buffer:
virtbt_open_vdev() posts one buffer at open, and the RX callback
replenishes exactly the one buffer it just consumed before kicking
again. The device can never have more than one RX descriptor
available, so every received frame serializes behind a full
kick/interrupt/workqueue round trip before the next one can even be
queued.
Drain all completed RX buffers from the virtqueue callback and pass
valid frames directly to hci_recv_frame(). Populate the RX ring until
it is full and replenish each consumed buffer in callback context with
GFP_ATOMIC. Keep a worker solely to retry refills with GFP_KERNEL
after an atomic allocation failure.
Serialize callback, refill, and teardown access to the RX virtqueue so
a refill cannot race with device removal.
Signed-off-by: Igor Skalkin <igor.skalkin@oss.qualcomm.com>
---
drivers/bluetooth/virtio_bt.c | 137 ++++++++++++++++++++++++++--------
1 file changed, 107 insertions(+), 30 deletions(-)
diff --git a/drivers/bluetooth/virtio_bt.c b/drivers/bluetooth/virtio_bt.c
index 96f21501e..cb42b322b 100644
--- a/drivers/bluetooth/virtio_bt.c
+++ b/drivers/bluetooth/virtio_bt.c
@@ -23,24 +23,39 @@ enum {
struct virtio_bluetooth {
struct virtio_device *vdev;
struct virtqueue *vqs[VIRTBT_NUM_VQS];
- struct work_struct rx;
+ struct work_struct rx_refill;
+ /* Serializes RX virtqueue operations and teardown. */
+ spinlock_t rx_lock;
struct hci_dev *hdev;
+ unsigned int rx_buf_count;
+ bool stopped;
};
-static int virtbt_add_inbuf(struct virtio_bluetooth *vbt)
+static int virtbt_add_inbuf(struct virtio_bluetooth *vbt, gfp_t gfp)
{
struct virtqueue *vq = vbt->vqs[VIRTBT_VQ_RX];
struct scatterlist sg[1];
struct sk_buff *skb;
+ unsigned long flags;
int err;
- skb = alloc_skb(VIRTBT_RX_BUF_SIZE, GFP_KERNEL);
+ skb = alloc_skb(VIRTBT_RX_BUF_SIZE, gfp);
if (!skb)
return -ENOMEM;
sg_init_one(sg, skb->data, VIRTBT_RX_BUF_SIZE);
- err = virtqueue_add_inbuf(vq, sg, 1, skb, GFP_KERNEL);
+ spin_lock_irqsave(&vbt->rx_lock, flags);
+ if (vbt->stopped) {
+ err = -ESHUTDOWN;
+ } else {
+ /* The lock serializes RX callback and refill worker producers. */
+ err = virtqueue_add_inbuf(vq, sg, 1, skb, GFP_ATOMIC);
+ if (!err)
+ vbt->rx_buf_count++;
+ }
+ spin_unlock_irqrestore(&vbt->rx_lock, flags);
+
if (err < 0) {
kfree_skb(skb);
return err;
@@ -56,10 +71,20 @@ static int virtbt_open(struct hci_dev *hdev)
static int virtbt_open_vdev(struct virtio_bluetooth *vbt)
{
- if (virtbt_add_inbuf(vbt) < 0)
+ struct virtqueue *vq = vbt->vqs[VIRTBT_VQ_RX];
+ int err;
+
+ if (!virtqueue_get_vring_size(vq))
return -EIO;
- virtqueue_kick(vbt->vqs[VIRTBT_VQ_RX]);
+ do {
+ err = virtbt_add_inbuf(vbt, GFP_KERNEL);
+ } while (!err);
+
+ if (!vbt->rx_buf_count)
+ return err;
+
+ virtqueue_kick(vq);
return 0;
}
@@ -70,9 +95,15 @@ static int virtbt_close(struct hci_dev *hdev)
static int virtbt_close_vdev(struct virtio_bluetooth *vbt)
{
+ unsigned long flags;
int i;
- cancel_work_sync(&vbt->rx);
+ spin_lock_irqsave(&vbt->rx_lock, flags);
+ vbt->stopped = true;
+ virtqueue_disable_cb(vbt->vqs[VIRTBT_VQ_RX]);
+ spin_unlock_irqrestore(&vbt->rx_lock, flags);
+
+ cancel_work_sync(&vbt->rx_refill);
for (i = 0; i < ARRAY_SIZE(vbt->vqs); i++) {
struct virtqueue *vq = vbt->vqs[i];
@@ -83,6 +114,10 @@ static int virtbt_close_vdev(struct virtio_bluetooth *vbt)
cond_resched();
}
+ spin_lock_irqsave(&vbt->rx_lock, flags);
+ vbt->rx_buf_count = 0;
+ spin_unlock_irqrestore(&vbt->rx_lock, flags);
+
return 0;
}
@@ -234,31 +269,17 @@ static void virtbt_rx_handle(struct virtio_bluetooth *vbt, struct sk_buff *skb)
hci_recv_frame(vbt->hdev, skb);
}
-static void virtbt_rx_work(struct work_struct *work)
+static void virtbt_rx_refill_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]\n",
- len, VIRTBT_RX_BUF_SIZE);
- kfree_skb(skb);
- } else {
- skb_put(skb, len);
- virtbt_rx_handle(vbt, skb);
- }
+ struct virtio_bluetooth, rx_refill);
+ bool kick = false;
- if (virtbt_add_inbuf(vbt) < 0)
- return;
+ while (!virtbt_add_inbuf(vbt, GFP_KERNEL))
+ kick = true;
- virtqueue_kick(vbt->vqs[VIRTBT_VQ_RX]);
+ if (kick)
+ virtqueue_kick(vbt->vqs[VIRTBT_VQ_RX]);
}
static void virtbt_tx_done(struct virtqueue *vq)
@@ -273,8 +294,63 @@ static void virtbt_tx_done(struct virtqueue *vq)
static void virtbt_rx_done(struct virtqueue *vq)
{
struct virtio_bluetooth *vbt = vq->vdev->priv;
+ bool cb_enabled = true;
+ bool kick = false;
+
+ for (;;) {
+ struct sk_buff *skb;
+ unsigned int len;
+ unsigned long flags;
+
+ spin_lock_irqsave(&vbt->rx_lock, flags);
+ if (vbt->stopped) {
+ spin_unlock_irqrestore(&vbt->rx_lock, flags);
+ return;
+ }
+
+ if (cb_enabled) {
+ virtqueue_disable_cb(vq);
+ cb_enabled = false;
+ }
+
+ skb = virtqueue_get_buf(vq, &len);
+ if (skb)
+ vbt->rx_buf_count--;
+
+ if (!skb) {
+ if (kick) {
+ spin_unlock_irqrestore(&vbt->rx_lock, flags);
+ kick = false;
+ virtqueue_kick(vq);
+ continue;
+ }
+
+ if (virtqueue_enable_cb(vq)) {
+ spin_unlock_irqrestore(&vbt->rx_lock, flags);
+ return;
+ }
+ cb_enabled = true;
+ }
+ spin_unlock_irqrestore(&vbt->rx_lock, flags);
- schedule_work(&vbt->rx);
+ if (!skb)
+ continue;
+
+ if (!len || len > VIRTBT_RX_BUF_SIZE) {
+ bt_dev_err_ratelimited(vbt->hdev,
+ "rx reply len %u outside [1, %u]\n",
+ len, VIRTBT_RX_BUF_SIZE);
+ kfree_skb(skb);
+ } else {
+ skb_put(skb, len);
+ virtbt_rx_handle(vbt, skb);
+ }
+
+ if (virtbt_add_inbuf(vbt, GFP_ATOMIC) < 0)
+ schedule_work(&vbt->rx_refill);
+ else
+ kick = true;
+ }
}
static int virtbt_probe(struct virtio_device *vdev)
@@ -307,7 +383,8 @@ static int virtbt_probe(struct virtio_device *vdev)
vdev->priv = vbt;
vbt->vdev = vdev;
- INIT_WORK(&vbt->rx, virtbt_rx_work);
+ INIT_WORK(&vbt->rx_refill, virtbt_rx_refill_work);
+ spin_lock_init(&vbt->rx_lock);
err = virtio_find_vqs(vdev, VIRTBT_NUM_VQS, vbt->vqs, vqs_info, NULL);
if (err)
--
2.53.0
^ permalink raw reply related [flat|nested] 4+ messages in thread