Linux virtualization list
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Michael S. Tsirkin" <mst@redhat.com>
Cc: virtualization@lists.linux.dev, ojeda@kernel.org,
	gary@garyguo.net, "Eugenio Perez" <eperezma@redhat.com>
Subject: Re: [PATCH v2 2/2] virtio_balloon: submit reported pages as individual buffers
Date: Tue, 27 Oct 2026 00:14:47 +0000	[thread overview]
Message-ID: <sashiko-outbox-185929@kernel.org> (raw)
In-Reply-To: <02257263433b001cd0a55a52486857df81dd4246.1793057483.git.mst@redhat.com>

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] virtio_balloon: infinite spin in virtballoon_free_page_report() on broken vq
- [Medium] virtio_balloon: callback toggle side effects in wait_event() condition
--

commit bced8a77808ca2850eb132d7f72c3d6fc5ae5935
Author: Michael S. Tsirkin <mst@redhat.com>

virtio_balloon: submit reported pages as individual buffers

This commit submits each reported free page as an individual virtqueue buffer
rather than a single scatter-gather chain to avoid exceeding the virtio 4 GiB
descriptor chain limit. To prevent interrupt storms from per-page buffers, it
disables reporting virtqueue callbacks at initialization and uses
virtqueue_enable_cb_delayed_max() while waiting for completion in
virtballoon_free_page_report().

> diff --git a/drivers/virtio/virtio_balloon.c b/drivers/virtio/virtio_balloon.c
> index ab3e3d887e00f..0a331acf2c8ef 100644
> --- a/drivers/virtio/virtio_balloon.c
> +++ b/drivers/virtio/virtio_balloon.c
> @@ -205,25 +205,53 @@ 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) {
> +		unsigned int unused;
>  
> -	return 0;
> +		virtqueue_kick(vq);
> +
> +		/* When host has read buffer, this completes via balloon_ack. */
> +		i = 0;
> +		wait_event(vb->acked, ({
> +			for (;;) {
> +				virtqueue_disable_cb(vq);
> +				while (virtqueue_get_buf(vq, &unused))
> +					i++;
> +
> +				/* Stop waiting if device used all that we added. */
> +				if (i >= nents)
> +					break;
> +
> +				if (virtqueue_enable_cb_delayed_max(vq))
> +					break;
> +			}
> +			i >= nents;
> +		}));

[Severity: High]
Can this inner for (;;) loop spin indefinitely in kernel workqueue context
when vq->broken becomes true before all nents buffers are drained?

In page_reporting_process() -> page_reporting_process_zone() ->
page_reporting_cycle() -> virtballoon_free_page_report(), if the device marks
buffers as used in the ring and vq->broken becomes true before all nents
buffers are consumed (either because virtqueue_get_buf() encounters a bad
descriptor and calls BAD_RING(), or because virtio_break_device() runs
concurrently during surprise removal or reset), virtqueue_get_buf() returns
NULL without advancing vq->last_used_idx or incrementing i.

Because i < nents, the loop then calls virtqueue_enable_cb_delayed_max(vq).
Unlike virtqueue_enable_cb_delayed(), which checks vq->broken:

drivers/virtio/virtio_ring.c:virtqueue_enable_cb_delayed() {
	struct vring_virtqueue *vq = to_vvq(_vq);

	if (vq->event_triggered)
		data_race(vq->event_triggered = false);

	if (unlikely(vq->broken))
		return true;
...
}

virtqueue_enable_cb_delayed_max() omits that check:

drivers/virtio/virtio_ring.c:virtqueue_enable_cb_delayed_max() {
	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);
}

When unconsumed used entries remain in the ring,
virtqueue_enable_cb_delayed_max_split() and
virtqueue_enable_cb_delayed_max_packed() both return false on every call:

drivers/virtio/virtio_ring.c:virtqueue_enable_cb_delayed_max_split() {
...
	if (unlikely((u16)(virtio16_to_cpu(vq->vq.vdev, vq->split.vring.used->idx)
						- vq->last_used_idx) > bufs)) {
		END_USE(vq);
		return false;
	}
	return true;
}

drivers/virtio/virtio_ring.c:virtqueue_enable_cb_delayed_max_packed() {
...
	if (is_used_desc_packed(vq, used_idx, wrap_counter)) {
		END_USE(vq);
		return false;
	}
	return true;
}

As a result, neither break condition in the for (;;) loop is ever met, and the
thread spins in a tight non-sleeping loop inside the wait_event() condition
rather than sleeping as noted in the commit message. Also, should
virtqueue_enable_cb_delayed_max_split() read used->idx via
vring_read_split_used_idx(vq) instead of a plain access?

