QEMU-Arm Archive on lore.kernel.org
 help / color / mirror / Atom feed
From: Jamin Lin <jamin_lin@aspeedtech.com>
To: "Cédric Le Goater" <clg@kaod.org>,
	"Peter Maydell" <peter.maydell@linaro.org>,
	"Steven Lee" <steven_lee@aspeedtech.com>,
	"Troy Lee" <leetroy@gmail.com>,
	"Kane Chen" <kane_chen@aspeedtech.com>,
	"Andrew Jeffery" <andrew@codeconstruct.com.au>,
	"Joel Stanley" <joel@jms.id.au>,
	"Pierrick Bouvier" <pierrick.bouvier@oss.qualcomm.com>,
	"Paolo Bonzini" <pbonzini@redhat.com>,
	"Gerd Hoffmann" <kraxel@redhat.com>,
	"open list:ASPEED BMCs" <qemu-arm@nongnu.org>,
	"open list:All patches CC here" <qemu-devel@nongnu.org>
Cc: Jamin Lin <jamin_lin@aspeedtech.com>, Troy Lee <troy_lee@aspeedtech.com>
Subject: [PATCH v2 5/6] hw/display/aspeed-vga: Add the 2D graphics engine
Date: Thu, 10 Sep 2026 01:44:53 +0000	[thread overview]
Message-ID: <20260910014447.4110781-6-jamin_lin@aspeedtech.com> (raw)
In-Reply-To: <20260910014447.4110781-1-jamin_lin@aspeedtech.com>

The display controller has a 2D engine at BAR 1 + 0x8000. It was not
modelled, so its status register read back as all ones, which bit 31
defines as a busy engine. ASPEED's UEFI driver waits for it and stops
when it tries to scroll the console.

Add the engine as a second subregion of BAR 1 and model the BitBLT
command, which is the one the firmware uses. Only the source copy
raster operation is done; other operations, clipping, patterns and the
transparent monochrome mask are logged and nothing is drawn. A
rectangle that does not fit in the framebuffer is rejected rather than
half copied.

The command carries a direction for each axis. When one is negative the
coordinates are the last row or column, so an overlapping copy starts
from that end and a scroll does not smear.

The status register reads as idle after reset, a write of one clears
that bit, and it is set again when a command finishes.

Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
---
 hw/display/aspeed-vga.h |  10 ++
 hw/display/aspeed-vga.c | 261 ++++++++++++++++++++++++++++++++++++++++
 hw/display/trace-events |   3 +
 3 files changed, 274 insertions(+)

diff --git a/hw/display/aspeed-vga.h b/hw/display/aspeed-vga.h
index 55dcc57108..625e513115 100644
--- a/hw/display/aspeed-vga.h
+++ b/hw/display/aspeed-vga.h
@@ -25,13 +25,23 @@ OBJECT_DECLARE_TYPE(AspeedVGAState, AspeedVGAClass, ASPEED_VGA)
 #define ASPEED_VGA_IOPORT_OFFSET    0x380
 #define ASPEED_VGA_IOPORT_SIZE      0x80
 
