Commit 6197c11dd5 for qemu.org

commit 6197c11dd5d2d5deefc553a663a37139e38c5ac9
Author: Chad Jablonski <chad@jablonski.xyz>
Date:   Wed May 6 17:39:20 2026 +0200

    ati-vga: fix ati_set_dirty address calculation

    This fixes three bugs with the ati_set_dirty address calculation.

    First, vbe_start_addr is a word offset. All other values in the
    calculation are byte offsets. It must be converted to bytes.

    Second, when setting the dirty region with memory_region_set_dirty
    the vbe_start_addr is used to calculate the start of the dirty region.
    This is a problem because the vbe_start_addr is the offset at which scan out
    begins. This puts it in the visible screen coordinate system. The dirty
    region however is in the virtual screen coordinate system. This can cause both
    overmarking and missed updates. This is removed from the calculation.

    Third, when the start address of a blit is outside of the bounds check
    the entire blit is missed and not set to dirty. This happens even if the
    blit does partially overlap with the visible screen. The fix here is to
    find the intersection of the visible screen and the blit and mark only
    that region as dirty.

    This does not attempt to apply clipping to the blit. So there will be
    overmarking in some cases.

    Signed-off-by: Chad Jablonski <chad@jablonski.xyz>
    [balaton: drop excess parenthesis, use offsets instead of pointers]
    Reviewed-by: BALATON Zoltan <balaton@eik.bme.hu>
    Tested-by: BALATON Zoltan <balaton@eik.bme.hu>
    Signed-off-by: BALATON Zoltan <balaton@eik.bme.hu>
    Message-ID: <20260506153920.C6B27596978@zero.eik.bme.hu>
    Signed-off-by: Philippe Mathieu-Daudé <philmd@linaro.org>

diff --git a/hw/display/ati_2d.c b/hw/display/ati_2d.c
index 504d1c5708..48498677c7 100644
--- a/hw/display/ati_2d.c
+++ b/hw/display/ati_2d.c
@@ -69,18 +69,28 @@ typedef struct {
 static void ati_set_dirty(VGACommonState *vga, const ATI2DCtx *ctx)
 {
     DisplaySurface *ds = qemu_console_surface(vga->con);
+    unsigned int bypp = ctx->bpp / 8;
+    hwaddr dirty_start = ctx->dst_offset + ctx->dst.x * bypp +
+                         ctx->dst.y * ctx->dst_stride;
+    hwaddr dirty_end = dirty_start + ctx->dst.width * bypp +
+                       (ctx->dst.height - 1) * ctx->dst_stride;
+    /*
+     * The blit may be outside of the visible screen (e.g. virtual desktops.)
+     * Dirty only the intersection of the visible screen and the blit.
+     */
+    hwaddr vis_start = vga->vbe_start_addr * 4;
+    hwaddr vis_end = vis_start + vga->vbe_regs[VBE_DISPI_INDEX_YRES] *
+                       vga->vbe_line_offset;
+    hwaddr start = MAX(vis_start, dirty_start);
+    hwaddr end = MIN(vis_end, dirty_end);

     (void)ds;
     DPRINTF("%p %u ds: %p %d %d rop: %x\n", vga->vram_ptr, vga->vbe_start_addr,
             surface_data(ds), surface_stride(ds), surface_bits_per_pixel(ds),
             ctx->rop3 >> 16);
-    if (ctx->dst_bits >= vga->vram_ptr + vga->vbe_start_addr &&
-        ctx->dst_bits < vga->vram_ptr + vga->vbe_start_addr +
-        vga->vbe_regs[VBE_DISPI_INDEX_YRES] * vga->vbe_line_offset) {
-        memory_region_set_dirty(&vga->vram,
-                                vga->vbe_start_addr + ctx->dst_offset +
-                                ctx->dst.y * ctx->dst_stride,
-                                ctx->dst.height * ctx->dst_stride);
+
+    if (start < end) {
+        memory_region_set_dirty(&vga->vram, start, end - start);
     }
 }