* [PATCH v2 0/6] Add ASPEED VGA PCI device support
@ 2026-09-10 1:44 Jamin Lin
2026-09-10 1:44 ` [PATCH v2 1/6] hw/display/vga: Allow a device to report vendor specific blanking Jamin Lin
` (5 more replies)
0 siblings, 6 replies; 7+ messages in thread
From: Jamin Lin @ 2026-09-10 1:44 UTC (permalink / raw)
To: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Kane Chen, Andrew Jeffery, Joel Stanley, Pierrick Bouvier,
Paolo Bonzini, Gerd Hoffmann, open list:ASPEED BMCs,
open list:All patches CC here
Cc: Jamin Lin, Troy Lee
This series adds support for the ASPEED VGA PCI device found on AST2600
and AST2700 platforms.
The longer-term goal is to support the video path used by BMC KVM
applications. The planned implementation consists of three stages:
1. Add an ASPEED VGA PCI device so that a host system running in QEMU can
use the ASPEED VGA device as its display adapter. This series
implements this stage.
2. Back the VGA framebuffer with shared memory, for example using a QEMU
memory backend backed by /dev/shm/vram. This allows the framebuffer
contents produced by the host QEMU instance to be accessed by another
QEMU instance emulating the BMC.
3. Add an ASPEED Video Engine model to the AST2600/AST2700 BMC machine.
The model will read the shared VGA framebuffer and emulate the video
capture and JPEG compression path used by the BMC.
The intended setup runs two QEMU instances on the same host machine:
+-------------------------------------------------------------------+
| Host Machine |
| |
| +---------------------------+ |
| | QEMU 1: VMM / Host Guest | |
| | | |
| | +---------------------+ | |
| | | ASPEED VGA PCI | | |
| | | device | | |
| | +----------+----------+ | |
| | | | |
| | v | |
| | +---------------------+ | |
| | | VGA VRAM | | |
| | +----------+----------+ | |
| +-------------|-------------+ |
| | |
| | memory-backend-file |
| v |
| +-----------------------+ |
| | /dev/shm/vram | |
| | Shared VRAM file | |
| +-----------+-----------+ |
| | |
| | read by |
| v |
| +---------------------------+ |
| | QEMU 2: BMC | |
| | AST2600 / AST2700 | |
| | | |
| | +---------------------+ | |
| | | ASPEED Video Engine | | |
| | | | | |
| | | - Read VRAM | | |
| | | - Capture frame | | |
| | | - Compress video | | |
| | +----------+----------+ | |
| +-------------|-------------+ |
| | |
+----------------|--------------------------------------------------+
With the complete implementation, users will be able to view the display
output of the emulated host through the OpenBMC KVM web interface.
This series adds the following devices:
- ``ast2600-vga`` Aspeed AST2600 VGA
- ``ast2700-vga`` Aspeed AST2700 VGA
The size of the framebuffer aperture is set with ``vgamem_mb``, 32 MiB by
default. The device comes up with the standard VGA BIOS; pass ``romfile``
to use Aspeed's own option ROM instead, which can be downloaded from
https://www.aspeedtech.com/support_driver/
For example, the AST2700 VGA device can be used with an `aarch64
virt` machine as follows:
$ qemu-system-aarch64 \
-M virt \
-cpu neoverse-n1 -smp 8 -m 4G \
-bios edk2-aarch64-code.fd \
-device ast2700-vga,vgamem_mb=32,romfile=uefi_arm_2700_vga.rom \
-drive file=noble-server-cloudimg-arm64.img,format=qcow2 \
-device qemu-xhci,id=xhci \
-device usb-kbd,bus=xhci.0 \
-device usb-tablet,bus=xhci.0 \
-display vnc=0.0.0.0:0
The disk image is an Ubuntu cloud image from
https://cloud-images.ubuntu.com/.
The output goes to the VNC backend on display 0, which listens on TCP port 5900,
so point a VNC viewer at that port to see the screen.
v1:
1. Add ASPEED VGA display controller
2. Add the hardware overlay cursor
3. Add the 2D graphics engine
v2:
1. Drop "Add the ASPEED VGA device id" patch
Jamin Lin (6):
hw/display/vga: Allow a device to report vendor specific blanking
hw/display/vga: Move VGA_HPEL_NEUTRAL to vga_int.h
hw/display/aspeed-vga: Add ASPEED VGA display controller
hw/display/aspeed-vga: Add the hardware overlay cursor
hw/display/aspeed-vga: Add the 2D graphics engine
docs/system/arm/aspeed: Document the VGA display controller
docs/system/arm/aspeed.rst | 37 +-
hw/display/aspeed-vga.h | 61 +++
hw/display/vga_int.h | 9 +
hw/display/aspeed-vga.c | 945 +++++++++++++++++++++++++++++++++++++
hw/display/vga.c | 11 +-
hw/display/Kconfig | 6 +
hw/display/meson.build | 1 +
hw/display/trace-events | 7 +
8 files changed, 1066 insertions(+), 11 deletions(-)
create mode 100644 hw/display/aspeed-vga.h
create mode 100644 hw/display/aspeed-vga.c
--
2.53.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v2 1/6] hw/display/vga: Allow a device to report vendor specific blanking
2026-09-10 1:44 [PATCH v2 0/6] Add ASPEED VGA PCI device support Jamin Lin
@ 2026-09-10 1:44 ` Jamin Lin
2026-09-10 1:44 ` [PATCH v2 2/6] hw/display/vga: Move VGA_HPEL_NEUTRAL to vga_int.h Jamin Lin
` (4 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Jamin Lin @ 2026-09-10 1:44 UTC (permalink / raw)
To: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Kane Chen, Andrew Jeffery, Joel Stanley, Pierrick Bouvier,
Paolo Bonzini, Gerd Hoffmann, open list:ASPEED BMCs,
open list:All patches CC here
Cc: Jamin Lin, Troy Lee
The common code decides that the display is blanked by looking at bit 5
of the attribute controller index register. A device which instead gates
the sync signals through vendor specific registers has no way to express
that, and keeps scanning out the last image after its CRTC was disabled.
Add an optional is_blanked() callback for such a device to report it.
Devices which leave it unset are unaffected.
Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
---
hw/display/vga_int.h | 1 +
hw/display/vga.c | 4 ++--
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/hw/display/vga_int.h b/hw/display/vga_int.h
index ca69ae9815..a017ab7a99 100644
--- a/hw/display/vga_int.h
+++ b/hw/display/vga_int.h
@@ -99,6 +99,7 @@ typedef struct VGACommonState {
uint8_t palette[768];
int32_t bank_offset;
int (*get_bpp)(struct VGACommonState *s);
+ bool (*is_blanked)(struct VGACommonState *s);
void (*get_params)(struct VGACommonState *s, VGADisplayParams *params);
void (*get_resolution)(struct VGACommonState *s,
int *pwidth,
diff --git a/hw/display/vga.c b/hw/display/vga.c
index cb0e28b79b..1d96308060 100644
--- a/hw/display/vga.c
+++ b/hw/display/vga.c
@@ -1814,7 +1814,7 @@ static bool vga_update_display(void *opaque)
/* nothing to do */
} else {
full_update = 0;
- if (!(s->ar_index & 0x20)) {
+ if (!(s->ar_index & 0x20) || (s->is_blanked && s->is_blanked(s))) {
graphic_mode = GMODE_BLANK;
} else {
graphic_mode = s->gr[VGA_GFX_MISC] & VGA_GR06_GRAPHICS_MODE;
@@ -1936,7 +1936,7 @@ static void vga_update_text(void *opaque, uint32_t *chardata)
qemu_flush_coalesced_mmio_buffer();
- if (!(s->ar_index & 0x20)) {
+ if (!(s->ar_index & 0x20) || (s->is_blanked && s->is_blanked(s))) {
graphic_mode = GMODE_BLANK;
} else {
graphic_mode = s->gr[VGA_GFX_MISC] & VGA_GR06_GRAPHICS_MODE;
--
2.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v2 2/6] hw/display/vga: Move VGA_HPEL_NEUTRAL to vga_int.h
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 ` Jamin Lin
2026-09-10 1:44 ` [PATCH v2 3/6] hw/display/aspeed-vga: Add ASPEED VGA display controller Jamin Lin
` (3 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Jamin Lin @ 2026-09-10 1:44 UTC (permalink / raw)
To: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Kane Chen, Andrew Jeffery, Joel Stanley, Pierrick Bouvier,
Paolo Bonzini, Gerd Hoffmann, open list:ASPEED BMCs,
open list:All patches CC here
Cc: Jamin Lin, Troy Lee
VGA_HPEL_NEUTRAL is the pel panning value that means no shift. Move it
to the header so that other VGA devices can use it.
No functional change.
Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
---
hw/display/vga_int.h | 8 ++++++++
hw/display/vga.c | 7 -------
2 files changed, 8 insertions(+), 7 deletions(-)
diff --git a/hw/display/vga_int.h b/hw/display/vga_int.h
index a017ab7a99..806baa427a 100644
--- a/hw/display/vga_int.h
+++ b/hw/display/vga_int.h
@@ -38,6 +38,14 @@
#define CH_ATTR_SIZE (160 * 100)
#define VGA_MAX_HEIGHT 2048
+/*
+ * This value corresponds to a shift of zero pixels
+ * in 9-dot text mode. In other modes, bit 3 is undefined;
+ * we just ignore it, so that 8 corresponds to zero pixels
+ * in all modes.
+ */
+#define VGA_HPEL_NEUTRAL 8
+
struct vga_precise_retrace {
int64_t ticks_per_char;
int64_t total_chars;
diff --git a/hw/display/vga.c b/hw/display/vga.c
index 1d96308060..ab06fb3c13 100644
--- a/hw/display/vga.c
+++ b/hw/display/vga.c
@@ -53,13 +53,6 @@ bool have_vga = true;
/* Address mask for non-VESA modes. */
#define VGA_VRAM_SIZE (256 * KiB)
-/* This value corresponds to a shift of zero pixels
- * in 9-dot text mode. In other modes, bit 3 is undefined;
- * we just ignore it, so that 8 corresponds to zero pixels
- * in all modes.
- */
-#define VGA_HPEL_NEUTRAL 8
-
/*
* Video Graphics Array (VGA)
*
--
2.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v2 3/6] hw/display/aspeed-vga: Add ASPEED VGA display controller
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 ` Jamin Lin
2026-09-10 1:44 ` [PATCH v2 4/6] hw/display/aspeed-vga: Add the hardware overlay cursor Jamin Lin
` (2 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Jamin Lin @ 2026-09-10 1:44 UTC (permalink / raw)
To: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Kane Chen, Andrew Jeffery, Joel Stanley, Pierrick Bouvier,
Paolo Bonzini, Gerd Hoffmann, open list:ASPEED BMCs,
open list:All patches CC here
Cc: Jamin Lin, Troy Lee
The host reaches the display controller of the ASPEED BMC SoCs as a PCI
VGA device. It follows the IBM VGA 1.0 specification and adds vendor
specific CRTC registers that describe the linear framebuffer.
- BAR 0 is the framebuffer. CRAA reports its size.
- BAR 1 is a window over the SoC registers. Only the mirror of the
legacy VGA I/O ports at offset 0x380 is modelled.
- The legacy VGA I/O ports and the 0xa0000 window let a VGA BIOS drive
the device until the guest driver takes over. The bochs VBE ports
are left out; the hardware has no such interface.
- The extended CRTC registers carry the color format (CRA3), the line
offset (CR13, CRB0), the scanout address (CR0C, CR0D, CRAF) and the
mode overflow bits (CRAC, CRAE). Until the driver sets a color
format the device behaves as a standard VGA.
- Blanking follows CR17 and CRB6, which is how the driver turns the
CRTC off.
Reset leaves the device as the BMC firmware would, so the driver does
not try to POST the chip. That includes selecting the color I/O
addresses, without which every access to the extended registers at
0x3d4/0x3d5 would be rejected as belonging to the monochrome range.
Port 0x3c3, the VGA enable register, is not in the common code and is
handled here.
The AST2600 and AST2700 differ only in the PCI revision id the driver
reads to tell the SoC generations apart, so that is a class attribute.
Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
---
hw/display/aspeed-vga.h | 49 ++++
hw/display/aspeed-vga.c | 490 ++++++++++++++++++++++++++++++++++++++++
hw/display/Kconfig | 6 +
hw/display/meson.build | 1 +
hw/display/trace-events | 4 +
5 files changed, 550 insertions(+)
create mode 100644 hw/display/aspeed-vga.h
create mode 100644 hw/display/aspeed-vga.c
diff --git a/hw/display/aspeed-vga.h b/hw/display/aspeed-vga.h
new file mode 100644
index 0000000000..4b0315f699
--- /dev/null
+++ b/hw/display/aspeed-vga.h
@@ -0,0 +1,49 @@
+/*
+ * ASPEED VGA display controller
+ *
+ * Copyright (c) 2026 ASPEED Technology Inc.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_DISPLAY_ASPEED_VGA_H
+#define HW_DISPLAY_ASPEED_VGA_H
+
+#include "qemu/units.h"
+#include "qom/object.h"
+#include "hw/pci/pci_device.h"
+#include "vga_int.h"
+
+#define TYPE_ASPEED_VGA "aspeed-vga"
+#define TYPE_AST2600_VGA "ast2600-vga"
+#define TYPE_AST2700_VGA "ast2700-vga"
+OBJECT_DECLARE_TYPE(AspeedVGAState, AspeedVGAClass, ASPEED_VGA)
+
+/* BAR 1 is a 128KB window over the SoC registers */
+#define ASPEED_VGA_MMIO_SIZE (128 * KiB)
+/* VGA, offset 0x380 from the BAR 1 register base */
+#define ASPEED_VGA_IOPORT_OFFSET 0x380
+#define ASPEED_VGA_IOPORT_SIZE 0x80
+
+struct AspeedVGAState {
+ PCIDevice parent_obj;
+
+ VGACommonState vga;
+ MemoryRegion mmio;
+ MemoryRegion ioport;
+
+ uint8_t vgaer;
+
+ /* saved standard VGA handlers, used while the extended mode is off */
+ int (*std_get_bpp)(VGACommonState *s);
+ void (*std_get_params)(VGACommonState *s, VGADisplayParams *params);
+ void (*std_get_resolution)(VGACommonState *s, int *pwidth, int *pheight);
+};
+
+struct AspeedVGAClass {
+ PCIDeviceClass parent_class;
+
+ uint8_t revision;
+};
+
+#endif /* HW_DISPLAY_ASPEED_VGA_H */
diff --git a/hw/display/aspeed-vga.c b/hw/display/aspeed-vga.c
new file mode 100644
index 0000000000..bf74a0c8ce
--- /dev/null
+++ b/hw/display/aspeed-vga.c
@@ -0,0 +1,490 @@
+/*
+ * ASPEED VGA display controller
+ *
+ * Copyright (c) 2026 ASPEED Technology Inc.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * The display controller found in the ASPEED BMC SoCs is exposed to the
+ * host as a PCI VGA device that is register compatible with the IBM VGA
+ * 1.0 specification, extended with a set of vendor specific registers.
+ *
+ * - BAR 0: linear framebuffer (VRAM)
+ * - BAR 1: SoC register window; the mirror of the legacy VGA I/O ports
+ * at offset 0x380 is the only implemented part
+ * - legacy VGA I/O ports and the 0xa0000 memory window, so that a
+ * plain VGA BIOS keeps working before the ast driver takes over
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "qemu/log.h"
+#include "hw/pci/pci_device.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/core/registerfields.h"
+#include "migration/vmstate.h"
+#include "qemu/module.h"
+#include "qom/object.h"
+#include "ui/console.h"
+#include "trace.h"
+#include "aspeed-vga.h"
+#include "vga_int.h"
+#include "vga_regs.h"
+
+/*
+ * VGA Display Controller (VGA)
+ *
+ * Offset 0 from the BAR 1 register base
+ */
+
+/* General Registers */
+REG8(VGAER, 0x3c3)
+ FIELD(VGAER, VGA_ENABLE, 0, 1)
+
+/*
+ * ASPEED extended CRT controller register indices
+ *
+ * The CRT controller is reached through its index and data port pair,
+ * 0x3b4/0x3b5 in mono mode and 0x3d4/0x3d5 in color mode.
+ */
+REG8(AST_CR_PASSWORD, 0x80)
+#define AST_CR_PASSWORD_UNLOCK 0xa8
+REG8(AST_CR_VRAM_RSRV, 0x99)
+REG8(AST_CR_PCI_CTRL2, 0xa1)
+ FIELD(AST_CR_PCI_CTRL2, MMIO_ENABLED, 2, 1)
+REG8(AST_CR_COLOR_MODE, 0xa3)
+ FIELD(AST_CR_COLOR_MODE, FORMAT, 0, 4)
+#define AST_CR_COLOR_MODE_FORMAT_32BPP BIT(3)
+#define AST_CR_COLOR_MODE_FORMAT_16BPP BIT(2)
+#define AST_CR_COLOR_MODE_FORMAT_15BPP BIT(1)
+#define AST_CR_COLOR_MODE_FORMAT_8BPP BIT(0)
+REG8(AST_CR_STRAP1, 0xaa)
+REG8(AST_CR_H_OVERFLOW, 0xac)
+ FIELD(AST_CR_H_OVERFLOW, HDE, 2, 2)
+REG8(AST_CR_V_OVERFLOW, 0xae)
+ FIELD(AST_CR_V_OVERFLOW, VDE_D10, 1, 1)
+REG8(AST_CR_START_ADDR_EXT, 0xaf)
+REG8(AST_CR_OFFSET_HI, 0xb0)
+ FIELD(AST_CR_OFFSET_HI, OFFSET, 0, 6)
+REG8(AST_CR_POWER_MGMT, 0xb6)
+ FIELD(AST_CR_POWER_MGMT, VSYNC_OFF, 1, 1)
+ FIELD(AST_CR_POWER_MGMT, HSYNC_OFF, 0, 1)
+REG8(AST_CR_SOC_SCRATCH0, 0xd0)
+ FIELD(AST_CR_SOC_SCRATCH0, VRAM_INIT_BY_BMC, 7, 1)
+ FIELD(AST_CR_SOC_SCRATCH0, VRAM_INIT_READY, 6, 1)
+ FIELD(AST_CR_SOC_SCRATCH0, IKVM_WIDESCREEN, 0, 1)
+
+/*
+ * 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
+ * standard VGA.
+ */
+static bool aspeed_vga_ext_enabled(AspeedVGAState *s)
+{
+ uint8_t format = FIELD_EX8(s->vga.cr[R_AST_CR_COLOR_MODE],
+ AST_CR_COLOR_MODE, FORMAT);
+
+ return format != 0;
+}
+
+static int aspeed_vga_get_bpp(VGACommonState *vga)
+{
+ AspeedVGAState *s = container_of(vga, AspeedVGAState, vga);
+ uint8_t format;
+
+ if (!aspeed_vga_ext_enabled(s)) {
+ return s->std_get_bpp(vga);
+ }
+
+ format = FIELD_EX8(vga->cr[R_AST_CR_COLOR_MODE],
+ AST_CR_COLOR_MODE, FORMAT);
+
+ switch (format) {
+ case AST_CR_COLOR_MODE_FORMAT_8BPP:
+ return 8;
+ case AST_CR_COLOR_MODE_FORMAT_15BPP:
+ return 15;
+ case AST_CR_COLOR_MODE_FORMAT_16BPP:
+ return 16;
+ case AST_CR_COLOR_MODE_FORMAT_32BPP:
+ return 32;
+ default:
+ return 0;
+ }
+}
+
+/*
+ * The ast driver turns the display off through CR17 bit 7 or the power
+ * management register, not through the attribute controller bit that the
+ * common VGA code looks at.
+ */
+static bool aspeed_vga_is_blanked(VGACommonState *vga)
+{
+ AspeedVGAState *s = container_of(vga, AspeedVGAState, vga);
+ bool crtc_sync_off;
+ bool pm_sync_off;
+
+ if (!aspeed_vga_ext_enabled(s)) {
+ return false;
+ }
+
+ crtc_sync_off = !(vga->cr[VGA_CRTC_MODE] & VGA_CR17_H_V_SIGNALS_ENABLED);
+ pm_sync_off = vga->cr[R_AST_CR_POWER_MGMT] &
+ (R_AST_CR_POWER_MGMT_HSYNC_OFF_MASK |
+ R_AST_CR_POWER_MGMT_VSYNC_OFF_MASK);
+
+ return crtc_sync_off || pm_sync_off;
+}
+
+static void aspeed_vga_get_resolution(VGACommonState *vga,
+ int *pwidth, int *pheight)
+{
+ AspeedVGAState *s = container_of(vga, AspeedVGAState, vga);
+ int height;
+ int width;
+
+ if (!aspeed_vga_ext_enabled(s)) {
+ s->std_get_resolution(vga, pwidth, pheight);
+ return;
+ }
+
+ /* horizontal display end, D[7:0] in CR01 and D[9:8] in CRAC */
+ width = vga->cr[VGA_CRTC_H_DISP] |
+ (FIELD_EX8(vga->cr[R_AST_CR_H_OVERFLOW],
+ AST_CR_H_OVERFLOW, HDE) << 8);
+ width = (width + 1) * 8;
+
+ /*
+ * vertical display end, D[7:0] in CR12, D[9:8] in CR07 and
+ * D[10] in CRAE
+ */
+ height = vga->cr[VGA_CRTC_V_DISP_END] |
+ ((vga->cr[VGA_CRTC_OVERFLOW] & 0x02) << 7) |
+ ((vga->cr[VGA_CRTC_OVERFLOW] & 0x40) << 3);
+ if (FIELD_EX8(vga->cr[R_AST_CR_V_OVERFLOW], AST_CR_V_OVERFLOW, VDE_D10)) {
+ height |= BIT(10);
+ }
+ height = height + 1;
+
+ *pwidth = width;
+ *pheight = height;
+}
+
+static void aspeed_vga_get_params(VGACommonState *vga,
+ VGADisplayParams *params)
+{
+ AspeedVGAState *s = container_of(vga, AspeedVGAState, vga);
+
+ if (!aspeed_vga_ext_enabled(s)) {
+ s->std_get_params(vga, params);
+ return;
+ }
+
+ /*
+ * GR05 bit 6 selects the mode 13 shift mode, which is packed pixel like
+ * the extended modes. Without it the common code picks its planar
+ * reader when it draws into its own surface.
+ */
+ vga->gr[VGA_GFX_MODE] |= BIT(6);
+
+ /*
+ * line offset is in units of 8 bytes, D[7:0] in CR13 and
+ * D[13:8] in CRB0
+ */
+ params->line_offset = (vga->cr[VGA_CRTC_OFFSET] |
+ (FIELD_EX8(vga->cr[R_AST_CR_OFFSET_HI],
+ AST_CR_OFFSET_HI, OFFSET) << 8)) * 8;
+
+ /*
+ * start address is in units of 4 bytes, D[7:0] in CR0D,
+ * D[15:8] in CR0C and D[23:16] in CRAF
+ */
+ params->start_addr = vga->cr[VGA_CRTC_START_LO] |
+ (vga->cr[VGA_CRTC_START_HI] << 8) |
+ (vga->cr[R_AST_CR_START_ADDR_EXT] << 16);
+
+ params->line_compare = 65535;
+ params->hpel = VGA_HPEL_NEUTRAL;
+ params->hpel_split = false;
+}
+
+static uint8_t aspeed_vga_vgamem_size_reg(uint32_t vram_size_mb)
+{
+ switch (vram_size_mb) {
+ case 8:
+ return 0;
+ case 16:
+ return 1;
+ case 32:
+ return 2;
+ case 64:
+ return 3;
+ default:
+ g_assert_not_reached();
+ }
+}
+
+static uint8_t aspeed_vga_read_byte(AspeedVGAState *s, uint32_t port)
+{
+ uint8_t val;
+
+ switch (port) {
+ case A_VGAER:
+ val = s->vgaer;
+ break;
+ default:
+ val = vga_ioport_read(&s->vga, port);
+ break;
+ }
+
+ trace_aspeed_vga_read_byte(port, val);
+ return val;
+}
+
+static void aspeed_vga_write_byte(AspeedVGAState *s, uint32_t port, uint8_t val)
+{
+ trace_aspeed_vga_write_byte(port, val);
+
+ switch (port) {
+ case A_VGAER:
+ s->vgaer = val & R_VGAER_VGA_ENABLE_MASK;
+ break;
+ default:
+ vga_ioport_write(&s->vga, port, val);
+ break;
+ }
+}
+
+static uint64_t aspeed_vga_ioport_read(void *opaque, hwaddr addr, unsigned size)
+{
+ AspeedVGAState *s = opaque;
+ uint64_t val = 0;
+ unsigned i;
+
+ addr += ASPEED_VGA_IOPORT_OFFSET;
+
+ for (i = 0; i < size; i++) {
+ val = deposit64(val, i * 8, 8, aspeed_vga_read_byte(s, addr + i));
+ }
+
+ return val;
+}
+
+static void aspeed_vga_ioport_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ AspeedVGAState *s = opaque;
+ unsigned i;
+
+ addr += ASPEED_VGA_IOPORT_OFFSET;
+
+ /*
+ * Byte by byte in little endian order, so that a single word write to an
+ * index/data register pair updates the index first.
+ */
+ for (i = 0; i < size; i++) {
+ aspeed_vga_write_byte(s, addr + i, extract64(val, i * 8, 8));
+ }
+}
+
+static const MemoryRegionOps aspeed_vga_ioport_ops = {
+ .read = aspeed_vga_ioport_read,
+ .write = aspeed_vga_ioport_write,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 4,
+ .impl.min_access_size = 1,
+ .impl.max_access_size = 4,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void aspeed_vga_reset_hold(Object *obj, ResetType type)
+{
+ AspeedVGAState *s = ASPEED_VGA(obj);
+ VGACommonState *vga = &s->vga;
+
+ vga_common_reset(vga);
+
+ s->vgaer = R_VGAER_VGA_ENABLE_MASK;
+
+ /*
+ * Select the color emulation I/O addresses, so that the CRTC answers at
+ * 0x3d4/0x3d5 where the extended registers live. The VGA BIOS normally
+ * does this, and the ast driver only does it when it has to POST the
+ * chip, which this model reports as already done.
+ */
+ vga->msr = VGA_MIS_COLOR;
+
+ vga->cr[R_AST_CR_PASSWORD] = AST_CR_PASSWORD_UNLOCK;
+ vga->cr[R_AST_CR_PCI_CTRL2] = R_AST_CR_PCI_CTRL2_MMIO_ENABLED_MASK;
+
+ /* VRAM size, no reserved area */
+ vga->cr[R_AST_CR_STRAP1] = aspeed_vga_vgamem_size_reg(vga->vram_size_mb);
+ vga->cr[R_AST_CR_VRAM_RSRV] = 0;
+
+ vga->cr[R_AST_CR_SOC_SCRATCH0] =
+ 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;
+}
+
+/*
+ * The standard VGA has a bochs VBE interface and this hardware does not, so
+ * set up the 0xa0000 window and the legacy I/O ports here, not vga_init().
+ */
+static void aspeed_vga_init_io(AspeedVGAState *s, PCIDevice *dev)
+{
+ MemoryRegion *address_space = pci_address_space(dev);
+ const MemoryRegionPortio *vga_ports;
+ const MemoryRegionPortio *vbe_ports;
+ VGACommonState *vga = &s->vga;
+ MemoryRegion *vga_io_memory;
+
+ vga->bank_offset = 0;
+ vga->legacy_address_space = address_space;
+
+ vga_io_memory = vga_init_io(vga, OBJECT(dev), &vga_ports, &vbe_ports);
+ memory_region_add_subregion_overlap(address_space, 0x000a0000,
+ vga_io_memory, 1);
+ memory_region_set_coalescing(vga_io_memory);
+
+ /* vga_init_io() fills in both VGA and VBE ports; only VGA is registered */
+ portio_list_init(&vga->vga_port_list, OBJECT(dev), vga_ports, vga, "vga");
+ portio_list_set_flush_coalesced(&vga->vga_port_list);
+ portio_list_add(&vga->vga_port_list, pci_address_space_io(dev), 0x3b0);
+}
+
+static void aspeed_vga_realize(PCIDevice *dev, Error **errp)
+{
+ AspeedVGAClass *ac = ASPEED_VGA_GET_CLASS(dev);
+ AspeedVGAState *s = ASPEED_VGA(dev);
+ VGACommonState *vga = &s->vga;
+
+ switch (vga->vram_size_mb) {
+ case 8:
+ case 16:
+ case 32:
+ case 64:
+ break;
+ default:
+ error_setg(errp,
+ TYPE_ASPEED_VGA ": vgamem_mb must be 8, 16, 32 or 64");
+ return;
+ }
+
+ if (!vga_common_init(vga, OBJECT(dev), errp)) {
+ return;
+ }
+
+ aspeed_vga_init_io(s, dev);
+
+ s->std_get_bpp = vga->get_bpp;
+ s->std_get_params = vga->get_params;
+ s->std_get_resolution = vga->get_resolution;
+ vga->get_bpp = aspeed_vga_get_bpp;
+ vga->get_params = aspeed_vga_get_params;
+ vga->get_resolution = aspeed_vga_get_resolution;
+ vga->is_blanked = aspeed_vga_is_blanked;
+ vga->big_endian_fb = false;
+
+ vga->con = qemu_graphic_console_create(DEVICE(dev), 0, vga->hw_ops, vga);
+
+ pci_set_byte(&dev->config[PCI_REVISION_ID], ac->revision);
+
+ memory_region_init_io(&s->mmio, OBJECT(dev), &unassigned_io_ops, NULL,
+ "aspeed-vga.container", ASPEED_VGA_MMIO_SIZE);
+
+ memory_region_init_io(&s->ioport, OBJECT(dev), &aspeed_vga_ioport_ops, s,
+ "aspeed-vga.ioport", ASPEED_VGA_IOPORT_SIZE);
+ memory_region_add_subregion(&s->mmio, ASPEED_VGA_IOPORT_OFFSET,
+ &s->ioport);
+
+ pci_register_bar(dev, 0, PCI_BASE_ADDRESS_MEM_PREFETCH, &vga->vram);
+ pci_register_bar(dev, 1, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->mmio);
+}
+
+static void aspeed_vga_exit(PCIDevice *dev)
+{
+ AspeedVGAState *s = ASPEED_VGA(dev);
+
+ qemu_graphic_console_close(s->vga.con);
+}
+
+static const VMStateDescription vmstate_aspeed_vga = {
+ .name = "aspeed-vga",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (const VMStateField[]) {
+ VMSTATE_PCI_DEVICE(parent_obj, AspeedVGAState),
+ VMSTATE_STRUCT(vga, AspeedVGAState, 0, vmstate_vga_common,
+ VGACommonState),
+ VMSTATE_UINT8(vgaer, AspeedVGAState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const Property aspeed_vga_properties[] = {
+ DEFINE_PROP_UINT32("vgamem_mb", AspeedVGAState, vga.vram_size_mb, 32),
+};
+
+static void aspeed_vga_class_init(ObjectClass *klass, const void *data)
+{
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ k->realize = aspeed_vga_realize;
+ k->exit = aspeed_vga_exit;
+ k->vendor_id = PCI_VENDOR_ID_ASPEED;
+ k->device_id = 0x2000;
+ k->class_id = PCI_CLASS_DISPLAY_VGA;
+ k->subsystem_vendor_id = PCI_VENDOR_ID_ASPEED;
+ k->subsystem_id = 0x2000;
+ k->romfile = "vgabios-stdvga.bin";
+
+ dc->vmsd = &vmstate_aspeed_vga;
+ rc->phases.hold = aspeed_vga_reset_hold;
+ device_class_set_props(dc, aspeed_vga_properties);
+ dc->hotpluggable = false;
+ set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories);
+}
+
+static void ast2600_vga_class_init(ObjectClass *klass, const void *data)
+{
+ AspeedVGAClass *avc = ASPEED_VGA_CLASS(klass);
+
+ DEVICE_CLASS(klass)->desc = "ASPEED AST2600 VGA";
+ avc->revision = 0x52;
+}
+
+static void ast2700_vga_class_init(ObjectClass *klass, const void *data)
+{
+ AspeedVGAClass *avc = ASPEED_VGA_CLASS(klass);
+
+ DEVICE_CLASS(klass)->desc = "ASPEED AST2700 VGA";
+ avc->revision = 0x72;
+}
+
+static const TypeInfo aspeed_vga_types[] = {
+ {
+ .name = TYPE_ASPEED_VGA,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(AspeedVGAState),
+ .class_size = sizeof(AspeedVGAClass),
+ .class_init = aspeed_vga_class_init,
+ .abstract = true,
+ .interfaces = (const InterfaceInfo[]) {
+ { INTERFACE_CONVENTIONAL_PCI_DEVICE },
+ { },
+ },
+ }, {
+ .name = TYPE_AST2600_VGA,
+ .parent = TYPE_ASPEED_VGA,
+ .class_init = ast2600_vga_class_init,
+ }, {
+ .name = TYPE_AST2700_VGA,
+ .parent = TYPE_ASPEED_VGA,
+ .class_init = ast2700_vga_class_init,
+ },
+};
+
+DEFINE_TYPES(aspeed_vga_types)
diff --git a/hw/display/Kconfig b/hw/display/Kconfig
index b3593fe981..c5253a08b3 100644
--- a/hw/display/Kconfig
+++ b/hw/display/Kconfig
@@ -48,6 +48,12 @@ config VGA_PCI
select VGA
select EDID
+config ASPEED_VGA
+ bool
+ default y if PCI_DEVICES
+ depends on PCI
+ select VGA
+
config VGA_ISA
bool
depends on ISA_BUS
diff --git a/hw/display/meson.build b/hw/display/meson.build
index ffecedbf70..f15e9c28a9 100644
--- a/hw/display/meson.build
+++ b/hw/display/meson.build
@@ -18,6 +18,7 @@ system_ss.add(when: 'CONFIG_SSD0303', if_true: files('ssd0303.c'))
system_ss.add(when: 'CONFIG_SSD0323', if_true: files('ssd0323.c'))
system_ss.add(when: 'CONFIG_XEN_BUS', if_true: files('xenfb.c'))
+system_ss.add(when: 'CONFIG_ASPEED_VGA', if_true: files('aspeed-vga.c'))
system_ss.add(when: 'CONFIG_VGA_PCI', if_true: files('vga-pci.c'))
system_ss.add(when: 'CONFIG_VGA_ISA', if_true: files('vga-isa.c'))
system_ss.add(when: 'CONFIG_VGA_MMIO', if_true: files('vga-mmio.c'))
diff --git a/hw/display/trace-events b/hw/display/trace-events
index 4bfc457fba..fd4289d755 100644
--- a/hw/display/trace-events
+++ b/hw/display/trace-events
@@ -138,6 +138,10 @@ vga_cirrus_write_blt(uint32_t offset, uint32_t val) "offset 0x%x, val 0x%x"
vga_cirrus_write_gr(uint8_t index, uint8_t val) "GR addr 0x%02x, val 0x%02x"
vga_cirrus_bitblt_start(uint8_t blt_rop, uint8_t blt_mode, uint8_t blt_modeext, int blt_width, int blt_height, int blt_dstpitch, int blt_srcpitch, uint32_t blt_dstaddr, uint32_t blt_srcaddr, uint8_t gr_val) "rop=0x%02x mode=0x%02x modeext=0x%02x w=%d h=%d dpitch=%d spitch=%d daddr=0x%08"PRIx32" saddr=0x%08"PRIx32" writemask=0x%02x"
+# 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"
+
# sii9022.c
sii9022_read_reg(uint8_t addr, uint8_t val) "addr 0x%02x, val 0x%02x"
sii9022_write_reg(uint8_t addr, uint8_t val) "addr 0x%02x, val 0x%02x"
--
2.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v2 4/6] hw/display/aspeed-vga: Add the hardware overlay cursor
2026-09-10 1:44 [PATCH v2 0/6] Add ASPEED VGA PCI device support Jamin Lin
` (2 preceding siblings ...)
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 ` Jamin Lin
2026-09-10 1:44 ` [PATCH v2 5/6] hw/display/aspeed-vga: Add the 2D graphics engine Jamin Lin
2026-09-10 1:44 ` [PATCH v2 6/6] docs/system/arm/aspeed: Document the VGA display controller Jamin Lin
5 siblings, 0 replies; 7+ messages in thread
From: Jamin Lin @ 2026-09-10 1:44 UTC (permalink / raw)
To: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Kane Chen, Andrew Jeffery, Joel Stanley, Pierrick Bouvier,
Paolo Bonzini, Gerd Hoffmann, open list:ASPEED BMCs,
open list:All patches CC here
Cc: Jamin Lin, Troy Lee
The display controller draws a 64x64 cursor over the picture. CRC2 to
CRCB hold its position, the address of its bitmap in the framebuffer,
the offsets into that bitmap and the format.
Draw it through the common code's cursor callbacks. The position cannot
be negative, so the offsets say how many rows and columns to skip when
the cursor runs off the top or the left of the screen.
CRCB selects the format. Both are modelled: ARGB4444, and the one the
datasheet calls 2 bpp, which is also two bytes per pixel but carries an
AND and an XOR mask in the top two bits instead of an alpha.
Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
---
hw/display/aspeed-vga.h | 2 +
hw/display/aspeed-vga.c | 194 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 196 insertions(+)
diff --git a/hw/display/aspeed-vga.h b/hw/display/aspeed-vga.h
index 4b0315f699..55dcc57108 100644
--- a/hw/display/aspeed-vga.h
+++ b/hw/display/aspeed-vga.h
@@ -33,6 +33,8 @@ struct AspeedVGAState {
MemoryRegion ioport;
uint8_t vgaer;
+ uint32_t last_cursor_y;
+ bool last_cursor_on;
/* saved standard VGA handlers, used while the extended mode is off */
int (*std_get_bpp)(VGACommonState *s);
diff --git a/hw/display/aspeed-vga.c b/hw/display/aspeed-vga.c
index bf74a0c8ce..e40d503910 100644
--- a/hw/display/aspeed-vga.c
+++ b/hw/display/aspeed-vga.c
@@ -26,6 +26,7 @@
#include "qemu/module.h"
#include "qom/object.h"
#include "ui/console.h"
+#include "ui/pixel_ops.h"
#include "trace.h"
#include "aspeed-vga.h"
#include "vga_int.h"
@@ -69,11 +70,40 @@ REG8(AST_CR_OFFSET_HI, 0xb0)
REG8(AST_CR_POWER_MGMT, 0xb6)
FIELD(AST_CR_POWER_MGMT, VSYNC_OFF, 1, 1)
FIELD(AST_CR_POWER_MGMT, HSYNC_OFF, 0, 1)
+REG8(AST_CR_CURSOR_XOFF, 0xc2)
+ FIELD(AST_CR_CURSOR_XOFF, OFFSET, 0, 6)
+REG8(AST_CR_CURSOR_YOFF, 0xc3)
+ FIELD(AST_CR_CURSOR_YOFF, OFFSET, 0, 6)
+REG8(AST_CR_CURSOR_X_LO, 0xc4)
+REG8(AST_CR_CURSOR_X_HI, 0xc5)
+ FIELD(AST_CR_CURSOR_X_HI, X, 0, 5)
+REG8(AST_CR_CURSOR_Y_LO, 0xc6)
+REG8(AST_CR_CURSOR_Y_HI, 0xc7)
+ FIELD(AST_CR_CURSOR_Y_HI, Y, 0, 4)
+REG8(AST_CR_CURSOR_ADDR0, 0xc8)
+ FIELD(AST_CR_CURSOR_ADDR0, ADDR, 1, 7)
+REG8(AST_CR_CURSOR_ADDR1, 0xc9)
+REG8(AST_CR_CURSOR_ADDR2, 0xca)
+ FIELD(AST_CR_CURSOR_ADDR2, ADDR, 0, 7)
+REG8(AST_CR_CURSOR_CTRL, 0xcb)
+ FIELD(AST_CR_CURSOR_CTRL, ENABLE, 1, 1)
+ FIELD(AST_CR_CURSOR_CTRL, FORMAT_ARGB4444, 0, 1)
REG8(AST_CR_SOC_SCRATCH0, 0xd0)
FIELD(AST_CR_SOC_SCRATCH0, VRAM_INIT_BY_BMC, 7, 1)
FIELD(AST_CR_SOC_SCRATCH0, VRAM_INIT_READY, 6, 1)
FIELD(AST_CR_SOC_SCRATCH0, IKVM_WIDESCREEN, 0, 1)
+/*
+ * The cursor bitmap is always 64x64 and lives in the framebuffer, two bytes
+ * per pixel in both formats.
+ */
+#define ASPEED_VGA_CURSOR_SIDE 64
+#define ASPEED_VGA_CURSOR_PITCH (ASPEED_VGA_CURSOR_SIDE * 2)
+#define ASPEED_VGA_CURSOR_SIZE (ASPEED_VGA_CURSOR_SIDE * \
+ ASPEED_VGA_CURSOR_PITCH)
+#define CURSOR_MONO_AND BIT(15)
+#define CURSOR_MONO_XOR BIT(14)
+
/*
* 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
@@ -176,10 +206,14 @@ static void aspeed_vga_get_params(VGACommonState *vga,
AspeedVGAState *s = container_of(vga, AspeedVGAState, vga);
if (!aspeed_vga_ext_enabled(s)) {
+ vga->force_shadow = false;
s->std_get_params(vga, params);
return;
}
+ vga->force_shadow = FIELD_EX8(vga->cr[R_AST_CR_CURSOR_CTRL],
+ AST_CR_CURSOR_CTRL, ENABLE);
+
/*
* GR05 bit 6 selects the mode 13 shift mode, which is packed pixel like
* the extended modes. Without it the common code picks its planar
@@ -208,6 +242,164 @@ static void aspeed_vga_get_params(VGACommonState *vga,
params->hpel_split = false;
}
+static bool aspeed_vga_cursor_geometry(AspeedVGAState *s, uint32_t *x,
+ uint32_t *y, uint32_t *xoff,
+ uint32_t *yoff, uint32_t *addr)
+{
+ VGACommonState *vga = &s->vga;
+ bool is_cursor_enable;
+
+ is_cursor_enable = FIELD_EX8(vga->cr[R_AST_CR_CURSOR_CTRL],
+ AST_CR_CURSOR_CTRL, ENABLE);
+
+ if (!aspeed_vga_ext_enabled(s) || !is_cursor_enable) {
+ return false;
+ }
+
+ *x = vga->cr[R_AST_CR_CURSOR_X_LO] |
+ (FIELD_EX8(vga->cr[R_AST_CR_CURSOR_X_HI], AST_CR_CURSOR_X_HI, X) << 8);
+ *y = vga->cr[R_AST_CR_CURSOR_Y_LO] |
+ (FIELD_EX8(vga->cr[R_AST_CR_CURSOR_Y_HI], AST_CR_CURSOR_Y_HI, Y) << 8);
+ *xoff = FIELD_EX8(vga->cr[R_AST_CR_CURSOR_XOFF],
+ AST_CR_CURSOR_XOFF, OFFSET);
+ *yoff = FIELD_EX8(vga->cr[R_AST_CR_CURSOR_YOFF],
+ AST_CR_CURSOR_YOFF, OFFSET);
+ /*
+ * cursor pattern address, D[10:4] in CRC8, D[18:11] in CRC9 and
+ * D[25:19] in CRCA. D[3:0] are not stored, so it is 16 byte aligned.
+ */
+ *addr = ((uint32_t)FIELD_EX8(vga->cr[R_AST_CR_CURSOR_ADDR0],
+ AST_CR_CURSOR_ADDR0, ADDR) << 4) |
+ ((uint32_t)vga->cr[R_AST_CR_CURSOR_ADDR1] << 11) |
+ ((uint32_t)FIELD_EX8(vga->cr[R_AST_CR_CURSOR_ADDR2],
+ AST_CR_CURSOR_ADDR2, ADDR) << 19);
+
+ return *addr + ASPEED_VGA_CURSOR_SIZE <= vga->vram_size;
+}
+
+static void aspeed_vga_cursor_invalidate(VGACommonState *vga)
+{
+ AspeedVGAState *s = container_of(vga, AspeedVGAState, vga);
+ uint32_t addr = 0;
+ uint32_t xoff = 0;
+ uint32_t yoff = 0;
+ uint32_t x = 0;
+ uint32_t y = 0;
+ bool on;
+
+ on = aspeed_vga_cursor_geometry(s, &x, &y, &xoff, &yoff, &addr);
+
+ /*
+ * Moving the cursor dirties no VRAM, so repaint both bands here: where
+ * it was, to erase it, and where it is now, to draw it.
+ */
+ if (s->last_cursor_on) {
+ vga_invalidate_scanlines(vga, s->last_cursor_y,
+ s->last_cursor_y + ASPEED_VGA_CURSOR_SIDE);
+ }
+ if (on) {
+ vga_invalidate_scanlines(vga, y, y + ASPEED_VGA_CURSOR_SIDE);
+ }
+
+ s->last_cursor_on = on;
+ s->last_cursor_y = on ? y : 0;
+}
+
+/* Source-over blend of one 8 bit color channel with a 4 bit alpha */
+static uint32_t aspeed_vga_blend_channel(uint32_t under, uint32_t over,
+ uint32_t alpha)
+{
+ return (under * (15 - alpha) + over * alpha) / 15;
+}
+
+static void aspeed_vga_cursor_draw_line(VGACommonState *vga, uint8_t *d,
+ int scr_y)
+{
+ AspeedVGAState *s = container_of(vga, AspeedVGAState, vga);
+ uint32_t *dst = (uint32_t *)d;
+ const uint8_t *row;
+ uint32_t screen_x;
+ uint32_t under;
+ uint32_t out_r;
+ uint32_t out_g;
+ uint32_t out_b;
+ uint32_t addr;
+ uint32_t xoff;
+ uint32_t yoff;
+ uint32_t cols;
+ uint32_t col;
+ uint16_t px;
+ uint32_t x;
+ uint32_t y;
+ uint32_t a;
+ uint32_t r;
+ uint32_t g;
+ uint32_t b;
+ bool argb;
+
+ /* nothing to draw: the cursor is off or outside VRAM */
+ if (!aspeed_vga_cursor_geometry(s, &x, &y, &xoff, &yoff, &addr)) {
+ return;
+ }
+
+ /* this scan line is above or below the cursor */
+ if (scr_y < y || scr_y >= y + (ASPEED_VGA_CURSOR_SIDE - yoff)) {
+ return;
+ }
+
+ /* the cursor is off the right edge of the screen */
+ if (x >= vga->last_scr_width) {
+ return;
+ }
+
+ /* xoff hides the leftmost columns of the bitmap */
+ cols = ASPEED_VGA_CURSOR_SIDE - xoff;
+
+ /* the screen edge hides the rightmost columns */
+ if (x + cols > vga->last_scr_width) {
+ cols = vga->last_scr_width - x;
+ }
+
+ argb = FIELD_EX8(vga->cr[R_AST_CR_CURSOR_CTRL],
+ AST_CR_CURSOR_CTRL, FORMAT_ARGB4444);
+
+ /* the bitmap row for this scan line, past the rows yoff hides */
+ row = vga->vram_ptr + addr +
+ (uint64_t)(yoff + scr_y - y) * ASPEED_VGA_CURSOR_PITCH;
+
+ for (col = 0; col < cols; col++) {
+ screen_x = x + col;
+ px = lduw_le_p(row + (xoff + col) * 2);
+ r = ((px >> 8) & 0xf) * 0x11;
+ g = ((px >> 4) & 0xf) * 0x11;
+ b = (px & 0xf) * 0x11;
+
+ if (!argb) {
+ /*
+ * AND clear draws the color. AND set leaves the pixel alone,
+ * or inverts it when XOR is set.
+ */
+ if (!(px & CURSOR_MONO_AND)) {
+ dst[screen_x] = rgb_to_pixel32(r, g, b);
+ } else if (px & CURSOR_MONO_XOR) {
+ dst[screen_x] = ~dst[screen_x] & 0xffffff;
+ }
+ continue;
+ }
+
+ a = px >> 12;
+ if (!a) {
+ continue;
+ }
+
+ under = dst[screen_x];
+ out_r = aspeed_vga_blend_channel((under >> 16) & 0xff, r, a);
+ out_g = aspeed_vga_blend_channel((under >> 8) & 0xff, g, a);
+ out_b = aspeed_vga_blend_channel(under & 0xff, b, a);
+ dst[screen_x] = rgb_to_pixel32(out_r, out_g, out_b);
+ }
+}
+
static uint8_t aspeed_vga_vgamem_size_reg(uint32_t vram_size_mb)
{
switch (vram_size_mb) {
@@ -384,6 +576,8 @@ static void aspeed_vga_realize(PCIDevice *dev, Error **errp)
vga->get_params = aspeed_vga_get_params;
vga->get_resolution = aspeed_vga_get_resolution;
vga->is_blanked = aspeed_vga_is_blanked;
+ vga->cursor_invalidate = aspeed_vga_cursor_invalidate;
+ vga->cursor_draw_line = aspeed_vga_cursor_draw_line;
vga->big_endian_fb = false;
vga->con = qemu_graphic_console_create(DEVICE(dev), 0, vga->hw_ops, vga);
--
2.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v2 5/6] hw/display/aspeed-vga: Add the 2D graphics engine
2026-09-10 1:44 [PATCH v2 0/6] Add ASPEED VGA PCI device support Jamin Lin
` (3 preceding siblings ...)
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
2026-09-10 1:44 ` [PATCH v2 6/6] docs/system/arm/aspeed: Document the VGA display controller Jamin Lin
5 siblings, 0 replies; 7+ messages in thread
From: Jamin Lin @ 2026-09-10 1:44 UTC (permalink / raw)
To: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Kane Chen, Andrew Jeffery, Joel Stanley, Pierrick Bouvier,
Paolo Bonzini, Gerd Hoffmann, open list:ASPEED BMCs,
open list:All patches CC here
Cc: Jamin Lin, Troy Lee
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
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v2 6/6] docs/system/arm/aspeed: Document the VGA display controller
2026-09-10 1:44 [PATCH v2 0/6] Add ASPEED VGA PCI device support Jamin Lin
` (4 preceding siblings ...)
2026-09-10 1:44 ` [PATCH v2 5/6] hw/display/aspeed-vga: Add the 2D graphics engine Jamin Lin
@ 2026-09-10 1:44 ` Jamin Lin
5 siblings, 0 replies; 7+ messages in thread
From: Jamin Lin @ 2026-09-10 1:44 UTC (permalink / raw)
To: Cédric Le Goater, Peter Maydell, Steven Lee, Troy Lee,
Kane Chen, Andrew Jeffery, Joel Stanley, Pierrick Bouvier,
Paolo Bonzini, Gerd Hoffmann, open list:ASPEED BMCs,
open list:All patches CC here
Cc: Jamin Lin, Troy Lee
The display controller was listed as missing, which it no longer is. It
does not belong with the rest of the machines' devices though: the host
reaches it as a PCI VGA endpoint, so it is plugged into whichever machine
stands in for the host rather than being part of the BMC machine.
Describe it that way, with the two flavours, the property that sizes the
framebuffer aperture, the option ROM, and how to reach the screen.
Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com>
---
docs/system/arm/aspeed.rst | 37 +++++++++++++++++++++++++++++++++++--
1 file changed, 35 insertions(+), 2 deletions(-)
diff --git a/docs/system/arm/aspeed.rst b/docs/system/arm/aspeed.rst
index 2d51ceeb84..0900b4202d 100644
--- a/docs/system/arm/aspeed.rst
+++ b/docs/system/arm/aspeed.rst
@@ -75,7 +75,6 @@ Missing devices
* Slave GPIO Controller
* Super I/O Controller
* PCI-Express 1 Controller
- * Graphic Display Controller
* MCTP Controller
* Mailbox Controller
* Virtual UART
@@ -319,7 +318,6 @@ Missing devices
* Slave GPIO Controller
* Super I/O Controller
* PCI-Express 1 Controller
- * Graphic Display Controller
* MCTP Controller
* Mailbox Controller
* Virtual UART
@@ -512,3 +510,38 @@ To boot a kernel directly from a Zephyr build tree:
$ qemu-system-arm -M ast1030-evb -nographic \
-kernel zephyr.bin
+
+VGA display controller
+======================
+
+The display controller of the Aspeed SoCs is reached by the host as a PCI
+VGA endpoint, so it is modelled as a device to be plugged into whichever
+machine plays that host rather than as part of the BMC machines above:
+
+- ``ast2600-vga`` Aspeed AST2600 VGA
+- ``ast2700-vga`` Aspeed AST2700 VGA
+
+The size of the framebuffer aperture is set with ``vgamem_mb``, 32 MiB by
+default. The device comes up with the standard VGA BIOS; pass ``romfile``
+to use Aspeed's own option ROM instead, which can be downloaded from
+https://www.aspeedtech.com/support_driver/
+
+To plug one into an ``aarch64`` guest standing in for the host:
+
+.. code-block:: bash
+
+ $ qemu-system-aarch64 \
+ -M virt \
+ -cpu neoverse-n1 -smp 8 -m 4G \
+ -bios edk2-aarch64-code.fd \
+ -device ast2700-vga,vgamem_mb=32,romfile=uefi_arm_2700_vga.rom \
+ -drive file=noble-server-cloudimg-arm64.img,format=qcow2 \
+ -device qemu-xhci,id=xhci \
+ -device usb-kbd,bus=xhci.0 \
+ -device usb-tablet,bus=xhci.0 \
+ -display vnc=0.0.0.0:0
+
+The disk image is an Ubuntu cloud image from
+https://cloud-images.ubuntu.com/. The output goes to the VNC backend on
+display 0, which listens on TCP port 5900, so point a VNC viewer at that
+port to see the screen.
--
2.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-09-10 1:46 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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 ` [PATCH v2 5/6] hw/display/aspeed-vga: Add the 2D graphics engine Jamin Lin
2026-09-10 1:44 ` [PATCH v2 6/6] docs/system/arm/aspeed: Document the VGA display controller Jamin Lin
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox