[PATCH 2/2] virtio_balloon: submit reported pages as individual buffers
From: Michael S. Tsirkin
Date: Mon Oct 26 2026 - 11:10:15 EST
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@xxxxxxxx>
Signed-off-by: Michael S. Tsirkin <mst@xxxxxxxxxx>
---
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