hv-balloon runs its event loop, including the receive path, from contexts
other than the VMBus channel notify callback: the QMP "balloon" handler
(hv_balloon_to_target()), the post-init timer and the open/close/reset
paths. Receiving is only legal between ringbuf_start_io() and
ringbuf_end_io(), which channel_event_cb() provides for the notify path.
When the receive ring is empty, vmbus_channel_recv_peek() returns before
seeking, so this is usually harmless. When the guest has just posted a
Dynamic Memory message (e.g. the periodic STATUS report, or the response
to a previous hot-add) and the QMP "balloon" command arrives before the
notifier runs, QEMU aborts:
hw/hyperv/vmbus.c:418: gpadl_iter_seek: Assertion `iter->active' failed.
Seen with a Windows Server 2022 guest (Hyper-V Dynamic Memory driver),
hv-balloon with a memdev and status-report=on, and a host-side controller
issuing a few "balloon" commands within a minute while an RDP user logged
in (the guest's committed memory jumped and hot-adds were in flight).
Make vmbus_channel_recv_start() start the ring I/O itself when it is
called outside the notify callback, remember that in the receive ring,
and end it in vmbus_channel_recv_done() (or right away if recv_start()
fails). The notify-callback path is unchanged.
Signed-off-by: FULL NAME <[email protected]>
---
hw/hyperv/vmbus.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 47 insertions(+), 2 deletions(-)
--- a/hw/hyperv/vmbus.c
+++ b/hw/hyperv/vmbus.c
@@ -121,6 +121,12 @@
uint32_t last_rd_idx;
/* last seen write index */
uint32_t last_seen_wr_idx;
+ /*
+ * recv_start was called outside the channel notify callback (e.g. from a
+ * QMP-triggered device path such as hv-balloon's balloon target change),
+ * so it started the I/O on the ring itself and recv_done must end it.
+ */
+ bool own_io;
} VMBusRecvRingBuf;
@@ -1113,7 +1119,7 @@
return req;
}
-int vmbus_channel_recv_start(VMBusChannel *chan)
+static int vmbus_channel_recv_start_io(VMBusChannel *chan)
{
VMBusRecvRingBuf *ringbuf = &chan->recv_ringbuf;
vmbus_ring_buffer *rb;
@@ -1132,6 +1138,33 @@
/* prevent reorder of the following data operation with write_index read */
smp_mb(); /* barrier pair [C] */
return 0;
+}
+
+int vmbus_channel_recv_start(VMBusChannel *chan)
+{
+ VMBusRecvRingBuf *ringbuf = &chan->recv_ringbuf;
+ bool own_io = false;
+ int ret;
+
+ /*
+ * Receives normally happen within channel_event_cb(), which brackets them
+ * with ringbuf_start_io()/ringbuf_end_io(). Device code may also receive
+ * from other contexts (hv-balloon does from its QMP balloon handler); make
+ * that safe instead of tripping the iter->active assertion in
+ * gpadl_iter_seek() when a packet is pending.
+ */
+ if (!ringbuf->common.iter.active) {
+ ringbuf_start_io(&ringbuf->common);
+ own_io = true;
+ }
+
+ ret = vmbus_channel_recv_start_io(chan);
+ if (ret && own_io) {
+ ringbuf_end_io(&ringbuf->common);
+ own_io = false;
+ }
+ ringbuf->own_io = own_io;
+ return ret;
}
void *vmbus_channel_recv_peek(VMBusChannel *chan, uint32_t size)
@@ -1208,7 +1241,7 @@
ringbuf->rd_idx = ringbuf_tell(&ringbuf->common);
}
-ssize_t vmbus_channel_recv_done(VMBusChannel *chan)
+static ssize_t vmbus_channel_recv_done_io(VMBusChannel *chan)
{
VMBusRecvRingBuf *ringbuf = &chan->recv_ringbuf;
vmbus_ring_buffer *rb;
@@ -1266,6 +1299,18 @@
ringbuf_unmap_hdr(&ringbuf->common, rb, true);
ringbuf->last_rd_idx = ringbuf->rd_idx;
return read;
+}
+
+ssize_t vmbus_channel_recv_done(VMBusChannel *chan)
+{
+ VMBusRecvRingBuf *ringbuf = &chan->recv_ringbuf;
+ ssize_t ret = vmbus_channel_recv_done_io(chan);
+
+ if (ringbuf->own_io) {
+ ringbuf_end_io(&ringbuf->common);
+ ringbuf->own_io = false;
+ }
+ return ret;
}
void vmbus_free_req(void *req)