Submit each reported page as a separate virtqueue buffer instead of one buffer with an sg list of all pages. This avoids indirect descriptor allocation (kmalloc in the reporting path) and gives per-page used length feedback from the device.
On error, the already-queued pages are kicked and drained before the error is returned. The caller (page_reporting_drain) then marks the batch as unreported, which is conservative but correct. 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. EVENT_IDX is not a concern: callbacks were never disabled, so the virtqueue manages used_event automatically. Signed-off-by: Michael S. Tsirkin <[email protected]> Assisted-by: Claude:claude-opus-4-6 --- drivers/virtio/virtio_balloon.c | 40 +++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/drivers/virtio/virtio_balloon.c b/drivers/virtio/virtio_balloon.c index 6a1a610c2cb1..53b4a3984e7d 100644 --- a/drivers/virtio/virtio_balloon.c +++ b/drivers/virtio/virtio_balloon.c @@ -187,7 +187,9 @@ static void tell_host(struct virtio_balloon *vb, struct virtqueue *vq) sg_init_one(&sg, vb->pfns, sizeof(vb->pfns[0]) * vb->num_pfns); - /* We should always be able to add one buffer to an empty queue. */ + /* We made sure the vq is large enough so we should always + * be able to add one buffer to an empty queue. + */ virtqueue_add_outbuf(vq, &sg, 1, vb, GFP_KERNEL); virtqueue_kick(vq); @@ -202,25 +204,35 @@ 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); + if (WARN_ON_ONCE(err)) { + nents = i; + break; + } + } - virtqueue_kick(vq); + if (nents) { + virtqueue_kick(vq); - /* When host has read buffer, this completes via balloon_ack */ - wait_event(vb->acked, virtqueue_get_buf(vq, &unused)); + /* When host has read buffer, this completes via balloon_ack */ + for (i = 0; i < nents; i++) { + unsigned int unused; - return 0; + wait_event(vb->acked, + virtqueue_get_buf(vq, &unused)); + } + } + + return err; } static void set_page_pfns(struct virtio_balloon *vb, -- MST

