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
next prev 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