+/*
+ * 2D Graphics Engine (G2D)
+ *
+ * Offset 0x8000 from the BAR 1 register base
+ */
+#define ASPEED_VGA_G2D_OFFSET    0x8000
+#define ASPEED_VGA_G2D_NR_REGS   (0x200 >> 2)
+
 struct AspeedVGAState {
     PCIDevice parent_obj;
 
     VGACommonState vga;
     MemoryRegion mmio;
     MemoryRegion ioport;
+    MemoryRegion g2d;
 
+    uint32_t g2d_regs[ASPEED_VGA_G2D_NR_REGS];
     uint8_t vgaer;
     uint32_t last_cursor_y;
     bool last_cursor_on;
diff --git a/hw/display/aspeed-vga.c b/hw/display/aspeed-vga.c
index e40d503910..f3ea154538 100644
--- a/hw/display/aspeed-vga.c
+++ b/hw/display/aspeed-vga.c
@@ -104,6 +104,259 @@ REG8(AST_CR_SOC_SCRATCH0, 0xd0)
 #define CURSOR_MONO_AND BIT(15)
 #define CURSOR_MONO_XOR BIT(14)
 
+/*
+ * 2D Graphics Engine (G2D)
+ *
+ * Offset 0x8000 from the BAR 1 register base
+ */
+REG32(GER_SRC_BASE, 0x00)
+    FIELD(GER_SRC_BASE, ADDR, 3, 27)
+REG32(GER_SRC_PITCH, 0x04)
+    FIELD(GER_SRC_PITCH, PITCH, 19, 11)
+REG32(GER_DST_BASE, 0x08)
+    FIELD(GER_DST_BASE, ADDR, 3, 27)
+REG32(GER_DST_PITCH, 0x0c)
+    FIELD(GER_DST_PITCH, PITCH, 19, 11)
+REG32(GER_DST_XY, 0x10)
+    FIELD(GER_DST_XY, X, 16, 12)
+    FIELD(GER_DST_XY, Y, 0, 12)
+REG32(GER_SRC_XY, 0x14)
+    FIELD(GER_SRC_XY, X, 16, 12)
+    FIELD(GER_SRC_XY, Y, 0, 12)
+REG32(GER_DIMENSION, 0x18)
+    FIELD(GER_DIMENSION, WIDTH, 16, 12)
+    FIELD(GER_DIMENSION, HEIGHT, 0, 12)
+REG32(GER_CMD, 0x3c)
+    FIELD(GER_CMD, NEG_X, 21, 1)
+    FIELD(GER_CMD, NEG_Y, 20, 1)
+    FIELD(GER_CMD, PATTERN, 16, 2)
+    FIELD(GER_CMD, ROP, 8, 8)
+    FIELD(GER_CMD, MONO_TRANSPARENT, 7, 1)
+    FIELD(GER_CMD, SRC_FROM_QUEUE, 6, 1)
+    FIELD(GER_CMD, COLOR, 4, 2)
+    FIELD(GER_CMD, CLIP, 3, 1)
+    FIELD(GER_CMD, TYPE, 0, 3)
+#define GER_CMD_TYPE_BITBLT     0
+#define GER_CMD_COLOR_TRUE      2
+#define GER_CMD_COLOR_HIGH      1
+#define GER_CMD_COLOR_256       0
+/*
+ * A ROP3 code: an 8 bit truth table for a boolean function of source,
+ * destination and pattern. 0xcc is the one that leaves the destination
+ * equal to the source, and is the only one modelled.
+ */
+#define GER_CMD_ROP_SRCCOPY     0xcc
+REG32(GER_STATUS, 0x4c)
+    FIELD(GER_STATUS, IDLE, 24, 1)
+    FIELD(GER_STATUS, CMDQ_SPACE, 20, 1)
+
+/*
+ * Copy a rectangle. Only the source copy raster operation is modelled,
+ * which is all the firmware needs to scroll and clear the console.
+ */
+static void aspeed_g2d_bitblt(AspeedVGAState *s)
+{
+    uint32_t src_x = FIELD_EX32(s->g2d_regs[R_GER_SRC_XY], GER_SRC_XY, X);
+    uint32_t src_y = FIELD_EX32(s->g2d_regs[R_GER_SRC_XY], GER_SRC_XY, Y);
+    uint32_t dst_x = FIELD_EX32(s->g2d_regs[R_GER_DST_XY], GER_DST_XY, X);
+    uint32_t dst_y = FIELD_EX32(s->g2d_regs[R_GER_DST_XY], GER_DST_XY, Y);
+    uint32_t src_pitch = FIELD_EX32(s->g2d_regs[R_GER_SRC_PITCH],
+                                    GER_SRC_PITCH, PITCH) * 8;
+    uint32_t dst_pitch = FIELD_EX32(s->g2d_regs[R_GER_DST_PITCH],
+                                    GER_DST_PITCH, PITCH) * 8;
+    uint32_t src_base = FIELD_EX32(s->g2d_regs[R_GER_SRC_BASE],
+                                   GER_SRC_BASE, ADDR) << 3;
+    uint32_t dst_base = FIELD_EX32(s->g2d_regs[R_GER_DST_BASE],
+                                   GER_DST_BASE, ADDR) << 3;
+    uint32_t height = FIELD_EX32(s->g2d_regs[R_GER_DIMENSION],
+                                 GER_DIMENSION, HEIGHT);
+    uint32_t width = FIELD_EX32(s->g2d_regs[R_GER_DIMENSION],
+                                GER_DIMENSION, WIDTH);
+    uint32_t cmd = s->g2d_regs[R_GER_CMD];
+    uint64_t src_offset;
+    uint64_t dst_offset;
+    uint32_t bytes_pp;
+    bool right_to_left;
+    bool bottom_up;
+    uint32_t color;
+    uint64_t line;
+    uint32_t i;
+    uint32_t y;
+
+    right_to_left = FIELD_EX32(cmd, GER_CMD, NEG_X);
+    bottom_up = FIELD_EX32(cmd, GER_CMD, NEG_Y);
+    color = FIELD_EX32(cmd, GER_CMD, COLOR);
+
+    switch (color) {
+    case GER_CMD_COLOR_256:
+        bytes_pp = 1;
+        break;
+    case GER_CMD_COLOR_HIGH:
+        bytes_pp = 2;
+        break;
+    case GER_CMD_COLOR_TRUE:
+        bytes_pp = 4;
+        break;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: 2D command 0x%08x has an invalid color mode\n",
+                      __func__, cmd);
+        return;
+    }
+
+    if (FIELD_EX32(cmd, GER_CMD, ROP) != GER_CMD_ROP_SRCCOPY) {
+        qemu_log_mask(LOG_UNIMP, "%s: unimplemented raster operation 0x%02x\n",
+                      __func__, FIELD_EX32(cmd, GER_CMD, ROP));
+        return;
+    }
+
+    if (FIELD_EX32(cmd, GER_CMD, SRC_FROM_QUEUE)) {
+        qemu_log_mask(LOG_UNIMP,
+                      "%s: source from the command queue is not implemented\n",
+                      __func__);
+        return;
+    }
+
+    if (FIELD_EX32(cmd, GER_CMD, CLIP)) {
+        qemu_log_mask(LOG_UNIMP,
+                      "%s: rectangular clipping is not implemented\n",
+                      __func__);
+        return;
+    }
+
+    if (FIELD_EX32(cmd, GER_CMD, MONO_TRANSPARENT)) {
+        qemu_log_mask(LOG_UNIMP,
+                      "%s: a transparent monochrome mask is not implemented\n",
+                      __func__);
+        return;
+    }
+
+    if (FIELD_EX32(cmd, GER_CMD, PATTERN)) {
+        qemu_log_mask(LOG_UNIMP, "%s: unimplemented pattern source %u\n",
+                      __func__, FIELD_EX32(cmd, GER_CMD, PATTERN));
+        return;
+    }
+
+    if (!width || !height || !src_pitch || !dst_pitch) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: 2D command 0x%08x has nothing to copy: "
+                      "width %u, height %u, src pitch %u, dst pitch %u\n",
+                      __func__, cmd, width, height, src_pitch, dst_pitch);
+        return;
+    }
+
+    trace_aspeed_g2d_bitblt(cmd, width, height, src_x, src_y, dst_x, dst_y,
+                            bytes_pp);
+
+    /*
+     * The engine renders away from the given corner, so when a direction is
+     * negative that corner is the last row or column. Step back to the top
+     * left one, which the rest of this function works from.
+     */
+    if (bottom_up) {
+        if (src_y + 1 < height || dst_y + 1 < height) {
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "%s: 2D command 0x%08x runs off the top of the "
+                          "framebuffer\n", __func__, cmd);
+            return;
+        }
+        src_y -= height - 1;
+        dst_y -= height - 1;
+    }
+
+    if (right_to_left) {
+        if (src_x + 1 < width || dst_x + 1 < width) {
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "%s: 2D command 0x%08x runs off the left of the "
+                          "framebuffer\n", __func__, cmd);
+            return;
+        }
+        src_x -= width - 1;
+        dst_x -= width - 1;
+    }
+
+    line = (uint64_t)width * bytes_pp;
+
+    src_offset = (uint64_t)src_base +
+                 (uint64_t)(src_y + height - 1) * src_pitch +
+                 (uint64_t)src_x * bytes_pp;
+    dst_offset = (uint64_t)dst_base +
+                 (uint64_t)(dst_y + height - 1) * dst_pitch +
+                 (uint64_t)dst_x * bytes_pp;
+
+    if (src_offset + line > s->vga.vram_size ||
+        dst_offset + line > s->vga.vram_size) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: 2D command 0x%08x reaches past the framebuffer\n",
+                      __func__, cmd);
+        return;
+    }
+
+    for (i = 0; i < height; i++) {
+        /* NEG_Y says which end to start from, so an overlap does not smear */
+        y = bottom_up ? height - 1 - i : i;
+        src_offset = (uint64_t)src_base +
+                     (uint64_t)(src_y + y) * src_pitch +
+                     (uint64_t)src_x * bytes_pp;
+        dst_offset = (uint64_t)dst_base +
+                     (uint64_t)(dst_y + y) * dst_pitch +
+                     (uint64_t)dst_x * bytes_pp;
+
+        memmove(s->vga.vram_ptr + dst_offset, s->vga.vram_ptr + src_offset,
+                line);
+        memory_region_set_dirty(&s->vga.vram, dst_offset, line);
+    }
+}
+
+static uint64_t aspeed_g2d_read(void *opaque, hwaddr addr, unsigned size)
+{
+    AspeedVGAState *s = opaque;
+    uint32_t reg = addr >> 2;
+    uint32_t val = s->g2d_regs[reg];
+
+    trace_aspeed_g2d_read(reg, val);
+    return val;
+}
+
+static void aspeed_g2d_write(void *opaque, hwaddr addr, uint64_t val,
+                             unsigned size)
+{
+    AspeedVGAState *s = opaque;
+    uint32_t reg = addr >> 2;
+
+    trace_aspeed_g2d_write(reg, val);
+
+    switch (reg) {
+    case R_GER_STATUS:
+        s->g2d_regs[reg] &= ~(val & (R_GER_STATUS_IDLE_MASK |
+                                     R_GER_STATUS_CMDQ_SPACE_MASK));
+        break;
+    case R_GER_CMD:
+        s->g2d_regs[reg] = val;
+        if (FIELD_EX32(val, GER_CMD, TYPE) == GER_CMD_TYPE_BITBLT) {
+            aspeed_g2d_bitblt(s);
+        } else {
+            qemu_log_mask(LOG_UNIMP, "%s: unimplemented 2D command type %u\n",
+                          __func__, FIELD_EX32(val, GER_CMD, TYPE));
+        }
+        s->g2d_regs[R_GER_STATUS] |= R_GER_STATUS_IDLE_MASK;
+        break;
+    default:
+        s->g2d_regs[reg] = val;
+        break;
+    }
+}
+
+static const MemoryRegionOps aspeed_g2d_ops = {
+    .read = aspeed_g2d_read,
+    .write = aspeed_g2d_write,
+    .valid.min_access_size = 4,
+    .valid.max_access_size = 4,
+    .impl.min_access_size = 4,
+    .impl.max_access_size = 4,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
 /*
  * The ast driver writes a color format to CRA3 when it switches to the
  * extended mode. Until then CRA3 is zero and the device behaves like a
@@ -517,6 +770,9 @@ static void aspeed_vga_reset_hold(Object *obj, ResetType type)
         R_AST_CR_SOC_SCRATCH0_VRAM_INIT_BY_BMC_MASK |
         R_AST_CR_SOC_SCRATCH0_VRAM_INIT_READY_MASK |
         R_AST_CR_SOC_SCRATCH0_IKVM_WIDESCREEN_MASK;
+
+    memset(s->g2d_regs, 0, sizeof(s->g2d_regs));
+    s->g2d_regs[R_GER_STATUS] = R_GER_STATUS_IDLE_MASK;
 }
 
 /*
@@ -592,6 +848,10 @@ static void aspeed_vga_realize(PCIDevice *dev, Error **errp)
     memory_region_add_subregion(&s->mmio, ASPEED_VGA_IOPORT_OFFSET,
                                 &s->ioport);
 
+    memory_region_init_io(&s->g2d, OBJECT(dev), &aspeed_g2d_ops, s,
+                          "aspeed-vga.g2d", ASPEED_VGA_G2D_NR_REGS << 2);
+    memory_region_add_subregion(&s->mmio, ASPEED_VGA_G2D_OFFSET, &s->g2d);
+
     pci_register_bar(dev, 0, PCI_BASE_ADDRESS_MEM_PREFETCH, &vga->vram);
     pci_register_bar(dev, 1, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->mmio);
 }
@@ -612,6 +872,7 @@ static const VMStateDescription vmstate_aspeed_vga = {
         VMSTATE_STRUCT(vga, AspeedVGAState, 0, vmstate_vga_common,
                        VGACommonState),
         VMSTATE_UINT8(vgaer, AspeedVGAState),
+        VMSTATE_UINT32_ARRAY(g2d_regs, AspeedVGAState, ASPEED_VGA_G2D_NR_REGS),
         VMSTATE_END_OF_LIST()
     }
 };
diff --git a/hw/display/trace-events b/hw/display/trace-events
index fd4289d755..0e2adc2b80 100644
--- a/hw/display/trace-events
+++ b/hw/display/trace-events
@@ -141,6 +141,9 @@ vga_cirrus_bitblt_start(uint8_t blt_rop, uint8_t blt_mode, uint8_t blt_modeext,
 # aspeed-vga.c
 aspeed_vga_read_byte(uint32_t port, uint8_t val) "port 0x%03x, val 0x%02x"
 aspeed_vga_write_byte(uint32_t port, uint8_t val) "port 0x%03x, val 0x%02x"
+aspeed_g2d_read(uint32_t reg, uint32_t val) "reg 0x%02x, val 0x%08x"
+aspeed_g2d_write(uint32_t reg, uint32_t val) "reg 0x%02x, val 0x%08x"
+aspeed_g2d_bitblt(uint32_t cmd, uint32_t w, uint32_t h, uint32_t sx, uint32_t sy, uint32_t dx, uint32_t dy, uint32_t bpp) "cmd 0x%08x, %ux%u, src %u,%u dst %u,%u, %u bytes/pixel"
 
 # sii9022.c
 sii9022_read_reg(uint8_t addr, uint8_t val) "addr 0x%02x, val 0x%02x"
-- 
2.53.0


  parent reply	other threads:[~2026-09-10  1:46 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-10  1:44 [PATCH v2 0/6] Add ASPEED VGA PCI device support Jamin Lin
2026-09-10  1:44 ` [PATCH v2 1/6] hw/display/vga: Allow a device to report vendor specific blanking Jamin Lin
2026-09-10  1:44 ` [PATCH v2 2/6] hw/display/vga: Move VGA_HPEL_NEUTRAL to vga_int.h Jamin Lin
2026-09-10  1:44 ` [PATCH v2 3/6] hw/display/aspeed-vga: Add ASPEED VGA display controller Jamin Lin
2026-09-10  1:44 ` [PATCH v2 4/6] hw/display/aspeed-vga: Add the hardware overlay cursor Jamin Lin
2026-09-10  1:44 ` Jamin Lin [this message]
2026-09-10  1:44 ` [PATCH v2 6/6] docs/system/arm/aspeed: Document the VGA display controller Jamin Lin

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=20260910014447.4110781-6-jamin_lin@aspeedtech.com \
    --to=jamin_lin@aspeedtech.com \
    --cc=andrew@codeconstruct.com.au \
    --cc=clg@kaod.org \
    --cc=joel@jms.id.au \
    --cc=kane_chen@aspeedtech.com \
    --cc=kraxel@redhat.com \
    --cc=leetroy@gmail.com \
    --cc=pbonzini@redhat.com \
    --cc=peter.maydell@linaro.org \
    --cc=pierrick.bouvier@oss.qualcomm.com \
    --cc=qemu-arm@nongnu.org \
    --cc=qemu-devel@nongnu.org \
    --cc=steven_lee@aspeedtech.com \
    --cc=troy_lee@aspeedtech.com \
    /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