Skip to content

feat(graphics): reported virtio-gpu's scanout changes to gfxfb - #466

Merged
FlareCoding merged 1 commit into
pr/gfxfb-output-eventsfrom
pr/virtio-gpu-output-events
Oct 26, 2026
Merged

FlareCoding merged 1 commit into
pr/gfxfb-output-eventsfrom
pr/virtio-gpu-output-events

Conversation

@FlareCoding

@FlareCoding FlareCoding commented Oct 26, 2026 •

Copy link
Copy Markdown
Owner

Summary

virtio-gpu lights every scanout (one of the device's virtual monitors) the device enables and follows the device's changes to them, reporting each change to gfxfb (the kernel's display interface), which queues an output event (a record naming the changed output) for every open file of /dev/gfxfb.

Changes

  • Learn of the device's scanout changes as they happen: attach() routes the device's configuration change interrupt to an MSI-X vector of its own, whose handler wakes the driver's task, and the task clears the display event (the bit of the device's config region that marks a change of the scanouts) before reading the scanouts with GET_DISPLAY_INFO, so a change the read misses raises the interrupt again. A device without that vector keeps its scanouts as attach lit them, with a warning in the boot log.
  • Bring each scanout to what the device reports: update_scanout() lights a dark scanout the device enables at a usable size (nonzero and at most 4096 by 4096), takes a lit one dark when the device disables it or reports an unusable size, and takes a resized one dark with relighting (gfxfb's mark of an output being lit again at another mode) set before lighting it at its new size, calling gfxfb::output_changed() after each change with no lock held. At attach, light_enabled_scanouts() lights every scanout the device enables at a usable size, or scanout 0 at 1024x768 when there is none.
  • Give each lighting a base buffer of its own: allocate_base() allocates a new base buffer (the buffer a scanout is lit with, which userland draws into) for every lighting, with no lock held and before a relight takes the scanout dark, and once gfxfb hands it back, after the scanout goes dark and no handle or mapping holds it, the driver's task destroys it as it destroys a buffer from create_buffer(). flush() clips each rect to the scanout's current base buffer, since a relight may have replaced the one whose regions gfxfb named.
  • Stop the device reading a scanout's buffers before gfxfb drops them: darken_scanout() hides the scanout's cursor when shown and sends SET_SCANOUT naming no resource, and only once the device confirms each command it sent does it end the scanout's unended present, if any, with GFXFB_PRESENT_DARK and cancel its latch timer. A darkening the device refuses keeps the scanout lit and reports nothing, and a relight that fails reports the scanout dark without relighting, each waiting for the device's next change to try again, except after a command has gone unanswered, when every later read of the scanouts fails.
  • Keep each scanout's state apart: virtio_gpu_scanout holds each scanout's base buffer, latch timer, shown resource and cursor, so presents, latch reports, both cursor calls, flush(), read_output() and base_buffer() act on the scanout they name. One spinlock covers every scanout's base, relighting mark and latch, so read_output(), base_buffer() and the latch timer never wait behind a command batch.
  • What a resize shows with this commit alone: stlxdm acts on no output event, so after a resize it keeps presenting at the old size, GFXFB_PRESENT refuses those presents, and the scanout shows the new base buffer's black pixels.
  • Resize under stlxdm: a boot that resized scanout 0 to 1280x720 through QEMU's VNC server took the scanout dark while it showed a buffer stlxdm created, then showed a black 1280x720 picture while the shell kept answering. Its log gained only the kernel's lines for the darkening and the lighting and stlxdm's "display present failed: Invalid argument", which stlxdm prints once for a run of refused presents.
  • Boots: no kernel test drives virtio-gpu, so a diagnostic program (on a branch of its own over this commit, with an init that starts no stlxdm) presented on every lit output of a two-scanout device on x86_64 and aarch64 while QEMU's VNC servers, one per scanout, added scanout 1 at 800x600, resized scanout 0 to 1280x720, removed scanout 1 and resized scanout 0 to 1024x768. The kernel lit and darkened each scanout as asked, hiding a shown cursor first and destroying each replaced base buffer's resource, and the program read the four changes in order.
  • Calls on the scanout named: in the same boots the program moved output 1's cursor, presented a buffer it created on output 1 before its base buffer again, and flushed a rect reaching past output 0's new size, and QEMU's trace showed each on the scanout named, the flush on output 0's new base buffer as gfxfb had clipped it.
  • Without the interrupt: boots whose device had no MSI-X vector, or only one, kept scanout 0 at 1920x1080 through a resize, logging the missing configuration change interrupt as a warning.
  • Live check: the desktop came up as before this commit on virtio-gpu with one scanout and on the firmware's framebuffer, and came up on a two-scanout device, with no faults or forbidden log lines on either architecture.
  • Not run: no darkening, lighting or read of the scanouts failed in any boot, so a scanout kept lit after a refused darkening, a relight reported dark without relighting and "failed to read the scanouts after a configuration change" are checked by reading. Every boot's device enabled scanout 0 at a usable size and no attach failed after lighting a scanout, so the fallback scanout's first change, a scanout reported past 4096 pixels, and detach() freeing an unregistered display's base buffers or a base buffer the device may still read are checked by reading too.
  • Flushes not run: GFXFB_FLUSH reaches the driver only for gfxfb's primary output (the output a one-screen program draws on), output 0 in every boot, with each rect already clipped to that output's base buffer, so flush() on another scanout and flush()'s own clip, which changes a rect only when a relight lands between gfxfb's clip and the call, are checked by reading.

Note

High Risk
Large kernel graphics driver change affecting DMA buffer lifetime, IRQ/task locking, and present/latch semantics across multiple outputs; incorrect teardown or relight could leak GPU memory or confuse userland display state.

Overview
Multi-scanout virtio-gpu replaces the single lit scanout and shared m_base_buffer with per-output virtio_gpu_scanout state (base buffer, cursor, latch timer, present tracking). Attach lights every device-enabled usable mode via light_enabled_scanouts(), with a 1024×768 fallback on scanout 0 when none are usable.

Runtime hotplug uses a dedicated MSI-X config vector (CONFIG_VECTOR): on_interrupt wakes the driver task, which clears VIRTIO_GPU_EVENT_DISPLAY, re-reads display info, and runs update_scanout() per output—darkening (cursor hide, empty SET_SCANOUT, GFXFB_PRESENT_DARK), relighting at new sizes with gfxfb::output_changed(), and clipping flush() rects to the current base. Without config MSI-X, scanouts stay as at attach (warned in the log).

Supporting changes: read_display_info() instead of picking one scanout; ERR_RAM_HELD / m_held_base for leaked backing after failed light; m_scanout_lock for non-blocking read_output() / base_buffer(); work queue merges config changes with buffer releases.

Reviewed by Cursor Bugbot for commit 528acb9. Bugbot is set up for automated code reviews on this repo. Configure here.

virtio-gpu lit one scanout at attach and never followed the device's
changes, so a resized scanout kept its old mode and an added one stayed
blank. A resized scanout is lit again with a new base buffer.
@FlareCoding
FlareCoding added this pull request to stack #465 October 26, 2026 03:22
@FlareCoding
FlareCoding merged commit f2b7dd7 into master Oct 26, 2026
11 checks passed
@FlareCoding
FlareCoding deleted the pr/virtio-gpu-output-events branch October 26, 2026 03:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant