On 2026/09/04 22:10, Marc-André Lureau wrote:
When the backend sends VHOST_USER_GPU_SCANOUT with zero dimensions to disable a scanout, the frontend only calls qemu_console_set_surface(NULL) without releasing the g->dmabuf[] entry or invoking qemu_console_gl_scanout_disable(). Display listeners (GTK, SDL) can therefore remain in GL scanout mode and keep redrawing the stale buffer.Release the DMA-BUF and call qemu_console_gl_scanout_disable() in the SCANOUT handler when a previously active DMA-BUF exists. Additionally, vhost_user_gpu_reset() and vhost_user_gpu_instance_finalize() never iterate the dmabuf[] array, so active DMA-BUFs leak on device reset or object destruction. Add cleanup loops to both paths.
We need to ensure that DMA-BUF reset cleanup happens on the main-thread.A guest writes zero to the modern PCI status register → virtio_pci_common_write() → virtio_pci_reset() → virtio_bus_reset() → virtio_reset() → vhost_user_gpu_reset(). This executes on the vCPU thread. The newly added release calls invoke GTK/SDL/EGL display listeners there. For example, egl_release_dmabuf() attempts to bind qemu_egl_rn_ctx, which remains current on the main thread, then calls glDeleteTextures() through egl_dmabuf_release_texture(). The context bind fails, leaving the GL deletion without the required current context. EGL 1.5 §3.7.3 explicitly requires EGL_BAD_ACCESS when the context is current on another thread.
Existing virtio_gpu_reset() handles this constraint by scheduling a main-loop BH and waiting, but it also has its own race and deadlock issues. [1] is my latest attempt to fix it. You may review it, and adopt its approach if it looks reasonable to you.
[1] https://lore.kernel.org/qemu-devel/[email protected]/
("[PATCH v3 0/9] virtio-gpu: Do not wait for the main thread during reset")
Fixes: 267f664658fe ("hw/display: add vhost-user-vga & gpu-pci") Signed-off-by: Marc-André Lureau <[email protected]> --- hw/display/vhost-user-gpu.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/hw/display/vhost-user-gpu.c b/hw/display/vhost-user-gpu.c index c09aca041135..3f1fd333eeab 100644 --- a/hw/display/vhost-user-gpu.c +++ b/hw/display/vhost-user-gpu.c @@ -263,6 +263,11 @@ vhost_user_gpu_handle_display(VhostUserGPU *g, VhostUserGpuMsg *msg) con = s->con;if (m->width == 0) {+ if (g->dmabuf[m->scanout_id]) { + qemu_console_gl_release_dmabuf(con, g->dmabuf[m->scanout_id]); + g_clear_pointer(&g->dmabuf[m->scanout_id], qemu_dmabuf_free); + qemu_console_gl_scanout_disable(con); + } qemu_console_set_surface(con, NULL); } else { s->ds = qemu_create_displaysurface(m->width, m->height); @@ -631,6 +636,10 @@ vhost_user_gpu_instance_finalize(Object *obj) { VhostUserGPU *g = VHOST_USER_GPU(obj);+ for (int i = 0; i < g->parent_obj.conf.max_outputs; i++) {
Please bound finalizer cleanup by the actual DMA-BUF array size.conf.max_outputs is a writable uint32 property, and the rejection of values above 16 happens only in virtio_gpu_base_device_realize(). The finalizer also runs after realization failure, including missing chardev, before that validation. Thus setting max_outputs=4096 makes this new loop traverse beyond dmabuf[16], reading and potentially freeing arbitrary values. Iterating ARRAY_SIZE(g->dmabuf) instead is the simplest fix I came up.
Regards, Akihiko Odaki
+ g_clear_pointer(&g->dmabuf[i], qemu_dmabuf_free); + } + object_unref(OBJECT(g->vhost)); }@@ -639,6 +648,15 @@ vhost_user_gpu_reset(VirtIODevice *vdev){ VhostUserGPU *g = VHOST_USER_GPU(vdev);+ for (int i = 0; i < g->parent_obj.conf.max_outputs; i++) {+ if (g->dmabuf[i]) { + QemuConsole *con = g->parent_obj.scanout[i].con; + qemu_console_gl_release_dmabuf(con, g->dmabuf[i]); + g_clear_pointer(&g->dmabuf[i], qemu_dmabuf_free); + qemu_console_gl_scanout_disable(con); + } + } + virtio_gpu_base_reset(VIRTIO_GPU_BASE(vdev));vhost_user_backend_stop(g->vhost);
