* [PATCH 1/2] virtio_ring: add virtqueue_enable_cb_delayed_max
@ 2026-10-26 15:09 Michael S. Tsirkin
2026-10-26 15:09 ` [PATCH 2/2] virtio_balloon: submit reported pages as individual buffers Michael S. Tsirkin
0 siblings, 1 reply; 3+ messages in thread
From: Michael S. Tsirkin @ 2026-10-26 15:09 UTC (permalink / raw)
To: linux-kernel; +Cc: Jason Wang, Xuan Zhuo, Eugenio Pérez, virtualization
virtqueue_enable_cb_delayed places the used event at 3/4 of the
currently queued buffers, so with VIRTIO_RING_F_EVENT_IDX the device
interrupts early and often - this is a good fit for drivers that poll
and process buffers as they trickle in, like virtio net.
Some drivers queue a batch of buffers and then sleep waiting for the
whole batch to complete. For these, an interrupt before the last
buffer is used is pure waste.
To fix, add virtqueue_enable_cb_delayed_max: same API, but the used
event is placed at the last currently queued buffer, so the device
only notifies us once it has used everything.
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
---
drivers/virtio/virtio_ring.c | 125 +++++++++++++++++++++++++++++++++++
include/linux/virtio.h | 2 +
2 files changed, 127 insertions(+)
diff --git a/drivers/virtio/virtio_ring.c b/drivers/virtio/virtio_ring.c
index fbca7ce1c6bf..0428c0ae9a8a 100644
--- a/drivers/virtio/virtio_ring.c
+++ b/drivers/virtio/virtio_ring.c
@@ -180,6 +180,7 @@ struct virtqueue_ops {
bool (*kick_prepare)(struct vring_virtqueue *vq);
void (*disable_cb)(struct vring_virtqueue *vq);
bool (*enable_cb_delayed)(struct vring_virtqueue *vq);
+ bool (*enable_cb_delayed_max)(struct vring_virtqueue *vq);
unsigned int (*enable_cb_prepare)(struct vring_virtqueue *vq);
bool (*poll)(const struct vring_virtqueue *vq,
unsigned int last_used_idx);
@@ -1122,6 +1123,42 @@ static bool virtqueue_enable_cb_delayed_split(struct vring_virtqueue *vq)
return true;
}
+static bool virtqueue_enable_cb_delayed_max_split(struct vring_virtqueue *vq)
+{
+ u16 bufs;
+
+ START_USE(vq);
+
+ /* We optimistically turn back on interrupts, then check if there was
+ * more to do. */
+ /* Depending on the VIRTIO_RING_F_USED_EVENT_IDX feature, we need to
+ * either clear the flags bit or point the event index at the next
+ * entry. Always update the event index to keep code simple. */
+ if (vq->split.avail_flags_shadow & VRING_AVAIL_F_NO_INTERRUPT) {
+ vq->split.avail_flags_shadow &= ~VRING_AVAIL_F_NO_INTERRUPT;
+ if (!vq->event)
+ vq->split.vring.avail->flags =
+ cpu_to_virtio16(vq->vq.vdev,
+ vq->split.avail_flags_shadow);
+ }
+ /* Point the used event at the last queued buffer: the device
+ * will only notify us once it has used everything. */
+ bufs = (u16)(vq->split.avail_idx_shadow - vq->last_used_idx);
+
+ virtio_store_mb(vq->weak_barriers,
+ &vring_used_event(&vq->split.vring),
+ cpu_to_virtio16(vq->vq.vdev, vq->last_used_idx + bufs));
+
+ if (unlikely((u16)(virtio16_to_cpu(vq->vq.vdev, vq->split.vring.used->idx)
+ - vq->last_used_idx) > bufs)) {
+ END_USE(vq);
+ return false;
+ }
+
+ END_USE(vq);
+ return true;
+}
+
static void *virtqueue_detach_unused_buf_split(struct vring_virtqueue *vq)
{
unsigned int i;
@@ -2318,6 +2355,67 @@ static bool virtqueue_enable_cb_delayed_packed(struct vring_virtqueue *vq)
return true;
}
+static bool virtqueue_enable_cb_delayed_max_packed(struct vring_virtqueue *vq)
+{
+ u16 used_idx, wrap_counter, last_used_idx;
+ u16 bufs;
+
+ START_USE(vq);
+
+ /*
+ * We optimistically turn back on interrupts, then check if there was
+ * more to do.
+ */
+
+ if (vq->event) {
+ /* Point the event at the last queued buffer: the device
+ * will only notify us once it has used everything. */
+ bufs = vq->packed.vring.num - vq->vq.num_free;
+ last_used_idx = READ_ONCE(vq->last_used_idx);
+ wrap_counter = packed_used_wrap_counter(last_used_idx);
+
+ used_idx = packed_last_used(last_used_idx) + bufs;
+ if (used_idx >= vq->packed.vring.num) {
+ used_idx -= vq->packed.vring.num;
+ wrap_counter ^= 1;
+ }
+
+ vq->packed.vring.driver->off_wrap = cpu_to_le16(used_idx |
+ (wrap_counter << VRING_PACKED_EVENT_F_WRAP_CTR));
+
+ /*
+ * We need to update event offset and event wrap
+ * counter first before updating event flags.
+ */
+ virtio_wmb(vq->weak_barriers);
+ }
+
+ if (vq->packed.event_flags_shadow == VRING_PACKED_EVENT_FLAG_DISABLE) {
+ vq->packed.event_flags_shadow = vq->event ?
+ VRING_PACKED_EVENT_FLAG_DESC :
+ VRING_PACKED_EVENT_FLAG_ENABLE;
+ vq->packed.vring.driver->flags =
+ cpu_to_le16(vq->packed.event_flags_shadow);
+ }
+
+ /*
+ * We need to update event suppression structure first
+ * before re-checking for more used buffers.
+ */
+ virtio_mb(vq->weak_barriers);
+
+ last_used_idx = READ_ONCE(vq->last_used_idx);
+ wrap_counter = packed_used_wrap_counter(last_used_idx);
+ used_idx = packed_last_used(last_used_idx);
+ if (is_used_desc_packed(vq, used_idx, wrap_counter)) {
+ END_USE(vq);
+ return false;
+ }
+
+ END_USE(vq);
+ return true;
+}
+
static void *virtqueue_detach_unused_buf_packed(struct vring_virtqueue *vq)
{
unsigned int i;
@@ -2640,6 +2738,7 @@ static const struct virtqueue_ops split_ops = {
.kick_prepare = virtqueue_kick_prepare_split,
.disable_cb = virtqueue_disable_cb_split,
.enable_cb_delayed = virtqueue_enable_cb_delayed_split,
+ .enable_cb_delayed_max = virtqueue_enable_cb_delayed_max_split,
.enable_cb_prepare = virtqueue_enable_cb_prepare_split,
.poll = virtqueue_poll_split,
.detach_unused_buf = virtqueue_detach_unused_buf_split,
@@ -2654,6 +2753,7 @@ static const struct virtqueue_ops packed_ops = {
.kick_prepare = virtqueue_kick_prepare_packed,
.disable_cb = virtqueue_disable_cb_packed,
.enable_cb_delayed = virtqueue_enable_cb_delayed_packed,
+ .enable_cb_delayed_max = virtqueue_enable_cb_delayed_max_packed,
.enable_cb_prepare = virtqueue_enable_cb_prepare_packed,
.poll = virtqueue_poll_packed,
.detach_unused_buf = virtqueue_detach_unused_buf_packed,
@@ -2668,6 +2768,7 @@ static const struct virtqueue_ops split_in_order_ops = {
.kick_prepare = virtqueue_kick_prepare_split,
.disable_cb = virtqueue_disable_cb_split,
.enable_cb_delayed = virtqueue_enable_cb_delayed_split,
+ .enable_cb_delayed_max = virtqueue_enable_cb_delayed_max_split,
.enable_cb_prepare = virtqueue_enable_cb_prepare_split,
.poll = virtqueue_poll_split,
.detach_unused_buf = virtqueue_detach_unused_buf_split,
@@ -2682,6 +2783,7 @@ static const struct virtqueue_ops packed_in_order_ops = {
.kick_prepare = virtqueue_kick_prepare_packed,
.disable_cb = virtqueue_disable_cb_packed,
.enable_cb_delayed = virtqueue_enable_cb_delayed_packed,
+ .enable_cb_delayed_max = virtqueue_enable_cb_delayed_max_packed,
.enable_cb_prepare = virtqueue_enable_cb_prepare_packed,
.poll = virtqueue_poll_packed,
.detach_unused_buf = virtqueue_detach_unused_buf_packed,
@@ -3197,6 +3299,29 @@ bool virtqueue_enable_cb_delayed(struct virtqueue *_vq)
}
EXPORT_SYMBOL_GPL(virtqueue_enable_cb_delayed);
+/**
+ * virtqueue_enable_cb_delayed_max - callback for all queued buffers
+ * @_vq: the struct virtqueue we're talking about.
+ *
+ * Like virtqueue_enable_cb_delayed, but with VIRTIO_RING_F_EVENT_IDX
+ * the used event is placed at the last currently queued buffer, so the
+ * device only notifies us once it has used everything we queued.
+ *
+ * Returns "true" if the callback is armed and we can sleep waiting
+ * for the notification, or "false" if some buffers were already used
+ * and we should poll instead.
+ */
+bool virtqueue_enable_cb_delayed_max(struct virtqueue *_vq)
+{
+ struct vring_virtqueue *vq = to_vvq(_vq);
+
+ if (vq->event_triggered)
+ data_race(vq->event_triggered = false);
+
+ return VIRTQUEUE_CALL(vq, enable_cb_delayed_max);
+}
+EXPORT_SYMBOL_GPL(virtqueue_enable_cb_delayed_max);
+
/**
* virtqueue_detach_unused_buf - detach first unused buffer
* @_vq: the struct virtqueue we're talking about.
diff --git a/include/linux/virtio.h b/include/linux/virtio.h
index 3bbc4cb6a672..214fa36b8eed 100644
--- a/include/linux/virtio.h
+++ b/include/linux/virtio.h
@@ -114,6 +114,8 @@ bool virtqueue_poll(struct virtqueue *vq, unsigned);
bool virtqueue_enable_cb_delayed(struct virtqueue *vq);
+bool virtqueue_enable_cb_delayed_max(struct virtqueue *vq);
+
void *virtqueue_detach_unused_buf(struct virtqueue *vq);
unsigned int virtqueue_get_vring_size(const struct virtqueue *vq);
--
MST
^ permalink raw reply [flat|nested] 3+ messages in thread* [PATCH 2/2] virtio_balloon: submit reported pages as individual buffers
2026-10-26 15:09 [PATCH 1/2] virtio_ring: add virtqueue_enable_cb_delayed_max Michael S. Tsirkin
@ 2026-10-26 15:09 ` Michael S. Tsirkin
2026-10-26 16:00 ` Michael S. Tsirkin
0 siblings, 1 reply; 3+ messages in thread
From: Michael S. Tsirkin @ 2026-10-26 15:09 UTC (permalink / raw)
To: linux-kernel
Cc: Jason Wang, Xuan Zhuo, Eugenio Pérez, virtualization,
Ruben Hakobyan, David Hildenbrand, Miguel Ojeda, Boqun Feng,
Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Alice Ryhl, Trevor Gross, Danilo Krummrich, rust-for-linux
The virtio_balloon driver reports a scatter-gather list of free pages
to the VMM so it may discard unused pages.
For an arm64 kernel with 64 KiB pages, the default reporting and
allocation orders allow entries ranging from 2 MiB to 512 MiB. If the
report contains a sufficient number of large entries, we will end up
with a descriptor chain that represents over 4 GiB of memory. This
violates virtio spec, which says:
Drivers MUST NOT add a descriptor chain longer than $2^{32}$ bytes in total;
Specifically, submitting $2^{32}$ or more breaks Rust-VMM.
Submit each reported page as a separate virtqueue buffer instead of one
buffer with an sg list of all pages. This also avoids indirect
descriptor allocation (kmalloc in the reporting path).
To avoid multiple buffers causing interrupt storms, keep callbacks
disabled while we wait for an event.
Note: if the virtqueue is broken, wait_event on
virtqueue_get_buf hangs. This is a pre-existing issue:
the old single-buffer code had the same hang.
Reported-by: Ruben Hakobyan <hruben@meta.com>
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
---
Lightly tested. Ruben, could you pls help test?
drivers/virtio/virtio_balloon.c | 48 ++++++++++++++++++++++++---------
1 file changed, 35 insertions(+), 13 deletions(-)
diff --git a/drivers/virtio/virtio_balloon.c b/drivers/virtio/virtio_balloon.c
index f6c2dff33f8a..363519fa890d 100644
--- a/drivers/virtio/virtio_balloon.c
+++ b/drivers/virtio/virtio_balloon.c
@@ -202,25 +202,47 @@ static int virtballoon_free_page_report(struct page_reporting_dev_info *pr_dev_i
struct virtio_balloon *vb =
container_of(pr_dev_info, struct virtio_balloon, pr_dev_info);
struct virtqueue *vq = vb->reporting_vq;
- unsigned int unused, err;
+ unsigned int i, err = 0;
/* We should always be able to add these buffers to an empty queue. */
- err = virtqueue_add_inbuf(vq, sg, nents, vb, GFP_NOWAIT);
+ for (i = 0; i < nents; i++) {
+ struct scatterlist one;
- /*
- * In the extremely unlikely case that something has occurred and we
- * are able to trigger an error we will simply display a warning
- * and exit without actually processing the pages.
- */
- if (WARN_ON_ONCE(err))
- return err;
+ sg_init_table(&one, 1);
+ sg_set_page(&one, sg_page(&sg[i]), sg[i].length,
+ sg[i].offset);
+ err = virtqueue_add_inbuf(vq, &one, 1, &sg[i], GFP_NOWAIT);
- virtqueue_kick(vq);
+ /*
+ * In the extremely unlikely case that something has occurred
+ * and we are able to trigger an error we will simply display
+ * a warning and exit without actually processing the pages.
+ */
+ if (WARN_ON_ONCE(err)) {
+ nents = i;
+ break;
+ }
+ }
- /* When host has read buffer, this completes via balloon_ack */
- wait_event(vb->acked, virtqueue_get_buf(vq, &unused));
+ if (nents) {
+ virtqueue_kick(vq);
- return 0;
+ /* When host has read buffer, this completes via balloon_ack.
+ * We report pages as individual buffers, so arm the callback
+ * to only interrupt once all the queued buffers have been
+ * used: otherwise we would get one interrupt per page.
+ */
+ for (i = 0; i < nents; i++) {
+ unsigned int unused;
+
+ virtqueue_enable_cb_delayed_max(vq);
+ wait_event(vb->acked,
+ virtqueue_get_buf(vq, &unused));
+ virtqueue_disable_cb(vq);
+ }
+ }
+
+ return err;
}
static void set_page_pfns(struct virtio_balloon *vb,
--
MST
^ permalink raw reply [flat|nested] 3+ messages in thread* Re: [PATCH 2/2] virtio_balloon: submit reported pages as individual buffers
2026-10-26 15:09 ` [PATCH 2/2] virtio_balloon: submit reported pages as individual buffers Michael S. Tsirkin
@ 2026-10-26 16:00 ` Michael S. Tsirkin
0 siblings, 0 replies; 3+ messages in thread
From: Michael S. Tsirkin @ 2026-10-26 16:00 UTC (permalink / raw)
To: linux-kernel
Cc: Jason Wang, Xuan Zhuo, Eugenio Pérez, virtualization,
Ruben Hakobyan, David Hildenbrand, Miguel Ojeda, Boqun Feng,
Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Alice Ryhl, Trevor Gross, Danilo Krummrich, rust-for-linux
On Mon, Oct 26, 2026 at 11:09:59AM -0400, Michael S. Tsirkin wrote:
> The virtio_balloon driver reports a scatter-gather list of free pages
> to the VMM so it may discard unused pages.
>
> For an arm64 kernel with 64 KiB pages, the default reporting and
> allocation orders allow entries ranging from 2 MiB to 512 MiB. If the
> report contains a sufficient number of large entries, we will end up
> with a descriptor chain that represents over 4 GiB of memory. This
> violates virtio spec, which says:
> Drivers MUST NOT add a descriptor chain longer than $2^{32}$ bytes in total;
>
> Specifically, submitting $2^{32}$ or more breaks Rust-VMM.
>
> Submit each reported page as a separate virtqueue buffer instead of one
> buffer with an sg list of all pages. This also avoids indirect
> descriptor allocation (kmalloc in the reporting path).
>
> To avoid multiple buffers causing interrupt storms, keep callbacks
> disabled while we wait for an event.
>
> Note: if the virtqueue is broken, wait_event on
> virtqueue_get_buf hangs. This is a pre-existing issue:
> the old single-buffer code had the same hang.
>
> Reported-by: Ruben Hakobyan <hruben@meta.com>
> Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
brown paperbag post,forgot to commit.
> ---
>
> Lightly tested. Ruben, could you pls help test?
>
> drivers/virtio/virtio_balloon.c | 48 ++++++++++++++++++++++++---------
> 1 file changed, 35 insertions(+), 13 deletions(-)
>
> diff --git a/drivers/virtio/virtio_balloon.c b/drivers/virtio/virtio_balloon.c
> index f6c2dff33f8a..363519fa890d 100644
> --- a/drivers/virtio/virtio_balloon.c
> +++ b/drivers/virtio/virtio_balloon.c
> @@ -202,25 +202,47 @@ static int virtballoon_free_page_report(struct page_reporting_dev_info *pr_dev_i
> struct virtio_balloon *vb =
> container_of(pr_dev_info, struct virtio_balloon, pr_dev_info);
> struct virtqueue *vq = vb->reporting_vq;
> - unsigned int unused, err;
> + unsigned int i, err = 0;
>
> /* We should always be able to add these buffers to an empty queue. */
> - err = virtqueue_add_inbuf(vq, sg, nents, vb, GFP_NOWAIT);
> + for (i = 0; i < nents; i++) {
> + struct scatterlist one;
>
> - /*
> - * In the extremely unlikely case that something has occurred and we
> - * are able to trigger an error we will simply display a warning
> - * and exit without actually processing the pages.
> - */
> - if (WARN_ON_ONCE(err))
> - return err;
> + sg_init_table(&one, 1);
> + sg_set_page(&one, sg_page(&sg[i]), sg[i].length,
> + sg[i].offset);
> + err = virtqueue_add_inbuf(vq, &one, 1, &sg[i], GFP_NOWAIT);
>
> - virtqueue_kick(vq);
> + /*
> + * In the extremely unlikely case that something has occurred
> + * and we are able to trigger an error we will simply display
> + * a warning and exit without actually processing the pages.
> + */
> + if (WARN_ON_ONCE(err)) {
> + nents = i;
> + break;
> + }
> + }
>
> - /* When host has read buffer, this completes via balloon_ack */
> - wait_event(vb->acked, virtqueue_get_buf(vq, &unused));
> + if (nents) {
> + virtqueue_kick(vq);
>
> - return 0;
> + /* When host has read buffer, this completes via balloon_ack.
> + * We report pages as individual buffers, so arm the callback
> + * to only interrupt once all the queued buffers have been
> + * used: otherwise we would get one interrupt per page.
> + */
> + for (i = 0; i < nents; i++) {
> + unsigned int unused;
> +
> + virtqueue_enable_cb_delayed_max(vq);
> + wait_event(vb->acked,
> + virtqueue_get_buf(vq, &unused));
> + virtqueue_disable_cb(vq);
> + }
> + }
> +
> + return err;
> }
>
> static void set_page_pfns(struct virtio_balloon *vb,
> --
> MST
>
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-10-26 16:00 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-26 15:09 [PATCH 1/2] virtio_ring: add virtqueue_enable_cb_delayed_max Michael S. Tsirkin
2026-10-26 15:09 ` [PATCH 2/2] virtio_balloon: submit reported pages as individual buffers Michael S. Tsirkin
2026-10-26 16:00 ` Michael S. Tsirkin
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®