[Severity: Medium]
Does placing virtqueue_disable_cb() and virtqueue_enable_cb_delayed_max()
inside the wait_event() condition cause unintended callback state changes when
wait_event() evaluates the condition multiple times?

In include/linux/wait.h, wait_event() evaluates the condition once before
calling __wait_event(), and ___wait_event() calls prepare_to_wait_event()
followed immediately by a second evaluation of the condition before calling
schedule():

include/linux/wait.h:wait_event() {
	might_sleep();
	if (condition)
		break;
	__wait_event(wq_head, condition);
}

When the host has not yet consumed all nents buffers on the first check,
virtqueue_enable_cb_delayed_max(vq) clears VRING_AVAIL_F_NO_INTERRUPT in
vq->split.avail_flags_shadow, sets vq->event_triggered = false, writes
vring_used_event = vq->last_used_idx + bufs, and issues virtio_store_mb().

Immediately afterwards, before sleeping in schedule(), the second condition
check in ___wait_event() runs virtqueue_disable_cb(vq):

drivers/virtio/virtio_ring.c:virtqueue_disable_cb_split() {
	if (!(vq->split.avail_flags_shadow & VRING_AVAIL_F_NO_INTERRUPT)) {
		vq->split.avail_flags_shadow |= VRING_AVAIL_F_NO_INTERRUPT;

		if (vq->event_triggered)
			return;

		if (vq->event)
			/* TODO: this is a hack. Figure out a cleaner value to write. */
			vring_used_event(&vq->split.vring) = 0x0;
...
}

Because the first check enabled callbacks and vq->event_triggered is still
false, virtqueue_disable_cb_split() writes vring_used_event = 0x0 into shared
ring memory while the kicked device is actively processing buffers, right
before virtqueue_enable_cb_delayed_max(vq) overwrites it again. When
vq->last_used_idx == 0 (for example, on the first reporting batch after probe
or on 16-bit wrap), transiently writing vring_used_event = 0x0 while the
device processes the first buffer (used->idx advancing from 0 to 1) satisfies
vring_need_event(0x0, 1, 0) on the host and triggers an immediate interrupt on
the first page. The same double evaluation also happens on any wakeup of the
shared vb->acked waitqueue from inflate_vq or deflate_vq.

In addition, on a packed virtqueue, when vring_interrupt() fires and sets
vq->event_triggered = true, waking wait_event() runs
virtqueue_disable_cb_packed(vq):

drivers/virtio/virtio_ring.c:virtqueue_disable_cb_packed() {
	if (vq->packed.event_flags_shadow != VRING_PACKED_EVENT_FLAG_DISABLE) {
		vq->packed.event_flags_shadow = VRING_PACKED_EVENT_FLAG_DISABLE;

		if (vq->event_triggered)
			return;

		vq->packed.vring.driver->flags =
			cpu_to_le16(vq->packed.event_flags_shadow);
	}
}

Because vq->event_triggered is true, virtqueue_disable_cb_packed() updates
event_flags_shadow to VRING_PACKED_EVENT_FLAG_DISABLE and returns early
without updating vq->packed.vring.driver->flags in shared memory. When
i >= nents then breaks out of the loop without calling
virtqueue_enable_cb_delayed_max(vq), driver->flags remains
VRING_PACKED_EVENT_FLAG_DESC after virtballoon_free_page_report() returns, and
subsequent virtqueue_disable_cb(vq) calls become no-ops because
event_flags_shadow is already VRING_PACKED_EVENT_FLAG_DISABLE.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/32e21059adb6cb1fd08c3f4426a313c77cf450ec.1793057483.git.mst@redhat.com?part=2

  reply	other threads:[~2026-10-27  0:14 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-26 23:53 [PATCH v2 1/2] virtio_ring: add virtqueue_enable_cb_delayed_max Michael S. Tsirkin
2026-10-26 23:53 ` [PATCH v2 2/2] virtio_balloon: submit reported pages as individual buffers Michael S. Tsirkin
2026-10-27  0:14   ` sashiko-bot [this message]
2026-10-27  9:26   ` Ruben Hakobyan
2026-10-27 10:34     ` Michael S. Tsirkin
2026-10-27 12:32       ` Ruben Hakobyan
2026-10-27 12:40         ` Michael S. Tsirkin
2026-10-27 14:39           ` Ruben Hakobyan
2026-10-27 19:17             ` Michael S. Tsirkin
2026-10-28 15:06               ` Ruben Hakobyan
2026-10-27  0:10 ` [PATCH v2 1/2] virtio_ring: add virtqueue_enable_cb_delayed_max sashiko-bot

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=sashiko-outbox-185929@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=eperezma@redhat.com \
    --cc=gary@garyguo.net \
    --cc=mst@redhat.com \
    --cc=ojeda@kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    --cc=virtualization@lists.linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox