aboutsummaryrefslogtreecommitdiffstats
path: root/hw/display
diff options
context:
space:
mode:
Diffstat (limited to 'hw/display')
-rw-r--r--hw/display/Kconfig136
-rw-r--r--hw/display/artist.c1500
-rw-r--r--hw/display/ati.c1052
-rw-r--r--hw/display/ati_2d.c206
-rw-r--r--hw/display/ati_dbg.c273
-rw-r--r--hw/display/ati_int.h107
-rw-r--r--hw/display/ati_regs.h481
-rw-r--r--hw/display/bcm2835_fb.c470
-rw-r--r--hw/display/blizzard.c1026
-rw-r--r--hw/display/bochs-display.c390
-rw-r--r--hw/display/cg3.c396
-rw-r--r--hw/display/cirrus_vga.c3024
-rw-r--r--hw/display/cirrus_vga_internal.h103
-rw-r--r--hw/display/cirrus_vga_isa.c99
-rw-r--r--hw/display/cirrus_vga_rop.h246
-rw-r--r--hw/display/cirrus_vga_rop2.h284
-rw-r--r--hw/display/dpcd.c165
-rw-r--r--hw/display/edid-generate.c563
-rw-r--r--hw/display/edid-region.c33
-rw-r--r--hw/display/exynos4210_fimd.c1972
-rw-r--r--hw/display/framebuffer.c122
-rw-r--r--hw/display/framebuffer.h65
-rw-r--r--hw/display/g364fb.c554
-rw-r--r--hw/display/i2c-ddc.c129
-rw-r--r--hw/display/jazz_led.c320
-rw-r--r--hw/display/macfb.c807
-rw-r--r--hw/display/meson.build100
-rw-r--r--hw/display/next-fb.c144
-rw-r--r--hw/display/omap_dss.c1093
-rw-r--r--hw/display/omap_lcdc.c508
-rw-r--r--hw/display/pl110.c609
-rw-r--r--hw/display/pl110_template.h301
-rw-r--r--hw/display/pxa2xx_lcd.c1451
-rw-r--r--hw/display/qxl-logger.c274
-rw-r--r--hw/display/qxl-render.c337
-rw-r--r--hw/display/qxl.c2524
-rw-r--r--hw/display/qxl.h177
-rw-r--r--hw/display/ramfb-standalone.c65
-rw-r--r--hw/display/ramfb.c135
-rw-r--r--hw/display/sii9022.c193
-rw-r--r--hw/display/sm501.c2124
-rw-r--r--hw/display/ssd0303.c336
-rw-r--r--hw/display/ssd0323.c388
-rw-r--r--hw/display/tc6393xb.c568
-rw-r--r--hw/display/tcx.c914
-rw-r--r--hw/display/trace-events176
-rw-r--r--hw/display/trace.h1
-rw-r--r--hw/display/vga-access.h49
-rw-r--r--hw/display/vga-helpers.h431
-rw-r--r--hw/display/vga-isa-mm.c114
-rw-r--r--hw/display/vga-isa.c115
-rw-r--r--hw/display/vga-pci.c420
-rw-r--r--hw/display/vga.c2305
-rw-r--r--hw/display/vga_int.h198
-rw-r--r--hw/display/vga_regs.h159
-rw-r--r--hw/display/vhost-user-gpu-pci.c53
-rw-r--r--hw/display/vhost-user-gpu.c608
-rw-r--r--hw/display/vhost-user-vga.c54
-rw-r--r--hw/display/virtio-gpu-base.c290
-rw-r--r--hw/display/virtio-gpu-gl.c171
-rw-r--r--hw/display/virtio-gpu-pci-gl.c58
-rw-r--r--hw/display/virtio-gpu-pci.c102
-rw-r--r--hw/display/virtio-gpu-udmabuf-stubs.c28
-rw-r--r--hw/display/virtio-gpu-udmabuf.c230
-rw-r--r--hw/display/virtio-gpu-virgl.c634
-rw-r--r--hw/display/virtio-gpu.c1459
-rw-r--r--hw/display/virtio-vga-gl.c50
-rw-r--r--hw/display/virtio-vga.c280
-rw-r--r--hw/display/virtio-vga.h29
-rw-r--r--hw/display/vmware_vga.c1366
-rw-r--r--hw/display/xenfb.c987
-rw-r--r--hw/display/xlnx_dp.c1383
72 files changed, 38514 insertions, 0 deletions
diff --git a/hw/display/Kconfig b/hw/display/Kconfig
new file mode 100644
index 000000000..a2306b67d
--- /dev/null
+++ b/hw/display/Kconfig
@@ -0,0 +1,136 @@
+config DDC
+ bool
+ depends on I2C
+ select EDID
+
+config EDID
+ bool
+
+config FW_CFG_DMA
+ bool
+
+config VGA_CIRRUS
+ bool
+ default y if PCI_DEVICES
+ depends on PCI
+ select VGA
+
+config G364FB
+ bool
+
+config JAZZ_LED
+ bool
+
+config PL110
+ bool
+ select FRAMEBUFFER
+
+config SII9022
+ bool
+ depends on I2C
+ select DDC
+
+config SSD0303
+ bool
+ depends on I2C
+
+config SSD0323
+ bool
+
+config VGA_PCI
+ bool
+ default y if PCI_DEVICES
+ depends on PCI
+ select VGA
+ select EDID
+
+config VGA_ISA
+ bool
+ depends on ISA_BUS
+ select VGA
+
+config VGA_ISA_MM
+ bool
+ select VGA
+
+config VMWARE_VGA
+ bool
+ default y if PCI_DEVICES
+ depends on PCI
+ select VGA
+
+config BOCHS_DISPLAY
+ bool
+ default y if PCI_DEVICES
+ depends on PCI
+ select VGA
+ select EDID
+
+config BLIZZARD
+ bool
+
+config FRAMEBUFFER
+ bool
+
+config SM501
+ bool
+ select I2C
+ select DDC
+ select SERIAL
+
+config TCX
+ bool
+
+config CG3
+ bool
+
+config ARTIST
+ bool
+ select FRAMEBUFFER
+
+config VGA
+ bool
+
+config QXL
+ bool
+ depends on SPICE && PCI
+ select VGA
+
+config VIRTIO_GPU
+ bool
+ default y
+ depends on VIRTIO
+ select EDID
+
+config VIRTIO_VGA
+ bool
+ # defaults to "N", enabled by specific boards
+ depends on VIRTIO_PCI
+ select VGA
+
+config VHOST_USER_GPU
+ bool
+ default y
+ depends on VIRTIO_GPU && VHOST_USER
+
+config VHOST_USER_VGA
+ bool
+ default y
+ depends on VIRTIO_VGA && VHOST_USER_GPU
+
+config DPCD
+ bool
+ select AUX
+
+config ATI_VGA
+ bool
+ default y if PCI_DEVICES
+ depends on PCI
+ select VGA
+ select BITBANG_I2C
+ select DDC
+
+config MACFB
+ bool
+ select FRAMEBUFFER
+ depends on NUBUS
diff --git a/hw/display/artist.c b/hw/display/artist.c
new file mode 100644
index 000000000..21b7fd1b4
--- /dev/null
+++ b/hw/display/artist.c
@@ -0,0 +1,1500 @@
+/*
+ * QEMU HP Artist Emulation
+ *
+ * Copyright (c) 2019 Sven Schnelle <svens@stackframe.org>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu-common.h"
+#include "qemu/error-report.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/units.h"
+#include "qapi/error.h"
+#include "hw/sysbus.h"
+#include "hw/loader.h"
+#include "hw/qdev-core.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "ui/console.h"
+#include "trace.h"
+#include "framebuffer.h"
+#include "qom/object.h"
+
+#define TYPE_ARTIST "artist"
+OBJECT_DECLARE_SIMPLE_TYPE(ARTISTState, ARTIST)
+
+#ifdef HOST_WORDS_BIGENDIAN
+#define ROP8OFF(_i) (3 - (_i))
+#else
+#define ROP8OFF
+#endif
+
+struct vram_buffer {
+ MemoryRegion mr;
+ uint8_t *data;
+ unsigned int size;
+ unsigned int width;
+ unsigned int height;
+};
+
+struct ARTISTState {
+ SysBusDevice parent_obj;
+
+ QemuConsole *con;
+ MemoryRegion vram_mem;
+ MemoryRegion mem_as_root;
+ MemoryRegion reg;
+ MemoryRegionSection fbsection;
+
+ void *vram_int_mr;
+ AddressSpace as;
+
+ struct vram_buffer vram_buffer[16];
+
+ uint16_t width;
+ uint16_t height;
+ uint16_t depth;
+
+ uint32_t fg_color;
+ uint32_t bg_color;
+
+ uint32_t vram_char_y;
+ uint32_t vram_bitmask;
+
+ uint32_t vram_start;
+ uint32_t vram_pos;
+
+ uint32_t vram_size;
+
+ uint32_t blockmove_source;
+ uint32_t blockmove_dest;
+ uint32_t blockmove_size;
+
+ uint32_t line_size;
+ uint32_t line_end;
+ uint32_t line_xy;
+ uint32_t line_pattern_start;
+ uint32_t line_pattern_skip;
+
+ uint32_t cursor_pos;
+
+ uint32_t cursor_height;
+ uint32_t cursor_width;
+
+ uint32_t plane_mask;
+
+ uint32_t reg_100080;
+ uint32_t reg_300200;
+ uint32_t reg_300208;
+ uint32_t reg_300218;
+
+ uint32_t cmap_bm_access;
+ uint32_t dst_bm_access;
+ uint32_t src_bm_access;
+ uint32_t control_plane;
+ uint32_t transfer_data;
+ uint32_t image_bitmap_op;
+
+ uint32_t font_write1;
+ uint32_t font_write2;
+ uint32_t font_write_pos_y;
+
+ int draw_line_pattern;
+};
+
+typedef enum {
+ ARTIST_BUFFER_AP = 1,
+ ARTIST_BUFFER_OVERLAY = 2,
+ ARTIST_BUFFER_CURSOR1 = 6,
+ ARTIST_BUFFER_CURSOR2 = 7,
+ ARTIST_BUFFER_ATTRIBUTE = 13,
+ ARTIST_BUFFER_CMAP = 15,
+} artist_buffer_t;
+
+typedef enum {
+ VRAM_IDX = 0x1004a0,
+ VRAM_BITMASK = 0x1005a0,
+ VRAM_WRITE_INCR_X = 0x100600,
+ VRAM_WRITE_INCR_X2 = 0x100604,
+ VRAM_WRITE_INCR_Y = 0x100620,
+ VRAM_START = 0x100800,
+ BLOCK_MOVE_SIZE = 0x100804,
+ BLOCK_MOVE_SOURCE = 0x100808,
+ TRANSFER_DATA = 0x100820,
+ FONT_WRITE_INCR_Y = 0x1008a0,
+ VRAM_START_TRIGGER = 0x100a00,
+ VRAM_SIZE_TRIGGER = 0x100a04,
+ FONT_WRITE_START = 0x100aa0,
+ BLOCK_MOVE_DEST_TRIGGER = 0x100b00,
+ BLOCK_MOVE_SIZE_TRIGGER = 0x100b04,
+ LINE_XY = 0x100ccc,
+ PATTERN_LINE_START = 0x100ecc,
+ LINE_SIZE = 0x100e04,
+ LINE_END = 0x100e44,
+ CMAP_BM_ACCESS = 0x118000,
+ DST_BM_ACCESS = 0x118004,
+ SRC_BM_ACCESS = 0x118008,
+ CONTROL_PLANE = 0x11800c,
+ FG_COLOR = 0x118010,
+ BG_COLOR = 0x118014,
+ PLANE_MASK = 0x118018,
+ IMAGE_BITMAP_OP = 0x11801c,
+ CURSOR_POS = 0x300100,
+ CURSOR_CTRL = 0x300104,
+} artist_reg_t;
+
+typedef enum {
+ ARTIST_ROP_CLEAR = 0,
+ ARTIST_ROP_COPY = 3,
+ ARTIST_ROP_XOR = 6,
+ ARTIST_ROP_NOT_DST = 10,
+ ARTIST_ROP_SET = 15,
+} artist_rop_t;
+
+#define REG_NAME(_x) case _x: return " "#_x;
+static const char *artist_reg_name(uint64_t addr)
+{
+ switch ((artist_reg_t)addr) {
+ REG_NAME(VRAM_IDX);
+ REG_NAME(VRAM_BITMASK);
+ REG_NAME(VRAM_WRITE_INCR_X);
+ REG_NAME(VRAM_WRITE_INCR_X2);
+ REG_NAME(VRAM_WRITE_INCR_Y);
+ REG_NAME(VRAM_START);
+ REG_NAME(BLOCK_MOVE_SIZE);
+ REG_NAME(BLOCK_MOVE_SOURCE);
+ REG_NAME(FG_COLOR);
+ REG_NAME(BG_COLOR);
+ REG_NAME(PLANE_MASK);
+ REG_NAME(VRAM_START_TRIGGER);
+ REG_NAME(VRAM_SIZE_TRIGGER);
+ REG_NAME(BLOCK_MOVE_DEST_TRIGGER);
+ REG_NAME(BLOCK_MOVE_SIZE_TRIGGER);
+ REG_NAME(TRANSFER_DATA);
+ REG_NAME(CONTROL_PLANE);
+ REG_NAME(IMAGE_BITMAP_OP);
+ REG_NAME(CMAP_BM_ACCESS);
+ REG_NAME(DST_BM_ACCESS);
+ REG_NAME(SRC_BM_ACCESS);
+ REG_NAME(CURSOR_POS);
+ REG_NAME(CURSOR_CTRL);
+ REG_NAME(LINE_XY);
+ REG_NAME(PATTERN_LINE_START);
+ REG_NAME(LINE_SIZE);
+ REG_NAME(LINE_END);
+ REG_NAME(FONT_WRITE_INCR_Y);
+ REG_NAME(FONT_WRITE_START);
+ }
+ return "";
+}
+#undef REG_NAME
+
+/* artist has a fixed line length of 2048 bytes. */
+#define ADDR_TO_Y(addr) extract32(addr, 11, 11)
+#define ADDR_TO_X(addr) extract32(addr, 0, 11)
+
+static int16_t artist_get_x(uint32_t reg)
+{
+ return reg >> 16;
+}
+
+static int16_t artist_get_y(uint32_t reg)
+{
+ return reg & 0xffff;
+}
+
+static void artist_invalidate_lines(struct vram_buffer *buf,
+ int starty, int height)
+{
+ int start = starty * buf->width;
+ int size;
+
+ if (starty + height > buf->height)
+ height = buf->height - starty;
+
+ size = height * buf->width;
+
+ if (start + size <= buf->size) {
+ memory_region_set_dirty(&buf->mr, start, size);
+ }
+}
+
+static int vram_write_pix_per_transfer(ARTISTState *s)
+{
+ if (s->cmap_bm_access) {
+ return 1 << ((s->cmap_bm_access >> 27) & 0x0f);
+ } else {
+ return 1 << ((s->dst_bm_access >> 27) & 0x0f);
+ }
+}
+
+static int vram_pixel_length(ARTISTState *s)
+{
+ if (s->cmap_bm_access) {
+ return (s->cmap_bm_access >> 24) & 0x07;
+ } else {
+ return (s->dst_bm_access >> 24) & 0x07;
+ }
+}
+
+static int vram_write_bufidx(ARTISTState *s)
+{
+ if (s->cmap_bm_access) {
+ return (s->cmap_bm_access >> 12) & 0x0f;
+ } else {
+ return (s->dst_bm_access >> 12) & 0x0f;
+ }
+}
+
+static int vram_read_bufidx(ARTISTState *s)
+{
+ if (s->cmap_bm_access) {
+ return (s->cmap_bm_access >> 12) & 0x0f;
+ } else {
+ return (s->src_bm_access >> 12) & 0x0f;
+ }
+}
+
+static struct vram_buffer *vram_read_buffer(ARTISTState *s)
+{
+ return &s->vram_buffer[vram_read_bufidx(s)];
+}
+
+static struct vram_buffer *vram_write_buffer(ARTISTState *s)
+{
+ return &s->vram_buffer[vram_write_bufidx(s)];
+}
+
+static uint8_t artist_get_color(ARTISTState *s)
+{
+ if (s->image_bitmap_op & 2) {
+ return s->fg_color;
+ } else {
+ return s->bg_color;
+ }
+}
+
+static artist_rop_t artist_get_op(ARTISTState *s)
+{
+ return (s->image_bitmap_op >> 8) & 0xf;
+}
+
+static void artist_rop8(ARTISTState *s, struct vram_buffer *buf,
+ unsigned int offset, uint8_t val)
+{
+ const artist_rop_t op = artist_get_op(s);
+ uint8_t plane_mask;
+ uint8_t *dst;
+
+ if (offset >= buf->size) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "rop8 offset:%u bufsize:%u\n", offset, buf->size);
+ return;
+ }
+ dst = buf->data + offset;
+ plane_mask = s->plane_mask & 0xff;
+
+ switch (op) {
+ case ARTIST_ROP_CLEAR:
+ *dst &= ~plane_mask;
+ break;
+
+ case ARTIST_ROP_COPY:
+ *dst = (*dst & ~plane_mask) | (val & plane_mask);
+ break;
+
+ case ARTIST_ROP_XOR:
+ *dst ^= val & plane_mask;
+ break;
+
+ case ARTIST_ROP_NOT_DST:
+ *dst ^= plane_mask;
+ break;
+
+ case ARTIST_ROP_SET:
+ *dst |= plane_mask;
+ break;
+
+ default:
+ qemu_log_mask(LOG_UNIMP, "%s: unsupported rop %d\n", __func__, op);
+ break;
+ }
+}
+
+static void artist_get_cursor_pos(ARTISTState *s, int *x, int *y)
+{
+ /*
+ * Don't know whether these magic offset values are configurable via
+ * some register. They are the same for all resolutions, so don't
+ * bother about it.
+ */
+
+ *y = 0x47a - artist_get_y(s->cursor_pos);
+ *x = ((artist_get_x(s->cursor_pos) - 338) / 2);
+
+ if (*x > s->width) {
+ *x = 0;
+ }
+
+ if (*y > s->height) {
+ *y = 0;
+ }
+}
+
+static void artist_invalidate_cursor(ARTISTState *s)
+{
+ int x, y;
+ artist_get_cursor_pos(s, &x, &y);
+ artist_invalidate_lines(&s->vram_buffer[ARTIST_BUFFER_AP],
+ y, s->cursor_height);
+}
+
+static void vram_bit_write(ARTISTState *s, int posy, bool incr_x,
+ int size, uint32_t data)
+{
+ struct vram_buffer *buf;
+ uint32_t vram_bitmask = s->vram_bitmask;
+ int mask, i, pix_count, pix_length;
+ unsigned int posx, offset, width;
+ uint8_t *data8, *p;
+
+ pix_count = vram_write_pix_per_transfer(s);
+ pix_length = vram_pixel_length(s);
+
+ buf = vram_write_buffer(s);
+ width = buf->width;
+
+ if (s->cmap_bm_access) {
+ offset = s->vram_pos;
+ } else {
+ posx = ADDR_TO_X(s->vram_pos >> 2);
+ posy += ADDR_TO_Y(s->vram_pos >> 2);
+ offset = posy * width + posx;
+ }
+
+ if (!buf->size || offset >= buf->size) {
+ return;
+ }
+
+ p = buf->data;
+
+ if (pix_count > size * 8) {
+ pix_count = size * 8;
+ }
+
+ switch (pix_length) {
+ case 0:
+ if (s->image_bitmap_op & 0x20000000) {
+ data &= vram_bitmask;
+ }
+
+ for (i = 0; i < pix_count; i++) {
+ uint32_t off = offset + pix_count - 1 - i;
+ if (off < buf->size) {
+ artist_rop8(s, buf, off,
+ (data & 1) ? (s->plane_mask >> 24) : 0);
+ }
+ data >>= 1;
+ }
+ memory_region_set_dirty(&buf->mr, offset, pix_count);
+ break;
+
+ case 3:
+ if (s->cmap_bm_access) {
+ if (offset + 3 < buf->size) {
+ *(uint32_t *)(p + offset) = data;
+ }
+ break;
+ }
+ data8 = (uint8_t *)&data;
+
+ for (i = 3; i >= 0; i--) {
+ if (!(s->image_bitmap_op & 0x20000000) ||
+ s->vram_bitmask & (1 << (28 + i))) {
+ uint32_t off = offset + 3 - i;
+ if (off < buf->size) {
+ artist_rop8(s, buf, off, data8[ROP8OFF(i)]);
+ }
+ }
+ }
+ memory_region_set_dirty(&buf->mr, offset, 3);
+ break;
+
+ case 6:
+ switch (size) {
+ default:
+ case 4:
+ vram_bitmask = s->vram_bitmask;
+ break;
+
+ case 2:
+ vram_bitmask = s->vram_bitmask >> 16;
+ break;
+
+ case 1:
+ vram_bitmask = s->vram_bitmask >> 24;
+ break;
+ }
+
+ for (i = 0; i < pix_count && offset + i < buf->size; i++) {
+ mask = 1 << (pix_count - 1 - i);
+
+ if (!(s->image_bitmap_op & 0x20000000) ||
+ (vram_bitmask & mask)) {
+ if (data & mask) {
+ artist_rop8(s, buf, offset + i, s->fg_color);
+ } else {
+ if (!(s->image_bitmap_op & 0x10000002)) {
+ artist_rop8(s, buf, offset + i, s->bg_color);
+ }
+ }
+ }
+ }
+ memory_region_set_dirty(&buf->mr, offset, pix_count);
+ break;
+
+ default:
+ qemu_log_mask(LOG_UNIMP, "%s: unknown pixel length %d\n",
+ __func__, pix_length);
+ break;
+ }
+
+ if (incr_x) {
+ if (s->cmap_bm_access) {
+ s->vram_pos += 4;
+ } else {
+ s->vram_pos += pix_count << 2;
+ }
+ }
+
+ if (vram_write_bufidx(s) == ARTIST_BUFFER_CURSOR1 ||
+ vram_write_bufidx(s) == ARTIST_BUFFER_CURSOR2) {
+ artist_invalidate_cursor(s);
+ }
+}
+
+static void block_move(ARTISTState *s,
+ unsigned int source_x, unsigned int source_y,
+ unsigned int dest_x, unsigned int dest_y,
+ unsigned int width, unsigned int height)
+{
+ struct vram_buffer *buf;
+ int line, endline, lineincr, startcolumn, endcolumn, columnincr, column;
+ unsigned int dst, src;
+
+ trace_artist_block_move(source_x, source_y, dest_x, dest_y, width, height);
+
+ if (s->control_plane != 0) {
+ /* We don't support CONTROL_PLANE accesses */
+ qemu_log_mask(LOG_UNIMP, "%s: CONTROL_PLANE: %08x\n", __func__,
+ s->control_plane);
+ return;
+ }
+
+ buf = &s->vram_buffer[ARTIST_BUFFER_AP];
+ if (height > buf->height) {
+ height = buf->height;
+ }
+ if (width > buf->width) {
+ width = buf->width;
+ }
+
+ if (dest_y > source_y) {
+ /* move down */
+ line = height - 1;
+ endline = -1;
+ lineincr = -1;
+ } else {
+ /* move up */
+ line = 0;
+ endline = height;
+ lineincr = 1;
+ }
+
+ if (dest_x > source_x) {
+ /* move right */
+ startcolumn = width - 1;
+ endcolumn = -1;
+ columnincr = -1;
+ } else {
+ /* move left */
+ startcolumn = 0;
+ endcolumn = width;
+ columnincr = 1;
+ }
+
+ for ( ; line != endline; line += lineincr) {
+ src = source_x + ((line + source_y) * buf->width) + startcolumn;
+ dst = dest_x + ((line + dest_y) * buf->width) + startcolumn;
+
+ for (column = startcolumn; column != endcolumn; column += columnincr) {
+ if (dst >= buf->size || src >= buf->size) {
+ continue;
+ }
+ artist_rop8(s, buf, dst, buf->data[src]);
+ src += columnincr;
+ dst += columnincr;
+ }
+ }
+
+ artist_invalidate_lines(buf, dest_y, height);
+}
+
+static void fill_window(ARTISTState *s,
+ unsigned int startx, unsigned int starty,
+ unsigned int width, unsigned int height)
+{
+ unsigned int offset;
+ uint8_t color = artist_get_color(s);
+ struct vram_buffer *buf;
+ int x, y;
+
+ trace_artist_fill_window(startx, starty, width, height,
+ s->image_bitmap_op, s->control_plane);
+
+ if (s->control_plane != 0) {
+ /* We don't support CONTROL_PLANE accesses */
+ qemu_log_mask(LOG_UNIMP, "%s: CONTROL_PLANE: %08x\n", __func__,
+ s->control_plane);
+ return;
+ }
+
+ if (s->reg_100080 == 0x7d) {
+ /*
+ * Not sure what this register really does, but
+ * 0x7d seems to enable autoincremt of the Y axis
+ * by the current block move height.
+ */
+ height = artist_get_y(s->blockmove_size);
+ s->vram_start += height;
+ }
+
+ buf = &s->vram_buffer[ARTIST_BUFFER_AP];
+
+ for (y = starty; y < starty + height; y++) {
+ offset = y * s->width;
+
+ for (x = startx; x < startx + width; x++) {
+ artist_rop8(s, buf, offset + x, color);
+ }
+ }
+ artist_invalidate_lines(buf, starty, height);
+}
+
+static void draw_line(ARTISTState *s,
+ unsigned int x1, unsigned int y1,
+ unsigned int x2, unsigned int y2,
+ bool update_start, int skip_pix, int max_pix)
+{
+ struct vram_buffer *buf = &s->vram_buffer[ARTIST_BUFFER_AP];
+ uint8_t color;
+ int dx, dy, t, e, x, y, incy, diago, horiz;
+ bool c1;
+
+ trace_artist_draw_line(x1, y1, x2, y2);
+
+ if ((x1 >= buf->width && x2 >= buf->width) ||
+ (y1 >= buf->height && y2 >= buf->height)) {
+ return;
+ }
+
+
+ if (update_start) {
+ s->vram_start = (x2 << 16) | y2;
+ }
+
+ if (x2 > x1) {
+ dx = x2 - x1;
+ } else {
+ dx = x1 - x2;
+ }
+ if (y2 > y1) {
+ dy = y2 - y1;
+ } else {
+ dy = y1 - y2;
+ }
+
+ c1 = false;
+ if (dy > dx) {
+ t = y2;
+ y2 = x2;
+ x2 = t;
+
+ t = y1;
+ y1 = x1;
+ x1 = t;
+
+ t = dx;
+ dx = dy;
+ dy = t;
+
+ c1 = true;
+ }
+
+ if (x1 > x2) {
+ t = y2;
+ y2 = y1;
+ y1 = t;
+
+ t = x1;
+ x1 = x2;
+ x2 = t;
+ }
+
+ horiz = dy << 1;
+ diago = (dy - dx) << 1;
+ e = (dy << 1) - dx;
+
+ if (y1 <= y2) {
+ incy = 1;
+ } else {
+ incy = -1;
+ }
+ x = x1;
+ y = y1;
+ color = artist_get_color(s);
+
+ do {
+ unsigned int ofs;
+
+ if (c1) {
+ ofs = x * s->width + y;
+ } else {
+ ofs = y * s->width + x;
+ }
+
+ if (skip_pix > 0) {
+ skip_pix--;
+ } else {
+ artist_rop8(s, buf, ofs, color);
+ }
+
+ if (e > 0) {
+ y += incy;
+ e += diago;
+ } else {
+ e += horiz;
+ }
+ x++;
+ } while (x <= x2 && (max_pix == -1 || --max_pix > 0));
+ if (c1)
+ artist_invalidate_lines(buf, x, dy+1);
+ else
+ artist_invalidate_lines(buf, y, dx+1);
+}
+
+static void draw_line_pattern_start(ARTISTState *s)
+{
+
+ int startx = artist_get_x(s->vram_start);
+ int starty = artist_get_y(s->vram_start);
+ int endx = artist_get_x(s->blockmove_size);
+ int endy = artist_get_y(s->blockmove_size);
+ int pstart = s->line_pattern_start >> 16;
+
+ draw_line(s, startx, starty, endx, endy, false, -1, pstart);
+ s->line_pattern_skip = pstart;
+}
+
+static void draw_line_pattern_next(ARTISTState *s)
+{
+
+ int startx = artist_get_x(s->vram_start);
+ int starty = artist_get_y(s->vram_start);
+ int endx = artist_get_x(s->blockmove_size);
+ int endy = artist_get_y(s->blockmove_size);
+ int line_xy = s->line_xy >> 16;
+
+ draw_line(s, startx, starty, endx, endy, false, s->line_pattern_skip,
+ s->line_pattern_skip + line_xy);
+ s->line_pattern_skip += line_xy;
+ s->image_bitmap_op ^= 2;
+}
+
+static void draw_line_size(ARTISTState *s, bool update_start)
+{
+
+ int startx = artist_get_x(s->vram_start);
+ int starty = artist_get_y(s->vram_start);
+ int endx = artist_get_x(s->line_size);
+ int endy = artist_get_y(s->line_size);
+
+ draw_line(s, startx, starty, endx, endy, update_start, -1, -1);
+}
+
+static void draw_line_xy(ARTISTState *s, bool update_start)
+{
+
+ int startx = artist_get_x(s->vram_start);
+ int starty = artist_get_y(s->vram_start);
+ int sizex = artist_get_x(s->blockmove_size);
+ int sizey = artist_get_y(s->blockmove_size);
+ int linexy = s->line_xy >> 16;
+ int endx, endy;
+
+ endx = startx;
+ endy = starty;
+
+ if (sizex > 0) {
+ endx = startx + linexy;
+ }
+
+ if (sizex < 0) {
+ endx = startx;
+ startx -= linexy;
+ }
+
+ if (sizey > 0) {
+ endy = starty + linexy;
+ }
+
+ if (sizey < 0) {
+ endy = starty;
+ starty -= linexy;
+ }
+
+ if (startx < 0) {
+ startx = 0;
+ }
+
+ if (endx < 0) {
+ endx = 0;
+ }
+
+ if (starty < 0) {
+ starty = 0;
+ }
+
+ if (endy < 0) {
+ endy = 0;
+ }
+
+ draw_line(s, startx, starty, endx, endy, false, -1, -1);
+}
+
+static void draw_line_end(ARTISTState *s, bool update_start)
+{
+
+ int startx = artist_get_x(s->vram_start);
+ int starty = artist_get_y(s->vram_start);
+ int endx = artist_get_x(s->line_end);
+ int endy = artist_get_y(s->line_end);
+
+ draw_line(s, startx, starty, endx, endy, update_start, -1, -1);
+}
+
+static void font_write16(ARTISTState *s, uint16_t val)
+{
+ struct vram_buffer *buf;
+ uint32_t color = (s->image_bitmap_op & 2) ? s->fg_color : s->bg_color;
+ uint16_t mask;
+ int i;
+
+ unsigned int startx = artist_get_x(s->vram_start);
+ unsigned int starty = artist_get_y(s->vram_start) + s->font_write_pos_y;
+ unsigned int offset = starty * s->width + startx;
+
+ buf = &s->vram_buffer[ARTIST_BUFFER_AP];
+
+ if (startx >= buf->width || starty >= buf->height ||
+ offset + 16 >= buf->size) {
+ return;
+ }
+
+ for (i = 0; i < 16; i++) {
+ mask = 1 << (15 - i);
+ if (val & mask) {
+ artist_rop8(s, buf, offset + i, color);
+ } else {
+ if (!(s->image_bitmap_op & 0x20000000)) {
+ artist_rop8(s, buf, offset + i, s->bg_color);
+ }
+ }
+ }
+ artist_invalidate_lines(buf, starty, 1);
+}
+
+static void font_write(ARTISTState *s, uint32_t val)
+{
+ font_write16(s, val >> 16);
+ if (++s->font_write_pos_y == artist_get_y(s->blockmove_size)) {
+ s->vram_start += (s->blockmove_size & 0xffff0000);
+ return;
+ }
+
+ font_write16(s, val & 0xffff);
+ if (++s->font_write_pos_y == artist_get_y(s->blockmove_size)) {
+ s->vram_start += (s->blockmove_size & 0xffff0000);
+ return;
+ }
+}
+
+static void combine_write_reg(hwaddr addr, uint64_t val, int size, void *out)
+{
+ /*
+ * FIXME: is there a qemu helper for this?
+ */
+
+#ifndef HOST_WORDS_BIGENDIAN
+ addr ^= 3;
+#endif
+
+ switch (size) {
+ case 1:
+ *(uint8_t *)(out + (addr & 3)) = val;
+ break;
+
+ case 2:
+ *(uint16_t *)(out + (addr & 2)) = val;
+ break;
+
+ case 4:
+ *(uint32_t *)out = val;
+ break;
+
+ default:
+ qemu_log_mask(LOG_UNIMP, "unsupported write size: %d\n", size);
+ }
+}
+
+static void artist_reg_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ ARTISTState *s = opaque;
+ int width, height;
+
+ trace_artist_reg_write(size, addr, artist_reg_name(addr & ~3ULL), val);
+
+ switch (addr & ~3ULL) {
+ case 0x100080:
+ combine_write_reg(addr, val, size, &s->reg_100080);
+ break;
+
+ case FG_COLOR:
+ combine_write_reg(addr, val, size, &s->fg_color);
+ break;
+
+ case BG_COLOR:
+ combine_write_reg(addr, val, size, &s->bg_color);
+ break;
+
+ case VRAM_BITMASK:
+ combine_write_reg(addr, val, size, &s->vram_bitmask);
+ break;
+
+ case VRAM_WRITE_INCR_Y:
+ vram_bit_write(s, s->vram_char_y++, false, size, val);
+ break;
+
+ case VRAM_WRITE_INCR_X:
+ case VRAM_WRITE_INCR_X2:
+ vram_bit_write(s, s->vram_char_y, true, size, val);
+ break;
+
+ case VRAM_IDX:
+ combine_write_reg(addr, val, size, &s->vram_pos);
+ s->vram_char_y = 0;
+ s->draw_line_pattern = 0;
+ break;
+
+ case VRAM_START:
+ combine_write_reg(addr, val, size, &s->vram_start);
+ s->draw_line_pattern = 0;
+ break;
+
+ case VRAM_START_TRIGGER:
+ combine_write_reg(addr, val, size, &s->vram_start);
+ fill_window(s, artist_get_x(s->vram_start),
+ artist_get_y(s->vram_start),
+ artist_get_x(s->blockmove_size),
+ artist_get_y(s->blockmove_size));
+ break;
+
+ case VRAM_SIZE_TRIGGER:
+ combine_write_reg(addr, val, size, &s->vram_size);
+
+ if (size == 2 && !(addr & 2)) {
+ height = artist_get_y(s->blockmove_size);
+ } else {
+ height = artist_get_y(s->vram_size);
+ }
+
+ if (size == 2 && (addr & 2)) {
+ width = artist_get_x(s->blockmove_size);
+ } else {
+ width = artist_get_x(s->vram_size);
+ }
+
+ fill_window(s, artist_get_x(s->vram_start),
+ artist_get_y(s->vram_start),
+ width, height);
+ break;
+
+ case LINE_XY:
+ combine_write_reg(addr, val, size, &s->line_xy);
+ if (s->draw_line_pattern) {
+ draw_line_pattern_next(s);
+ } else {
+ draw_line_xy(s, true);
+ }
+ break;
+
+ case PATTERN_LINE_START:
+ combine_write_reg(addr, val, size, &s->line_pattern_start);
+ s->draw_line_pattern = 1;
+ draw_line_pattern_start(s);
+ break;
+
+ case LINE_SIZE:
+ combine_write_reg(addr, val, size, &s->line_size);
+ draw_line_size(s, true);
+ break;
+
+ case LINE_END:
+ combine_write_reg(addr, val, size, &s->line_end);
+ draw_line_end(s, true);
+ break;
+
+ case BLOCK_MOVE_SIZE:
+ combine_write_reg(addr, val, size, &s->blockmove_size);
+ break;
+
+ case BLOCK_MOVE_SOURCE:
+ combine_write_reg(addr, val, size, &s->blockmove_source);
+ break;
+
+ case BLOCK_MOVE_DEST_TRIGGER:
+ combine_write_reg(addr, val, size, &s->blockmove_dest);
+
+ block_move(s, artist_get_x(s->blockmove_source),
+ artist_get_y(s->blockmove_source),
+ artist_get_x(s->blockmove_dest),
+ artist_get_y(s->blockmove_dest),
+ artist_get_x(s->blockmove_size),
+ artist_get_y(s->blockmove_size));
+ break;
+
+ case BLOCK_MOVE_SIZE_TRIGGER:
+ combine_write_reg(addr, val, size, &s->blockmove_size);
+
+ block_move(s,
+ artist_get_x(s->blockmove_source),
+ artist_get_y(s->blockmove_source),
+ artist_get_x(s->vram_start),
+ artist_get_y(s->vram_start),
+ artist_get_x(s->blockmove_size),
+ artist_get_y(s->blockmove_size));
+ break;
+
+ case PLANE_MASK:
+ combine_write_reg(addr, val, size, &s->plane_mask);
+ break;
+
+ case CMAP_BM_ACCESS:
+ combine_write_reg(addr, val, size, &s->cmap_bm_access);
+ break;
+
+ case DST_BM_ACCESS:
+ combine_write_reg(addr, val, size, &s->dst_bm_access);
+ s->cmap_bm_access = 0;
+ break;
+
+ case SRC_BM_ACCESS:
+ combine_write_reg(addr, val, size, &s->src_bm_access);
+ s->cmap_bm_access = 0;
+ break;
+
+ case CONTROL_PLANE:
+ combine_write_reg(addr, val, size, &s->control_plane);
+ break;
+
+ case TRANSFER_DATA:
+ combine_write_reg(addr, val, size, &s->transfer_data);
+ break;
+
+ case 0x300200:
+ combine_write_reg(addr, val, size, &s->reg_300200);
+ break;
+
+ case 0x300208:
+ combine_write_reg(addr, val, size, &s->reg_300208);
+ break;
+
+ case 0x300218:
+ combine_write_reg(addr, val, size, &s->reg_300218);
+ break;
+
+ case CURSOR_POS:
+ artist_invalidate_cursor(s);
+ combine_write_reg(addr, val, size, &s->cursor_pos);
+ artist_invalidate_cursor(s);
+ break;
+
+ case CURSOR_CTRL:
+ break;
+
+ case IMAGE_BITMAP_OP:
+ combine_write_reg(addr, val, size, &s->image_bitmap_op);
+ break;
+
+ case FONT_WRITE_INCR_Y:
+ combine_write_reg(addr, val, size, &s->font_write1);
+ font_write(s, s->font_write1);
+ break;
+
+ case FONT_WRITE_START:
+ combine_write_reg(addr, val, size, &s->font_write2);
+ s->font_write_pos_y = 0;
+ font_write(s, s->font_write2);
+ break;
+
+ case 300104:
+ break;
+
+ default:
+ qemu_log_mask(LOG_UNIMP, "%s: unknown register: reg=%08" HWADDR_PRIx
+ " val=%08" PRIx64 " size=%d\n",
+ __func__, addr, val, size);
+ break;
+ }
+}
+
+static uint64_t combine_read_reg(hwaddr addr, int size, void *in)
+{
+ /*
+ * FIXME: is there a qemu helper for this?
+ */
+
+#ifndef HOST_WORDS_BIGENDIAN
+ addr ^= 3;
+#endif
+
+ switch (size) {
+ case 1:
+ return *(uint8_t *)(in + (addr & 3));
+
+ case 2:
+ return *(uint16_t *)(in + (addr & 2));
+
+ case 4:
+ return *(uint32_t *)in;
+
+ default:
+ qemu_log_mask(LOG_UNIMP, "unsupported read size: %d\n", size);
+ return 0;
+ }
+}
+
+static uint64_t artist_reg_read(void *opaque, hwaddr addr, unsigned size)
+{
+ ARTISTState *s = opaque;
+ uint32_t val = 0;
+
+ switch (addr & ~3ULL) {
+ /* Unknown status registers */
+ case 0:
+ break;
+
+ case 0x211110:
+ val = (s->width << 16) | s->height;
+ if (s->depth == 1) {
+ val |= 1 << 31;
+ }
+ break;
+
+ case 0x100000:
+ case 0x300000:
+ case 0x300004:
+ case 0x300308:
+ case 0x380000:
+ break;
+
+ case 0x300008:
+ case 0x380008:
+ /*
+ * FIFO ready flag. we're not emulating the FIFOs
+ * so we're always ready
+ */
+ val = 0x10;
+ break;
+
+ case 0x300200:
+ val = s->reg_300200;
+ break;
+
+ case 0x300208:
+ val = s->reg_300208;
+ break;
+
+ case 0x300218:
+ val = s->reg_300218;
+ break;
+
+ case 0x30023c:
+ val = 0xac4ffdac;
+ break;
+
+ case 0x380004:
+ /* 0x02000000 Buserror */
+ val = 0x6dc20006;
+ break;
+
+ default:
+ qemu_log_mask(LOG_UNIMP, "%s: unknown register: %08" HWADDR_PRIx
+ " size %d\n", __func__, addr, size);
+ break;
+ }
+ val = combine_read_reg(addr, size, &val);
+ trace_artist_reg_read(size, addr, artist_reg_name(addr & ~3ULL), val);
+ return val;
+}
+
+static void artist_vram_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ ARTISTState *s = opaque;
+ struct vram_buffer *buf;
+ unsigned int posy, posx;
+ unsigned int offset;
+ trace_artist_vram_write(size, addr, val);
+
+ if (s->cmap_bm_access) {
+ buf = &s->vram_buffer[ARTIST_BUFFER_CMAP];
+ if (addr + 3 < buf->size) {
+ *(uint32_t *)(buf->data + addr) = val;
+ }
+ return;
+ }
+
+ buf = vram_write_buffer(s);
+ posy = ADDR_TO_Y(addr >> 2);
+ posx = ADDR_TO_X(addr >> 2);
+
+ if (!buf->size) {
+ return;
+ }
+
+ if (posy > buf->height || posx > buf->width) {
+ return;
+ }
+
+ offset = posy * buf->width + posx;
+ if (offset >= buf->size) {
+ return;
+ }
+
+ switch (size) {
+ case 4:
+ if (offset + 3 < buf->size) {
+ *(uint32_t *)(buf->data + offset) = be32_to_cpu(val);
+ memory_region_set_dirty(&buf->mr, offset, 4);
+ }
+ break;
+ case 2:
+ if (offset + 1 < buf->size) {
+ *(uint16_t *)(buf->data + offset) = be16_to_cpu(val);
+ memory_region_set_dirty(&buf->mr, offset, 2);
+ }
+ break;
+ case 1:
+ if (offset < buf->size) {
+ *(uint8_t *)(buf->data + offset) = val;
+ memory_region_set_dirty(&buf->mr, offset, 1);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static uint64_t artist_vram_read(void *opaque, hwaddr addr, unsigned size)
+{
+ ARTISTState *s = opaque;
+ struct vram_buffer *buf;
+ uint64_t val;
+ unsigned int posy, posx;
+
+ if (s->cmap_bm_access) {
+ buf = &s->vram_buffer[ARTIST_BUFFER_CMAP];
+ val = 0;
+ if (addr < buf->size && addr + 3 < buf->size) {
+ val = *(uint32_t *)(buf->data + addr);
+ }
+ trace_artist_vram_read(size, addr, 0, 0, val);
+ return val;
+ }
+
+ buf = vram_read_buffer(s);
+ if (!buf->size) {
+ return 0;
+ }
+
+ posy = ADDR_TO_Y(addr >> 2);
+ posx = ADDR_TO_X(addr >> 2);
+
+ if (posy > buf->height || posx > buf->width) {
+ return 0;
+ }
+
+ val = cpu_to_be32(*(uint32_t *)(buf->data + posy * buf->width + posx));
+ trace_artist_vram_read(size, addr, posx, posy, val);
+ return val;
+}
+
+static const MemoryRegionOps artist_reg_ops = {
+ .read = artist_reg_read,
+ .write = artist_reg_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .impl.min_access_size = 1,
+ .impl.max_access_size = 4,
+};
+
+static const MemoryRegionOps artist_vram_ops = {
+ .read = artist_vram_read,
+ .write = artist_vram_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .impl.min_access_size = 1,
+ .impl.max_access_size = 4,
+};
+
+static void artist_draw_cursor(ARTISTState *s)
+{
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ uint32_t *data = (uint32_t *)surface_data(surface);
+ struct vram_buffer *cursor0, *cursor1 , *buf;
+ int cx, cy, cursor_pos_x, cursor_pos_y;
+
+ cursor0 = &s->vram_buffer[ARTIST_BUFFER_CURSOR1];
+ cursor1 = &s->vram_buffer[ARTIST_BUFFER_CURSOR2];
+ buf = &s->vram_buffer[ARTIST_BUFFER_AP];
+
+ artist_get_cursor_pos(s, &cursor_pos_x, &cursor_pos_y);
+
+ for (cy = 0; cy < s->cursor_height; cy++) {
+
+ for (cx = 0; cx < s->cursor_width; cx++) {
+
+ if (cursor_pos_y + cy < 0 ||
+ cursor_pos_x + cx < 0 ||
+ cursor_pos_y + cy > buf->height - 1 ||
+ cursor_pos_x + cx > buf->width) {
+ continue;
+ }
+
+ int dstoffset = (cursor_pos_y + cy) * s->width +
+ (cursor_pos_x + cx);
+
+ if (cursor0->data[cy * cursor0->width + cx]) {
+ data[dstoffset] = 0;
+ } else {
+ if (cursor1->data[cy * cursor1->width + cx]) {
+ data[dstoffset] = 0xffffff;
+ }
+ }
+ }
+ }
+}
+
+static void artist_draw_line(void *opaque, uint8_t *d, const uint8_t *src,
+ int width, int pitch)
+{
+ ARTISTState *s = ARTIST(opaque);
+ uint32_t *cmap, *data = (uint32_t *)d;
+ int x;
+
+ cmap = (uint32_t *)(s->vram_buffer[ARTIST_BUFFER_CMAP].data + 0x400);
+
+ for (x = 0; x < s->width; x++) {
+ *data++ = cmap[*src++];
+ }
+}
+
+static void artist_update_display(void *opaque)
+{
+ ARTISTState *s = opaque;
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ int first = 0, last;
+
+
+ framebuffer_update_display(surface, &s->fbsection, s->width, s->height,
+ s->width, s->width * 4, 0, 0, artist_draw_line,
+ s, &first, &last);
+
+ artist_draw_cursor(s);
+
+ dpy_gfx_update(s->con, 0, 0, s->width, s->height);
+}
+
+static void artist_invalidate(void *opaque)
+{
+ ARTISTState *s = ARTIST(opaque);
+ struct vram_buffer *buf = &s->vram_buffer[ARTIST_BUFFER_AP];
+ memory_region_set_dirty(&buf->mr, 0, buf->size);
+}
+
+static const GraphicHwOps artist_ops = {
+ .invalidate = artist_invalidate,
+ .gfx_update = artist_update_display,
+};
+
+static void artist_initfn(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ ARTISTState *s = ARTIST(obj);
+
+ memory_region_init_io(&s->reg, obj, &artist_reg_ops, s, "artist.reg",
+ 4 * MiB);
+ memory_region_init_io(&s->vram_mem, obj, &artist_vram_ops, s, "artist.vram",
+ 8 * MiB);
+ sysbus_init_mmio(sbd, &s->reg);
+ sysbus_init_mmio(sbd, &s->vram_mem);
+}
+
+static void artist_create_buffer(ARTISTState *s, const char *name,
+ hwaddr *offset, unsigned int idx,
+ int width, int height)
+{
+ struct vram_buffer *buf = s->vram_buffer + idx;
+
+ memory_region_init_ram(&buf->mr, NULL, name, width * height,
+ &error_fatal);
+ memory_region_add_subregion_overlap(&s->mem_as_root, *offset, &buf->mr, 0);
+
+ buf->data = memory_region_get_ram_ptr(&buf->mr);
+ buf->size = height * width;
+ buf->width = width;
+ buf->height = height;
+
+ *offset += buf->size;
+}
+
+static void artist_realizefn(DeviceState *dev, Error **errp)
+{
+ ARTISTState *s = ARTIST(dev);
+ struct vram_buffer *buf;
+ hwaddr offset = 0;
+
+ if (s->width > 2048 || s->height > 2048) {
+ error_report("artist: screen size can not exceed 2048 x 2048 pixel.");
+ s->width = MIN(s->width, 2048);
+ s->height = MIN(s->height, 2048);
+ }
+
+ if (s->width < 640 || s->height < 480) {
+ error_report("artist: minimum screen size is 640 x 480 pixel.");
+ s->width = MAX(s->width, 640);
+ s->height = MAX(s->height, 480);
+ }
+
+ memory_region_init(&s->mem_as_root, OBJECT(dev), "artist", ~0ull);
+ address_space_init(&s->as, &s->mem_as_root, "artist");
+
+ artist_create_buffer(s, "cmap", &offset, ARTIST_BUFFER_CMAP, 2048, 4);
+ artist_create_buffer(s, "ap", &offset, ARTIST_BUFFER_AP,
+ s->width, s->height);
+ artist_create_buffer(s, "cursor1", &offset, ARTIST_BUFFER_CURSOR1, 64, 64);
+ artist_create_buffer(s, "cursor2", &offset, ARTIST_BUFFER_CURSOR2, 64, 64);
+ artist_create_buffer(s, "attribute", &offset, ARTIST_BUFFER_ATTRIBUTE,
+ 64, 64);
+
+ buf = &s->vram_buffer[ARTIST_BUFFER_AP];
+ framebuffer_update_memory_section(&s->fbsection, &buf->mr, 0,
+ buf->width, buf->height);
+ /*
+ * no idea whether the cursor is fixed size or not, so assume 32x32 which
+ * seems sufficient for HP-UX X11.
+ */
+ s->cursor_height = 32;
+ s->cursor_width = 32;
+
+ s->con = graphic_console_init(dev, 0, &artist_ops, s);
+ qemu_console_resize(s->con, s->width, s->height);
+}
+
+static int vmstate_artist_post_load(void *opaque, int version_id)
+{
+ artist_invalidate(opaque);
+ return 0;
+}
+
+static const VMStateDescription vmstate_artist = {
+ .name = "artist",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = vmstate_artist_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT16(height, ARTISTState),
+ VMSTATE_UINT16(width, ARTISTState),
+ VMSTATE_UINT16(depth, ARTISTState),
+ VMSTATE_UINT32(fg_color, ARTISTState),
+ VMSTATE_UINT32(bg_color, ARTISTState),
+ VMSTATE_UINT32(vram_char_y, ARTISTState),
+ VMSTATE_UINT32(vram_bitmask, ARTISTState),
+ VMSTATE_UINT32(vram_start, ARTISTState),
+ VMSTATE_UINT32(vram_pos, ARTISTState),
+ VMSTATE_UINT32(vram_size, ARTISTState),
+ VMSTATE_UINT32(blockmove_source, ARTISTState),
+ VMSTATE_UINT32(blockmove_dest, ARTISTState),
+ VMSTATE_UINT32(blockmove_size, ARTISTState),
+ VMSTATE_UINT32(line_size, ARTISTState),
+ VMSTATE_UINT32(line_end, ARTISTState),
+ VMSTATE_UINT32(line_xy, ARTISTState),
+ VMSTATE_UINT32(cursor_pos, ARTISTState),
+ VMSTATE_UINT32(cursor_height, ARTISTState),
+ VMSTATE_UINT32(cursor_width, ARTISTState),
+ VMSTATE_UINT32(plane_mask, ARTISTState),
+ VMSTATE_UINT32(reg_100080, ARTISTState),
+ VMSTATE_UINT32(reg_300200, ARTISTState),
+ VMSTATE_UINT32(reg_300208, ARTISTState),
+ VMSTATE_UINT32(reg_300218, ARTISTState),
+ VMSTATE_UINT32(cmap_bm_access, ARTISTState),
+ VMSTATE_UINT32(dst_bm_access, ARTISTState),
+ VMSTATE_UINT32(src_bm_access, ARTISTState),
+ VMSTATE_UINT32(control_plane, ARTISTState),
+ VMSTATE_UINT32(transfer_data, ARTISTState),
+ VMSTATE_UINT32(image_bitmap_op, ARTISTState),
+ VMSTATE_UINT32(font_write1, ARTISTState),
+ VMSTATE_UINT32(font_write2, ARTISTState),
+ VMSTATE_UINT32(font_write_pos_y, ARTISTState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property artist_properties[] = {
+ DEFINE_PROP_UINT16("width", ARTISTState, width, 1280),
+ DEFINE_PROP_UINT16("height", ARTISTState, height, 1024),
+ DEFINE_PROP_UINT16("depth", ARTISTState, depth, 8),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void artist_reset(DeviceState *qdev)
+{
+}
+
+static void artist_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = artist_realizefn;
+ dc->vmsd = &vmstate_artist;
+ dc->reset = artist_reset;
+ device_class_set_props(dc, artist_properties);
+}
+
+static const TypeInfo artist_info = {
+ .name = TYPE_ARTIST,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(ARTISTState),
+ .instance_init = artist_initfn,
+ .class_init = artist_class_init,
+};
+
+static void artist_register_types(void)
+{
+ type_register_static(&artist_info);
+}
+
+type_init(artist_register_types)
diff --git a/hw/display/ati.c b/hw/display/ati.c
new file mode 100644
index 000000000..31f22754d
--- /dev/null
+++ b/hw/display/ati.c
@@ -0,0 +1,1052 @@
+/*
+ * QEMU ATI SVGA emulation
+ *
+ * Copyright (c) 2019 BALATON Zoltan
+ *
+ * This work is licensed under the GNU GPL license version 2 or later.
+ */
+
+/*
+ * WARNING:
+ * This is very incomplete and only enough for Linux console and some
+ * unaccelerated X output at the moment.
+ * Currently it's little more than a frame buffer with minimal functions,
+ * other more advanced features of the hardware are yet to be implemented.
+ * We only aim for Rage 128 Pro (and some RV100) and 2D only at first,
+ * No 3D at all yet (maybe after 2D works, but feel free to improve it)
+ */
+
+#include "qemu/osdep.h"
+#include "ati_int.h"
+#include "ati_regs.h"
+#include "vga-access.h"
+#include "hw/qdev-properties.h"
+#include "vga_regs.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/error-report.h"
+#include "qapi/error.h"
+#include "ui/console.h"
+#include "hw/display/i2c-ddc.h"
+#include "trace.h"
+
+#define ATI_DEBUG_HW_CURSOR 0
+
+static const struct {
+ const char *name;
+ uint16_t dev_id;
+} ati_model_aliases[] = {
+ { "rage128p", PCI_DEVICE_ID_ATI_RAGE128_PF },
+ { "rv100", PCI_DEVICE_ID_ATI_RADEON_QY },
+};
+
+enum { VGA_MODE, EXT_MODE };
+
+static void ati_vga_switch_mode(ATIVGAState *s)
+{
+ DPRINTF("%d -> %d\n",
+ s->mode, !!(s->regs.crtc_gen_cntl & CRTC2_EXT_DISP_EN));
+ if (s->regs.crtc_gen_cntl & CRTC2_EXT_DISP_EN) {
+ /* Extended mode enabled */
+ s->mode = EXT_MODE;
+ if (s->regs.crtc_gen_cntl & CRTC2_EN) {
+ /* CRT controller enabled, use CRTC values */
+ /* FIXME Should these be the same as VGA CRTC regs? */
+ uint32_t offs = s->regs.crtc_offset & 0x07ffffff;
+ int stride = (s->regs.crtc_pitch & 0x7ff) * 8;
+ int bpp = 0;
+ int h, v;
+
+ if (s->regs.crtc_h_total_disp == 0) {
+ s->regs.crtc_h_total_disp = ((640 / 8) - 1) << 16;
+ }
+ if (s->regs.crtc_v_total_disp == 0) {
+ s->regs.crtc_v_total_disp = (480 - 1) << 16;
+ }
+ h = ((s->regs.crtc_h_total_disp >> 16) + 1) * 8;
+ v = (s->regs.crtc_v_total_disp >> 16) + 1;
+ switch (s->regs.crtc_gen_cntl & CRTC_PIX_WIDTH_MASK) {
+ case CRTC_PIX_WIDTH_4BPP:
+ bpp = 4;
+ break;
+ case CRTC_PIX_WIDTH_8BPP:
+ bpp = 8;
+ break;
+ case CRTC_PIX_WIDTH_15BPP:
+ bpp = 15;
+ break;
+ case CRTC_PIX_WIDTH_16BPP:
+ bpp = 16;
+ break;
+ case CRTC_PIX_WIDTH_24BPP:
+ bpp = 24;
+ break;
+ case CRTC_PIX_WIDTH_32BPP:
+ bpp = 32;
+ break;
+ default:
+ qemu_log_mask(LOG_UNIMP, "Unsupported bpp value\n");
+ return;
+ }
+ DPRINTF("Switching to %dx%d %d %d @ %x\n", h, v, stride, bpp, offs);
+ vbe_ioport_write_index(&s->vga, 0, VBE_DISPI_INDEX_ENABLE);
+ vbe_ioport_write_data(&s->vga, 0, VBE_DISPI_DISABLED);
+ s->vga.big_endian_fb = (s->regs.config_cntl & APER_0_ENDIAN ||
+ s->regs.config_cntl & APER_1_ENDIAN ?
+ true : false);
+ /* reset VBE regs then set up mode */
+ s->vga.vbe_regs[VBE_DISPI_INDEX_XRES] = h;
+ s->vga.vbe_regs[VBE_DISPI_INDEX_YRES] = v;
+ s->vga.vbe_regs[VBE_DISPI_INDEX_BPP] = bpp;
+ /* enable mode via ioport so it updates vga regs */
+ vbe_ioport_write_index(&s->vga, 0, VBE_DISPI_INDEX_ENABLE);
+ vbe_ioport_write_data(&s->vga, 0, VBE_DISPI_ENABLED |
+ VBE_DISPI_LFB_ENABLED | VBE_DISPI_NOCLEARMEM |
+ (s->regs.dac_cntl & DAC_8BIT_EN ? VBE_DISPI_8BIT_DAC : 0));
+ /* now set offset and stride after enable as that resets these */
+ if (stride) {
+ int bypp = DIV_ROUND_UP(bpp, BITS_PER_BYTE);
+
+ vbe_ioport_write_index(&s->vga, 0, VBE_DISPI_INDEX_VIRT_WIDTH);
+ vbe_ioport_write_data(&s->vga, 0, stride);
+ stride *= bypp;
+ if (offs % stride) {
+ DPRINTF("CRTC offset is not multiple of pitch\n");
+ vbe_ioport_write_index(&s->vga, 0,
+ VBE_DISPI_INDEX_X_OFFSET);
+ vbe_ioport_write_data(&s->vga, 0, offs % stride / bypp);
+ }
+ vbe_ioport_write_index(&s->vga, 0, VBE_DISPI_INDEX_Y_OFFSET);
+ vbe_ioport_write_data(&s->vga, 0, offs / stride);
+ DPRINTF("VBE offset (%d,%d), vbe_start_addr=%x\n",
+ s->vga.vbe_regs[VBE_DISPI_INDEX_X_OFFSET],
+ s->vga.vbe_regs[VBE_DISPI_INDEX_Y_OFFSET],
+ s->vga.vbe_start_addr);
+ }
+ }
+ } else {
+ /* VGA mode enabled */
+ s->mode = VGA_MODE;
+ vbe_ioport_write_index(&s->vga, 0, VBE_DISPI_INDEX_ENABLE);
+ vbe_ioport_write_data(&s->vga, 0, VBE_DISPI_DISABLED);
+ }
+}
+
+/* Used by host side hardware cursor */
+static void ati_cursor_define(ATIVGAState *s)
+{
+ uint8_t data[1024];
+ uint32_t srcoff;
+ int i, j, idx = 0;
+
+ if ((s->regs.cur_offset & BIT(31)) || s->cursor_guest_mode) {
+ return; /* Do not update cursor if locked or rendered by guest */
+ }
+ /* FIXME handle cur_hv_offs correctly */
+ srcoff = s->regs.cur_offset -
+ (s->regs.cur_hv_offs >> 16) - (s->regs.cur_hv_offs & 0xffff) * 16;
+ for (i = 0; i < 64; i++) {
+ for (j = 0; j < 8; j++, idx++) {
+ data[idx] = vga_read_byte(&s->vga, srcoff + i * 16 + j);
+ data[512 + idx] = vga_read_byte(&s->vga, srcoff + i * 16 + j + 8);
+ }
+ }
+ if (!s->cursor) {
+ s->cursor = cursor_alloc(64, 64);
+ }
+ cursor_set_mono(s->cursor, s->regs.cur_color1, s->regs.cur_color0,
+ &data[512], 1, &data[0]);
+ dpy_cursor_define(s->vga.con, s->cursor);
+}
+
+/* Alternatively support guest rendered hardware cursor */
+static void ati_cursor_invalidate(VGACommonState *vga)
+{
+ ATIVGAState *s = container_of(vga, ATIVGAState, vga);
+ int size = (s->regs.crtc_gen_cntl & CRTC2_CUR_EN) ? 64 : 0;
+
+ if (s->regs.cur_offset & BIT(31)) {
+ return; /* Do not update cursor if locked */
+ }
+ if (s->cursor_size != size ||
+ vga->hw_cursor_x != s->regs.cur_hv_pos >> 16 ||
+ vga->hw_cursor_y != (s->regs.cur_hv_pos & 0xffff) ||
+ s->cursor_offset != s->regs.cur_offset - (s->regs.cur_hv_offs >> 16) -
+ (s->regs.cur_hv_offs & 0xffff) * 16) {
+ /* Remove old cursor then update and show new one if needed */
+ vga_invalidate_scanlines(vga, vga->hw_cursor_y, vga->hw_cursor_y + 63);
+ vga->hw_cursor_x = s->regs.cur_hv_pos >> 16;
+ vga->hw_cursor_y = s->regs.cur_hv_pos & 0xffff;
+ s->cursor_offset = s->regs.cur_offset - (s->regs.cur_hv_offs >> 16) -
+ (s->regs.cur_hv_offs & 0xffff) * 16;
+ s->cursor_size = size;
+ if (size) {
+ vga_invalidate_scanlines(vga,
+ vga->hw_cursor_y, vga->hw_cursor_y + 63);
+ }
+ }
+}
+
+static void ati_cursor_draw_line(VGACommonState *vga, uint8_t *d, int scr_y)
+{
+ ATIVGAState *s = container_of(vga, ATIVGAState, vga);
+ uint32_t srcoff;
+ uint32_t *dp = (uint32_t *)d;
+ int i, j, h;
+
+ if (!(s->regs.crtc_gen_cntl & CRTC2_CUR_EN) ||
+ scr_y < vga->hw_cursor_y || scr_y >= vga->hw_cursor_y + 64 ||
+ scr_y > s->regs.crtc_v_total_disp >> 16) {
+ return;
+ }
+ /* FIXME handle cur_hv_offs correctly */
+ srcoff = s->cursor_offset + (scr_y - vga->hw_cursor_y) * 16;
+ dp = &dp[vga->hw_cursor_x];
+ h = ((s->regs.crtc_h_total_disp >> 16) + 1) * 8;
+ for (i = 0; i < 8; i++) {
+ uint32_t color;
+ uint8_t abits = vga_read_byte(vga, srcoff + i);
+ uint8_t xbits = vga_read_byte(vga, srcoff + i + 8);
+ for (j = 0; j < 8; j++, abits <<= 1, xbits <<= 1) {
+ if (abits & BIT(7)) {
+ if (xbits & BIT(7)) {
+ color = dp[i * 8 + j] ^ 0xffffffff; /* complement */
+ } else {
+ continue; /* transparent, no change */
+ }
+ } else {
+ color = (xbits & BIT(7) ? s->regs.cur_color1 :
+ s->regs.cur_color0) | 0xff000000;
+ }
+ if (vga->hw_cursor_x + i * 8 + j >= h) {
+ return; /* end of screen, don't span to next line */
+ }
+ dp[i * 8 + j] = color;
+ }
+ }
+}
+
+static uint64_t ati_i2c(bitbang_i2c_interface *i2c, uint64_t data, int base)
+{
+ bool c = (data & BIT(base + 17) ? !!(data & BIT(base + 1)) : 1);
+ bool d = (data & BIT(base + 16) ? !!(data & BIT(base)) : 1);
+
+ bitbang_i2c_set(i2c, BITBANG_I2C_SCL, c);
+ d = bitbang_i2c_set(i2c, BITBANG_I2C_SDA, d);
+
+ data &= ~0xf00ULL;
+ if (c) {
+ data |= BIT(base + 9);
+ }
+ if (d) {
+ data |= BIT(base + 8);
+ }
+ return data;
+}
+
+static void ati_vga_update_irq(ATIVGAState *s)
+{
+ pci_set_irq(&s->dev, !!(s->regs.gen_int_status & s->regs.gen_int_cntl));
+}
+
+static void ati_vga_vblank_irq(void *opaque)
+{
+ ATIVGAState *s = opaque;
+
+ timer_mod(&s->vblank_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
+ NANOSECONDS_PER_SECOND / 60);
+ s->regs.gen_int_status |= CRTC_VBLANK_INT;
+ ati_vga_update_irq(s);
+}
+
+static inline uint64_t ati_reg_read_offs(uint32_t reg, int offs,
+ unsigned int size)
+{
+ if (offs == 0 && size == 4) {
+ return reg;
+ } else {
+ return extract32(reg, offs * BITS_PER_BYTE, size * BITS_PER_BYTE);
+ }
+}
+
+static uint64_t ati_mm_read(void *opaque, hwaddr addr, unsigned int size)
+{
+ ATIVGAState *s = opaque;
+ uint64_t val = 0;
+
+ switch (addr) {
+ case MM_INDEX:
+ val = s->regs.mm_index;
+ break;
+ case MM_DATA ... MM_DATA + 3:
+ /* indexed access to regs or memory */
+ if (s->regs.mm_index & BIT(31)) {
+ uint32_t idx = s->regs.mm_index & ~BIT(31);
+ if (idx <= s->vga.vram_size - size) {
+ val = ldn_le_p(s->vga.vram_ptr + idx, size);
+ }
+ } else if (s->regs.mm_index > MM_DATA + 3) {
+ val = ati_mm_read(s, s->regs.mm_index + addr - MM_DATA, size);
+ } else {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ati_mm_read: mm_index too small: %u\n", s->regs.mm_index);
+ }
+ break;
+ case BIOS_0_SCRATCH ... BUS_CNTL - 1:
+ {
+ int i = (addr - BIOS_0_SCRATCH) / 4;
+ if (s->dev_id == PCI_DEVICE_ID_ATI_RAGE128_PF && i > 3) {
+ break;
+ }
+ val = ati_reg_read_offs(s->regs.bios_scratch[i],
+ addr - (BIOS_0_SCRATCH + i * 4), size);
+ break;
+ }
+ case GEN_INT_CNTL:
+ val = s->regs.gen_int_cntl;
+ break;
+ case GEN_INT_STATUS:
+ val = s->regs.gen_int_status;
+ break;
+ case CRTC_GEN_CNTL ... CRTC_GEN_CNTL + 3:
+ val = ati_reg_read_offs(s->regs.crtc_gen_cntl,
+ addr - CRTC_GEN_CNTL, size);
+ break;
+ case CRTC_EXT_CNTL ... CRTC_EXT_CNTL + 3:
+ val = ati_reg_read_offs(s->regs.crtc_ext_cntl,
+ addr - CRTC_EXT_CNTL, size);
+ break;
+ case DAC_CNTL:
+ val = s->regs.dac_cntl;
+ break;
+ case GPIO_VGA_DDC:
+ val = s->regs.gpio_vga_ddc;
+ break;
+ case GPIO_DVI_DDC:
+ val = s->regs.gpio_dvi_ddc;
+ break;
+ case GPIO_MONID ... GPIO_MONID + 3:
+ val = ati_reg_read_offs(s->regs.gpio_monid,
+ addr - GPIO_MONID, size);
+ break;
+ case PALETTE_INDEX:
+ /* FIXME unaligned access */
+ val = vga_ioport_read(&s->vga, VGA_PEL_IR) << 16;
+ val |= vga_ioport_read(&s->vga, VGA_PEL_IW) & 0xff;
+ break;
+ case PALETTE_DATA:
+ val = vga_ioport_read(&s->vga, VGA_PEL_D);
+ break;
+ case CNFG_CNTL:
+ val = s->regs.config_cntl;
+ break;
+ case CNFG_MEMSIZE:
+ val = s->vga.vram_size;
+ break;
+ case CONFIG_APER_0_BASE:
+ case CONFIG_APER_1_BASE:
+ val = pci_default_read_config(&s->dev,
+ PCI_BASE_ADDRESS_0, size) & 0xfffffff0;
+ break;
+ case CONFIG_APER_SIZE:
+ val = s->vga.vram_size;
+ break;
+ case CONFIG_REG_1_BASE:
+ val = pci_default_read_config(&s->dev,
+ PCI_BASE_ADDRESS_2, size) & 0xfffffff0;
+ break;
+ case CONFIG_REG_APER_SIZE:
+ val = memory_region_size(&s->mm);
+ break;
+ case MC_STATUS:
+ val = 5;
+ break;
+ case MEM_SDRAM_MODE_REG:
+ if (s->dev_id != PCI_DEVICE_ID_ATI_RAGE128_PF) {
+ val = BIT(28) | BIT(20);
+ }
+ break;
+ case RBBM_STATUS:
+ case GUI_STAT:
+ val = 64; /* free CMDFIFO entries */
+ break;
+ case CRTC_H_TOTAL_DISP:
+ val = s->regs.crtc_h_total_disp;
+ break;
+ case CRTC_H_SYNC_STRT_WID:
+ val = s->regs.crtc_h_sync_strt_wid;
+ break;
+ case CRTC_V_TOTAL_DISP:
+ val = s->regs.crtc_v_total_disp;
+ break;
+ case CRTC_V_SYNC_STRT_WID:
+ val = s->regs.crtc_v_sync_strt_wid;
+ break;
+ case CRTC_OFFSET:
+ val = s->regs.crtc_offset;
+ break;
+ case CRTC_OFFSET_CNTL:
+ val = s->regs.crtc_offset_cntl;
+ break;
+ case CRTC_PITCH:
+ val = s->regs.crtc_pitch;
+ break;
+ case 0xf00 ... 0xfff:
+ val = pci_default_read_config(&s->dev, addr - 0xf00, size);
+ break;
+ case CUR_OFFSET ... CUR_OFFSET + 3:
+ val = ati_reg_read_offs(s->regs.cur_offset, addr - CUR_OFFSET, size);
+ break;
+ case CUR_HORZ_VERT_POSN ... CUR_HORZ_VERT_POSN + 3:
+ val = ati_reg_read_offs(s->regs.cur_hv_pos,
+ addr - CUR_HORZ_VERT_POSN, size);
+ if (addr + size > CUR_HORZ_VERT_POSN + 3) {
+ val |= (s->regs.cur_offset & BIT(31)) >> (4 - size);
+ }
+ break;
+ case CUR_HORZ_VERT_OFF ... CUR_HORZ_VERT_OFF + 3:
+ val = ati_reg_read_offs(s->regs.cur_hv_offs,
+ addr - CUR_HORZ_VERT_OFF, size);
+ if (addr + size > CUR_HORZ_VERT_OFF + 3) {
+ val |= (s->regs.cur_offset & BIT(31)) >> (4 - size);
+ }
+ break;
+ case CUR_CLR0 ... CUR_CLR0 + 3:
+ val = ati_reg_read_offs(s->regs.cur_color0, addr - CUR_CLR0, size);
+ break;
+ case CUR_CLR1 ... CUR_CLR1 + 3:
+ val = ati_reg_read_offs(s->regs.cur_color1, addr - CUR_CLR1, size);
+ break;
+ case DST_OFFSET:
+ val = s->regs.dst_offset;
+ break;
+ case DST_PITCH:
+ val = s->regs.dst_pitch;
+ if (s->dev_id == PCI_DEVICE_ID_ATI_RAGE128_PF) {
+ val &= s->regs.dst_tile << 16;
+ }
+ break;
+ case DST_WIDTH:
+ val = s->regs.dst_width;
+ break;
+ case DST_HEIGHT:
+ val = s->regs.dst_height;
+ break;
+ case SRC_X:
+ val = s->regs.src_x;
+ break;
+ case SRC_Y:
+ val = s->regs.src_y;
+ break;
+ case DST_X:
+ val = s->regs.dst_x;
+ break;
+ case DST_Y:
+ val = s->regs.dst_y;
+ break;
+ case DP_GUI_MASTER_CNTL:
+ val = s->regs.dp_gui_master_cntl;
+ break;
+ case SRC_OFFSET:
+ val = s->regs.src_offset;
+ break;
+ case SRC_PITCH:
+ val = s->regs.src_pitch;
+ if (s->dev_id == PCI_DEVICE_ID_ATI_RAGE128_PF) {
+ val &= s->regs.src_tile << 16;
+ }
+ break;
+ case DP_BRUSH_BKGD_CLR:
+ val = s->regs.dp_brush_bkgd_clr;
+ break;
+ case DP_BRUSH_FRGD_CLR:
+ val = s->regs.dp_brush_frgd_clr;
+ break;
+ case DP_SRC_FRGD_CLR:
+ val = s->regs.dp_src_frgd_clr;
+ break;
+ case DP_SRC_BKGD_CLR:
+ val = s->regs.dp_src_bkgd_clr;
+ break;
+ case DP_CNTL:
+ val = s->regs.dp_cntl;
+ break;
+ case DP_DATATYPE:
+ val = s->regs.dp_datatype;
+ break;
+ case DP_MIX:
+ val = s->regs.dp_mix;
+ break;
+ case DP_WRITE_MASK:
+ val = s->regs.dp_write_mask;
+ break;
+ case DEFAULT_OFFSET:
+ val = s->regs.default_offset;
+ if (s->dev_id != PCI_DEVICE_ID_ATI_RAGE128_PF) {
+ val >>= 10;
+ val |= s->regs.default_pitch << 16;
+ val |= s->regs.default_tile << 30;
+ }
+ break;
+ case DEFAULT_PITCH:
+ val = s->regs.default_pitch;
+ val |= s->regs.default_tile << 16;
+ break;
+ case DEFAULT_SC_BOTTOM_RIGHT:
+ val = s->regs.default_sc_bottom_right;
+ break;
+ default:
+ break;
+ }
+ if (addr < CUR_OFFSET || addr > CUR_CLR1 || ATI_DEBUG_HW_CURSOR) {
+ trace_ati_mm_read(size, addr, ati_reg_name(addr & ~3ULL), val);
+ }
+ return val;
+}
+
+static inline void ati_reg_write_offs(uint32_t *reg, int offs,
+ uint64_t data, unsigned int size)
+{
+ if (offs == 0 && size == 4) {
+ *reg = data;
+ } else {
+ *reg = deposit32(*reg, offs * BITS_PER_BYTE, size * BITS_PER_BYTE,
+ data);
+ }
+}
+
+static void ati_mm_write(void *opaque, hwaddr addr,
+ uint64_t data, unsigned int size)
+{
+ ATIVGAState *s = opaque;
+
+ if (addr < CUR_OFFSET || addr > CUR_CLR1 || ATI_DEBUG_HW_CURSOR) {
+ trace_ati_mm_write(size, addr, ati_reg_name(addr & ~3ULL), data);
+ }
+ switch (addr) {
+ case MM_INDEX:
+ s->regs.mm_index = data & ~3;
+ break;
+ case MM_DATA ... MM_DATA + 3:
+ /* indexed access to regs or memory */
+ if (s->regs.mm_index & BIT(31)) {
+ uint32_t idx = s->regs.mm_index & ~BIT(31);
+ if (idx <= s->vga.vram_size - size) {
+ stn_le_p(s->vga.vram_ptr + idx, size, data);
+ }
+ } else if (s->regs.mm_index > MM_DATA + 3) {
+ ati_mm_write(s, s->regs.mm_index + addr - MM_DATA, data, size);
+ } else {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ati_mm_write: mm_index too small: %u\n", s->regs.mm_index);
+ }
+ break;
+ case BIOS_0_SCRATCH ... BUS_CNTL - 1:
+ {
+ int i = (addr - BIOS_0_SCRATCH) / 4;
+ if (s->dev_id == PCI_DEVICE_ID_ATI_RAGE128_PF && i > 3) {
+ break;
+ }
+ ati_reg_write_offs(&s->regs.bios_scratch[i],
+ addr - (BIOS_0_SCRATCH + i * 4), data, size);
+ break;
+ }
+ case GEN_INT_CNTL:
+ s->regs.gen_int_cntl = data;
+ if (data & CRTC_VBLANK_INT) {
+ ati_vga_vblank_irq(s);
+ } else {
+ timer_del(&s->vblank_timer);
+ ati_vga_update_irq(s);
+ }
+ break;
+ case GEN_INT_STATUS:
+ data &= (s->dev_id == PCI_DEVICE_ID_ATI_RAGE128_PF ?
+ 0x000f040fUL : 0xfc080effUL);
+ s->regs.gen_int_status &= ~data;
+ ati_vga_update_irq(s);
+ break;
+ case CRTC_GEN_CNTL ... CRTC_GEN_CNTL + 3:
+ {
+ uint32_t val = s->regs.crtc_gen_cntl;
+ ati_reg_write_offs(&s->regs.crtc_gen_cntl,
+ addr - CRTC_GEN_CNTL, data, size);
+ if ((val & CRTC2_CUR_EN) != (s->regs.crtc_gen_cntl & CRTC2_CUR_EN)) {
+ if (s->cursor_guest_mode) {
+ s->vga.force_shadow = !!(s->regs.crtc_gen_cntl & CRTC2_CUR_EN);
+ } else {
+ if (s->regs.crtc_gen_cntl & CRTC2_CUR_EN) {
+ ati_cursor_define(s);
+ }
+ dpy_mouse_set(s->vga.con, s->regs.cur_hv_pos >> 16,
+ s->regs.cur_hv_pos & 0xffff,
+ (s->regs.crtc_gen_cntl & CRTC2_CUR_EN) != 0);
+ }
+ }
+ if ((val & (CRTC2_EXT_DISP_EN | CRTC2_EN)) !=
+ (s->regs.crtc_gen_cntl & (CRTC2_EXT_DISP_EN | CRTC2_EN))) {
+ ati_vga_switch_mode(s);
+ }
+ break;
+ }
+ case CRTC_EXT_CNTL ... CRTC_EXT_CNTL + 3:
+ {
+ uint32_t val = s->regs.crtc_ext_cntl;
+ ati_reg_write_offs(&s->regs.crtc_ext_cntl,
+ addr - CRTC_EXT_CNTL, data, size);
+ if (s->regs.crtc_ext_cntl & CRT_CRTC_DISPLAY_DIS) {
+ DPRINTF("Display disabled\n");
+ s->vga.ar_index &= ~BIT(5);
+ } else {
+ DPRINTF("Display enabled\n");
+ s->vga.ar_index |= BIT(5);
+ ati_vga_switch_mode(s);
+ }
+ if ((val & CRT_CRTC_DISPLAY_DIS) !=
+ (s->regs.crtc_ext_cntl & CRT_CRTC_DISPLAY_DIS)) {
+ ati_vga_switch_mode(s);
+ }
+ break;
+ }
+ case DAC_CNTL:
+ s->regs.dac_cntl = data & 0xffffe3ff;
+ s->vga.dac_8bit = !!(data & DAC_8BIT_EN);
+ break;
+ case GPIO_VGA_DDC:
+ if (s->dev_id != PCI_DEVICE_ID_ATI_RAGE128_PF) {
+ /* FIXME: Maybe add a property to select VGA or DVI port? */
+ }
+ break;
+ case GPIO_DVI_DDC:
+ if (s->dev_id != PCI_DEVICE_ID_ATI_RAGE128_PF) {
+ s->regs.gpio_dvi_ddc = ati_i2c(&s->bbi2c, data, 0);
+ }
+ break;
+ case GPIO_MONID ... GPIO_MONID + 3:
+ /* FIXME What does Radeon have here? */
+ if (s->dev_id == PCI_DEVICE_ID_ATI_RAGE128_PF) {
+ ati_reg_write_offs(&s->regs.gpio_monid,
+ addr - GPIO_MONID, data, size);
+ /*
+ * Rage128p accesses DDC used to get EDID via these bits.
+ * Because some drivers access this via multiple byte writes
+ * we have to be careful when we send bits to avoid spurious
+ * changes in bitbang_i2c state. So only do it when mask is set
+ * and either the enable bits are changed or output bits changed
+ * while enabled.
+ */
+ if ((s->regs.gpio_monid & BIT(25)) &&
+ ((addr <= GPIO_MONID + 2 && addr + size > GPIO_MONID + 2) ||
+ (addr == GPIO_MONID && (s->regs.gpio_monid & 0x60000)))) {
+ s->regs.gpio_monid = ati_i2c(&s->bbi2c, s->regs.gpio_monid, 1);
+ }
+ }
+ break;
+ case PALETTE_INDEX ... PALETTE_INDEX + 3:
+ if (size == 4) {
+ vga_ioport_write(&s->vga, VGA_PEL_IR, (data >> 16) & 0xff);
+ vga_ioport_write(&s->vga, VGA_PEL_IW, data & 0xff);
+ } else {
+ if (addr == PALETTE_INDEX) {
+ vga_ioport_write(&s->vga, VGA_PEL_IW, data & 0xff);
+ } else {
+ vga_ioport_write(&s->vga, VGA_PEL_IR, data & 0xff);
+ }
+ }
+ break;
+ case PALETTE_DATA ... PALETTE_DATA + 3:
+ data <<= addr - PALETTE_DATA;
+ data = bswap32(data) >> 8;
+ vga_ioport_write(&s->vga, VGA_PEL_D, data & 0xff);
+ data >>= 8;
+ vga_ioport_write(&s->vga, VGA_PEL_D, data & 0xff);
+ data >>= 8;
+ vga_ioport_write(&s->vga, VGA_PEL_D, data & 0xff);
+ break;
+ case CNFG_CNTL:
+ s->regs.config_cntl = data;
+ break;
+ case CRTC_H_TOTAL_DISP:
+ s->regs.crtc_h_total_disp = data & 0x07ff07ff;
+ break;
+ case CRTC_H_SYNC_STRT_WID:
+ s->regs.crtc_h_sync_strt_wid = data & 0x17bf1fff;
+ break;
+ case CRTC_V_TOTAL_DISP:
+ s->regs.crtc_v_total_disp = data & 0x0fff0fff;
+ break;
+ case CRTC_V_SYNC_STRT_WID:
+ s->regs.crtc_v_sync_strt_wid = data & 0x9f0fff;
+ break;
+ case CRTC_OFFSET:
+ s->regs.crtc_offset = data & 0xc7ffffff;
+ break;
+ case CRTC_OFFSET_CNTL:
+ s->regs.crtc_offset_cntl = data; /* FIXME */
+ break;
+ case CRTC_PITCH:
+ s->regs.crtc_pitch = data & 0x07ff07ff;
+ break;
+ case 0xf00 ... 0xfff:
+ /* read-only copy of PCI config space so ignore writes */
+ break;
+ case CUR_OFFSET ... CUR_OFFSET + 3:
+ {
+ uint32_t t = s->regs.cur_offset;
+
+ ati_reg_write_offs(&t, addr - CUR_OFFSET, data, size);
+ t &= 0x87fffff0;
+ if (s->regs.cur_offset != t) {
+ s->regs.cur_offset = t;
+ ati_cursor_define(s);
+ }
+ break;
+ }
+ case CUR_HORZ_VERT_POSN ... CUR_HORZ_VERT_POSN + 3:
+ {
+ uint32_t t = s->regs.cur_hv_pos | (s->regs.cur_offset & BIT(31));
+
+ ati_reg_write_offs(&t, addr - CUR_HORZ_VERT_POSN, data, size);
+ s->regs.cur_hv_pos = t & 0x3fff0fff;
+ if (t & BIT(31)) {
+ s->regs.cur_offset |= t & BIT(31);
+ } else if (s->regs.cur_offset & BIT(31)) {
+ s->regs.cur_offset &= ~BIT(31);
+ ati_cursor_define(s);
+ }
+ if (!s->cursor_guest_mode &&
+ (s->regs.crtc_gen_cntl & CRTC2_CUR_EN) && !(t & BIT(31))) {
+ dpy_mouse_set(s->vga.con, s->regs.cur_hv_pos >> 16,
+ s->regs.cur_hv_pos & 0xffff, 1);
+ }
+ break;
+ }
+ case CUR_HORZ_VERT_OFF:
+ {
+ uint32_t t = s->regs.cur_hv_offs | (s->regs.cur_offset & BIT(31));
+
+ ati_reg_write_offs(&t, addr - CUR_HORZ_VERT_OFF, data, size);
+ s->regs.cur_hv_offs = t & 0x3f003f;
+ if (t & BIT(31)) {
+ s->regs.cur_offset |= t & BIT(31);
+ } else if (s->regs.cur_offset & BIT(31)) {
+ s->regs.cur_offset &= ~BIT(31);
+ ati_cursor_define(s);
+ }
+ break;
+ }
+ case CUR_CLR0 ... CUR_CLR0 + 3:
+ {
+ uint32_t t = s->regs.cur_color0;
+
+ ati_reg_write_offs(&t, addr - CUR_CLR0, data, size);
+ t &= 0xffffff;
+ if (s->regs.cur_color0 != t) {
+ s->regs.cur_color0 = t;
+ ati_cursor_define(s);
+ }
+ break;
+ }
+ case CUR_CLR1 ... CUR_CLR1 + 3:
+ /*
+ * Update cursor unconditionally here because some clients set up
+ * other registers before actually writing cursor data to memory at
+ * offset so we would miss cursor change unless always updating here
+ */
+ ati_reg_write_offs(&s->regs.cur_color1, addr - CUR_CLR1, data, size);
+ s->regs.cur_color1 &= 0xffffff;
+ ati_cursor_define(s);
+ break;
+ case DST_OFFSET:
+ if (s->dev_id == PCI_DEVICE_ID_ATI_RAGE128_PF) {
+ s->regs.dst_offset = data & 0xfffffff0;
+ } else {
+ s->regs.dst_offset = data & 0xfffffc00;
+ }
+ break;
+ case DST_PITCH:
+ if (s->dev_id == PCI_DEVICE_ID_ATI_RAGE128_PF) {
+ s->regs.dst_pitch = data & 0x3fff;
+ s->regs.dst_tile = (data >> 16) & 1;
+ } else {
+ s->regs.dst_pitch = data & 0x3ff0;
+ }
+ break;
+ case DST_TILE:
+ if (s->dev_id == PCI_DEVICE_ID_ATI_RADEON_QY) {
+ s->regs.dst_tile = data & 3;
+ }
+ break;
+ case DST_WIDTH:
+ s->regs.dst_width = data & 0x3fff;
+ ati_2d_blt(s);
+ break;
+ case DST_HEIGHT:
+ s->regs.dst_height = data & 0x3fff;
+ break;
+ case SRC_X:
+ s->regs.src_x = data & 0x3fff;
+ break;
+ case SRC_Y:
+ s->regs.src_y = data & 0x3fff;
+ break;
+ case DST_X:
+ s->regs.dst_x = data & 0x3fff;
+ break;
+ case DST_Y:
+ s->regs.dst_y = data & 0x3fff;
+ break;
+ case SRC_PITCH_OFFSET:
+ if (s->dev_id == PCI_DEVICE_ID_ATI_RAGE128_PF) {
+ s->regs.src_offset = (data & 0x1fffff) << 5;
+ s->regs.src_pitch = (data & 0x7fe00000) >> 21;
+ s->regs.src_tile = data >> 31;
+ } else {
+ s->regs.src_offset = (data & 0x3fffff) << 10;
+ s->regs.src_pitch = (data & 0x3fc00000) >> 16;
+ s->regs.src_tile = (data >> 30) & 1;
+ }
+ break;
+ case DST_PITCH_OFFSET:
+ if (s->dev_id == PCI_DEVICE_ID_ATI_RAGE128_PF) {
+ s->regs.dst_offset = (data & 0x1fffff) << 5;
+ s->regs.dst_pitch = (data & 0x7fe00000) >> 21;
+ s->regs.dst_tile = data >> 31;
+ } else {
+ s->regs.dst_offset = (data & 0x3fffff) << 10;
+ s->regs.dst_pitch = (data & 0x3fc00000) >> 16;
+ s->regs.dst_tile = data >> 30;
+ }
+ break;
+ case SRC_Y_X:
+ s->regs.src_x = data & 0x3fff;
+ s->regs.src_y = (data >> 16) & 0x3fff;
+ break;
+ case DST_Y_X:
+ s->regs.dst_x = data & 0x3fff;
+ s->regs.dst_y = (data >> 16) & 0x3fff;
+ break;
+ case DST_HEIGHT_WIDTH:
+ s->regs.dst_width = data & 0x3fff;
+ s->regs.dst_height = (data >> 16) & 0x3fff;
+ ati_2d_blt(s);
+ break;
+ case DP_GUI_MASTER_CNTL:
+ s->regs.dp_gui_master_cntl = data & 0xf800000f;
+ s->regs.dp_datatype = (data & 0x0f00) >> 8 | (data & 0x30f0) << 4 |
+ (data & 0x4000) << 16;
+ s->regs.dp_mix = (data & GMC_ROP3_MASK) | (data & 0x7000000) >> 16;
+ break;
+ case DST_WIDTH_X:
+ s->regs.dst_x = data & 0x3fff;
+ s->regs.dst_width = (data >> 16) & 0x3fff;
+ ati_2d_blt(s);
+ break;
+ case SRC_X_Y:
+ s->regs.src_y = data & 0x3fff;
+ s->regs.src_x = (data >> 16) & 0x3fff;
+ break;
+ case DST_X_Y:
+ s->regs.dst_y = data & 0x3fff;
+ s->regs.dst_x = (data >> 16) & 0x3fff;
+ break;
+ case DST_WIDTH_HEIGHT:
+ s->regs.dst_height = data & 0x3fff;
+ s->regs.dst_width = (data >> 16) & 0x3fff;
+ ati_2d_blt(s);
+ break;
+ case DST_HEIGHT_Y:
+ s->regs.dst_y = data & 0x3fff;
+ s->regs.dst_height = (data >> 16) & 0x3fff;
+ break;
+ case SRC_OFFSET:
+ if (s->dev_id == PCI_DEVICE_ID_ATI_RAGE128_PF) {
+ s->regs.src_offset = data & 0xfffffff0;
+ } else {
+ s->regs.src_offset = data & 0xfffffc00;
+ }
+ break;
+ case SRC_PITCH:
+ if (s->dev_id == PCI_DEVICE_ID_ATI_RAGE128_PF) {
+ s->regs.src_pitch = data & 0x3fff;
+ s->regs.src_tile = (data >> 16) & 1;
+ } else {
+ s->regs.src_pitch = data & 0x3ff0;
+ }
+ break;
+ case DP_BRUSH_BKGD_CLR:
+ s->regs.dp_brush_bkgd_clr = data;
+ break;
+ case DP_BRUSH_FRGD_CLR:
+ s->regs.dp_brush_frgd_clr = data;
+ break;
+ case DP_CNTL:
+ s->regs.dp_cntl = data;
+ break;
+ case DP_DATATYPE:
+ s->regs.dp_datatype = data & 0xe0070f0f;
+ break;
+ case DP_MIX:
+ s->regs.dp_mix = data & 0x00ff0700;
+ break;
+ case DP_WRITE_MASK:
+ s->regs.dp_write_mask = data;
+ break;
+ case DEFAULT_OFFSET:
+ if (s->dev_id == PCI_DEVICE_ID_ATI_RAGE128_PF) {
+ s->regs.default_offset = data & 0xfffffff0;
+ } else {
+ /* Radeon has DEFAULT_PITCH_OFFSET here like DST_PITCH_OFFSET */
+ s->regs.default_offset = (data & 0x3fffff) << 10;
+ s->regs.default_pitch = (data & 0x3fc00000) >> 16;
+ s->regs.default_tile = data >> 30;
+ }
+ break;
+ case DEFAULT_PITCH:
+ if (s->dev_id == PCI_DEVICE_ID_ATI_RAGE128_PF) {
+ s->regs.default_pitch = data & 0x3fff;
+ s->regs.default_tile = (data >> 16) & 1;
+ }
+ break;
+ case DEFAULT_SC_BOTTOM_RIGHT:
+ s->regs.default_sc_bottom_right = data & 0x3fff3fff;
+ break;
+ default:
+ break;
+ }
+}
+
+static const MemoryRegionOps ati_mm_ops = {
+ .read = ati_mm_read,
+ .write = ati_mm_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void ati_vga_realize(PCIDevice *dev, Error **errp)
+{
+ ATIVGAState *s = ATI_VGA(dev);
+ VGACommonState *vga = &s->vga;
+
+ if (s->model) {
+ int i;
+ for (i = 0; i < ARRAY_SIZE(ati_model_aliases); i++) {
+ if (!strcmp(s->model, ati_model_aliases[i].name)) {
+ s->dev_id = ati_model_aliases[i].dev_id;
+ break;
+ }
+ }
+ if (i >= ARRAY_SIZE(ati_model_aliases)) {
+ warn_report("Unknown ATI VGA model name, "
+ "using default rage128p");
+ }
+ }
+ if (s->dev_id != PCI_DEVICE_ID_ATI_RAGE128_PF &&
+ s->dev_id != PCI_DEVICE_ID_ATI_RADEON_QY) {
+ error_setg(errp, "Unknown ATI VGA device id, "
+ "only 0x5046 and 0x5159 are supported");
+ return;
+ }
+ pci_set_word(dev->config + PCI_DEVICE_ID, s->dev_id);
+
+ if (s->dev_id == PCI_DEVICE_ID_ATI_RADEON_QY &&
+ s->vga.vram_size_mb < 16) {
+ warn_report("Too small video memory for device id");
+ s->vga.vram_size_mb = 16;
+ }
+
+ /* init vga bits */
+ vga_common_init(vga, OBJECT(s));
+ vga_init(vga, OBJECT(s), pci_address_space(dev),
+ pci_address_space_io(dev), true);
+ vga->con = graphic_console_init(DEVICE(s), 0, s->vga.hw_ops, &s->vga);
+ if (s->cursor_guest_mode) {
+ vga->cursor_invalidate = ati_cursor_invalidate;
+ vga->cursor_draw_line = ati_cursor_draw_line;
+ }
+
+ /* ddc, edid */
+ I2CBus *i2cbus = i2c_init_bus(DEVICE(s), "ati-vga.ddc");
+ bitbang_i2c_init(&s->bbi2c, i2cbus);
+ I2CSlave *i2cddc = I2C_SLAVE(qdev_new(TYPE_I2CDDC));
+ i2c_slave_set_address(i2cddc, 0x50);
+ qdev_realize_and_unref(DEVICE(i2cddc), BUS(i2cbus), &error_abort);
+
+ /* mmio register space */
+ memory_region_init_io(&s->mm, OBJECT(s), &ati_mm_ops, s,
+ "ati.mmregs", 0x4000);
+ /* io space is alias to beginning of mmregs */
+ memory_region_init_alias(&s->io, OBJECT(s), "ati.io", &s->mm, 0, 0x100);
+
+ pci_register_bar(dev, 0, PCI_BASE_ADDRESS_MEM_PREFETCH, &vga->vram);
+ pci_register_bar(dev, 1, PCI_BASE_ADDRESS_SPACE_IO, &s->io);
+ pci_register_bar(dev, 2, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->mm);
+
+ /* most interrupts are not yet emulated but MacOS needs at least VBlank */
+ dev->config[PCI_INTERRUPT_PIN] = 1;
+ timer_init_ns(&s->vblank_timer, QEMU_CLOCK_VIRTUAL, ati_vga_vblank_irq, s);
+}
+
+static void ati_vga_reset(DeviceState *dev)
+{
+ ATIVGAState *s = ATI_VGA(dev);
+
+ timer_del(&s->vblank_timer);
+ ati_vga_update_irq(s);
+
+ /* reset vga */
+ vga_common_reset(&s->vga);
+ s->mode = VGA_MODE;
+}
+
+static void ati_vga_exit(PCIDevice *dev)
+{
+ ATIVGAState *s = ATI_VGA(dev);
+
+ timer_del(&s->vblank_timer);
+ graphic_console_close(s->vga.con);
+}
+
+static Property ati_vga_properties[] = {
+ DEFINE_PROP_UINT32("vgamem_mb", ATIVGAState, vga.vram_size_mb, 16),
+ DEFINE_PROP_STRING("model", ATIVGAState, model),
+ DEFINE_PROP_UINT16("x-device-id", ATIVGAState, dev_id,
+ PCI_DEVICE_ID_ATI_RAGE128_PF),
+ DEFINE_PROP_BOOL("guest_hwcursor", ATIVGAState, cursor_guest_mode, false),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static void ati_vga_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ dc->reset = ati_vga_reset;
+ device_class_set_props(dc, ati_vga_properties);
+ dc->hotpluggable = false;
+ set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories);
+
+ k->class_id = PCI_CLASS_DISPLAY_VGA;
+ k->vendor_id = PCI_VENDOR_ID_ATI;
+ k->device_id = PCI_DEVICE_ID_ATI_RAGE128_PF;
+ k->romfile = "vgabios-ati.bin";
+ k->realize = ati_vga_realize;
+ k->exit = ati_vga_exit;
+}
+
+static const TypeInfo ati_vga_info = {
+ .name = TYPE_ATI_VGA,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(ATIVGAState),
+ .class_init = ati_vga_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { INTERFACE_CONVENTIONAL_PCI_DEVICE },
+ { },
+ },
+};
+
+static void ati_vga_register_types(void)
+{
+ type_register_static(&ati_vga_info);
+}
+
+type_init(ati_vga_register_types)
diff --git a/hw/display/ati_2d.c b/hw/display/ati_2d.c
new file mode 100644
index 000000000..4dc10ea79
--- /dev/null
+++ b/hw/display/ati_2d.c
@@ -0,0 +1,206 @@
+/*
+ * QEMU ATI SVGA emulation
+ * 2D engine functions
+ *
+ * Copyright (c) 2019 BALATON Zoltan
+ *
+ * This work is licensed under the GNU GPL license version 2 or later.
+ */
+
+#include "qemu/osdep.h"
+#include "ati_int.h"
+#include "ati_regs.h"
+#include "qemu/log.h"
+#include "ui/pixel_ops.h"
+
+/*
+ * NOTE:
+ * This is 2D _acceleration_ and supposed to be fast. Therefore, don't try to
+ * reinvent the wheel (unlikely to get better with a naive implementation than
+ * existing libraries) and avoid (poorly) reimplementing gfx primitives.
+ * That is unnecessary and would become a performance problem. Instead, try to
+ * map to and reuse existing optimised facilities (e.g. pixman) wherever
+ * possible.
+ */
+
+static int ati_bpp_from_datatype(ATIVGAState *s)
+{
+ switch (s->regs.dp_datatype & 0xf) {
+ case 2:
+ return 8;
+ case 3:
+ case 4:
+ return 16;
+ case 5:
+ return 24;
+ case 6:
+ return 32;
+ default:
+ qemu_log_mask(LOG_UNIMP, "Unknown dst datatype %d\n",
+ s->regs.dp_datatype & 0xf);
+ return 0;
+ }
+}
+
+#define DEFAULT_CNTL (s->regs.dp_gui_master_cntl & GMC_DST_PITCH_OFFSET_CNTL)
+
+void ati_2d_blt(ATIVGAState *s)
+{
+ /* FIXME it is probably more complex than this and may need to be */
+ /* rewritten but for now as a start just to get some output: */
+ DisplaySurface *ds = qemu_console_surface(s->vga.con);
+ DPRINTF("%p %u ds: %p %d %d rop: %x\n", s->vga.vram_ptr,
+ s->vga.vbe_start_addr, surface_data(ds), surface_stride(ds),
+ surface_bits_per_pixel(ds),
+ (s->regs.dp_mix & GMC_ROP3_MASK) >> 16);
+ unsigned dst_x = (s->regs.dp_cntl & DST_X_LEFT_TO_RIGHT ?
+ s->regs.dst_x : s->regs.dst_x + 1 - s->regs.dst_width);
+ unsigned dst_y = (s->regs.dp_cntl & DST_Y_TOP_TO_BOTTOM ?
+ s->regs.dst_y : s->regs.dst_y + 1 - s->regs.dst_height);
+ int bpp = ati_bpp_from_datatype(s);
+ if (!bpp) {
+ qemu_log_mask(LOG_GUEST_ERROR, "Invalid bpp\n");
+ return;
+ }
+ int dst_stride = DEFAULT_CNTL ? s->regs.dst_pitch : s->regs.default_pitch;
+ if (!dst_stride) {
+ qemu_log_mask(LOG_GUEST_ERROR, "Zero dest pitch\n");
+ return;
+ }
+ uint8_t *dst_bits = s->vga.vram_ptr + (DEFAULT_CNTL ?
+ s->regs.dst_offset : s->regs.default_offset);
+
+ if (s->dev_id == PCI_DEVICE_ID_ATI_RAGE128_PF) {
+ dst_bits += s->regs.crtc_offset & 0x07ffffff;
+ dst_stride *= bpp;
+ }
+ uint8_t *end = s->vga.vram_ptr + s->vga.vram_size;
+ if (dst_x > 0x3fff || dst_y > 0x3fff || dst_bits >= end
+ || dst_bits + dst_x
+ + (dst_y + s->regs.dst_height) * dst_stride >= end) {
+ qemu_log_mask(LOG_UNIMP, "blt outside vram not implemented\n");
+ return;
+ }
+ DPRINTF("%d %d %d, %d %d %d, (%d,%d) -> (%d,%d) %dx%d %c %c\n",
+ s->regs.src_offset, s->regs.dst_offset, s->regs.default_offset,
+ s->regs.src_pitch, s->regs.dst_pitch, s->regs.default_pitch,
+ s->regs.src_x, s->regs.src_y, s->regs.dst_x, s->regs.dst_y,
+ s->regs.dst_width, s->regs.dst_height,
+ (s->regs.dp_cntl & DST_X_LEFT_TO_RIGHT ? '>' : '<'),
+ (s->regs.dp_cntl & DST_Y_TOP_TO_BOTTOM ? 'v' : '^'));
+ switch (s->regs.dp_mix & GMC_ROP3_MASK) {
+ case ROP3_SRCCOPY:
+ {
+ unsigned src_x = (s->regs.dp_cntl & DST_X_LEFT_TO_RIGHT ?
+ s->regs.src_x : s->regs.src_x + 1 - s->regs.dst_width);
+ unsigned src_y = (s->regs.dp_cntl & DST_Y_TOP_TO_BOTTOM ?
+ s->regs.src_y : s->regs.src_y + 1 - s->regs.dst_height);
+ int src_stride = DEFAULT_CNTL ?
+ s->regs.src_pitch : s->regs.default_pitch;
+ if (!src_stride) {
+ qemu_log_mask(LOG_GUEST_ERROR, "Zero source pitch\n");
+ return;
+ }
+ uint8_t *src_bits = s->vga.vram_ptr + (DEFAULT_CNTL ?
+ s->regs.src_offset : s->regs.default_offset);
+
+ if (s->dev_id == PCI_DEVICE_ID_ATI_RAGE128_PF) {
+ src_bits += s->regs.crtc_offset & 0x07ffffff;
+ src_stride *= bpp;
+ }
+ if (src_x > 0x3fff || src_y > 0x3fff || src_bits >= end
+ || src_bits + src_x
+ + (src_y + s->regs.dst_height) * src_stride >= end) {
+ qemu_log_mask(LOG_UNIMP, "blt outside vram not implemented\n");
+ return;
+ }
+
+ src_stride /= sizeof(uint32_t);
+ dst_stride /= sizeof(uint32_t);
+ DPRINTF("pixman_blt(%p, %p, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d)\n",
+ src_bits, dst_bits, src_stride, dst_stride, bpp, bpp,
+ src_x, src_y, dst_x, dst_y,
+ s->regs.dst_width, s->regs.dst_height);
+ if (s->regs.dp_cntl & DST_X_LEFT_TO_RIGHT &&
+ s->regs.dp_cntl & DST_Y_TOP_TO_BOTTOM) {
+ pixman_blt((uint32_t *)src_bits, (uint32_t *)dst_bits,
+ src_stride, dst_stride, bpp, bpp,
+ src_x, src_y, dst_x, dst_y,
+ s->regs.dst_width, s->regs.dst_height);
+ } else {
+ /* FIXME: We only really need a temporary if src and dst overlap */
+ int llb = s->regs.dst_width * (bpp / 8);
+ int tmp_stride = DIV_ROUND_UP(llb, sizeof(uint32_t));
+ uint32_t *tmp = g_malloc(tmp_stride * sizeof(uint32_t) *
+ s->regs.dst_height);
+ pixman_blt((uint32_t *)src_bits, tmp,
+ src_stride, tmp_stride, bpp, bpp,
+ src_x, src_y, 0, 0,
+ s->regs.dst_width, s->regs.dst_height);
+ pixman_blt(tmp, (uint32_t *)dst_bits,
+ tmp_stride, dst_stride, bpp, bpp,
+ 0, 0, dst_x, dst_y,
+ s->regs.dst_width, s->regs.dst_height);
+ g_free(tmp);
+ }
+ if (dst_bits >= s->vga.vram_ptr + s->vga.vbe_start_addr &&
+ dst_bits < s->vga.vram_ptr + s->vga.vbe_start_addr +
+ s->vga.vbe_regs[VBE_DISPI_INDEX_YRES] * s->vga.vbe_line_offset) {
+ memory_region_set_dirty(&s->vga.vram, s->vga.vbe_start_addr +
+ s->regs.dst_offset +
+ dst_y * surface_stride(ds),
+ s->regs.dst_height * surface_stride(ds));
+ }
+ s->regs.dst_x = (s->regs.dp_cntl & DST_X_LEFT_TO_RIGHT ?
+ dst_x + s->regs.dst_width : dst_x);
+ s->regs.dst_y = (s->regs.dp_cntl & DST_Y_TOP_TO_BOTTOM ?
+ dst_y + s->regs.dst_height : dst_y);
+ break;
+ }
+ case ROP3_PATCOPY:
+ case ROP3_BLACKNESS:
+ case ROP3_WHITENESS:
+ {
+ uint32_t filler = 0;
+
+ switch (s->regs.dp_mix & GMC_ROP3_MASK) {
+ case ROP3_PATCOPY:
+ filler = s->regs.dp_brush_frgd_clr;
+ break;
+ case ROP3_BLACKNESS:
+ filler = 0xffUL << 24 | rgb_to_pixel32(s->vga.palette[0],
+ s->vga.palette[1], s->vga.palette[2]);
+ break;
+ case ROP3_WHITENESS:
+ filler = 0xffUL << 24 | rgb_to_pixel32(s->vga.palette[3],
+ s->vga.palette[4], s->vga.palette[5]);
+ break;
+ }
+
+ dst_stride /= sizeof(uint32_t);
+ DPRINTF("pixman_fill(%p, %d, %d, %d, %d, %d, %d, %x)\n",
+ dst_bits, dst_stride, bpp,
+ s->regs.dst_x, s->regs.dst_y,
+ s->regs.dst_width, s->regs.dst_height,
+ filler);
+ pixman_fill((uint32_t *)dst_bits, dst_stride, bpp,
+ s->regs.dst_x, s->regs.dst_y,
+ s->regs.dst_width, s->regs.dst_height,
+ filler);
+ if (dst_bits >= s->vga.vram_ptr + s->vga.vbe_start_addr &&
+ dst_bits < s->vga.vram_ptr + s->vga.vbe_start_addr +
+ s->vga.vbe_regs[VBE_DISPI_INDEX_YRES] * s->vga.vbe_line_offset) {
+ memory_region_set_dirty(&s->vga.vram, s->vga.vbe_start_addr +
+ s->regs.dst_offset +
+ dst_y * surface_stride(ds),
+ s->regs.dst_height * surface_stride(ds));
+ }
+ s->regs.dst_y = (s->regs.dp_cntl & DST_Y_TOP_TO_BOTTOM ?
+ dst_y + s->regs.dst_height : dst_y);
+ break;
+ }
+ default:
+ qemu_log_mask(LOG_UNIMP, "Unimplemented ati_2d blt op %x\n",
+ (s->regs.dp_mix & GMC_ROP3_MASK) >> 16);
+ }
+}
diff --git a/hw/display/ati_dbg.c b/hw/display/ati_dbg.c
new file mode 100644
index 000000000..bd0ecd48c
--- /dev/null
+++ b/hw/display/ati_dbg.c
@@ -0,0 +1,273 @@
+#include "qemu/osdep.h"
+#include "ati_int.h"
+
+#ifdef DEBUG_ATI
+struct ati_regdesc {
+ const char *name;
+ int num;
+};
+
+static struct ati_regdesc ati_reg_names[] = {
+ {"MM_INDEX", 0x0000},
+ {"MM_DATA", 0x0004},
+ {"CLOCK_CNTL_INDEX", 0x0008},
+ {"CLOCK_CNTL_DATA", 0x000c},
+ {"BIOS_0_SCRATCH", 0x0010},
+ {"BUS_CNTL", 0x0030},
+ {"BUS_CNTL1", 0x0034},
+ {"GEN_INT_CNTL", 0x0040},
+ {"GEN_INT_STATUS", 0x0044},
+ {"CRTC_GEN_CNTL", 0x0050},
+ {"CRTC_EXT_CNTL", 0x0054},
+ {"DAC_CNTL", 0x0058},
+ {"GPIO_VGA_DDC", 0x0060},
+ {"GPIO_DVI_DDC", 0x0064},
+ {"GPIO_MONID", 0x0068},
+ {"I2C_CNTL_1", 0x0094},
+ {"AMCGPIO_MASK_MIR", 0x009c},
+ {"AMCGPIO_A_MIR", 0x00a0},
+ {"AMCGPIO_Y_MIR", 0x00a4},
+ {"AMCGPIO_EN_MIR", 0x00a8},
+ {"PALETTE_INDEX", 0x00b0},
+ {"PALETTE_DATA", 0x00b4},
+ {"CNFG_CNTL", 0x00e0},
+ {"GEN_RESET_CNTL", 0x00f0},
+ {"CNFG_MEMSIZE", 0x00f8},
+ {"CONFIG_APER_0_BASE", 0x0100},
+ {"CONFIG_APER_1_BASE", 0x0104},
+ {"CONFIG_APER_SIZE", 0x0108},
+ {"CONFIG_REG_1_BASE", 0x010c},
+ {"CONFIG_REG_APER_SIZE", 0x0110},
+ {"MEM_CNTL", 0x0140},
+ {"MC_FB_LOCATION", 0x0148},
+ {"MC_AGP_LOCATION", 0x014C},
+ {"MC_STATUS", 0x0150},
+ {"MEM_SDRAM_MODE_REG", 0x0158},
+ {"MEM_POWER_MISC", 0x015c},
+ {"AGP_BASE", 0x0170},
+ {"AGP_CNTL", 0x0174},
+ {"AGP_APER_OFFSET", 0x0178},
+ {"PCI_GART_PAGE", 0x017c},
+ {"PC_NGUI_MODE", 0x0180},
+ {"PC_NGUI_CTLSTAT", 0x0184},
+ {"MPP_TB_CONFIG", 0x01C0},
+ {"MPP_GP_CONFIG", 0x01C8},
+ {"VIPH_CONTROL", 0x01D0},
+ {"CRTC_H_TOTAL_DISP", 0x0200},
+ {"CRTC_H_SYNC_STRT_WID", 0x0204},
+ {"CRTC_V_TOTAL_DISP", 0x0208},
+ {"CRTC_V_SYNC_STRT_WID", 0x020c},
+ {"CRTC_VLINE_CRNT_VLINE", 0x0210},
+ {"CRTC_CRNT_FRAME", 0x0214},
+ {"CRTC_GUI_TRIG_VLINE", 0x0218},
+ {"CRTC_OFFSET", 0x0224},
+ {"CRTC_OFFSET_CNTL", 0x0228},
+ {"CRTC_PITCH", 0x022c},
+ {"OVR_CLR", 0x0230},
+ {"OVR_WID_LEFT_RIGHT", 0x0234},
+ {"OVR_WID_TOP_BOTTOM", 0x0238},
+ {"CUR_OFFSET", 0x0260},
+ {"CUR_HORZ_VERT_POSN", 0x0264},
+ {"CUR_HORZ_VERT_OFF", 0x0268},
+ {"CUR_CLR0", 0x026c},
+ {"CUR_CLR1", 0x0270},
+ {"LVDS_GEN_CNTL", 0x02d0},
+ {"DDA_CONFIG", 0x02e0},
+ {"DDA_ON_OFF", 0x02e4},
+ {"VGA_DDA_CONFIG", 0x02e8},
+ {"VGA_DDA_ON_OFF", 0x02ec},
+ {"CRTC2_H_TOTAL_DISP", 0x0300},
+ {"CRTC2_H_SYNC_STRT_WID", 0x0304},
+ {"CRTC2_V_TOTAL_DISP", 0x0308},
+ {"CRTC2_V_SYNC_STRT_WID", 0x030c},
+ {"CRTC2_VLINE_CRNT_VLINE", 0x0310},
+ {"CRTC2_CRNT_FRAME", 0x0314},
+ {"CRTC2_GUI_TRIG_VLINE", 0x0318},
+ {"CRTC2_OFFSET", 0x0324},
+ {"CRTC2_OFFSET_CNTL", 0x0328},
+ {"CRTC2_PITCH", 0x032c},
+ {"DDA2_CONFIG", 0x03e0},
+ {"DDA2_ON_OFF", 0x03e4},
+ {"CRTC2_GEN_CNTL", 0x03f8},
+ {"CRTC2_STATUS", 0x03fc},
+ {"OV0_SCALE_CNTL", 0x0420},
+ {"SUBPIC_CNTL", 0x0540},
+ {"PM4_BUFFER_OFFSET", 0x0700},
+ {"PM4_BUFFER_CNTL", 0x0704},
+ {"PM4_BUFFER_WM_CNTL", 0x0708},
+ {"PM4_BUFFER_DL_RPTR_ADDR", 0x070c},
+ {"PM4_BUFFER_DL_RPTR", 0x0710},
+ {"PM4_BUFFER_DL_WPTR", 0x0714},
+ {"PM4_VC_FPU_SETUP", 0x071c},
+ {"PM4_FPU_CNTL", 0x0720},
+ {"PM4_VC_FORMAT", 0x0724},
+ {"PM4_VC_CNTL", 0x0728},
+ {"PM4_VC_I01", 0x072c},
+ {"PM4_VC_VLOFF", 0x0730},
+ {"PM4_VC_VLSIZE", 0x0734},
+ {"PM4_IW_INDOFF", 0x0738},
+ {"PM4_IW_INDSIZE", 0x073c},
+ {"PM4_FPU_FPX0", 0x0740},
+ {"PM4_FPU_FPY0", 0x0744},
+ {"PM4_FPU_FPX1", 0x0748},
+ {"PM4_FPU_FPY1", 0x074c},
+ {"PM4_FPU_FPX2", 0x0750},
+ {"PM4_FPU_FPY2", 0x0754},
+ {"PM4_FPU_FPY3", 0x0758},
+ {"PM4_FPU_FPY4", 0x075c},
+ {"PM4_FPU_FPY5", 0x0760},
+ {"PM4_FPU_FPY6", 0x0764},
+ {"PM4_FPU_FPR", 0x0768},
+ {"PM4_FPU_FPG", 0x076c},
+ {"PM4_FPU_FPB", 0x0770},
+ {"PM4_FPU_FPA", 0x0774},
+ {"PM4_FPU_INTXY0", 0x0780},
+ {"PM4_FPU_INTXY1", 0x0784},
+ {"PM4_FPU_INTXY2", 0x0788},
+ {"PM4_FPU_INTARGB", 0x078c},
+ {"PM4_FPU_FPTWICEAREA", 0x0790},
+ {"PM4_FPU_DMAJOR01", 0x0794},
+ {"PM4_FPU_DMAJOR12", 0x0798},
+ {"PM4_FPU_DMAJOR02", 0x079c},
+ {"PM4_FPU_STAT", 0x07a0},
+ {"PM4_STAT", 0x07b8},
+ {"PM4_TEST_CNTL", 0x07d0},
+ {"PM4_MICROCODE_ADDR", 0x07d4},
+ {"PM4_MICROCODE_RADDR", 0x07d8},
+ {"PM4_MICROCODE_DATAH", 0x07dc},
+ {"PM4_MICROCODE_DATAL", 0x07e0},
+ {"PM4_CMDFIFO_ADDR", 0x07e4},
+ {"PM4_CMDFIFO_DATAH", 0x07e8},
+ {"PM4_CMDFIFO_DATAL", 0x07ec},
+ {"PM4_BUFFER_ADDR", 0x07f0},
+ {"PM4_BUFFER_DATAH", 0x07f4},
+ {"PM4_BUFFER_DATAL", 0x07f8},
+ {"PM4_MICRO_CNTL", 0x07fc},
+ {"CAP0_TRIG_CNTL", 0x0950},
+ {"CAP1_TRIG_CNTL", 0x09c0},
+ {"RBBM_STATUS", 0x0e40},
+ {"PM4_FIFO_DATA_EVEN", 0x1000},
+ {"PM4_FIFO_DATA_ODD", 0x1004},
+ {"DST_OFFSET", 0x1404},
+ {"DST_PITCH", 0x1408},
+ {"DST_WIDTH", 0x140c},
+ {"DST_HEIGHT", 0x1410},
+ {"SRC_X", 0x1414},
+ {"SRC_Y", 0x1418},
+ {"DST_X", 0x141c},
+ {"DST_Y", 0x1420},
+ {"SRC_PITCH_OFFSET", 0x1428},
+ {"DST_PITCH_OFFSET", 0x142c},
+ {"SRC_Y_X", 0x1434},
+ {"DST_Y_X", 0x1438},
+ {"DST_HEIGHT_WIDTH", 0x143c},
+ {"DP_GUI_MASTER_CNTL", 0x146c},
+ {"BRUSH_SCALE", 0x1470},
+ {"BRUSH_Y_X", 0x1474},
+ {"DP_BRUSH_BKGD_CLR", 0x1478},
+ {"DP_BRUSH_FRGD_CLR", 0x147c},
+ {"DST_WIDTH_X", 0x1588},
+ {"DST_HEIGHT_WIDTH_8", 0x158c},
+ {"SRC_X_Y", 0x1590},
+ {"DST_X_Y", 0x1594},
+ {"DST_WIDTH_HEIGHT", 0x1598},
+ {"DST_WIDTH_X_INCY", 0x159c},
+ {"DST_HEIGHT_Y", 0x15a0},
+ {"DST_X_SUB", 0x15a4},
+ {"DST_Y_SUB", 0x15a8},
+ {"SRC_OFFSET", 0x15ac},
+ {"SRC_PITCH", 0x15b0},
+ {"DST_HEIGHT_WIDTH_BW", 0x15b4},
+ {"CLR_CMP_CNTL", 0x15c0},
+ {"CLR_CMP_CLR_SRC", 0x15c4},
+ {"CLR_CMP_CLR_DST", 0x15c8},
+ {"CLR_CMP_MASK", 0x15cc},
+ {"DP_SRC_FRGD_CLR", 0x15d8},
+ {"DP_SRC_BKGD_CLR", 0x15dc},
+ {"DST_BRES_ERR", 0x1628},
+ {"DST_BRES_INC", 0x162c},
+ {"DST_BRES_DEC", 0x1630},
+ {"DST_BRES_LNTH", 0x1634},
+ {"DST_BRES_LNTH_SUB", 0x1638},
+ {"SC_LEFT", 0x1640},
+ {"SC_RIGHT", 0x1644},
+ {"SC_TOP", 0x1648},
+ {"SC_BOTTOM", 0x164c},
+ {"SRC_SC_RIGHT", 0x1654},
+ {"SRC_SC_BOTTOM", 0x165c},
+ {"GUI_DEBUG0", 0x16a0},
+ {"GUI_DEBUG1", 0x16a4},
+ {"GUI_TIMEOUT", 0x16b0},
+ {"GUI_TIMEOUT0", 0x16b4},
+ {"GUI_TIMEOUT1", 0x16b8},
+ {"GUI_PROBE", 0x16bc},
+ {"DP_CNTL", 0x16c0},
+ {"DP_DATATYPE", 0x16c4},
+ {"DP_MIX", 0x16c8},
+ {"DP_WRITE_MASK", 0x16cc},
+ {"DP_CNTL_XDIR_YDIR_YMAJOR", 0x16d0},
+ {"DEFAULT_OFFSET", 0x16e0},
+ {"DEFAULT_PITCH", 0x16e4},
+ {"DEFAULT_SC_BOTTOM_RIGHT", 0x16e8},
+ {"SC_TOP_LEFT", 0x16ec},
+ {"SC_BOTTOM_RIGHT", 0x16f0},
+ {"SRC_SC_BOTTOM_RIGHT", 0x16f4},
+ {"DST_TILE", 0x1700},
+ {"WAIT_UNTIL", 0x1720},
+ {"CACHE_CNTL", 0x1724},
+ {"GUI_STAT", 0x1740},
+ {"PC_GUI_MODE", 0x1744},
+ {"PC_GUI_CTLSTAT", 0x1748},
+ {"PC_DEBUG_MODE", 0x1760},
+ {"BRES_DST_ERR_DEC", 0x1780},
+ {"TRAIL_BRES_T12_ERR_DEC", 0x1784},
+ {"TRAIL_BRES_T12_INC", 0x1788},
+ {"DP_T12_CNTL", 0x178c},
+ {"DST_BRES_T1_LNTH", 0x1790},
+ {"DST_BRES_T2_LNTH", 0x1794},
+ {"SCALE_SRC_HEIGHT_WIDTH", 0x1994},
+ {"SCALE_OFFSET_0", 0x1998},
+ {"SCALE_PITCH", 0x199c},
+ {"SCALE_X_INC", 0x19a0},
+ {"SCALE_Y_INC", 0x19a4},
+ {"SCALE_HACC", 0x19a8},
+ {"SCALE_VACC", 0x19ac},
+ {"SCALE_DST_X_Y", 0x19b0},
+ {"SCALE_DST_HEIGHT_WIDTH", 0x19b4},
+ {"SCALE_3D_CNTL", 0x1a00},
+ {"SCALE_3D_DATATYPE", 0x1a20},
+ {"SETUP_CNTL", 0x1bc4},
+ {"SOLID_COLOR", 0x1bc8},
+ {"WINDOW_XY_OFFSET", 0x1bcc},
+ {"DRAW_LINE_POINT", 0x1bd0},
+ {"SETUP_CNTL_PM4", 0x1bd4},
+ {"DST_PITCH_OFFSET_C", 0x1c80},
+ {"DP_GUI_MASTER_CNTL_C", 0x1c84},
+ {"SC_TOP_LEFT_C", 0x1c88},
+ {"SC_BOTTOM_RIGHT_C", 0x1c8c},
+ {"CLR_CMP_MASK_3D", 0x1A28},
+ {"MISC_3D_STATE_CNTL_REG", 0x1CA0},
+ {"MC_SRC1_CNTL", 0x19D8},
+ {"TEX_CNTL", 0x1800},
+ {"RAGE128_MPP_TB_CONFIG", 0x01c0},
+ {NULL, -1}
+};
+
+const char *ati_reg_name(int num)
+{
+ int i;
+
+ num &= ~3;
+ for (i = 0; ati_reg_names[i].name; i++) {
+ if (ati_reg_names[i].num == num) {
+ return ati_reg_names[i].name;
+ }
+ }
+ return "unknown";
+}
+#else
+const char *ati_reg_name(int num)
+{
+ return "";
+}
+#endif
diff --git a/hw/display/ati_int.h b/hw/display/ati_int.h
new file mode 100644
index 000000000..8acb9c746
--- /dev/null
+++ b/hw/display/ati_int.h
@@ -0,0 +1,107 @@
+/*
+ * QEMU ATI SVGA emulation
+ *
+ * Copyright (c) 2019 BALATON Zoltan
+ *
+ * This work is licensed under the GNU GPL license version 2 or later.
+ */
+
+#ifndef ATI_INT_H
+#define ATI_INT_H
+
+#include "qemu/timer.h"
+#include "hw/pci/pci.h"
+#include "hw/i2c/bitbang_i2c.h"
+#include "vga_int.h"
+#include "qom/object.h"
+
+/*#define DEBUG_ATI*/
+
+#ifdef DEBUG_ATI
+#define DPRINTF(fmt, ...) printf("%s: " fmt, __func__, ## __VA_ARGS__)
+#else
+#define DPRINTF(fmt, ...) do {} while (0)
+#endif
+
+#define PCI_VENDOR_ID_ATI 0x1002
+/* Rage128 Pro GL */
+#define PCI_DEVICE_ID_ATI_RAGE128_PF 0x5046
+/* Radeon RV100 (VE) */
+#define PCI_DEVICE_ID_ATI_RADEON_QY 0x5159
+
+#define TYPE_ATI_VGA "ati-vga"
+OBJECT_DECLARE_SIMPLE_TYPE(ATIVGAState, ATI_VGA)
+
+typedef struct ATIVGARegs {
+ uint32_t mm_index;
+ uint32_t bios_scratch[8];
+ uint32_t gen_int_cntl;
+ uint32_t gen_int_status;
+ uint32_t crtc_gen_cntl;
+ uint32_t crtc_ext_cntl;
+ uint32_t dac_cntl;
+ uint32_t gpio_vga_ddc;
+ uint32_t gpio_dvi_ddc;
+ uint32_t gpio_monid;
+ uint32_t config_cntl;
+ uint32_t crtc_h_total_disp;
+ uint32_t crtc_h_sync_strt_wid;
+ uint32_t crtc_v_total_disp;
+ uint32_t crtc_v_sync_strt_wid;
+ uint32_t crtc_offset;
+ uint32_t crtc_offset_cntl;
+ uint32_t crtc_pitch;
+ uint32_t cur_offset;
+ uint32_t cur_hv_pos;
+ uint32_t cur_hv_offs;
+ uint32_t cur_color0;
+ uint32_t cur_color1;
+ uint32_t dst_offset;
+ uint32_t dst_pitch;
+ uint32_t dst_tile;
+ uint32_t dst_width;
+ uint32_t dst_height;
+ uint32_t src_offset;
+ uint32_t src_pitch;
+ uint32_t src_tile;
+ uint32_t src_x;
+ uint32_t src_y;
+ uint32_t dst_x;
+ uint32_t dst_y;
+ uint32_t dp_gui_master_cntl;
+ uint32_t dp_brush_bkgd_clr;
+ uint32_t dp_brush_frgd_clr;
+ uint32_t dp_src_frgd_clr;
+ uint32_t dp_src_bkgd_clr;
+ uint32_t dp_cntl;
+ uint32_t dp_datatype;
+ uint32_t dp_mix;
+ uint32_t dp_write_mask;
+ uint32_t default_offset;
+ uint32_t default_pitch;
+ uint32_t default_tile;
+ uint32_t default_sc_bottom_right;
+} ATIVGARegs;
+
+struct ATIVGAState {
+ PCIDevice dev;
+ VGACommonState vga;
+ char *model;
+ uint16_t dev_id;
+ uint8_t mode;
+ bool cursor_guest_mode;
+ uint16_t cursor_size;
+ uint32_t cursor_offset;
+ QEMUCursor *cursor;
+ QEMUTimer vblank_timer;
+ bitbang_i2c_interface bbi2c;
+ MemoryRegion io;
+ MemoryRegion mm;
+ ATIVGARegs regs;
+};
+
+const char *ati_reg_name(int num);
+
+void ati_2d_blt(ATIVGAState *s);
+
+#endif /* ATI_INT_H */
diff --git a/hw/display/ati_regs.h b/hw/display/ati_regs.h
new file mode 100644
index 000000000..d6282b2ef
--- /dev/null
+++ b/hw/display/ati_regs.h
@@ -0,0 +1,481 @@
+/*
+ * ATI VGA register definitions
+ *
+ * based on:
+ * linux/include/video/aty128.h
+ * Register definitions for ATI Rage128 boards
+ * Anthony Tong <atong@uiuc.edu>, 1999
+ * Brad Douglas <brad@neruo.com>, 2000
+ *
+ * and linux/include/video/radeon.h
+ *
+ * This work is licensed under the GNU GPL license version 2.
+ */
+
+/*
+ * Register mapping:
+ * 0x0000-0x00ff Misc regs also accessible via io and mmio space
+ * 0x0100-0x0eff Misc regs only accessible via mmio
+ * 0x0f00-0x0fff Read-only copy of PCI config regs
+ * 0x1000-0x13ff Concurrent Command Engine (CCE) regs
+ * 0x1400-0x1fff GUI (drawing engine) regs
+ */
+
+#ifndef ATI_REGS_H
+#define ATI_REGS_H
+
+#undef DEFAULT_PITCH /* needed for mingw builds */
+
+#define MM_INDEX 0x0000
+#define MM_DATA 0x0004
+#define CLOCK_CNTL_INDEX 0x0008
+#define CLOCK_CNTL_DATA 0x000c
+#define BIOS_0_SCRATCH 0x0010
+#define BUS_CNTL 0x0030
+#define BUS_CNTL1 0x0034
+#define GEN_INT_CNTL 0x0040
+#define GEN_INT_STATUS 0x0044
+#define CRTC_GEN_CNTL 0x0050
+#define CRTC_EXT_CNTL 0x0054
+#define DAC_CNTL 0x0058
+#define GPIO_VGA_DDC 0x0060
+#define GPIO_DVI_DDC 0x0064
+#define GPIO_MONID 0x0068
+#define I2C_CNTL_1 0x0094
+#define AMCGPIO_MASK_MIR 0x009c
+#define AMCGPIO_A_MIR 0x00a0
+#define AMCGPIO_Y_MIR 0x00a4
+#define AMCGPIO_EN_MIR 0x00a8
+#define PALETTE_INDEX 0x00b0
+#define PALETTE_DATA 0x00b4
+#define CNFG_CNTL 0x00e0
+#define GEN_RESET_CNTL 0x00f0
+#define CNFG_MEMSIZE 0x00f8
+#define CONFIG_APER_0_BASE 0x0100
+#define CONFIG_APER_1_BASE 0x0104
+#define CONFIG_APER_SIZE 0x0108
+#define CONFIG_REG_1_BASE 0x010c
+#define CONFIG_REG_APER_SIZE 0x0110
+#define MEM_CNTL 0x0140
+#define MC_FB_LOCATION 0x0148
+#define MC_AGP_LOCATION 0x014C
+#define MC_STATUS 0x0150
+#define MEM_SDRAM_MODE_REG 0x0158
+#define MEM_POWER_MISC 0x015c
+#define AGP_BASE 0x0170
+#define AGP_CNTL 0x0174
+#define AGP_APER_OFFSET 0x0178
+#define PCI_GART_PAGE 0x017c
+#define PC_NGUI_MODE 0x0180
+#define PC_NGUI_CTLSTAT 0x0184
+#define MPP_TB_CONFIG 0x01C0
+#define MPP_GP_CONFIG 0x01C8
+#define VIPH_CONTROL 0x01D0
+#define CRTC_H_TOTAL_DISP 0x0200
+#define CRTC_H_SYNC_STRT_WID 0x0204
+#define CRTC_V_TOTAL_DISP 0x0208
+#define CRTC_V_SYNC_STRT_WID 0x020c
+#define CRTC_VLINE_CRNT_VLINE 0x0210
+#define CRTC_CRNT_FRAME 0x0214
+#define CRTC_GUI_TRIG_VLINE 0x0218
+#define CRTC_OFFSET 0x0224
+#define CRTC_OFFSET_CNTL 0x0228
+#define CRTC_PITCH 0x022c
+#define OVR_CLR 0x0230
+#define OVR_WID_LEFT_RIGHT 0x0234
+#define OVR_WID_TOP_BOTTOM 0x0238
+#define CUR_OFFSET 0x0260
+#define CUR_HORZ_VERT_POSN 0x0264
+#define CUR_HORZ_VERT_OFF 0x0268
+#define CUR_CLR0 0x026c
+#define CUR_CLR1 0x0270
+#define LVDS_GEN_CNTL 0x02d0
+#define DDA_CONFIG 0x02e0
+#define DDA_ON_OFF 0x02e4
+#define VGA_DDA_CONFIG 0x02e8
+#define VGA_DDA_ON_OFF 0x02ec
+#define CRTC2_H_TOTAL_DISP 0x0300
+#define CRTC2_H_SYNC_STRT_WID 0x0304
+#define CRTC2_V_TOTAL_DISP 0x0308
+#define CRTC2_V_SYNC_STRT_WID 0x030c
+#define CRTC2_VLINE_CRNT_VLINE 0x0310
+#define CRTC2_CRNT_FRAME 0x0314
+#define CRTC2_GUI_TRIG_VLINE 0x0318
+#define CRTC2_OFFSET 0x0324
+#define CRTC2_OFFSET_CNTL 0x0328
+#define CRTC2_PITCH 0x032c
+#define DDA2_CONFIG 0x03e0
+#define DDA2_ON_OFF 0x03e4
+#define CRTC2_GEN_CNTL 0x03f8
+#define CRTC2_STATUS 0x03fc
+#define OV0_SCALE_CNTL 0x0420
+#define SUBPIC_CNTL 0x0540
+#define PM4_BUFFER_OFFSET 0x0700
+#define PM4_BUFFER_CNTL 0x0704
+#define PM4_BUFFER_WM_CNTL 0x0708
+#define PM4_BUFFER_DL_RPTR_ADDR 0x070c
+#define PM4_BUFFER_DL_RPTR 0x0710
+#define PM4_BUFFER_DL_WPTR 0x0714
+#define PM4_VC_FPU_SETUP 0x071c
+#define PM4_FPU_CNTL 0x0720
+#define PM4_VC_FORMAT 0x0724
+#define PM4_VC_CNTL 0x0728
+#define PM4_VC_I01 0x072c
+#define PM4_VC_VLOFF 0x0730
+#define PM4_VC_VLSIZE 0x0734
+#define PM4_IW_INDOFF 0x0738
+#define PM4_IW_INDSIZE 0x073c
+#define PM4_FPU_FPX0 0x0740
+#define PM4_FPU_FPY0 0x0744
+#define PM4_FPU_FPX1 0x0748
+#define PM4_FPU_FPY1 0x074c
+#define PM4_FPU_FPX2 0x0750
+#define PM4_FPU_FPY2 0x0754
+#define PM4_FPU_FPY3 0x0758
+#define PM4_FPU_FPY4 0x075c
+#define PM4_FPU_FPY5 0x0760
+#define PM4_FPU_FPY6 0x0764
+#define PM4_FPU_FPR 0x0768
+#define PM4_FPU_FPG 0x076c
+#define PM4_FPU_FPB 0x0770
+#define PM4_FPU_FPA 0x0774
+#define PM4_FPU_INTXY0 0x0780
+#define PM4_FPU_INTXY1 0x0784
+#define PM4_FPU_INTXY2 0x0788
+#define PM4_FPU_INTARGB 0x078c
+#define PM4_FPU_FPTWICEAREA 0x0790
+#define PM4_FPU_DMAJOR01 0x0794
+#define PM4_FPU_DMAJOR12 0x0798
+#define PM4_FPU_DMAJOR02 0x079c
+#define PM4_FPU_STAT 0x07a0
+#define PM4_STAT 0x07b8
+#define PM4_TEST_CNTL 0x07d0
+#define PM4_MICROCODE_ADDR 0x07d4
+#define PM4_MICROCODE_RADDR 0x07d8
+#define PM4_MICROCODE_DATAH 0x07dc
+#define PM4_MICROCODE_DATAL 0x07e0
+#define PM4_CMDFIFO_ADDR 0x07e4
+#define PM4_CMDFIFO_DATAH 0x07e8
+#define PM4_CMDFIFO_DATAL 0x07ec
+#define PM4_BUFFER_ADDR 0x07f0
+#define PM4_BUFFER_DATAH 0x07f4
+#define PM4_BUFFER_DATAL 0x07f8
+#define PM4_MICRO_CNTL 0x07fc
+#define CAP0_TRIG_CNTL 0x0950
+#define CAP1_TRIG_CNTL 0x09c0
+
+#define RBBM_STATUS 0x0e40
+
+/*
+ * GUI Block Memory Mapped Registers
+ * These registers are FIFOed.
+ */
+#define PM4_FIFO_DATA_EVEN 0x1000
+#define PM4_FIFO_DATA_ODD 0x1004
+
+#define DST_OFFSET 0x1404
+#define DST_PITCH 0x1408
+#define DST_WIDTH 0x140c
+#define DST_HEIGHT 0x1410
+#define SRC_X 0x1414
+#define SRC_Y 0x1418
+#define DST_X 0x141c
+#define DST_Y 0x1420
+#define SRC_PITCH_OFFSET 0x1428
+#define DST_PITCH_OFFSET 0x142c
+#define SRC_Y_X 0x1434
+#define DST_Y_X 0x1438
+#define DST_HEIGHT_WIDTH 0x143c
+#define DP_GUI_MASTER_CNTL 0x146c
+#define BRUSH_SCALE 0x1470
+#define BRUSH_Y_X 0x1474
+#define DP_BRUSH_BKGD_CLR 0x1478
+#define DP_BRUSH_FRGD_CLR 0x147c
+#define DST_WIDTH_X 0x1588
+#define DST_HEIGHT_WIDTH_8 0x158c
+#define SRC_X_Y 0x1590
+#define DST_X_Y 0x1594
+#define DST_WIDTH_HEIGHT 0x1598
+#define DST_WIDTH_X_INCY 0x159c
+#define DST_HEIGHT_Y 0x15a0
+#define DST_X_SUB 0x15a4
+#define DST_Y_SUB 0x15a8
+#define SRC_OFFSET 0x15ac
+#define SRC_PITCH 0x15b0
+#define DST_HEIGHT_WIDTH_BW 0x15b4
+#define CLR_CMP_CNTL 0x15c0
+#define CLR_CMP_CLR_SRC 0x15c4
+#define CLR_CMP_CLR_DST 0x15c8
+#define CLR_CMP_MASK 0x15cc
+#define DP_SRC_FRGD_CLR 0x15d8
+#define DP_SRC_BKGD_CLR 0x15dc
+#define DST_BRES_ERR 0x1628
+#define DST_BRES_INC 0x162c
+#define DST_BRES_DEC 0x1630
+#define DST_BRES_LNTH 0x1634
+#define DST_BRES_LNTH_SUB 0x1638
+#define SC_LEFT 0x1640
+#define SC_RIGHT 0x1644
+#define SC_TOP 0x1648
+#define SC_BOTTOM 0x164c
+#define SRC_SC_RIGHT 0x1654
+#define SRC_SC_BOTTOM 0x165c
+#define GUI_DEBUG0 0x16a0
+#define GUI_DEBUG1 0x16a4
+#define GUI_TIMEOUT 0x16b0
+#define GUI_TIMEOUT0 0x16b4
+#define GUI_TIMEOUT1 0x16b8
+#define GUI_PROBE 0x16bc
+#define DP_CNTL 0x16c0
+#define DP_DATATYPE 0x16c4
+#define DP_MIX 0x16c8
+#define DP_WRITE_MASK 0x16cc
+#define DP_CNTL_XDIR_YDIR_YMAJOR 0x16d0
+#define DEFAULT_OFFSET 0x16e0
+#define DEFAULT_PITCH 0x16e4
+#define DEFAULT_SC_BOTTOM_RIGHT 0x16e8
+#define SC_TOP_LEFT 0x16ec
+#define SC_BOTTOM_RIGHT 0x16f0
+#define SRC_SC_BOTTOM_RIGHT 0x16f4
+#define DST_TILE 0x1700
+#define WAIT_UNTIL 0x1720
+#define CACHE_CNTL 0x1724
+#define GUI_STAT 0x1740
+#define PC_GUI_MODE 0x1744
+#define PC_GUI_CTLSTAT 0x1748
+#define PC_DEBUG_MODE 0x1760
+#define BRES_DST_ERR_DEC 0x1780
+#define TRAIL_BRES_T12_ERR_DEC 0x1784
+#define TRAIL_BRES_T12_INC 0x1788
+#define DP_T12_CNTL 0x178c
+#define DST_BRES_T1_LNTH 0x1790
+#define DST_BRES_T2_LNTH 0x1794
+#define SCALE_SRC_HEIGHT_WIDTH 0x1994
+#define SCALE_OFFSET_0 0x1998
+#define SCALE_PITCH 0x199c
+#define SCALE_X_INC 0x19a0
+#define SCALE_Y_INC 0x19a4
+#define SCALE_HACC 0x19a8
+#define SCALE_VACC 0x19ac
+#define SCALE_DST_X_Y 0x19b0
+#define SCALE_DST_HEIGHT_WIDTH 0x19b4
+#define SCALE_3D_CNTL 0x1a00
+#define SCALE_3D_DATATYPE 0x1a20
+#define SETUP_CNTL 0x1bc4
+#define SOLID_COLOR 0x1bc8
+#define WINDOW_XY_OFFSET 0x1bcc
+#define DRAW_LINE_POINT 0x1bd0
+#define SETUP_CNTL_PM4 0x1bd4
+#define DST_PITCH_OFFSET_C 0x1c80
+#define DP_GUI_MASTER_CNTL_C 0x1c84
+#define SC_TOP_LEFT_C 0x1c88
+#define SC_BOTTOM_RIGHT_C 0x1c8c
+
+#define CLR_CMP_MASK_3D 0x1A28
+#define MISC_3D_STATE_CNTL_REG 0x1CA0
+#define MC_SRC1_CNTL 0x19D8
+#define TEX_CNTL 0x1800
+
+/* CONSTANTS */
+#define GUI_ACTIVE 0x80000000
+#define ENGINE_IDLE 0x0
+
+#define PLL_WR_EN 0x00000080
+
+#define CLK_PIN_CNTL 0x01
+#define PPLL_CNTL 0x02
+#define PPLL_REF_DIV 0x03
+#define PPLL_DIV_0 0x04
+#define PPLL_DIV_1 0x05
+#define PPLL_DIV_2 0x06
+#define PPLL_DIV_3 0x07
+#define VCLK_ECP_CNTL 0x08
+#define HTOTAL_CNTL 0x09
+#define X_MPLL_REF_FB_DIV 0x0a
+#define XPLL_CNTL 0x0b
+#define XDLL_CNTL 0x0c
+#define XCLK_CNTL 0x0d
+#define MPLL_CNTL 0x0e
+#define MCLK_CNTL 0x0f
+#define AGP_PLL_CNTL 0x10
+#define FCP_CNTL 0x12
+#define PLL_TEST_CNTL 0x13
+#define P2PLL_CNTL 0x2a
+#define P2PLL_REF_DIV 0x2b
+#define P2PLL_DIV_0 0x2b
+#define POWER_MANAGEMENT 0x2f
+
+#define PPLL_RESET 0x00000001
+#define PPLL_ATOMIC_UPDATE_EN 0x00010000
+#define PPLL_VGA_ATOMIC_UPDATE_EN 0x00020000
+#define PPLL_REF_DIV_MASK 0x000003FF
+#define PPLL_FB3_DIV_MASK 0x000007FF
+#define PPLL_POST3_DIV_MASK 0x00070000
+#define PPLL_ATOMIC_UPDATE_R 0x00008000
+#define PPLL_ATOMIC_UPDATE_W 0x00008000
+#define MEM_CFG_TYPE_MASK 0x00000003
+#define XCLK_SRC_SEL_MASK 0x00000007
+#define XPLL_FB_DIV_MASK 0x0000FF00
+#define X_MPLL_REF_DIV_MASK 0x000000FF
+
+/* GEN_INT_CNTL) */
+#define CRTC_VBLANK_INT 0x00000001
+#define CRTC_VLINE_INT 0x00000002
+#define CRTC_VSYNC_INT 0x00000004
+
+/* Config control values (CONFIG_CNTL) */
+#define APER_0_ENDIAN 0x00000003
+#define APER_1_ENDIAN 0x0000000c
+#define CFG_VGA_IO_DIS 0x00000400
+
+/* CRTC control values (CRTC_GEN_CNTL) */
+#define CRTC_CSYNC_EN 0x00000010
+
+#define CRTC2_DBL_SCAN_EN 0x00000001
+#define CRTC2_DISPLAY_DIS 0x00800000
+#define CRTC2_FIFO_EXTSENSE 0x00200000
+#define CRTC2_ICON_EN 0x00100000
+#define CRTC2_CUR_EN 0x00010000
+#define CRTC2_EXT_DISP_EN 0x01000000
+#define CRTC2_EN 0x02000000
+#define CRTC2_DISP_REQ_EN_B 0x04000000
+
+#define CRTC_PIX_WIDTH_MASK 0x00000700
+#define CRTC_PIX_WIDTH_4BPP 0x00000100
+#define CRTC_PIX_WIDTH_8BPP 0x00000200
+#define CRTC_PIX_WIDTH_15BPP 0x00000300
+#define CRTC_PIX_WIDTH_16BPP 0x00000400
+#define CRTC_PIX_WIDTH_24BPP 0x00000500
+#define CRTC_PIX_WIDTH_32BPP 0x00000600
+
+/* DAC_CNTL bit constants */
+#define DAC_8BIT_EN 0x00000100
+#define DAC_MASK 0xFF000000
+#define DAC_BLANKING 0x00000004
+#define DAC_RANGE_CNTL 0x00000003
+#define DAC_CLK_SEL 0x00000010
+#define DAC_PALETTE_ACCESS_CNTL 0x00000020
+#define DAC_PALETTE2_SNOOP_EN 0x00000040
+#define DAC_PDWN 0x00008000
+
+/* CRTC_EXT_CNTL */
+#define CRT_CRTC_DISPLAY_DIS 0x00000400
+#define CRT_CRTC_ON 0x00008000
+
+/* GEN_RESET_CNTL bit constants */
+#define SOFT_RESET_GUI 0x00000001
+#define SOFT_RESET_VCLK 0x00000100
+#define SOFT_RESET_PCLK 0x00000200
+#define SOFT_RESET_ECP 0x00000400
+#define SOFT_RESET_DISPENG_XCLK 0x00000800
+
+/* PC_GUI_CTLSTAT bit constants */
+#define PC_BUSY_INIT 0x10000000
+#define PC_BUSY_GUI 0x20000000
+#define PC_BUSY_NGUI 0x40000000
+#define PC_BUSY 0x80000000
+
+#define BUS_MASTER_DIS 0x00000040
+#define PM4_BUFFER_CNTL_NONPM4 0x00000000
+
+/* DP_DATATYPE bit constants */
+#define DST_8BPP 0x00000002
+#define DST_15BPP 0x00000003
+#define DST_16BPP 0x00000004
+#define DST_24BPP 0x00000005
+#define DST_32BPP 0x00000006
+
+#define BRUSH_SOLIDCOLOR 0x00000d00
+
+/* DP_GUI_MASTER_CNTL bit constants */
+#define GMC_SRC_PITCH_OFFSET_CNTL 0x00000001
+#define GMC_DST_PITCH_OFFSET_CNTL 0x00000002
+#define GMC_SRC_CLIP_DEFAULT 0x00000000
+#define GMC_DST_CLIP_DEFAULT 0x00000000
+#define GMC_BRUSH_SOLIDCOLOR 0x000000d0
+#define GMC_SRC_DSTCOLOR 0x00003000
+#define GMC_BYTE_ORDER_MSB_TO_LSB 0x00000000
+#define GMC_DP_SRC_RECT 0x02000000
+#define GMC_3D_FCN_EN_CLR 0x00000000
+#define GMC_AUX_CLIP_CLEAR 0x20000000
+#define GMC_DST_CLR_CMP_FCN_CLEAR 0x10000000
+#define GMC_WRITE_MASK_SET 0x40000000
+#define GMC_DP_CONVERSION_TEMP_6500 0x00000000
+
+/* DP_GUI_MASTER_CNTL ROP3 named constants */
+#define GMC_ROP3_MASK 0x00ff0000
+#define ROP3_BLACKNESS 0x00000000
+#define ROP3_SRCCOPY 0x00cc0000
+#define ROP3_PATCOPY 0x00f00000
+#define ROP3_WHITENESS 0x00ff0000
+
+#define SRC_DSTCOLOR 0x00030000
+
+/* DP_CNTL bit constants */
+#define DST_X_RIGHT_TO_LEFT 0x00000000
+#define DST_X_LEFT_TO_RIGHT 0x00000001
+#define DST_Y_BOTTOM_TO_TOP 0x00000000
+#define DST_Y_TOP_TO_BOTTOM 0x00000002
+#define DST_X_MAJOR 0x00000000
+#define DST_Y_MAJOR 0x00000004
+#define DST_X_TILE 0x00000008
+#define DST_Y_TILE 0x00000010
+#define DST_LAST_PEL 0x00000020
+#define DST_TRAIL_X_RIGHT_TO_LEFT 0x00000000
+#define DST_TRAIL_X_LEFT_TO_RIGHT 0x00000040
+#define DST_TRAP_FILL_RIGHT_TO_LEFT 0x00000000
+#define DST_TRAP_FILL_LEFT_TO_RIGHT 0x00000080
+#define DST_BRES_SIGN 0x00000100
+#define DST_HOST_BIG_ENDIAN_EN 0x00000200
+#define DST_POLYLINE_NONLAST 0x00008000
+#define DST_RASTER_STALL 0x00010000
+#define DST_POLY_EDGE 0x00040000
+
+/* DP_MIX bit constants */
+#define DP_SRC_RECT 0x00000200
+#define DP_SRC_HOST 0x00000300
+#define DP_SRC_HOST_BYTEALIGN 0x00000400
+
+/* LVDS_GEN_CNTL constants */
+#define LVDS_BL_MOD_LEVEL_MASK 0x0000ff00
+#define LVDS_BL_MOD_LEVEL_SHIFT 8
+#define LVDS_BL_MOD_EN 0x00010000
+#define LVDS_DIGION 0x00040000
+#define LVDS_BLON 0x00080000
+#define LVDS_ON 0x00000001
+#define LVDS_DISPLAY_DIS 0x00000002
+#define LVDS_PANEL_TYPE_2PIX_PER_CLK 0x00000004
+#define LVDS_PANEL_24BITS_TFT 0x00000008
+#define LVDS_FRAME_MOD_NO 0x00000000
+#define LVDS_FRAME_MOD_2_LEVELS 0x00000010
+#define LVDS_FRAME_MOD_4_LEVELS 0x00000020
+#define LVDS_RST_FM 0x00000040
+#define LVDS_EN 0x00000080
+
+/* CRTC2_GEN_CNTL constants */
+#define CRTC2_EN 0x02000000
+
+/* POWER_MANAGEMENT constants */
+#define PWR_MGT_ON 0x00000001
+#define PWR_MGT_MODE_MASK 0x00000006
+#define PWR_MGT_MODE_PIN 0x00000000
+#define PWR_MGT_MODE_REGISTER 0x00000002
+#define PWR_MGT_MODE_TIMER 0x00000004
+#define PWR_MGT_MODE_PCI 0x00000006
+#define PWR_MGT_AUTO_PWR_UP_EN 0x00000008
+#define PWR_MGT_ACTIVITY_PIN_ON 0x00000010
+#define PWR_MGT_STANDBY_POL 0x00000020
+#define PWR_MGT_SUSPEND_POL 0x00000040
+#define PWR_MGT_SELF_REFRESH 0x00000080
+#define PWR_MGT_ACTIVITY_PIN_EN 0x00000100
+#define PWR_MGT_KEYBD_SNOOP 0x00000200
+#define PWR_MGT_TRISTATE_MEM_EN 0x00000800
+#define PWR_MGT_SELW4MS 0x00001000
+#define PWR_MGT_SLOWDOWN_MCLK 0x00002000
+
+#define PMI_PMSCR_REG 0x60
+
+/* used by ATI bug fix for hardware ROM */
+#define RAGE128_MPP_TB_CONFIG 0x01c0
+
+#endif /* ATI_REGS_H */
diff --git a/hw/display/bcm2835_fb.c b/hw/display/bcm2835_fb.c
new file mode 100644
index 000000000..2be77bdd3
--- /dev/null
+++ b/hw/display/bcm2835_fb.c
@@ -0,0 +1,470 @@
+/*
+ * Raspberry Pi emulation (c) 2012 Gregory Estrade
+ * Refactoring for Pi2 Copyright (c) 2015, Microsoft. Written by Andrew Baumann.
+ *
+ * Heavily based on milkymist-vgafb.c, copyright terms below:
+ * QEMU model of the Milkymist VGA framebuffer.
+ *
+ * Copyright (c) 2010-2012 Michael Walle <michael@walle.cc>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "hw/display/bcm2835_fb.h"
+#include "hw/hw.h"
+#include "hw/irq.h"
+#include "framebuffer.h"
+#include "ui/pixel_ops.h"
+#include "hw/misc/bcm2835_mbox_defs.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+
+#define DEFAULT_VCRAM_SIZE 0x4000000
+#define BCM2835_FB_OFFSET 0x00100000
+
+/* Maximum permitted framebuffer size; experimentally determined on an rpi2 */
+#define XRES_MAX 3840
+#define YRES_MAX 2560
+/* Framebuffer size used if guest requests zero size */
+#define XRES_SMALL 592
+#define YRES_SMALL 488
+
+static void fb_invalidate_display(void *opaque)
+{
+ BCM2835FBState *s = BCM2835_FB(opaque);
+
+ s->invalidate = true;
+}
+
+static void draw_line_src16(void *opaque, uint8_t *dst, const uint8_t *src,
+ int width, int deststep)
+{
+ BCM2835FBState *s = opaque;
+ uint16_t rgb565;
+ uint32_t rgb888;
+ uint8_t r, g, b;
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ int bpp = surface_bits_per_pixel(surface);
+
+ while (width--) {
+ switch (s->config.bpp) {
+ case 8:
+ /* lookup palette starting at video ram base
+ * TODO: cache translation, rather than doing this each time!
+ */
+ rgb888 = ldl_le_phys(&s->dma_as, s->vcram_base + (*src << 2));
+ r = (rgb888 >> 0) & 0xff;
+ g = (rgb888 >> 8) & 0xff;
+ b = (rgb888 >> 16) & 0xff;
+ src++;
+ break;
+ case 16:
+ rgb565 = lduw_le_p(src);
+ r = ((rgb565 >> 11) & 0x1f) << 3;
+ g = ((rgb565 >> 5) & 0x3f) << 2;
+ b = ((rgb565 >> 0) & 0x1f) << 3;
+ src += 2;
+ break;
+ case 24:
+ rgb888 = ldl_le_p(src);
+ r = (rgb888 >> 0) & 0xff;
+ g = (rgb888 >> 8) & 0xff;
+ b = (rgb888 >> 16) & 0xff;
+ src += 3;
+ break;
+ case 32:
+ rgb888 = ldl_le_p(src);
+ r = (rgb888 >> 0) & 0xff;
+ g = (rgb888 >> 8) & 0xff;
+ b = (rgb888 >> 16) & 0xff;
+ src += 4;
+ break;
+ default:
+ r = 0;
+ g = 0;
+ b = 0;
+ break;
+ }
+
+ if (s->config.pixo == 0) {
+ /* swap to BGR pixel format */
+ uint8_t tmp = r;
+ r = b;
+ b = tmp;
+ }
+
+ switch (bpp) {
+ case 8:
+ *dst++ = rgb_to_pixel8(r, g, b);
+ break;
+ case 15:
+ *(uint16_t *)dst = rgb_to_pixel15(r, g, b);
+ dst += 2;
+ break;
+ case 16:
+ *(uint16_t *)dst = rgb_to_pixel16(r, g, b);
+ dst += 2;
+ break;
+ case 24:
+ rgb888 = rgb_to_pixel24(r, g, b);
+ *dst++ = rgb888 & 0xff;
+ *dst++ = (rgb888 >> 8) & 0xff;
+ *dst++ = (rgb888 >> 16) & 0xff;
+ break;
+ case 32:
+ *(uint32_t *)dst = rgb_to_pixel32(r, g, b);
+ dst += 4;
+ break;
+ default:
+ return;
+ }
+ }
+}
+
+static bool fb_use_offsets(BCM2835FBConfig *config)
+{
+ /*
+ * Return true if we should use the viewport offsets.
+ * Experimentally, the hardware seems to do this only if the
+ * viewport size is larger than the physical screen. (It doesn't
+ * prevent the guest setting this silly viewport setting, though...)
+ */
+ return config->xres_virtual > config->xres &&
+ config->yres_virtual > config->yres;
+}
+
+static void fb_update_display(void *opaque)
+{
+ BCM2835FBState *s = opaque;
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ int first = 0;
+ int last = 0;
+ int src_width = 0;
+ int dest_width = 0;
+ uint32_t xoff = 0, yoff = 0;
+
+ if (s->lock || !s->config.xres) {
+ return;
+ }
+
+ src_width = bcm2835_fb_get_pitch(&s->config);
+ if (fb_use_offsets(&s->config)) {
+ xoff = s->config.xoffset;
+ yoff = s->config.yoffset;
+ }
+
+ dest_width = s->config.xres;
+
+ switch (surface_bits_per_pixel(surface)) {
+ case 0:
+ return;
+ case 8:
+ break;
+ case 15:
+ dest_width *= 2;
+ break;
+ case 16:
+ dest_width *= 2;
+ break;
+ case 24:
+ dest_width *= 3;
+ break;
+ case 32:
+ dest_width *= 4;
+ break;
+ default:
+ hw_error("bcm2835_fb: bad color depth\n");
+ break;
+ }
+
+ if (s->invalidate) {
+ hwaddr base = s->config.base + xoff + (hwaddr)yoff * src_width;
+ framebuffer_update_memory_section(&s->fbsection, s->dma_mr,
+ base,
+ s->config.yres, src_width);
+ }
+
+ framebuffer_update_display(surface, &s->fbsection,
+ s->config.xres, s->config.yres,
+ src_width, dest_width, 0, s->invalidate,
+ draw_line_src16, s, &first, &last);
+
+ if (first >= 0) {
+ dpy_gfx_update(s->con, 0, first, s->config.xres,
+ last - first + 1);
+ }
+
+ s->invalidate = false;
+}
+
+void bcm2835_fb_validate_config(BCM2835FBConfig *config)
+{
+ /*
+ * Validate the config, and clip any bogus values into range,
+ * as the hardware does. Note that fb_update_display() relies on
+ * this happening to prevent it from performing out-of-range
+ * accesses on redraw.
+ */
+ config->xres = MIN(config->xres, XRES_MAX);
+ config->xres_virtual = MIN(config->xres_virtual, XRES_MAX);
+ config->yres = MIN(config->yres, YRES_MAX);
+ config->yres_virtual = MIN(config->yres_virtual, YRES_MAX);
+
+ /*
+ * These are not minima: a 40x40 framebuffer will be accepted.
+ * They're only used as defaults if the guest asks for zero size.
+ */
+ if (config->xres == 0) {
+ config->xres = XRES_SMALL;
+ }
+ if (config->yres == 0) {
+ config->yres = YRES_SMALL;
+ }
+ if (config->xres_virtual == 0) {
+ config->xres_virtual = config->xres;
+ }
+ if (config->yres_virtual == 0) {
+ config->yres_virtual = config->yres;
+ }
+
+ if (fb_use_offsets(config)) {
+ /* Clip the offsets so the viewport is within the physical screen */
+ config->xoffset = MIN(config->xoffset,
+ config->xres_virtual - config->xres);
+ config->yoffset = MIN(config->yoffset,
+ config->yres_virtual - config->yres);
+ }
+}
+
+void bcm2835_fb_reconfigure(BCM2835FBState *s, BCM2835FBConfig *newconfig)
+{
+ s->lock = true;
+
+ s->config = *newconfig;
+
+ s->invalidate = true;
+ qemu_console_resize(s->con, s->config.xres, s->config.yres);
+ s->lock = false;
+}
+
+static void bcm2835_fb_mbox_push(BCM2835FBState *s, uint32_t value)
+{
+ uint32_t pitch;
+ uint32_t size;
+ BCM2835FBConfig newconf;
+
+ value &= ~0xf;
+
+ newconf.xres = ldl_le_phys(&s->dma_as, value);
+ newconf.yres = ldl_le_phys(&s->dma_as, value + 4);
+ newconf.xres_virtual = ldl_le_phys(&s->dma_as, value + 8);
+ newconf.yres_virtual = ldl_le_phys(&s->dma_as, value + 12);
+ newconf.bpp = ldl_le_phys(&s->dma_as, value + 20);
+ newconf.xoffset = ldl_le_phys(&s->dma_as, value + 24);
+ newconf.yoffset = ldl_le_phys(&s->dma_as, value + 28);
+
+ newconf.base = s->vcram_base | (value & 0xc0000000);
+ newconf.base += BCM2835_FB_OFFSET;
+
+ /* Copy fields which we don't want to change from the existing config */
+ newconf.pixo = s->config.pixo;
+ newconf.alpha = s->config.alpha;
+
+ bcm2835_fb_validate_config(&newconf);
+
+ pitch = bcm2835_fb_get_pitch(&newconf);
+ size = bcm2835_fb_get_size(&newconf);
+
+ stl_le_phys(&s->dma_as, value + 16, pitch);
+ stl_le_phys(&s->dma_as, value + 32, newconf.base);
+ stl_le_phys(&s->dma_as, value + 36, size);
+
+ bcm2835_fb_reconfigure(s, &newconf);
+}
+
+static uint64_t bcm2835_fb_read(void *opaque, hwaddr offset, unsigned size)
+{
+ BCM2835FBState *s = opaque;
+ uint32_t res = 0;
+
+ switch (offset) {
+ case MBOX_AS_DATA:
+ res = MBOX_CHAN_FB;
+ s->pending = false;
+ qemu_set_irq(s->mbox_irq, 0);
+ break;
+
+ case MBOX_AS_PENDING:
+ res = s->pending;
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset %"HWADDR_PRIx"\n",
+ __func__, offset);
+ return 0;
+ }
+
+ return res;
+}
+
+static void bcm2835_fb_write(void *opaque, hwaddr offset, uint64_t value,
+ unsigned size)
+{
+ BCM2835FBState *s = opaque;
+
+ switch (offset) {
+ case MBOX_AS_DATA:
+ /* bcm2835_mbox should check our pending status before pushing */
+ assert(!s->pending);
+ s->pending = true;
+ bcm2835_fb_mbox_push(s, value);
+ qemu_set_irq(s->mbox_irq, 1);
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset %"HWADDR_PRIx"\n",
+ __func__, offset);
+ return;
+ }
+}
+
+static const MemoryRegionOps bcm2835_fb_ops = {
+ .read = bcm2835_fb_read,
+ .write = bcm2835_fb_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 4,
+};
+
+static const VMStateDescription vmstate_bcm2835_fb = {
+ .name = TYPE_BCM2835_FB,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_BOOL(lock, BCM2835FBState),
+ VMSTATE_BOOL(invalidate, BCM2835FBState),
+ VMSTATE_BOOL(pending, BCM2835FBState),
+ VMSTATE_UINT32(config.xres, BCM2835FBState),
+ VMSTATE_UINT32(config.yres, BCM2835FBState),
+ VMSTATE_UINT32(config.xres_virtual, BCM2835FBState),
+ VMSTATE_UINT32(config.yres_virtual, BCM2835FBState),
+ VMSTATE_UINT32(config.xoffset, BCM2835FBState),
+ VMSTATE_UINT32(config.yoffset, BCM2835FBState),
+ VMSTATE_UINT32(config.bpp, BCM2835FBState),
+ VMSTATE_UINT32(config.base, BCM2835FBState),
+ VMSTATE_UNUSED(8), /* Was pitch and size */
+ VMSTATE_UINT32(config.pixo, BCM2835FBState),
+ VMSTATE_UINT32(config.alpha, BCM2835FBState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const GraphicHwOps vgafb_ops = {
+ .invalidate = fb_invalidate_display,
+ .gfx_update = fb_update_display,
+};
+
+static void bcm2835_fb_init(Object *obj)
+{
+ BCM2835FBState *s = BCM2835_FB(obj);
+
+ memory_region_init_io(&s->iomem, obj, &bcm2835_fb_ops, s, TYPE_BCM2835_FB,
+ 0x10);
+ sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem);
+ sysbus_init_irq(SYS_BUS_DEVICE(s), &s->mbox_irq);
+}
+
+static void bcm2835_fb_reset(DeviceState *dev)
+{
+ BCM2835FBState *s = BCM2835_FB(dev);
+
+ s->pending = false;
+
+ s->config = s->initial_config;
+
+ s->invalidate = true;
+ s->lock = false;
+}
+
+static void bcm2835_fb_realize(DeviceState *dev, Error **errp)
+{
+ BCM2835FBState *s = BCM2835_FB(dev);
+ Object *obj;
+
+ if (s->vcram_base == 0) {
+ error_setg(errp, "%s: required vcram-base property not set", __func__);
+ return;
+ }
+
+ obj = object_property_get_link(OBJECT(dev), "dma-mr", &error_abort);
+
+ /* Fill in the parts of initial_config that are not set by QOM properties */
+ s->initial_config.xres_virtual = s->initial_config.xres;
+ s->initial_config.yres_virtual = s->initial_config.yres;
+ s->initial_config.xoffset = 0;
+ s->initial_config.yoffset = 0;
+ s->initial_config.base = s->vcram_base + BCM2835_FB_OFFSET;
+
+ s->dma_mr = MEMORY_REGION(obj);
+ address_space_init(&s->dma_as, s->dma_mr, TYPE_BCM2835_FB "-memory");
+
+ bcm2835_fb_reset(dev);
+
+ s->con = graphic_console_init(dev, 0, &vgafb_ops, s);
+ qemu_console_resize(s->con, s->config.xres, s->config.yres);
+}
+
+static Property bcm2835_fb_props[] = {
+ DEFINE_PROP_UINT32("vcram-base", BCM2835FBState, vcram_base, 0),/*required*/
+ DEFINE_PROP_UINT32("vcram-size", BCM2835FBState, vcram_size,
+ DEFAULT_VCRAM_SIZE),
+ DEFINE_PROP_UINT32("xres", BCM2835FBState, initial_config.xres, 640),
+ DEFINE_PROP_UINT32("yres", BCM2835FBState, initial_config.yres, 480),
+ DEFINE_PROP_UINT32("bpp", BCM2835FBState, initial_config.bpp, 16),
+ DEFINE_PROP_UINT32("pixo", BCM2835FBState,
+ initial_config.pixo, 1), /* 1=RGB, 0=BGR */
+ DEFINE_PROP_UINT32("alpha", BCM2835FBState,
+ initial_config.alpha, 2), /* alpha ignored */
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static void bcm2835_fb_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ device_class_set_props(dc, bcm2835_fb_props);
+ dc->realize = bcm2835_fb_realize;
+ dc->reset = bcm2835_fb_reset;
+ dc->vmsd = &vmstate_bcm2835_fb;
+}
+
+static TypeInfo bcm2835_fb_info = {
+ .name = TYPE_BCM2835_FB,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(BCM2835FBState),
+ .class_init = bcm2835_fb_class_init,
+ .instance_init = bcm2835_fb_init,
+};
+
+static void bcm2835_fb_register_types(void)
+{
+ type_register_static(&bcm2835_fb_info);
+}
+
+type_init(bcm2835_fb_register_types)
diff --git a/hw/display/blizzard.c b/hw/display/blizzard.c
new file mode 100644
index 000000000..105241577
--- /dev/null
+++ b/hw/display/blizzard.c
@@ -0,0 +1,1026 @@
+/*
+ * Epson S1D13744/S1D13745 (Blizzard/Hailstorm/Tornado) LCD/TV controller.
+ *
+ * Copyright (C) 2008 Nokia Corporation
+ * Written by Andrzej Zaborowski <andrew@openedhand.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/bitops.h"
+#include "ui/console.h"
+#include "hw/display/blizzard.h"
+#include "ui/pixel_ops.h"
+
+typedef void (*blizzard_fn_t)(uint8_t *, const uint8_t *, unsigned int);
+
+typedef struct {
+ uint8_t reg;
+ uint32_t addr;
+ int swallow;
+
+ int pll;
+ int pll_range;
+ int pll_ctrl;
+ uint8_t pll_mode;
+ uint8_t clksel;
+ int memenable;
+ int memrefresh;
+ uint8_t timing[3];
+ int priority;
+
+ uint8_t lcd_config;
+ int x;
+ int y;
+ int skipx;
+ int skipy;
+ uint8_t hndp;
+ uint8_t vndp;
+ uint8_t hsync;
+ uint8_t vsync;
+ uint8_t pclk;
+ uint8_t u;
+ uint8_t v;
+ uint8_t yrc[2];
+ int ix[2];
+ int iy[2];
+ int ox[2];
+ int oy[2];
+
+ int enable;
+ int blank;
+ int bpp;
+ int invalidate;
+ int mx[2];
+ int my[2];
+ uint8_t mode;
+ uint8_t effect;
+ uint8_t iformat;
+ uint8_t source;
+ QemuConsole *con;
+ blizzard_fn_t *line_fn_tab[2];
+ void *fb;
+
+ uint8_t hssi_config[3];
+ uint8_t tv_config;
+ uint8_t tv_timing[4];
+ uint8_t vbi;
+ uint8_t tv_x;
+ uint8_t tv_y;
+ uint8_t tv_test;
+ uint8_t tv_filter_config;
+ uint8_t tv_filter_idx;
+ uint8_t tv_filter_coeff[0x20];
+ uint8_t border_r;
+ uint8_t border_g;
+ uint8_t border_b;
+ uint8_t gamma_config;
+ uint8_t gamma_idx;
+ uint8_t gamma_lut[0x100];
+ uint8_t matrix_ena;
+ uint8_t matrix_coeff[0x12];
+ uint8_t matrix_r;
+ uint8_t matrix_g;
+ uint8_t matrix_b;
+ uint8_t pm;
+ uint8_t status;
+ uint8_t rgbgpio_dir;
+ uint8_t rgbgpio;
+ uint8_t gpio_dir;
+ uint8_t gpio;
+ uint8_t gpio_edge[2];
+ uint8_t gpio_irq;
+ uint8_t gpio_pdown;
+
+ struct {
+ int x;
+ int y;
+ int dx;
+ int dy;
+ int len;
+ int buflen;
+ void *buf;
+ void *data;
+ uint16_t *ptr;
+ int angle;
+ int pitch;
+ blizzard_fn_t line_fn;
+ } data;
+} BlizzardState;
+
+/* Bytes(!) per pixel */
+static const int blizzard_iformat_bpp[0x10] = {
+ 0,
+ 2, /* RGB 5:6:5*/
+ 3, /* RGB 6:6:6 mode 1 */
+ 3, /* RGB 8:8:8 mode 1 */
+ 0, 0,
+ 4, /* RGB 6:6:6 mode 2 */
+ 4, /* RGB 8:8:8 mode 2 */
+ 0, /* YUV 4:2:2 */
+ 0, /* YUV 4:2:0 */
+ 0, 0, 0, 0, 0, 0,
+};
+
+static void blizzard_window(BlizzardState *s)
+{
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ uint8_t *src, *dst;
+ int bypp[2];
+ int bypl[3];
+ int y;
+ blizzard_fn_t fn = s->data.line_fn;
+
+ if (!fn)
+ return;
+ if (s->mx[0] > s->data.x)
+ s->mx[0] = s->data.x;
+ if (s->my[0] > s->data.y)
+ s->my[0] = s->data.y;
+ if (s->mx[1] < s->data.x + s->data.dx)
+ s->mx[1] = s->data.x + s->data.dx;
+ if (s->my[1] < s->data.y + s->data.dy)
+ s->my[1] = s->data.y + s->data.dy;
+
+ bypp[0] = s->bpp;
+ bypp[1] = surface_bytes_per_pixel(surface);
+ bypl[0] = bypp[0] * s->data.pitch;
+ bypl[1] = bypp[1] * s->x;
+ bypl[2] = bypp[0] * s->data.dx;
+
+ src = s->data.data;
+ dst = s->fb + bypl[1] * s->data.y + bypp[1] * s->data.x;
+ for (y = s->data.dy; y > 0; y --, src += bypl[0], dst += bypl[1])
+ fn(dst, src, bypl[2]);
+}
+
+static int blizzard_transfer_setup(BlizzardState *s)
+{
+ if (s->source > 3 || !s->bpp ||
+ s->ix[1] < s->ix[0] || s->iy[1] < s->iy[0])
+ return 0;
+
+ s->data.angle = s->effect & 3;
+ s->data.line_fn = s->line_fn_tab[!!s->data.angle][s->iformat];
+ s->data.x = s->ix[0];
+ s->data.y = s->iy[0];
+ s->data.dx = s->ix[1] - s->ix[0] + 1;
+ s->data.dy = s->iy[1] - s->iy[0] + 1;
+ s->data.len = s->bpp * s->data.dx * s->data.dy;
+ s->data.pitch = s->data.dx;
+ if (s->data.len > s->data.buflen) {
+ s->data.buf = g_realloc(s->data.buf, s->data.len);
+ s->data.buflen = s->data.len;
+ }
+ s->data.ptr = s->data.buf;
+ s->data.data = s->data.buf;
+ s->data.len /= 2;
+ return 1;
+}
+
+static void blizzard_reset(BlizzardState *s)
+{
+ s->reg = 0;
+ s->swallow = 0;
+
+ s->pll = 9;
+ s->pll_range = 1;
+ s->pll_ctrl = 0x14;
+ s->pll_mode = 0x32;
+ s->clksel = 0x00;
+ s->memenable = 0;
+ s->memrefresh = 0x25c;
+ s->timing[0] = 0x3f;
+ s->timing[1] = 0x13;
+ s->timing[2] = 0x21;
+ s->priority = 0;
+
+ s->lcd_config = 0x74;
+ s->x = 8;
+ s->y = 1;
+ s->skipx = 0;
+ s->skipy = 0;
+ s->hndp = 3;
+ s->vndp = 2;
+ s->hsync = 1;
+ s->vsync = 1;
+ s->pclk = 0x80;
+
+ s->ix[0] = 0;
+ s->ix[1] = 0;
+ s->iy[0] = 0;
+ s->iy[1] = 0;
+ s->ox[0] = 0;
+ s->ox[1] = 0;
+ s->oy[0] = 0;
+ s->oy[1] = 0;
+
+ s->yrc[0] = 0x00;
+ s->yrc[1] = 0x30;
+ s->u = 0;
+ s->v = 0;
+
+ s->iformat = 3;
+ s->source = 0;
+ s->bpp = blizzard_iformat_bpp[s->iformat];
+
+ s->hssi_config[0] = 0x00;
+ s->hssi_config[1] = 0x00;
+ s->hssi_config[2] = 0x01;
+ s->tv_config = 0x00;
+ s->tv_timing[0] = 0x00;
+ s->tv_timing[1] = 0x00;
+ s->tv_timing[2] = 0x00;
+ s->tv_timing[3] = 0x00;
+ s->vbi = 0x10;
+ s->tv_x = 0x14;
+ s->tv_y = 0x03;
+ s->tv_test = 0x00;
+ s->tv_filter_config = 0x80;
+ s->tv_filter_idx = 0x00;
+ s->border_r = 0x10;
+ s->border_g = 0x80;
+ s->border_b = 0x80;
+ s->gamma_config = 0x00;
+ s->gamma_idx = 0x00;
+ s->matrix_ena = 0x00;
+ memset(&s->matrix_coeff, 0, sizeof(s->matrix_coeff));
+ s->matrix_r = 0x00;
+ s->matrix_g = 0x00;
+ s->matrix_b = 0x00;
+ s->pm = 0x02;
+ s->status = 0x00;
+ s->rgbgpio_dir = 0x00;
+ s->gpio_dir = 0x00;
+ s->gpio_edge[0] = 0x00;
+ s->gpio_edge[1] = 0x00;
+ s->gpio_irq = 0x00;
+ s->gpio_pdown = 0xff;
+}
+
+static inline void blizzard_invalidate_display(void *opaque) {
+ BlizzardState *s = (BlizzardState *) opaque;
+
+ s->invalidate = 1;
+}
+
+static uint16_t blizzard_reg_read(void *opaque, uint8_t reg)
+{
+ BlizzardState *s = (BlizzardState *) opaque;
+
+ switch (reg) {
+ case 0x00: /* Revision Code */
+ return 0xa5;
+
+ case 0x02: /* Configuration Readback */
+ return 0x83; /* Macrovision OK, CNF[2:0] = 3 */
+
+ case 0x04: /* PLL M-Divider */
+ return (s->pll - 1) | (1 << 7);
+ case 0x06: /* PLL Lock Range Control */
+ return s->pll_range;
+ case 0x08: /* PLL Lock Synthesis Control 0 */
+ return s->pll_ctrl & 0xff;
+ case 0x0a: /* PLL Lock Synthesis Control 1 */
+ return s->pll_ctrl >> 8;
+ case 0x0c: /* PLL Mode Control 0 */
+ return s->pll_mode;
+
+ case 0x0e: /* Clock-Source Select */
+ return s->clksel;
+
+ case 0x10: /* Memory Controller Activate */
+ case 0x14: /* Memory Controller Bank 0 Status Flag */
+ return s->memenable;
+
+ case 0x18: /* Auto-Refresh Interval Setting 0 */
+ return s->memrefresh & 0xff;
+ case 0x1a: /* Auto-Refresh Interval Setting 1 */
+ return s->memrefresh >> 8;
+
+ case 0x1c: /* Power-On Sequence Timing Control */
+ return s->timing[0];
+ case 0x1e: /* Timing Control 0 */
+ return s->timing[1];
+ case 0x20: /* Timing Control 1 */
+ return s->timing[2];
+
+ case 0x24: /* Arbitration Priority Control */
+ return s->priority;
+
+ case 0x28: /* LCD Panel Configuration */
+ return s->lcd_config;
+
+ case 0x2a: /* LCD Horizontal Display Width */
+ return s->x >> 3;
+ case 0x2c: /* LCD Horizontal Non-display Period */
+ return s->hndp;
+ case 0x2e: /* LCD Vertical Display Height 0 */
+ return s->y & 0xff;
+ case 0x30: /* LCD Vertical Display Height 1 */
+ return s->y >> 8;
+ case 0x32: /* LCD Vertical Non-display Period */
+ return s->vndp;
+ case 0x34: /* LCD HS Pulse-width */
+ return s->hsync;
+ case 0x36: /* LCd HS Pulse Start Position */
+ return s->skipx >> 3;
+ case 0x38: /* LCD VS Pulse-width */
+ return s->vsync;
+ case 0x3a: /* LCD VS Pulse Start Position */
+ return s->skipy;
+
+ case 0x3c: /* PCLK Polarity */
+ return s->pclk;
+
+ case 0x3e: /* High-speed Serial Interface Tx Configuration Port 0 */
+ return s->hssi_config[0];
+ case 0x40: /* High-speed Serial Interface Tx Configuration Port 1 */
+ return s->hssi_config[1];
+ case 0x42: /* High-speed Serial Interface Tx Mode */
+ return s->hssi_config[2];
+ case 0x44: /* TV Display Configuration */
+ return s->tv_config;
+ case 0x46 ... 0x4c: /* TV Vertical Blanking Interval Data bits */
+ return s->tv_timing[(reg - 0x46) >> 1];
+ case 0x4e: /* VBI: Closed Caption / XDS Control / Status */
+ return s->vbi;
+ case 0x50: /* TV Horizontal Start Position */
+ return s->tv_x;
+ case 0x52: /* TV Vertical Start Position */
+ return s->tv_y;
+ case 0x54: /* TV Test Pattern Setting */
+ return s->tv_test;
+ case 0x56: /* TV Filter Setting */
+ return s->tv_filter_config;
+ case 0x58: /* TV Filter Coefficient Index */
+ return s->tv_filter_idx;
+ case 0x5a: /* TV Filter Coefficient Data */
+ if (s->tv_filter_idx < 0x20)
+ return s->tv_filter_coeff[s->tv_filter_idx ++];
+ return 0;
+
+ case 0x60: /* Input YUV/RGB Translate Mode 0 */
+ return s->yrc[0];
+ case 0x62: /* Input YUV/RGB Translate Mode 1 */
+ return s->yrc[1];
+ case 0x64: /* U Data Fix */
+ return s->u;
+ case 0x66: /* V Data Fix */
+ return s->v;
+
+ case 0x68: /* Display Mode */
+ return s->mode;
+
+ case 0x6a: /* Special Effects */
+ return s->effect;
+
+ case 0x6c: /* Input Window X Start Position 0 */
+ return s->ix[0] & 0xff;
+ case 0x6e: /* Input Window X Start Position 1 */
+ return s->ix[0] >> 3;
+ case 0x70: /* Input Window Y Start Position 0 */
+ return s->ix[0] & 0xff;
+ case 0x72: /* Input Window Y Start Position 1 */
+ return s->ix[0] >> 3;
+ case 0x74: /* Input Window X End Position 0 */
+ return s->ix[1] & 0xff;
+ case 0x76: /* Input Window X End Position 1 */
+ return s->ix[1] >> 3;
+ case 0x78: /* Input Window Y End Position 0 */
+ return s->ix[1] & 0xff;
+ case 0x7a: /* Input Window Y End Position 1 */
+ return s->ix[1] >> 3;
+ case 0x7c: /* Output Window X Start Position 0 */
+ return s->ox[0] & 0xff;
+ case 0x7e: /* Output Window X Start Position 1 */
+ return s->ox[0] >> 3;
+ case 0x80: /* Output Window Y Start Position 0 */
+ return s->oy[0] & 0xff;
+ case 0x82: /* Output Window Y Start Position 1 */
+ return s->oy[0] >> 3;
+ case 0x84: /* Output Window X End Position 0 */
+ return s->ox[1] & 0xff;
+ case 0x86: /* Output Window X End Position 1 */
+ return s->ox[1] >> 3;
+ case 0x88: /* Output Window Y End Position 0 */
+ return s->oy[1] & 0xff;
+ case 0x8a: /* Output Window Y End Position 1 */
+ return s->oy[1] >> 3;
+
+ case 0x8c: /* Input Data Format */
+ return s->iformat;
+ case 0x8e: /* Data Source Select */
+ return s->source;
+ case 0x90: /* Display Memory Data Port */
+ return 0;
+
+ case 0xa8: /* Border Color 0 */
+ return s->border_r;
+ case 0xaa: /* Border Color 1 */
+ return s->border_g;
+ case 0xac: /* Border Color 2 */
+ return s->border_b;
+
+ case 0xb4: /* Gamma Correction Enable */
+ return s->gamma_config;
+ case 0xb6: /* Gamma Correction Table Index */
+ return s->gamma_idx;
+ case 0xb8: /* Gamma Correction Table Data */
+ return s->gamma_lut[s->gamma_idx ++];
+
+ case 0xba: /* 3x3 Matrix Enable */
+ return s->matrix_ena;
+ case 0xbc ... 0xde: /* Coefficient Registers */
+ return s->matrix_coeff[(reg - 0xbc) >> 1];
+ case 0xe0: /* 3x3 Matrix Red Offset */
+ return s->matrix_r;
+ case 0xe2: /* 3x3 Matrix Green Offset */
+ return s->matrix_g;
+ case 0xe4: /* 3x3 Matrix Blue Offset */
+ return s->matrix_b;
+
+ case 0xe6: /* Power-save */
+ return s->pm;
+ case 0xe8: /* Non-display Period Control / Status */
+ return s->status | (1 << 5);
+ case 0xea: /* RGB Interface Control */
+ return s->rgbgpio_dir;
+ case 0xec: /* RGB Interface Status */
+ return s->rgbgpio;
+ case 0xee: /* General-purpose IO Pins Configuration */
+ return s->gpio_dir;
+ case 0xf0: /* General-purpose IO Pins Status / Control */
+ return s->gpio;
+ case 0xf2: /* GPIO Positive Edge Interrupt Trigger */
+ return s->gpio_edge[0];
+ case 0xf4: /* GPIO Negative Edge Interrupt Trigger */
+ return s->gpio_edge[1];
+ case 0xf6: /* GPIO Interrupt Status */
+ return s->gpio_irq;
+ case 0xf8: /* GPIO Pull-down Control */
+ return s->gpio_pdown;
+
+ default:
+ fprintf(stderr, "%s: unknown register %02x\n", __func__, reg);
+ return 0;
+ }
+}
+
+static void blizzard_reg_write(void *opaque, uint8_t reg, uint16_t value)
+{
+ BlizzardState *s = (BlizzardState *) opaque;
+
+ switch (reg) {
+ case 0x04: /* PLL M-Divider */
+ s->pll = (value & 0x3f) + 1;
+ break;
+ case 0x06: /* PLL Lock Range Control */
+ s->pll_range = value & 3;
+ break;
+ case 0x08: /* PLL Lock Synthesis Control 0 */
+ s->pll_ctrl &= 0xf00;
+ s->pll_ctrl |= (value << 0) & 0x0ff;
+ break;
+ case 0x0a: /* PLL Lock Synthesis Control 1 */
+ s->pll_ctrl &= 0x0ff;
+ s->pll_ctrl |= (value << 8) & 0xf00;
+ break;
+ case 0x0c: /* PLL Mode Control 0 */
+ s->pll_mode = value & 0x77;
+ if ((value & 3) == 0 || (value & 3) == 3)
+ fprintf(stderr, "%s: wrong PLL Control bits (%i)\n",
+ __func__, value & 3);
+ break;
+
+ case 0x0e: /* Clock-Source Select */
+ s->clksel = value & 0xff;
+ break;
+
+ case 0x10: /* Memory Controller Activate */
+ s->memenable = value & 1;
+ break;
+ case 0x14: /* Memory Controller Bank 0 Status Flag */
+ break;
+
+ case 0x18: /* Auto-Refresh Interval Setting 0 */
+ s->memrefresh &= 0xf00;
+ s->memrefresh |= (value << 0) & 0x0ff;
+ break;
+ case 0x1a: /* Auto-Refresh Interval Setting 1 */
+ s->memrefresh &= 0x0ff;
+ s->memrefresh |= (value << 8) & 0xf00;
+ break;
+
+ case 0x1c: /* Power-On Sequence Timing Control */
+ s->timing[0] = value & 0x7f;
+ break;
+ case 0x1e: /* Timing Control 0 */
+ s->timing[1] = value & 0x17;
+ break;
+ case 0x20: /* Timing Control 1 */
+ s->timing[2] = value & 0x35;
+ break;
+
+ case 0x24: /* Arbitration Priority Control */
+ s->priority = value & 1;
+ break;
+
+ case 0x28: /* LCD Panel Configuration */
+ s->lcd_config = value & 0xff;
+ if (value & (1 << 7))
+ fprintf(stderr, "%s: data swap not supported!\n", __func__);
+ break;
+
+ case 0x2a: /* LCD Horizontal Display Width */
+ s->x = value << 3;
+ break;
+ case 0x2c: /* LCD Horizontal Non-display Period */
+ s->hndp = value & 0xff;
+ break;
+ case 0x2e: /* LCD Vertical Display Height 0 */
+ s->y &= 0x300;
+ s->y |= (value << 0) & 0x0ff;
+ break;
+ case 0x30: /* LCD Vertical Display Height 1 */
+ s->y &= 0x0ff;
+ s->y |= (value << 8) & 0x300;
+ break;
+ case 0x32: /* LCD Vertical Non-display Period */
+ s->vndp = value & 0xff;
+ break;
+ case 0x34: /* LCD HS Pulse-width */
+ s->hsync = value & 0xff;
+ break;
+ case 0x36: /* LCD HS Pulse Start Position */
+ s->skipx = value & 0xff;
+ break;
+ case 0x38: /* LCD VS Pulse-width */
+ s->vsync = value & 0xbf;
+ break;
+ case 0x3a: /* LCD VS Pulse Start Position */
+ s->skipy = value & 0xff;
+ break;
+
+ case 0x3c: /* PCLK Polarity */
+ s->pclk = value & 0x82;
+ /* Affects calculation of s->hndp, s->hsync and s->skipx. */
+ break;
+
+ case 0x3e: /* High-speed Serial Interface Tx Configuration Port 0 */
+ s->hssi_config[0] = value;
+ break;
+ case 0x40: /* High-speed Serial Interface Tx Configuration Port 1 */
+ s->hssi_config[1] = value;
+ if (((value >> 4) & 3) == 3)
+ fprintf(stderr, "%s: Illegal active-data-links value\n",
+ __func__);
+ break;
+ case 0x42: /* High-speed Serial Interface Tx Mode */
+ s->hssi_config[2] = value & 0xbd;
+ break;
+
+ case 0x44: /* TV Display Configuration */
+ s->tv_config = value & 0xfe;
+ break;
+ case 0x46 ... 0x4c: /* TV Vertical Blanking Interval Data bits 0 */
+ s->tv_timing[(reg - 0x46) >> 1] = value;
+ break;
+ case 0x4e: /* VBI: Closed Caption / XDS Control / Status */
+ s->vbi = value;
+ break;
+ case 0x50: /* TV Horizontal Start Position */
+ s->tv_x = value;
+ break;
+ case 0x52: /* TV Vertical Start Position */
+ s->tv_y = value & 0x7f;
+ break;
+ case 0x54: /* TV Test Pattern Setting */
+ s->tv_test = value;
+ break;
+ case 0x56: /* TV Filter Setting */
+ s->tv_filter_config = value & 0xbf;
+ break;
+ case 0x58: /* TV Filter Coefficient Index */
+ s->tv_filter_idx = value & 0x1f;
+ break;
+ case 0x5a: /* TV Filter Coefficient Data */
+ if (s->tv_filter_idx < 0x20)
+ s->tv_filter_coeff[s->tv_filter_idx ++] = value;
+ break;
+
+ case 0x60: /* Input YUV/RGB Translate Mode 0 */
+ s->yrc[0] = value & 0xb0;
+ break;
+ case 0x62: /* Input YUV/RGB Translate Mode 1 */
+ s->yrc[1] = value & 0x30;
+ break;
+ case 0x64: /* U Data Fix */
+ s->u = value & 0xff;
+ break;
+ case 0x66: /* V Data Fix */
+ s->v = value & 0xff;
+ break;
+
+ case 0x68: /* Display Mode */
+ if ((s->mode ^ value) & 3)
+ s->invalidate = 1;
+ s->mode = value & 0xb7;
+ s->enable = value & 1;
+ s->blank = (value >> 1) & 1;
+ if (value & (1 << 4))
+ fprintf(stderr, "%s: Macrovision enable attempt!\n", __func__);
+ break;
+
+ case 0x6a: /* Special Effects */
+ s->effect = value & 0xfb;
+ break;
+
+ case 0x6c: /* Input Window X Start Position 0 */
+ s->ix[0] &= 0x300;
+ s->ix[0] |= (value << 0) & 0x0ff;
+ break;
+ case 0x6e: /* Input Window X Start Position 1 */
+ s->ix[0] &= 0x0ff;
+ s->ix[0] |= (value << 8) & 0x300;
+ break;
+ case 0x70: /* Input Window Y Start Position 0 */
+ s->iy[0] &= 0x300;
+ s->iy[0] |= (value << 0) & 0x0ff;
+ break;
+ case 0x72: /* Input Window Y Start Position 1 */
+ s->iy[0] &= 0x0ff;
+ s->iy[0] |= (value << 8) & 0x300;
+ break;
+ case 0x74: /* Input Window X End Position 0 */
+ s->ix[1] &= 0x300;
+ s->ix[1] |= (value << 0) & 0x0ff;
+ break;
+ case 0x76: /* Input Window X End Position 1 */
+ s->ix[1] &= 0x0ff;
+ s->ix[1] |= (value << 8) & 0x300;
+ break;
+ case 0x78: /* Input Window Y End Position 0 */
+ s->iy[1] &= 0x300;
+ s->iy[1] |= (value << 0) & 0x0ff;
+ break;
+ case 0x7a: /* Input Window Y End Position 1 */
+ s->iy[1] &= 0x0ff;
+ s->iy[1] |= (value << 8) & 0x300;
+ break;
+ case 0x7c: /* Output Window X Start Position 0 */
+ s->ox[0] &= 0x300;
+ s->ox[0] |= (value << 0) & 0x0ff;
+ break;
+ case 0x7e: /* Output Window X Start Position 1 */
+ s->ox[0] &= 0x0ff;
+ s->ox[0] |= (value << 8) & 0x300;
+ break;
+ case 0x80: /* Output Window Y Start Position 0 */
+ s->oy[0] &= 0x300;
+ s->oy[0] |= (value << 0) & 0x0ff;
+ break;
+ case 0x82: /* Output Window Y Start Position 1 */
+ s->oy[0] &= 0x0ff;
+ s->oy[0] |= (value << 8) & 0x300;
+ break;
+ case 0x84: /* Output Window X End Position 0 */
+ s->ox[1] &= 0x300;
+ s->ox[1] |= (value << 0) & 0x0ff;
+ break;
+ case 0x86: /* Output Window X End Position 1 */
+ s->ox[1] &= 0x0ff;
+ s->ox[1] |= (value << 8) & 0x300;
+ break;
+ case 0x88: /* Output Window Y End Position 0 */
+ s->oy[1] &= 0x300;
+ s->oy[1] |= (value << 0) & 0x0ff;
+ break;
+ case 0x8a: /* Output Window Y End Position 1 */
+ s->oy[1] &= 0x0ff;
+ s->oy[1] |= (value << 8) & 0x300;
+ break;
+
+ case 0x8c: /* Input Data Format */
+ s->iformat = value & 0xf;
+ s->bpp = blizzard_iformat_bpp[s->iformat];
+ if (!s->bpp)
+ fprintf(stderr, "%s: Illegal or unsupported input format %x\n",
+ __func__, s->iformat);
+ break;
+ case 0x8e: /* Data Source Select */
+ s->source = value & 7;
+ /* Currently all windows will be "destructive overlays". */
+ if ((!(s->effect & (1 << 3)) && (s->ix[0] != s->ox[0] ||
+ s->iy[0] != s->oy[0] ||
+ s->ix[1] != s->ox[1] ||
+ s->iy[1] != s->oy[1])) ||
+ !((s->ix[1] - s->ix[0]) & (s->iy[1] - s->iy[0]) &
+ (s->ox[1] - s->ox[0]) & (s->oy[1] - s->oy[0]) & 1))
+ fprintf(stderr, "%s: Illegal input/output window positions\n",
+ __func__);
+
+ blizzard_transfer_setup(s);
+ break;
+
+ case 0x90: /* Display Memory Data Port */
+ if (!s->data.len && !blizzard_transfer_setup(s))
+ break;
+
+ *s->data.ptr ++ = value;
+ if (-- s->data.len == 0)
+ blizzard_window(s);
+ break;
+
+ case 0xa8: /* Border Color 0 */
+ s->border_r = value;
+ break;
+ case 0xaa: /* Border Color 1 */
+ s->border_g = value;
+ break;
+ case 0xac: /* Border Color 2 */
+ s->border_b = value;
+ break;
+
+ case 0xb4: /* Gamma Correction Enable */
+ s->gamma_config = value & 0x87;
+ break;
+ case 0xb6: /* Gamma Correction Table Index */
+ s->gamma_idx = value;
+ break;
+ case 0xb8: /* Gamma Correction Table Data */
+ s->gamma_lut[s->gamma_idx ++] = value;
+ break;
+
+ case 0xba: /* 3x3 Matrix Enable */
+ s->matrix_ena = value & 1;
+ break;
+ case 0xbc ... 0xde: /* Coefficient Registers */
+ s->matrix_coeff[(reg - 0xbc) >> 1] = value & ((reg & 2) ? 0x80 : 0xff);
+ break;
+ case 0xe0: /* 3x3 Matrix Red Offset */
+ s->matrix_r = value;
+ break;
+ case 0xe2: /* 3x3 Matrix Green Offset */
+ s->matrix_g = value;
+ break;
+ case 0xe4: /* 3x3 Matrix Blue Offset */
+ s->matrix_b = value;
+ break;
+
+ case 0xe6: /* Power-save */
+ s->pm = value & 0x83;
+ if (value & s->mode & 1)
+ fprintf(stderr, "%s: The display must be disabled before entering "
+ "Standby Mode\n", __func__);
+ break;
+ case 0xe8: /* Non-display Period Control / Status */
+ s->status = value & 0x1b;
+ break;
+ case 0xea: /* RGB Interface Control */
+ s->rgbgpio_dir = value & 0x8f;
+ break;
+ case 0xec: /* RGB Interface Status */
+ s->rgbgpio = value & 0xcf;
+ break;
+ case 0xee: /* General-purpose IO Pins Configuration */
+ s->gpio_dir = value;
+ break;
+ case 0xf0: /* General-purpose IO Pins Status / Control */
+ s->gpio = value;
+ break;
+ case 0xf2: /* GPIO Positive Edge Interrupt Trigger */
+ s->gpio_edge[0] = value;
+ break;
+ case 0xf4: /* GPIO Negative Edge Interrupt Trigger */
+ s->gpio_edge[1] = value;
+ break;
+ case 0xf6: /* GPIO Interrupt Status */
+ s->gpio_irq &= value;
+ break;
+ case 0xf8: /* GPIO Pull-down Control */
+ s->gpio_pdown = value;
+ break;
+
+ default:
+ fprintf(stderr, "%s: unknown register %02x\n", __func__, reg);
+ break;
+ }
+}
+
+uint16_t s1d13745_read(void *opaque, int dc)
+{
+ BlizzardState *s = (BlizzardState *) opaque;
+ uint16_t value = blizzard_reg_read(s, s->reg);
+
+ if (s->swallow -- > 0)
+ return 0;
+ if (dc)
+ s->reg ++;
+
+ return value;
+}
+
+void s1d13745_write(void *opaque, int dc, uint16_t value)
+{
+ BlizzardState *s = (BlizzardState *) opaque;
+
+ if (s->swallow -- > 0)
+ return;
+ if (dc) {
+ blizzard_reg_write(s, s->reg, value);
+
+ if (s->reg != 0x90 && s->reg != 0x5a && s->reg != 0xb8)
+ s->reg += 2;
+ } else
+ s->reg = value & 0xff;
+}
+
+void s1d13745_write_block(void *opaque, int dc,
+ void *buf, size_t len, int pitch)
+{
+ BlizzardState *s = (BlizzardState *) opaque;
+
+ while (len > 0) {
+ if (s->reg == 0x90 && dc &&
+ (s->data.len || blizzard_transfer_setup(s)) &&
+ len >= (s->data.len << 1)) {
+ len -= s->data.len << 1;
+ s->data.len = 0;
+ s->data.data = buf;
+ if (pitch)
+ s->data.pitch = pitch;
+ blizzard_window(s);
+ s->data.data = s->data.buf;
+ continue;
+ }
+
+ s1d13745_write(opaque, dc, *(uint16_t *) buf);
+ len -= 2;
+ buf += 2;
+ }
+}
+
+static void blizzard_update_display(void *opaque)
+{
+ BlizzardState *s = (BlizzardState *) opaque;
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ int y, bypp, bypl, bwidth;
+ uint8_t *src, *dst;
+
+ if (!s->enable)
+ return;
+
+ if (s->x != surface_width(surface) || s->y != surface_height(surface)) {
+ s->invalidate = 1;
+ qemu_console_resize(s->con, s->x, s->y);
+ surface = qemu_console_surface(s->con);
+ }
+
+ if (s->invalidate) {
+ s->invalidate = 0;
+
+ if (s->blank) {
+ bypp = surface_bytes_per_pixel(surface);
+ memset(surface_data(surface), 0, bypp * s->x * s->y);
+ return;
+ }
+
+ s->mx[0] = 0;
+ s->mx[1] = s->x;
+ s->my[0] = 0;
+ s->my[1] = s->y;
+ }
+
+ if (s->mx[1] <= s->mx[0])
+ return;
+
+ bypp = surface_bytes_per_pixel(surface);
+ bypl = bypp * s->x;
+ bwidth = bypp * (s->mx[1] - s->mx[0]);
+ y = s->my[0];
+ src = s->fb + bypl * y + bypp * s->mx[0];
+ dst = surface_data(surface) + bypl * y + bypp * s->mx[0];
+ for (; y < s->my[1]; y ++, src += bypl, dst += bypl)
+ memcpy(dst, src, bwidth);
+
+ dpy_gfx_update(s->con, s->mx[0], s->my[0],
+ s->mx[1] - s->mx[0], y - s->my[0]);
+
+ s->mx[0] = s->x;
+ s->mx[1] = 0;
+ s->my[0] = s->y;
+ s->my[1] = 0;
+}
+
+static void blizzard_draw_line16_32(uint32_t *dest,
+ const uint16_t *src, unsigned int width)
+{
+ uint16_t data;
+ unsigned int r, g, b;
+ const uint16_t *end = (const void *) src + width;
+ while (src < end) {
+ data = *src ++;
+ b = extract16(data, 0, 5) << 3;
+ g = extract16(data, 5, 6) << 2;
+ r = extract16(data, 11, 5) << 3;
+ *dest++ = rgb_to_pixel32(r, g, b);
+ }
+}
+
+static void blizzard_draw_line24mode1_32(uint32_t *dest,
+ const uint8_t *src, unsigned int width)
+{
+ /* TODO: check if SDL 24-bit planes are not in the same format and
+ * if so, use memcpy */
+ unsigned int r[2], g[2], b[2];
+ const uint8_t *end = src + width;
+ while (src < end) {
+ g[0] = *src ++;
+ r[0] = *src ++;
+ r[1] = *src ++;
+ b[0] = *src ++;
+ *dest++ = rgb_to_pixel32(r[0], g[0], b[0]);
+ b[1] = *src ++;
+ g[1] = *src ++;
+ *dest++ = rgb_to_pixel32(r[1], g[1], b[1]);
+ }
+}
+
+static void blizzard_draw_line24mode2_32(uint32_t *dest,
+ const uint8_t *src, unsigned int width)
+{
+ unsigned int r, g, b;
+ const uint8_t *end = src + width;
+ while (src < end) {
+ r = *src ++;
+ src ++;
+ b = *src ++;
+ g = *src ++;
+ *dest++ = rgb_to_pixel32(r, g, b);
+ }
+}
+
+/* No rotation */
+static blizzard_fn_t blizzard_draw_fn_32[0x10] = {
+ NULL,
+ /* RGB 5:6:5*/
+ (blizzard_fn_t) blizzard_draw_line16_32,
+ /* RGB 6:6:6 mode 1 */
+ (blizzard_fn_t) blizzard_draw_line24mode1_32,
+ /* RGB 8:8:8 mode 1 */
+ (blizzard_fn_t) blizzard_draw_line24mode1_32,
+ NULL, NULL,
+ /* RGB 6:6:6 mode 2 */
+ (blizzard_fn_t) blizzard_draw_line24mode2_32,
+ /* RGB 8:8:8 mode 2 */
+ (blizzard_fn_t) blizzard_draw_line24mode2_32,
+ /* YUV 4:2:2 */
+ NULL,
+ /* YUV 4:2:0 */
+ NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+};
+
+/* 90deg, 180deg and 270deg rotation */
+static blizzard_fn_t blizzard_draw_fn_r_32[0x10] = {
+ /* TODO */
+ [0 ... 0xf] = NULL,
+};
+
+static const GraphicHwOps blizzard_ops = {
+ .invalidate = blizzard_invalidate_display,
+ .gfx_update = blizzard_update_display,
+};
+
+void *s1d13745_init(qemu_irq gpio_int)
+{
+ BlizzardState *s = (BlizzardState *) g_malloc0(sizeof(*s));
+ DisplaySurface *surface;
+
+ s->fb = g_malloc(0x180000);
+
+ s->con = graphic_console_init(NULL, 0, &blizzard_ops, s);
+ surface = qemu_console_surface(s->con);
+
+ assert(surface_bits_per_pixel(surface) == 32);
+
+ s->line_fn_tab[0] = blizzard_draw_fn_32;
+ s->line_fn_tab[1] = blizzard_draw_fn_r_32;
+
+ blizzard_reset(s);
+
+ return s;
+}
diff --git a/hw/display/bochs-display.c b/hw/display/bochs-display.c
new file mode 100644
index 000000000..8ed734b19
--- /dev/null
+++ b/hw/display/bochs-display.c
@@ -0,0 +1,390 @@
+/*
+ * QEMU PCI bochs display adapter.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/module.h"
+#include "qemu/units.h"
+#include "hw/pci/pci.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "hw/display/bochs-vbe.h"
+#include "hw/display/edid.h"
+
+#include "qapi/error.h"
+
+#include "ui/console.h"
+#include "ui/qemu-pixman.h"
+#include "qom/object.h"
+
+typedef struct BochsDisplayMode {
+ pixman_format_code_t format;
+ uint32_t bytepp;
+ uint32_t width;
+ uint32_t height;
+ uint32_t stride;
+ uint64_t offset;
+ uint64_t size;
+} BochsDisplayMode;
+
+struct BochsDisplayState {
+ /* parent */
+ PCIDevice pci;
+
+ /* device elements */
+ QemuConsole *con;
+ MemoryRegion vram;
+ MemoryRegion mmio;
+ MemoryRegion vbe;
+ MemoryRegion qext;
+ MemoryRegion edid;
+
+ /* device config */
+ uint64_t vgamem;
+ bool enable_edid;
+ qemu_edid_info edid_info;
+ uint8_t edid_blob[256];
+
+ /* device registers */
+ uint16_t vbe_regs[VBE_DISPI_INDEX_NB];
+ bool big_endian_fb;
+
+ /* device state */
+ BochsDisplayMode mode;
+};
+
+#define TYPE_BOCHS_DISPLAY "bochs-display"
+OBJECT_DECLARE_SIMPLE_TYPE(BochsDisplayState, BOCHS_DISPLAY)
+
+static const VMStateDescription vmstate_bochs_display = {
+ .name = "bochs-display",
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(pci, BochsDisplayState),
+ VMSTATE_UINT16_ARRAY(vbe_regs, BochsDisplayState, VBE_DISPI_INDEX_NB),
+ VMSTATE_BOOL(big_endian_fb, BochsDisplayState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static uint64_t bochs_display_vbe_read(void *ptr, hwaddr addr,
+ unsigned size)
+{
+ BochsDisplayState *s = ptr;
+ unsigned int index = addr >> 1;
+
+ switch (index) {
+ case VBE_DISPI_INDEX_ID:
+ return VBE_DISPI_ID5;
+ case VBE_DISPI_INDEX_VIDEO_MEMORY_64K:
+ return s->vgamem / (64 * KiB);
+ }
+
+ if (index >= ARRAY_SIZE(s->vbe_regs)) {
+ return -1;
+ }
+ return s->vbe_regs[index];
+}
+
+static void bochs_display_vbe_write(void *ptr, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ BochsDisplayState *s = ptr;
+ unsigned int index = addr >> 1;
+
+ if (index >= ARRAY_SIZE(s->vbe_regs)) {
+ return;
+ }
+ s->vbe_regs[index] = val;
+}
+
+static const MemoryRegionOps bochs_display_vbe_ops = {
+ .read = bochs_display_vbe_read,
+ .write = bochs_display_vbe_write,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 4,
+ .impl.min_access_size = 2,
+ .impl.max_access_size = 2,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static uint64_t bochs_display_qext_read(void *ptr, hwaddr addr,
+ unsigned size)
+{
+ BochsDisplayState *s = ptr;
+
+ switch (addr) {
+ case PCI_VGA_QEXT_REG_SIZE:
+ return PCI_VGA_QEXT_SIZE;
+ case PCI_VGA_QEXT_REG_BYTEORDER:
+ return s->big_endian_fb ?
+ PCI_VGA_QEXT_BIG_ENDIAN : PCI_VGA_QEXT_LITTLE_ENDIAN;
+ default:
+ return 0;
+ }
+}
+
+static void bochs_display_qext_write(void *ptr, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ BochsDisplayState *s = ptr;
+
+ switch (addr) {
+ case PCI_VGA_QEXT_REG_BYTEORDER:
+ if (val == PCI_VGA_QEXT_BIG_ENDIAN) {
+ s->big_endian_fb = true;
+ }
+ if (val == PCI_VGA_QEXT_LITTLE_ENDIAN) {
+ s->big_endian_fb = false;
+ }
+ break;
+ }
+}
+
+static const MemoryRegionOps bochs_display_qext_ops = {
+ .read = bochs_display_qext_read,
+ .write = bochs_display_qext_write,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 4,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static int bochs_display_get_mode(BochsDisplayState *s,
+ BochsDisplayMode *mode)
+{
+ uint16_t *vbe = s->vbe_regs;
+ uint32_t virt_width;
+
+ if (!(vbe[VBE_DISPI_INDEX_ENABLE] & VBE_DISPI_ENABLED)) {
+ return -1;
+ }
+
+ memset(mode, 0, sizeof(*mode));
+ switch (vbe[VBE_DISPI_INDEX_BPP]) {
+ case 16:
+ /* best effort: support native endianess only */
+ mode->format = PIXMAN_r5g6b5;
+ mode->bytepp = 2;
+ break;
+ case 32:
+ mode->format = s->big_endian_fb
+ ? PIXMAN_BE_x8r8g8b8
+ : PIXMAN_LE_x8r8g8b8;
+ mode->bytepp = 4;
+ break;
+ default:
+ return -1;
+ }
+
+ mode->width = vbe[VBE_DISPI_INDEX_XRES];
+ mode->height = vbe[VBE_DISPI_INDEX_YRES];
+ virt_width = vbe[VBE_DISPI_INDEX_VIRT_WIDTH];
+ if (virt_width < mode->width) {
+ virt_width = mode->width;
+ }
+ mode->stride = virt_width * mode->bytepp;
+ mode->size = (uint64_t)mode->stride * mode->height;
+ mode->offset = ((uint64_t)vbe[VBE_DISPI_INDEX_X_OFFSET] * mode->bytepp +
+ (uint64_t)vbe[VBE_DISPI_INDEX_Y_OFFSET] * mode->stride);
+
+ if (mode->width < 64 || mode->height < 64) {
+ return -1;
+ }
+ if (mode->offset + mode->size > s->vgamem) {
+ return -1;
+ }
+ return 0;
+}
+
+static void bochs_display_update(void *opaque)
+{
+ BochsDisplayState *s = opaque;
+ DirtyBitmapSnapshot *snap = NULL;
+ bool full_update = false;
+ BochsDisplayMode mode;
+ DisplaySurface *ds;
+ uint8_t *ptr;
+ bool dirty;
+ int y, ys, ret;
+
+ ret = bochs_display_get_mode(s, &mode);
+ if (ret < 0) {
+ /* no (valid) video mode */
+ return;
+ }
+
+ if (memcmp(&s->mode, &mode, sizeof(mode)) != 0) {
+ /* video mode switch */
+ s->mode = mode;
+ ptr = memory_region_get_ram_ptr(&s->vram);
+ ds = qemu_create_displaysurface_from(mode.width,
+ mode.height,
+ mode.format,
+ mode.stride,
+ ptr + mode.offset);
+ dpy_gfx_replace_surface(s->con, ds);
+ full_update = true;
+ }
+
+ if (full_update) {
+ dpy_gfx_update_full(s->con);
+ } else {
+ snap = memory_region_snapshot_and_clear_dirty(&s->vram,
+ mode.offset, mode.size,
+ DIRTY_MEMORY_VGA);
+ ys = -1;
+ for (y = 0; y < mode.height; y++) {
+ dirty = memory_region_snapshot_get_dirty(&s->vram, snap,
+ mode.offset + mode.stride * y,
+ mode.stride);
+ if (dirty && ys < 0) {
+ ys = y;
+ }
+ if (!dirty && ys >= 0) {
+ dpy_gfx_update(s->con, 0, ys,
+ mode.width, y - ys);
+ ys = -1;
+ }
+ }
+ if (ys >= 0) {
+ dpy_gfx_update(s->con, 0, ys,
+ mode.width, y - ys);
+ }
+
+ g_free(snap);
+ }
+}
+
+static const GraphicHwOps bochs_display_gfx_ops = {
+ .gfx_update = bochs_display_update,
+};
+
+static void bochs_display_realize(PCIDevice *dev, Error **errp)
+{
+ BochsDisplayState *s = BOCHS_DISPLAY(dev);
+ Object *obj = OBJECT(dev);
+ int ret;
+
+ if (s->vgamem < 4 * MiB) {
+ error_setg(errp, "bochs-display: video memory too small");
+ return;
+ }
+ if (s->vgamem > 256 * MiB) {
+ error_setg(errp, "bochs-display: video memory too big");
+ return;
+ }
+ s->vgamem = pow2ceil(s->vgamem);
+
+ s->con = graphic_console_init(DEVICE(dev), 0, &bochs_display_gfx_ops, s);
+
+ memory_region_init_ram(&s->vram, obj, "bochs-display-vram", s->vgamem,
+ &error_fatal);
+ memory_region_init_io(&s->vbe, obj, &bochs_display_vbe_ops, s,
+ "bochs dispi interface", PCI_VGA_BOCHS_SIZE);
+ memory_region_init_io(&s->qext, obj, &bochs_display_qext_ops, s,
+ "qemu extended regs", PCI_VGA_QEXT_SIZE);
+
+ memory_region_init_io(&s->mmio, obj, &unassigned_io_ops, NULL,
+ "bochs-display-mmio", PCI_VGA_MMIO_SIZE);
+ memory_region_add_subregion(&s->mmio, PCI_VGA_BOCHS_OFFSET, &s->vbe);
+ memory_region_add_subregion(&s->mmio, PCI_VGA_QEXT_OFFSET, &s->qext);
+
+ pci_set_byte(&s->pci.config[PCI_REVISION_ID], 2);
+ pci_register_bar(&s->pci, 0, PCI_BASE_ADDRESS_MEM_PREFETCH, &s->vram);
+ pci_register_bar(&s->pci, 2, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->mmio);
+
+ if (s->enable_edid) {
+ qemu_edid_generate(s->edid_blob, sizeof(s->edid_blob), &s->edid_info);
+ qemu_edid_region_io(&s->edid, obj, s->edid_blob, sizeof(s->edid_blob));
+ memory_region_add_subregion(&s->mmio, 0, &s->edid);
+ }
+
+ if (pci_bus_is_express(pci_get_bus(dev))) {
+ ret = pcie_endpoint_cap_init(dev, 0x80);
+ assert(ret > 0);
+ } else {
+ dev->cap_present &= ~QEMU_PCI_CAP_EXPRESS;
+ }
+
+ memory_region_set_log(&s->vram, true, DIRTY_MEMORY_VGA);
+}
+
+static bool bochs_display_get_big_endian_fb(Object *obj, Error **errp)
+{
+ BochsDisplayState *s = BOCHS_DISPLAY(obj);
+
+ return s->big_endian_fb;
+}
+
+static void bochs_display_set_big_endian_fb(Object *obj, bool value,
+ Error **errp)
+{
+ BochsDisplayState *s = BOCHS_DISPLAY(obj);
+
+ s->big_endian_fb = value;
+}
+
+static void bochs_display_init(Object *obj)
+{
+ PCIDevice *dev = PCI_DEVICE(obj);
+
+ /* Expose framebuffer byteorder via QOM */
+ object_property_add_bool(obj, "big-endian-framebuffer",
+ bochs_display_get_big_endian_fb,
+ bochs_display_set_big_endian_fb);
+
+ dev->cap_present |= QEMU_PCI_CAP_EXPRESS;
+}
+
+static void bochs_display_exit(PCIDevice *dev)
+{
+ BochsDisplayState *s = BOCHS_DISPLAY(dev);
+
+ graphic_console_close(s->con);
+}
+
+static Property bochs_display_properties[] = {
+ DEFINE_PROP_SIZE("vgamem", BochsDisplayState, vgamem, 16 * MiB),
+ DEFINE_PROP_BOOL("edid", BochsDisplayState, enable_edid, true),
+ DEFINE_EDID_PROPERTIES(BochsDisplayState, edid_info),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void bochs_display_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->class_id = PCI_CLASS_DISPLAY_OTHER;
+ k->vendor_id = PCI_VENDOR_ID_QEMU;
+ k->device_id = PCI_DEVICE_ID_QEMU_VGA;
+
+ k->realize = bochs_display_realize;
+ k->romfile = "vgabios-bochs-display.bin";
+ k->exit = bochs_display_exit;
+ dc->vmsd = &vmstate_bochs_display;
+ device_class_set_props(dc, bochs_display_properties);
+ set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories);
+}
+
+static const TypeInfo bochs_display_type_info = {
+ .name = TYPE_BOCHS_DISPLAY,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(BochsDisplayState),
+ .instance_init = bochs_display_init,
+ .class_init = bochs_display_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { INTERFACE_PCIE_DEVICE },
+ { INTERFACE_CONVENTIONAL_PCI_DEVICE },
+ { },
+ },
+};
+
+static void bochs_display_register_types(void)
+{
+ type_register_static(&bochs_display_type_info);
+}
+
+type_init(bochs_display_register_types)
diff --git a/hw/display/cg3.c b/hw/display/cg3.c
new file mode 100644
index 000000000..4b7e78d91
--- /dev/null
+++ b/hw/display/cg3.c
@@ -0,0 +1,396 @@
+/*
+ * QEMU CG3 Frame buffer
+ *
+ * Copyright (c) 2012 Bob Breuer
+ * Copyright (c) 2013 Mark Cave-Ayland
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu-common.h"
+#include "qemu/datadir.h"
+#include "qapi/error.h"
+#include "qemu/error-report.h"
+#include "ui/console.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "hw/irq.h"
+#include "hw/loader.h"
+#include "hw/qdev-properties.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "trace.h"
+#include "qom/object.h"
+
+/* Change to 1 to enable debugging */
+#define DEBUG_CG3 0
+
+#define CG3_ROM_FILE "QEMU,cgthree.bin"
+#define FCODE_MAX_ROM_SIZE 0x10000
+
+#define CG3_REG_SIZE 0x20
+
+#define CG3_REG_BT458_ADDR 0x0
+#define CG3_REG_BT458_COLMAP 0x4
+#define CG3_REG_FBC_CTRL 0x10
+#define CG3_REG_FBC_STATUS 0x11
+#define CG3_REG_FBC_CURSTART 0x12
+#define CG3_REG_FBC_CUREND 0x13
+#define CG3_REG_FBC_VCTRL 0x14
+
+/* Control register flags */
+#define CG3_CR_ENABLE_INTS 0x80
+
+/* Status register flags */
+#define CG3_SR_PENDING_INT 0x80
+#define CG3_SR_1152_900_76_B 0x60
+#define CG3_SR_ID_COLOR 0x01
+
+#define CG3_VRAM_SIZE 0x100000
+#define CG3_VRAM_OFFSET 0x800000
+
+#define TYPE_CG3 "cgthree"
+OBJECT_DECLARE_SIMPLE_TYPE(CG3State, CG3)
+
+struct CG3State {
+ SysBusDevice parent_obj;
+
+ QemuConsole *con;
+ qemu_irq irq;
+ hwaddr prom_addr;
+ MemoryRegion vram_mem;
+ MemoryRegion rom;
+ MemoryRegion reg;
+ uint32_t vram_size;
+ int full_update;
+ uint8_t regs[16];
+ uint8_t r[256], g[256], b[256];
+ uint16_t width, height, depth;
+ uint8_t dac_index, dac_state;
+};
+
+static void cg3_update_display(void *opaque)
+{
+ CG3State *s = opaque;
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ const uint8_t *pix;
+ uint32_t *data;
+ uint32_t dval;
+ int x, y, y_start;
+ unsigned int width, height;
+ ram_addr_t page;
+ DirtyBitmapSnapshot *snap = NULL;
+
+ if (surface_bits_per_pixel(surface) != 32) {
+ return;
+ }
+ width = s->width;
+ height = s->height;
+
+ y_start = -1;
+ pix = memory_region_get_ram_ptr(&s->vram_mem);
+ data = (uint32_t *)surface_data(surface);
+
+ if (!s->full_update) {
+ snap = memory_region_snapshot_and_clear_dirty(&s->vram_mem, 0x0,
+ memory_region_size(&s->vram_mem),
+ DIRTY_MEMORY_VGA);
+ }
+
+ for (y = 0; y < height; y++) {
+ int update;
+
+ page = (ram_addr_t)y * width;
+
+ if (s->full_update) {
+ update = 1;
+ } else {
+ update = memory_region_snapshot_get_dirty(&s->vram_mem, snap, page,
+ width);
+ }
+
+ if (update) {
+ if (y_start < 0) {
+ y_start = y;
+ }
+
+ for (x = 0; x < width; x++) {
+ dval = *pix++;
+ dval = (s->r[dval] << 16) | (s->g[dval] << 8) | s->b[dval];
+ *data++ = dval;
+ }
+ } else {
+ if (y_start >= 0) {
+ dpy_gfx_update(s->con, 0, y_start, width, y - y_start);
+ y_start = -1;
+ }
+ pix += width;
+ data += width;
+ }
+ }
+ s->full_update = 0;
+ if (y_start >= 0) {
+ dpy_gfx_update(s->con, 0, y_start, width, y - y_start);
+ }
+ /* vsync interrupt? */
+ if (s->regs[0] & CG3_CR_ENABLE_INTS) {
+ s->regs[1] |= CG3_SR_PENDING_INT;
+ qemu_irq_raise(s->irq);
+ }
+ g_free(snap);
+}
+
+static void cg3_invalidate_display(void *opaque)
+{
+ CG3State *s = opaque;
+
+ memory_region_set_dirty(&s->vram_mem, 0, CG3_VRAM_SIZE);
+}
+
+static uint64_t cg3_reg_read(void *opaque, hwaddr addr, unsigned size)
+{
+ CG3State *s = opaque;
+ int val;
+
+ switch (addr) {
+ case CG3_REG_BT458_ADDR:
+ case CG3_REG_BT458_COLMAP:
+ val = 0;
+ break;
+ case CG3_REG_FBC_CTRL:
+ val = s->regs[0];
+ break;
+ case CG3_REG_FBC_STATUS:
+ /* monitor ID 6, board type = 1 (color) */
+ val = s->regs[1] | CG3_SR_1152_900_76_B | CG3_SR_ID_COLOR;
+ break;
+ case CG3_REG_FBC_CURSTART ... CG3_REG_SIZE - 1:
+ val = s->regs[addr - 0x10];
+ break;
+ default:
+ qemu_log_mask(LOG_UNIMP,
+ "cg3: Unimplemented register read "
+ "reg 0x%" HWADDR_PRIx " size 0x%x\n",
+ addr, size);
+ val = 0;
+ break;
+ }
+ trace_cg3_read(addr, val, size);
+
+ return val;
+}
+
+static void cg3_reg_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ CG3State *s = opaque;
+ uint8_t regval;
+ int i;
+
+ trace_cg3_write(addr, val, size);
+ switch (addr) {
+ case CG3_REG_BT458_ADDR:
+ s->dac_index = val;
+ s->dac_state = 0;
+ break;
+ case CG3_REG_BT458_COLMAP:
+ /* This register can be written to as either a long word or a byte */
+ if (size == 1) {
+ val <<= 24;
+ }
+
+ for (i = 0; i < size; i++) {
+ regval = val >> 24;
+
+ switch (s->dac_state) {
+ case 0:
+ s->r[s->dac_index] = regval;
+ s->dac_state++;
+ break;
+ case 1:
+ s->g[s->dac_index] = regval;
+ s->dac_state++;
+ break;
+ case 2:
+ s->b[s->dac_index] = regval;
+ /* Index autoincrement */
+ s->dac_index = (s->dac_index + 1) & 0xff;
+ /* fall through */
+ default:
+ s->dac_state = 0;
+ break;
+ }
+ val <<= 8;
+ }
+ s->full_update = 1;
+ break;
+ case CG3_REG_FBC_CTRL:
+ s->regs[0] = val;
+ break;
+ case CG3_REG_FBC_STATUS:
+ if (s->regs[1] & CG3_SR_PENDING_INT) {
+ /* clear interrupt */
+ s->regs[1] &= ~CG3_SR_PENDING_INT;
+ qemu_irq_lower(s->irq);
+ }
+ break;
+ case CG3_REG_FBC_CURSTART ... CG3_REG_SIZE - 1:
+ s->regs[addr - 0x10] = val;
+ break;
+ default:
+ qemu_log_mask(LOG_UNIMP,
+ "cg3: Unimplemented register write "
+ "reg 0x%" HWADDR_PRIx " size 0x%x value 0x%" PRIx64 "\n",
+ addr, size, val);
+ break;
+ }
+}
+
+static const MemoryRegionOps cg3_reg_ops = {
+ .read = cg3_reg_read,
+ .write = cg3_reg_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ },
+};
+
+static const GraphicHwOps cg3_ops = {
+ .invalidate = cg3_invalidate_display,
+ .gfx_update = cg3_update_display,
+};
+
+static void cg3_initfn(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ CG3State *s = CG3(obj);
+
+ memory_region_init_rom_nomigrate(&s->rom, obj, "cg3.prom",
+ FCODE_MAX_ROM_SIZE, &error_fatal);
+ sysbus_init_mmio(sbd, &s->rom);
+
+ memory_region_init_io(&s->reg, obj, &cg3_reg_ops, s, "cg3.reg",
+ CG3_REG_SIZE);
+ sysbus_init_mmio(sbd, &s->reg);
+}
+
+static void cg3_realizefn(DeviceState *dev, Error **errp)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+ CG3State *s = CG3(dev);
+ int ret;
+ char *fcode_filename;
+
+ /* FCode ROM */
+ vmstate_register_ram_global(&s->rom);
+ fcode_filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, CG3_ROM_FILE);
+ if (fcode_filename) {
+ ret = load_image_mr(fcode_filename, &s->rom);
+ g_free(fcode_filename);
+ if (ret < 0 || ret > FCODE_MAX_ROM_SIZE) {
+ warn_report("cg3: could not load prom '%s'", CG3_ROM_FILE);
+ }
+ }
+
+ memory_region_init_ram(&s->vram_mem, NULL, "cg3.vram", s->vram_size,
+ &error_fatal);
+ memory_region_set_log(&s->vram_mem, true, DIRTY_MEMORY_VGA);
+ sysbus_init_mmio(sbd, &s->vram_mem);
+
+ sysbus_init_irq(sbd, &s->irq);
+
+ s->con = graphic_console_init(dev, 0, &cg3_ops, s);
+ qemu_console_resize(s->con, s->width, s->height);
+}
+
+static int vmstate_cg3_post_load(void *opaque, int version_id)
+{
+ CG3State *s = opaque;
+
+ cg3_invalidate_display(s);
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_cg3 = {
+ .name = "cg3",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = vmstate_cg3_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT16(height, CG3State),
+ VMSTATE_UINT16(width, CG3State),
+ VMSTATE_UINT16(depth, CG3State),
+ VMSTATE_BUFFER(r, CG3State),
+ VMSTATE_BUFFER(g, CG3State),
+ VMSTATE_BUFFER(b, CG3State),
+ VMSTATE_UINT8(dac_index, CG3State),
+ VMSTATE_UINT8(dac_state, CG3State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void cg3_reset(DeviceState *d)
+{
+ CG3State *s = CG3(d);
+
+ /* Initialize palette */
+ memset(s->r, 0, 256);
+ memset(s->g, 0, 256);
+ memset(s->b, 0, 256);
+
+ s->dac_state = 0;
+ s->full_update = 1;
+ qemu_irq_lower(s->irq);
+}
+
+static Property cg3_properties[] = {
+ DEFINE_PROP_UINT32("vram-size", CG3State, vram_size, -1),
+ DEFINE_PROP_UINT16("width", CG3State, width, -1),
+ DEFINE_PROP_UINT16("height", CG3State, height, -1),
+ DEFINE_PROP_UINT16("depth", CG3State, depth, -1),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void cg3_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = cg3_realizefn;
+ dc->reset = cg3_reset;
+ dc->vmsd = &vmstate_cg3;
+ device_class_set_props(dc, cg3_properties);
+}
+
+static const TypeInfo cg3_info = {
+ .name = TYPE_CG3,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(CG3State),
+ .instance_init = cg3_initfn,
+ .class_init = cg3_class_init,
+};
+
+static void cg3_register_types(void)
+{
+ type_register_static(&cg3_info);
+}
+
+type_init(cg3_register_types)
diff --git a/hw/display/cirrus_vga.c b/hw/display/cirrus_vga.c
new file mode 100644
index 000000000..fdca6ca65
--- /dev/null
+++ b/hw/display/cirrus_vga.c
@@ -0,0 +1,3024 @@
+/*
+ * QEMU Cirrus CLGD 54xx VGA Emulator.
+ *
+ * Copyright (c) 2004 Fabrice Bellard
+ * Copyright (c) 2004 Makoto Suzuki (suzu)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+/*
+ * Reference: Finn Thogersons' VGADOC4b:
+ *
+ * http://web.archive.org/web/20021019054927/http://home.worldonline.dk/finth/
+ *
+ * VGADOC4b.ZIP content available at:
+ *
+ * https://pdos.csail.mit.edu/6.828/2005/readings/hardware/vgadoc
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/module.h"
+#include "qemu/units.h"
+#include "qemu/log.h"
+#include "sysemu/reset.h"
+#include "qapi/error.h"
+#include "trace.h"
+#include "hw/pci/pci.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "ui/pixel_ops.h"
+#include "cirrus_vga_internal.h"
+#include "qom/object.h"
+
+/*
+ * TODO:
+ * - destination write mask support not complete (bits 5..7)
+ * - optimize linear mappings
+ * - optimize bitblt functions
+ */
+
+//#define DEBUG_CIRRUS
+
+/***************************************
+ *
+ * definitions
+ *
+ ***************************************/
+
+// sequencer 0x07
+#define CIRRUS_SR7_BPP_VGA 0x00
+#define CIRRUS_SR7_BPP_SVGA 0x01
+#define CIRRUS_SR7_BPP_MASK 0x0e
+#define CIRRUS_SR7_BPP_8 0x00
+#define CIRRUS_SR7_BPP_16_DOUBLEVCLK 0x02
+#define CIRRUS_SR7_BPP_24 0x04
+#define CIRRUS_SR7_BPP_16 0x06
+#define CIRRUS_SR7_BPP_32 0x08
+#define CIRRUS_SR7_ISAADDR_MASK 0xe0
+
+// sequencer 0x0f
+#define CIRRUS_MEMSIZE_512k 0x08
+#define CIRRUS_MEMSIZE_1M 0x10
+#define CIRRUS_MEMSIZE_2M 0x18
+#define CIRRUS_MEMFLAGS_BANKSWITCH 0x80 // bank switching is enabled.
+
+// sequencer 0x12
+#define CIRRUS_CURSOR_SHOW 0x01
+#define CIRRUS_CURSOR_HIDDENPEL 0x02
+#define CIRRUS_CURSOR_LARGE 0x04 // 64x64 if set, 32x32 if clear
+
+// sequencer 0x17
+#define CIRRUS_BUSTYPE_VLBFAST 0x10
+#define CIRRUS_BUSTYPE_PCI 0x20
+#define CIRRUS_BUSTYPE_VLBSLOW 0x30
+#define CIRRUS_BUSTYPE_ISA 0x38
+#define CIRRUS_MMIO_ENABLE 0x04
+#define CIRRUS_MMIO_USE_PCIADDR 0x40 // 0xb8000 if cleared.
+#define CIRRUS_MEMSIZEEXT_DOUBLE 0x80
+
+// control 0x0b
+#define CIRRUS_BANKING_DUAL 0x01
+#define CIRRUS_BANKING_GRANULARITY_16K 0x20 // set:16k, clear:4k
+
+// control 0x30
+#define CIRRUS_BLTMODE_BACKWARDS 0x01
+#define CIRRUS_BLTMODE_MEMSYSDEST 0x02
+#define CIRRUS_BLTMODE_MEMSYSSRC 0x04
+#define CIRRUS_BLTMODE_TRANSPARENTCOMP 0x08
+#define CIRRUS_BLTMODE_PATTERNCOPY 0x40
+#define CIRRUS_BLTMODE_COLOREXPAND 0x80
+#define CIRRUS_BLTMODE_PIXELWIDTHMASK 0x30
+#define CIRRUS_BLTMODE_PIXELWIDTH8 0x00
+#define CIRRUS_BLTMODE_PIXELWIDTH16 0x10
+#define CIRRUS_BLTMODE_PIXELWIDTH24 0x20
+#define CIRRUS_BLTMODE_PIXELWIDTH32 0x30
+
+// control 0x31
+#define CIRRUS_BLT_BUSY 0x01
+#define CIRRUS_BLT_START 0x02
+#define CIRRUS_BLT_RESET 0x04
+#define CIRRUS_BLT_FIFOUSED 0x10
+#define CIRRUS_BLT_AUTOSTART 0x80
+
+// control 0x32
+#define CIRRUS_ROP_0 0x00
+#define CIRRUS_ROP_SRC_AND_DST 0x05
+#define CIRRUS_ROP_NOP 0x06
+#define CIRRUS_ROP_SRC_AND_NOTDST 0x09
+#define CIRRUS_ROP_NOTDST 0x0b
+#define CIRRUS_ROP_SRC 0x0d
+#define CIRRUS_ROP_1 0x0e
+#define CIRRUS_ROP_NOTSRC_AND_DST 0x50
+#define CIRRUS_ROP_SRC_XOR_DST 0x59
+#define CIRRUS_ROP_SRC_OR_DST 0x6d
+#define CIRRUS_ROP_NOTSRC_OR_NOTDST 0x90
+#define CIRRUS_ROP_SRC_NOTXOR_DST 0x95
+#define CIRRUS_ROP_SRC_OR_NOTDST 0xad
+#define CIRRUS_ROP_NOTSRC 0xd0
+#define CIRRUS_ROP_NOTSRC_OR_DST 0xd6
+#define CIRRUS_ROP_NOTSRC_AND_NOTDST 0xda
+
+#define CIRRUS_ROP_NOP_INDEX 2
+#define CIRRUS_ROP_SRC_INDEX 5
+
+// control 0x33
+#define CIRRUS_BLTMODEEXT_SOLIDFILL 0x04
+#define CIRRUS_BLTMODEEXT_COLOREXPINV 0x02
+#define CIRRUS_BLTMODEEXT_DWORDGRANULARITY 0x01
+
+// memory-mapped IO
+#define CIRRUS_MMIO_BLTBGCOLOR 0x00 // dword
+#define CIRRUS_MMIO_BLTFGCOLOR 0x04 // dword
+#define CIRRUS_MMIO_BLTWIDTH 0x08 // word
+#define CIRRUS_MMIO_BLTHEIGHT 0x0a // word
+#define CIRRUS_MMIO_BLTDESTPITCH 0x0c // word
+#define CIRRUS_MMIO_BLTSRCPITCH 0x0e // word
+#define CIRRUS_MMIO_BLTDESTADDR 0x10 // dword
+#define CIRRUS_MMIO_BLTSRCADDR 0x14 // dword
+#define CIRRUS_MMIO_BLTWRITEMASK 0x17 // byte
+#define CIRRUS_MMIO_BLTMODE 0x18 // byte
+#define CIRRUS_MMIO_BLTROP 0x1a // byte
+#define CIRRUS_MMIO_BLTMODEEXT 0x1b // byte
+#define CIRRUS_MMIO_BLTTRANSPARENTCOLOR 0x1c // word?
+#define CIRRUS_MMIO_BLTTRANSPARENTCOLORMASK 0x20 // word?
+#define CIRRUS_MMIO_LINEARDRAW_START_X 0x24 // word
+#define CIRRUS_MMIO_LINEARDRAW_START_Y 0x26 // word
+#define CIRRUS_MMIO_LINEARDRAW_END_X 0x28 // word
+#define CIRRUS_MMIO_LINEARDRAW_END_Y 0x2a // word
+#define CIRRUS_MMIO_LINEARDRAW_LINESTYLE_INC 0x2c // byte
+#define CIRRUS_MMIO_LINEARDRAW_LINESTYLE_ROLLOVER 0x2d // byte
+#define CIRRUS_MMIO_LINEARDRAW_LINESTYLE_MASK 0x2e // byte
+#define CIRRUS_MMIO_LINEARDRAW_LINESTYLE_ACCUM 0x2f // byte
+#define CIRRUS_MMIO_BRESENHAM_K1 0x30 // word
+#define CIRRUS_MMIO_BRESENHAM_K3 0x32 // word
+#define CIRRUS_MMIO_BRESENHAM_ERROR 0x34 // word
+#define CIRRUS_MMIO_BRESENHAM_DELTA_MAJOR 0x36 // word
+#define CIRRUS_MMIO_BRESENHAM_DIRECTION 0x38 // byte
+#define CIRRUS_MMIO_LINEDRAW_MODE 0x39 // byte
+#define CIRRUS_MMIO_BLTSTATUS 0x40 // byte
+
+#define CIRRUS_PNPMMIO_SIZE 0x1000
+
+typedef void (*cirrus_fill_t)(struct CirrusVGAState *s,
+ uint32_t dstaddr, int dst_pitch,
+ int width, int height);
+
+struct PCICirrusVGAState {
+ PCIDevice dev;
+ CirrusVGAState cirrus_vga;
+};
+
+#define TYPE_PCI_CIRRUS_VGA "cirrus-vga"
+OBJECT_DECLARE_SIMPLE_TYPE(PCICirrusVGAState, PCI_CIRRUS_VGA)
+
+static uint8_t rop_to_index[256];
+
+/***************************************
+ *
+ * prototypes.
+ *
+ ***************************************/
+
+
+static void cirrus_bitblt_reset(CirrusVGAState *s);
+static void cirrus_update_memory_access(CirrusVGAState *s);
+
+/***************************************
+ *
+ * raster operations
+ *
+ ***************************************/
+
+static bool blit_region_is_unsafe(struct CirrusVGAState *s,
+ int32_t pitch, int32_t addr)
+{
+ if (!pitch) {
+ return true;
+ }
+ if (pitch < 0) {
+ int64_t min = addr
+ + ((int64_t)s->cirrus_blt_height - 1) * pitch
+ - s->cirrus_blt_width;
+ if (min < -1 || addr >= s->vga.vram_size) {
+ return true;
+ }
+ } else {
+ int64_t max = addr
+ + ((int64_t)s->cirrus_blt_height-1) * pitch
+ + s->cirrus_blt_width;
+ if (max > s->vga.vram_size) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static bool blit_is_unsafe(struct CirrusVGAState *s, bool dst_only)
+{
+ /* should be the case, see cirrus_bitblt_start */
+ assert(s->cirrus_blt_width > 0);
+ assert(s->cirrus_blt_height > 0);
+
+ if (s->cirrus_blt_width > CIRRUS_BLTBUFSIZE) {
+ return true;
+ }
+
+ if (blit_region_is_unsafe(s, s->cirrus_blt_dstpitch,
+ s->cirrus_blt_dstaddr)) {
+ return true;
+ }
+ if (dst_only) {
+ return false;
+ }
+ if (blit_region_is_unsafe(s, s->cirrus_blt_srcpitch,
+ s->cirrus_blt_srcaddr)) {
+ return true;
+ }
+
+ return false;
+}
+
+static void cirrus_bitblt_rop_nop(CirrusVGAState *s,
+ uint32_t dstaddr, uint32_t srcaddr,
+ int dstpitch,int srcpitch,
+ int bltwidth,int bltheight)
+{
+}
+
+static void cirrus_bitblt_fill_nop(CirrusVGAState *s,
+ uint32_t dstaddr,
+ int dstpitch, int bltwidth,int bltheight)
+{
+}
+
+static inline uint8_t cirrus_src(CirrusVGAState *s, uint32_t srcaddr)
+{
+ if (s->cirrus_srccounter) {
+ /* cputovideo */
+ return s->cirrus_bltbuf[srcaddr & (CIRRUS_BLTBUFSIZE - 1)];
+ } else {
+ /* videotovideo */
+ return s->vga.vram_ptr[srcaddr & s->cirrus_addr_mask];
+ }
+}
+
+static inline uint16_t cirrus_src16(CirrusVGAState *s, uint32_t srcaddr)
+{
+ uint16_t *src;
+
+ if (s->cirrus_srccounter) {
+ /* cputovideo */
+ src = (void *)&s->cirrus_bltbuf[srcaddr & (CIRRUS_BLTBUFSIZE - 1) & ~1];
+ } else {
+ /* videotovideo */
+ src = (void *)&s->vga.vram_ptr[srcaddr & s->cirrus_addr_mask & ~1];
+ }
+ return *src;
+}
+
+static inline uint32_t cirrus_src32(CirrusVGAState *s, uint32_t srcaddr)
+{
+ uint32_t *src;
+
+ if (s->cirrus_srccounter) {
+ /* cputovideo */
+ src = (void *)&s->cirrus_bltbuf[srcaddr & (CIRRUS_BLTBUFSIZE - 1) & ~3];
+ } else {
+ /* videotovideo */
+ src = (void *)&s->vga.vram_ptr[srcaddr & s->cirrus_addr_mask & ~3];
+ }
+ return *src;
+}
+
+#define ROP_NAME 0
+#define ROP_FN(d, s) 0
+#include "cirrus_vga_rop.h"
+
+#define ROP_NAME src_and_dst
+#define ROP_FN(d, s) (s) & (d)
+#include "cirrus_vga_rop.h"
+
+#define ROP_NAME src_and_notdst
+#define ROP_FN(d, s) (s) & (~(d))
+#include "cirrus_vga_rop.h"
+
+#define ROP_NAME notdst
+#define ROP_FN(d, s) ~(d)
+#include "cirrus_vga_rop.h"
+
+#define ROP_NAME src
+#define ROP_FN(d, s) s
+#include "cirrus_vga_rop.h"
+
+#define ROP_NAME 1
+#define ROP_FN(d, s) ~0
+#include "cirrus_vga_rop.h"
+
+#define ROP_NAME notsrc_and_dst
+#define ROP_FN(d, s) (~(s)) & (d)
+#include "cirrus_vga_rop.h"
+
+#define ROP_NAME src_xor_dst
+#define ROP_FN(d, s) (s) ^ (d)
+#include "cirrus_vga_rop.h"
+
+#define ROP_NAME src_or_dst
+#define ROP_FN(d, s) (s) | (d)
+#include "cirrus_vga_rop.h"
+
+#define ROP_NAME notsrc_or_notdst
+#define ROP_FN(d, s) (~(s)) | (~(d))
+#include "cirrus_vga_rop.h"
+
+#define ROP_NAME src_notxor_dst
+#define ROP_FN(d, s) ~((s) ^ (d))
+#include "cirrus_vga_rop.h"
+
+#define ROP_NAME src_or_notdst
+#define ROP_FN(d, s) (s) | (~(d))
+#include "cirrus_vga_rop.h"
+
+#define ROP_NAME notsrc
+#define ROP_FN(d, s) (~(s))
+#include "cirrus_vga_rop.h"
+
+#define ROP_NAME notsrc_or_dst
+#define ROP_FN(d, s) (~(s)) | (d)
+#include "cirrus_vga_rop.h"
+
+#define ROP_NAME notsrc_and_notdst
+#define ROP_FN(d, s) (~(s)) & (~(d))
+#include "cirrus_vga_rop.h"
+
+static const cirrus_bitblt_rop_t cirrus_fwd_rop[16] = {
+ cirrus_bitblt_rop_fwd_0,
+ cirrus_bitblt_rop_fwd_src_and_dst,
+ cirrus_bitblt_rop_nop,
+ cirrus_bitblt_rop_fwd_src_and_notdst,
+ cirrus_bitblt_rop_fwd_notdst,
+ cirrus_bitblt_rop_fwd_src,
+ cirrus_bitblt_rop_fwd_1,
+ cirrus_bitblt_rop_fwd_notsrc_and_dst,
+ cirrus_bitblt_rop_fwd_src_xor_dst,
+ cirrus_bitblt_rop_fwd_src_or_dst,
+ cirrus_bitblt_rop_fwd_notsrc_or_notdst,
+ cirrus_bitblt_rop_fwd_src_notxor_dst,
+ cirrus_bitblt_rop_fwd_src_or_notdst,
+ cirrus_bitblt_rop_fwd_notsrc,
+ cirrus_bitblt_rop_fwd_notsrc_or_dst,
+ cirrus_bitblt_rop_fwd_notsrc_and_notdst,
+};
+
+static const cirrus_bitblt_rop_t cirrus_bkwd_rop[16] = {
+ cirrus_bitblt_rop_bkwd_0,
+ cirrus_bitblt_rop_bkwd_src_and_dst,
+ cirrus_bitblt_rop_nop,
+ cirrus_bitblt_rop_bkwd_src_and_notdst,
+ cirrus_bitblt_rop_bkwd_notdst,
+ cirrus_bitblt_rop_bkwd_src,
+ cirrus_bitblt_rop_bkwd_1,
+ cirrus_bitblt_rop_bkwd_notsrc_and_dst,
+ cirrus_bitblt_rop_bkwd_src_xor_dst,
+ cirrus_bitblt_rop_bkwd_src_or_dst,
+ cirrus_bitblt_rop_bkwd_notsrc_or_notdst,
+ cirrus_bitblt_rop_bkwd_src_notxor_dst,
+ cirrus_bitblt_rop_bkwd_src_or_notdst,
+ cirrus_bitblt_rop_bkwd_notsrc,
+ cirrus_bitblt_rop_bkwd_notsrc_or_dst,
+ cirrus_bitblt_rop_bkwd_notsrc_and_notdst,
+};
+
+#define TRANSP_ROP(name) {\
+ name ## _8,\
+ name ## _16,\
+ }
+#define TRANSP_NOP(func) {\
+ func,\
+ func,\
+ }
+
+static const cirrus_bitblt_rop_t cirrus_fwd_transp_rop[16][2] = {
+ TRANSP_ROP(cirrus_bitblt_rop_fwd_transp_0),
+ TRANSP_ROP(cirrus_bitblt_rop_fwd_transp_src_and_dst),
+ TRANSP_NOP(cirrus_bitblt_rop_nop),
+ TRANSP_ROP(cirrus_bitblt_rop_fwd_transp_src_and_notdst),
+ TRANSP_ROP(cirrus_bitblt_rop_fwd_transp_notdst),
+ TRANSP_ROP(cirrus_bitblt_rop_fwd_transp_src),
+ TRANSP_ROP(cirrus_bitblt_rop_fwd_transp_1),
+ TRANSP_ROP(cirrus_bitblt_rop_fwd_transp_notsrc_and_dst),
+ TRANSP_ROP(cirrus_bitblt_rop_fwd_transp_src_xor_dst),
+ TRANSP_ROP(cirrus_bitblt_rop_fwd_transp_src_or_dst),
+ TRANSP_ROP(cirrus_bitblt_rop_fwd_transp_notsrc_or_notdst),
+ TRANSP_ROP(cirrus_bitblt_rop_fwd_transp_src_notxor_dst),
+ TRANSP_ROP(cirrus_bitblt_rop_fwd_transp_src_or_notdst),
+ TRANSP_ROP(cirrus_bitblt_rop_fwd_transp_notsrc),
+ TRANSP_ROP(cirrus_bitblt_rop_fwd_transp_notsrc_or_dst),
+ TRANSP_ROP(cirrus_bitblt_rop_fwd_transp_notsrc_and_notdst),
+};
+
+static const cirrus_bitblt_rop_t cirrus_bkwd_transp_rop[16][2] = {
+ TRANSP_ROP(cirrus_bitblt_rop_bkwd_transp_0),
+ TRANSP_ROP(cirrus_bitblt_rop_bkwd_transp_src_and_dst),
+ TRANSP_NOP(cirrus_bitblt_rop_nop),
+ TRANSP_ROP(cirrus_bitblt_rop_bkwd_transp_src_and_notdst),
+ TRANSP_ROP(cirrus_bitblt_rop_bkwd_transp_notdst),
+ TRANSP_ROP(cirrus_bitblt_rop_bkwd_transp_src),
+ TRANSP_ROP(cirrus_bitblt_rop_bkwd_transp_1),
+ TRANSP_ROP(cirrus_bitblt_rop_bkwd_transp_notsrc_and_dst),
+ TRANSP_ROP(cirrus_bitblt_rop_bkwd_transp_src_xor_dst),
+ TRANSP_ROP(cirrus_bitblt_rop_bkwd_transp_src_or_dst),
+ TRANSP_ROP(cirrus_bitblt_rop_bkwd_transp_notsrc_or_notdst),
+ TRANSP_ROP(cirrus_bitblt_rop_bkwd_transp_src_notxor_dst),
+ TRANSP_ROP(cirrus_bitblt_rop_bkwd_transp_src_or_notdst),
+ TRANSP_ROP(cirrus_bitblt_rop_bkwd_transp_notsrc),
+ TRANSP_ROP(cirrus_bitblt_rop_bkwd_transp_notsrc_or_dst),
+ TRANSP_ROP(cirrus_bitblt_rop_bkwd_transp_notsrc_and_notdst),
+};
+
+#define ROP2(name) {\
+ name ## _8,\
+ name ## _16,\
+ name ## _24,\
+ name ## _32,\
+ }
+
+#define ROP_NOP2(func) {\
+ func,\
+ func,\
+ func,\
+ func,\
+ }
+
+static const cirrus_bitblt_rop_t cirrus_patternfill[16][4] = {
+ ROP2(cirrus_patternfill_0),
+ ROP2(cirrus_patternfill_src_and_dst),
+ ROP_NOP2(cirrus_bitblt_rop_nop),
+ ROP2(cirrus_patternfill_src_and_notdst),
+ ROP2(cirrus_patternfill_notdst),
+ ROP2(cirrus_patternfill_src),
+ ROP2(cirrus_patternfill_1),
+ ROP2(cirrus_patternfill_notsrc_and_dst),
+ ROP2(cirrus_patternfill_src_xor_dst),
+ ROP2(cirrus_patternfill_src_or_dst),
+ ROP2(cirrus_patternfill_notsrc_or_notdst),
+ ROP2(cirrus_patternfill_src_notxor_dst),
+ ROP2(cirrus_patternfill_src_or_notdst),
+ ROP2(cirrus_patternfill_notsrc),
+ ROP2(cirrus_patternfill_notsrc_or_dst),
+ ROP2(cirrus_patternfill_notsrc_and_notdst),
+};
+
+static const cirrus_bitblt_rop_t cirrus_colorexpand_transp[16][4] = {
+ ROP2(cirrus_colorexpand_transp_0),
+ ROP2(cirrus_colorexpand_transp_src_and_dst),
+ ROP_NOP2(cirrus_bitblt_rop_nop),
+ ROP2(cirrus_colorexpand_transp_src_and_notdst),
+ ROP2(cirrus_colorexpand_transp_notdst),
+ ROP2(cirrus_colorexpand_transp_src),
+ ROP2(cirrus_colorexpand_transp_1),
+ ROP2(cirrus_colorexpand_transp_notsrc_and_dst),
+ ROP2(cirrus_colorexpand_transp_src_xor_dst),
+ ROP2(cirrus_colorexpand_transp_src_or_dst),
+ ROP2(cirrus_colorexpand_transp_notsrc_or_notdst),
+ ROP2(cirrus_colorexpand_transp_src_notxor_dst),
+ ROP2(cirrus_colorexpand_transp_src_or_notdst),
+ ROP2(cirrus_colorexpand_transp_notsrc),
+ ROP2(cirrus_colorexpand_transp_notsrc_or_dst),
+ ROP2(cirrus_colorexpand_transp_notsrc_and_notdst),
+};
+
+static const cirrus_bitblt_rop_t cirrus_colorexpand[16][4] = {
+ ROP2(cirrus_colorexpand_0),
+ ROP2(cirrus_colorexpand_src_and_dst),
+ ROP_NOP2(cirrus_bitblt_rop_nop),
+ ROP2(cirrus_colorexpand_src_and_notdst),
+ ROP2(cirrus_colorexpand_notdst),
+ ROP2(cirrus_colorexpand_src),
+ ROP2(cirrus_colorexpand_1),
+ ROP2(cirrus_colorexpand_notsrc_and_dst),
+ ROP2(cirrus_colorexpand_src_xor_dst),
+ ROP2(cirrus_colorexpand_src_or_dst),
+ ROP2(cirrus_colorexpand_notsrc_or_notdst),
+ ROP2(cirrus_colorexpand_src_notxor_dst),
+ ROP2(cirrus_colorexpand_src_or_notdst),
+ ROP2(cirrus_colorexpand_notsrc),
+ ROP2(cirrus_colorexpand_notsrc_or_dst),
+ ROP2(cirrus_colorexpand_notsrc_and_notdst),
+};
+
+static const cirrus_bitblt_rop_t cirrus_colorexpand_pattern_transp[16][4] = {
+ ROP2(cirrus_colorexpand_pattern_transp_0),
+ ROP2(cirrus_colorexpand_pattern_transp_src_and_dst),
+ ROP_NOP2(cirrus_bitblt_rop_nop),
+ ROP2(cirrus_colorexpand_pattern_transp_src_and_notdst),
+ ROP2(cirrus_colorexpand_pattern_transp_notdst),
+ ROP2(cirrus_colorexpand_pattern_transp_src),
+ ROP2(cirrus_colorexpand_pattern_transp_1),
+ ROP2(cirrus_colorexpand_pattern_transp_notsrc_and_dst),
+ ROP2(cirrus_colorexpand_pattern_transp_src_xor_dst),
+ ROP2(cirrus_colorexpand_pattern_transp_src_or_dst),
+ ROP2(cirrus_colorexpand_pattern_transp_notsrc_or_notdst),
+ ROP2(cirrus_colorexpand_pattern_transp_src_notxor_dst),
+ ROP2(cirrus_colorexpand_pattern_transp_src_or_notdst),
+ ROP2(cirrus_colorexpand_pattern_transp_notsrc),
+ ROP2(cirrus_colorexpand_pattern_transp_notsrc_or_dst),
+ ROP2(cirrus_colorexpand_pattern_transp_notsrc_and_notdst),
+};
+
+static const cirrus_bitblt_rop_t cirrus_colorexpand_pattern[16][4] = {
+ ROP2(cirrus_colorexpand_pattern_0),
+ ROP2(cirrus_colorexpand_pattern_src_and_dst),
+ ROP_NOP2(cirrus_bitblt_rop_nop),
+ ROP2(cirrus_colorexpand_pattern_src_and_notdst),
+ ROP2(cirrus_colorexpand_pattern_notdst),
+ ROP2(cirrus_colorexpand_pattern_src),
+ ROP2(cirrus_colorexpand_pattern_1),
+ ROP2(cirrus_colorexpand_pattern_notsrc_and_dst),
+ ROP2(cirrus_colorexpand_pattern_src_xor_dst),
+ ROP2(cirrus_colorexpand_pattern_src_or_dst),
+ ROP2(cirrus_colorexpand_pattern_notsrc_or_notdst),
+ ROP2(cirrus_colorexpand_pattern_src_notxor_dst),
+ ROP2(cirrus_colorexpand_pattern_src_or_notdst),
+ ROP2(cirrus_colorexpand_pattern_notsrc),
+ ROP2(cirrus_colorexpand_pattern_notsrc_or_dst),
+ ROP2(cirrus_colorexpand_pattern_notsrc_and_notdst),
+};
+
+static const cirrus_fill_t cirrus_fill[16][4] = {
+ ROP2(cirrus_fill_0),
+ ROP2(cirrus_fill_src_and_dst),
+ ROP_NOP2(cirrus_bitblt_fill_nop),
+ ROP2(cirrus_fill_src_and_notdst),
+ ROP2(cirrus_fill_notdst),
+ ROP2(cirrus_fill_src),
+ ROP2(cirrus_fill_1),
+ ROP2(cirrus_fill_notsrc_and_dst),
+ ROP2(cirrus_fill_src_xor_dst),
+ ROP2(cirrus_fill_src_or_dst),
+ ROP2(cirrus_fill_notsrc_or_notdst),
+ ROP2(cirrus_fill_src_notxor_dst),
+ ROP2(cirrus_fill_src_or_notdst),
+ ROP2(cirrus_fill_notsrc),
+ ROP2(cirrus_fill_notsrc_or_dst),
+ ROP2(cirrus_fill_notsrc_and_notdst),
+};
+
+static inline void cirrus_bitblt_fgcol(CirrusVGAState *s)
+{
+ unsigned int color;
+ switch (s->cirrus_blt_pixelwidth) {
+ case 1:
+ s->cirrus_blt_fgcol = s->cirrus_shadow_gr1;
+ break;
+ case 2:
+ color = s->cirrus_shadow_gr1 | (s->vga.gr[0x11] << 8);
+ s->cirrus_blt_fgcol = le16_to_cpu(color);
+ break;
+ case 3:
+ s->cirrus_blt_fgcol = s->cirrus_shadow_gr1 |
+ (s->vga.gr[0x11] << 8) | (s->vga.gr[0x13] << 16);
+ break;
+ default:
+ case 4:
+ color = s->cirrus_shadow_gr1 | (s->vga.gr[0x11] << 8) |
+ (s->vga.gr[0x13] << 16) | (s->vga.gr[0x15] << 24);
+ s->cirrus_blt_fgcol = le32_to_cpu(color);
+ break;
+ }
+}
+
+static inline void cirrus_bitblt_bgcol(CirrusVGAState *s)
+{
+ unsigned int color;
+ switch (s->cirrus_blt_pixelwidth) {
+ case 1:
+ s->cirrus_blt_bgcol = s->cirrus_shadow_gr0;
+ break;
+ case 2:
+ color = s->cirrus_shadow_gr0 | (s->vga.gr[0x10] << 8);
+ s->cirrus_blt_bgcol = le16_to_cpu(color);
+ break;
+ case 3:
+ s->cirrus_blt_bgcol = s->cirrus_shadow_gr0 |
+ (s->vga.gr[0x10] << 8) | (s->vga.gr[0x12] << 16);
+ break;
+ default:
+ case 4:
+ color = s->cirrus_shadow_gr0 | (s->vga.gr[0x10] << 8) |
+ (s->vga.gr[0x12] << 16) | (s->vga.gr[0x14] << 24);
+ s->cirrus_blt_bgcol = le32_to_cpu(color);
+ break;
+ }
+}
+
+static void cirrus_invalidate_region(CirrusVGAState * s, int off_begin,
+ int off_pitch, int bytesperline,
+ int lines)
+{
+ int y;
+ int off_cur;
+ int off_cur_end;
+
+ if (off_pitch < 0) {
+ off_begin -= bytesperline - 1;
+ }
+
+ for (y = 0; y < lines; y++) {
+ off_cur = off_begin & s->cirrus_addr_mask;
+ off_cur_end = ((off_cur + bytesperline - 1) & s->cirrus_addr_mask) + 1;
+ if (off_cur_end >= off_cur) {
+ memory_region_set_dirty(&s->vga.vram, off_cur, off_cur_end - off_cur);
+ } else {
+ /* wraparound */
+ memory_region_set_dirty(&s->vga.vram, off_cur,
+ s->cirrus_addr_mask + 1 - off_cur);
+ memory_region_set_dirty(&s->vga.vram, 0, off_cur_end);
+ }
+ off_begin += off_pitch;
+ }
+}
+
+static int cirrus_bitblt_common_patterncopy(CirrusVGAState *s)
+{
+ uint32_t patternsize;
+ bool videosrc = !s->cirrus_srccounter;
+
+ if (videosrc) {
+ switch (s->vga.get_bpp(&s->vga)) {
+ case 8:
+ patternsize = 64;
+ break;
+ case 15:
+ case 16:
+ patternsize = 128;
+ break;
+ case 24:
+ case 32:
+ default:
+ patternsize = 256;
+ break;
+ }
+ s->cirrus_blt_srcaddr &= ~(patternsize - 1);
+ if (s->cirrus_blt_srcaddr + patternsize > s->vga.vram_size) {
+ return 0;
+ }
+ }
+
+ if (blit_is_unsafe(s, true)) {
+ return 0;
+ }
+
+ (*s->cirrus_rop) (s, s->cirrus_blt_dstaddr,
+ videosrc ? s->cirrus_blt_srcaddr : 0,
+ s->cirrus_blt_dstpitch, 0,
+ s->cirrus_blt_width, s->cirrus_blt_height);
+ cirrus_invalidate_region(s, s->cirrus_blt_dstaddr,
+ s->cirrus_blt_dstpitch, s->cirrus_blt_width,
+ s->cirrus_blt_height);
+ return 1;
+}
+
+/* fill */
+
+static int cirrus_bitblt_solidfill(CirrusVGAState *s, int blt_rop)
+{
+ cirrus_fill_t rop_func;
+
+ if (blit_is_unsafe(s, true)) {
+ return 0;
+ }
+ rop_func = cirrus_fill[rop_to_index[blt_rop]][s->cirrus_blt_pixelwidth - 1];
+ rop_func(s, s->cirrus_blt_dstaddr,
+ s->cirrus_blt_dstpitch,
+ s->cirrus_blt_width, s->cirrus_blt_height);
+ cirrus_invalidate_region(s, s->cirrus_blt_dstaddr,
+ s->cirrus_blt_dstpitch, s->cirrus_blt_width,
+ s->cirrus_blt_height);
+ cirrus_bitblt_reset(s);
+ return 1;
+}
+
+/***************************************
+ *
+ * bitblt (video-to-video)
+ *
+ ***************************************/
+
+static int cirrus_bitblt_videotovideo_patterncopy(CirrusVGAState * s)
+{
+ return cirrus_bitblt_common_patterncopy(s);
+}
+
+static int cirrus_do_copy(CirrusVGAState *s, int dst, int src, int w, int h)
+{
+ int sx = 0, sy = 0;
+ int dx = 0, dy = 0;
+ int depth = 0;
+ int notify = 0;
+
+ /* make sure to only copy if it's a plain copy ROP */
+ if (*s->cirrus_rop == cirrus_bitblt_rop_fwd_src ||
+ *s->cirrus_rop == cirrus_bitblt_rop_bkwd_src) {
+
+ int width, height;
+
+ depth = s->vga.get_bpp(&s->vga) / 8;
+ if (!depth) {
+ return 0;
+ }
+ s->vga.get_resolution(&s->vga, &width, &height);
+
+ /* extra x, y */
+ sx = (src % ABS(s->cirrus_blt_srcpitch)) / depth;
+ sy = (src / ABS(s->cirrus_blt_srcpitch));
+ dx = (dst % ABS(s->cirrus_blt_dstpitch)) / depth;
+ dy = (dst / ABS(s->cirrus_blt_dstpitch));
+
+ /* normalize width */
+ w /= depth;
+
+ /* if we're doing a backward copy, we have to adjust
+ our x/y to be the upper left corner (instead of the lower
+ right corner) */
+ if (s->cirrus_blt_dstpitch < 0) {
+ sx -= (s->cirrus_blt_width / depth) - 1;
+ dx -= (s->cirrus_blt_width / depth) - 1;
+ sy -= s->cirrus_blt_height - 1;
+ dy -= s->cirrus_blt_height - 1;
+ }
+
+ /* are we in the visible portion of memory? */
+ if (sx >= 0 && sy >= 0 && dx >= 0 && dy >= 0 &&
+ (sx + w) <= width && (sy + h) <= height &&
+ (dx + w) <= width && (dy + h) <= height) {
+ notify = 1;
+ }
+ }
+
+ (*s->cirrus_rop) (s, s->cirrus_blt_dstaddr,
+ s->cirrus_blt_srcaddr,
+ s->cirrus_blt_dstpitch, s->cirrus_blt_srcpitch,
+ s->cirrus_blt_width, s->cirrus_blt_height);
+
+ if (notify) {
+ dpy_gfx_update(s->vga.con, dx, dy,
+ s->cirrus_blt_width / depth,
+ s->cirrus_blt_height);
+ }
+
+ /* we don't have to notify the display that this portion has
+ changed since qemu_console_copy implies this */
+
+ cirrus_invalidate_region(s, s->cirrus_blt_dstaddr,
+ s->cirrus_blt_dstpitch, s->cirrus_blt_width,
+ s->cirrus_blt_height);
+
+ return 1;
+}
+
+static int cirrus_bitblt_videotovideo_copy(CirrusVGAState * s)
+{
+ if (blit_is_unsafe(s, false))
+ return 0;
+
+ return cirrus_do_copy(s, s->cirrus_blt_dstaddr - s->vga.start_addr,
+ s->cirrus_blt_srcaddr - s->vga.start_addr,
+ s->cirrus_blt_width, s->cirrus_blt_height);
+}
+
+/***************************************
+ *
+ * bitblt (cpu-to-video)
+ *
+ ***************************************/
+
+static void cirrus_bitblt_cputovideo_next(CirrusVGAState * s)
+{
+ int copy_count;
+ uint8_t *end_ptr;
+
+ if (s->cirrus_srccounter > 0) {
+ if (s->cirrus_blt_mode & CIRRUS_BLTMODE_PATTERNCOPY) {
+ cirrus_bitblt_common_patterncopy(s);
+ the_end:
+ s->cirrus_srccounter = 0;
+ cirrus_bitblt_reset(s);
+ } else {
+ /* at least one scan line */
+ do {
+ (*s->cirrus_rop)(s, s->cirrus_blt_dstaddr,
+ 0, 0, 0, s->cirrus_blt_width, 1);
+ cirrus_invalidate_region(s, s->cirrus_blt_dstaddr, 0,
+ s->cirrus_blt_width, 1);
+ s->cirrus_blt_dstaddr += s->cirrus_blt_dstpitch;
+ s->cirrus_srccounter -= s->cirrus_blt_srcpitch;
+ if (s->cirrus_srccounter <= 0)
+ goto the_end;
+ /* more bytes than needed can be transferred because of
+ word alignment, so we keep them for the next line */
+ /* XXX: keep alignment to speed up transfer */
+ end_ptr = s->cirrus_bltbuf + s->cirrus_blt_srcpitch;
+ copy_count = s->cirrus_srcptr_end - end_ptr;
+ memmove(s->cirrus_bltbuf, end_ptr, copy_count);
+ s->cirrus_srcptr = s->cirrus_bltbuf + copy_count;
+ s->cirrus_srcptr_end = s->cirrus_bltbuf + s->cirrus_blt_srcpitch;
+ } while (s->cirrus_srcptr >= s->cirrus_srcptr_end);
+ }
+ }
+}
+
+/***************************************
+ *
+ * bitblt wrapper
+ *
+ ***************************************/
+
+static void cirrus_bitblt_reset(CirrusVGAState * s)
+{
+ int need_update;
+
+ s->vga.gr[0x31] &=
+ ~(CIRRUS_BLT_START | CIRRUS_BLT_BUSY | CIRRUS_BLT_FIFOUSED);
+ need_update = s->cirrus_srcptr != &s->cirrus_bltbuf[0]
+ || s->cirrus_srcptr_end != &s->cirrus_bltbuf[0];
+ s->cirrus_srcptr = &s->cirrus_bltbuf[0];
+ s->cirrus_srcptr_end = &s->cirrus_bltbuf[0];
+ s->cirrus_srccounter = 0;
+ if (!need_update)
+ return;
+ cirrus_update_memory_access(s);
+}
+
+static int cirrus_bitblt_cputovideo(CirrusVGAState * s)
+{
+ int w;
+
+ if (blit_is_unsafe(s, true)) {
+ return 0;
+ }
+
+ s->cirrus_blt_mode &= ~CIRRUS_BLTMODE_MEMSYSSRC;
+ s->cirrus_srcptr = &s->cirrus_bltbuf[0];
+ s->cirrus_srcptr_end = &s->cirrus_bltbuf[0];
+
+ if (s->cirrus_blt_mode & CIRRUS_BLTMODE_PATTERNCOPY) {
+ if (s->cirrus_blt_mode & CIRRUS_BLTMODE_COLOREXPAND) {
+ s->cirrus_blt_srcpitch = 8;
+ } else {
+ /* XXX: check for 24 bpp */
+ s->cirrus_blt_srcpitch = 8 * 8 * s->cirrus_blt_pixelwidth;
+ }
+ s->cirrus_srccounter = s->cirrus_blt_srcpitch;
+ } else {
+ if (s->cirrus_blt_mode & CIRRUS_BLTMODE_COLOREXPAND) {
+ w = s->cirrus_blt_width / s->cirrus_blt_pixelwidth;
+ if (s->cirrus_blt_modeext & CIRRUS_BLTMODEEXT_DWORDGRANULARITY)
+ s->cirrus_blt_srcpitch = ((w + 31) >> 5);
+ else
+ s->cirrus_blt_srcpitch = ((w + 7) >> 3);
+ } else {
+ /* always align input size to 32 bits */
+ s->cirrus_blt_srcpitch = (s->cirrus_blt_width + 3) & ~3;
+ }
+ s->cirrus_srccounter = s->cirrus_blt_srcpitch * s->cirrus_blt_height;
+ }
+
+ /* the blit_is_unsafe call above should catch this */
+ assert(s->cirrus_blt_srcpitch <= CIRRUS_BLTBUFSIZE);
+
+ s->cirrus_srcptr = s->cirrus_bltbuf;
+ s->cirrus_srcptr_end = s->cirrus_bltbuf + s->cirrus_blt_srcpitch;
+ cirrus_update_memory_access(s);
+ return 1;
+}
+
+static int cirrus_bitblt_videotocpu(CirrusVGAState * s)
+{
+ /* XXX */
+ qemu_log_mask(LOG_UNIMP,
+ "cirrus: bitblt (video to cpu) is not implemented\n");
+ return 0;
+}
+
+static int cirrus_bitblt_videotovideo(CirrusVGAState * s)
+{
+ int ret;
+
+ if (s->cirrus_blt_mode & CIRRUS_BLTMODE_PATTERNCOPY) {
+ ret = cirrus_bitblt_videotovideo_patterncopy(s);
+ } else {
+ ret = cirrus_bitblt_videotovideo_copy(s);
+ }
+ if (ret)
+ cirrus_bitblt_reset(s);
+ return ret;
+}
+
+static void cirrus_bitblt_start(CirrusVGAState * s)
+{
+ uint8_t blt_rop;
+
+ if (!s->enable_blitter) {
+ goto bitblt_ignore;
+ }
+
+ s->vga.gr[0x31] |= CIRRUS_BLT_BUSY;
+
+ s->cirrus_blt_width = (s->vga.gr[0x20] | (s->vga.gr[0x21] << 8)) + 1;
+ s->cirrus_blt_height = (s->vga.gr[0x22] | (s->vga.gr[0x23] << 8)) + 1;
+ s->cirrus_blt_dstpitch = (s->vga.gr[0x24] | (s->vga.gr[0x25] << 8));
+ s->cirrus_blt_srcpitch = (s->vga.gr[0x26] | (s->vga.gr[0x27] << 8));
+ s->cirrus_blt_dstaddr =
+ (s->vga.gr[0x28] | (s->vga.gr[0x29] << 8) | (s->vga.gr[0x2a] << 16));
+ s->cirrus_blt_srcaddr =
+ (s->vga.gr[0x2c] | (s->vga.gr[0x2d] << 8) | (s->vga.gr[0x2e] << 16));
+ s->cirrus_blt_mode = s->vga.gr[0x30];
+ s->cirrus_blt_modeext = s->vga.gr[0x33];
+ blt_rop = s->vga.gr[0x32];
+
+ s->cirrus_blt_dstaddr &= s->cirrus_addr_mask;
+ s->cirrus_blt_srcaddr &= s->cirrus_addr_mask;
+
+ trace_vga_cirrus_bitblt_start(blt_rop,
+ s->cirrus_blt_mode,
+ s->cirrus_blt_modeext,
+ s->cirrus_blt_width,
+ s->cirrus_blt_height,
+ s->cirrus_blt_dstpitch,
+ s->cirrus_blt_srcpitch,
+ s->cirrus_blt_dstaddr,
+ s->cirrus_blt_srcaddr,
+ s->vga.gr[0x2f]);
+
+ switch (s->cirrus_blt_mode & CIRRUS_BLTMODE_PIXELWIDTHMASK) {
+ case CIRRUS_BLTMODE_PIXELWIDTH8:
+ s->cirrus_blt_pixelwidth = 1;
+ break;
+ case CIRRUS_BLTMODE_PIXELWIDTH16:
+ s->cirrus_blt_pixelwidth = 2;
+ break;
+ case CIRRUS_BLTMODE_PIXELWIDTH24:
+ s->cirrus_blt_pixelwidth = 3;
+ break;
+ case CIRRUS_BLTMODE_PIXELWIDTH32:
+ s->cirrus_blt_pixelwidth = 4;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "cirrus: bitblt - pixel width is unknown\n");
+ goto bitblt_ignore;
+ }
+ s->cirrus_blt_mode &= ~CIRRUS_BLTMODE_PIXELWIDTHMASK;
+
+ if ((s->
+ cirrus_blt_mode & (CIRRUS_BLTMODE_MEMSYSSRC |
+ CIRRUS_BLTMODE_MEMSYSDEST))
+ == (CIRRUS_BLTMODE_MEMSYSSRC | CIRRUS_BLTMODE_MEMSYSDEST)) {
+ qemu_log_mask(LOG_UNIMP,
+ "cirrus: bitblt - memory-to-memory copy requested\n");
+ goto bitblt_ignore;
+ }
+
+ if ((s->cirrus_blt_modeext & CIRRUS_BLTMODEEXT_SOLIDFILL) &&
+ (s->cirrus_blt_mode & (CIRRUS_BLTMODE_MEMSYSDEST |
+ CIRRUS_BLTMODE_TRANSPARENTCOMP |
+ CIRRUS_BLTMODE_PATTERNCOPY |
+ CIRRUS_BLTMODE_COLOREXPAND)) ==
+ (CIRRUS_BLTMODE_PATTERNCOPY | CIRRUS_BLTMODE_COLOREXPAND)) {
+ cirrus_bitblt_fgcol(s);
+ cirrus_bitblt_solidfill(s, blt_rop);
+ } else {
+ if ((s->cirrus_blt_mode & (CIRRUS_BLTMODE_COLOREXPAND |
+ CIRRUS_BLTMODE_PATTERNCOPY)) ==
+ CIRRUS_BLTMODE_COLOREXPAND) {
+
+ if (s->cirrus_blt_mode & CIRRUS_BLTMODE_TRANSPARENTCOMP) {
+ if (s->cirrus_blt_modeext & CIRRUS_BLTMODEEXT_COLOREXPINV)
+ cirrus_bitblt_bgcol(s);
+ else
+ cirrus_bitblt_fgcol(s);
+ s->cirrus_rop = cirrus_colorexpand_transp[rop_to_index[blt_rop]][s->cirrus_blt_pixelwidth - 1];
+ } else {
+ cirrus_bitblt_fgcol(s);
+ cirrus_bitblt_bgcol(s);
+ s->cirrus_rop = cirrus_colorexpand[rop_to_index[blt_rop]][s->cirrus_blt_pixelwidth - 1];
+ }
+ } else if (s->cirrus_blt_mode & CIRRUS_BLTMODE_PATTERNCOPY) {
+ if (s->cirrus_blt_mode & CIRRUS_BLTMODE_COLOREXPAND) {
+ if (s->cirrus_blt_mode & CIRRUS_BLTMODE_TRANSPARENTCOMP) {
+ if (s->cirrus_blt_modeext & CIRRUS_BLTMODEEXT_COLOREXPINV)
+ cirrus_bitblt_bgcol(s);
+ else
+ cirrus_bitblt_fgcol(s);
+ s->cirrus_rop = cirrus_colorexpand_pattern_transp[rop_to_index[blt_rop]][s->cirrus_blt_pixelwidth - 1];
+ } else {
+ cirrus_bitblt_fgcol(s);
+ cirrus_bitblt_bgcol(s);
+ s->cirrus_rop = cirrus_colorexpand_pattern[rop_to_index[blt_rop]][s->cirrus_blt_pixelwidth - 1];
+ }
+ } else {
+ s->cirrus_rop = cirrus_patternfill[rop_to_index[blt_rop]][s->cirrus_blt_pixelwidth - 1];
+ }
+ } else {
+ if (s->cirrus_blt_mode & CIRRUS_BLTMODE_TRANSPARENTCOMP) {
+ if (s->cirrus_blt_pixelwidth > 2) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "cirrus: src transparent without colorexpand "
+ "must be 8bpp or 16bpp\n");
+ goto bitblt_ignore;
+ }
+ if (s->cirrus_blt_mode & CIRRUS_BLTMODE_BACKWARDS) {
+ s->cirrus_blt_dstpitch = -s->cirrus_blt_dstpitch;
+ s->cirrus_blt_srcpitch = -s->cirrus_blt_srcpitch;
+ s->cirrus_rop = cirrus_bkwd_transp_rop[rop_to_index[blt_rop]][s->cirrus_blt_pixelwidth - 1];
+ } else {
+ s->cirrus_rop = cirrus_fwd_transp_rop[rop_to_index[blt_rop]][s->cirrus_blt_pixelwidth - 1];
+ }
+ } else {
+ if (s->cirrus_blt_mode & CIRRUS_BLTMODE_BACKWARDS) {
+ s->cirrus_blt_dstpitch = -s->cirrus_blt_dstpitch;
+ s->cirrus_blt_srcpitch = -s->cirrus_blt_srcpitch;
+ s->cirrus_rop = cirrus_bkwd_rop[rop_to_index[blt_rop]];
+ } else {
+ s->cirrus_rop = cirrus_fwd_rop[rop_to_index[blt_rop]];
+ }
+ }
+ }
+ // setup bitblt engine.
+ if (s->cirrus_blt_mode & CIRRUS_BLTMODE_MEMSYSSRC) {
+ if (!cirrus_bitblt_cputovideo(s))
+ goto bitblt_ignore;
+ } else if (s->cirrus_blt_mode & CIRRUS_BLTMODE_MEMSYSDEST) {
+ if (!cirrus_bitblt_videotocpu(s))
+ goto bitblt_ignore;
+ } else {
+ if (!cirrus_bitblt_videotovideo(s))
+ goto bitblt_ignore;
+ }
+ }
+ return;
+ bitblt_ignore:;
+ cirrus_bitblt_reset(s);
+}
+
+static void cirrus_write_bitblt(CirrusVGAState * s, unsigned reg_value)
+{
+ unsigned old_value;
+
+ old_value = s->vga.gr[0x31];
+ s->vga.gr[0x31] = reg_value;
+
+ if (((old_value & CIRRUS_BLT_RESET) != 0) &&
+ ((reg_value & CIRRUS_BLT_RESET) == 0)) {
+ cirrus_bitblt_reset(s);
+ } else if (((old_value & CIRRUS_BLT_START) == 0) &&
+ ((reg_value & CIRRUS_BLT_START) != 0)) {
+ cirrus_bitblt_start(s);
+ }
+}
+
+
+/***************************************
+ *
+ * basic parameters
+ *
+ ***************************************/
+
+static void cirrus_get_offsets(VGACommonState *s1,
+ uint32_t *pline_offset,
+ uint32_t *pstart_addr,
+ uint32_t *pline_compare)
+{
+ CirrusVGAState * s = container_of(s1, CirrusVGAState, vga);
+ uint32_t start_addr, line_offset, line_compare;
+
+ line_offset = s->vga.cr[0x13]
+ | ((s->vga.cr[0x1b] & 0x10) << 4);
+ line_offset <<= 3;
+ *pline_offset = line_offset;
+
+ start_addr = (s->vga.cr[0x0c] << 8)
+ | s->vga.cr[0x0d]
+ | ((s->vga.cr[0x1b] & 0x01) << 16)
+ | ((s->vga.cr[0x1b] & 0x0c) << 15)
+ | ((s->vga.cr[0x1d] & 0x80) << 12);
+ *pstart_addr = start_addr;
+
+ line_compare = s->vga.cr[0x18] |
+ ((s->vga.cr[0x07] & 0x10) << 4) |
+ ((s->vga.cr[0x09] & 0x40) << 3);
+ *pline_compare = line_compare;
+}
+
+static uint32_t cirrus_get_bpp16_depth(CirrusVGAState * s)
+{
+ uint32_t ret = 16;
+
+ switch (s->cirrus_hidden_dac_data & 0xf) {
+ case 0:
+ ret = 15;
+ break; /* Sierra HiColor */
+ case 1:
+ ret = 16;
+ break; /* XGA HiColor */
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "cirrus: invalid DAC value 0x%x in 16bpp\n",
+ (s->cirrus_hidden_dac_data & 0xf));
+ ret = 15; /* XXX */
+ break;
+ }
+ return ret;
+}
+
+static int cirrus_get_bpp(VGACommonState *s1)
+{
+ CirrusVGAState * s = container_of(s1, CirrusVGAState, vga);
+ uint32_t ret = 8;
+
+ if ((s->vga.sr[0x07] & 0x01) != 0) {
+ /* Cirrus SVGA */
+ switch (s->vga.sr[0x07] & CIRRUS_SR7_BPP_MASK) {
+ case CIRRUS_SR7_BPP_8:
+ ret = 8;
+ break;
+ case CIRRUS_SR7_BPP_16_DOUBLEVCLK:
+ ret = cirrus_get_bpp16_depth(s);
+ break;
+ case CIRRUS_SR7_BPP_24:
+ ret = 24;
+ break;
+ case CIRRUS_SR7_BPP_16:
+ ret = cirrus_get_bpp16_depth(s);
+ break;
+ case CIRRUS_SR7_BPP_32:
+ ret = 32;
+ break;
+ default:
+#ifdef DEBUG_CIRRUS
+ printf("cirrus: unknown bpp - sr7=%x\n", s->vga.sr[0x7]);
+#endif
+ ret = 8;
+ break;
+ }
+ } else {
+ /* VGA */
+ ret = 0;
+ }
+
+ return ret;
+}
+
+static void cirrus_get_resolution(VGACommonState *s, int *pwidth, int *pheight)
+{
+ int width, height;
+
+ width = (s->cr[0x01] + 1) * 8;
+ height = s->cr[0x12] |
+ ((s->cr[0x07] & 0x02) << 7) |
+ ((s->cr[0x07] & 0x40) << 3);
+ height = (height + 1);
+ /* interlace support */
+ if (s->cr[0x1a] & 0x01)
+ height = height * 2;
+ *pwidth = width;
+ *pheight = height;
+}
+
+/***************************************
+ *
+ * bank memory
+ *
+ ***************************************/
+
+static void cirrus_update_bank_ptr(CirrusVGAState * s, unsigned bank_index)
+{
+ unsigned offset;
+ unsigned limit;
+
+ if ((s->vga.gr[0x0b] & 0x01) != 0) /* dual bank */
+ offset = s->vga.gr[0x09 + bank_index];
+ else /* single bank */
+ offset = s->vga.gr[0x09];
+
+ if ((s->vga.gr[0x0b] & 0x20) != 0)
+ offset <<= 14;
+ else
+ offset <<= 12;
+
+ if (s->real_vram_size <= offset)
+ limit = 0;
+ else
+ limit = s->real_vram_size - offset;
+
+ if (((s->vga.gr[0x0b] & 0x01) == 0) && (bank_index != 0)) {
+ if (limit > 0x8000) {
+ offset += 0x8000;
+ limit -= 0x8000;
+ } else {
+ limit = 0;
+ }
+ }
+
+ if (limit > 0) {
+ s->cirrus_bank_base[bank_index] = offset;
+ s->cirrus_bank_limit[bank_index] = limit;
+ } else {
+ s->cirrus_bank_base[bank_index] = 0;
+ s->cirrus_bank_limit[bank_index] = 0;
+ }
+}
+
+/***************************************
+ *
+ * I/O access between 0x3c4-0x3c5
+ *
+ ***************************************/
+
+static int cirrus_vga_read_sr(CirrusVGAState * s)
+{
+ switch (s->vga.sr_index) {
+ case 0x00: // Standard VGA
+ case 0x01: // Standard VGA
+ case 0x02: // Standard VGA
+ case 0x03: // Standard VGA
+ case 0x04: // Standard VGA
+ return s->vga.sr[s->vga.sr_index];
+ case 0x06: // Unlock Cirrus extensions
+ return s->vga.sr[s->vga.sr_index];
+ case 0x10:
+ case 0x30:
+ case 0x50:
+ case 0x70: // Graphics Cursor X
+ case 0x90:
+ case 0xb0:
+ case 0xd0:
+ case 0xf0: // Graphics Cursor X
+ return s->vga.sr[0x10];
+ case 0x11:
+ case 0x31:
+ case 0x51:
+ case 0x71: // Graphics Cursor Y
+ case 0x91:
+ case 0xb1:
+ case 0xd1:
+ case 0xf1: // Graphics Cursor Y
+ return s->vga.sr[0x11];
+ case 0x05: // ???
+ case 0x07: // Extended Sequencer Mode
+ case 0x08: // EEPROM Control
+ case 0x09: // Scratch Register 0
+ case 0x0a: // Scratch Register 1
+ case 0x0b: // VCLK 0
+ case 0x0c: // VCLK 1
+ case 0x0d: // VCLK 2
+ case 0x0e: // VCLK 3
+ case 0x0f: // DRAM Control
+ case 0x12: // Graphics Cursor Attribute
+ case 0x13: // Graphics Cursor Pattern Address
+ case 0x14: // Scratch Register 2
+ case 0x15: // Scratch Register 3
+ case 0x16: // Performance Tuning Register
+ case 0x17: // Configuration Readback and Extended Control
+ case 0x18: // Signature Generator Control
+ case 0x19: // Signal Generator Result
+ case 0x1a: // Signal Generator Result
+ case 0x1b: // VCLK 0 Denominator & Post
+ case 0x1c: // VCLK 1 Denominator & Post
+ case 0x1d: // VCLK 2 Denominator & Post
+ case 0x1e: // VCLK 3 Denominator & Post
+ case 0x1f: // BIOS Write Enable and MCLK select
+#ifdef DEBUG_CIRRUS
+ printf("cirrus: handled inport sr_index %02x\n", s->vga.sr_index);
+#endif
+ return s->vga.sr[s->vga.sr_index];
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "cirrus: inport sr_index 0x%02x\n", s->vga.sr_index);
+ return 0xff;
+ }
+}
+
+static void cirrus_vga_write_sr(CirrusVGAState * s, uint32_t val)
+{
+ switch (s->vga.sr_index) {
+ case 0x00: // Standard VGA
+ case 0x01: // Standard VGA
+ case 0x02: // Standard VGA
+ case 0x03: // Standard VGA
+ case 0x04: // Standard VGA
+ s->vga.sr[s->vga.sr_index] = val & sr_mask[s->vga.sr_index];
+ if (s->vga.sr_index == 1)
+ s->vga.update_retrace_info(&s->vga);
+ break;
+ case 0x06: // Unlock Cirrus extensions
+ val &= 0x17;
+ if (val == 0x12) {
+ s->vga.sr[s->vga.sr_index] = 0x12;
+ } else {
+ s->vga.sr[s->vga.sr_index] = 0x0f;
+ }
+ break;
+ case 0x10:
+ case 0x30:
+ case 0x50:
+ case 0x70: // Graphics Cursor X
+ case 0x90:
+ case 0xb0:
+ case 0xd0:
+ case 0xf0: // Graphics Cursor X
+ s->vga.sr[0x10] = val;
+ s->vga.hw_cursor_x = (val << 3) | (s->vga.sr_index >> 5);
+ break;
+ case 0x11:
+ case 0x31:
+ case 0x51:
+ case 0x71: // Graphics Cursor Y
+ case 0x91:
+ case 0xb1:
+ case 0xd1:
+ case 0xf1: // Graphics Cursor Y
+ s->vga.sr[0x11] = val;
+ s->vga.hw_cursor_y = (val << 3) | (s->vga.sr_index >> 5);
+ break;
+ case 0x07: // Extended Sequencer Mode
+ cirrus_update_memory_access(s);
+ /* fall through */
+ case 0x08: // EEPROM Control
+ case 0x09: // Scratch Register 0
+ case 0x0a: // Scratch Register 1
+ case 0x0b: // VCLK 0
+ case 0x0c: // VCLK 1
+ case 0x0d: // VCLK 2
+ case 0x0e: // VCLK 3
+ case 0x0f: // DRAM Control
+ case 0x13: // Graphics Cursor Pattern Address
+ case 0x14: // Scratch Register 2
+ case 0x15: // Scratch Register 3
+ case 0x16: // Performance Tuning Register
+ case 0x18: // Signature Generator Control
+ case 0x19: // Signature Generator Result
+ case 0x1a: // Signature Generator Result
+ case 0x1b: // VCLK 0 Denominator & Post
+ case 0x1c: // VCLK 1 Denominator & Post
+ case 0x1d: // VCLK 2 Denominator & Post
+ case 0x1e: // VCLK 3 Denominator & Post
+ case 0x1f: // BIOS Write Enable and MCLK select
+ s->vga.sr[s->vga.sr_index] = val;
+#ifdef DEBUG_CIRRUS
+ printf("cirrus: handled outport sr_index %02x, sr_value %02x\n",
+ s->vga.sr_index, val);
+#endif
+ break;
+ case 0x12: // Graphics Cursor Attribute
+ s->vga.sr[0x12] = val;
+ s->vga.force_shadow = !!(val & CIRRUS_CURSOR_SHOW);
+#ifdef DEBUG_CIRRUS
+ printf("cirrus: cursor ctl SR12=%02x (force shadow: %d)\n",
+ val, s->vga.force_shadow);
+#endif
+ break;
+ case 0x17: // Configuration Readback and Extended Control
+ s->vga.sr[s->vga.sr_index] = (s->vga.sr[s->vga.sr_index] & 0x38)
+ | (val & 0xc7);
+ cirrus_update_memory_access(s);
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "cirrus: outport sr_index 0x%02x, sr_value 0x%02x\n",
+ s->vga.sr_index, val);
+ break;
+ }
+}
+
+/***************************************
+ *
+ * I/O access at 0x3c6
+ *
+ ***************************************/
+
+static int cirrus_read_hidden_dac(CirrusVGAState * s)
+{
+ if (++s->cirrus_hidden_dac_lockindex == 5) {
+ s->cirrus_hidden_dac_lockindex = 0;
+ return s->cirrus_hidden_dac_data;
+ }
+ return 0xff;
+}
+
+static void cirrus_write_hidden_dac(CirrusVGAState * s, int reg_value)
+{
+ if (s->cirrus_hidden_dac_lockindex == 4) {
+ s->cirrus_hidden_dac_data = reg_value;
+#if defined(DEBUG_CIRRUS)
+ printf("cirrus: outport hidden DAC, value %02x\n", reg_value);
+#endif
+ }
+ s->cirrus_hidden_dac_lockindex = 0;
+}
+
+/***************************************
+ *
+ * I/O access at 0x3c9
+ *
+ ***************************************/
+
+static int cirrus_vga_read_palette(CirrusVGAState * s)
+{
+ int val;
+
+ if ((s->vga.sr[0x12] & CIRRUS_CURSOR_HIDDENPEL)) {
+ val = s->cirrus_hidden_palette[(s->vga.dac_read_index & 0x0f) * 3 +
+ s->vga.dac_sub_index];
+ } else {
+ val = s->vga.palette[s->vga.dac_read_index * 3 + s->vga.dac_sub_index];
+ }
+ if (++s->vga.dac_sub_index == 3) {
+ s->vga.dac_sub_index = 0;
+ s->vga.dac_read_index++;
+ }
+ return val;
+}
+
+static void cirrus_vga_write_palette(CirrusVGAState * s, int reg_value)
+{
+ s->vga.dac_cache[s->vga.dac_sub_index] = reg_value;
+ if (++s->vga.dac_sub_index == 3) {
+ if ((s->vga.sr[0x12] & CIRRUS_CURSOR_HIDDENPEL)) {
+ memcpy(&s->cirrus_hidden_palette[(s->vga.dac_write_index & 0x0f) * 3],
+ s->vga.dac_cache, 3);
+ } else {
+ memcpy(&s->vga.palette[s->vga.dac_write_index * 3], s->vga.dac_cache, 3);
+ }
+ /* XXX update cursor */
+ s->vga.dac_sub_index = 0;
+ s->vga.dac_write_index++;
+ }
+}
+
+/***************************************
+ *
+ * I/O access between 0x3ce-0x3cf
+ *
+ ***************************************/
+
+static int cirrus_vga_read_gr(CirrusVGAState * s, unsigned reg_index)
+{
+ switch (reg_index) {
+ case 0x00: // Standard VGA, BGCOLOR 0x000000ff
+ return s->cirrus_shadow_gr0;
+ case 0x01: // Standard VGA, FGCOLOR 0x000000ff
+ return s->cirrus_shadow_gr1;
+ case 0x02: // Standard VGA
+ case 0x03: // Standard VGA
+ case 0x04: // Standard VGA
+ case 0x06: // Standard VGA
+ case 0x07: // Standard VGA
+ case 0x08: // Standard VGA
+ return s->vga.gr[s->vga.gr_index];
+ case 0x05: // Standard VGA, Cirrus extended mode
+ default:
+ break;
+ }
+
+ if (reg_index < 0x3a) {
+ return s->vga.gr[reg_index];
+ } else {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "cirrus: inport gr_index 0x%02x\n", reg_index);
+ return 0xff;
+ }
+}
+
+static void
+cirrus_vga_write_gr(CirrusVGAState * s, unsigned reg_index, int reg_value)
+{
+ trace_vga_cirrus_write_gr(reg_index, reg_value);
+ switch (reg_index) {
+ case 0x00: // Standard VGA, BGCOLOR 0x000000ff
+ s->vga.gr[reg_index] = reg_value & gr_mask[reg_index];
+ s->cirrus_shadow_gr0 = reg_value;
+ break;
+ case 0x01: // Standard VGA, FGCOLOR 0x000000ff
+ s->vga.gr[reg_index] = reg_value & gr_mask[reg_index];
+ s->cirrus_shadow_gr1 = reg_value;
+ break;
+ case 0x02: // Standard VGA
+ case 0x03: // Standard VGA
+ case 0x04: // Standard VGA
+ case 0x06: // Standard VGA
+ case 0x07: // Standard VGA
+ case 0x08: // Standard VGA
+ s->vga.gr[reg_index] = reg_value & gr_mask[reg_index];
+ break;
+ case 0x05: // Standard VGA, Cirrus extended mode
+ s->vga.gr[reg_index] = reg_value & 0x7f;
+ cirrus_update_memory_access(s);
+ break;
+ case 0x09: // bank offset #0
+ case 0x0A: // bank offset #1
+ s->vga.gr[reg_index] = reg_value;
+ cirrus_update_bank_ptr(s, 0);
+ cirrus_update_bank_ptr(s, 1);
+ cirrus_update_memory_access(s);
+ break;
+ case 0x0B:
+ s->vga.gr[reg_index] = reg_value;
+ cirrus_update_bank_ptr(s, 0);
+ cirrus_update_bank_ptr(s, 1);
+ cirrus_update_memory_access(s);
+ break;
+ case 0x10: // BGCOLOR 0x0000ff00
+ case 0x11: // FGCOLOR 0x0000ff00
+ case 0x12: // BGCOLOR 0x00ff0000
+ case 0x13: // FGCOLOR 0x00ff0000
+ case 0x14: // BGCOLOR 0xff000000
+ case 0x15: // FGCOLOR 0xff000000
+ case 0x20: // BLT WIDTH 0x0000ff
+ case 0x22: // BLT HEIGHT 0x0000ff
+ case 0x24: // BLT DEST PITCH 0x0000ff
+ case 0x26: // BLT SRC PITCH 0x0000ff
+ case 0x28: // BLT DEST ADDR 0x0000ff
+ case 0x29: // BLT DEST ADDR 0x00ff00
+ case 0x2c: // BLT SRC ADDR 0x0000ff
+ case 0x2d: // BLT SRC ADDR 0x00ff00
+ case 0x2f: // BLT WRITEMASK
+ case 0x30: // BLT MODE
+ case 0x32: // RASTER OP
+ case 0x33: // BLT MODEEXT
+ case 0x34: // BLT TRANSPARENT COLOR 0x00ff
+ case 0x35: // BLT TRANSPARENT COLOR 0xff00
+ case 0x38: // BLT TRANSPARENT COLOR MASK 0x00ff
+ case 0x39: // BLT TRANSPARENT COLOR MASK 0xff00
+ s->vga.gr[reg_index] = reg_value;
+ break;
+ case 0x21: // BLT WIDTH 0x001f00
+ case 0x23: // BLT HEIGHT 0x001f00
+ case 0x25: // BLT DEST PITCH 0x001f00
+ case 0x27: // BLT SRC PITCH 0x001f00
+ s->vga.gr[reg_index] = reg_value & 0x1f;
+ break;
+ case 0x2a: // BLT DEST ADDR 0x3f0000
+ s->vga.gr[reg_index] = reg_value & 0x3f;
+ /* if auto start mode, starts bit blt now */
+ if (s->vga.gr[0x31] & CIRRUS_BLT_AUTOSTART) {
+ cirrus_bitblt_start(s);
+ }
+ break;
+ case 0x2e: // BLT SRC ADDR 0x3f0000
+ s->vga.gr[reg_index] = reg_value & 0x3f;
+ break;
+ case 0x31: // BLT STATUS/START
+ cirrus_write_bitblt(s, reg_value);
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "cirrus: outport gr_index 0x%02x, gr_value 0x%02x\n",
+ reg_index, reg_value);
+ break;
+ }
+}
+
+/***************************************
+ *
+ * I/O access between 0x3d4-0x3d5
+ *
+ ***************************************/
+
+static int cirrus_vga_read_cr(CirrusVGAState * s, unsigned reg_index)
+{
+ switch (reg_index) {
+ case 0x00: // Standard VGA
+ case 0x01: // Standard VGA
+ case 0x02: // Standard VGA
+ case 0x03: // Standard VGA
+ case 0x04: // Standard VGA
+ case 0x05: // Standard VGA
+ case 0x06: // Standard VGA
+ case 0x07: // Standard VGA
+ case 0x08: // Standard VGA
+ case 0x09: // Standard VGA
+ case 0x0a: // Standard VGA
+ case 0x0b: // Standard VGA
+ case 0x0c: // Standard VGA
+ case 0x0d: // Standard VGA
+ case 0x0e: // Standard VGA
+ case 0x0f: // Standard VGA
+ case 0x10: // Standard VGA
+ case 0x11: // Standard VGA
+ case 0x12: // Standard VGA
+ case 0x13: // Standard VGA
+ case 0x14: // Standard VGA
+ case 0x15: // Standard VGA
+ case 0x16: // Standard VGA
+ case 0x17: // Standard VGA
+ case 0x18: // Standard VGA
+ return s->vga.cr[s->vga.cr_index];
+ case 0x24: // Attribute Controller Toggle Readback (R)
+ return (s->vga.ar_flip_flop << 7);
+ case 0x19: // Interlace End
+ case 0x1a: // Miscellaneous Control
+ case 0x1b: // Extended Display Control
+ case 0x1c: // Sync Adjust and Genlock
+ case 0x1d: // Overlay Extended Control
+ case 0x22: // Graphics Data Latches Readback (R)
+ case 0x25: // Part Status
+ case 0x27: // Part ID (R)
+ return s->vga.cr[s->vga.cr_index];
+ case 0x26: // Attribute Controller Index Readback (R)
+ return s->vga.ar_index & 0x3f;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "cirrus: inport cr_index 0x%02x\n", reg_index);
+ return 0xff;
+ }
+}
+
+static void cirrus_vga_write_cr(CirrusVGAState * s, int reg_value)
+{
+ switch (s->vga.cr_index) {
+ case 0x00: // Standard VGA
+ case 0x01: // Standard VGA
+ case 0x02: // Standard VGA
+ case 0x03: // Standard VGA
+ case 0x04: // Standard VGA
+ case 0x05: // Standard VGA
+ case 0x06: // Standard VGA
+ case 0x07: // Standard VGA
+ case 0x08: // Standard VGA
+ case 0x09: // Standard VGA
+ case 0x0a: // Standard VGA
+ case 0x0b: // Standard VGA
+ case 0x0c: // Standard VGA
+ case 0x0d: // Standard VGA
+ case 0x0e: // Standard VGA
+ case 0x0f: // Standard VGA
+ case 0x10: // Standard VGA
+ case 0x11: // Standard VGA
+ case 0x12: // Standard VGA
+ case 0x13: // Standard VGA
+ case 0x14: // Standard VGA
+ case 0x15: // Standard VGA
+ case 0x16: // Standard VGA
+ case 0x17: // Standard VGA
+ case 0x18: // Standard VGA
+ /* handle CR0-7 protection */
+ if ((s->vga.cr[0x11] & 0x80) && s->vga.cr_index <= 7) {
+ /* can always write bit 4 of CR7 */
+ if (s->vga.cr_index == 7)
+ s->vga.cr[7] = (s->vga.cr[7] & ~0x10) | (reg_value & 0x10);
+ return;
+ }
+ s->vga.cr[s->vga.cr_index] = reg_value;
+ switch(s->vga.cr_index) {
+ case 0x00:
+ case 0x04:
+ case 0x05:
+ case 0x06:
+ case 0x07:
+ case 0x11:
+ case 0x17:
+ s->vga.update_retrace_info(&s->vga);
+ break;
+ }
+ break;
+ case 0x19: // Interlace End
+ case 0x1a: // Miscellaneous Control
+ case 0x1b: // Extended Display Control
+ case 0x1c: // Sync Adjust and Genlock
+ case 0x1d: // Overlay Extended Control
+ s->vga.cr[s->vga.cr_index] = reg_value;
+#ifdef DEBUG_CIRRUS
+ printf("cirrus: handled outport cr_index %02x, cr_value %02x\n",
+ s->vga.cr_index, reg_value);
+#endif
+ break;
+ case 0x22: // Graphics Data Latches Readback (R)
+ case 0x24: // Attribute Controller Toggle Readback (R)
+ case 0x26: // Attribute Controller Index Readback (R)
+ case 0x27: // Part ID (R)
+ break;
+ case 0x25: // Part Status
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "cirrus: outport cr_index 0x%02x, cr_value 0x%02x\n",
+ s->vga.cr_index, reg_value);
+ break;
+ }
+}
+
+/***************************************
+ *
+ * memory-mapped I/O (bitblt)
+ *
+ ***************************************/
+
+static uint8_t cirrus_mmio_blt_read(CirrusVGAState * s, unsigned address)
+{
+ int value = 0xff;
+
+ switch (address) {
+ case (CIRRUS_MMIO_BLTBGCOLOR + 0):
+ value = cirrus_vga_read_gr(s, 0x00);
+ break;
+ case (CIRRUS_MMIO_BLTBGCOLOR + 1):
+ value = cirrus_vga_read_gr(s, 0x10);
+ break;
+ case (CIRRUS_MMIO_BLTBGCOLOR + 2):
+ value = cirrus_vga_read_gr(s, 0x12);
+ break;
+ case (CIRRUS_MMIO_BLTBGCOLOR + 3):
+ value = cirrus_vga_read_gr(s, 0x14);
+ break;
+ case (CIRRUS_MMIO_BLTFGCOLOR + 0):
+ value = cirrus_vga_read_gr(s, 0x01);
+ break;
+ case (CIRRUS_MMIO_BLTFGCOLOR + 1):
+ value = cirrus_vga_read_gr(s, 0x11);
+ break;
+ case (CIRRUS_MMIO_BLTFGCOLOR + 2):
+ value = cirrus_vga_read_gr(s, 0x13);
+ break;
+ case (CIRRUS_MMIO_BLTFGCOLOR + 3):
+ value = cirrus_vga_read_gr(s, 0x15);
+ break;
+ case (CIRRUS_MMIO_BLTWIDTH + 0):
+ value = cirrus_vga_read_gr(s, 0x20);
+ break;
+ case (CIRRUS_MMIO_BLTWIDTH + 1):
+ value = cirrus_vga_read_gr(s, 0x21);
+ break;
+ case (CIRRUS_MMIO_BLTHEIGHT + 0):
+ value = cirrus_vga_read_gr(s, 0x22);
+ break;
+ case (CIRRUS_MMIO_BLTHEIGHT + 1):
+ value = cirrus_vga_read_gr(s, 0x23);
+ break;
+ case (CIRRUS_MMIO_BLTDESTPITCH + 0):
+ value = cirrus_vga_read_gr(s, 0x24);
+ break;
+ case (CIRRUS_MMIO_BLTDESTPITCH + 1):
+ value = cirrus_vga_read_gr(s, 0x25);
+ break;
+ case (CIRRUS_MMIO_BLTSRCPITCH + 0):
+ value = cirrus_vga_read_gr(s, 0x26);
+ break;
+ case (CIRRUS_MMIO_BLTSRCPITCH + 1):
+ value = cirrus_vga_read_gr(s, 0x27);
+ break;
+ case (CIRRUS_MMIO_BLTDESTADDR + 0):
+ value = cirrus_vga_read_gr(s, 0x28);
+ break;
+ case (CIRRUS_MMIO_BLTDESTADDR + 1):
+ value = cirrus_vga_read_gr(s, 0x29);
+ break;
+ case (CIRRUS_MMIO_BLTDESTADDR + 2):
+ value = cirrus_vga_read_gr(s, 0x2a);
+ break;
+ case (CIRRUS_MMIO_BLTSRCADDR + 0):
+ value = cirrus_vga_read_gr(s, 0x2c);
+ break;
+ case (CIRRUS_MMIO_BLTSRCADDR + 1):
+ value = cirrus_vga_read_gr(s, 0x2d);
+ break;
+ case (CIRRUS_MMIO_BLTSRCADDR + 2):
+ value = cirrus_vga_read_gr(s, 0x2e);
+ break;
+ case CIRRUS_MMIO_BLTWRITEMASK:
+ value = cirrus_vga_read_gr(s, 0x2f);
+ break;
+ case CIRRUS_MMIO_BLTMODE:
+ value = cirrus_vga_read_gr(s, 0x30);
+ break;
+ case CIRRUS_MMIO_BLTROP:
+ value = cirrus_vga_read_gr(s, 0x32);
+ break;
+ case CIRRUS_MMIO_BLTMODEEXT:
+ value = cirrus_vga_read_gr(s, 0x33);
+ break;
+ case (CIRRUS_MMIO_BLTTRANSPARENTCOLOR + 0):
+ value = cirrus_vga_read_gr(s, 0x34);
+ break;
+ case (CIRRUS_MMIO_BLTTRANSPARENTCOLOR + 1):
+ value = cirrus_vga_read_gr(s, 0x35);
+ break;
+ case (CIRRUS_MMIO_BLTTRANSPARENTCOLORMASK + 0):
+ value = cirrus_vga_read_gr(s, 0x38);
+ break;
+ case (CIRRUS_MMIO_BLTTRANSPARENTCOLORMASK + 1):
+ value = cirrus_vga_read_gr(s, 0x39);
+ break;
+ case CIRRUS_MMIO_BLTSTATUS:
+ value = cirrus_vga_read_gr(s, 0x31);
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "cirrus: mmio read - address 0x%04x\n", address);
+ break;
+ }
+
+ trace_vga_cirrus_write_blt(address, value);
+ return (uint8_t) value;
+}
+
+static void cirrus_mmio_blt_write(CirrusVGAState * s, unsigned address,
+ uint8_t value)
+{
+ trace_vga_cirrus_write_blt(address, value);
+ switch (address) {
+ case (CIRRUS_MMIO_BLTBGCOLOR + 0):
+ cirrus_vga_write_gr(s, 0x00, value);
+ break;
+ case (CIRRUS_MMIO_BLTBGCOLOR + 1):
+ cirrus_vga_write_gr(s, 0x10, value);
+ break;
+ case (CIRRUS_MMIO_BLTBGCOLOR + 2):
+ cirrus_vga_write_gr(s, 0x12, value);
+ break;
+ case (CIRRUS_MMIO_BLTBGCOLOR + 3):
+ cirrus_vga_write_gr(s, 0x14, value);
+ break;
+ case (CIRRUS_MMIO_BLTFGCOLOR + 0):
+ cirrus_vga_write_gr(s, 0x01, value);
+ break;
+ case (CIRRUS_MMIO_BLTFGCOLOR + 1):
+ cirrus_vga_write_gr(s, 0x11, value);
+ break;
+ case (CIRRUS_MMIO_BLTFGCOLOR + 2):
+ cirrus_vga_write_gr(s, 0x13, value);
+ break;
+ case (CIRRUS_MMIO_BLTFGCOLOR + 3):
+ cirrus_vga_write_gr(s, 0x15, value);
+ break;
+ case (CIRRUS_MMIO_BLTWIDTH + 0):
+ cirrus_vga_write_gr(s, 0x20, value);
+ break;
+ case (CIRRUS_MMIO_BLTWIDTH + 1):
+ cirrus_vga_write_gr(s, 0x21, value);
+ break;
+ case (CIRRUS_MMIO_BLTHEIGHT + 0):
+ cirrus_vga_write_gr(s, 0x22, value);
+ break;
+ case (CIRRUS_MMIO_BLTHEIGHT + 1):
+ cirrus_vga_write_gr(s, 0x23, value);
+ break;
+ case (CIRRUS_MMIO_BLTDESTPITCH + 0):
+ cirrus_vga_write_gr(s, 0x24, value);
+ break;
+ case (CIRRUS_MMIO_BLTDESTPITCH + 1):
+ cirrus_vga_write_gr(s, 0x25, value);
+ break;
+ case (CIRRUS_MMIO_BLTSRCPITCH + 0):
+ cirrus_vga_write_gr(s, 0x26, value);
+ break;
+ case (CIRRUS_MMIO_BLTSRCPITCH + 1):
+ cirrus_vga_write_gr(s, 0x27, value);
+ break;
+ case (CIRRUS_MMIO_BLTDESTADDR + 0):
+ cirrus_vga_write_gr(s, 0x28, value);
+ break;
+ case (CIRRUS_MMIO_BLTDESTADDR + 1):
+ cirrus_vga_write_gr(s, 0x29, value);
+ break;
+ case (CIRRUS_MMIO_BLTDESTADDR + 2):
+ cirrus_vga_write_gr(s, 0x2a, value);
+ break;
+ case (CIRRUS_MMIO_BLTDESTADDR + 3):
+ /* ignored */
+ break;
+ case (CIRRUS_MMIO_BLTSRCADDR + 0):
+ cirrus_vga_write_gr(s, 0x2c, value);
+ break;
+ case (CIRRUS_MMIO_BLTSRCADDR + 1):
+ cirrus_vga_write_gr(s, 0x2d, value);
+ break;
+ case (CIRRUS_MMIO_BLTSRCADDR + 2):
+ cirrus_vga_write_gr(s, 0x2e, value);
+ break;
+ case CIRRUS_MMIO_BLTWRITEMASK:
+ cirrus_vga_write_gr(s, 0x2f, value);
+ break;
+ case CIRRUS_MMIO_BLTMODE:
+ cirrus_vga_write_gr(s, 0x30, value);
+ break;
+ case CIRRUS_MMIO_BLTROP:
+ cirrus_vga_write_gr(s, 0x32, value);
+ break;
+ case CIRRUS_MMIO_BLTMODEEXT:
+ cirrus_vga_write_gr(s, 0x33, value);
+ break;
+ case (CIRRUS_MMIO_BLTTRANSPARENTCOLOR + 0):
+ cirrus_vga_write_gr(s, 0x34, value);
+ break;
+ case (CIRRUS_MMIO_BLTTRANSPARENTCOLOR + 1):
+ cirrus_vga_write_gr(s, 0x35, value);
+ break;
+ case (CIRRUS_MMIO_BLTTRANSPARENTCOLORMASK + 0):
+ cirrus_vga_write_gr(s, 0x38, value);
+ break;
+ case (CIRRUS_MMIO_BLTTRANSPARENTCOLORMASK + 1):
+ cirrus_vga_write_gr(s, 0x39, value);
+ break;
+ case CIRRUS_MMIO_BLTSTATUS:
+ cirrus_vga_write_gr(s, 0x31, value);
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "cirrus: mmio write - addr 0x%04x val 0x%02x (ignored)\n",
+ address, value);
+ break;
+ }
+}
+
+/***************************************
+ *
+ * write mode 4/5
+ *
+ ***************************************/
+
+static void cirrus_mem_writeb_mode4and5_8bpp(CirrusVGAState * s,
+ unsigned mode,
+ unsigned offset,
+ uint32_t mem_value)
+{
+ int x;
+ unsigned val = mem_value;
+ uint8_t *dst;
+
+ for (x = 0; x < 8; x++) {
+ dst = s->vga.vram_ptr + ((offset + x) & s->cirrus_addr_mask);
+ if (val & 0x80) {
+ *dst = s->cirrus_shadow_gr1;
+ } else if (mode == 5) {
+ *dst = s->cirrus_shadow_gr0;
+ }
+ val <<= 1;
+ }
+ memory_region_set_dirty(&s->vga.vram, offset, 8);
+}
+
+static void cirrus_mem_writeb_mode4and5_16bpp(CirrusVGAState * s,
+ unsigned mode,
+ unsigned offset,
+ uint32_t mem_value)
+{
+ int x;
+ unsigned val = mem_value;
+ uint8_t *dst;
+
+ for (x = 0; x < 8; x++) {
+ dst = s->vga.vram_ptr + ((offset + 2 * x) & s->cirrus_addr_mask & ~1);
+ if (val & 0x80) {
+ *dst = s->cirrus_shadow_gr1;
+ *(dst + 1) = s->vga.gr[0x11];
+ } else if (mode == 5) {
+ *dst = s->cirrus_shadow_gr0;
+ *(dst + 1) = s->vga.gr[0x10];
+ }
+ val <<= 1;
+ }
+ memory_region_set_dirty(&s->vga.vram, offset, 16);
+}
+
+/***************************************
+ *
+ * memory access between 0xa0000-0xbffff
+ *
+ ***************************************/
+
+static uint64_t cirrus_vga_mem_read(void *opaque,
+ hwaddr addr,
+ uint32_t size)
+{
+ CirrusVGAState *s = opaque;
+ unsigned bank_index;
+ unsigned bank_offset;
+ uint32_t val;
+
+ if ((s->vga.sr[0x07] & 0x01) == 0) {
+ return vga_mem_readb(&s->vga, addr);
+ }
+
+ if (addr < 0x10000) {
+ /* XXX handle bitblt */
+ /* video memory */
+ bank_index = addr >> 15;
+ bank_offset = addr & 0x7fff;
+ if (bank_offset < s->cirrus_bank_limit[bank_index]) {
+ bank_offset += s->cirrus_bank_base[bank_index];
+ if ((s->vga.gr[0x0B] & 0x14) == 0x14) {
+ bank_offset <<= 4;
+ } else if (s->vga.gr[0x0B] & 0x02) {
+ bank_offset <<= 3;
+ }
+ bank_offset &= s->cirrus_addr_mask;
+ val = *(s->vga.vram_ptr + bank_offset);
+ } else
+ val = 0xff;
+ } else if (addr >= 0x18000 && addr < 0x18100) {
+ /* memory-mapped I/O */
+ val = 0xff;
+ if ((s->vga.sr[0x17] & 0x44) == 0x04) {
+ val = cirrus_mmio_blt_read(s, addr & 0xff);
+ }
+ } else {
+ val = 0xff;
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "cirrus: mem_readb 0x" TARGET_FMT_plx "\n", addr);
+ }
+ return val;
+}
+
+static void cirrus_vga_mem_write(void *opaque,
+ hwaddr addr,
+ uint64_t mem_value,
+ uint32_t size)
+{
+ CirrusVGAState *s = opaque;
+ unsigned bank_index;
+ unsigned bank_offset;
+ unsigned mode;
+
+ if ((s->vga.sr[0x07] & 0x01) == 0) {
+ vga_mem_writeb(&s->vga, addr, mem_value);
+ return;
+ }
+
+ if (addr < 0x10000) {
+ if (s->cirrus_srcptr != s->cirrus_srcptr_end) {
+ /* bitblt */
+ *s->cirrus_srcptr++ = (uint8_t) mem_value;
+ if (s->cirrus_srcptr >= s->cirrus_srcptr_end) {
+ cirrus_bitblt_cputovideo_next(s);
+ }
+ } else {
+ /* video memory */
+ bank_index = addr >> 15;
+ bank_offset = addr & 0x7fff;
+ if (bank_offset < s->cirrus_bank_limit[bank_index]) {
+ bank_offset += s->cirrus_bank_base[bank_index];
+ if ((s->vga.gr[0x0B] & 0x14) == 0x14) {
+ bank_offset <<= 4;
+ } else if (s->vga.gr[0x0B] & 0x02) {
+ bank_offset <<= 3;
+ }
+ bank_offset &= s->cirrus_addr_mask;
+ mode = s->vga.gr[0x05] & 0x7;
+ if (mode < 4 || mode > 5 || ((s->vga.gr[0x0B] & 0x4) == 0)) {
+ *(s->vga.vram_ptr + bank_offset) = mem_value;
+ memory_region_set_dirty(&s->vga.vram, bank_offset,
+ sizeof(mem_value));
+ } else {
+ if ((s->vga.gr[0x0B] & 0x14) != 0x14) {
+ cirrus_mem_writeb_mode4and5_8bpp(s, mode,
+ bank_offset,
+ mem_value);
+ } else {
+ cirrus_mem_writeb_mode4and5_16bpp(s, mode,
+ bank_offset,
+ mem_value);
+ }
+ }
+ }
+ }
+ } else if (addr >= 0x18000 && addr < 0x18100) {
+ /* memory-mapped I/O */
+ if ((s->vga.sr[0x17] & 0x44) == 0x04) {
+ cirrus_mmio_blt_write(s, addr & 0xff, mem_value);
+ }
+ } else {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "cirrus: mem_writeb 0x" TARGET_FMT_plx " "
+ "value 0x%02" PRIx64 "\n", addr, mem_value);
+ }
+}
+
+static const MemoryRegionOps cirrus_vga_mem_ops = {
+ .read = cirrus_vga_mem_read,
+ .write = cirrus_vga_mem_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+/***************************************
+ *
+ * hardware cursor
+ *
+ ***************************************/
+
+static inline void invalidate_cursor1(CirrusVGAState *s)
+{
+ if (s->last_hw_cursor_size) {
+ vga_invalidate_scanlines(&s->vga,
+ s->last_hw_cursor_y + s->last_hw_cursor_y_start,
+ s->last_hw_cursor_y + s->last_hw_cursor_y_end);
+ }
+}
+
+static inline void cirrus_cursor_compute_yrange(CirrusVGAState *s)
+{
+ const uint8_t *src;
+ uint32_t content;
+ int y, y_min, y_max;
+
+ src = s->vga.vram_ptr + s->real_vram_size - 16 * KiB;
+ if (s->vga.sr[0x12] & CIRRUS_CURSOR_LARGE) {
+ src += (s->vga.sr[0x13] & 0x3c) * 256;
+ y_min = 64;
+ y_max = -1;
+ for(y = 0; y < 64; y++) {
+ content = ((uint32_t *)src)[0] |
+ ((uint32_t *)src)[1] |
+ ((uint32_t *)src)[2] |
+ ((uint32_t *)src)[3];
+ if (content) {
+ if (y < y_min)
+ y_min = y;
+ if (y > y_max)
+ y_max = y;
+ }
+ src += 16;
+ }
+ } else {
+ src += (s->vga.sr[0x13] & 0x3f) * 256;
+ y_min = 32;
+ y_max = -1;
+ for(y = 0; y < 32; y++) {
+ content = ((uint32_t *)src)[0] |
+ ((uint32_t *)(src + 128))[0];
+ if (content) {
+ if (y < y_min)
+ y_min = y;
+ if (y > y_max)
+ y_max = y;
+ }
+ src += 4;
+ }
+ }
+ if (y_min > y_max) {
+ s->last_hw_cursor_y_start = 0;
+ s->last_hw_cursor_y_end = 0;
+ } else {
+ s->last_hw_cursor_y_start = y_min;
+ s->last_hw_cursor_y_end = y_max + 1;
+ }
+}
+
+/* NOTE: we do not currently handle the cursor bitmap change, so we
+ update the cursor only if it moves. */
+static void cirrus_cursor_invalidate(VGACommonState *s1)
+{
+ CirrusVGAState *s = container_of(s1, CirrusVGAState, vga);
+ int size;
+
+ if (!(s->vga.sr[0x12] & CIRRUS_CURSOR_SHOW)) {
+ size = 0;
+ } else {
+ if (s->vga.sr[0x12] & CIRRUS_CURSOR_LARGE)
+ size = 64;
+ else
+ size = 32;
+ }
+ /* invalidate last cursor and new cursor if any change */
+ if (s->last_hw_cursor_size != size ||
+ s->last_hw_cursor_x != s->vga.hw_cursor_x ||
+ s->last_hw_cursor_y != s->vga.hw_cursor_y) {
+
+ invalidate_cursor1(s);
+
+ s->last_hw_cursor_size = size;
+ s->last_hw_cursor_x = s->vga.hw_cursor_x;
+ s->last_hw_cursor_y = s->vga.hw_cursor_y;
+ /* compute the real cursor min and max y */
+ cirrus_cursor_compute_yrange(s);
+ invalidate_cursor1(s);
+ }
+}
+
+static void vga_draw_cursor_line(uint8_t *d1,
+ const uint8_t *src1,
+ int poffset, int w,
+ unsigned int color0,
+ unsigned int color1,
+ unsigned int color_xor)
+{
+ const uint8_t *plane0, *plane1;
+ int x, b0, b1;
+ uint8_t *d;
+
+ d = d1;
+ plane0 = src1;
+ plane1 = src1 + poffset;
+ for (x = 0; x < w; x++) {
+ b0 = (plane0[x >> 3] >> (7 - (x & 7))) & 1;
+ b1 = (plane1[x >> 3] >> (7 - (x & 7))) & 1;
+ switch (b0 | (b1 << 1)) {
+ case 0:
+ break;
+ case 1:
+ ((uint32_t *)d)[0] ^= color_xor;
+ break;
+ case 2:
+ ((uint32_t *)d)[0] = color0;
+ break;
+ case 3:
+ ((uint32_t *)d)[0] = color1;
+ break;
+ }
+ d += 4;
+ }
+}
+
+static void cirrus_cursor_draw_line(VGACommonState *s1, uint8_t *d1, int scr_y)
+{
+ CirrusVGAState *s = container_of(s1, CirrusVGAState, vga);
+ int w, h, x1, x2, poffset;
+ unsigned int color0, color1;
+ const uint8_t *palette, *src;
+ uint32_t content;
+
+ if (!(s->vga.sr[0x12] & CIRRUS_CURSOR_SHOW))
+ return;
+ /* fast test to see if the cursor intersects with the scan line */
+ if (s->vga.sr[0x12] & CIRRUS_CURSOR_LARGE) {
+ h = 64;
+ } else {
+ h = 32;
+ }
+ if (scr_y < s->vga.hw_cursor_y ||
+ scr_y >= (s->vga.hw_cursor_y + h)) {
+ return;
+ }
+
+ src = s->vga.vram_ptr + s->real_vram_size - 16 * KiB;
+ if (s->vga.sr[0x12] & CIRRUS_CURSOR_LARGE) {
+ src += (s->vga.sr[0x13] & 0x3c) * 256;
+ src += (scr_y - s->vga.hw_cursor_y) * 16;
+ poffset = 8;
+ content = ((uint32_t *)src)[0] |
+ ((uint32_t *)src)[1] |
+ ((uint32_t *)src)[2] |
+ ((uint32_t *)src)[3];
+ } else {
+ src += (s->vga.sr[0x13] & 0x3f) * 256;
+ src += (scr_y - s->vga.hw_cursor_y) * 4;
+
+
+ poffset = 128;
+ content = ((uint32_t *)src)[0] |
+ ((uint32_t *)(src + 128))[0];
+ }
+ /* if nothing to draw, no need to continue */
+ if (!content)
+ return;
+ w = h;
+
+ x1 = s->vga.hw_cursor_x;
+ if (x1 >= s->vga.last_scr_width)
+ return;
+ x2 = s->vga.hw_cursor_x + w;
+ if (x2 > s->vga.last_scr_width)
+ x2 = s->vga.last_scr_width;
+ w = x2 - x1;
+ palette = s->cirrus_hidden_palette;
+ color0 = rgb_to_pixel32(c6_to_8(palette[0x0 * 3]),
+ c6_to_8(palette[0x0 * 3 + 1]),
+ c6_to_8(palette[0x0 * 3 + 2]));
+ color1 = rgb_to_pixel32(c6_to_8(palette[0xf * 3]),
+ c6_to_8(palette[0xf * 3 + 1]),
+ c6_to_8(palette[0xf * 3 + 2]));
+ d1 += x1 * 4;
+ vga_draw_cursor_line(d1, src, poffset, w, color0, color1, 0xffffff);
+}
+
+/***************************************
+ *
+ * LFB memory access
+ *
+ ***************************************/
+
+static uint64_t cirrus_linear_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ CirrusVGAState *s = opaque;
+ uint32_t ret;
+
+ addr &= s->cirrus_addr_mask;
+
+ if (((s->vga.sr[0x17] & 0x44) == 0x44) &&
+ ((addr & s->linear_mmio_mask) == s->linear_mmio_mask)) {
+ /* memory-mapped I/O */
+ ret = cirrus_mmio_blt_read(s, addr & 0xff);
+ } else if (0) {
+ /* XXX handle bitblt */
+ ret = 0xff;
+ } else {
+ /* video memory */
+ if ((s->vga.gr[0x0B] & 0x14) == 0x14) {
+ addr <<= 4;
+ } else if (s->vga.gr[0x0B] & 0x02) {
+ addr <<= 3;
+ }
+ addr &= s->cirrus_addr_mask;
+ ret = *(s->vga.vram_ptr + addr);
+ }
+
+ return ret;
+}
+
+static void cirrus_linear_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ CirrusVGAState *s = opaque;
+ unsigned mode;
+
+ addr &= s->cirrus_addr_mask;
+
+ if (((s->vga.sr[0x17] & 0x44) == 0x44) &&
+ ((addr & s->linear_mmio_mask) == s->linear_mmio_mask)) {
+ /* memory-mapped I/O */
+ cirrus_mmio_blt_write(s, addr & 0xff, val);
+ } else if (s->cirrus_srcptr != s->cirrus_srcptr_end) {
+ /* bitblt */
+ *s->cirrus_srcptr++ = (uint8_t) val;
+ if (s->cirrus_srcptr >= s->cirrus_srcptr_end) {
+ cirrus_bitblt_cputovideo_next(s);
+ }
+ } else {
+ /* video memory */
+ if ((s->vga.gr[0x0B] & 0x14) == 0x14) {
+ addr <<= 4;
+ } else if (s->vga.gr[0x0B] & 0x02) {
+ addr <<= 3;
+ }
+ addr &= s->cirrus_addr_mask;
+
+ mode = s->vga.gr[0x05] & 0x7;
+ if (mode < 4 || mode > 5 || ((s->vga.gr[0x0B] & 0x4) == 0)) {
+ *(s->vga.vram_ptr + addr) = (uint8_t) val;
+ memory_region_set_dirty(&s->vga.vram, addr, 1);
+ } else {
+ if ((s->vga.gr[0x0B] & 0x14) != 0x14) {
+ cirrus_mem_writeb_mode4and5_8bpp(s, mode, addr, val);
+ } else {
+ cirrus_mem_writeb_mode4and5_16bpp(s, mode, addr, val);
+ }
+ }
+ }
+}
+
+/***************************************
+ *
+ * system to screen memory access
+ *
+ ***************************************/
+
+
+static uint64_t cirrus_linear_bitblt_read(void *opaque,
+ hwaddr addr,
+ unsigned size)
+{
+ CirrusVGAState *s = opaque;
+
+ /* XXX handle bitblt */
+ (void)s;
+ qemu_log_mask(LOG_UNIMP,
+ "cirrus: linear bitblt is not implemented\n");
+
+ return 0xff;
+}
+
+static void cirrus_linear_bitblt_write(void *opaque,
+ hwaddr addr,
+ uint64_t val,
+ unsigned size)
+{
+ CirrusVGAState *s = opaque;
+
+ if (s->cirrus_srcptr != s->cirrus_srcptr_end) {
+ /* bitblt */
+ *s->cirrus_srcptr++ = (uint8_t) val;
+ if (s->cirrus_srcptr >= s->cirrus_srcptr_end) {
+ cirrus_bitblt_cputovideo_next(s);
+ }
+ }
+}
+
+static const MemoryRegionOps cirrus_linear_bitblt_io_ops = {
+ .read = cirrus_linear_bitblt_read,
+ .write = cirrus_linear_bitblt_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static void map_linear_vram_bank(CirrusVGAState *s, unsigned bank)
+{
+ MemoryRegion *mr = &s->cirrus_bank[bank];
+ bool enabled = !(s->cirrus_srcptr != s->cirrus_srcptr_end)
+ && !((s->vga.sr[0x07] & 0x01) == 0)
+ && !((s->vga.gr[0x0B] & 0x14) == 0x14)
+ && !(s->vga.gr[0x0B] & 0x02);
+
+ memory_region_set_enabled(mr, enabled);
+ memory_region_set_alias_offset(mr, s->cirrus_bank_base[bank]);
+}
+
+static void map_linear_vram(CirrusVGAState *s)
+{
+ if (s->bustype == CIRRUS_BUSTYPE_PCI && !s->linear_vram) {
+ s->linear_vram = true;
+ memory_region_add_subregion_overlap(&s->pci_bar, 0, &s->vga.vram, 1);
+ }
+ map_linear_vram_bank(s, 0);
+ map_linear_vram_bank(s, 1);
+}
+
+static void unmap_linear_vram(CirrusVGAState *s)
+{
+ if (s->bustype == CIRRUS_BUSTYPE_PCI && s->linear_vram) {
+ s->linear_vram = false;
+ memory_region_del_subregion(&s->pci_bar, &s->vga.vram);
+ }
+ memory_region_set_enabled(&s->cirrus_bank[0], false);
+ memory_region_set_enabled(&s->cirrus_bank[1], false);
+}
+
+/* Compute the memory access functions */
+static void cirrus_update_memory_access(CirrusVGAState *s)
+{
+ unsigned mode;
+
+ memory_region_transaction_begin();
+ if ((s->vga.sr[0x17] & 0x44) == 0x44) {
+ goto generic_io;
+ } else if (s->cirrus_srcptr != s->cirrus_srcptr_end) {
+ goto generic_io;
+ } else {
+ if ((s->vga.gr[0x0B] & 0x14) == 0x14) {
+ goto generic_io;
+ } else if (s->vga.gr[0x0B] & 0x02) {
+ goto generic_io;
+ }
+
+ mode = s->vga.gr[0x05] & 0x7;
+ if (mode < 4 || mode > 5 || ((s->vga.gr[0x0B] & 0x4) == 0)) {
+ map_linear_vram(s);
+ } else {
+ generic_io:
+ unmap_linear_vram(s);
+ }
+ }
+ memory_region_transaction_commit();
+}
+
+
+/* I/O ports */
+
+static uint64_t cirrus_vga_ioport_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ CirrusVGAState *c = opaque;
+ VGACommonState *s = &c->vga;
+ int val, index;
+
+ addr += 0x3b0;
+
+ if (vga_ioport_invalid(s, addr)) {
+ val = 0xff;
+ } else {
+ switch (addr) {
+ case 0x3c0:
+ if (s->ar_flip_flop == 0) {
+ val = s->ar_index;
+ } else {
+ val = 0;
+ }
+ break;
+ case 0x3c1:
+ index = s->ar_index & 0x1f;
+ if (index < 21)
+ val = s->ar[index];
+ else
+ val = 0;
+ break;
+ case 0x3c2:
+ val = s->st00;
+ break;
+ case 0x3c4:
+ val = s->sr_index;
+ break;
+ case 0x3c5:
+ val = cirrus_vga_read_sr(c);
+ break;
+ break;
+ case 0x3c6:
+ val = cirrus_read_hidden_dac(c);
+ break;
+ case 0x3c7:
+ val = s->dac_state;
+ break;
+ case 0x3c8:
+ val = s->dac_write_index;
+ c->cirrus_hidden_dac_lockindex = 0;
+ break;
+ case 0x3c9:
+ val = cirrus_vga_read_palette(c);
+ break;
+ case 0x3ca:
+ val = s->fcr;
+ break;
+ case 0x3cc:
+ val = s->msr;
+ break;
+ case 0x3ce:
+ val = s->gr_index;
+ break;
+ case 0x3cf:
+ val = cirrus_vga_read_gr(c, s->gr_index);
+ break;
+ case 0x3b4:
+ case 0x3d4:
+ val = s->cr_index;
+ break;
+ case 0x3b5:
+ case 0x3d5:
+ val = cirrus_vga_read_cr(c, s->cr_index);
+ break;
+ case 0x3ba:
+ case 0x3da:
+ /* just toggle to fool polling */
+ val = s->st01 = s->retrace(s);
+ s->ar_flip_flop = 0;
+ break;
+ default:
+ val = 0x00;
+ break;
+ }
+ }
+ trace_vga_cirrus_read_io(addr, val);
+ return val;
+}
+
+static void cirrus_vga_ioport_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ CirrusVGAState *c = opaque;
+ VGACommonState *s = &c->vga;
+ int index;
+
+ addr += 0x3b0;
+
+ /* check port range access depending on color/monochrome mode */
+ if (vga_ioport_invalid(s, addr)) {
+ return;
+ }
+ trace_vga_cirrus_write_io(addr, val);
+
+ switch (addr) {
+ case 0x3c0:
+ if (s->ar_flip_flop == 0) {
+ val &= 0x3f;
+ s->ar_index = val;
+ } else {
+ index = s->ar_index & 0x1f;
+ switch (index) {
+ case 0x00 ... 0x0f:
+ s->ar[index] = val & 0x3f;
+ break;
+ case 0x10:
+ s->ar[index] = val & ~0x10;
+ break;
+ case 0x11:
+ s->ar[index] = val;
+ break;
+ case 0x12:
+ s->ar[index] = val & ~0xc0;
+ break;
+ case 0x13:
+ s->ar[index] = val & ~0xf0;
+ break;
+ case 0x14:
+ s->ar[index] = val & ~0xf0;
+ break;
+ default:
+ break;
+ }
+ }
+ s->ar_flip_flop ^= 1;
+ break;
+ case 0x3c2:
+ s->msr = val & ~0x10;
+ s->update_retrace_info(s);
+ break;
+ case 0x3c4:
+ s->sr_index = val;
+ break;
+ case 0x3c5:
+ cirrus_vga_write_sr(c, val);
+ break;
+ case 0x3c6:
+ cirrus_write_hidden_dac(c, val);
+ break;
+ case 0x3c7:
+ s->dac_read_index = val;
+ s->dac_sub_index = 0;
+ s->dac_state = 3;
+ break;
+ case 0x3c8:
+ s->dac_write_index = val;
+ s->dac_sub_index = 0;
+ s->dac_state = 0;
+ break;
+ case 0x3c9:
+ cirrus_vga_write_palette(c, val);
+ break;
+ case 0x3ce:
+ s->gr_index = val;
+ break;
+ case 0x3cf:
+ cirrus_vga_write_gr(c, s->gr_index, val);
+ break;
+ case 0x3b4:
+ case 0x3d4:
+ s->cr_index = val;
+ break;
+ case 0x3b5:
+ case 0x3d5:
+ cirrus_vga_write_cr(c, val);
+ break;
+ case 0x3ba:
+ case 0x3da:
+ s->fcr = val & 0x10;
+ break;
+ }
+}
+
+/***************************************
+ *
+ * memory-mapped I/O access
+ *
+ ***************************************/
+
+static uint64_t cirrus_mmio_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ CirrusVGAState *s = opaque;
+
+ if (addr >= 0x100) {
+ return cirrus_mmio_blt_read(s, addr - 0x100);
+ } else {
+ return cirrus_vga_ioport_read(s, addr + 0x10, size);
+ }
+}
+
+static void cirrus_mmio_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ CirrusVGAState *s = opaque;
+
+ if (addr >= 0x100) {
+ cirrus_mmio_blt_write(s, addr - 0x100, val);
+ } else {
+ cirrus_vga_ioport_write(s, addr + 0x10, val, size);
+ }
+}
+
+static const MemoryRegionOps cirrus_mmio_io_ops = {
+ .read = cirrus_mmio_read,
+ .write = cirrus_mmio_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+/* load/save state */
+
+static int cirrus_post_load(void *opaque, int version_id)
+{
+ CirrusVGAState *s = opaque;
+
+ s->vga.gr[0x00] = s->cirrus_shadow_gr0 & 0x0f;
+ s->vga.gr[0x01] = s->cirrus_shadow_gr1 & 0x0f;
+
+ cirrus_update_bank_ptr(s, 0);
+ cirrus_update_bank_ptr(s, 1);
+ cirrus_update_memory_access(s);
+ /* force refresh */
+ s->vga.graphic_mode = -1;
+
+ return 0;
+}
+
+const VMStateDescription vmstate_cirrus_vga = {
+ .name = "cirrus_vga",
+ .version_id = 2,
+ .minimum_version_id = 1,
+ .post_load = cirrus_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(vga.latch, CirrusVGAState),
+ VMSTATE_UINT8(vga.sr_index, CirrusVGAState),
+ VMSTATE_BUFFER(vga.sr, CirrusVGAState),
+ VMSTATE_UINT8(vga.gr_index, CirrusVGAState),
+ VMSTATE_UINT8(cirrus_shadow_gr0, CirrusVGAState),
+ VMSTATE_UINT8(cirrus_shadow_gr1, CirrusVGAState),
+ VMSTATE_BUFFER_START_MIDDLE(vga.gr, CirrusVGAState, 2),
+ VMSTATE_UINT8(vga.ar_index, CirrusVGAState),
+ VMSTATE_BUFFER(vga.ar, CirrusVGAState),
+ VMSTATE_INT32(vga.ar_flip_flop, CirrusVGAState),
+ VMSTATE_UINT8(vga.cr_index, CirrusVGAState),
+ VMSTATE_BUFFER(vga.cr, CirrusVGAState),
+ VMSTATE_UINT8(vga.msr, CirrusVGAState),
+ VMSTATE_UINT8(vga.fcr, CirrusVGAState),
+ VMSTATE_UINT8(vga.st00, CirrusVGAState),
+ VMSTATE_UINT8(vga.st01, CirrusVGAState),
+ VMSTATE_UINT8(vga.dac_state, CirrusVGAState),
+ VMSTATE_UINT8(vga.dac_sub_index, CirrusVGAState),
+ VMSTATE_UINT8(vga.dac_read_index, CirrusVGAState),
+ VMSTATE_UINT8(vga.dac_write_index, CirrusVGAState),
+ VMSTATE_BUFFER(vga.dac_cache, CirrusVGAState),
+ VMSTATE_BUFFER(vga.palette, CirrusVGAState),
+ VMSTATE_INT32(vga.bank_offset, CirrusVGAState),
+ VMSTATE_UINT8(cirrus_hidden_dac_lockindex, CirrusVGAState),
+ VMSTATE_UINT8(cirrus_hidden_dac_data, CirrusVGAState),
+ VMSTATE_UINT32(vga.hw_cursor_x, CirrusVGAState),
+ VMSTATE_UINT32(vga.hw_cursor_y, CirrusVGAState),
+ /* XXX: we do not save the bitblt state - we assume we do not save
+ the state when the blitter is active */
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_pci_cirrus_vga = {
+ .name = "cirrus_vga",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(dev, PCICirrusVGAState),
+ VMSTATE_STRUCT(cirrus_vga, PCICirrusVGAState, 0,
+ vmstate_cirrus_vga, CirrusVGAState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+/***************************************
+ *
+ * initialize
+ *
+ ***************************************/
+
+static void cirrus_reset(void *opaque)
+{
+ CirrusVGAState *s = opaque;
+
+ vga_common_reset(&s->vga);
+ unmap_linear_vram(s);
+ s->vga.sr[0x06] = 0x0f;
+ if (s->device_id == CIRRUS_ID_CLGD5446) {
+ /* 4MB 64 bit memory config, always PCI */
+ s->vga.sr[0x1F] = 0x2d; // MemClock
+ s->vga.gr[0x18] = 0x0f; // fastest memory configuration
+ s->vga.sr[0x0f] = 0x98;
+ s->vga.sr[0x17] = 0x20;
+ s->vga.sr[0x15] = 0x04; /* memory size, 3=2MB, 4=4MB */
+ } else {
+ s->vga.sr[0x1F] = 0x22; // MemClock
+ s->vga.sr[0x0F] = CIRRUS_MEMSIZE_2M;
+ s->vga.sr[0x17] = s->bustype;
+ s->vga.sr[0x15] = 0x03; /* memory size, 3=2MB, 4=4MB */
+ }
+ s->vga.cr[0x27] = s->device_id;
+
+ s->cirrus_hidden_dac_lockindex = 5;
+ s->cirrus_hidden_dac_data = 0;
+}
+
+static const MemoryRegionOps cirrus_linear_io_ops = {
+ .read = cirrus_linear_read,
+ .write = cirrus_linear_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static const MemoryRegionOps cirrus_vga_io_ops = {
+ .read = cirrus_vga_ioport_read,
+ .write = cirrus_vga_ioport_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+void cirrus_init_common(CirrusVGAState *s, Object *owner,
+ int device_id, int is_pci,
+ MemoryRegion *system_memory, MemoryRegion *system_io)
+{
+ int i;
+ static int inited;
+
+ if (!inited) {
+ inited = 1;
+ for(i = 0;i < 256; i++)
+ rop_to_index[i] = CIRRUS_ROP_NOP_INDEX; /* nop rop */
+ rop_to_index[CIRRUS_ROP_0] = 0;
+ rop_to_index[CIRRUS_ROP_SRC_AND_DST] = 1;
+ rop_to_index[CIRRUS_ROP_NOP] = 2;
+ rop_to_index[CIRRUS_ROP_SRC_AND_NOTDST] = 3;
+ rop_to_index[CIRRUS_ROP_NOTDST] = 4;
+ rop_to_index[CIRRUS_ROP_SRC] = 5;
+ rop_to_index[CIRRUS_ROP_1] = 6;
+ rop_to_index[CIRRUS_ROP_NOTSRC_AND_DST] = 7;
+ rop_to_index[CIRRUS_ROP_SRC_XOR_DST] = 8;
+ rop_to_index[CIRRUS_ROP_SRC_OR_DST] = 9;
+ rop_to_index[CIRRUS_ROP_NOTSRC_OR_NOTDST] = 10;
+ rop_to_index[CIRRUS_ROP_SRC_NOTXOR_DST] = 11;
+ rop_to_index[CIRRUS_ROP_SRC_OR_NOTDST] = 12;
+ rop_to_index[CIRRUS_ROP_NOTSRC] = 13;
+ rop_to_index[CIRRUS_ROP_NOTSRC_OR_DST] = 14;
+ rop_to_index[CIRRUS_ROP_NOTSRC_AND_NOTDST] = 15;
+ s->device_id = device_id;
+ if (is_pci)
+ s->bustype = CIRRUS_BUSTYPE_PCI;
+ else
+ s->bustype = CIRRUS_BUSTYPE_ISA;
+ }
+
+ /* Register ioport 0x3b0 - 0x3df */
+ memory_region_init_io(&s->cirrus_vga_io, owner, &cirrus_vga_io_ops, s,
+ "cirrus-io", 0x30);
+ memory_region_set_flush_coalesced(&s->cirrus_vga_io);
+ memory_region_add_subregion(system_io, 0x3b0, &s->cirrus_vga_io);
+
+ memory_region_init(&s->low_mem_container, owner,
+ "cirrus-lowmem-container",
+ 0x20000);
+
+ memory_region_init_io(&s->low_mem, owner, &cirrus_vga_mem_ops, s,
+ "cirrus-low-memory", 0x20000);
+ memory_region_add_subregion(&s->low_mem_container, 0, &s->low_mem);
+ for (i = 0; i < 2; ++i) {
+ static const char *names[] = { "vga.bank0", "vga.bank1" };
+ MemoryRegion *bank = &s->cirrus_bank[i];
+ memory_region_init_alias(bank, owner, names[i], &s->vga.vram,
+ 0, 0x8000);
+ memory_region_set_enabled(bank, false);
+ memory_region_add_subregion_overlap(&s->low_mem_container, i * 0x8000,
+ bank, 1);
+ }
+ memory_region_add_subregion_overlap(system_memory,
+ 0x000a0000,
+ &s->low_mem_container,
+ 1);
+ memory_region_set_coalescing(&s->low_mem);
+
+ /* I/O handler for LFB */
+ memory_region_init_io(&s->cirrus_linear_io, owner, &cirrus_linear_io_ops, s,
+ "cirrus-linear-io", s->vga.vram_size_mb * MiB);
+ memory_region_set_flush_coalesced(&s->cirrus_linear_io);
+
+ /* I/O handler for LFB */
+ memory_region_init_io(&s->cirrus_linear_bitblt_io, owner,
+ &cirrus_linear_bitblt_io_ops,
+ s,
+ "cirrus-bitblt-mmio",
+ 0x400000);
+ memory_region_set_flush_coalesced(&s->cirrus_linear_bitblt_io);
+
+ /* I/O handler for memory-mapped I/O */
+ memory_region_init_io(&s->cirrus_mmio_io, owner, &cirrus_mmio_io_ops, s,
+ "cirrus-mmio", CIRRUS_PNPMMIO_SIZE);
+ memory_region_set_flush_coalesced(&s->cirrus_mmio_io);
+
+ s->real_vram_size =
+ (s->device_id == CIRRUS_ID_CLGD5446) ? 4 * MiB : 2 * MiB;
+
+ /* XXX: s->vga.vram_size must be a power of two */
+ s->cirrus_addr_mask = s->real_vram_size - 1;
+ s->linear_mmio_mask = s->real_vram_size - 256;
+
+ s->vga.get_bpp = cirrus_get_bpp;
+ s->vga.get_offsets = cirrus_get_offsets;
+ s->vga.get_resolution = cirrus_get_resolution;
+ s->vga.cursor_invalidate = cirrus_cursor_invalidate;
+ s->vga.cursor_draw_line = cirrus_cursor_draw_line;
+
+ qemu_register_reset(cirrus_reset, s);
+}
+
+/***************************************
+ *
+ * PCI bus support
+ *
+ ***************************************/
+
+static void pci_cirrus_vga_realize(PCIDevice *dev, Error **errp)
+{
+ PCICirrusVGAState *d = PCI_CIRRUS_VGA(dev);
+ CirrusVGAState *s = &d->cirrus_vga;
+ PCIDeviceClass *pc = PCI_DEVICE_GET_CLASS(dev);
+ int16_t device_id = pc->device_id;
+
+ /* follow real hardware, cirrus card emulated has 4 MB video memory.
+ Also accept 8 MB/16 MB for backward compatibility. */
+ if (s->vga.vram_size_mb != 4 && s->vga.vram_size_mb != 8 &&
+ s->vga.vram_size_mb != 16) {
+ error_setg(errp, "Invalid cirrus_vga ram size '%u'",
+ s->vga.vram_size_mb);
+ return;
+ }
+ /* setup VGA */
+ vga_common_init(&s->vga, OBJECT(dev));
+ cirrus_init_common(s, OBJECT(dev), device_id, 1, pci_address_space(dev),
+ pci_address_space_io(dev));
+ s->vga.con = graphic_console_init(DEVICE(dev), 0, s->vga.hw_ops, &s->vga);
+
+ /* setup PCI */
+
+ memory_region_init(&s->pci_bar, OBJECT(dev), "cirrus-pci-bar0", 0x2000000);
+
+ /* XXX: add byte swapping apertures */
+ memory_region_add_subregion(&s->pci_bar, 0, &s->cirrus_linear_io);
+ memory_region_add_subregion(&s->pci_bar, 0x1000000,
+ &s->cirrus_linear_bitblt_io);
+
+ /* setup memory space */
+ /* memory #0 LFB */
+ /* memory #1 memory-mapped I/O */
+ /* XXX: s->vga.vram_size must be a power of two */
+ pci_register_bar(&d->dev, 0, PCI_BASE_ADDRESS_MEM_PREFETCH, &s->pci_bar);
+ if (device_id == CIRRUS_ID_CLGD5446) {
+ pci_register_bar(&d->dev, 1, 0, &s->cirrus_mmio_io);
+ }
+}
+
+static Property pci_vga_cirrus_properties[] = {
+ DEFINE_PROP_UINT32("vgamem_mb", struct PCICirrusVGAState,
+ cirrus_vga.vga.vram_size_mb, 4),
+ DEFINE_PROP_BOOL("blitter", struct PCICirrusVGAState,
+ cirrus_vga.enable_blitter, true),
+ DEFINE_PROP_BOOL("global-vmstate", struct PCICirrusVGAState,
+ cirrus_vga.vga.global_vmstate, false),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void cirrus_vga_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = pci_cirrus_vga_realize;
+ k->romfile = VGABIOS_CIRRUS_FILENAME;
+ k->vendor_id = PCI_VENDOR_ID_CIRRUS;
+ k->device_id = CIRRUS_ID_CLGD5446;
+ k->class_id = PCI_CLASS_DISPLAY_VGA;
+ set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories);
+ dc->desc = "Cirrus CLGD 54xx VGA";
+ dc->vmsd = &vmstate_pci_cirrus_vga;
+ device_class_set_props(dc, pci_vga_cirrus_properties);
+ dc->hotpluggable = false;
+}
+
+static const TypeInfo cirrus_vga_info = {
+ .name = TYPE_PCI_CIRRUS_VGA,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(PCICirrusVGAState),
+ .class_init = cirrus_vga_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { INTERFACE_CONVENTIONAL_PCI_DEVICE },
+ { },
+ },
+};
+
+static void cirrus_vga_register_types(void)
+{
+ type_register_static(&cirrus_vga_info);
+}
+
+type_init(cirrus_vga_register_types)
diff --git a/hw/display/cirrus_vga_internal.h b/hw/display/cirrus_vga_internal.h
new file mode 100644
index 000000000..a78ebbd92
--- /dev/null
+++ b/hw/display/cirrus_vga_internal.h
@@ -0,0 +1,103 @@
+/*
+ * QEMU Cirrus CLGD 54xx VGA Emulator, ISA bus support
+ *
+ * Copyright (c) 2004 Fabrice Bellard
+ * Copyright (c) 2004 Makoto Suzuki (suzu)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifndef CIRRUS_VGA_INTERNAL_H
+#define CIRRUS_VGA_INTERNAL_H
+
+#include "vga_int.h"
+
+/* IDs */
+#define CIRRUS_ID_CLGD5422 (0x23 << 2)
+#define CIRRUS_ID_CLGD5426 (0x24 << 2)
+#define CIRRUS_ID_CLGD5424 (0x25 << 2)
+#define CIRRUS_ID_CLGD5428 (0x26 << 2)
+#define CIRRUS_ID_CLGD5430 (0x28 << 2)
+#define CIRRUS_ID_CLGD5434 (0x2A << 2)
+#define CIRRUS_ID_CLGD5436 (0x2B << 2)
+#define CIRRUS_ID_CLGD5446 (0x2E << 2)
+
+extern const VMStateDescription vmstate_cirrus_vga;
+
+struct CirrusVGAState;
+typedef void (*cirrus_bitblt_rop_t)(struct CirrusVGAState *s,
+ uint32_t dstaddr, uint32_t srcaddr,
+ int dstpitch, int srcpitch,
+ int bltwidth, int bltheight);
+
+typedef struct CirrusVGAState {
+ VGACommonState vga;
+
+ MemoryRegion cirrus_vga_io;
+ MemoryRegion cirrus_linear_io;
+ MemoryRegion cirrus_linear_bitblt_io;
+ MemoryRegion cirrus_mmio_io;
+ MemoryRegion pci_bar;
+ bool linear_vram; /* vga.vram mapped over cirrus_linear_io */
+ MemoryRegion low_mem_container; /* container for 0xa0000-0xc0000 */
+ MemoryRegion low_mem; /* always mapped, overridden by: */
+ MemoryRegion cirrus_bank[2]; /* aliases at 0xa0000-0xb0000 */
+ uint32_t cirrus_addr_mask;
+ uint32_t linear_mmio_mask;
+ uint8_t cirrus_shadow_gr0;
+ uint8_t cirrus_shadow_gr1;
+ uint8_t cirrus_hidden_dac_lockindex;
+ uint8_t cirrus_hidden_dac_data;
+ uint32_t cirrus_bank_base[2];
+ uint32_t cirrus_bank_limit[2];
+ uint8_t cirrus_hidden_palette[48];
+ bool enable_blitter;
+ int cirrus_blt_pixelwidth;
+ int cirrus_blt_width;
+ int cirrus_blt_height;
+ int cirrus_blt_dstpitch;
+ int cirrus_blt_srcpitch;
+ uint32_t cirrus_blt_fgcol;
+ uint32_t cirrus_blt_bgcol;
+ uint32_t cirrus_blt_dstaddr;
+ uint32_t cirrus_blt_srcaddr;
+ uint8_t cirrus_blt_mode;
+ uint8_t cirrus_blt_modeext;
+ cirrus_bitblt_rop_t cirrus_rop;
+#define CIRRUS_BLTBUFSIZE (2048 * 4) /* one line width */
+ uint8_t cirrus_bltbuf[CIRRUS_BLTBUFSIZE];
+ uint8_t *cirrus_srcptr;
+ uint8_t *cirrus_srcptr_end;
+ uint32_t cirrus_srccounter;
+ /* hwcursor display state */
+ int last_hw_cursor_size;
+ int last_hw_cursor_x;
+ int last_hw_cursor_y;
+ int last_hw_cursor_y_start;
+ int last_hw_cursor_y_end;
+ int real_vram_size; /* XXX: suppress that */
+ int device_id;
+ int bustype;
+} CirrusVGAState;
+
+void cirrus_init_common(CirrusVGAState *s, Object *owner,
+ int device_id, int is_pci,
+ MemoryRegion *system_memory, MemoryRegion *system_io);
+
+#endif
diff --git a/hw/display/cirrus_vga_isa.c b/hw/display/cirrus_vga_isa.c
new file mode 100644
index 000000000..4f6fb1af3
--- /dev/null
+++ b/hw/display/cirrus_vga_isa.c
@@ -0,0 +1,99 @@
+/*
+ * QEMU Cirrus CLGD 54xx VGA Emulator, ISA bus support
+ *
+ * Copyright (c) 2004 Fabrice Bellard
+ * Copyright (c) 2004 Makoto Suzuki (suzu)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "qemu/module.h"
+#include "hw/loader.h"
+#include "hw/qdev-properties.h"
+#include "hw/isa/isa.h"
+#include "cirrus_vga_internal.h"
+#include "qom/object.h"
+
+#define TYPE_ISA_CIRRUS_VGA "isa-cirrus-vga"
+OBJECT_DECLARE_SIMPLE_TYPE(ISACirrusVGAState, ISA_CIRRUS_VGA)
+
+struct ISACirrusVGAState {
+ ISADevice parent_obj;
+
+ CirrusVGAState cirrus_vga;
+};
+
+static void isa_cirrus_vga_realizefn(DeviceState *dev, Error **errp)
+{
+ ISADevice *isadev = ISA_DEVICE(dev);
+ ISACirrusVGAState *d = ISA_CIRRUS_VGA(dev);
+ VGACommonState *s = &d->cirrus_vga.vga;
+
+ /* follow real hardware, cirrus card emulated has 4 MB video memory.
+ Also accept 8 MB/16 MB for backward compatibility. */
+ if (s->vram_size_mb != 4 && s->vram_size_mb != 8 &&
+ s->vram_size_mb != 16) {
+ error_setg(errp, "Invalid cirrus_vga ram size '%u'",
+ s->vram_size_mb);
+ return;
+ }
+ s->global_vmstate = true;
+ vga_common_init(s, OBJECT(dev));
+ cirrus_init_common(&d->cirrus_vga, OBJECT(dev), CIRRUS_ID_CLGD5430, 0,
+ isa_address_space(isadev),
+ isa_address_space_io(isadev));
+ s->con = graphic_console_init(dev, 0, s->hw_ops, s);
+ rom_add_vga(VGABIOS_CIRRUS_FILENAME);
+ /* XXX ISA-LFB support */
+ /* FIXME not qdev yet */
+}
+
+static Property isa_cirrus_vga_properties[] = {
+ DEFINE_PROP_UINT32("vgamem_mb", struct ISACirrusVGAState,
+ cirrus_vga.vga.vram_size_mb, 4),
+ DEFINE_PROP_BOOL("blitter", struct ISACirrusVGAState,
+ cirrus_vga.enable_blitter, true),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void isa_cirrus_vga_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->vmsd = &vmstate_cirrus_vga;
+ dc->realize = isa_cirrus_vga_realizefn;
+ device_class_set_props(dc, isa_cirrus_vga_properties);
+ set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories);
+}
+
+static const TypeInfo isa_cirrus_vga_info = {
+ .name = TYPE_ISA_CIRRUS_VGA,
+ .parent = TYPE_ISA_DEVICE,
+ .instance_size = sizeof(ISACirrusVGAState),
+ .class_init = isa_cirrus_vga_class_init,
+};
+
+static void cirrus_vga_isa_register_types(void)
+{
+ type_register_static(&isa_cirrus_vga_info);
+}
+
+type_init(cirrus_vga_isa_register_types)
diff --git a/hw/display/cirrus_vga_rop.h b/hw/display/cirrus_vga_rop.h
new file mode 100644
index 000000000..0841b9efa
--- /dev/null
+++ b/hw/display/cirrus_vga_rop.h
@@ -0,0 +1,246 @@
+/*
+ * QEMU Cirrus CLGD 54xx VGA Emulator.
+ *
+ * Copyright (c) 2004 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+static inline void glue(rop_8_, ROP_NAME)(CirrusVGAState *s,
+ uint32_t dstaddr, uint8_t src)
+{
+ uint8_t *dst = &s->vga.vram_ptr[dstaddr & s->cirrus_addr_mask];
+ *dst = ROP_FN(*dst, src);
+}
+
+static inline void glue(rop_tr_8_, ROP_NAME)(CirrusVGAState *s,
+ uint32_t dstaddr, uint8_t src,
+ uint8_t transp)
+{
+ uint8_t *dst = &s->vga.vram_ptr[dstaddr & s->cirrus_addr_mask];
+ uint8_t pixel = ROP_FN(*dst, src);
+ if (pixel != transp) {
+ *dst = pixel;
+ }
+}
+
+static inline void glue(rop_16_, ROP_NAME)(CirrusVGAState *s,
+ uint32_t dstaddr, uint16_t src)
+{
+ uint16_t *dst = (uint16_t *)
+ (&s->vga.vram_ptr[dstaddr & s->cirrus_addr_mask & ~1]);
+ *dst = ROP_FN(*dst, src);
+}
+
+static inline void glue(rop_tr_16_, ROP_NAME)(CirrusVGAState *s,
+ uint32_t dstaddr, uint16_t src,
+ uint16_t transp)
+{
+ uint16_t *dst = (uint16_t *)
+ (&s->vga.vram_ptr[dstaddr & s->cirrus_addr_mask & ~1]);
+ uint16_t pixel = ROP_FN(*dst, src);
+ if (pixel != transp) {
+ *dst = pixel;
+ }
+}
+
+static inline void glue(rop_32_, ROP_NAME)(CirrusVGAState *s,
+ uint32_t dstaddr, uint32_t src)
+{
+ uint32_t *dst = (uint32_t *)
+ (&s->vga.vram_ptr[dstaddr & s->cirrus_addr_mask & ~3]);
+ *dst = ROP_FN(*dst, src);
+}
+
+#define ROP_OP(st, d, s) glue(rop_8_, ROP_NAME)(st, d, s)
+#define ROP_OP_TR(st, d, s, t) glue(rop_tr_8_, ROP_NAME)(st, d, s, t)
+#define ROP_OP_16(st, d, s) glue(rop_16_, ROP_NAME)(st, d, s)
+#define ROP_OP_TR_16(st, d, s, t) glue(rop_tr_16_, ROP_NAME)(st, d, s, t)
+#define ROP_OP_32(st, d, s) glue(rop_32_, ROP_NAME)(st, d, s)
+#undef ROP_FN
+
+static void
+glue(cirrus_bitblt_rop_fwd_, ROP_NAME)(CirrusVGAState *s,
+ uint32_t dstaddr,
+ uint32_t srcaddr,
+ int dstpitch, int srcpitch,
+ int bltwidth, int bltheight)
+{
+ int x,y;
+ dstpitch -= bltwidth;
+ srcpitch -= bltwidth;
+
+ if (bltheight > 1 && (dstpitch < 0 || srcpitch < 0)) {
+ return;
+ }
+
+ for (y = 0; y < bltheight; y++) {
+ for (x = 0; x < bltwidth; x++) {
+ ROP_OP(s, dstaddr, cirrus_src(s, srcaddr));
+ dstaddr++;
+ srcaddr++;
+ }
+ dstaddr += dstpitch;
+ srcaddr += srcpitch;
+ }
+}
+
+static void
+glue(cirrus_bitblt_rop_bkwd_, ROP_NAME)(CirrusVGAState *s,
+ uint32_t dstaddr,
+ uint32_t srcaddr,
+ int dstpitch, int srcpitch,
+ int bltwidth, int bltheight)
+{
+ int x,y;
+ dstpitch += bltwidth;
+ srcpitch += bltwidth;
+ for (y = 0; y < bltheight; y++) {
+ for (x = 0; x < bltwidth; x++) {
+ ROP_OP(s, dstaddr, cirrus_src(s, srcaddr));
+ dstaddr--;
+ srcaddr--;
+ }
+ dstaddr += dstpitch;
+ srcaddr += srcpitch;
+ }
+}
+
+static void
+glue(glue(cirrus_bitblt_rop_fwd_transp_, ROP_NAME),_8)(CirrusVGAState *s,
+ uint32_t dstaddr,
+ uint32_t srcaddr,
+ int dstpitch,
+ int srcpitch,
+ int bltwidth,
+ int bltheight)
+{
+ int x,y;
+ uint8_t transp = s->vga.gr[0x34];
+ dstpitch -= bltwidth;
+ srcpitch -= bltwidth;
+
+ if (bltheight > 1 && (dstpitch < 0 || srcpitch < 0)) {
+ return;
+ }
+
+ for (y = 0; y < bltheight; y++) {
+ for (x = 0; x < bltwidth; x++) {
+ ROP_OP_TR(s, dstaddr, cirrus_src(s, srcaddr), transp);
+ dstaddr++;
+ srcaddr++;
+ }
+ dstaddr += dstpitch;
+ srcaddr += srcpitch;
+ }
+}
+
+static void
+glue(glue(cirrus_bitblt_rop_bkwd_transp_, ROP_NAME),_8)(CirrusVGAState *s,
+ uint32_t dstaddr,
+ uint32_t srcaddr,
+ int dstpitch,
+ int srcpitch,
+ int bltwidth,
+ int bltheight)
+{
+ int x,y;
+ uint8_t transp = s->vga.gr[0x34];
+ dstpitch += bltwidth;
+ srcpitch += bltwidth;
+ for (y = 0; y < bltheight; y++) {
+ for (x = 0; x < bltwidth; x++) {
+ ROP_OP_TR(s, dstaddr, cirrus_src(s, srcaddr), transp);
+ dstaddr--;
+ srcaddr--;
+ }
+ dstaddr += dstpitch;
+ srcaddr += srcpitch;
+ }
+}
+
+static void
+glue(glue(cirrus_bitblt_rop_fwd_transp_, ROP_NAME),_16)(CirrusVGAState *s,
+ uint32_t dstaddr,
+ uint32_t srcaddr,
+ int dstpitch,
+ int srcpitch,
+ int bltwidth,
+ int bltheight)
+{
+ int x,y;
+ uint16_t transp = s->vga.gr[0x34] | (uint16_t)s->vga.gr[0x35] << 8;
+ dstpitch -= bltwidth;
+ srcpitch -= bltwidth;
+
+ if (bltheight > 1 && (dstpitch < 0 || srcpitch < 0)) {
+ return;
+ }
+
+ for (y = 0; y < bltheight; y++) {
+ for (x = 0; x < bltwidth; x+=2) {
+ ROP_OP_TR_16(s, dstaddr, cirrus_src16(s, srcaddr), transp);
+ dstaddr += 2;
+ srcaddr += 2;
+ }
+ dstaddr += dstpitch;
+ srcaddr += srcpitch;
+ }
+}
+
+static void
+glue(glue(cirrus_bitblt_rop_bkwd_transp_, ROP_NAME),_16)(CirrusVGAState *s,
+ uint32_t dstaddr,
+ uint32_t srcaddr,
+ int dstpitch,
+ int srcpitch,
+ int bltwidth,
+ int bltheight)
+{
+ int x,y;
+ uint16_t transp = s->vga.gr[0x34] | (uint16_t)s->vga.gr[0x35] << 8;
+ dstpitch += bltwidth;
+ srcpitch += bltwidth;
+ for (y = 0; y < bltheight; y++) {
+ for (x = 0; x < bltwidth; x+=2) {
+ ROP_OP_TR_16(s, dstaddr - 1, cirrus_src16(s, srcaddr - 1), transp);
+ dstaddr -= 2;
+ srcaddr -= 2;
+ }
+ dstaddr += dstpitch;
+ srcaddr += srcpitch;
+ }
+}
+
+#define DEPTH 8
+#include "cirrus_vga_rop2.h"
+
+#define DEPTH 16
+#include "cirrus_vga_rop2.h"
+
+#define DEPTH 24
+#include "cirrus_vga_rop2.h"
+
+#define DEPTH 32
+#include "cirrus_vga_rop2.h"
+
+#undef ROP_NAME
+#undef ROP_OP
+#undef ROP_OP_16
+#undef ROP_OP_32
diff --git a/hw/display/cirrus_vga_rop2.h b/hw/display/cirrus_vga_rop2.h
new file mode 100644
index 000000000..b208b7348
--- /dev/null
+++ b/hw/display/cirrus_vga_rop2.h
@@ -0,0 +1,284 @@
+/*
+ * QEMU Cirrus CLGD 54xx VGA Emulator.
+ *
+ * Copyright (c) 2004 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#if DEPTH == 8
+#define PUTPIXEL(s, a, c) ROP_OP(s, a, c)
+#elif DEPTH == 16
+#define PUTPIXEL(s, a, c) ROP_OP_16(s, a, c)
+#elif DEPTH == 24
+#define PUTPIXEL(s, a, c) do { \
+ ROP_OP(s, a, c); \
+ ROP_OP(s, a + 1, (c >> 8)); \
+ ROP_OP(s, a + 2, (c >> 16)); \
+ } while (0)
+#elif DEPTH == 32
+#define PUTPIXEL(s, a, c) ROP_OP_32(s, a, c)
+#else
+#error unsupported DEPTH
+#endif
+
+static void
+glue(glue(glue(cirrus_patternfill_, ROP_NAME), _),DEPTH)
+ (CirrusVGAState *s, uint32_t dstaddr,
+ uint32_t srcaddr,
+ int dstpitch, int srcpitch,
+ int bltwidth, int bltheight)
+{
+ uint32_t addr;
+ int x, y, pattern_y, pattern_pitch, pattern_x;
+ unsigned int col;
+ uint32_t src1addr;
+#if DEPTH == 24
+ int skipleft = s->vga.gr[0x2f] & 0x1f;
+#else
+ int skipleft = (s->vga.gr[0x2f] & 0x07) * (DEPTH / 8);
+#endif
+
+#if DEPTH == 8
+ pattern_pitch = 8;
+#elif DEPTH == 16
+ pattern_pitch = 16;
+#else
+ pattern_pitch = 32;
+#endif
+ pattern_y = s->cirrus_blt_srcaddr & 7;
+ for(y = 0; y < bltheight; y++) {
+ pattern_x = skipleft;
+ addr = dstaddr + skipleft;
+ src1addr = srcaddr + pattern_y * pattern_pitch;
+ for (x = skipleft; x < bltwidth; x += (DEPTH / 8)) {
+#if DEPTH == 8
+ col = cirrus_src(s, src1addr + pattern_x);
+ pattern_x = (pattern_x + 1) & 7;
+#elif DEPTH == 16
+ col = cirrus_src16(s, src1addr + pattern_x);
+ pattern_x = (pattern_x + 2) & 15;
+#elif DEPTH == 24
+ {
+ uint32_t src2addr = src1addr + pattern_x * 3;
+ col = cirrus_src(s, src2addr) |
+ (cirrus_src(s, src2addr + 1) << 8) |
+ (cirrus_src(s, src2addr + 2) << 16);
+ pattern_x = (pattern_x + 1) & 7;
+ }
+#else
+ col = cirrus_src32(s, src1addr + pattern_x);
+ pattern_x = (pattern_x + 4) & 31;
+#endif
+ PUTPIXEL(s, addr, col);
+ addr += (DEPTH / 8);
+ }
+ pattern_y = (pattern_y + 1) & 7;
+ dstaddr += dstpitch;
+ }
+}
+
+/* NOTE: srcpitch is ignored */
+static void
+glue(glue(glue(cirrus_colorexpand_transp_, ROP_NAME), _),DEPTH)
+ (CirrusVGAState *s, uint32_t dstaddr,
+ uint32_t srcaddr,
+ int dstpitch, int srcpitch,
+ int bltwidth, int bltheight)
+{
+ uint32_t addr;
+ int x, y;
+ unsigned bits, bits_xor;
+ unsigned int col;
+ unsigned bitmask;
+ unsigned index;
+#if DEPTH == 24
+ int dstskipleft = s->vga.gr[0x2f] & 0x1f;
+ int srcskipleft = dstskipleft / 3;
+#else
+ int srcskipleft = s->vga.gr[0x2f] & 0x07;
+ int dstskipleft = srcskipleft * (DEPTH / 8);
+#endif
+
+ if (s->cirrus_blt_modeext & CIRRUS_BLTMODEEXT_COLOREXPINV) {
+ bits_xor = 0xff;
+ col = s->cirrus_blt_bgcol;
+ } else {
+ bits_xor = 0x00;
+ col = s->cirrus_blt_fgcol;
+ }
+
+ for(y = 0; y < bltheight; y++) {
+ bitmask = 0x80 >> srcskipleft;
+ bits = cirrus_src(s, srcaddr++) ^ bits_xor;
+ addr = dstaddr + dstskipleft;
+ for (x = dstskipleft; x < bltwidth; x += (DEPTH / 8)) {
+ if ((bitmask & 0xff) == 0) {
+ bitmask = 0x80;
+ bits = cirrus_src(s, srcaddr++) ^ bits_xor;
+ }
+ index = (bits & bitmask);
+ if (index) {
+ PUTPIXEL(s, addr, col);
+ }
+ addr += (DEPTH / 8);
+ bitmask >>= 1;
+ }
+ dstaddr += dstpitch;
+ }
+}
+
+static void
+glue(glue(glue(cirrus_colorexpand_, ROP_NAME), _),DEPTH)
+ (CirrusVGAState *s, uint32_t dstaddr,
+ uint32_t srcaddr,
+ int dstpitch, int srcpitch,
+ int bltwidth, int bltheight)
+{
+ uint32_t colors[2];
+ uint32_t addr;
+ int x, y;
+ unsigned bits;
+ unsigned int col;
+ unsigned bitmask;
+ int srcskipleft = s->vga.gr[0x2f] & 0x07;
+ int dstskipleft = srcskipleft * (DEPTH / 8);
+
+ colors[0] = s->cirrus_blt_bgcol;
+ colors[1] = s->cirrus_blt_fgcol;
+ for(y = 0; y < bltheight; y++) {
+ bitmask = 0x80 >> srcskipleft;
+ bits = cirrus_src(s, srcaddr++);
+ addr = dstaddr + dstskipleft;
+ for (x = dstskipleft; x < bltwidth; x += (DEPTH / 8)) {
+ if ((bitmask & 0xff) == 0) {
+ bitmask = 0x80;
+ bits = cirrus_src(s, srcaddr++);
+ }
+ col = colors[!!(bits & bitmask)];
+ PUTPIXEL(s, addr, col);
+ addr += (DEPTH / 8);
+ bitmask >>= 1;
+ }
+ dstaddr += dstpitch;
+ }
+}
+
+static void
+glue(glue(glue(cirrus_colorexpand_pattern_transp_, ROP_NAME), _),DEPTH)
+ (CirrusVGAState *s, uint32_t dstaddr,
+ uint32_t srcaddr,
+ int dstpitch, int srcpitch,
+ int bltwidth, int bltheight)
+{
+ uint32_t addr;
+ int x, y, bitpos, pattern_y;
+ unsigned int bits, bits_xor;
+ unsigned int col;
+#if DEPTH == 24
+ int dstskipleft = s->vga.gr[0x2f] & 0x1f;
+ int srcskipleft = dstskipleft / 3;
+#else
+ int srcskipleft = s->vga.gr[0x2f] & 0x07;
+ int dstskipleft = srcskipleft * (DEPTH / 8);
+#endif
+
+ if (s->cirrus_blt_modeext & CIRRUS_BLTMODEEXT_COLOREXPINV) {
+ bits_xor = 0xff;
+ col = s->cirrus_blt_bgcol;
+ } else {
+ bits_xor = 0x00;
+ col = s->cirrus_blt_fgcol;
+ }
+ pattern_y = s->cirrus_blt_srcaddr & 7;
+
+ for(y = 0; y < bltheight; y++) {
+ bits = cirrus_src(s, srcaddr + pattern_y) ^ bits_xor;
+ bitpos = 7 - srcskipleft;
+ addr = dstaddr + dstskipleft;
+ for (x = dstskipleft; x < bltwidth; x += (DEPTH / 8)) {
+ if ((bits >> bitpos) & 1) {
+ PUTPIXEL(s, addr, col);
+ }
+ addr += (DEPTH / 8);
+ bitpos = (bitpos - 1) & 7;
+ }
+ pattern_y = (pattern_y + 1) & 7;
+ dstaddr += dstpitch;
+ }
+}
+
+static void
+glue(glue(glue(cirrus_colorexpand_pattern_, ROP_NAME), _),DEPTH)
+ (CirrusVGAState *s, uint32_t dstaddr,
+ uint32_t srcaddr,
+ int dstpitch, int srcpitch,
+ int bltwidth, int bltheight)
+{
+ uint32_t colors[2];
+ uint32_t addr;
+ int x, y, bitpos, pattern_y;
+ unsigned int bits;
+ unsigned int col;
+ int srcskipleft = s->vga.gr[0x2f] & 0x07;
+ int dstskipleft = srcskipleft * (DEPTH / 8);
+
+ colors[0] = s->cirrus_blt_bgcol;
+ colors[1] = s->cirrus_blt_fgcol;
+ pattern_y = s->cirrus_blt_srcaddr & 7;
+
+ for(y = 0; y < bltheight; y++) {
+ bits = cirrus_src(s, srcaddr + pattern_y);
+ bitpos = 7 - srcskipleft;
+ addr = dstaddr + dstskipleft;
+ for (x = dstskipleft; x < bltwidth; x += (DEPTH / 8)) {
+ col = colors[(bits >> bitpos) & 1];
+ PUTPIXEL(s, addr, col);
+ addr += (DEPTH / 8);
+ bitpos = (bitpos - 1) & 7;
+ }
+ pattern_y = (pattern_y + 1) & 7;
+ dstaddr += dstpitch;
+ }
+}
+
+static void
+glue(glue(glue(cirrus_fill_, ROP_NAME), _),DEPTH)
+ (CirrusVGAState *s,
+ uint32_t dstaddr, int dst_pitch,
+ int width, int height)
+{
+ uint32_t addr;
+ uint32_t col;
+ int x, y;
+
+ col = s->cirrus_blt_fgcol;
+
+ for(y = 0; y < height; y++) {
+ addr = dstaddr;
+ for(x = 0; x < width; x += (DEPTH / 8)) {
+ PUTPIXEL(s, addr, col);
+ addr += (DEPTH / 8);
+ }
+ dstaddr += dst_pitch;
+ }
+}
+
+#undef DEPTH
+#undef PUTPIXEL
diff --git a/hw/display/dpcd.c b/hw/display/dpcd.c
new file mode 100644
index 000000000..64463654a
--- /dev/null
+++ b/hw/display/dpcd.c
@@ -0,0 +1,165 @@
+/*
+ * Xilinx Display Port Control Data
+ *
+ * Copyright (C) 2015 : GreenSocs Ltd
+ * http://www.greensocs.com/ , email: info@greensocs.com
+ *
+ * Developed by :
+ * Frederic Konrad <fred.konrad@greensocs.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option)any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/*
+ * This is a simple AUX slave which emulates a connected screen.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "hw/misc/auxbus.h"
+#include "migration/vmstate.h"
+#include "hw/display/dpcd.h"
+#include "trace.h"
+
+#define DPCD_READABLE_AREA 0x600
+
+struct DPCDState {
+ /*< private >*/
+ AUXSlave parent_obj;
+
+ /*< public >*/
+ /*
+ * The DCPD is 0x7FFFF length but read as 0 after offset 0x5FF.
+ */
+ uint8_t dpcd_info[DPCD_READABLE_AREA];
+
+ MemoryRegion iomem;
+};
+
+static uint64_t dpcd_read(void *opaque, hwaddr offset, unsigned size)
+{
+ uint8_t ret;
+ DPCDState *e = DPCD(opaque);
+
+ if (offset < DPCD_READABLE_AREA) {
+ ret = e->dpcd_info[offset];
+ } else {
+ qemu_log_mask(LOG_GUEST_ERROR, "dpcd: Bad offset 0x%" HWADDR_PRIX "\n",
+ offset);
+ ret = 0;
+ }
+ trace_dpcd_read(offset, ret);
+
+ return ret;
+}
+
+static void dpcd_write(void *opaque, hwaddr offset, uint64_t value,
+ unsigned size)
+{
+ DPCDState *e = DPCD(opaque);
+
+ trace_dpcd_write(offset, value);
+ if (offset < DPCD_READABLE_AREA) {
+ e->dpcd_info[offset] = value;
+ } else {
+ qemu_log_mask(LOG_GUEST_ERROR, "dpcd: Bad offset 0x%" HWADDR_PRIX "\n",
+ offset);
+ }
+}
+
+static const MemoryRegionOps aux_ops = {
+ .read = dpcd_read,
+ .write = dpcd_write,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static void dpcd_reset(DeviceState *dev)
+{
+ DPCDState *s = DPCD(dev);
+
+ memset(&(s->dpcd_info), 0, sizeof(s->dpcd_info));
+
+ s->dpcd_info[DPCD_REVISION] = DPCD_REV_1_0;
+ s->dpcd_info[DPCD_MAX_LINK_RATE] = DPCD_5_4GBPS;
+ s->dpcd_info[DPCD_MAX_LANE_COUNT] = DPCD_FOUR_LANES;
+ s->dpcd_info[DPCD_RECEIVE_PORT0_CAP_0] = DPCD_EDID_PRESENT;
+ /* buffer size */
+ s->dpcd_info[DPCD_RECEIVE_PORT0_CAP_1] = 0xFF;
+
+ s->dpcd_info[DPCD_LANE0_1_STATUS] = DPCD_LANE0_CR_DONE
+ | DPCD_LANE0_CHANNEL_EQ_DONE
+ | DPCD_LANE0_SYMBOL_LOCKED
+ | DPCD_LANE1_CR_DONE
+ | DPCD_LANE1_CHANNEL_EQ_DONE
+ | DPCD_LANE1_SYMBOL_LOCKED;
+ s->dpcd_info[DPCD_LANE2_3_STATUS] = DPCD_LANE2_CR_DONE
+ | DPCD_LANE2_CHANNEL_EQ_DONE
+ | DPCD_LANE2_SYMBOL_LOCKED
+ | DPCD_LANE3_CR_DONE
+ | DPCD_LANE3_CHANNEL_EQ_DONE
+ | DPCD_LANE3_SYMBOL_LOCKED;
+
+ s->dpcd_info[DPCD_LANE_ALIGN_STATUS_UPDATED] = DPCD_INTERLANE_ALIGN_DONE;
+ s->dpcd_info[DPCD_SINK_STATUS] = DPCD_RECEIVE_PORT_0_STATUS;
+}
+
+static void dpcd_init(Object *obj)
+{
+ DPCDState *s = DPCD(obj);
+
+ memory_region_init_io(&s->iomem, obj, &aux_ops, s, TYPE_DPCD, 0x80000);
+ aux_init_mmio(AUX_SLAVE(obj), &s->iomem);
+}
+
+static const VMStateDescription vmstate_dpcd = {
+ .name = TYPE_DPCD,
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8_ARRAY_V(dpcd_info, DPCDState, DPCD_READABLE_AREA, 0),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void dpcd_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ dc->reset = dpcd_reset;
+ dc->vmsd = &vmstate_dpcd;
+}
+
+static const TypeInfo dpcd_info = {
+ .name = TYPE_DPCD,
+ .parent = TYPE_AUX_SLAVE,
+ .instance_size = sizeof(DPCDState),
+ .class_init = dpcd_class_init,
+ .instance_init = dpcd_init,
+};
+
+static void dpcd_register_types(void)
+{
+ type_register_static(&dpcd_info);
+}
+
+type_init(dpcd_register_types)
diff --git a/hw/display/edid-generate.c b/hw/display/edid-generate.c
new file mode 100644
index 000000000..f2b874d5e
--- /dev/null
+++ b/hw/display/edid-generate.c
@@ -0,0 +1,563 @@
+/*
+ * QEMU EDID generator.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+#include "qemu/osdep.h"
+#include "qemu/bswap.h"
+#include "hw/display/edid.h"
+
+static const struct edid_mode {
+ uint32_t xres;
+ uint32_t yres;
+ uint32_t byte;
+ uint32_t xtra3;
+ uint32_t bit;
+ uint32_t dta;
+} modes[] = {
+ /* dea/dta extension timings (all @ 50 Hz) */
+ { .xres = 5120, .yres = 2160, .dta = 125 },
+ { .xres = 4096, .yres = 2160, .dta = 101 },
+ { .xres = 3840, .yres = 2160, .dta = 96 },
+ { .xres = 2560, .yres = 1080, .dta = 89 },
+ { .xres = 2048, .yres = 1152 },
+ { .xres = 1920, .yres = 1080, .dta = 31 },
+
+ /* additional standard timings 3 (all @ 60Hz) */
+ { .xres = 1920, .yres = 1200, .xtra3 = 10, .bit = 0 },
+ { .xres = 1600, .yres = 1200, .xtra3 = 9, .bit = 2 },
+ { .xres = 1680, .yres = 1050, .xtra3 = 9, .bit = 5 },
+ { .xres = 1440, .yres = 900, .xtra3 = 8, .bit = 5 },
+ { .xres = 1280, .yres = 1024, .xtra3 = 7, .bit = 1 },
+ { .xres = 1280, .yres = 960, .xtra3 = 7, .bit = 3 },
+ { .xres = 1280, .yres = 768, .xtra3 = 7, .bit = 6 },
+
+ { .xres = 1920, .yres = 1440, .xtra3 = 11, .bit = 5 },
+ { .xres = 1856, .yres = 1392, .xtra3 = 10, .bit = 3 },
+ { .xres = 1792, .yres = 1344, .xtra3 = 10, .bit = 5 },
+ { .xres = 1440, .yres = 1050, .xtra3 = 8, .bit = 1 },
+ { .xres = 1360, .yres = 768, .xtra3 = 8, .bit = 7 },
+
+ /* established timings (all @ 60Hz) */
+ { .xres = 1024, .yres = 768, .byte = 36, .bit = 3 },
+ { .xres = 800, .yres = 600, .byte = 35, .bit = 0 },
+ { .xres = 640, .yres = 480, .byte = 35, .bit = 5 },
+};
+
+typedef struct Timings {
+ uint32_t xfront;
+ uint32_t xsync;
+ uint32_t xblank;
+
+ uint32_t yfront;
+ uint32_t ysync;
+ uint32_t yblank;
+
+ uint64_t clock;
+} Timings;
+
+static void generate_timings(Timings *timings, uint32_t refresh_rate,
+ uint32_t xres, uint32_t yres)
+{
+ /* pull some realistic looking timings out of thin air */
+ timings->xfront = xres * 25 / 100;
+ timings->xsync = xres * 3 / 100;
+ timings->xblank = xres * 35 / 100;
+
+ timings->yfront = yres * 5 / 1000;
+ timings->ysync = yres * 5 / 1000;
+ timings->yblank = yres * 35 / 1000;
+
+ timings->clock = ((uint64_t)refresh_rate *
+ (xres + timings->xblank) *
+ (yres + timings->yblank)) / 10000000;
+}
+
+static void edid_ext_dta(uint8_t *dta)
+{
+ dta[0] = 0x02;
+ dta[1] = 0x03;
+ dta[2] = 0x05;
+ dta[3] = 0x00;
+
+ /* video data block */
+ dta[4] = 0x40;
+}
+
+static void edid_ext_dta_mode(uint8_t *dta, uint8_t nr)
+{
+ dta[dta[2]] = nr;
+ dta[2]++;
+ dta[4]++;
+}
+
+static int edid_std_mode(uint8_t *mode, uint32_t xres, uint32_t yres)
+{
+ uint32_t aspect;
+
+ if (xres == 0 || yres == 0) {
+ mode[0] = 0x01;
+ mode[1] = 0x01;
+ return 0;
+
+ } else if (xres * 10 == yres * 16) {
+ aspect = 0;
+ } else if (xres * 3 == yres * 4) {
+ aspect = 1;
+ } else if (xres * 4 == yres * 5) {
+ aspect = 2;
+ } else if (xres * 9 == yres * 16) {
+ aspect = 3;
+ } else {
+ return -1;
+ }
+
+ if ((xres / 8) - 31 > 255) {
+ return -1;
+ }
+
+ mode[0] = (xres / 8) - 31;
+ mode[1] = ((aspect << 6) | (60 - 60));
+ return 0;
+}
+
+static void edid_fill_modes(uint8_t *edid, uint8_t *xtra3, uint8_t *dta,
+ uint32_t maxx, uint32_t maxy)
+{
+ const struct edid_mode *mode;
+ int std = 38;
+ int rc, i;
+
+ for (i = 0; i < ARRAY_SIZE(modes); i++) {
+ mode = modes + i;
+
+ if ((maxx && mode->xres > maxx) ||
+ (maxy && mode->yres > maxy)) {
+ continue;
+ }
+
+ if (mode->byte) {
+ edid[mode->byte] |= (1 << mode->bit);
+ } else if (std < 54) {
+ rc = edid_std_mode(edid + std, mode->xres, mode->yres);
+ if (rc == 0) {
+ std += 2;
+ }
+ } else if (mode->xtra3 && xtra3) {
+ xtra3[mode->xtra3] |= (1 << mode->bit);
+ }
+
+ if (dta && mode->dta) {
+ edid_ext_dta_mode(dta, mode->dta);
+ }
+ }
+
+ while (std < 54) {
+ edid_std_mode(edid + std, 0, 0);
+ std += 2;
+ }
+}
+
+static void edid_checksum(uint8_t *edid, size_t len)
+{
+ uint32_t sum = 0;
+ int i;
+
+ for (i = 0; i < len; i++) {
+ sum += edid[i];
+ }
+ sum &= 0xff;
+ if (sum) {
+ edid[len] = 0x100 - sum;
+ }
+}
+
+static uint8_t *edid_desc_next(uint8_t *edid, uint8_t *dta, uint8_t *desc)
+{
+ if (desc == NULL) {
+ return NULL;
+ }
+ if (desc + 18 + 18 < edid + 127) {
+ return desc + 18;
+ }
+ if (dta) {
+ if (desc < edid + 127) {
+ return dta + dta[2];
+ }
+ if (desc + 18 + 18 < dta + 127) {
+ return desc + 18;
+ }
+ }
+ return NULL;
+}
+
+static void edid_desc_type(uint8_t *desc, uint8_t type)
+{
+ desc[0] = 0;
+ desc[1] = 0;
+ desc[2] = 0;
+ desc[3] = type;
+ desc[4] = 0;
+}
+
+static void edid_desc_text(uint8_t *desc, uint8_t type,
+ const char *text)
+{
+ size_t len;
+
+ edid_desc_type(desc, type);
+ memset(desc + 5, ' ', 13);
+
+ len = strlen(text);
+ if (len > 12) {
+ len = 12;
+ }
+ memcpy(desc + 5, text, len);
+ desc[5 + len] = '\n';
+}
+
+static void edid_desc_ranges(uint8_t *desc)
+{
+ edid_desc_type(desc, 0xfd);
+
+ /* vertical (50 -> 125 Hz) */
+ desc[5] = 50;
+ desc[6] = 125;
+
+ /* horizontal (30 -> 160 kHz) */
+ desc[7] = 30;
+ desc[8] = 160;
+
+ /* max dot clock (2550 MHz) */
+ desc[9] = 2550 / 10;
+
+ /* no extended timing information */
+ desc[10] = 0x01;
+
+ /* padding */
+ desc[11] = '\n';
+ memset(desc + 12, ' ', 6);
+}
+
+/* additional standard timings 3 */
+static void edid_desc_xtra3_std(uint8_t *desc)
+{
+ edid_desc_type(desc, 0xf7);
+ desc[5] = 10;
+}
+
+static void edid_desc_dummy(uint8_t *desc)
+{
+ edid_desc_type(desc, 0x10);
+}
+
+static void edid_desc_timing(uint8_t *desc, uint32_t refresh_rate,
+ uint32_t xres, uint32_t yres,
+ uint32_t xmm, uint32_t ymm)
+{
+ Timings timings;
+ generate_timings(&timings, refresh_rate, xres, yres);
+ stl_le_p(desc, timings.clock);
+
+ desc[2] = xres & 0xff;
+ desc[3] = timings.xblank & 0xff;
+ desc[4] = (((xres & 0xf00) >> 4) |
+ ((timings.xblank & 0xf00) >> 8));
+
+ desc[5] = yres & 0xff;
+ desc[6] = timings.yblank & 0xff;
+ desc[7] = (((yres & 0xf00) >> 4) |
+ ((timings.yblank & 0xf00) >> 8));
+
+ desc[8] = timings.xfront & 0xff;
+ desc[9] = timings.xsync & 0xff;
+
+ desc[10] = (((timings.yfront & 0x00f) << 4) |
+ ((timings.ysync & 0x00f) << 0));
+ desc[11] = (((timings.xfront & 0x300) >> 2) |
+ ((timings.xsync & 0x300) >> 4) |
+ ((timings.yfront & 0x030) >> 2) |
+ ((timings.ysync & 0x030) >> 4));
+
+ desc[12] = xmm & 0xff;
+ desc[13] = ymm & 0xff;
+ desc[14] = (((xmm & 0xf00) >> 4) |
+ ((ymm & 0xf00) >> 8));
+
+ desc[17] = 0x18;
+}
+
+static uint32_t edid_to_10bit(float value)
+{
+ return (uint32_t)(value * 1024 + 0.5);
+}
+
+static void edid_colorspace(uint8_t *edid,
+ float rx, float ry,
+ float gx, float gy,
+ float bx, float by,
+ float wx, float wy)
+{
+ uint32_t red_x = edid_to_10bit(rx);
+ uint32_t red_y = edid_to_10bit(ry);
+ uint32_t green_x = edid_to_10bit(gx);
+ uint32_t green_y = edid_to_10bit(gy);
+ uint32_t blue_x = edid_to_10bit(bx);
+ uint32_t blue_y = edid_to_10bit(by);
+ uint32_t white_x = edid_to_10bit(wx);
+ uint32_t white_y = edid_to_10bit(wy);
+
+ edid[25] = (((red_x & 0x03) << 6) |
+ ((red_y & 0x03) << 4) |
+ ((green_x & 0x03) << 2) |
+ ((green_y & 0x03) << 0));
+ edid[26] = (((blue_x & 0x03) << 6) |
+ ((blue_y & 0x03) << 4) |
+ ((white_x & 0x03) << 2) |
+ ((white_y & 0x03) << 0));
+ edid[27] = red_x >> 2;
+ edid[28] = red_y >> 2;
+ edid[29] = green_x >> 2;
+ edid[30] = green_y >> 2;
+ edid[31] = blue_x >> 2;
+ edid[32] = blue_y >> 2;
+ edid[33] = white_x >> 2;
+ edid[34] = white_y >> 2;
+}
+
+static uint32_t qemu_edid_dpi_from_mm(uint32_t mm, uint32_t res)
+{
+ return res * 254 / 10 / mm;
+}
+
+uint32_t qemu_edid_dpi_to_mm(uint32_t dpi, uint32_t res)
+{
+ return res * 254 / 10 / dpi;
+}
+
+static void init_displayid(uint8_t *did)
+{
+ did[0] = 0x70; /* display id extension */
+ did[1] = 0x13; /* version 1.3 */
+ did[2] = 4; /* length */
+ did[3] = 0x03; /* product type (0x03 == standalone display device) */
+ edid_checksum(did + 1, did[2] + 4);
+}
+
+static void qemu_displayid_generate(uint8_t *did, uint32_t refresh_rate,
+ uint32_t xres, uint32_t yres,
+ uint32_t xmm, uint32_t ymm)
+{
+ Timings timings;
+ generate_timings(&timings, refresh_rate, xres, yres);
+
+ did[0] = 0x70; /* display id extension */
+ did[1] = 0x13; /* version 1.3 */
+ did[2] = 23; /* length */
+ did[3] = 0x03; /* product type (0x03 == standalone display device) */
+
+ did[5] = 0x03; /* Detailed Timings Data Block */
+ did[6] = 0x00; /* revision */
+ did[7] = 0x14; /* block length */
+
+ did[8] = timings.clock & 0xff;
+ did[9] = (timings.clock & 0xff00) >> 8;
+ did[10] = (timings.clock & 0xff0000) >> 16;
+
+ did[11] = 0x88; /* leave aspect ratio undefined */
+
+ stw_le_p(did + 12, 0xffff & (xres - 1));
+ stw_le_p(did + 14, 0xffff & (timings.xblank - 1));
+ stw_le_p(did + 16, 0xffff & (timings.xfront - 1));
+ stw_le_p(did + 18, 0xffff & (timings.xsync - 1));
+
+ stw_le_p(did + 20, 0xffff & (yres - 1));
+ stw_le_p(did + 22, 0xffff & (timings.yblank - 1));
+ stw_le_p(did + 24, 0xffff & (timings.yfront - 1));
+ stw_le_p(did + 26, 0xffff & (timings.ysync - 1));
+
+ edid_checksum(did + 1, did[2] + 4);
+}
+
+void qemu_edid_generate(uint8_t *edid, size_t size,
+ qemu_edid_info *info)
+{
+ uint8_t *desc = edid + 54;
+ uint8_t *xtra3 = NULL;
+ uint8_t *dta = NULL;
+ uint8_t *did = NULL;
+ uint32_t width_mm, height_mm;
+ uint32_t refresh_rate = info->refresh_rate ? info->refresh_rate : 75000;
+ uint32_t dpi = 100; /* if no width_mm/height_mm */
+ uint32_t large_screen = 0;
+
+ /* =============== set defaults =============== */
+
+ if (!info->vendor || strlen(info->vendor) != 3) {
+ info->vendor = "RHT";
+ }
+ if (!info->name) {
+ info->name = "QEMU Monitor";
+ }
+ if (!info->prefx) {
+ info->prefx = 1024;
+ }
+ if (!info->prefy) {
+ info->prefy = 768;
+ }
+ if (info->prefx >= 4096 || info->prefy >= 4096) {
+ large_screen = 1;
+ }
+ if (info->width_mm && info->height_mm) {
+ width_mm = info->width_mm;
+ height_mm = info->height_mm;
+ dpi = qemu_edid_dpi_from_mm(width_mm, info->prefx);
+ } else {
+ width_mm = qemu_edid_dpi_to_mm(dpi, info->prefx);
+ height_mm = qemu_edid_dpi_to_mm(dpi, info->prefy);
+ }
+
+ /* =============== extensions =============== */
+
+ if (size >= 256) {
+ dta = edid + 128;
+ edid[126]++;
+ edid_ext_dta(dta);
+ }
+
+ if (size >= 384 && large_screen) {
+ did = edid + 256;
+ edid[126]++;
+ init_displayid(did);
+ }
+
+ /* =============== header information =============== */
+
+ /* fixed */
+ edid[0] = 0x00;
+ edid[1] = 0xff;
+ edid[2] = 0xff;
+ edid[3] = 0xff;
+ edid[4] = 0xff;
+ edid[5] = 0xff;
+ edid[6] = 0xff;
+ edid[7] = 0x00;
+
+ /* manufacturer id, product code, serial number */
+ uint16_t vendor_id = ((((info->vendor[0] - '@') & 0x1f) << 10) |
+ (((info->vendor[1] - '@') & 0x1f) << 5) |
+ (((info->vendor[2] - '@') & 0x1f) << 0));
+ uint16_t model_nr = 0x1234;
+ uint32_t serial_nr = info->serial ? atoi(info->serial) : 0;
+ stw_be_p(edid + 8, vendor_id);
+ stw_le_p(edid + 10, model_nr);
+ stl_le_p(edid + 12, serial_nr);
+
+ /* manufacture week and year */
+ edid[16] = 42;
+ edid[17] = 2014 - 1990;
+
+ /* edid version */
+ edid[18] = 1;
+ edid[19] = 4;
+
+
+ /* =============== basic display parameters =============== */
+
+ /* video input: digital, 8bpc, displayport */
+ edid[20] = 0xa5;
+
+ /* screen size: undefined */
+ edid[21] = width_mm / 10;
+ edid[22] = height_mm / 10;
+
+ /* display gamma: 2.2 */
+ edid[23] = 220 - 100;
+
+ /* supported features bitmap: std sRGB, preferred timing */
+ edid[24] = 0x06;
+
+
+ /* =============== chromaticity coordinates =============== */
+
+ /* standard sRGB colorspace */
+ edid_colorspace(edid,
+ 0.6400, 0.3300, /* red */
+ 0.3000, 0.6000, /* green */
+ 0.1500, 0.0600, /* blue */
+ 0.3127, 0.3290); /* white point */
+
+ /* =============== established timing bitmap =============== */
+ /* =============== standard timing information =============== */
+
+ /* both filled by edid_fill_modes() */
+
+
+ /* =============== descriptor blocks =============== */
+
+ if (!large_screen) {
+ /* The DTD section has only 12 bits to store the resolution */
+ edid_desc_timing(desc, refresh_rate, info->prefx, info->prefy,
+ width_mm, height_mm);
+ desc = edid_desc_next(edid, dta, desc);
+ }
+
+ xtra3 = desc;
+ edid_desc_xtra3_std(xtra3);
+ desc = edid_desc_next(edid, dta, desc);
+ edid_fill_modes(edid, xtra3, dta, info->maxx, info->maxy);
+ /*
+ * dta video data block is finished at thus point,
+ * so dta descriptor offsets don't move any more.
+ */
+
+ edid_desc_ranges(desc);
+ desc = edid_desc_next(edid, dta, desc);
+
+ if (desc && info->name) {
+ edid_desc_text(desc, 0xfc, info->name);
+ desc = edid_desc_next(edid, dta, desc);
+ }
+
+ if (desc && info->serial) {
+ edid_desc_text(desc, 0xff, info->serial);
+ desc = edid_desc_next(edid, dta, desc);
+ }
+
+ while (desc) {
+ edid_desc_dummy(desc);
+ desc = edid_desc_next(edid, dta, desc);
+ }
+
+ /* =============== display id extensions =============== */
+
+ if (did && large_screen) {
+ qemu_displayid_generate(did, refresh_rate, info->prefx, info->prefy,
+ width_mm, height_mm);
+ }
+
+ /* =============== finish up =============== */
+
+ edid_checksum(edid, 127);
+ if (dta) {
+ edid_checksum(dta, 127);
+ }
+ if (did) {
+ edid_checksum(did, 127);
+ }
+}
+
+size_t qemu_edid_size(uint8_t *edid)
+{
+ uint32_t exts;
+
+ if (edid[0] != 0x00 ||
+ edid[1] != 0xff) {
+ /* doesn't look like a valid edid block */
+ return 0;
+ }
+
+ exts = edid[126];
+ return 128 * (exts + 1);
+}
diff --git a/hw/display/edid-region.c b/hw/display/edid-region.c
new file mode 100644
index 000000000..675429dc1
--- /dev/null
+++ b/hw/display/edid-region.c
@@ -0,0 +1,33 @@
+#include "qemu/osdep.h"
+#include "exec/memory.h"
+#include "hw/display/edid.h"
+
+static uint64_t edid_region_read(void *ptr, hwaddr addr, unsigned size)
+{
+ uint8_t *edid = ptr;
+
+ return edid[addr];
+}
+
+static void edid_region_write(void *ptr, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ /* read only */
+}
+
+static const MemoryRegionOps edid_region_ops = {
+ .read = edid_region_read,
+ .write = edid_region_write,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 4,
+ .impl.min_access_size = 1,
+ .impl.max_access_size = 1,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+void qemu_edid_region_io(MemoryRegion *region, Object *owner,
+ uint8_t *edid, size_t size)
+{
+ memory_region_init_io(region, owner, &edid_region_ops,
+ edid, "edid", size);
+}
diff --git a/hw/display/exynos4210_fimd.c b/hw/display/exynos4210_fimd.c
new file mode 100644
index 000000000..34a960a97
--- /dev/null
+++ b/hw/display/exynos4210_fimd.c
@@ -0,0 +1,1972 @@
+/*
+ * Samsung exynos4210 Display Controller (FIMD)
+ *
+ * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
+ * All rights reserved.
+ * Based on LCD controller for Samsung S5PC1xx-based board emulation
+ * by Kirill Batuzov <batuzovk@ispras.ru>
+ *
+ * Contributed by Mitsyanko Igor <i.mitsyanko@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/hw.h"
+#include "hw/irq.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "ui/console.h"
+#include "ui/pixel_ops.h"
+#include "qemu/bswap.h"
+#include "qemu/module.h"
+#include "qemu/log.h"
+#include "qom/object.h"
+
+/* Debug messages configuration */
+#define EXYNOS4210_FIMD_DEBUG 0
+#define EXYNOS4210_FIMD_MODE_TRACE 0
+
+#if EXYNOS4210_FIMD_DEBUG == 0
+ #define DPRINT_L1(fmt, args...) do { } while (0)
+ #define DPRINT_L2(fmt, args...) do { } while (0)
+#elif EXYNOS4210_FIMD_DEBUG == 1
+ #define DPRINT_L1(fmt, args...) \
+ do {fprintf(stderr, "QEMU FIMD: "fmt, ## args); } while (0)
+ #define DPRINT_L2(fmt, args...) do { } while (0)
+#else
+ #define DPRINT_L1(fmt, args...) \
+ do {fprintf(stderr, "QEMU FIMD: "fmt, ## args); } while (0)
+ #define DPRINT_L2(fmt, args...) \
+ do {fprintf(stderr, "QEMU FIMD: "fmt, ## args); } while (0)
+#endif
+
+#if EXYNOS4210_FIMD_MODE_TRACE == 0
+ #define DPRINT_TRACE(fmt, args...) do { } while (0)
+#else
+ #define DPRINT_TRACE(fmt, args...) \
+ do {fprintf(stderr, "QEMU FIMD: "fmt, ## args); } while (0)
+#endif
+
+#define NUM_OF_WINDOWS 5
+#define FIMD_REGS_SIZE 0x4114
+
+/* Video main control registers */
+#define FIMD_VIDCON0 0x0000
+#define FIMD_VIDCON1 0x0004
+#define FIMD_VIDCON2 0x0008
+#define FIMD_VIDCON3 0x000C
+#define FIMD_VIDCON0_ENVID_F (1 << 0)
+#define FIMD_VIDCON0_ENVID (1 << 1)
+#define FIMD_VIDCON0_ENVID_MASK ((1 << 0) | (1 << 1))
+#define FIMD_VIDCON1_ROMASK 0x07FFE000
+
+/* Video time control registers */
+#define FIMD_VIDTCON_START 0x10
+#define FIMD_VIDTCON_END 0x1C
+#define FIMD_VIDTCON2_SIZE_MASK 0x07FF
+#define FIMD_VIDTCON2_HOR_SHIFT 0
+#define FIMD_VIDTCON2_VER_SHIFT 11
+
+/* Window control registers */
+#define FIMD_WINCON_START 0x0020
+#define FIMD_WINCON_END 0x0030
+#define FIMD_WINCON_ROMASK 0x82200000
+#define FIMD_WINCON_ENWIN (1 << 0)
+#define FIMD_WINCON_BLD_PIX (1 << 6)
+#define FIMD_WINCON_ALPHA_MUL (1 << 7)
+#define FIMD_WINCON_ALPHA_SEL (1 << 1)
+#define FIMD_WINCON_SWAP 0x078000
+#define FIMD_WINCON_SWAP_SHIFT 15
+#define FIMD_WINCON_SWAP_WORD 0x1
+#define FIMD_WINCON_SWAP_HWORD 0x2
+#define FIMD_WINCON_SWAP_BYTE 0x4
+#define FIMD_WINCON_SWAP_BITS 0x8
+#define FIMD_WINCON_BUFSTAT_L (1 << 21)
+#define FIMD_WINCON_BUFSTAT_H (1 << 31)
+#define FIMD_WINCON_BUFSTATUS ((1 << 21) | (1 << 31))
+#define FIMD_WINCON_BUF0_STAT ((0 << 21) | (0 << 31))
+#define FIMD_WINCON_BUF1_STAT ((1 << 21) | (0 << 31))
+#define FIMD_WINCON_BUF2_STAT ((0 << 21) | (1U << 31))
+#define FIMD_WINCON_BUFSELECT ((1 << 20) | (1 << 30))
+#define FIMD_WINCON_BUF0_SEL ((0 << 20) | (0 << 30))
+#define FIMD_WINCON_BUF1_SEL ((1 << 20) | (0 << 30))
+#define FIMD_WINCON_BUF2_SEL ((0 << 20) | (1 << 30))
+#define FIMD_WINCON_BUFMODE (1 << 14)
+#define IS_PALETTIZED_MODE(w) (w->wincon & 0xC)
+#define PAL_MODE_WITH_ALPHA(x) ((x) == 7)
+#define WIN_BPP_MODE(w) ((w->wincon >> 2) & 0xF)
+#define WIN_BPP_MODE_WITH_ALPHA(w) \
+ (WIN_BPP_MODE(w) == 0xD || WIN_BPP_MODE(w) == 0xE)
+
+/* Shadow control register */
+#define FIMD_SHADOWCON 0x0034
+#define FIMD_WINDOW_PROTECTED(s, w) ((s) & (1 << (10 + (w))))
+/* Channel mapping control register */
+#define FIMD_WINCHMAP 0x003C
+
+/* Window position control registers */
+#define FIMD_VIDOSD_START 0x0040
+#define FIMD_VIDOSD_END 0x0088
+#define FIMD_VIDOSD_COORD_MASK 0x07FF
+#define FIMD_VIDOSD_HOR_SHIFT 11
+#define FIMD_VIDOSD_VER_SHIFT 0
+#define FIMD_VIDOSD_ALPHA_AEN0 0xFFF000
+#define FIMD_VIDOSD_AEN0_SHIFT 12
+#define FIMD_VIDOSD_ALPHA_AEN1 0x000FFF
+
+/* Frame buffer address registers */
+#define FIMD_VIDWADD0_START 0x00A0
+#define FIMD_VIDWADD0_END 0x00C4
+#define FIMD_VIDWADD0_END 0x00C4
+#define FIMD_VIDWADD1_START 0x00D0
+#define FIMD_VIDWADD1_END 0x00F4
+#define FIMD_VIDWADD2_START 0x0100
+#define FIMD_VIDWADD2_END 0x0110
+#define FIMD_VIDWADD2_PAGEWIDTH 0x1FFF
+#define FIMD_VIDWADD2_OFFSIZE 0x1FFF
+#define FIMD_VIDWADD2_OFFSIZE_SHIFT 13
+#define FIMD_VIDW0ADD0_B2 0x20A0
+#define FIMD_VIDW4ADD0_B2 0x20C0
+
+/* Video interrupt control registers */
+#define FIMD_VIDINTCON0 0x130
+#define FIMD_VIDINTCON1 0x134
+
+/* Window color key registers */
+#define FIMD_WKEYCON_START 0x140
+#define FIMD_WKEYCON_END 0x15C
+#define FIMD_WKEYCON0_COMPKEY 0x00FFFFFF
+#define FIMD_WKEYCON0_CTL_SHIFT 24
+#define FIMD_WKEYCON0_DIRCON (1 << 24)
+#define FIMD_WKEYCON0_KEYEN (1 << 25)
+#define FIMD_WKEYCON0_KEYBLEN (1 << 26)
+/* Window color key alpha control register */
+#define FIMD_WKEYALPHA_START 0x160
+#define FIMD_WKEYALPHA_END 0x16C
+
+/* Dithering control register */
+#define FIMD_DITHMODE 0x170
+
+/* Window alpha control registers */
+#define FIMD_VIDALPHA_ALPHA_LOWER 0x000F0F0F
+#define FIMD_VIDALPHA_ALPHA_UPPER 0x00F0F0F0
+#define FIMD_VIDWALPHA_START 0x21C
+#define FIMD_VIDWALPHA_END 0x240
+
+/* Window color map registers */
+#define FIMD_WINMAP_START 0x180
+#define FIMD_WINMAP_END 0x190
+#define FIMD_WINMAP_EN (1 << 24)
+#define FIMD_WINMAP_COLOR_MASK 0x00FFFFFF
+
+/* Window palette control registers */
+#define FIMD_WPALCON_HIGH 0x019C
+#define FIMD_WPALCON_LOW 0x01A0
+#define FIMD_WPALCON_UPDATEEN (1 << 9)
+#define FIMD_WPAL_W0PAL_L 0x07
+#define FIMD_WPAL_W0PAL_L_SHT 0
+#define FIMD_WPAL_W1PAL_L 0x07
+#define FIMD_WPAL_W1PAL_L_SHT 3
+#define FIMD_WPAL_W2PAL_L 0x01
+#define FIMD_WPAL_W2PAL_L_SHT 6
+#define FIMD_WPAL_W2PAL_H 0x06
+#define FIMD_WPAL_W2PAL_H_SHT 8
+#define FIMD_WPAL_W3PAL_L 0x01
+#define FIMD_WPAL_W3PAL_L_SHT 7
+#define FIMD_WPAL_W3PAL_H 0x06
+#define FIMD_WPAL_W3PAL_H_SHT 12
+#define FIMD_WPAL_W4PAL_L 0x01
+#define FIMD_WPAL_W4PAL_L_SHT 8
+#define FIMD_WPAL_W4PAL_H 0x06
+#define FIMD_WPAL_W4PAL_H_SHT 16
+
+/* Trigger control registers */
+#define FIMD_TRIGCON 0x01A4
+#define FIMD_TRIGCON_ROMASK 0x00000004
+
+/* LCD I80 Interface Control */
+#define FIMD_I80IFCON_START 0x01B0
+#define FIMD_I80IFCON_END 0x01BC
+/* Color gain control register */
+#define FIMD_COLORGAINCON 0x01C0
+/* LCD i80 Interface Command Control */
+#define FIMD_LDI_CMDCON0 0x01D0
+#define FIMD_LDI_CMDCON1 0x01D4
+/* I80 System Interface Manual Command Control */
+#define FIMD_SIFCCON0 0x01E0
+#define FIMD_SIFCCON2 0x01E8
+
+/* Hue Control Registers */
+#define FIMD_HUECOEFCR_START 0x01EC
+#define FIMD_HUECOEFCR_END 0x01F4
+#define FIMD_HUECOEFCB_START 0x01FC
+#define FIMD_HUECOEFCB_END 0x0208
+#define FIMD_HUEOFFSET 0x020C
+
+/* Video interrupt control registers */
+#define FIMD_VIDINT_INTFIFOPEND (1 << 0)
+#define FIMD_VIDINT_INTFRMPEND (1 << 1)
+#define FIMD_VIDINT_INTI80PEND (1 << 2)
+#define FIMD_VIDINT_INTEN (1 << 0)
+#define FIMD_VIDINT_INTFIFOEN (1 << 1)
+#define FIMD_VIDINT_INTFRMEN (1 << 12)
+#define FIMD_VIDINT_I80IFDONE (1 << 17)
+
+/* Window blend equation control registers */
+#define FIMD_BLENDEQ_START 0x0244
+#define FIMD_BLENDEQ_END 0x0250
+#define FIMD_BLENDCON 0x0260
+#define FIMD_ALPHA_8BIT (1 << 0)
+#define FIMD_BLENDEQ_COEF_MASK 0xF
+
+/* Window RTQOS Control Registers */
+#define FIMD_WRTQOSCON_START 0x0264
+#define FIMD_WRTQOSCON_END 0x0274
+
+/* LCD I80 Interface Command */
+#define FIMD_I80IFCMD_START 0x0280
+#define FIMD_I80IFCMD_END 0x02AC
+
+/* Shadow windows control registers */
+#define FIMD_SHD_ADD0_START 0x40A0
+#define FIMD_SHD_ADD0_END 0x40C0
+#define FIMD_SHD_ADD1_START 0x40D0
+#define FIMD_SHD_ADD1_END 0x40F0
+#define FIMD_SHD_ADD2_START 0x4100
+#define FIMD_SHD_ADD2_END 0x4110
+
+/* Palette memory */
+#define FIMD_PAL_MEM_START 0x2400
+#define FIMD_PAL_MEM_END 0x37FC
+/* Palette memory aliases for windows 0 and 1 */
+#define FIMD_PALMEM_AL_START 0x0400
+#define FIMD_PALMEM_AL_END 0x0BFC
+
+typedef struct {
+ uint8_t r, g, b;
+ /* D[31..24]dummy, D[23..16]rAlpha, D[15..8]gAlpha, D[7..0]bAlpha */
+ uint32_t a;
+} rgba;
+#define RGBA_SIZE 7
+
+typedef void pixel_to_rgb_func(uint32_t pixel, rgba *p);
+typedef struct Exynos4210fimdWindow Exynos4210fimdWindow;
+
+struct Exynos4210fimdWindow {
+ uint32_t wincon; /* Window control register */
+ uint32_t buf_start[3]; /* Start address for video frame buffer */
+ uint32_t buf_end[3]; /* End address for video frame buffer */
+ uint32_t keycon[2]; /* Window color key registers */
+ uint32_t keyalpha; /* Color key alpha control register */
+ uint32_t winmap; /* Window color map register */
+ uint32_t blendeq; /* Window blending equation control register */
+ uint32_t rtqoscon; /* Window RTQOS Control Registers */
+ uint32_t palette[256]; /* Palette RAM */
+ uint32_t shadow_buf_start; /* Start address of shadow frame buffer */
+ uint32_t shadow_buf_end; /* End address of shadow frame buffer */
+ uint32_t shadow_buf_size; /* Virtual shadow screen width */
+
+ pixel_to_rgb_func *pixel_to_rgb;
+ void (*draw_line)(Exynos4210fimdWindow *w, uint8_t *src, uint8_t *dst,
+ bool blend);
+ uint32_t (*get_alpha)(Exynos4210fimdWindow *w, uint32_t pix_a);
+ uint16_t lefttop_x, lefttop_y; /* VIDOSD0 register */
+ uint16_t rightbot_x, rightbot_y; /* VIDOSD1 register */
+ uint32_t osdsize; /* VIDOSD2&3 register */
+ uint32_t alpha_val[2]; /* VIDOSD2&3, VIDWALPHA registers */
+ uint16_t virtpage_width; /* VIDWADD2 register */
+ uint16_t virtpage_offsize; /* VIDWADD2 register */
+ MemoryRegionSection mem_section; /* RAM fragment containing framebuffer */
+ uint8_t *host_fb_addr; /* Host pointer to window's framebuffer */
+ hwaddr fb_len; /* Framebuffer length */
+};
+
+#define TYPE_EXYNOS4210_FIMD "exynos4210.fimd"
+OBJECT_DECLARE_SIMPLE_TYPE(Exynos4210fimdState, EXYNOS4210_FIMD)
+
+struct Exynos4210fimdState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ QemuConsole *console;
+ qemu_irq irq[3];
+
+ uint32_t vidcon[4]; /* Video main control registers 0-3 */
+ uint32_t vidtcon[4]; /* Video time control registers 0-3 */
+ uint32_t shadowcon; /* Window shadow control register */
+ uint32_t winchmap; /* Channel mapping control register */
+ uint32_t vidintcon[2]; /* Video interrupt control registers */
+ uint32_t dithmode; /* Dithering control register */
+ uint32_t wpalcon[2]; /* Window palette control registers */
+ uint32_t trigcon; /* Trigger control register */
+ uint32_t i80ifcon[4]; /* I80 interface control registers */
+ uint32_t colorgaincon; /* Color gain control register */
+ uint32_t ldi_cmdcon[2]; /* LCD I80 interface command control */
+ uint32_t sifccon[3]; /* I80 System Interface Manual Command Control */
+ uint32_t huecoef_cr[4]; /* Hue control registers */
+ uint32_t huecoef_cb[4]; /* Hue control registers */
+ uint32_t hueoffset; /* Hue offset control register */
+ uint32_t blendcon; /* Blending control register */
+ uint32_t i80ifcmd[12]; /* LCD I80 Interface Command */
+
+ Exynos4210fimdWindow window[5]; /* Window-specific registers */
+ uint8_t *ifb; /* Internal frame buffer */
+ bool invalidate; /* Image needs to be redrawn */
+ bool enabled; /* Display controller is enabled */
+};
+
+/* Perform byte/halfword/word swap of data according to WINCON */
+static inline void fimd_swap_data(unsigned int swap_ctl, uint64_t *data)
+{
+ int i;
+ uint64_t res;
+ uint64_t x = *data;
+
+ if (swap_ctl & FIMD_WINCON_SWAP_BITS) {
+ res = 0;
+ for (i = 0; i < 64; i++) {
+ if (x & (1ULL << (63 - i))) {
+ res |= (1ULL << i);
+ }
+ }
+ x = res;
+ }
+
+ if (swap_ctl & FIMD_WINCON_SWAP_BYTE) {
+ x = bswap64(x);
+ }
+
+ if (swap_ctl & FIMD_WINCON_SWAP_HWORD) {
+ x = ((x & 0x000000000000FFFFULL) << 48) |
+ ((x & 0x00000000FFFF0000ULL) << 16) |
+ ((x & 0x0000FFFF00000000ULL) >> 16) |
+ ((x & 0xFFFF000000000000ULL) >> 48);
+ }
+
+ if (swap_ctl & FIMD_WINCON_SWAP_WORD) {
+ x = ((x & 0x00000000FFFFFFFFULL) << 32) |
+ ((x & 0xFFFFFFFF00000000ULL) >> 32);
+ }
+
+ *data = x;
+}
+
+/* Conversion routines of Pixel data from frame buffer area to internal RGBA
+ * pixel representation.
+ * Every color component internally represented as 8-bit value. If original
+ * data has less than 8 bit for component, data is extended to 8 bit. For
+ * example, if blue component has only two possible values 0 and 1 it will be
+ * extended to 0 and 0xFF */
+
+/* One bit for alpha representation */
+#define DEF_PIXEL_TO_RGB_A1(N, R, G, B) \
+static void N(uint32_t pixel, rgba *p) \
+{ \
+ p->b = ((pixel & ((1 << (B)) - 1)) << (8 - (B))) | \
+ ((pixel >> (2 * (B) - 8)) & ((1 << (8 - (B))) - 1)); \
+ pixel >>= (B); \
+ p->g = (pixel & ((1 << (G)) - 1)) << (8 - (G)) | \
+ ((pixel >> (2 * (G) - 8)) & ((1 << (8 - (G))) - 1)); \
+ pixel >>= (G); \
+ p->r = (pixel & ((1 << (R)) - 1)) << (8 - (R)) | \
+ ((pixel >> (2 * (R) - 8)) & ((1 << (8 - (R))) - 1)); \
+ pixel >>= (R); \
+ p->a = (pixel & 0x1); \
+}
+
+DEF_PIXEL_TO_RGB_A1(pixel_a444_to_rgb, 4, 4, 4)
+DEF_PIXEL_TO_RGB_A1(pixel_a555_to_rgb, 5, 5, 5)
+DEF_PIXEL_TO_RGB_A1(pixel_a666_to_rgb, 6, 6, 6)
+DEF_PIXEL_TO_RGB_A1(pixel_a665_to_rgb, 6, 6, 5)
+DEF_PIXEL_TO_RGB_A1(pixel_a888_to_rgb, 8, 8, 8)
+DEF_PIXEL_TO_RGB_A1(pixel_a887_to_rgb, 8, 8, 7)
+
+/* Alpha component is always zero */
+#define DEF_PIXEL_TO_RGB_A0(N, R, G, B) \
+static void N(uint32_t pixel, rgba *p) \
+{ \
+ p->b = ((pixel & ((1 << (B)) - 1)) << (8 - (B))) | \
+ ((pixel >> (2 * (B) - 8)) & ((1 << (8 - (B))) - 1)); \
+ pixel >>= (B); \
+ p->g = (pixel & ((1 << (G)) - 1)) << (8 - (G)) | \
+ ((pixel >> (2 * (G) - 8)) & ((1 << (8 - (G))) - 1)); \
+ pixel >>= (G); \
+ p->r = (pixel & ((1 << (R)) - 1)) << (8 - (R)) | \
+ ((pixel >> (2 * (R) - 8)) & ((1 << (8 - (R))) - 1)); \
+ p->a = 0x0; \
+}
+
+DEF_PIXEL_TO_RGB_A0(pixel_565_to_rgb, 5, 6, 5)
+DEF_PIXEL_TO_RGB_A0(pixel_555_to_rgb, 5, 5, 5)
+DEF_PIXEL_TO_RGB_A0(pixel_666_to_rgb, 6, 6, 6)
+DEF_PIXEL_TO_RGB_A0(pixel_888_to_rgb, 8, 8, 8)
+
+/* Alpha component has some meaningful value */
+#define DEF_PIXEL_TO_RGB_A(N, R, G, B, A) \
+static void N(uint32_t pixel, rgba *p) \
+{ \
+ p->b = ((pixel & ((1 << (B)) - 1)) << (8 - (B))) | \
+ ((pixel >> (2 * (B) - 8)) & ((1 << (8 - (B))) - 1)); \
+ pixel >>= (B); \
+ p->g = (pixel & ((1 << (G)) - 1)) << (8 - (G)) | \
+ ((pixel >> (2 * (G) - 8)) & ((1 << (8 - (G))) - 1)); \
+ pixel >>= (G); \
+ p->r = (pixel & ((1 << (R)) - 1)) << (8 - (R)) | \
+ ((pixel >> (2 * (R) - 8)) & ((1 << (8 - (R))) - 1)); \
+ pixel >>= (R); \
+ p->a = (pixel & ((1 << (A)) - 1)) << (8 - (A)) | \
+ ((pixel >> (2 * (A) - 8)) & ((1 << (8 - (A))) - 1)); \
+ p->a = p->a | (p->a << 8) | (p->a << 16); \
+}
+
+DEF_PIXEL_TO_RGB_A(pixel_4444_to_rgb, 4, 4, 4, 4)
+DEF_PIXEL_TO_RGB_A(pixel_8888_to_rgb, 8, 8, 8, 8)
+
+/* Lookup table to extent 2-bit color component to 8 bit */
+static const uint8_t pixel_lutable_2b[4] = {
+ 0x0, 0x55, 0xAA, 0xFF
+};
+/* Lookup table to extent 3-bit color component to 8 bit */
+static const uint8_t pixel_lutable_3b[8] = {
+ 0x0, 0x24, 0x49, 0x6D, 0x92, 0xB6, 0xDB, 0xFF
+};
+/* Special case for a232 bpp mode */
+static void pixel_a232_to_rgb(uint32_t pixel, rgba *p)
+{
+ p->b = pixel_lutable_2b[(pixel & 0x3)];
+ pixel >>= 2;
+ p->g = pixel_lutable_3b[(pixel & 0x7)];
+ pixel >>= 3;
+ p->r = pixel_lutable_2b[(pixel & 0x3)];
+ pixel >>= 2;
+ p->a = (pixel & 0x1);
+}
+
+/* Special case for (5+1, 5+1, 5+1) mode. Data bit 15 is common LSB
+ * for all three color components */
+static void pixel_1555_to_rgb(uint32_t pixel, rgba *p)
+{
+ uint8_t comm = (pixel >> 15) & 1;
+ p->b = ((((pixel & 0x1F) << 1) | comm) << 2) | ((pixel >> 3) & 0x3);
+ pixel >>= 5;
+ p->g = ((((pixel & 0x1F) << 1) | comm) << 2) | ((pixel >> 3) & 0x3);
+ pixel >>= 5;
+ p->r = ((((pixel & 0x1F) << 1) | comm) << 2) | ((pixel >> 3) & 0x3);
+ p->a = 0x0;
+}
+
+/* Put/get pixel to/from internal LCD Controller framebuffer */
+
+static int put_pixel_ifb(const rgba p, uint8_t *d)
+{
+ *(uint8_t *)d++ = p.r;
+ *(uint8_t *)d++ = p.g;
+ *(uint8_t *)d++ = p.b;
+ *(uint32_t *)d = p.a;
+ return RGBA_SIZE;
+}
+
+static int get_pixel_ifb(const uint8_t *s, rgba *p)
+{
+ p->r = *(uint8_t *)s++;
+ p->g = *(uint8_t *)s++;
+ p->b = *(uint8_t *)s++;
+ p->a = (*(uint32_t *)s) & 0x00FFFFFF;
+ return RGBA_SIZE;
+}
+
+static pixel_to_rgb_func *palette_data_format[8] = {
+ [0] = pixel_565_to_rgb,
+ [1] = pixel_a555_to_rgb,
+ [2] = pixel_666_to_rgb,
+ [3] = pixel_a665_to_rgb,
+ [4] = pixel_a666_to_rgb,
+ [5] = pixel_888_to_rgb,
+ [6] = pixel_a888_to_rgb,
+ [7] = pixel_8888_to_rgb
+};
+
+/* Returns Index in palette data formats table for given window number WINDOW */
+static uint32_t
+exynos4210_fimd_palette_format(Exynos4210fimdState *s, int window)
+{
+ uint32_t ret;
+
+ switch (window) {
+ case 0:
+ ret = (s->wpalcon[1] >> FIMD_WPAL_W0PAL_L_SHT) & FIMD_WPAL_W0PAL_L;
+ if (ret != 7) {
+ ret = 6 - ret;
+ }
+ break;
+ case 1:
+ ret = (s->wpalcon[1] >> FIMD_WPAL_W1PAL_L_SHT) & FIMD_WPAL_W1PAL_L;
+ if (ret != 7) {
+ ret = 6 - ret;
+ }
+ break;
+ case 2:
+ ret = ((s->wpalcon[0] >> FIMD_WPAL_W2PAL_H_SHT) & FIMD_WPAL_W2PAL_H) |
+ ((s->wpalcon[1] >> FIMD_WPAL_W2PAL_L_SHT) & FIMD_WPAL_W2PAL_L);
+ break;
+ case 3:
+ ret = ((s->wpalcon[0] >> FIMD_WPAL_W3PAL_H_SHT) & FIMD_WPAL_W3PAL_H) |
+ ((s->wpalcon[1] >> FIMD_WPAL_W3PAL_L_SHT) & FIMD_WPAL_W3PAL_L);
+ break;
+ case 4:
+ ret = ((s->wpalcon[0] >> FIMD_WPAL_W4PAL_H_SHT) & FIMD_WPAL_W4PAL_H) |
+ ((s->wpalcon[1] >> FIMD_WPAL_W4PAL_L_SHT) & FIMD_WPAL_W4PAL_L);
+ break;
+ default:
+ hw_error("exynos4210.fimd: incorrect window number %d\n", window);
+ ret = 0;
+ break;
+ }
+ return ret;
+}
+
+#define FIMD_1_MINUS_COLOR(x) \
+ ((0xFF - ((x) & 0xFF)) | (0xFF00 - ((x) & 0xFF00)) | \
+ (0xFF0000 - ((x) & 0xFF0000)))
+#define EXTEND_LOWER_HALFBYTE(x) (((x) & 0xF0F0F) | (((x) << 4) & 0xF0F0F0))
+#define EXTEND_UPPER_HALFBYTE(x) (((x) & 0xF0F0F0) | (((x) >> 4) & 0xF0F0F))
+
+/* Multiply three lower bytes of two 32-bit words with each other.
+ * Each byte with values 0-255 is considered as a number with possible values
+ * in a range [0 - 1] */
+static inline uint32_t fimd_mult_each_byte(uint32_t a, uint32_t b)
+{
+ uint32_t tmp;
+ uint32_t ret;
+
+ ret = ((tmp = (((a & 0xFF) * (b & 0xFF)) / 0xFF)) > 0xFF) ? 0xFF : tmp;
+ ret |= ((tmp = ((((a >> 8) & 0xFF) * ((b >> 8) & 0xFF)) / 0xFF)) > 0xFF) ?
+ 0xFF00 : tmp << 8;
+ ret |= ((tmp = ((((a >> 16) & 0xFF) * ((b >> 16) & 0xFF)) / 0xFF)) > 0xFF) ?
+ 0xFF0000 : tmp << 16;
+ return ret;
+}
+
+/* For each corresponding bytes of two 32-bit words: (a*b + c*d)
+ * Byte values 0-255 are mapped to a range [0 .. 1] */
+static inline uint32_t
+fimd_mult_and_sum_each_byte(uint32_t a, uint32_t b, uint32_t c, uint32_t d)
+{
+ uint32_t tmp;
+ uint32_t ret;
+
+ ret = ((tmp = (((a & 0xFF) * (b & 0xFF) + (c & 0xFF) * (d & 0xFF)) / 0xFF))
+ > 0xFF) ? 0xFF : tmp;
+ ret |= ((tmp = ((((a >> 8) & 0xFF) * ((b >> 8) & 0xFF) + ((c >> 8) & 0xFF) *
+ ((d >> 8) & 0xFF)) / 0xFF)) > 0xFF) ? 0xFF00 : tmp << 8;
+ ret |= ((tmp = ((((a >> 16) & 0xFF) * ((b >> 16) & 0xFF) +
+ ((c >> 16) & 0xFF) * ((d >> 16) & 0xFF)) / 0xFF)) > 0xFF) ?
+ 0xFF0000 : tmp << 16;
+ return ret;
+}
+
+/* These routines cover all possible sources of window's transparent factor
+ * used in blending equation. Choice of routine is affected by WPALCON
+ * registers, BLENDCON register and window's WINCON register */
+
+static uint32_t fimd_get_alpha_pix(Exynos4210fimdWindow *w, uint32_t pix_a)
+{
+ return pix_a;
+}
+
+static uint32_t
+fimd_get_alpha_pix_extlow(Exynos4210fimdWindow *w, uint32_t pix_a)
+{
+ return EXTEND_LOWER_HALFBYTE(pix_a);
+}
+
+static uint32_t
+fimd_get_alpha_pix_exthigh(Exynos4210fimdWindow *w, uint32_t pix_a)
+{
+ return EXTEND_UPPER_HALFBYTE(pix_a);
+}
+
+static uint32_t fimd_get_alpha_mult(Exynos4210fimdWindow *w, uint32_t pix_a)
+{
+ return fimd_mult_each_byte(pix_a, w->alpha_val[0]);
+}
+
+static uint32_t fimd_get_alpha_mult_ext(Exynos4210fimdWindow *w, uint32_t pix_a)
+{
+ return fimd_mult_each_byte(EXTEND_LOWER_HALFBYTE(pix_a),
+ EXTEND_UPPER_HALFBYTE(w->alpha_val[0]));
+}
+
+static uint32_t fimd_get_alpha_aen(Exynos4210fimdWindow *w, uint32_t pix_a)
+{
+ return w->alpha_val[pix_a];
+}
+
+static uint32_t fimd_get_alpha_aen_ext(Exynos4210fimdWindow *w, uint32_t pix_a)
+{
+ return EXTEND_UPPER_HALFBYTE(w->alpha_val[pix_a]);
+}
+
+static uint32_t fimd_get_alpha_sel(Exynos4210fimdWindow *w, uint32_t pix_a)
+{
+ return w->alpha_val[(w->wincon & FIMD_WINCON_ALPHA_SEL) ? 1 : 0];
+}
+
+static uint32_t fimd_get_alpha_sel_ext(Exynos4210fimdWindow *w, uint32_t pix_a)
+{
+ return EXTEND_UPPER_HALFBYTE(w->alpha_val[(w->wincon &
+ FIMD_WINCON_ALPHA_SEL) ? 1 : 0]);
+}
+
+/* Updates currently active alpha value get function for specified window */
+static void fimd_update_get_alpha(Exynos4210fimdState *s, int win)
+{
+ Exynos4210fimdWindow *w = &s->window[win];
+ const bool alpha_is_8bit = s->blendcon & FIMD_ALPHA_8BIT;
+
+ if (w->wincon & FIMD_WINCON_BLD_PIX) {
+ if ((w->wincon & FIMD_WINCON_ALPHA_SEL) && WIN_BPP_MODE_WITH_ALPHA(w)) {
+ /* In this case, alpha component contains meaningful value */
+ if (w->wincon & FIMD_WINCON_ALPHA_MUL) {
+ w->get_alpha = alpha_is_8bit ?
+ fimd_get_alpha_mult : fimd_get_alpha_mult_ext;
+ } else {
+ w->get_alpha = alpha_is_8bit ?
+ fimd_get_alpha_pix : fimd_get_alpha_pix_extlow;
+ }
+ } else {
+ if (IS_PALETTIZED_MODE(w) &&
+ PAL_MODE_WITH_ALPHA(exynos4210_fimd_palette_format(s, win))) {
+ /* Alpha component has 8-bit numeric value */
+ w->get_alpha = alpha_is_8bit ?
+ fimd_get_alpha_pix : fimd_get_alpha_pix_exthigh;
+ } else {
+ /* Alpha has only two possible values (AEN) */
+ w->get_alpha = alpha_is_8bit ?
+ fimd_get_alpha_aen : fimd_get_alpha_aen_ext;
+ }
+ }
+ } else {
+ w->get_alpha = alpha_is_8bit ? fimd_get_alpha_sel :
+ fimd_get_alpha_sel_ext;
+ }
+}
+
+/* Blends current window's (w) pixel (foreground pixel *ret) with background
+ * window (w_blend) pixel p_bg according to formula:
+ * NEW_COLOR = a_coef x FG_PIXEL_COLOR + b_coef x BG_PIXEL_COLOR
+ * NEW_ALPHA = p_coef x FG_ALPHA + q_coef x BG_ALPHA
+ */
+static void
+exynos4210_fimd_blend_pixel(Exynos4210fimdWindow *w, rgba p_bg, rgba *ret)
+{
+ rgba p_fg = *ret;
+ uint32_t bg_color = ((p_bg.r & 0xFF) << 16) | ((p_bg.g & 0xFF) << 8) |
+ (p_bg.b & 0xFF);
+ uint32_t fg_color = ((p_fg.r & 0xFF) << 16) | ((p_fg.g & 0xFF) << 8) |
+ (p_fg.b & 0xFF);
+ uint32_t alpha_fg = p_fg.a;
+ int i;
+ /* It is possible that blending equation parameters a and b do not
+ * depend on window BLENEQ register. Account for this with first_coef */
+ enum { A_COEF = 0, B_COEF = 1, P_COEF = 2, Q_COEF = 3, COEF_NUM = 4};
+ uint32_t first_coef = A_COEF;
+ uint32_t blend_param[COEF_NUM];
+
+ if (w->keycon[0] & FIMD_WKEYCON0_KEYEN) {
+ uint32_t colorkey = (w->keycon[1] &
+ ~(w->keycon[0] & FIMD_WKEYCON0_COMPKEY)) & FIMD_WKEYCON0_COMPKEY;
+
+ if ((w->keycon[0] & FIMD_WKEYCON0_DIRCON) &&
+ (bg_color & ~(w->keycon[0] & FIMD_WKEYCON0_COMPKEY)) == colorkey) {
+ /* Foreground pixel is displayed */
+ if (w->keycon[0] & FIMD_WKEYCON0_KEYBLEN) {
+ alpha_fg = w->keyalpha;
+ blend_param[A_COEF] = alpha_fg;
+ blend_param[B_COEF] = FIMD_1_MINUS_COLOR(alpha_fg);
+ } else {
+ alpha_fg = 0;
+ blend_param[A_COEF] = 0xFFFFFF;
+ blend_param[B_COEF] = 0x0;
+ }
+ first_coef = P_COEF;
+ } else if ((w->keycon[0] & FIMD_WKEYCON0_DIRCON) == 0 &&
+ (fg_color & ~(w->keycon[0] & FIMD_WKEYCON0_COMPKEY)) == colorkey) {
+ /* Background pixel is displayed */
+ if (w->keycon[0] & FIMD_WKEYCON0_KEYBLEN) {
+ alpha_fg = w->keyalpha;
+ blend_param[A_COEF] = alpha_fg;
+ blend_param[B_COEF] = FIMD_1_MINUS_COLOR(alpha_fg);
+ } else {
+ alpha_fg = 0;
+ blend_param[A_COEF] = 0x0;
+ blend_param[B_COEF] = 0xFFFFFF;
+ }
+ first_coef = P_COEF;
+ }
+ }
+
+ for (i = first_coef; i < COEF_NUM; i++) {
+ switch ((w->blendeq >> i * 6) & FIMD_BLENDEQ_COEF_MASK) {
+ case 0:
+ blend_param[i] = 0;
+ break;
+ case 1:
+ blend_param[i] = 0xFFFFFF;
+ break;
+ case 2:
+ blend_param[i] = alpha_fg;
+ break;
+ case 3:
+ blend_param[i] = FIMD_1_MINUS_COLOR(alpha_fg);
+ break;
+ case 4:
+ blend_param[i] = p_bg.a;
+ break;
+ case 5:
+ blend_param[i] = FIMD_1_MINUS_COLOR(p_bg.a);
+ break;
+ case 6:
+ blend_param[i] = w->alpha_val[0];
+ break;
+ case 10:
+ blend_param[i] = fg_color;
+ break;
+ case 11:
+ blend_param[i] = FIMD_1_MINUS_COLOR(fg_color);
+ break;
+ case 12:
+ blend_param[i] = bg_color;
+ break;
+ case 13:
+ blend_param[i] = FIMD_1_MINUS_COLOR(bg_color);
+ break;
+ default:
+ hw_error("exynos4210.fimd: blend equation coef illegal value\n");
+ break;
+ }
+ }
+
+ fg_color = fimd_mult_and_sum_each_byte(bg_color, blend_param[B_COEF],
+ fg_color, blend_param[A_COEF]);
+ ret->b = fg_color & 0xFF;
+ fg_color >>= 8;
+ ret->g = fg_color & 0xFF;
+ fg_color >>= 8;
+ ret->r = fg_color & 0xFF;
+ ret->a = fimd_mult_and_sum_each_byte(alpha_fg, blend_param[P_COEF],
+ p_bg.a, blend_param[Q_COEF]);
+}
+
+/* These routines read data from video frame buffer in system RAM, convert
+ * this data to display controller internal representation, if necessary,
+ * perform pixel blending with data, currently presented in internal buffer.
+ * Result is stored in display controller internal frame buffer. */
+
+/* Draw line with index in palette table in RAM frame buffer data */
+#define DEF_DRAW_LINE_PALETTE(N) \
+static void glue(draw_line_palette_, N)(Exynos4210fimdWindow *w, uint8_t *src, \
+ uint8_t *dst, bool blend) \
+{ \
+ int width = w->rightbot_x - w->lefttop_x + 1; \
+ uint8_t *ifb = dst; \
+ uint8_t swap = (w->wincon & FIMD_WINCON_SWAP) >> FIMD_WINCON_SWAP_SHIFT; \
+ uint64_t data; \
+ rgba p, p_old; \
+ int i; \
+ do { \
+ memcpy(&data, src, sizeof(data)); \
+ src += 8; \
+ fimd_swap_data(swap, &data); \
+ for (i = (64 / (N) - 1); i >= 0; i--) { \
+ w->pixel_to_rgb(w->palette[(data >> ((N) * i)) & \
+ ((1ULL << (N)) - 1)], &p); \
+ p.a = w->get_alpha(w, p.a); \
+ if (blend) { \
+ ifb += get_pixel_ifb(ifb, &p_old); \
+ exynos4210_fimd_blend_pixel(w, p_old, &p); \
+ } \
+ dst += put_pixel_ifb(p, dst); \
+ } \
+ width -= (64 / (N)); \
+ } while (width > 0); \
+}
+
+/* Draw line with direct color value in RAM frame buffer data */
+#define DEF_DRAW_LINE_NOPALETTE(N) \
+static void glue(draw_line_, N)(Exynos4210fimdWindow *w, uint8_t *src, \
+ uint8_t *dst, bool blend) \
+{ \
+ int width = w->rightbot_x - w->lefttop_x + 1; \
+ uint8_t *ifb = dst; \
+ uint8_t swap = (w->wincon & FIMD_WINCON_SWAP) >> FIMD_WINCON_SWAP_SHIFT; \
+ uint64_t data; \
+ rgba p, p_old; \
+ int i; \
+ do { \
+ memcpy(&data, src, sizeof(data)); \
+ src += 8; \
+ fimd_swap_data(swap, &data); \
+ for (i = (64 / (N) - 1); i >= 0; i--) { \
+ w->pixel_to_rgb((data >> ((N) * i)) & ((1ULL << (N)) - 1), &p); \
+ p.a = w->get_alpha(w, p.a); \
+ if (blend) { \
+ ifb += get_pixel_ifb(ifb, &p_old); \
+ exynos4210_fimd_blend_pixel(w, p_old, &p); \
+ } \
+ dst += put_pixel_ifb(p, dst); \
+ } \
+ width -= (64 / (N)); \
+ } while (width > 0); \
+}
+
+DEF_DRAW_LINE_PALETTE(1)
+DEF_DRAW_LINE_PALETTE(2)
+DEF_DRAW_LINE_PALETTE(4)
+DEF_DRAW_LINE_PALETTE(8)
+DEF_DRAW_LINE_NOPALETTE(8) /* 8bpp mode has palette and non-palette versions */
+DEF_DRAW_LINE_NOPALETTE(16)
+DEF_DRAW_LINE_NOPALETTE(32)
+
+/* Special draw line routine for window color map case */
+static void draw_line_mapcolor(Exynos4210fimdWindow *w, uint8_t *src,
+ uint8_t *dst, bool blend)
+{
+ rgba p, p_old;
+ uint8_t *ifb = dst;
+ int width = w->rightbot_x - w->lefttop_x + 1;
+ uint32_t map_color = w->winmap & FIMD_WINMAP_COLOR_MASK;
+
+ do {
+ pixel_888_to_rgb(map_color, &p);
+ p.a = w->get_alpha(w, p.a);
+ if (blend) {
+ ifb += get_pixel_ifb(ifb, &p_old);
+ exynos4210_fimd_blend_pixel(w, p_old, &p);
+ }
+ dst += put_pixel_ifb(p, dst);
+ } while (--width);
+}
+
+/* Write RGB to QEMU's GraphicConsole framebuffer */
+
+static int put_to_qemufb_pixel8(const rgba p, uint8_t *d)
+{
+ uint32_t pixel = rgb_to_pixel8(p.r, p.g, p.b);
+ *(uint8_t *)d = pixel;
+ return 1;
+}
+
+static int put_to_qemufb_pixel15(const rgba p, uint8_t *d)
+{
+ uint32_t pixel = rgb_to_pixel15(p.r, p.g, p.b);
+ *(uint16_t *)d = pixel;
+ return 2;
+}
+
+static int put_to_qemufb_pixel16(const rgba p, uint8_t *d)
+{
+ uint32_t pixel = rgb_to_pixel16(p.r, p.g, p.b);
+ *(uint16_t *)d = pixel;
+ return 2;
+}
+
+static int put_to_qemufb_pixel24(const rgba p, uint8_t *d)
+{
+ uint32_t pixel = rgb_to_pixel24(p.r, p.g, p.b);
+ *(uint8_t *)d++ = (pixel >> 0) & 0xFF;
+ *(uint8_t *)d++ = (pixel >> 8) & 0xFF;
+ *(uint8_t *)d++ = (pixel >> 16) & 0xFF;
+ return 3;
+}
+
+static int put_to_qemufb_pixel32(const rgba p, uint8_t *d)
+{
+ uint32_t pixel = rgb_to_pixel24(p.r, p.g, p.b);
+ *(uint32_t *)d = pixel;
+ return 4;
+}
+
+/* Routine to copy pixel from internal buffer to QEMU buffer */
+static int (*put_pixel_toqemu)(const rgba p, uint8_t *pixel);
+static inline void fimd_update_putpix_qemu(int bpp)
+{
+ switch (bpp) {
+ case 8:
+ put_pixel_toqemu = put_to_qemufb_pixel8;
+ break;
+ case 15:
+ put_pixel_toqemu = put_to_qemufb_pixel15;
+ break;
+ case 16:
+ put_pixel_toqemu = put_to_qemufb_pixel16;
+ break;
+ case 24:
+ put_pixel_toqemu = put_to_qemufb_pixel24;
+ break;
+ case 32:
+ put_pixel_toqemu = put_to_qemufb_pixel32;
+ break;
+ default:
+ hw_error("exynos4210.fimd: unsupported BPP (%d)", bpp);
+ break;
+ }
+}
+
+/* Routine to copy a line from internal frame buffer to QEMU display */
+static void fimd_copy_line_toqemu(int width, uint8_t *src, uint8_t *dst)
+{
+ rgba p;
+
+ do {
+ src += get_pixel_ifb(src, &p);
+ dst += put_pixel_toqemu(p, dst);
+ } while (--width);
+}
+
+/* Parse BPPMODE_F = WINCON1[5:2] bits */
+static void exynos4210_fimd_update_win_bppmode(Exynos4210fimdState *s, int win)
+{
+ Exynos4210fimdWindow *w = &s->window[win];
+
+ if (w->winmap & FIMD_WINMAP_EN) {
+ w->draw_line = draw_line_mapcolor;
+ return;
+ }
+
+ switch (WIN_BPP_MODE(w)) {
+ case 0:
+ w->draw_line = draw_line_palette_1;
+ w->pixel_to_rgb =
+ palette_data_format[exynos4210_fimd_palette_format(s, win)];
+ break;
+ case 1:
+ w->draw_line = draw_line_palette_2;
+ w->pixel_to_rgb =
+ palette_data_format[exynos4210_fimd_palette_format(s, win)];
+ break;
+ case 2:
+ w->draw_line = draw_line_palette_4;
+ w->pixel_to_rgb =
+ palette_data_format[exynos4210_fimd_palette_format(s, win)];
+ break;
+ case 3:
+ w->draw_line = draw_line_palette_8;
+ w->pixel_to_rgb =
+ palette_data_format[exynos4210_fimd_palette_format(s, win)];
+ break;
+ case 4:
+ w->draw_line = draw_line_8;
+ w->pixel_to_rgb = pixel_a232_to_rgb;
+ break;
+ case 5:
+ w->draw_line = draw_line_16;
+ w->pixel_to_rgb = pixel_565_to_rgb;
+ break;
+ case 6:
+ w->draw_line = draw_line_16;
+ w->pixel_to_rgb = pixel_a555_to_rgb;
+ break;
+ case 7:
+ w->draw_line = draw_line_16;
+ w->pixel_to_rgb = pixel_1555_to_rgb;
+ break;
+ case 8:
+ w->draw_line = draw_line_32;
+ w->pixel_to_rgb = pixel_666_to_rgb;
+ break;
+ case 9:
+ w->draw_line = draw_line_32;
+ w->pixel_to_rgb = pixel_a665_to_rgb;
+ break;
+ case 10:
+ w->draw_line = draw_line_32;
+ w->pixel_to_rgb = pixel_a666_to_rgb;
+ break;
+ case 11:
+ w->draw_line = draw_line_32;
+ w->pixel_to_rgb = pixel_888_to_rgb;
+ break;
+ case 12:
+ w->draw_line = draw_line_32;
+ w->pixel_to_rgb = pixel_a887_to_rgb;
+ break;
+ case 13:
+ w->draw_line = draw_line_32;
+ if ((w->wincon & FIMD_WINCON_BLD_PIX) && (w->wincon &
+ FIMD_WINCON_ALPHA_SEL)) {
+ w->pixel_to_rgb = pixel_8888_to_rgb;
+ } else {
+ w->pixel_to_rgb = pixel_a888_to_rgb;
+ }
+ break;
+ case 14:
+ w->draw_line = draw_line_16;
+ if ((w->wincon & FIMD_WINCON_BLD_PIX) && (w->wincon &
+ FIMD_WINCON_ALPHA_SEL)) {
+ w->pixel_to_rgb = pixel_4444_to_rgb;
+ } else {
+ w->pixel_to_rgb = pixel_a444_to_rgb;
+ }
+ break;
+ case 15:
+ w->draw_line = draw_line_16;
+ w->pixel_to_rgb = pixel_555_to_rgb;
+ break;
+ }
+}
+
+#if EXYNOS4210_FIMD_MODE_TRACE > 0
+static const char *exynos4210_fimd_get_bppmode(int mode_code)
+{
+ switch (mode_code) {
+ case 0:
+ return "1 bpp";
+ case 1:
+ return "2 bpp";
+ case 2:
+ return "4 bpp";
+ case 3:
+ return "8 bpp (palettized)";
+ case 4:
+ return "8 bpp (non-palettized, A: 1-R:2-G:3-B:2)";
+ case 5:
+ return "16 bpp (non-palettized, R:5-G:6-B:5)";
+ case 6:
+ return "16 bpp (non-palettized, A:1-R:5-G:5-B:5)";
+ case 7:
+ return "16 bpp (non-palettized, I :1-R:5-G:5-B:5)";
+ case 8:
+ return "Unpacked 18 bpp (non-palettized, R:6-G:6-B:6)";
+ case 9:
+ return "Unpacked 18bpp (non-palettized,A:1-R:6-G:6-B:5)";
+ case 10:
+ return "Unpacked 19bpp (non-palettized,A:1-R:6-G:6-B:6)";
+ case 11:
+ return "Unpacked 24 bpp (non-palettized R:8-G:8-B:8)";
+ case 12:
+ return "Unpacked 24 bpp (non-palettized A:1-R:8-G:8-B:7)";
+ case 13:
+ return "Unpacked 25 bpp (non-palettized A:1-R:8-G:8-B:8)";
+ case 14:
+ return "Unpacked 13 bpp (non-palettized A:1-R:4-G:4-B:4)";
+ case 15:
+ return "Unpacked 15 bpp (non-palettized R:5-G:5-B:5)";
+ default:
+ return "Non-existing bpp mode";
+ }
+}
+
+static inline void exynos4210_fimd_trace_bppmode(Exynos4210fimdState *s,
+ int win_num, uint32_t val)
+{
+ Exynos4210fimdWindow *w = &s->window[win_num];
+
+ if (w->winmap & FIMD_WINMAP_EN) {
+ printf("QEMU FIMD: Window %d is mapped with MAPCOLOR=0x%x\n",
+ win_num, w->winmap & 0xFFFFFF);
+ return;
+ }
+
+ if ((val != 0xFFFFFFFF) && ((w->wincon >> 2) & 0xF) == ((val >> 2) & 0xF)) {
+ return;
+ }
+ printf("QEMU FIMD: Window %d BPP mode set to %s\n", win_num,
+ exynos4210_fimd_get_bppmode((val >> 2) & 0xF));
+}
+#else
+static inline void exynos4210_fimd_trace_bppmode(Exynos4210fimdState *s,
+ int win_num, uint32_t val)
+{
+
+}
+#endif
+
+static inline int fimd_get_buffer_id(Exynos4210fimdWindow *w)
+{
+ switch (w->wincon & FIMD_WINCON_BUFSTATUS) {
+ case FIMD_WINCON_BUF0_STAT:
+ return 0;
+ case FIMD_WINCON_BUF1_STAT:
+ return 1;
+ case FIMD_WINCON_BUF2_STAT:
+ return 2;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "FIMD: Non-existent buffer index\n");
+ return 0;
+ }
+}
+
+static void exynos4210_fimd_invalidate(void *opaque)
+{
+ Exynos4210fimdState *s = (Exynos4210fimdState *)opaque;
+ s->invalidate = true;
+}
+
+/* Updates specified window's MemorySection based on values of WINCON,
+ * VIDOSDA, VIDOSDB, VIDWADDx and SHADOWCON registers */
+static void fimd_update_memory_section(Exynos4210fimdState *s, unsigned win)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(s);
+ Exynos4210fimdWindow *w = &s->window[win];
+ hwaddr fb_start_addr, fb_mapped_len;
+
+ if (!s->enabled || !(w->wincon & FIMD_WINCON_ENWIN) ||
+ FIMD_WINDOW_PROTECTED(s->shadowcon, win)) {
+ return;
+ }
+
+ if (w->host_fb_addr) {
+ cpu_physical_memory_unmap(w->host_fb_addr, w->fb_len, 0, 0);
+ w->host_fb_addr = NULL;
+ w->fb_len = 0;
+ }
+
+ fb_start_addr = w->buf_start[fimd_get_buffer_id(w)];
+ /* Total number of bytes of virtual screen used by current window */
+ w->fb_len = fb_mapped_len = (w->virtpage_width + w->virtpage_offsize) *
+ (w->rightbot_y - w->lefttop_y + 1);
+
+ /* TODO: add .exit and unref the region there. Not needed yet since sysbus
+ * does not support hot-unplug.
+ */
+ if (w->mem_section.mr) {
+ memory_region_set_log(w->mem_section.mr, false, DIRTY_MEMORY_VGA);
+ memory_region_unref(w->mem_section.mr);
+ }
+
+ w->mem_section = memory_region_find(sysbus_address_space(sbd),
+ fb_start_addr, w->fb_len);
+ assert(w->mem_section.mr);
+ assert(w->mem_section.offset_within_address_space == fb_start_addr);
+ DPRINT_TRACE("Window %u framebuffer changed: address=0x%08x, len=0x%x\n",
+ win, fb_start_addr, w->fb_len);
+
+ if (int128_get64(w->mem_section.size) != w->fb_len ||
+ !memory_region_is_ram(w->mem_section.mr)) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "FIMD: Failed to find window %u framebuffer region\n",
+ win);
+ goto error_return;
+ }
+
+ w->host_fb_addr = cpu_physical_memory_map(fb_start_addr, &fb_mapped_len,
+ false);
+ if (!w->host_fb_addr) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "FIMD: Failed to map window %u framebuffer\n", win);
+ goto error_return;
+ }
+
+ if (fb_mapped_len != w->fb_len) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "FIMD: Window %u mapped framebuffer length is less than "
+ "expected\n", win);
+ cpu_physical_memory_unmap(w->host_fb_addr, fb_mapped_len, 0, 0);
+ goto error_return;
+ }
+ memory_region_set_log(w->mem_section.mr, true, DIRTY_MEMORY_VGA);
+ exynos4210_fimd_invalidate(s);
+ return;
+
+error_return:
+ memory_region_unref(w->mem_section.mr);
+ w->mem_section.mr = NULL;
+ w->mem_section.size = int128_zero();
+ w->host_fb_addr = NULL;
+ w->fb_len = 0;
+}
+
+static void exynos4210_fimd_enable(Exynos4210fimdState *s, bool enabled)
+{
+ if (enabled && !s->enabled) {
+ unsigned w;
+ s->enabled = true;
+ for (w = 0; w < NUM_OF_WINDOWS; w++) {
+ fimd_update_memory_section(s, w);
+ }
+ }
+ s->enabled = enabled;
+ DPRINT_TRACE("display controller %s\n", enabled ? "enabled" : "disabled");
+}
+
+static inline uint32_t unpack_upper_4(uint32_t x)
+{
+ return ((x & 0xF00) << 12) | ((x & 0xF0) << 8) | ((x & 0xF) << 4);
+}
+
+static inline uint32_t pack_upper_4(uint32_t x)
+{
+ return (((x & 0xF00000) >> 12) | ((x & 0xF000) >> 8) |
+ ((x & 0xF0) >> 4)) & 0xFFF;
+}
+
+static void exynos4210_fimd_update_irq(Exynos4210fimdState *s)
+{
+ if (!(s->vidintcon[0] & FIMD_VIDINT_INTEN)) {
+ qemu_irq_lower(s->irq[0]);
+ qemu_irq_lower(s->irq[1]);
+ qemu_irq_lower(s->irq[2]);
+ return;
+ }
+ if ((s->vidintcon[0] & FIMD_VIDINT_INTFIFOEN) &&
+ (s->vidintcon[1] & FIMD_VIDINT_INTFIFOPEND)) {
+ qemu_irq_raise(s->irq[0]);
+ } else {
+ qemu_irq_lower(s->irq[0]);
+ }
+ if ((s->vidintcon[0] & FIMD_VIDINT_INTFRMEN) &&
+ (s->vidintcon[1] & FIMD_VIDINT_INTFRMPEND)) {
+ qemu_irq_raise(s->irq[1]);
+ } else {
+ qemu_irq_lower(s->irq[1]);
+ }
+ if ((s->vidintcon[0] & FIMD_VIDINT_I80IFDONE) &&
+ (s->vidintcon[1] & FIMD_VIDINT_INTI80PEND)) {
+ qemu_irq_raise(s->irq[2]);
+ } else {
+ qemu_irq_lower(s->irq[2]);
+ }
+}
+
+static void exynos4210_update_resolution(Exynos4210fimdState *s)
+{
+ DisplaySurface *surface = qemu_console_surface(s->console);
+
+ /* LCD resolution is stored in VIDEO TIME CONTROL REGISTER 2 */
+ uint32_t width = ((s->vidtcon[2] >> FIMD_VIDTCON2_HOR_SHIFT) &
+ FIMD_VIDTCON2_SIZE_MASK) + 1;
+ uint32_t height = ((s->vidtcon[2] >> FIMD_VIDTCON2_VER_SHIFT) &
+ FIMD_VIDTCON2_SIZE_MASK) + 1;
+
+ if (s->ifb == NULL || surface_width(surface) != width ||
+ surface_height(surface) != height) {
+ DPRINT_L1("Resolution changed from %ux%u to %ux%u\n",
+ surface_width(surface), surface_height(surface), width, height);
+ qemu_console_resize(s->console, width, height);
+ s->ifb = g_realloc(s->ifb, width * height * RGBA_SIZE + 1);
+ memset(s->ifb, 0, width * height * RGBA_SIZE + 1);
+ exynos4210_fimd_invalidate(s);
+ }
+}
+
+static void exynos4210_fimd_update(void *opaque)
+{
+ Exynos4210fimdState *s = (Exynos4210fimdState *)opaque;
+ DisplaySurface *surface;
+ Exynos4210fimdWindow *w;
+ DirtyBitmapSnapshot *snap;
+ int i, line;
+ hwaddr fb_line_addr, inc_size;
+ int scrn_height;
+ int first_line = -1, last_line = -1, scrn_width;
+ bool blend = false;
+ uint8_t *host_fb_addr;
+ bool is_dirty = false;
+ int global_width;
+
+ if (!s || !s->console || !s->enabled ||
+ surface_bits_per_pixel(qemu_console_surface(s->console)) == 0) {
+ return;
+ }
+
+ global_width = (s->vidtcon[2] & FIMD_VIDTCON2_SIZE_MASK) + 1;
+ exynos4210_update_resolution(s);
+ surface = qemu_console_surface(s->console);
+
+ for (i = 0; i < NUM_OF_WINDOWS; i++) {
+ w = &s->window[i];
+ if ((w->wincon & FIMD_WINCON_ENWIN) && w->host_fb_addr) {
+ scrn_height = w->rightbot_y - w->lefttop_y + 1;
+ scrn_width = w->virtpage_width;
+ /* Total width of virtual screen page in bytes */
+ inc_size = scrn_width + w->virtpage_offsize;
+ host_fb_addr = w->host_fb_addr;
+ fb_line_addr = w->mem_section.offset_within_region;
+ snap = memory_region_snapshot_and_clear_dirty(w->mem_section.mr,
+ fb_line_addr, inc_size * scrn_height, DIRTY_MEMORY_VGA);
+
+ for (line = 0; line < scrn_height; line++) {
+ is_dirty = memory_region_snapshot_get_dirty(w->mem_section.mr,
+ snap, fb_line_addr, scrn_width);
+
+ if (s->invalidate || is_dirty) {
+ if (first_line == -1) {
+ first_line = line;
+ }
+ last_line = line;
+ w->draw_line(w, host_fb_addr, s->ifb +
+ w->lefttop_x * RGBA_SIZE + (w->lefttop_y + line) *
+ global_width * RGBA_SIZE, blend);
+ }
+ host_fb_addr += inc_size;
+ fb_line_addr += inc_size;
+ }
+ g_free(snap);
+ blend = true;
+ }
+ }
+
+ /* Copy resulting image to QEMU_CONSOLE. */
+ if (first_line >= 0) {
+ uint8_t *d;
+ int bpp;
+
+ bpp = surface_bits_per_pixel(surface);
+ fimd_update_putpix_qemu(bpp);
+ bpp = (bpp + 1) >> 3;
+ d = surface_data(surface);
+ for (line = first_line; line <= last_line; line++) {
+ fimd_copy_line_toqemu(global_width, s->ifb + global_width * line *
+ RGBA_SIZE, d + global_width * line * bpp);
+ }
+ dpy_gfx_update_full(s->console);
+ }
+ s->invalidate = false;
+ s->vidintcon[1] |= FIMD_VIDINT_INTFRMPEND;
+ if ((s->vidcon[0] & FIMD_VIDCON0_ENVID_F) == 0) {
+ exynos4210_fimd_enable(s, false);
+ }
+ exynos4210_fimd_update_irq(s);
+}
+
+static void exynos4210_fimd_reset(DeviceState *d)
+{
+ Exynos4210fimdState *s = EXYNOS4210_FIMD(d);
+ unsigned w;
+
+ DPRINT_TRACE("Display controller reset\n");
+ /* Set all display controller registers to 0 */
+ memset(&s->vidcon, 0, (uint8_t *)&s->window - (uint8_t *)&s->vidcon);
+ for (w = 0; w < NUM_OF_WINDOWS; w++) {
+ memset(&s->window[w], 0, sizeof(Exynos4210fimdWindow));
+ s->window[w].blendeq = 0xC2;
+ exynos4210_fimd_update_win_bppmode(s, w);
+ exynos4210_fimd_trace_bppmode(s, w, 0xFFFFFFFF);
+ fimd_update_get_alpha(s, w);
+ }
+
+ g_free(s->ifb);
+ s->ifb = NULL;
+
+ exynos4210_fimd_invalidate(s);
+ exynos4210_fimd_enable(s, false);
+ /* Some registers have non-zero initial values */
+ s->winchmap = 0x7D517D51;
+ s->colorgaincon = 0x10040100;
+ s->huecoef_cr[0] = s->huecoef_cr[3] = 0x01000100;
+ s->huecoef_cb[0] = s->huecoef_cb[3] = 0x01000100;
+ s->hueoffset = 0x01800080;
+}
+
+static void exynos4210_fimd_write(void *opaque, hwaddr offset,
+ uint64_t val, unsigned size)
+{
+ Exynos4210fimdState *s = (Exynos4210fimdState *)opaque;
+ unsigned w, i;
+ uint32_t old_value;
+
+ DPRINT_L2("write offset 0x%08x, value=%llu(0x%08llx)\n", offset,
+ (long long unsigned int)val, (long long unsigned int)val);
+
+ switch (offset) {
+ case FIMD_VIDCON0:
+ if ((val & FIMD_VIDCON0_ENVID_MASK) == FIMD_VIDCON0_ENVID_MASK) {
+ exynos4210_fimd_enable(s, true);
+ } else {
+ if ((val & FIMD_VIDCON0_ENVID) == 0) {
+ exynos4210_fimd_enable(s, false);
+ }
+ }
+ s->vidcon[0] = val;
+ break;
+ case FIMD_VIDCON1:
+ /* Leave read-only bits as is */
+ val = (val & (~FIMD_VIDCON1_ROMASK)) |
+ (s->vidcon[1] & FIMD_VIDCON1_ROMASK);
+ s->vidcon[1] = val;
+ break;
+ case FIMD_VIDCON2 ... FIMD_VIDCON3:
+ s->vidcon[(offset) >> 2] = val;
+ break;
+ case FIMD_VIDTCON_START ... FIMD_VIDTCON_END:
+ s->vidtcon[(offset - FIMD_VIDTCON_START) >> 2] = val;
+ break;
+ case FIMD_WINCON_START ... FIMD_WINCON_END:
+ w = (offset - FIMD_WINCON_START) >> 2;
+ /* Window's current buffer ID */
+ i = fimd_get_buffer_id(&s->window[w]);
+ old_value = s->window[w].wincon;
+ val = (val & ~FIMD_WINCON_ROMASK) |
+ (s->window[w].wincon & FIMD_WINCON_ROMASK);
+ if (w == 0) {
+ /* Window 0 wincon ALPHA_MUL bit must always be 0 */
+ val &= ~FIMD_WINCON_ALPHA_MUL;
+ }
+ exynos4210_fimd_trace_bppmode(s, w, val);
+ switch (val & FIMD_WINCON_BUFSELECT) {
+ case FIMD_WINCON_BUF0_SEL:
+ val &= ~FIMD_WINCON_BUFSTATUS;
+ break;
+ case FIMD_WINCON_BUF1_SEL:
+ val = (val & ~FIMD_WINCON_BUFSTAT_H) | FIMD_WINCON_BUFSTAT_L;
+ break;
+ case FIMD_WINCON_BUF2_SEL:
+ if (val & FIMD_WINCON_BUFMODE) {
+ val = (val & ~FIMD_WINCON_BUFSTAT_L) | FIMD_WINCON_BUFSTAT_H;
+ }
+ break;
+ default:
+ break;
+ }
+ s->window[w].wincon = val;
+ exynos4210_fimd_update_win_bppmode(s, w);
+ fimd_update_get_alpha(s, w);
+ if ((i != fimd_get_buffer_id(&s->window[w])) ||
+ (!(old_value & FIMD_WINCON_ENWIN) && (s->window[w].wincon &
+ FIMD_WINCON_ENWIN))) {
+ fimd_update_memory_section(s, w);
+ }
+ break;
+ case FIMD_SHADOWCON:
+ old_value = s->shadowcon;
+ s->shadowcon = val;
+ for (w = 0; w < NUM_OF_WINDOWS; w++) {
+ if (FIMD_WINDOW_PROTECTED(old_value, w) &&
+ !FIMD_WINDOW_PROTECTED(s->shadowcon, w)) {
+ fimd_update_memory_section(s, w);
+ }
+ }
+ break;
+ case FIMD_WINCHMAP:
+ s->winchmap = val;
+ break;
+ case FIMD_VIDOSD_START ... FIMD_VIDOSD_END:
+ w = (offset - FIMD_VIDOSD_START) >> 4;
+ i = ((offset - FIMD_VIDOSD_START) & 0xF) >> 2;
+ switch (i) {
+ case 0:
+ old_value = s->window[w].lefttop_y;
+ s->window[w].lefttop_x = (val >> FIMD_VIDOSD_HOR_SHIFT) &
+ FIMD_VIDOSD_COORD_MASK;
+ s->window[w].lefttop_y = (val >> FIMD_VIDOSD_VER_SHIFT) &
+ FIMD_VIDOSD_COORD_MASK;
+ if (s->window[w].lefttop_y != old_value) {
+ fimd_update_memory_section(s, w);
+ }
+ break;
+ case 1:
+ old_value = s->window[w].rightbot_y;
+ s->window[w].rightbot_x = (val >> FIMD_VIDOSD_HOR_SHIFT) &
+ FIMD_VIDOSD_COORD_MASK;
+ s->window[w].rightbot_y = (val >> FIMD_VIDOSD_VER_SHIFT) &
+ FIMD_VIDOSD_COORD_MASK;
+ if (s->window[w].rightbot_y != old_value) {
+ fimd_update_memory_section(s, w);
+ }
+ break;
+ case 2:
+ if (w == 0) {
+ s->window[w].osdsize = val;
+ } else {
+ s->window[w].alpha_val[0] =
+ unpack_upper_4((val & FIMD_VIDOSD_ALPHA_AEN0) >>
+ FIMD_VIDOSD_AEN0_SHIFT) |
+ (s->window[w].alpha_val[0] & FIMD_VIDALPHA_ALPHA_LOWER);
+ s->window[w].alpha_val[1] =
+ unpack_upper_4(val & FIMD_VIDOSD_ALPHA_AEN1) |
+ (s->window[w].alpha_val[1] & FIMD_VIDALPHA_ALPHA_LOWER);
+ }
+ break;
+ case 3:
+ if (w != 1 && w != 2) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "FIMD: Bad write offset 0x%08"HWADDR_PRIx"\n",
+ offset);
+ return;
+ }
+ s->window[w].osdsize = val;
+ break;
+ }
+ break;
+ case FIMD_VIDWADD0_START ... FIMD_VIDWADD0_END:
+ w = (offset - FIMD_VIDWADD0_START) >> 3;
+ i = ((offset - FIMD_VIDWADD0_START) >> 2) & 1;
+ if (i == fimd_get_buffer_id(&s->window[w]) &&
+ s->window[w].buf_start[i] != val) {
+ s->window[w].buf_start[i] = val;
+ fimd_update_memory_section(s, w);
+ break;
+ }
+ s->window[w].buf_start[i] = val;
+ break;
+ case FIMD_VIDWADD1_START ... FIMD_VIDWADD1_END:
+ w = (offset - FIMD_VIDWADD1_START) >> 3;
+ i = ((offset - FIMD_VIDWADD1_START) >> 2) & 1;
+ s->window[w].buf_end[i] = val;
+ break;
+ case FIMD_VIDWADD2_START ... FIMD_VIDWADD2_END:
+ w = (offset - FIMD_VIDWADD2_START) >> 2;
+ if (((val & FIMD_VIDWADD2_PAGEWIDTH) != s->window[w].virtpage_width) ||
+ (((val >> FIMD_VIDWADD2_OFFSIZE_SHIFT) & FIMD_VIDWADD2_OFFSIZE) !=
+ s->window[w].virtpage_offsize)) {
+ s->window[w].virtpage_width = val & FIMD_VIDWADD2_PAGEWIDTH;
+ s->window[w].virtpage_offsize =
+ (val >> FIMD_VIDWADD2_OFFSIZE_SHIFT) & FIMD_VIDWADD2_OFFSIZE;
+ fimd_update_memory_section(s, w);
+ }
+ break;
+ case FIMD_VIDINTCON0:
+ s->vidintcon[0] = val;
+ break;
+ case FIMD_VIDINTCON1:
+ s->vidintcon[1] &= ~(val & 7);
+ exynos4210_fimd_update_irq(s);
+ break;
+ case FIMD_WKEYCON_START ... FIMD_WKEYCON_END:
+ w = ((offset - FIMD_WKEYCON_START) >> 3) + 1;
+ i = ((offset - FIMD_WKEYCON_START) >> 2) & 1;
+ s->window[w].keycon[i] = val;
+ break;
+ case FIMD_WKEYALPHA_START ... FIMD_WKEYALPHA_END:
+ w = ((offset - FIMD_WKEYALPHA_START) >> 2) + 1;
+ s->window[w].keyalpha = val;
+ break;
+ case FIMD_DITHMODE:
+ s->dithmode = val;
+ break;
+ case FIMD_WINMAP_START ... FIMD_WINMAP_END:
+ w = (offset - FIMD_WINMAP_START) >> 2;
+ old_value = s->window[w].winmap;
+ s->window[w].winmap = val;
+ if ((val & FIMD_WINMAP_EN) ^ (old_value & FIMD_WINMAP_EN)) {
+ exynos4210_fimd_invalidate(s);
+ exynos4210_fimd_update_win_bppmode(s, w);
+ exynos4210_fimd_trace_bppmode(s, w, 0xFFFFFFFF);
+ exynos4210_fimd_update(s);
+ }
+ break;
+ case FIMD_WPALCON_HIGH ... FIMD_WPALCON_LOW:
+ i = (offset - FIMD_WPALCON_HIGH) >> 2;
+ s->wpalcon[i] = val;
+ if (s->wpalcon[1] & FIMD_WPALCON_UPDATEEN) {
+ for (w = 0; w < NUM_OF_WINDOWS; w++) {
+ exynos4210_fimd_update_win_bppmode(s, w);
+ fimd_update_get_alpha(s, w);
+ }
+ }
+ break;
+ case FIMD_TRIGCON:
+ val = (val & ~FIMD_TRIGCON_ROMASK) | (s->trigcon & FIMD_TRIGCON_ROMASK);
+ s->trigcon = val;
+ break;
+ case FIMD_I80IFCON_START ... FIMD_I80IFCON_END:
+ s->i80ifcon[(offset - FIMD_I80IFCON_START) >> 2] = val;
+ break;
+ case FIMD_COLORGAINCON:
+ s->colorgaincon = val;
+ break;
+ case FIMD_LDI_CMDCON0 ... FIMD_LDI_CMDCON1:
+ s->ldi_cmdcon[(offset - FIMD_LDI_CMDCON0) >> 2] = val;
+ break;
+ case FIMD_SIFCCON0 ... FIMD_SIFCCON2:
+ i = (offset - FIMD_SIFCCON0) >> 2;
+ if (i != 2) {
+ s->sifccon[i] = val;
+ }
+ break;
+ case FIMD_HUECOEFCR_START ... FIMD_HUECOEFCR_END:
+ i = (offset - FIMD_HUECOEFCR_START) >> 2;
+ s->huecoef_cr[i] = val;
+ break;
+ case FIMD_HUECOEFCB_START ... FIMD_HUECOEFCB_END:
+ i = (offset - FIMD_HUECOEFCB_START) >> 2;
+ s->huecoef_cb[i] = val;
+ break;
+ case FIMD_HUEOFFSET:
+ s->hueoffset = val;
+ break;
+ case FIMD_VIDWALPHA_START ... FIMD_VIDWALPHA_END:
+ w = ((offset - FIMD_VIDWALPHA_START) >> 3);
+ i = ((offset - FIMD_VIDWALPHA_START) >> 2) & 1;
+ if (w == 0) {
+ s->window[w].alpha_val[i] = val;
+ } else {
+ s->window[w].alpha_val[i] = (val & FIMD_VIDALPHA_ALPHA_LOWER) |
+ (s->window[w].alpha_val[i] & FIMD_VIDALPHA_ALPHA_UPPER);
+ }
+ break;
+ case FIMD_BLENDEQ_START ... FIMD_BLENDEQ_END:
+ s->window[(offset - FIMD_BLENDEQ_START) >> 2].blendeq = val;
+ break;
+ case FIMD_BLENDCON:
+ old_value = s->blendcon;
+ s->blendcon = val;
+ if ((s->blendcon & FIMD_ALPHA_8BIT) != (old_value & FIMD_ALPHA_8BIT)) {
+ for (w = 0; w < NUM_OF_WINDOWS; w++) {
+ fimd_update_get_alpha(s, w);
+ }
+ }
+ break;
+ case FIMD_WRTQOSCON_START ... FIMD_WRTQOSCON_END:
+ s->window[(offset - FIMD_WRTQOSCON_START) >> 2].rtqoscon = val;
+ break;
+ case FIMD_I80IFCMD_START ... FIMD_I80IFCMD_END:
+ s->i80ifcmd[(offset - FIMD_I80IFCMD_START) >> 2] = val;
+ break;
+ case FIMD_VIDW0ADD0_B2 ... FIMD_VIDW4ADD0_B2:
+ if (offset & 0x0004) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "FIMD: bad write offset 0x%08"HWADDR_PRIx"\n",
+ offset);
+ break;
+ }
+ w = (offset - FIMD_VIDW0ADD0_B2) >> 3;
+ if (fimd_get_buffer_id(&s->window[w]) == 2 &&
+ s->window[w].buf_start[2] != val) {
+ s->window[w].buf_start[2] = val;
+ fimd_update_memory_section(s, w);
+ break;
+ }
+ s->window[w].buf_start[2] = val;
+ break;
+ case FIMD_SHD_ADD0_START ... FIMD_SHD_ADD0_END:
+ if (offset & 0x0004) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "FIMD: bad write offset 0x%08"HWADDR_PRIx"\n",
+ offset);
+ break;
+ }
+ s->window[(offset - FIMD_SHD_ADD0_START) >> 3].shadow_buf_start = val;
+ break;
+ case FIMD_SHD_ADD1_START ... FIMD_SHD_ADD1_END:
+ if (offset & 0x0004) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "FIMD: bad write offset 0x%08"HWADDR_PRIx"\n",
+ offset);
+ break;
+ }
+ s->window[(offset - FIMD_SHD_ADD1_START) >> 3].shadow_buf_end = val;
+ break;
+ case FIMD_SHD_ADD2_START ... FIMD_SHD_ADD2_END:
+ s->window[(offset - FIMD_SHD_ADD2_START) >> 2].shadow_buf_size = val;
+ break;
+ case FIMD_PAL_MEM_START ... FIMD_PAL_MEM_END:
+ w = (offset - FIMD_PAL_MEM_START) >> 10;
+ i = ((offset - FIMD_PAL_MEM_START) >> 2) & 0xFF;
+ s->window[w].palette[i] = val;
+ break;
+ case FIMD_PALMEM_AL_START ... FIMD_PALMEM_AL_END:
+ /* Palette memory aliases for windows 0 and 1 */
+ w = (offset - FIMD_PALMEM_AL_START) >> 10;
+ i = ((offset - FIMD_PALMEM_AL_START) >> 2) & 0xFF;
+ s->window[w].palette[i] = val;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "FIMD: bad write offset 0x%08"HWADDR_PRIx"\n", offset);
+ break;
+ }
+}
+
+static uint64_t exynos4210_fimd_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ Exynos4210fimdState *s = (Exynos4210fimdState *)opaque;
+ int w, i;
+ uint32_t ret = 0;
+
+ DPRINT_L2("read offset 0x%08x\n", offset);
+
+ switch (offset) {
+ case FIMD_VIDCON0 ... FIMD_VIDCON3:
+ return s->vidcon[(offset - FIMD_VIDCON0) >> 2];
+ case FIMD_VIDTCON_START ... FIMD_VIDTCON_END:
+ return s->vidtcon[(offset - FIMD_VIDTCON_START) >> 2];
+ case FIMD_WINCON_START ... FIMD_WINCON_END:
+ return s->window[(offset - FIMD_WINCON_START) >> 2].wincon;
+ case FIMD_SHADOWCON:
+ return s->shadowcon;
+ case FIMD_WINCHMAP:
+ return s->winchmap;
+ case FIMD_VIDOSD_START ... FIMD_VIDOSD_END:
+ w = (offset - FIMD_VIDOSD_START) >> 4;
+ i = ((offset - FIMD_VIDOSD_START) & 0xF) >> 2;
+ switch (i) {
+ case 0:
+ ret = ((s->window[w].lefttop_x & FIMD_VIDOSD_COORD_MASK) <<
+ FIMD_VIDOSD_HOR_SHIFT) |
+ (s->window[w].lefttop_y & FIMD_VIDOSD_COORD_MASK);
+ break;
+ case 1:
+ ret = ((s->window[w].rightbot_x & FIMD_VIDOSD_COORD_MASK) <<
+ FIMD_VIDOSD_HOR_SHIFT) |
+ (s->window[w].rightbot_y & FIMD_VIDOSD_COORD_MASK);
+ break;
+ case 2:
+ if (w == 0) {
+ ret = s->window[w].osdsize;
+ } else {
+ ret = (pack_upper_4(s->window[w].alpha_val[0]) <<
+ FIMD_VIDOSD_AEN0_SHIFT) |
+ pack_upper_4(s->window[w].alpha_val[1]);
+ }
+ break;
+ case 3:
+ if (w != 1 && w != 2) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "FIMD: bad read offset 0x%08"HWADDR_PRIx"\n",
+ offset);
+ return 0xBAADBAAD;
+ }
+ ret = s->window[w].osdsize;
+ break;
+ }
+ return ret;
+ case FIMD_VIDWADD0_START ... FIMD_VIDWADD0_END:
+ w = (offset - FIMD_VIDWADD0_START) >> 3;
+ i = ((offset - FIMD_VIDWADD0_START) >> 2) & 1;
+ return s->window[w].buf_start[i];
+ case FIMD_VIDWADD1_START ... FIMD_VIDWADD1_END:
+ w = (offset - FIMD_VIDWADD1_START) >> 3;
+ i = ((offset - FIMD_VIDWADD1_START) >> 2) & 1;
+ return s->window[w].buf_end[i];
+ case FIMD_VIDWADD2_START ... FIMD_VIDWADD2_END:
+ w = (offset - FIMD_VIDWADD2_START) >> 2;
+ return s->window[w].virtpage_width | (s->window[w].virtpage_offsize <<
+ FIMD_VIDWADD2_OFFSIZE_SHIFT);
+ case FIMD_VIDINTCON0 ... FIMD_VIDINTCON1:
+ return s->vidintcon[(offset - FIMD_VIDINTCON0) >> 2];
+ case FIMD_WKEYCON_START ... FIMD_WKEYCON_END:
+ w = ((offset - FIMD_WKEYCON_START) >> 3) + 1;
+ i = ((offset - FIMD_WKEYCON_START) >> 2) & 1;
+ return s->window[w].keycon[i];
+ case FIMD_WKEYALPHA_START ... FIMD_WKEYALPHA_END:
+ w = ((offset - FIMD_WKEYALPHA_START) >> 2) + 1;
+ return s->window[w].keyalpha;
+ case FIMD_DITHMODE:
+ return s->dithmode;
+ case FIMD_WINMAP_START ... FIMD_WINMAP_END:
+ return s->window[(offset - FIMD_WINMAP_START) >> 2].winmap;
+ case FIMD_WPALCON_HIGH ... FIMD_WPALCON_LOW:
+ return s->wpalcon[(offset - FIMD_WPALCON_HIGH) >> 2];
+ case FIMD_TRIGCON:
+ return s->trigcon;
+ case FIMD_I80IFCON_START ... FIMD_I80IFCON_END:
+ return s->i80ifcon[(offset - FIMD_I80IFCON_START) >> 2];
+ case FIMD_COLORGAINCON:
+ return s->colorgaincon;
+ case FIMD_LDI_CMDCON0 ... FIMD_LDI_CMDCON1:
+ return s->ldi_cmdcon[(offset - FIMD_LDI_CMDCON0) >> 2];
+ case FIMD_SIFCCON0 ... FIMD_SIFCCON2:
+ i = (offset - FIMD_SIFCCON0) >> 2;
+ return s->sifccon[i];
+ case FIMD_HUECOEFCR_START ... FIMD_HUECOEFCR_END:
+ i = (offset - FIMD_HUECOEFCR_START) >> 2;
+ return s->huecoef_cr[i];
+ case FIMD_HUECOEFCB_START ... FIMD_HUECOEFCB_END:
+ i = (offset - FIMD_HUECOEFCB_START) >> 2;
+ return s->huecoef_cb[i];
+ case FIMD_HUEOFFSET:
+ return s->hueoffset;
+ case FIMD_VIDWALPHA_START ... FIMD_VIDWALPHA_END:
+ w = ((offset - FIMD_VIDWALPHA_START) >> 3);
+ i = ((offset - FIMD_VIDWALPHA_START) >> 2) & 1;
+ return s->window[w].alpha_val[i] &
+ (w == 0 ? 0xFFFFFF : FIMD_VIDALPHA_ALPHA_LOWER);
+ case FIMD_BLENDEQ_START ... FIMD_BLENDEQ_END:
+ return s->window[(offset - FIMD_BLENDEQ_START) >> 2].blendeq;
+ case FIMD_BLENDCON:
+ return s->blendcon;
+ case FIMD_WRTQOSCON_START ... FIMD_WRTQOSCON_END:
+ return s->window[(offset - FIMD_WRTQOSCON_START) >> 2].rtqoscon;
+ case FIMD_I80IFCMD_START ... FIMD_I80IFCMD_END:
+ return s->i80ifcmd[(offset - FIMD_I80IFCMD_START) >> 2];
+ case FIMD_VIDW0ADD0_B2 ... FIMD_VIDW4ADD0_B2:
+ if (offset & 0x0004) {
+ break;
+ }
+ return s->window[(offset - FIMD_VIDW0ADD0_B2) >> 3].buf_start[2];
+ case FIMD_SHD_ADD0_START ... FIMD_SHD_ADD0_END:
+ if (offset & 0x0004) {
+ break;
+ }
+ return s->window[(offset - FIMD_SHD_ADD0_START) >> 3].shadow_buf_start;
+ case FIMD_SHD_ADD1_START ... FIMD_SHD_ADD1_END:
+ if (offset & 0x0004) {
+ break;
+ }
+ return s->window[(offset - FIMD_SHD_ADD1_START) >> 3].shadow_buf_end;
+ case FIMD_SHD_ADD2_START ... FIMD_SHD_ADD2_END:
+ return s->window[(offset - FIMD_SHD_ADD2_START) >> 2].shadow_buf_size;
+ case FIMD_PAL_MEM_START ... FIMD_PAL_MEM_END:
+ w = (offset - FIMD_PAL_MEM_START) >> 10;
+ i = ((offset - FIMD_PAL_MEM_START) >> 2) & 0xFF;
+ return s->window[w].palette[i];
+ case FIMD_PALMEM_AL_START ... FIMD_PALMEM_AL_END:
+ /* Palette aliases for win 0,1 */
+ w = (offset - FIMD_PALMEM_AL_START) >> 10;
+ i = ((offset - FIMD_PALMEM_AL_START) >> 2) & 0xFF;
+ return s->window[w].palette[i];
+ }
+
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "FIMD: bad read offset 0x%08"HWADDR_PRIx"\n", offset);
+ return 0xBAADBAAD;
+}
+
+static const MemoryRegionOps exynos4210_fimd_mmio_ops = {
+ .read = exynos4210_fimd_read,
+ .write = exynos4210_fimd_write,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ .unaligned = false
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int exynos4210_fimd_load(void *opaque, int version_id)
+{
+ Exynos4210fimdState *s = (Exynos4210fimdState *)opaque;
+ int w;
+
+ if (version_id != 1) {
+ return -EINVAL;
+ }
+
+ for (w = 0; w < NUM_OF_WINDOWS; w++) {
+ exynos4210_fimd_update_win_bppmode(s, w);
+ fimd_update_get_alpha(s, w);
+ fimd_update_memory_section(s, w);
+ }
+
+ /* Redraw the whole screen */
+ exynos4210_update_resolution(s);
+ exynos4210_fimd_invalidate(s);
+ exynos4210_fimd_enable(s, (s->vidcon[0] & FIMD_VIDCON0_ENVID_MASK) ==
+ FIMD_VIDCON0_ENVID_MASK);
+ return 0;
+}
+
+static const VMStateDescription exynos4210_fimd_window_vmstate = {
+ .name = "exynos4210.fimd_window",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(wincon, Exynos4210fimdWindow),
+ VMSTATE_UINT32_ARRAY(buf_start, Exynos4210fimdWindow, 3),
+ VMSTATE_UINT32_ARRAY(buf_end, Exynos4210fimdWindow, 3),
+ VMSTATE_UINT32_ARRAY(keycon, Exynos4210fimdWindow, 2),
+ VMSTATE_UINT32(keyalpha, Exynos4210fimdWindow),
+ VMSTATE_UINT32(winmap, Exynos4210fimdWindow),
+ VMSTATE_UINT32(blendeq, Exynos4210fimdWindow),
+ VMSTATE_UINT32(rtqoscon, Exynos4210fimdWindow),
+ VMSTATE_UINT32_ARRAY(palette, Exynos4210fimdWindow, 256),
+ VMSTATE_UINT32(shadow_buf_start, Exynos4210fimdWindow),
+ VMSTATE_UINT32(shadow_buf_end, Exynos4210fimdWindow),
+ VMSTATE_UINT32(shadow_buf_size, Exynos4210fimdWindow),
+ VMSTATE_UINT16(lefttop_x, Exynos4210fimdWindow),
+ VMSTATE_UINT16(lefttop_y, Exynos4210fimdWindow),
+ VMSTATE_UINT16(rightbot_x, Exynos4210fimdWindow),
+ VMSTATE_UINT16(rightbot_y, Exynos4210fimdWindow),
+ VMSTATE_UINT32(osdsize, Exynos4210fimdWindow),
+ VMSTATE_UINT32_ARRAY(alpha_val, Exynos4210fimdWindow, 2),
+ VMSTATE_UINT16(virtpage_width, Exynos4210fimdWindow),
+ VMSTATE_UINT16(virtpage_offsize, Exynos4210fimdWindow),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription exynos4210_fimd_vmstate = {
+ .name = "exynos4210.fimd",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = exynos4210_fimd_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(vidcon, Exynos4210fimdState, 4),
+ VMSTATE_UINT32_ARRAY(vidtcon, Exynos4210fimdState, 4),
+ VMSTATE_UINT32(shadowcon, Exynos4210fimdState),
+ VMSTATE_UINT32(winchmap, Exynos4210fimdState),
+ VMSTATE_UINT32_ARRAY(vidintcon, Exynos4210fimdState, 2),
+ VMSTATE_UINT32(dithmode, Exynos4210fimdState),
+ VMSTATE_UINT32_ARRAY(wpalcon, Exynos4210fimdState, 2),
+ VMSTATE_UINT32(trigcon, Exynos4210fimdState),
+ VMSTATE_UINT32_ARRAY(i80ifcon, Exynos4210fimdState, 4),
+ VMSTATE_UINT32(colorgaincon, Exynos4210fimdState),
+ VMSTATE_UINT32_ARRAY(ldi_cmdcon, Exynos4210fimdState, 2),
+ VMSTATE_UINT32_ARRAY(sifccon, Exynos4210fimdState, 3),
+ VMSTATE_UINT32_ARRAY(huecoef_cr, Exynos4210fimdState, 4),
+ VMSTATE_UINT32_ARRAY(huecoef_cb, Exynos4210fimdState, 4),
+ VMSTATE_UINT32(hueoffset, Exynos4210fimdState),
+ VMSTATE_UINT32_ARRAY(i80ifcmd, Exynos4210fimdState, 12),
+ VMSTATE_UINT32(blendcon, Exynos4210fimdState),
+ VMSTATE_STRUCT_ARRAY(window, Exynos4210fimdState, 5, 1,
+ exynos4210_fimd_window_vmstate, Exynos4210fimdWindow),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const GraphicHwOps exynos4210_fimd_ops = {
+ .invalidate = exynos4210_fimd_invalidate,
+ .gfx_update = exynos4210_fimd_update,
+};
+
+static void exynos4210_fimd_init(Object *obj)
+{
+ Exynos4210fimdState *s = EXYNOS4210_FIMD(obj);
+ SysBusDevice *dev = SYS_BUS_DEVICE(obj);
+
+ s->ifb = NULL;
+
+ sysbus_init_irq(dev, &s->irq[0]);
+ sysbus_init_irq(dev, &s->irq[1]);
+ sysbus_init_irq(dev, &s->irq[2]);
+
+ memory_region_init_io(&s->iomem, obj, &exynos4210_fimd_mmio_ops, s,
+ "exynos4210.fimd", FIMD_REGS_SIZE);
+ sysbus_init_mmio(dev, &s->iomem);
+}
+
+static void exynos4210_fimd_realize(DeviceState *dev, Error **errp)
+{
+ Exynos4210fimdState *s = EXYNOS4210_FIMD(dev);
+
+ s->console = graphic_console_init(dev, 0, &exynos4210_fimd_ops, s);
+}
+
+static void exynos4210_fimd_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->vmsd = &exynos4210_fimd_vmstate;
+ dc->reset = exynos4210_fimd_reset;
+ dc->realize = exynos4210_fimd_realize;
+}
+
+static const TypeInfo exynos4210_fimd_info = {
+ .name = TYPE_EXYNOS4210_FIMD,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(Exynos4210fimdState),
+ .instance_init = exynos4210_fimd_init,
+ .class_init = exynos4210_fimd_class_init,
+};
+
+static void exynos4210_fimd_register_types(void)
+{
+ type_register_static(&exynos4210_fimd_info);
+}
+
+type_init(exynos4210_fimd_register_types)
diff --git a/hw/display/framebuffer.c b/hw/display/framebuffer.c
new file mode 100644
index 000000000..4485aa335
--- /dev/null
+++ b/hw/display/framebuffer.c
@@ -0,0 +1,122 @@
+/*
+ * Framebuffer device helper routines
+ *
+ * Copyright (c) 2009 CodeSourcery
+ * Written by Paul Brook <paul@codesourcery.com>
+ *
+ * This code is licensed under the GNU GPLv2.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+/* TODO:
+ - Do something similar for framebuffers with local ram
+ - Handle rotation here instead of hacking dest_pitch
+ - Use common pixel conversion routines instead of per-device drawfn
+ - Remove all DisplayState knowledge from devices.
+ */
+
+#include "qemu/osdep.h"
+#include "ui/console.h"
+#include "framebuffer.h"
+
+void framebuffer_update_memory_section(
+ MemoryRegionSection *mem_section,
+ MemoryRegion *root,
+ hwaddr base,
+ unsigned rows,
+ unsigned src_width)
+{
+ hwaddr src_len = (hwaddr)rows * src_width;
+
+ if (mem_section->mr) {
+ memory_region_set_log(mem_section->mr, false, DIRTY_MEMORY_VGA);
+ memory_region_unref(mem_section->mr);
+ mem_section->mr = NULL;
+ }
+
+ *mem_section = memory_region_find(root, base, src_len);
+ if (!mem_section->mr) {
+ return;
+ }
+
+ if (int128_get64(mem_section->size) < src_len ||
+ !memory_region_is_ram(mem_section->mr)) {
+ memory_region_unref(mem_section->mr);
+ mem_section->mr = NULL;
+ return;
+ }
+
+ memory_region_set_log(mem_section->mr, true, DIRTY_MEMORY_VGA);
+}
+
+/* Render an image from a shared memory framebuffer. */
+void framebuffer_update_display(
+ DisplaySurface *ds,
+ MemoryRegionSection *mem_section,
+ int cols, /* Width in pixels. */
+ int rows, /* Height in pixels. */
+ int src_width, /* Length of source line, in bytes. */
+ int dest_row_pitch, /* Bytes between adjacent horizontal output pixels. */
+ int dest_col_pitch, /* Bytes between adjacent vertical output pixels. */
+ int invalidate, /* nonzero to redraw the whole image. */
+ drawfn fn,
+ void *opaque,
+ int *first_row, /* Input and output. */
+ int *last_row /* Output only */)
+{
+ DirtyBitmapSnapshot *snap;
+ uint8_t *dest;
+ uint8_t *src;
+ int first, last = 0;
+ int dirty;
+ int i;
+ ram_addr_t addr;
+ MemoryRegion *mem;
+
+ i = *first_row;
+ *first_row = -1;
+
+ mem = mem_section->mr;
+ if (!mem) {
+ return;
+ }
+
+ addr = mem_section->offset_within_region;
+ src = memory_region_get_ram_ptr(mem) + addr;
+
+ dest = surface_data(ds);
+ if (dest_col_pitch < 0) {
+ dest -= dest_col_pitch * (cols - 1);
+ }
+ if (dest_row_pitch < 0) {
+ dest -= dest_row_pitch * (rows - 1);
+ }
+ first = -1;
+
+ addr += i * src_width;
+ src += i * src_width;
+ dest += i * dest_row_pitch;
+
+ snap = memory_region_snapshot_and_clear_dirty(mem, addr, src_width * rows,
+ DIRTY_MEMORY_VGA);
+ for (; i < rows; i++) {
+ dirty = memory_region_snapshot_get_dirty(mem, snap, addr, src_width);
+ if (dirty || invalidate) {
+ fn(opaque, dest, src, cols, dest_col_pitch);
+ if (first == -1)
+ first = i;
+ last = i;
+ }
+ addr += src_width;
+ src += src_width;
+ dest += dest_row_pitch;
+ }
+ g_free(snap);
+ if (first < 0) {
+ return;
+ }
+ *first_row = first;
+ *last_row = last;
+}
diff --git a/hw/display/framebuffer.h b/hw/display/framebuffer.h
new file mode 100644
index 000000000..38fa0dcec
--- /dev/null
+++ b/hw/display/framebuffer.h
@@ -0,0 +1,65 @@
+#ifndef QEMU_FRAMEBUFFER_H
+#define QEMU_FRAMEBUFFER_H
+
+#include "exec/memory.h"
+
+/* Framebuffer device helper routines. */
+
+typedef void (*drawfn)(void *, uint8_t *, const uint8_t *, int, int);
+
+/* framebuffer_update_memory_section: Update framebuffer
+ * #MemoryRegionSection, for example if the framebuffer is switched to
+ * a different memory area.
+ *
+ * @mem_section: Output #MemoryRegionSection, to be passed to
+ * framebuffer_update_display().
+ * @root: #MemoryRegion within which the framebuffer lies
+ * @base: Base address of the framebuffer within @root.
+ * @rows: Height of the screen.
+ * @src_width: Number of bytes in framebuffer memory between two rows.
+ */
+void framebuffer_update_memory_section(
+ MemoryRegionSection *mem_section,
+ MemoryRegion *root,
+ hwaddr base,
+ unsigned rows,
+ unsigned src_width);
+
+/* framebuffer_update_display: Draw the framebuffer on a surface.
+ *
+ * @ds: #DisplaySurface to draw to.
+ * @mem_section: #MemoryRegionSection provided by
+ * framebuffer_update_memory_section().
+ * @cols: Width the screen.
+ * @rows: Height of the screen.
+ * @src_width: Number of bytes in framebuffer memory between two rows.
+ * @dest_row_pitch: Number of bytes in the surface data between two rows.
+ * Negative if the framebuffer is stored in the opposite order (e.g.
+ * bottom-to-top) compared to the framebuffer.
+ * @dest_col_pitch: Number of bytes in the surface data between two pixels.
+ * Negative if the framebuffer is stored in the opposite order (e.g.
+ * right-to-left) compared to the framebuffer.
+ * @invalidate: True if the function should redraw the whole screen
+ * without checking the DIRTY_MEMORY_VGA dirty bitmap.
+ * @fn: Drawing function to be called for each row that has to be drawn.
+ * @opaque: Opaque pointer passed to @fn.
+ * @first_row: Pointer to an integer, receives the number of the first row
+ * that was drawn (either the first dirty row, or 0 if @invalidate is true).
+ * @last_row: Pointer to an integer, receives the number of the last row that
+ * was drawn (either the last dirty row, or @rows-1 if @invalidate is true).
+ */
+void framebuffer_update_display(
+ DisplaySurface *ds,
+ MemoryRegionSection *mem_section,
+ int cols,
+ int rows,
+ int src_width,
+ int dest_row_pitch,
+ int dest_col_pitch,
+ int invalidate,
+ drawfn fn,
+ void *opaque,
+ int *first_row,
+ int *last_row);
+
+#endif
diff --git a/hw/display/g364fb.c b/hw/display/g364fb.c
new file mode 100644
index 000000000..caca86d77
--- /dev/null
+++ b/hw/display/g364fb.c
@@ -0,0 +1,554 @@
+/*
+ * QEMU G364 framebuffer Emulator.
+ *
+ * Copyright (c) 2007-2011 Herve Poussineau
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/units.h"
+#include "hw/hw.h"
+#include "hw/irq.h"
+#include "hw/qdev-properties.h"
+#include "qapi/error.h"
+#include "qemu/error-report.h"
+#include "qemu/module.h"
+#include "ui/console.h"
+#include "ui/pixel_ops.h"
+#include "trace.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "qom/object.h"
+
+typedef struct G364State {
+ /* hardware */
+ uint32_t vram_size;
+ qemu_irq irq;
+ MemoryRegion mem_vram;
+ MemoryRegion mem_ctrl;
+ /* registers */
+ uint8_t color_palette[256][3];
+ uint8_t cursor_palette[3][3];
+ uint16_t cursor[512];
+ uint32_t cursor_position;
+ uint32_t ctla;
+ uint32_t top_of_screen;
+ uint32_t width, height; /* in pixels */
+ /* display refresh support */
+ QemuConsole *con;
+ int depth;
+ int blanked;
+} G364State;
+
+#define REG_BOOT 0x000000
+#define REG_DISPLAY 0x000118
+#define REG_VDISPLAY 0x000150
+#define REG_CTLA 0x000300
+#define REG_TOP 0x000400
+#define REG_CURS_PAL 0x000508
+#define REG_CURS_POS 0x000638
+#define REG_CLR_PAL 0x000800
+#define REG_CURS_PAT 0x001000
+#define REG_RESET 0x100000
+
+#define CTLA_FORCE_BLANK 0x00000400
+#define CTLA_NO_CURSOR 0x00800000
+
+#define G364_PAGE_SIZE 4096
+
+static inline int check_dirty(G364State *s, DirtyBitmapSnapshot *snap, ram_addr_t page)
+{
+ return memory_region_snapshot_get_dirty(&s->mem_vram, snap, page, G364_PAGE_SIZE);
+}
+
+static void g364fb_draw_graphic8(G364State *s)
+{
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ DirtyBitmapSnapshot *snap;
+ int i, w;
+ uint8_t *vram;
+ uint8_t *data_display, *dd;
+ ram_addr_t page;
+ int x, y;
+ int xmin, xmax;
+ int ymin, ymax;
+ int xcursor, ycursor;
+ unsigned int (*rgb_to_pixel)(unsigned int r, unsigned int g, unsigned int b);
+
+ switch (surface_bits_per_pixel(surface)) {
+ case 8:
+ rgb_to_pixel = rgb_to_pixel8;
+ w = 1;
+ break;
+ case 15:
+ rgb_to_pixel = rgb_to_pixel15;
+ w = 2;
+ break;
+ case 16:
+ rgb_to_pixel = rgb_to_pixel16;
+ w = 2;
+ break;
+ case 32:
+ rgb_to_pixel = rgb_to_pixel32;
+ w = 4;
+ break;
+ default:
+ hw_error("g364: unknown host depth %d",
+ surface_bits_per_pixel(surface));
+ return;
+ }
+
+ page = 0;
+
+ x = y = 0;
+ xmin = s->width;
+ xmax = 0;
+ ymin = s->height;
+ ymax = 0;
+
+ if (!(s->ctla & CTLA_NO_CURSOR)) {
+ xcursor = s->cursor_position >> 12;
+ ycursor = s->cursor_position & 0xfff;
+ } else {
+ xcursor = ycursor = -65;
+ }
+
+ vram = memory_region_get_ram_ptr(&s->mem_vram) + s->top_of_screen;
+ /* XXX: out of range in vram? */
+ data_display = dd = surface_data(surface);
+ snap = memory_region_snapshot_and_clear_dirty(&s->mem_vram, 0, s->vram_size,
+ DIRTY_MEMORY_VGA);
+ while (y < s->height) {
+ if (check_dirty(s, snap, page)) {
+ if (y < ymin)
+ ymin = ymax = y;
+ if (x < xmin)
+ xmin = x;
+ for (i = 0; i < G364_PAGE_SIZE; i++) {
+ uint8_t index;
+ unsigned int color;
+ if (unlikely((y >= ycursor && y < ycursor + 64) &&
+ (x >= xcursor && x < xcursor + 64))) {
+ /* pointer area */
+ int xdiff = x - xcursor;
+ uint16_t curs = s->cursor[(y - ycursor) * 8 + xdiff / 8];
+ int op = (curs >> ((xdiff & 7) * 2)) & 3;
+ if (likely(op == 0)) {
+ /* transparent */
+ index = *vram;
+ color = (*rgb_to_pixel)(
+ s->color_palette[index][0],
+ s->color_palette[index][1],
+ s->color_palette[index][2]);
+ } else {
+ /* get cursor color */
+ index = op - 1;
+ color = (*rgb_to_pixel)(
+ s->cursor_palette[index][0],
+ s->cursor_palette[index][1],
+ s->cursor_palette[index][2]);
+ }
+ } else {
+ /* normal area */
+ index = *vram;
+ color = (*rgb_to_pixel)(
+ s->color_palette[index][0],
+ s->color_palette[index][1],
+ s->color_palette[index][2]);
+ }
+ memcpy(dd, &color, w);
+ dd += w;
+ x++;
+ vram++;
+ if (x == s->width) {
+ xmax = s->width - 1;
+ y++;
+ if (y == s->height) {
+ ymax = s->height - 1;
+ goto done;
+ }
+ data_display = dd = data_display + surface_stride(surface);
+ xmin = 0;
+ x = 0;
+ }
+ }
+ if (x > xmax)
+ xmax = x;
+ if (y > ymax)
+ ymax = y;
+ } else {
+ int dy;
+ if (xmax || ymax) {
+ dpy_gfx_update(s->con, xmin, ymin,
+ xmax - xmin + 1, ymax - ymin + 1);
+ xmin = s->width;
+ xmax = 0;
+ ymin = s->height;
+ ymax = 0;
+ }
+ x += G364_PAGE_SIZE;
+ dy = x / s->width;
+ x = x % s->width;
+ y += dy;
+ vram += G364_PAGE_SIZE;
+ data_display += dy * surface_stride(surface);
+ dd = data_display + x * w;
+ }
+ page += G364_PAGE_SIZE;
+ }
+
+done:
+ if (xmax || ymax) {
+ dpy_gfx_update(s->con, xmin, ymin, xmax - xmin + 1, ymax - ymin + 1);
+ }
+ g_free(snap);
+}
+
+static void g364fb_draw_blank(G364State *s)
+{
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ int i, w;
+ uint8_t *d;
+
+ if (s->blanked) {
+ /* Screen is already blank. No need to redraw it */
+ return;
+ }
+
+ w = s->width * surface_bytes_per_pixel(surface);
+ d = surface_data(surface);
+ for (i = 0; i < s->height; i++) {
+ memset(d, 0, w);
+ d += surface_stride(surface);
+ }
+
+ dpy_gfx_update_full(s->con);
+ s->blanked = 1;
+}
+
+static void g364fb_update_display(void *opaque)
+{
+ G364State *s = opaque;
+ DisplaySurface *surface = qemu_console_surface(s->con);
+
+ qemu_flush_coalesced_mmio_buffer();
+
+ if (s->width == 0 || s->height == 0)
+ return;
+
+ if (s->width != surface_width(surface) ||
+ s->height != surface_height(surface)) {
+ qemu_console_resize(s->con, s->width, s->height);
+ }
+
+ if (s->ctla & CTLA_FORCE_BLANK) {
+ g364fb_draw_blank(s);
+ } else if (s->depth == 8) {
+ g364fb_draw_graphic8(s);
+ } else {
+ error_report("g364: unknown guest depth %d", s->depth);
+ }
+
+ qemu_irq_raise(s->irq);
+}
+
+static inline void g364fb_invalidate_display(void *opaque)
+{
+ G364State *s = opaque;
+
+ s->blanked = 0;
+ memory_region_set_dirty(&s->mem_vram, 0, s->vram_size);
+}
+
+static void g364fb_reset(G364State *s)
+{
+ uint8_t *vram = memory_region_get_ram_ptr(&s->mem_vram);
+
+ qemu_irq_lower(s->irq);
+
+ memset(s->color_palette, 0, sizeof(s->color_palette));
+ memset(s->cursor_palette, 0, sizeof(s->cursor_palette));
+ memset(s->cursor, 0, sizeof(s->cursor));
+ s->cursor_position = 0;
+ s->ctla = 0;
+ s->top_of_screen = 0;
+ s->width = s->height = 0;
+ memset(vram, 0, s->vram_size);
+ g364fb_invalidate_display(s);
+}
+
+/* called for accesses to io ports */
+static uint64_t g364fb_ctrl_read(void *opaque,
+ hwaddr addr,
+ unsigned int size)
+{
+ G364State *s = opaque;
+ uint32_t val;
+
+ if (addr >= REG_CURS_PAT && addr < REG_CURS_PAT + 0x1000) {
+ /* cursor pattern */
+ int idx = (addr - REG_CURS_PAT) >> 3;
+ val = s->cursor[idx];
+ } else if (addr >= REG_CURS_PAL && addr < REG_CURS_PAL + 0x18) {
+ /* cursor palette */
+ int idx = (addr - REG_CURS_PAL) >> 3;
+ val = ((uint32_t)s->cursor_palette[idx][0] << 16);
+ val |= ((uint32_t)s->cursor_palette[idx][1] << 8);
+ val |= ((uint32_t)s->cursor_palette[idx][2] << 0);
+ } else {
+ switch (addr) {
+ case REG_DISPLAY:
+ val = s->width / 4;
+ break;
+ case REG_VDISPLAY:
+ val = s->height * 2;
+ break;
+ case REG_CTLA:
+ val = s->ctla;
+ break;
+ default:
+ {
+ error_report("g364: invalid read at [" TARGET_FMT_plx "]",
+ addr);
+ val = 0;
+ break;
+ }
+ }
+ }
+
+ trace_g364fb_read(addr, val);
+
+ return val;
+}
+
+static void g364fb_update_depth(G364State *s)
+{
+ static const int depths[8] = { 1, 2, 4, 8, 15, 16, 0 };
+ s->depth = depths[(s->ctla & 0x00700000) >> 20];
+}
+
+static void g364_invalidate_cursor_position(G364State *s)
+{
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ int ymin, ymax, start, end;
+
+ /* invalidate only near the cursor */
+ ymin = s->cursor_position & 0xfff;
+ ymax = MIN(s->height, ymin + 64);
+ start = ymin * surface_stride(surface);
+ end = (ymax + 1) * surface_stride(surface);
+
+ memory_region_set_dirty(&s->mem_vram, start, end - start);
+}
+
+static void g364fb_ctrl_write(void *opaque,
+ hwaddr addr,
+ uint64_t val,
+ unsigned int size)
+{
+ G364State *s = opaque;
+
+ trace_g364fb_write(addr, val);
+
+ if (addr >= REG_CLR_PAL && addr < REG_CLR_PAL + 0x800) {
+ /* color palette */
+ int idx = (addr - REG_CLR_PAL) >> 3;
+ s->color_palette[idx][0] = (val >> 16) & 0xff;
+ s->color_palette[idx][1] = (val >> 8) & 0xff;
+ s->color_palette[idx][2] = val & 0xff;
+ g364fb_invalidate_display(s);
+ } else if (addr >= REG_CURS_PAT && addr < REG_CURS_PAT + 0x1000) {
+ /* cursor pattern */
+ int idx = (addr - REG_CURS_PAT) >> 3;
+ s->cursor[idx] = val;
+ g364fb_invalidate_display(s);
+ } else if (addr >= REG_CURS_PAL && addr < REG_CURS_PAL + 0x18) {
+ /* cursor palette */
+ int idx = (addr - REG_CURS_PAL) >> 3;
+ s->cursor_palette[idx][0] = (val >> 16) & 0xff;
+ s->cursor_palette[idx][1] = (val >> 8) & 0xff;
+ s->cursor_palette[idx][2] = val & 0xff;
+ g364fb_invalidate_display(s);
+ } else {
+ switch (addr) {
+ case REG_BOOT: /* Boot timing */
+ case 0x00108: /* Line timing: half sync */
+ case 0x00110: /* Line timing: back porch */
+ case 0x00120: /* Line timing: short display */
+ case 0x00128: /* Frame timing: broad pulse */
+ case 0x00130: /* Frame timing: v sync */
+ case 0x00138: /* Frame timing: v preequalise */
+ case 0x00140: /* Frame timing: v postequalise */
+ case 0x00148: /* Frame timing: v blank */
+ case 0x00158: /* Line timing: line time */
+ case 0x00160: /* Frame store: line start */
+ case 0x00168: /* vram cycle: mem init */
+ case 0x00170: /* vram cycle: transfer delay */
+ case 0x00200: /* vram cycle: mask register */
+ /* ignore */
+ break;
+ case REG_TOP:
+ s->top_of_screen = val;
+ g364fb_invalidate_display(s);
+ break;
+ case REG_DISPLAY:
+ s->width = val * 4;
+ break;
+ case REG_VDISPLAY:
+ s->height = val / 2;
+ break;
+ case REG_CTLA:
+ s->ctla = val;
+ g364fb_update_depth(s);
+ g364fb_invalidate_display(s);
+ break;
+ case REG_CURS_POS:
+ g364_invalidate_cursor_position(s);
+ s->cursor_position = val;
+ g364_invalidate_cursor_position(s);
+ break;
+ case REG_RESET:
+ g364fb_reset(s);
+ break;
+ default:
+ error_report("g364: invalid write of 0x%" PRIx64
+ " at [" TARGET_FMT_plx "]", val, addr);
+ break;
+ }
+ }
+ qemu_irq_lower(s->irq);
+}
+
+static const MemoryRegionOps g364fb_ctrl_ops = {
+ .read = g364fb_ctrl_read,
+ .write = g364fb_ctrl_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl.min_access_size = 4,
+ .impl.max_access_size = 4,
+};
+
+static int g364fb_post_load(void *opaque, int version_id)
+{
+ G364State *s = opaque;
+
+ /* force refresh */
+ g364fb_update_depth(s);
+ g364fb_invalidate_display(s);
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_g364fb = {
+ .name = "g364fb",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .post_load = g364fb_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_BUFFER_UNSAFE(color_palette, G364State, 0, 256 * 3),
+ VMSTATE_BUFFER_UNSAFE(cursor_palette, G364State, 0, 9),
+ VMSTATE_UINT16_ARRAY(cursor, G364State, 512),
+ VMSTATE_UINT32(cursor_position, G364State),
+ VMSTATE_UINT32(ctla, G364State),
+ VMSTATE_UINT32(top_of_screen, G364State),
+ VMSTATE_UINT32(width, G364State),
+ VMSTATE_UINT32(height, G364State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const GraphicHwOps g364fb_ops = {
+ .invalidate = g364fb_invalidate_display,
+ .gfx_update = g364fb_update_display,
+};
+
+static void g364fb_init(DeviceState *dev, G364State *s)
+{
+ s->con = graphic_console_init(dev, 0, &g364fb_ops, s);
+
+ memory_region_init_io(&s->mem_ctrl, OBJECT(dev), &g364fb_ctrl_ops, s,
+ "ctrl", 0x180000);
+ memory_region_init_ram(&s->mem_vram, NULL, "g364fb.vram", s->vram_size,
+ &error_fatal);
+ memory_region_set_log(&s->mem_vram, true, DIRTY_MEMORY_VGA);
+}
+
+#define TYPE_G364 "sysbus-g364"
+OBJECT_DECLARE_SIMPLE_TYPE(G364SysBusState, G364)
+
+struct G364SysBusState {
+ SysBusDevice parent_obj;
+
+ G364State g364;
+};
+
+static void g364fb_sysbus_realize(DeviceState *dev, Error **errp)
+{
+ G364SysBusState *sbs = G364(dev);
+ G364State *s = &sbs->g364;
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+ g364fb_init(dev, s);
+ sysbus_init_irq(sbd, &s->irq);
+ sysbus_init_mmio(sbd, &s->mem_ctrl);
+ sysbus_init_mmio(sbd, &s->mem_vram);
+}
+
+static void g364fb_sysbus_reset(DeviceState *d)
+{
+ G364SysBusState *s = G364(d);
+
+ g364fb_reset(&s->g364);
+}
+
+static Property g364fb_sysbus_properties[] = {
+ DEFINE_PROP_UINT32("vram_size", G364SysBusState, g364.vram_size, 8 * MiB),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static const VMStateDescription vmstate_g364fb_sysbus = {
+ .name = "g364fb-sysbus",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(g364, G364SysBusState, 2, vmstate_g364fb, G364State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void g364fb_sysbus_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = g364fb_sysbus_realize;
+ set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories);
+ dc->desc = "G364 framebuffer";
+ dc->reset = g364fb_sysbus_reset;
+ dc->vmsd = &vmstate_g364fb_sysbus;
+ device_class_set_props(dc, g364fb_sysbus_properties);
+}
+
+static const TypeInfo g364fb_sysbus_info = {
+ .name = TYPE_G364,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(G364SysBusState),
+ .class_init = g364fb_sysbus_class_init,
+};
+
+static void g364fb_register_types(void)
+{
+ type_register_static(&g364fb_sysbus_info);
+}
+
+type_init(g364fb_register_types)
diff --git a/hw/display/i2c-ddc.c b/hw/display/i2c-ddc.c
new file mode 100644
index 000000000..13eb529fc
--- /dev/null
+++ b/hw/display/i2c-ddc.c
@@ -0,0 +1,129 @@
+/* A simple I2C slave for returning monitor EDID data via DDC.
+ *
+ * Copyright (c) 2011 Linaro Limited
+ * Written by Peter Maydell
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "hw/i2c/i2c.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "hw/display/i2c-ddc.h"
+
+#ifndef DEBUG_I2CDDC
+#define DEBUG_I2CDDC 0
+#endif
+
+#define DPRINTF(fmt, ...) do { \
+ if (DEBUG_I2CDDC) { \
+ qemu_log("i2c-ddc: " fmt , ## __VA_ARGS__); \
+ } \
+} while (0)
+
+static void i2c_ddc_reset(DeviceState *ds)
+{
+ I2CDDCState *s = I2CDDC(ds);
+
+ s->firstbyte = false;
+ s->reg = 0;
+}
+
+static int i2c_ddc_event(I2CSlave *i2c, enum i2c_event event)
+{
+ I2CDDCState *s = I2CDDC(i2c);
+
+ if (event == I2C_START_SEND) {
+ s->firstbyte = true;
+ }
+
+ return 0;
+}
+
+static uint8_t i2c_ddc_rx(I2CSlave *i2c)
+{
+ I2CDDCState *s = I2CDDC(i2c);
+
+ int value;
+ value = s->edid_blob[s->reg % sizeof(s->edid_blob)];
+ s->reg++;
+ return value;
+}
+
+static int i2c_ddc_tx(I2CSlave *i2c, uint8_t data)
+{
+ I2CDDCState *s = I2CDDC(i2c);
+ if (s->firstbyte) {
+ s->reg = data;
+ s->firstbyte = false;
+ DPRINTF("[EDID] Written new pointer: %u\n", data);
+ return 0;
+ }
+
+ /* Ignore all writes */
+ s->reg++;
+ return 0;
+}
+
+static void i2c_ddc_init(Object *obj)
+{
+ I2CDDCState *s = I2CDDC(obj);
+
+ qemu_edid_generate(s->edid_blob, sizeof(s->edid_blob), &s->edid_info);
+}
+
+static const VMStateDescription vmstate_i2c_ddc = {
+ .name = TYPE_I2CDDC,
+ .version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_BOOL(firstbyte, I2CDDCState),
+ VMSTATE_UINT8(reg, I2CDDCState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property i2c_ddc_properties[] = {
+ DEFINE_EDID_PROPERTIES(I2CDDCState, edid_info),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void i2c_ddc_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+ I2CSlaveClass *isc = I2C_SLAVE_CLASS(oc);
+
+ dc->reset = i2c_ddc_reset;
+ dc->vmsd = &vmstate_i2c_ddc;
+ device_class_set_props(dc, i2c_ddc_properties);
+ isc->event = i2c_ddc_event;
+ isc->recv = i2c_ddc_rx;
+ isc->send = i2c_ddc_tx;
+}
+
+static TypeInfo i2c_ddc_info = {
+ .name = TYPE_I2CDDC,
+ .parent = TYPE_I2C_SLAVE,
+ .instance_size = sizeof(I2CDDCState),
+ .instance_init = i2c_ddc_init,
+ .class_init = i2c_ddc_class_init
+};
+
+static void ddc_register_devices(void)
+{
+ type_register_static(&i2c_ddc_info);
+}
+
+type_init(ddc_register_devices);
diff --git a/hw/display/jazz_led.c b/hw/display/jazz_led.c
new file mode 100644
index 000000000..dd5f4696c
--- /dev/null
+++ b/hw/display/jazz_led.c
@@ -0,0 +1,320 @@
+/*
+ * QEMU JAZZ LED emulator.
+ *
+ * Copyright (c) 2007-2012 Herve Poussineau
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/module.h"
+#include "ui/console.h"
+#include "ui/pixel_ops.h"
+#include "trace.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "qom/object.h"
+
+typedef enum {
+ REDRAW_NONE = 0, REDRAW_SEGMENTS = 1, REDRAW_BACKGROUND = 2,
+} screen_state_t;
+
+#define TYPE_JAZZ_LED "jazz-led"
+OBJECT_DECLARE_SIMPLE_TYPE(LedState, JAZZ_LED)
+
+struct LedState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ uint8_t segments;
+ QemuConsole *con;
+ screen_state_t state;
+};
+
+static uint64_t jazz_led_read(void *opaque, hwaddr addr,
+ unsigned int size)
+{
+ LedState *s = opaque;
+ uint8_t val;
+
+ val = s->segments;
+ trace_jazz_led_read(addr, val);
+
+ return val;
+}
+
+static void jazz_led_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned int size)
+{
+ LedState *s = opaque;
+ uint8_t new_val = val & 0xff;
+
+ trace_jazz_led_write(addr, new_val);
+
+ s->segments = new_val;
+ s->state |= REDRAW_SEGMENTS;
+}
+
+static const MemoryRegionOps led_ops = {
+ .read = jazz_led_read,
+ .write = jazz_led_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .impl.min_access_size = 1,
+ .impl.max_access_size = 1,
+};
+
+/***********************************************************/
+/* jazz_led display */
+
+static void draw_horizontal_line(DisplaySurface *ds,
+ int posy, int posx1, int posx2,
+ uint32_t color)
+{
+ uint8_t *d;
+ int x, bpp;
+
+ bpp = (surface_bits_per_pixel(ds) + 7) >> 3;
+ d = surface_data(ds) + surface_stride(ds) * posy + bpp * posx1;
+ switch (bpp) {
+ case 1:
+ for (x = posx1; x <= posx2; x++) {
+ *((uint8_t *)d) = color;
+ d++;
+ }
+ break;
+ case 2:
+ for (x = posx1; x <= posx2; x++) {
+ *((uint16_t *)d) = color;
+ d += 2;
+ }
+ break;
+ case 4:
+ for (x = posx1; x <= posx2; x++) {
+ *((uint32_t *)d) = color;
+ d += 4;
+ }
+ break;
+ }
+}
+
+static void draw_vertical_line(DisplaySurface *ds,
+ int posx, int posy1, int posy2,
+ uint32_t color)
+{
+ uint8_t *d;
+ int y, bpp;
+
+ bpp = (surface_bits_per_pixel(ds) + 7) >> 3;
+ d = surface_data(ds) + surface_stride(ds) * posy1 + bpp * posx;
+ switch (bpp) {
+ case 1:
+ for (y = posy1; y <= posy2; y++) {
+ *((uint8_t *)d) = color;
+ d += surface_stride(ds);
+ }
+ break;
+ case 2:
+ for (y = posy1; y <= posy2; y++) {
+ *((uint16_t *)d) = color;
+ d += surface_stride(ds);
+ }
+ break;
+ case 4:
+ for (y = posy1; y <= posy2; y++) {
+ *((uint32_t *)d) = color;
+ d += surface_stride(ds);
+ }
+ break;
+ }
+}
+
+static void jazz_led_update_display(void *opaque)
+{
+ LedState *s = opaque;
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ uint8_t *d1;
+ uint32_t color_segment, color_led;
+ int y, bpp;
+
+ if (s->state & REDRAW_BACKGROUND) {
+ /* clear screen */
+ bpp = (surface_bits_per_pixel(surface) + 7) >> 3;
+ d1 = surface_data(surface);
+ for (y = 0; y < surface_height(surface); y++) {
+ memset(d1, 0x00, surface_width(surface) * bpp);
+ d1 += surface_stride(surface);
+ }
+ }
+
+ if (s->state & REDRAW_SEGMENTS) {
+ /* set colors according to bpp */
+ switch (surface_bits_per_pixel(surface)) {
+ case 8:
+ color_segment = rgb_to_pixel8(0xaa, 0xaa, 0xaa);
+ color_led = rgb_to_pixel8(0x00, 0xff, 0x00);
+ break;
+ case 15:
+ color_segment = rgb_to_pixel15(0xaa, 0xaa, 0xaa);
+ color_led = rgb_to_pixel15(0x00, 0xff, 0x00);
+ break;
+ case 16:
+ color_segment = rgb_to_pixel16(0xaa, 0xaa, 0xaa);
+ color_led = rgb_to_pixel16(0x00, 0xff, 0x00);
+ break;
+ case 24:
+ color_segment = rgb_to_pixel24(0xaa, 0xaa, 0xaa);
+ color_led = rgb_to_pixel24(0x00, 0xff, 0x00);
+ break;
+ case 32:
+ color_segment = rgb_to_pixel32(0xaa, 0xaa, 0xaa);
+ color_led = rgb_to_pixel32(0x00, 0xff, 0x00);
+ break;
+ default:
+ return;
+ }
+
+ /* display segments */
+ draw_horizontal_line(surface, 40, 10, 40,
+ (s->segments & 0x02) ? color_segment : 0);
+ draw_vertical_line(surface, 10, 10, 40,
+ (s->segments & 0x04) ? color_segment : 0);
+ draw_vertical_line(surface, 10, 40, 70,
+ (s->segments & 0x08) ? color_segment : 0);
+ draw_horizontal_line(surface, 70, 10, 40,
+ (s->segments & 0x10) ? color_segment : 0);
+ draw_vertical_line(surface, 40, 40, 70,
+ (s->segments & 0x20) ? color_segment : 0);
+ draw_vertical_line(surface, 40, 10, 40,
+ (s->segments & 0x40) ? color_segment : 0);
+ draw_horizontal_line(surface, 10, 10, 40,
+ (s->segments & 0x80) ? color_segment : 0);
+
+ /* display led */
+ if (!(s->segments & 0x01)) {
+ color_led = 0; /* black */
+ }
+ draw_horizontal_line(surface, 68, 50, 50, color_led);
+ draw_horizontal_line(surface, 69, 49, 51, color_led);
+ draw_horizontal_line(surface, 70, 48, 52, color_led);
+ draw_horizontal_line(surface, 71, 49, 51, color_led);
+ draw_horizontal_line(surface, 72, 50, 50, color_led);
+ }
+
+ s->state = REDRAW_NONE;
+ dpy_gfx_update_full(s->con);
+}
+
+static void jazz_led_invalidate_display(void *opaque)
+{
+ LedState *s = opaque;
+ s->state |= REDRAW_SEGMENTS | REDRAW_BACKGROUND;
+}
+
+static void jazz_led_text_update(void *opaque, console_ch_t *chardata)
+{
+ LedState *s = opaque;
+ char buf[3];
+
+ dpy_text_cursor(s->con, -1, -1);
+ qemu_console_resize(s->con, 2, 1);
+
+ /* TODO: draw the segments */
+ snprintf(buf, 3, "%02hhx", s->segments);
+ console_write_ch(chardata++, ATTR2CHTYPE(buf[0], QEMU_COLOR_BLUE,
+ QEMU_COLOR_BLACK, 1));
+ console_write_ch(chardata++, ATTR2CHTYPE(buf[1], QEMU_COLOR_BLUE,
+ QEMU_COLOR_BLACK, 1));
+
+ dpy_text_update(s->con, 0, 0, 2, 1);
+}
+
+static int jazz_led_post_load(void *opaque, int version_id)
+{
+ /* force refresh */
+ jazz_led_invalidate_display(opaque);
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_jazz_led = {
+ .name = "jazz-led",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .post_load = jazz_led_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(segments, LedState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const GraphicHwOps jazz_led_ops = {
+ .invalidate = jazz_led_invalidate_display,
+ .gfx_update = jazz_led_update_display,
+ .text_update = jazz_led_text_update,
+};
+
+static void jazz_led_init(Object *obj)
+{
+ LedState *s = JAZZ_LED(obj);
+ SysBusDevice *dev = SYS_BUS_DEVICE(obj);
+
+ memory_region_init_io(&s->iomem, obj, &led_ops, s, "led", 1);
+ sysbus_init_mmio(dev, &s->iomem);
+}
+
+static void jazz_led_realize(DeviceState *dev, Error **errp)
+{
+ LedState *s = JAZZ_LED(dev);
+
+ s->con = graphic_console_init(dev, 0, &jazz_led_ops, s);
+}
+
+static void jazz_led_reset(DeviceState *d)
+{
+ LedState *s = JAZZ_LED(d);
+
+ s->segments = 0;
+ s->state = REDRAW_SEGMENTS | REDRAW_BACKGROUND;
+ qemu_console_resize(s->con, 60, 80);
+}
+
+static void jazz_led_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->desc = "Jazz LED display",
+ dc->vmsd = &vmstate_jazz_led;
+ dc->reset = jazz_led_reset;
+ dc->realize = jazz_led_realize;
+}
+
+static const TypeInfo jazz_led_info = {
+ .name = TYPE_JAZZ_LED,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(LedState),
+ .instance_init = jazz_led_init,
+ .class_init = jazz_led_class_init,
+};
+
+static void jazz_led_register(void)
+{
+ type_register_static(&jazz_led_info);
+}
+
+type_init(jazz_led_register);
diff --git a/hw/display/macfb.c b/hw/display/macfb.c
new file mode 100644
index 000000000..277d3e663
--- /dev/null
+++ b/hw/display/macfb.c
@@ -0,0 +1,807 @@
+/*
+ * QEMU Motorola 680x0 Macintosh Video Card Emulation
+ * Copyright (c) 2012-2018 Laurent Vivier
+ *
+ * some parts from QEMU G364 framebuffer Emulator.
+ * Copyright (c) 2007-2011 Herve Poussineau
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/units.h"
+#include "hw/sysbus.h"
+#include "ui/console.h"
+#include "ui/pixel_ops.h"
+#include "hw/nubus/nubus.h"
+#include "hw/display/macfb.h"
+#include "qapi/error.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "trace.h"
+
+#define VIDEO_BASE 0x0
+#define DAFB_BASE 0x00800000
+
+#define MACFB_PAGE_SIZE 4096
+#define MACFB_VRAM_SIZE (4 * MiB)
+
+#define DAFB_MODE_VADDR1 0x0
+#define DAFB_MODE_VADDR2 0x4
+#define DAFB_MODE_CTRL1 0x8
+#define DAFB_MODE_CTRL2 0xc
+#define DAFB_MODE_SENSE 0x1c
+#define DAFB_INTR_MASK 0x104
+#define DAFB_INTR_STAT 0x108
+#define DAFB_INTR_CLEAR 0x10c
+#define DAFB_RESET 0x200
+#define DAFB_LUT 0x213
+
+#define DAFB_INTR_VBL 0x4
+
+/* Vertical Blank period (60.15Hz) */
+#define DAFB_INTR_VBL_PERIOD_NS 16625800
+
+/*
+ * Quadra sense codes taken from Apple Technical Note HW26:
+ * "Macintosh Quadra Built-In Video". The sense codes and
+ * extended sense codes have different meanings:
+ *
+ * Sense:
+ * bit 2: SENSE2 (pin 10)
+ * bit 1: SENSE1 (pin 7)
+ * bit 0: SENSE0 (pin 4)
+ *
+ * 0 = pin tied to ground
+ * 1 = pin unconnected
+ *
+ * Extended Sense:
+ * bit 2: pins 4-10
+ * bit 1: pins 10-7
+ * bit 0: pins 7-4
+ *
+ * 0 = pins tied together
+ * 1 = pins unconnected
+ *
+ * Reads from the sense register appear to be active low, i.e. a 1 indicates
+ * that the pin is tied to ground, a 0 indicates the pin is disconnected.
+ *
+ * Writes to the sense register appear to activate pulldowns i.e. a 1 enables
+ * a pulldown on a particular pin.
+ *
+ * The MacOS toolbox appears to use a series of reads and writes to first
+ * determine if extended sense is to be used, and then check which pins are
+ * tied together in order to determine the display type.
+ */
+
+typedef struct MacFbSense {
+ uint8_t type;
+ uint8_t sense;
+ uint8_t ext_sense;
+} MacFbSense;
+
+static MacFbSense macfb_sense_table[] = {
+ { MACFB_DISPLAY_APPLE_21_COLOR, 0x0, 0 },
+ { MACFB_DISPLAY_APPLE_PORTRAIT, 0x1, 0 },
+ { MACFB_DISPLAY_APPLE_12_RGB, 0x2, 0 },
+ { MACFB_DISPLAY_APPLE_2PAGE_MONO, 0x3, 0 },
+ { MACFB_DISPLAY_NTSC_UNDERSCAN, 0x4, 0 },
+ { MACFB_DISPLAY_NTSC_OVERSCAN, 0x4, 0 },
+ { MACFB_DISPLAY_APPLE_12_MONO, 0x6, 0 },
+ { MACFB_DISPLAY_APPLE_13_RGB, 0x6, 0 },
+ { MACFB_DISPLAY_16_COLOR, 0x7, 0x3 },
+ { MACFB_DISPLAY_PAL1_UNDERSCAN, 0x7, 0x0 },
+ { MACFB_DISPLAY_PAL1_OVERSCAN, 0x7, 0x0 },
+ { MACFB_DISPLAY_PAL2_UNDERSCAN, 0x7, 0x6 },
+ { MACFB_DISPLAY_PAL2_OVERSCAN, 0x7, 0x6 },
+ { MACFB_DISPLAY_VGA, 0x7, 0x5 },
+ { MACFB_DISPLAY_SVGA, 0x7, 0x5 },
+};
+
+static MacFbMode macfb_mode_table[] = {
+ { MACFB_DISPLAY_VGA, 1, 0x100, 0x71e, 640, 480, 0x400, 0x1000 },
+ { MACFB_DISPLAY_VGA, 2, 0x100, 0x70e, 640, 480, 0x400, 0x1000 },
+ { MACFB_DISPLAY_VGA, 4, 0x100, 0x706, 640, 480, 0x400, 0x1000 },
+ { MACFB_DISPLAY_VGA, 8, 0x100, 0x702, 640, 480, 0x400, 0x1000 },
+ { MACFB_DISPLAY_VGA, 24, 0x100, 0x7ff, 640, 480, 0x1000, 0x1000 },
+ { MACFB_DISPLAY_VGA, 1, 0xd0 , 0x70e, 800, 600, 0x340, 0xe00 },
+ { MACFB_DISPLAY_VGA, 2, 0xd0 , 0x706, 800, 600, 0x340, 0xe00 },
+ { MACFB_DISPLAY_VGA, 4, 0xd0 , 0x702, 800, 600, 0x340, 0xe00 },
+ { MACFB_DISPLAY_VGA, 8, 0xd0, 0x700, 800, 600, 0x340, 0xe00 },
+ { MACFB_DISPLAY_VGA, 24, 0x340, 0x100, 800, 600, 0xd00, 0xe00 },
+ { MACFB_DISPLAY_APPLE_21_COLOR, 1, 0x90, 0x506, 1152, 870, 0x240, 0x80 },
+ { MACFB_DISPLAY_APPLE_21_COLOR, 2, 0x90, 0x502, 1152, 870, 0x240, 0x80 },
+ { MACFB_DISPLAY_APPLE_21_COLOR, 4, 0x90, 0x500, 1152, 870, 0x240, 0x80 },
+ { MACFB_DISPLAY_APPLE_21_COLOR, 8, 0x120, 0x5ff, 1152, 870, 0x480, 0x80 },
+};
+
+typedef void macfb_draw_line_func(MacfbState *s, uint8_t *d, uint32_t addr,
+ int width);
+
+static inline uint8_t macfb_read_byte(MacfbState *s, uint32_t addr)
+{
+ return s->vram[addr & s->vram_bit_mask];
+}
+
+/* 1-bit color */
+static void macfb_draw_line1(MacfbState *s, uint8_t *d, uint32_t addr,
+ int width)
+{
+ uint8_t r, g, b;
+ int x;
+
+ for (x = 0; x < width; x++) {
+ int bit = x & 7;
+ int idx = (macfb_read_byte(s, addr) >> (7 - bit)) & 1;
+ r = s->color_palette[idx * 3];
+ g = s->color_palette[idx * 3 + 1];
+ b = s->color_palette[idx * 3 + 2];
+ addr += (bit == 7);
+
+ *(uint32_t *)d = rgb_to_pixel32(r, g, b);
+ d += 4;
+ }
+}
+
+/* 2-bit color */
+static void macfb_draw_line2(MacfbState *s, uint8_t *d, uint32_t addr,
+ int width)
+{
+ uint8_t r, g, b;
+ int x;
+
+ for (x = 0; x < width; x++) {
+ int bit = (x & 3);
+ int idx = (macfb_read_byte(s, addr) >> ((3 - bit) << 1)) & 3;
+ r = s->color_palette[idx * 3];
+ g = s->color_palette[idx * 3 + 1];
+ b = s->color_palette[idx * 3 + 2];
+ addr += (bit == 3);
+
+ *(uint32_t *)d = rgb_to_pixel32(r, g, b);
+ d += 4;
+ }
+}
+
+/* 4-bit color */
+static void macfb_draw_line4(MacfbState *s, uint8_t *d, uint32_t addr,
+ int width)
+{
+ uint8_t r, g, b;
+ int x;
+
+ for (x = 0; x < width; x++) {
+ int bit = x & 1;
+ int idx = (macfb_read_byte(s, addr) >> ((1 - bit) << 2)) & 15;
+ r = s->color_palette[idx * 3];
+ g = s->color_palette[idx * 3 + 1];
+ b = s->color_palette[idx * 3 + 2];
+ addr += (bit == 1);
+
+ *(uint32_t *)d = rgb_to_pixel32(r, g, b);
+ d += 4;
+ }
+}
+
+/* 8-bit color */
+static void macfb_draw_line8(MacfbState *s, uint8_t *d, uint32_t addr,
+ int width)
+{
+ uint8_t r, g, b;
+ int x;
+
+ for (x = 0; x < width; x++) {
+ r = s->color_palette[macfb_read_byte(s, addr) * 3];
+ g = s->color_palette[macfb_read_byte(s, addr) * 3 + 1];
+ b = s->color_palette[macfb_read_byte(s, addr) * 3 + 2];
+ addr++;
+
+ *(uint32_t *)d = rgb_to_pixel32(r, g, b);
+ d += 4;
+ }
+}
+
+/* 16-bit color */
+static void macfb_draw_line16(MacfbState *s, uint8_t *d, uint32_t addr,
+ int width)
+{
+ uint8_t r, g, b;
+ int x;
+
+ for (x = 0; x < width; x++) {
+ uint16_t pixel;
+ pixel = (macfb_read_byte(s, addr) << 8) | macfb_read_byte(s, addr + 1);
+ r = ((pixel >> 10) & 0x1f) << 3;
+ g = ((pixel >> 5) & 0x1f) << 3;
+ b = (pixel & 0x1f) << 3;
+ addr += 2;
+
+ *(uint32_t *)d = rgb_to_pixel32(r, g, b);
+ d += 4;
+ }
+}
+
+/* 24-bit color */
+static void macfb_draw_line24(MacfbState *s, uint8_t *d, uint32_t addr,
+ int width)
+{
+ uint8_t r, g, b;
+ int x;
+
+ for (x = 0; x < width; x++) {
+ r = macfb_read_byte(s, addr + 1);
+ g = macfb_read_byte(s, addr + 2);
+ b = macfb_read_byte(s, addr + 3);
+ addr += 4;
+
+ *(uint32_t *)d = rgb_to_pixel32(r, g, b);
+ d += 4;
+ }
+}
+
+
+enum {
+ MACFB_DRAW_LINE1,
+ MACFB_DRAW_LINE2,
+ MACFB_DRAW_LINE4,
+ MACFB_DRAW_LINE8,
+ MACFB_DRAW_LINE16,
+ MACFB_DRAW_LINE24,
+ MACFB_DRAW_LINE_NB,
+};
+
+static macfb_draw_line_func * const
+ macfb_draw_line_table[MACFB_DRAW_LINE_NB] = {
+ macfb_draw_line1,
+ macfb_draw_line2,
+ macfb_draw_line4,
+ macfb_draw_line8,
+ macfb_draw_line16,
+ macfb_draw_line24,
+};
+
+static int macfb_check_dirty(MacfbState *s, DirtyBitmapSnapshot *snap,
+ ram_addr_t addr, int len)
+{
+ return memory_region_snapshot_get_dirty(&s->mem_vram, snap, addr, len);
+}
+
+static void macfb_draw_graphic(MacfbState *s)
+{
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ DirtyBitmapSnapshot *snap = NULL;
+ ram_addr_t page;
+ uint32_t v = 0;
+ int y, ymin;
+ int macfb_stride = s->mode->stride;
+ macfb_draw_line_func *macfb_draw_line;
+
+ switch (s->depth) {
+ case 1:
+ v = MACFB_DRAW_LINE1;
+ break;
+ case 2:
+ v = MACFB_DRAW_LINE2;
+ break;
+ case 4:
+ v = MACFB_DRAW_LINE4;
+ break;
+ case 8:
+ v = MACFB_DRAW_LINE8;
+ break;
+ case 16:
+ v = MACFB_DRAW_LINE16;
+ break;
+ case 24:
+ v = MACFB_DRAW_LINE24;
+ break;
+ }
+
+ macfb_draw_line = macfb_draw_line_table[v];
+ assert(macfb_draw_line != NULL);
+
+ snap = memory_region_snapshot_and_clear_dirty(&s->mem_vram, 0x0,
+ memory_region_size(&s->mem_vram),
+ DIRTY_MEMORY_VGA);
+
+ ymin = -1;
+ page = s->mode->offset;
+ for (y = 0; y < s->height; y++, page += macfb_stride) {
+ if (macfb_check_dirty(s, snap, page, macfb_stride)) {
+ uint8_t *data_display;
+
+ data_display = surface_data(surface) + y * surface_stride(surface);
+ macfb_draw_line(s, data_display, page, s->width);
+
+ if (ymin < 0) {
+ ymin = y;
+ }
+ } else {
+ if (ymin >= 0) {
+ dpy_gfx_update(s->con, 0, ymin, s->width, y - ymin);
+ ymin = -1;
+ }
+ }
+ }
+
+ if (ymin >= 0) {
+ dpy_gfx_update(s->con, 0, ymin, s->width, y - ymin);
+ }
+
+ g_free(snap);
+}
+
+static void macfb_invalidate_display(void *opaque)
+{
+ MacfbState *s = opaque;
+
+ memory_region_set_dirty(&s->mem_vram, 0, MACFB_VRAM_SIZE);
+}
+
+static uint32_t macfb_sense_read(MacfbState *s)
+{
+ MacFbSense *macfb_sense;
+ uint8_t sense;
+
+ assert(s->type < ARRAY_SIZE(macfb_sense_table));
+ macfb_sense = &macfb_sense_table[s->type];
+ if (macfb_sense->sense == 0x7) {
+ /* Extended sense */
+ sense = 0;
+ if (!(macfb_sense->ext_sense & 1)) {
+ /* Pins 7-4 together */
+ if (~s->regs[DAFB_MODE_SENSE >> 2] & 3) {
+ sense = (~s->regs[DAFB_MODE_SENSE >> 2] & 7) | 3;
+ }
+ }
+ if (!(macfb_sense->ext_sense & 2)) {
+ /* Pins 10-7 together */
+ if (~s->regs[DAFB_MODE_SENSE >> 2] & 6) {
+ sense = (~s->regs[DAFB_MODE_SENSE >> 2] & 7) | 6;
+ }
+ }
+ if (!(macfb_sense->ext_sense & 4)) {
+ /* Pins 4-10 together */
+ if (~s->regs[DAFB_MODE_SENSE >> 2] & 5) {
+ sense = (~s->regs[DAFB_MODE_SENSE >> 2] & 7) | 5;
+ }
+ }
+ } else {
+ /* Normal sense */
+ sense = (~macfb_sense->sense & 7) |
+ (~s->regs[DAFB_MODE_SENSE >> 2] & 7);
+ }
+
+ trace_macfb_sense_read(sense);
+ return sense;
+}
+
+static void macfb_sense_write(MacfbState *s, uint32_t val)
+{
+ s->regs[DAFB_MODE_SENSE >> 2] = val;
+
+ trace_macfb_sense_write(val);
+ return;
+}
+
+static void macfb_update_mode(MacfbState *s)
+{
+ s->width = s->mode->width;
+ s->height = s->mode->height;
+ s->depth = s->mode->depth;
+
+ trace_macfb_update_mode(s->width, s->height, s->depth);
+ macfb_invalidate_display(s);
+}
+
+static void macfb_mode_write(MacfbState *s)
+{
+ MacFbMode *macfb_mode;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(macfb_mode_table); i++) {
+ macfb_mode = &macfb_mode_table[i];
+
+ if (s->type != macfb_mode->type) {
+ continue;
+ }
+
+ if ((s->regs[DAFB_MODE_CTRL1 >> 2] & 0xff) ==
+ (macfb_mode->mode_ctrl1 & 0xff) &&
+ (s->regs[DAFB_MODE_CTRL2 >> 2] & 0xff) ==
+ (macfb_mode->mode_ctrl2 & 0xff)) {
+ s->mode = macfb_mode;
+ macfb_update_mode(s);
+ break;
+ }
+ }
+}
+
+static MacFbMode *macfb_find_mode(MacfbDisplayType display_type,
+ uint16_t width, uint16_t height,
+ uint8_t depth)
+{
+ MacFbMode *macfb_mode;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(macfb_mode_table); i++) {
+ macfb_mode = &macfb_mode_table[i];
+
+ if (display_type == macfb_mode->type && width == macfb_mode->width &&
+ height == macfb_mode->height && depth == macfb_mode->depth) {
+ return macfb_mode;
+ }
+ }
+
+ return NULL;
+}
+
+static gchar *macfb_mode_list(void)
+{
+ GString *list = g_string_new("");
+ MacFbMode *macfb_mode;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(macfb_mode_table); i++) {
+ macfb_mode = &macfb_mode_table[i];
+
+ g_string_append_printf(list, " %dx%dx%d\n", macfb_mode->width,
+ macfb_mode->height, macfb_mode->depth);
+ }
+
+ return g_string_free(list, FALSE);
+}
+
+
+static void macfb_update_display(void *opaque)
+{
+ MacfbState *s = opaque;
+ DisplaySurface *surface = qemu_console_surface(s->con);
+
+ qemu_flush_coalesced_mmio_buffer();
+
+ if (s->width == 0 || s->height == 0) {
+ return;
+ }
+
+ if (s->width != surface_width(surface) ||
+ s->height != surface_height(surface)) {
+ qemu_console_resize(s->con, s->width, s->height);
+ }
+
+ macfb_draw_graphic(s);
+}
+
+static void macfb_update_irq(MacfbState *s)
+{
+ uint32_t irq_state = s->irq_state & s->irq_mask;
+
+ if (irq_state) {
+ qemu_irq_raise(s->irq);
+ } else {
+ qemu_irq_lower(s->irq);
+ }
+}
+
+static int64_t macfb_next_vbl(void)
+{
+ return (qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + DAFB_INTR_VBL_PERIOD_NS) /
+ DAFB_INTR_VBL_PERIOD_NS * DAFB_INTR_VBL_PERIOD_NS;
+}
+
+static void macfb_vbl_timer(void *opaque)
+{
+ MacfbState *s = opaque;
+ int64_t next_vbl;
+
+ s->irq_state |= DAFB_INTR_VBL;
+ macfb_update_irq(s);
+
+ /* 60 Hz irq */
+ next_vbl = macfb_next_vbl();
+ timer_mod(s->vbl_timer, next_vbl);
+}
+
+static void macfb_reset(MacfbState *s)
+{
+ int i;
+
+ s->palette_current = 0;
+ for (i = 0; i < 256; i++) {
+ s->color_palette[i * 3] = 255 - i;
+ s->color_palette[i * 3 + 1] = 255 - i;
+ s->color_palette[i * 3 + 2] = 255 - i;
+ }
+ memset(s->vram, 0, MACFB_VRAM_SIZE);
+ macfb_invalidate_display(s);
+}
+
+static uint64_t macfb_ctrl_read(void *opaque,
+ hwaddr addr,
+ unsigned int size)
+{
+ MacfbState *s = opaque;
+ uint64_t val = 0;
+
+ switch (addr) {
+ case DAFB_MODE_VADDR1:
+ case DAFB_MODE_VADDR2:
+ case DAFB_MODE_CTRL1:
+ case DAFB_MODE_CTRL2:
+ val = s->regs[addr >> 2];
+ break;
+ case DAFB_INTR_STAT:
+ val = s->irq_state;
+ break;
+ case DAFB_MODE_SENSE:
+ val = macfb_sense_read(s);
+ break;
+ }
+
+ trace_macfb_ctrl_read(addr, val, size);
+ return val;
+}
+
+static void macfb_ctrl_write(void *opaque,
+ hwaddr addr,
+ uint64_t val,
+ unsigned int size)
+{
+ MacfbState *s = opaque;
+ int64_t next_vbl;
+
+ switch (addr) {
+ case DAFB_MODE_VADDR1:
+ case DAFB_MODE_VADDR2:
+ s->regs[addr >> 2] = val;
+ break;
+ case DAFB_MODE_CTRL1 ... DAFB_MODE_CTRL1 + 3:
+ case DAFB_MODE_CTRL2 ... DAFB_MODE_CTRL2 + 3:
+ s->regs[addr >> 2] = val;
+ if (val) {
+ macfb_mode_write(s);
+ }
+ break;
+ case DAFB_MODE_SENSE:
+ macfb_sense_write(s, val);
+ break;
+ case DAFB_INTR_MASK:
+ s->irq_mask = val;
+ if (val & DAFB_INTR_VBL) {
+ next_vbl = macfb_next_vbl();
+ timer_mod(s->vbl_timer, next_vbl);
+ } else {
+ timer_del(s->vbl_timer);
+ }
+ break;
+ case DAFB_INTR_CLEAR:
+ s->irq_state &= ~DAFB_INTR_VBL;
+ macfb_update_irq(s);
+ break;
+ case DAFB_RESET:
+ s->palette_current = 0;
+ s->irq_state &= ~DAFB_INTR_VBL;
+ macfb_update_irq(s);
+ break;
+ case DAFB_LUT:
+ s->color_palette[s->palette_current] = val;
+ s->palette_current = (s->palette_current + 1) %
+ ARRAY_SIZE(s->color_palette);
+ if (s->palette_current % 3) {
+ macfb_invalidate_display(s);
+ }
+ break;
+ }
+
+ trace_macfb_ctrl_write(addr, val, size);
+}
+
+static const MemoryRegionOps macfb_ctrl_ops = {
+ .read = macfb_ctrl_read,
+ .write = macfb_ctrl_write,
+ .endianness = DEVICE_BIG_ENDIAN,
+ .impl.min_access_size = 1,
+ .impl.max_access_size = 4,
+};
+
+static int macfb_post_load(void *opaque, int version_id)
+{
+ macfb_mode_write(opaque);
+ return 0;
+}
+
+static const VMStateDescription vmstate_macfb = {
+ .name = "macfb",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .post_load = macfb_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8_ARRAY(color_palette, MacfbState, 256 * 3),
+ VMSTATE_UINT32(palette_current, MacfbState),
+ VMSTATE_UINT32_ARRAY(regs, MacfbState, MACFB_NUM_REGS),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const GraphicHwOps macfb_ops = {
+ .invalidate = macfb_invalidate_display,
+ .gfx_update = macfb_update_display,
+};
+
+static bool macfb_common_realize(DeviceState *dev, MacfbState *s, Error **errp)
+{
+ DisplaySurface *surface;
+
+ s->mode = macfb_find_mode(s->type, s->width, s->height, s->depth);
+ if (!s->mode) {
+ gchar *list;
+ error_setg(errp, "unknown display mode: width %d, height %d, depth %d",
+ s->width, s->height, s->depth);
+ list = macfb_mode_list();
+ error_append_hint(errp, "Available modes:\n%s", list);
+ g_free(list);
+
+ return false;
+ }
+
+ s->con = graphic_console_init(dev, 0, &macfb_ops, s);
+ surface = qemu_console_surface(s->con);
+
+ if (surface_bits_per_pixel(surface) != 32) {
+ error_setg(errp, "unknown host depth %d",
+ surface_bits_per_pixel(surface));
+ return false;
+ }
+
+ memory_region_init_io(&s->mem_ctrl, OBJECT(dev), &macfb_ctrl_ops, s,
+ "macfb-ctrl", 0x1000);
+
+ memory_region_init_ram(&s->mem_vram, OBJECT(dev), "macfb-vram",
+ MACFB_VRAM_SIZE, &error_abort);
+ s->vram = memory_region_get_ram_ptr(&s->mem_vram);
+ s->vram_bit_mask = MACFB_VRAM_SIZE - 1;
+ memory_region_set_coalescing(&s->mem_vram);
+
+ s->vbl_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, macfb_vbl_timer, s);
+ macfb_update_mode(s);
+ return true;
+}
+
+static void macfb_sysbus_realize(DeviceState *dev, Error **errp)
+{
+ MacfbSysBusState *s = MACFB(dev);
+ MacfbState *ms = &s->macfb;
+
+ if (!macfb_common_realize(dev, ms, errp)) {
+ return;
+ }
+
+ sysbus_init_mmio(SYS_BUS_DEVICE(s), &ms->mem_ctrl);
+ sysbus_init_mmio(SYS_BUS_DEVICE(s), &ms->mem_vram);
+
+ qdev_init_gpio_out(dev, &ms->irq, 1);
+}
+
+static void macfb_nubus_set_irq(void *opaque, int n, int level)
+{
+ MacfbNubusState *s = NUBUS_MACFB(opaque);
+ NubusDevice *nd = NUBUS_DEVICE(s);
+
+ nubus_set_irq(nd, level);
+}
+
+static void macfb_nubus_realize(DeviceState *dev, Error **errp)
+{
+ NubusDevice *nd = NUBUS_DEVICE(dev);
+ MacfbNubusState *s = NUBUS_MACFB(dev);
+ MacfbNubusDeviceClass *ndc = NUBUS_MACFB_GET_CLASS(dev);
+ MacfbState *ms = &s->macfb;
+
+ ndc->parent_realize(dev, errp);
+ if (*errp) {
+ return;
+ }
+
+ if (!macfb_common_realize(dev, ms, errp)) {
+ return;
+ }
+
+ memory_region_add_subregion(&nd->slot_mem, DAFB_BASE, &ms->mem_ctrl);
+ memory_region_add_subregion(&nd->slot_mem, VIDEO_BASE, &ms->mem_vram);
+
+ ms->irq = qemu_allocate_irq(macfb_nubus_set_irq, s, 0);
+}
+
+static void macfb_nubus_unrealize(DeviceState *dev)
+{
+ MacfbNubusState *s = NUBUS_MACFB(dev);
+ MacfbNubusDeviceClass *ndc = NUBUS_MACFB_GET_CLASS(dev);
+ MacfbState *ms = &s->macfb;
+
+ ndc->parent_unrealize(dev);
+
+ qemu_free_irq(ms->irq);
+}
+
+static void macfb_sysbus_reset(DeviceState *d)
+{
+ MacfbSysBusState *s = MACFB(d);
+ macfb_reset(&s->macfb);
+}
+
+static void macfb_nubus_reset(DeviceState *d)
+{
+ MacfbNubusState *s = NUBUS_MACFB(d);
+ macfb_reset(&s->macfb);
+}
+
+static Property macfb_sysbus_properties[] = {
+ DEFINE_PROP_UINT32("width", MacfbSysBusState, macfb.width, 640),
+ DEFINE_PROP_UINT32("height", MacfbSysBusState, macfb.height, 480),
+ DEFINE_PROP_UINT8("depth", MacfbSysBusState, macfb.depth, 8),
+ DEFINE_PROP_UINT8("display", MacfbSysBusState, macfb.type,
+ MACFB_DISPLAY_VGA),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static Property macfb_nubus_properties[] = {
+ DEFINE_PROP_UINT32("width", MacfbNubusState, macfb.width, 640),
+ DEFINE_PROP_UINT32("height", MacfbNubusState, macfb.height, 480),
+ DEFINE_PROP_UINT8("depth", MacfbNubusState, macfb.depth, 8),
+ DEFINE_PROP_UINT8("display", MacfbNubusState, macfb.type,
+ MACFB_DISPLAY_VGA),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void macfb_sysbus_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = macfb_sysbus_realize;
+ dc->desc = "SysBus Macintosh framebuffer";
+ dc->reset = macfb_sysbus_reset;
+ dc->vmsd = &vmstate_macfb;
+ device_class_set_props(dc, macfb_sysbus_properties);
+}
+
+static void macfb_nubus_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ MacfbNubusDeviceClass *ndc = NUBUS_MACFB_CLASS(klass);
+
+ device_class_set_parent_realize(dc, macfb_nubus_realize,
+ &ndc->parent_realize);
+ device_class_set_parent_unrealize(dc, macfb_nubus_unrealize,
+ &ndc->parent_unrealize);
+ dc->desc = "Nubus Macintosh framebuffer";
+ dc->reset = macfb_nubus_reset;
+ dc->vmsd = &vmstate_macfb;
+ set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories);
+ device_class_set_props(dc, macfb_nubus_properties);
+}
+
+static TypeInfo macfb_sysbus_info = {
+ .name = TYPE_MACFB,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(MacfbSysBusState),
+ .class_init = macfb_sysbus_class_init,
+};
+
+static TypeInfo macfb_nubus_info = {
+ .name = TYPE_NUBUS_MACFB,
+ .parent = TYPE_NUBUS_DEVICE,
+ .instance_size = sizeof(MacfbNubusState),
+ .class_init = macfb_nubus_class_init,
+ .class_size = sizeof(MacfbNubusDeviceClass),
+};
+
+static void macfb_register_types(void)
+{
+ type_register_static(&macfb_sysbus_info);
+ type_register_static(&macfb_nubus_info);
+}
+
+type_init(macfb_register_types)
diff --git a/hw/display/meson.build b/hw/display/meson.build
new file mode 100644
index 000000000..861c43ff9
--- /dev/null
+++ b/hw/display/meson.build
@@ -0,0 +1,100 @@
+hw_display_modules = {}
+
+softmmu_ss.add(when: 'CONFIG_DDC', if_true: files('i2c-ddc.c'))
+softmmu_ss.add(when: 'CONFIG_EDID', if_true: files('edid-generate.c', 'edid-region.c'))
+
+softmmu_ss.add(when: 'CONFIG_FW_CFG_DMA', if_true: files('ramfb.c'))
+softmmu_ss.add(when: 'CONFIG_FW_CFG_DMA', if_true: files('ramfb-standalone.c'))
+
+softmmu_ss.add(when: 'CONFIG_VGA_CIRRUS', if_true: files('cirrus_vga.c'))
+softmmu_ss.add(when: ['CONFIG_VGA_CIRRUS', 'CONFIG_VGA_ISA'], if_true: files('cirrus_vga_isa.c'))
+softmmu_ss.add(when: 'CONFIG_G364FB', if_true: files('g364fb.c'))
+softmmu_ss.add(when: 'CONFIG_JAZZ_LED', if_true: files('jazz_led.c'))
+softmmu_ss.add(when: 'CONFIG_PL110', if_true: files('pl110.c'))
+softmmu_ss.add(when: 'CONFIG_SII9022', if_true: files('sii9022.c'))
+softmmu_ss.add(when: 'CONFIG_SSD0303', if_true: files('ssd0303.c'))
+softmmu_ss.add(when: 'CONFIG_SSD0323', if_true: files('ssd0323.c'))
+softmmu_ss.add(when: 'CONFIG_XEN', if_true: files('xenfb.c'))
+
+softmmu_ss.add(when: 'CONFIG_VGA_PCI', if_true: files('vga-pci.c'))
+softmmu_ss.add(when: 'CONFIG_VGA_ISA', if_true: files('vga-isa.c'))
+softmmu_ss.add(when: 'CONFIG_VGA_ISA_MM', if_true: files('vga-isa-mm.c'))
+softmmu_ss.add(when: 'CONFIG_VMWARE_VGA', if_true: files('vmware_vga.c'))
+softmmu_ss.add(when: 'CONFIG_BOCHS_DISPLAY', if_true: files('bochs-display.c'))
+
+softmmu_ss.add(when: 'CONFIG_BLIZZARD', if_true: files('blizzard.c'))
+softmmu_ss.add(when: 'CONFIG_EXYNOS4', if_true: files('exynos4210_fimd.c'))
+softmmu_ss.add(when: 'CONFIG_FRAMEBUFFER', if_true: files('framebuffer.c'))
+softmmu_ss.add(when: 'CONFIG_ZAURUS', if_true: files('tc6393xb.c'))
+
+softmmu_ss.add(when: 'CONFIG_OMAP', if_true: files('omap_dss.c'))
+softmmu_ss.add(when: 'CONFIG_PXA2XX', if_true: files('pxa2xx_lcd.c'))
+softmmu_ss.add(when: 'CONFIG_RASPI', if_true: files('bcm2835_fb.c'))
+softmmu_ss.add(when: 'CONFIG_SM501', if_true: files('sm501.c'))
+softmmu_ss.add(when: 'CONFIG_TCX', if_true: files('tcx.c'))
+softmmu_ss.add(when: 'CONFIG_CG3', if_true: files('cg3.c'))
+softmmu_ss.add(when: 'CONFIG_MACFB', if_true: files('macfb.c'))
+softmmu_ss.add(when: 'CONFIG_NEXTCUBE', if_true: files('next-fb.c'))
+
+specific_ss.add(when: 'CONFIG_VGA', if_true: files('vga.c'))
+
+if config_all_devices.has_key('CONFIG_QXL')
+ qxl_ss = ss.source_set()
+ qxl_ss.add(when: 'CONFIG_QXL', if_true: [files('qxl.c', 'qxl-logger.c', 'qxl-render.c'),
+ pixman, spice])
+ hw_display_modules += {'qxl': qxl_ss}
+endif
+
+softmmu_ss.add(when: 'CONFIG_DPCD', if_true: files('dpcd.c'))
+softmmu_ss.add(when: 'CONFIG_XLNX_ZYNQMP_ARM', if_true: files('xlnx_dp.c'))
+
+softmmu_ss.add(when: 'CONFIG_ARTIST', if_true: files('artist.c'))
+
+softmmu_ss.add(when: [pixman, 'CONFIG_ATI_VGA'], if_true: files('ati.c', 'ati_2d.c', 'ati_dbg.c'))
+
+if config_all_devices.has_key('CONFIG_VIRTIO_GPU')
+ virtio_gpu_ss = ss.source_set()
+ virtio_gpu_ss.add(when: 'CONFIG_VIRTIO_GPU',
+ if_true: [files('virtio-gpu-base.c', 'virtio-gpu.c'), pixman])
+ virtio_gpu_ss.add(when: 'CONFIG_LINUX', if_true: files('virtio-gpu-udmabuf.c'),
+ if_false: files('virtio-gpu-udmabuf-stubs.c'))
+ virtio_gpu_ss.add(when: 'CONFIG_VHOST_USER_GPU', if_true: files('vhost-user-gpu.c'))
+ hw_display_modules += {'virtio-gpu': virtio_gpu_ss}
+
+ virtio_gpu_gl_ss = ss.source_set()
+ virtio_gpu_gl_ss.add(when: ['CONFIG_VIRTIO_GPU', virgl, opengl],
+ if_true: [files('virtio-gpu-gl.c', 'virtio-gpu-virgl.c'), pixman, virgl])
+ hw_display_modules += {'virtio-gpu-gl': virtio_gpu_gl_ss}
+endif
+
+if config_all_devices.has_key('CONFIG_VIRTIO_PCI')
+ virtio_gpu_pci_ss = ss.source_set()
+ virtio_gpu_pci_ss.add(when: ['CONFIG_VIRTIO_GPU', 'CONFIG_VIRTIO_PCI'],
+ if_true: [files('virtio-gpu-pci.c'), pixman])
+ virtio_gpu_pci_ss.add(when: ['CONFIG_VHOST_USER_GPU', 'CONFIG_VIRTIO_PCI'],
+ if_true: files('vhost-user-gpu-pci.c'))
+ hw_display_modules += {'virtio-gpu-pci': virtio_gpu_pci_ss}
+
+ virtio_gpu_pci_gl_ss = ss.source_set()
+ virtio_gpu_pci_gl_ss.add(when: ['CONFIG_VIRTIO_GPU', 'CONFIG_VIRTIO_PCI', virgl, opengl],
+ if_true: [files('virtio-gpu-pci-gl.c'), pixman])
+ hw_display_modules += {'virtio-gpu-pci-gl': virtio_gpu_pci_gl_ss}
+endif
+
+if config_all_devices.has_key('CONFIG_VIRTIO_VGA')
+ virtio_vga_ss = ss.source_set()
+ virtio_vga_ss.add(when: 'CONFIG_VIRTIO_VGA',
+ if_true: [files('virtio-vga.c'), pixman])
+ virtio_vga_ss.add(when: 'CONFIG_VHOST_USER_VGA',
+ if_true: files('vhost-user-vga.c'))
+ hw_display_modules += {'virtio-vga': virtio_vga_ss}
+
+ virtio_vga_gl_ss = ss.source_set()
+ virtio_vga_gl_ss.add(when: ['CONFIG_VIRTIO_VGA', virgl, opengl],
+ if_true: [files('virtio-vga-gl.c'), pixman])
+ hw_display_modules += {'virtio-vga-gl': virtio_vga_gl_ss}
+endif
+
+specific_ss.add(when: 'CONFIG_OMAP', if_true: files('omap_lcdc.c'))
+
+modules += { 'hw-display': hw_display_modules }
diff --git a/hw/display/next-fb.c b/hw/display/next-fb.c
new file mode 100644
index 000000000..dd6a1aa8a
--- /dev/null
+++ b/hw/display/next-fb.c
@@ -0,0 +1,144 @@
+/*
+ * NeXT Cube/Station Framebuffer Emulation
+ *
+ * Copyright (c) 2011 Bryce Lanham
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "ui/console.h"
+#include "hw/loader.h"
+#include "framebuffer.h"
+#include "ui/pixel_ops.h"
+#include "hw/m68k/next-cube.h"
+#include "qom/object.h"
+
+OBJECT_DECLARE_SIMPLE_TYPE(NeXTFbState, NEXTFB)
+
+struct NeXTFbState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion fb_mr;
+ MemoryRegionSection fbsection;
+ QemuConsole *con;
+
+ uint32_t cols;
+ uint32_t rows;
+ int invalidate;
+};
+
+static void nextfb_draw_line(void *opaque, uint8_t *d, const uint8_t *s,
+ int width, int pitch)
+{
+ NeXTFbState *nfbstate = NEXTFB(opaque);
+ static const uint32_t pal[4] = {
+ 0xFFFFFFFF, 0xFFAAAAAA, 0xFF555555, 0xFF000000
+ };
+ uint32_t *buf = (uint32_t *)d;
+ int i = 0;
+
+ for (i = 0; i < nfbstate->cols / 4; i++) {
+ int j = i * 4;
+ uint8_t src = s[i];
+ buf[j + 3] = pal[src & 0x3];
+ src >>= 2;
+ buf[j + 2] = pal[src & 0x3];
+ src >>= 2;
+ buf[j + 1] = pal[src & 0x3];
+ src >>= 2;
+ buf[j + 0] = pal[src & 0x3];
+ }
+}
+
+static void nextfb_update(void *opaque)
+{
+ NeXTFbState *s = NEXTFB(opaque);
+ int dest_width = 4;
+ int src_width;
+ int first = 0;
+ int last = 0;
+ DisplaySurface *surface = qemu_console_surface(s->con);
+
+ src_width = s->cols / 4 + 8;
+ dest_width = s->cols * 4;
+
+ if (s->invalidate) {
+ framebuffer_update_memory_section(&s->fbsection, &s->fb_mr, 0,
+ s->cols, src_width);
+ s->invalidate = 0;
+ }
+
+ framebuffer_update_display(surface, &s->fbsection, s->cols, s->rows,
+ src_width, dest_width, 0, 1, nextfb_draw_line,
+ s, &first, &last);
+
+ dpy_gfx_update(s->con, 0, 0, s->cols, s->rows);
+}
+
+static void nextfb_invalidate(void *opaque)
+{
+ NeXTFbState *s = NEXTFB(opaque);
+ s->invalidate = 1;
+}
+
+static const GraphicHwOps nextfb_ops = {
+ .invalidate = nextfb_invalidate,
+ .gfx_update = nextfb_update,
+};
+
+static void nextfb_realize(DeviceState *dev, Error **errp)
+{
+ NeXTFbState *s = NEXTFB(dev);
+
+ memory_region_init_ram(&s->fb_mr, OBJECT(dev), "next-video", 0x1CB100,
+ &error_fatal);
+ sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->fb_mr);
+
+ s->invalidate = 1;
+ s->cols = 1120;
+ s->rows = 832;
+
+ s->con = graphic_console_init(dev, 0, &nextfb_ops, s);
+ qemu_console_resize(s->con, s->cols, s->rows);
+}
+
+static void nextfb_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories);
+ dc->realize = nextfb_realize;
+
+ /* Note: This device does not any state that we have to reset or migrate */
+}
+
+static const TypeInfo nextfb_info = {
+ .name = TYPE_NEXTFB,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(NeXTFbState),
+ .class_init = nextfb_class_init,
+};
+
+static void nextfb_register_types(void)
+{
+ type_register_static(&nextfb_info);
+}
+
+type_init(nextfb_register_types)
diff --git a/hw/display/omap_dss.c b/hw/display/omap_dss.c
new file mode 100644
index 000000000..21fde58a2
--- /dev/null
+++ b/hw/display/omap_dss.c
@@ -0,0 +1,1093 @@
+/*
+ * OMAP2 Display Subsystem.
+ *
+ * Copyright (C) 2008 Nokia Corporation
+ * Written by Andrzej Zaborowski <andrew@openedhand.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/hw.h"
+#include "hw/irq.h"
+#include "ui/console.h"
+#include "hw/arm/omap.h"
+
+struct omap_dss_s {
+ qemu_irq irq;
+ qemu_irq drq;
+ DisplayState *state;
+ MemoryRegion iomem_diss1, iomem_disc1, iomem_rfbi1, iomem_venc1, iomem_im3;
+
+ int autoidle;
+ int control;
+ int enable;
+
+ struct omap_dss_panel_s {
+ int enable;
+ int nx;
+ int ny;
+
+ int x;
+ int y;
+ } dig, lcd;
+
+ struct {
+ uint32_t idlemode;
+ uint32_t irqst;
+ uint32_t irqen;
+ uint32_t control;
+ uint32_t config;
+ uint32_t capable;
+ uint32_t timing[4];
+ int line;
+ uint32_t bg[2];
+ uint32_t trans[2];
+
+ struct omap_dss_plane_s {
+ int enable;
+ int bpp;
+ int posx;
+ int posy;
+ int nx;
+ int ny;
+
+ hwaddr addr[3];
+
+ uint32_t attr;
+ uint32_t tresh;
+ int rowinc;
+ int colinc;
+ int wininc;
+ } l[3];
+
+ int invalidate;
+ uint16_t palette[256];
+ } dispc;
+
+ struct {
+ int idlemode;
+ uint32_t control;
+ int enable;
+ int pixels;
+ int busy;
+ int skiplines;
+ uint16_t rxbuf;
+ uint32_t config[2];
+ uint32_t time[4];
+ uint32_t data[6];
+ uint16_t vsync;
+ uint16_t hsync;
+ struct rfbi_chip_s *chip[2];
+ } rfbi;
+};
+
+static void omap_dispc_interrupt_update(struct omap_dss_s *s)
+{
+ qemu_set_irq(s->irq, s->dispc.irqst & s->dispc.irqen);
+}
+
+static void omap_rfbi_reset(struct omap_dss_s *s)
+{
+ s->rfbi.idlemode = 0;
+ s->rfbi.control = 2;
+ s->rfbi.enable = 0;
+ s->rfbi.pixels = 0;
+ s->rfbi.skiplines = 0;
+ s->rfbi.busy = 0;
+ s->rfbi.config[0] = 0x00310000;
+ s->rfbi.config[1] = 0x00310000;
+ s->rfbi.time[0] = 0;
+ s->rfbi.time[1] = 0;
+ s->rfbi.time[2] = 0;
+ s->rfbi.time[3] = 0;
+ s->rfbi.data[0] = 0;
+ s->rfbi.data[1] = 0;
+ s->rfbi.data[2] = 0;
+ s->rfbi.data[3] = 0;
+ s->rfbi.data[4] = 0;
+ s->rfbi.data[5] = 0;
+ s->rfbi.vsync = 0;
+ s->rfbi.hsync = 0;
+}
+
+void omap_dss_reset(struct omap_dss_s *s)
+{
+ s->autoidle = 0;
+ s->control = 0;
+ s->enable = 0;
+
+ s->dig.enable = 0;
+ s->dig.nx = 1;
+ s->dig.ny = 1;
+
+ s->lcd.enable = 0;
+ s->lcd.nx = 1;
+ s->lcd.ny = 1;
+
+ s->dispc.idlemode = 0;
+ s->dispc.irqst = 0;
+ s->dispc.irqen = 0;
+ s->dispc.control = 0;
+ s->dispc.config = 0;
+ s->dispc.capable = 0x161;
+ s->dispc.timing[0] = 0;
+ s->dispc.timing[1] = 0;
+ s->dispc.timing[2] = 0;
+ s->dispc.timing[3] = 0;
+ s->dispc.line = 0;
+ s->dispc.bg[0] = 0;
+ s->dispc.bg[1] = 0;
+ s->dispc.trans[0] = 0;
+ s->dispc.trans[1] = 0;
+
+ s->dispc.l[0].enable = 0;
+ s->dispc.l[0].bpp = 0;
+ s->dispc.l[0].addr[0] = 0;
+ s->dispc.l[0].addr[1] = 0;
+ s->dispc.l[0].addr[2] = 0;
+ s->dispc.l[0].posx = 0;
+ s->dispc.l[0].posy = 0;
+ s->dispc.l[0].nx = 1;
+ s->dispc.l[0].ny = 1;
+ s->dispc.l[0].attr = 0;
+ s->dispc.l[0].tresh = 0;
+ s->dispc.l[0].rowinc = 1;
+ s->dispc.l[0].colinc = 1;
+ s->dispc.l[0].wininc = 0;
+
+ omap_rfbi_reset(s);
+ omap_dispc_interrupt_update(s);
+}
+
+static uint64_t omap_diss_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_dss_s *s = (struct omap_dss_s *) opaque;
+
+ if (size != 4) {
+ return omap_badwidth_read32(opaque, addr);
+ }
+
+ switch (addr) {
+ case 0x00: /* DSS_REVISIONNUMBER */
+ return 0x20;
+
+ case 0x10: /* DSS_SYSCONFIG */
+ return s->autoidle;
+
+ case 0x14: /* DSS_SYSSTATUS */
+ return 1; /* RESETDONE */
+
+ case 0x40: /* DSS_CONTROL */
+ return s->control;
+
+ case 0x50: /* DSS_PSA_LCD_REG_1 */
+ case 0x54: /* DSS_PSA_LCD_REG_2 */
+ case 0x58: /* DSS_PSA_VIDEO_REG */
+ /* TODO: fake some values when appropriate s->control bits are set */
+ return 0;
+
+ case 0x5c: /* DSS_STATUS */
+ return 1 + (s->control & 1);
+
+ default:
+ break;
+ }
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_diss_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap_dss_s *s = (struct omap_dss_s *) opaque;
+
+ if (size != 4) {
+ omap_badwidth_write32(opaque, addr, value);
+ return;
+ }
+
+ switch (addr) {
+ case 0x00: /* DSS_REVISIONNUMBER */
+ case 0x14: /* DSS_SYSSTATUS */
+ case 0x50: /* DSS_PSA_LCD_REG_1 */
+ case 0x54: /* DSS_PSA_LCD_REG_2 */
+ case 0x58: /* DSS_PSA_VIDEO_REG */
+ case 0x5c: /* DSS_STATUS */
+ OMAP_RO_REG(addr);
+ break;
+
+ case 0x10: /* DSS_SYSCONFIG */
+ if (value & 2) /* SOFTRESET */
+ omap_dss_reset(s);
+ s->autoidle = value & 1;
+ break;
+
+ case 0x40: /* DSS_CONTROL */
+ s->control = value & 0x3dd;
+ break;
+
+ default:
+ OMAP_BAD_REG(addr);
+ }
+}
+
+static const MemoryRegionOps omap_diss_ops = {
+ .read = omap_diss_read,
+ .write = omap_diss_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static uint64_t omap_disc_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_dss_s *s = (struct omap_dss_s *) opaque;
+
+ if (size != 4) {
+ return omap_badwidth_read32(opaque, addr);
+ }
+
+ switch (addr) {
+ case 0x000: /* DISPC_REVISION */
+ return 0x20;
+
+ case 0x010: /* DISPC_SYSCONFIG */
+ return s->dispc.idlemode;
+
+ case 0x014: /* DISPC_SYSSTATUS */
+ return 1; /* RESETDONE */
+
+ case 0x018: /* DISPC_IRQSTATUS */
+ return s->dispc.irqst;
+
+ case 0x01c: /* DISPC_IRQENABLE */
+ return s->dispc.irqen;
+
+ case 0x040: /* DISPC_CONTROL */
+ return s->dispc.control;
+
+ case 0x044: /* DISPC_CONFIG */
+ return s->dispc.config;
+
+ case 0x048: /* DISPC_CAPABLE */
+ return s->dispc.capable;
+
+ case 0x04c: /* DISPC_DEFAULT_COLOR0 */
+ return s->dispc.bg[0];
+ case 0x050: /* DISPC_DEFAULT_COLOR1 */
+ return s->dispc.bg[1];
+ case 0x054: /* DISPC_TRANS_COLOR0 */
+ return s->dispc.trans[0];
+ case 0x058: /* DISPC_TRANS_COLOR1 */
+ return s->dispc.trans[1];
+
+ case 0x05c: /* DISPC_LINE_STATUS */
+ return 0x7ff;
+ case 0x060: /* DISPC_LINE_NUMBER */
+ return s->dispc.line;
+
+ case 0x064: /* DISPC_TIMING_H */
+ return s->dispc.timing[0];
+ case 0x068: /* DISPC_TIMING_V */
+ return s->dispc.timing[1];
+ case 0x06c: /* DISPC_POL_FREQ */
+ return s->dispc.timing[2];
+ case 0x070: /* DISPC_DIVISOR */
+ return s->dispc.timing[3];
+
+ case 0x078: /* DISPC_SIZE_DIG */
+ return ((s->dig.ny - 1) << 16) | (s->dig.nx - 1);
+ case 0x07c: /* DISPC_SIZE_LCD */
+ return ((s->lcd.ny - 1) << 16) | (s->lcd.nx - 1);
+
+ case 0x080: /* DISPC_GFX_BA0 */
+ return s->dispc.l[0].addr[0];
+ case 0x084: /* DISPC_GFX_BA1 */
+ return s->dispc.l[0].addr[1];
+ case 0x088: /* DISPC_GFX_POSITION */
+ return (s->dispc.l[0].posy << 16) | s->dispc.l[0].posx;
+ case 0x08c: /* DISPC_GFX_SIZE */
+ return ((s->dispc.l[0].ny - 1) << 16) | (s->dispc.l[0].nx - 1);
+ case 0x0a0: /* DISPC_GFX_ATTRIBUTES */
+ return s->dispc.l[0].attr;
+ case 0x0a4: /* DISPC_GFX_FIFO_TRESHOLD */
+ return s->dispc.l[0].tresh;
+ case 0x0a8: /* DISPC_GFX_FIFO_SIZE_STATUS */
+ return 256;
+ case 0x0ac: /* DISPC_GFX_ROW_INC */
+ return s->dispc.l[0].rowinc;
+ case 0x0b0: /* DISPC_GFX_PIXEL_INC */
+ return s->dispc.l[0].colinc;
+ case 0x0b4: /* DISPC_GFX_WINDOW_SKIP */
+ return s->dispc.l[0].wininc;
+ case 0x0b8: /* DISPC_GFX_TABLE_BA */
+ return s->dispc.l[0].addr[2];
+
+ case 0x0bc: /* DISPC_VID1_BA0 */
+ case 0x0c0: /* DISPC_VID1_BA1 */
+ case 0x0c4: /* DISPC_VID1_POSITION */
+ case 0x0c8: /* DISPC_VID1_SIZE */
+ case 0x0cc: /* DISPC_VID1_ATTRIBUTES */
+ case 0x0d0: /* DISPC_VID1_FIFO_TRESHOLD */
+ case 0x0d4: /* DISPC_VID1_FIFO_SIZE_STATUS */
+ case 0x0d8: /* DISPC_VID1_ROW_INC */
+ case 0x0dc: /* DISPC_VID1_PIXEL_INC */
+ case 0x0e0: /* DISPC_VID1_FIR */
+ case 0x0e4: /* DISPC_VID1_PICTURE_SIZE */
+ case 0x0e8: /* DISPC_VID1_ACCU0 */
+ case 0x0ec: /* DISPC_VID1_ACCU1 */
+ case 0x0f0 ... 0x140: /* DISPC_VID1_FIR_COEF, DISPC_VID1_CONV_COEF */
+ case 0x14c: /* DISPC_VID2_BA0 */
+ case 0x150: /* DISPC_VID2_BA1 */
+ case 0x154: /* DISPC_VID2_POSITION */
+ case 0x158: /* DISPC_VID2_SIZE */
+ case 0x15c: /* DISPC_VID2_ATTRIBUTES */
+ case 0x160: /* DISPC_VID2_FIFO_TRESHOLD */
+ case 0x164: /* DISPC_VID2_FIFO_SIZE_STATUS */
+ case 0x168: /* DISPC_VID2_ROW_INC */
+ case 0x16c: /* DISPC_VID2_PIXEL_INC */
+ case 0x170: /* DISPC_VID2_FIR */
+ case 0x174: /* DISPC_VID2_PICTURE_SIZE */
+ case 0x178: /* DISPC_VID2_ACCU0 */
+ case 0x17c: /* DISPC_VID2_ACCU1 */
+ case 0x180 ... 0x1d0: /* DISPC_VID2_FIR_COEF, DISPC_VID2_CONV_COEF */
+ case 0x1d4: /* DISPC_DATA_CYCLE1 */
+ case 0x1d8: /* DISPC_DATA_CYCLE2 */
+ case 0x1dc: /* DISPC_DATA_CYCLE3 */
+ return 0;
+
+ default:
+ break;
+ }
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_disc_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap_dss_s *s = (struct omap_dss_s *) opaque;
+
+ if (size != 4) {
+ omap_badwidth_write32(opaque, addr, value);
+ return;
+ }
+
+ switch (addr) {
+ case 0x010: /* DISPC_SYSCONFIG */
+ if (value & 2) /* SOFTRESET */
+ omap_dss_reset(s);
+ s->dispc.idlemode = value & 0x301b;
+ break;
+
+ case 0x018: /* DISPC_IRQSTATUS */
+ s->dispc.irqst &= ~value;
+ omap_dispc_interrupt_update(s);
+ break;
+
+ case 0x01c: /* DISPC_IRQENABLE */
+ s->dispc.irqen = value & 0xffff;
+ omap_dispc_interrupt_update(s);
+ break;
+
+ case 0x040: /* DISPC_CONTROL */
+ s->dispc.control = value & 0x07ff9fff;
+ s->dig.enable = (value >> 1) & 1;
+ s->lcd.enable = (value >> 0) & 1;
+ if (value & (1 << 12)) /* OVERLAY_OPTIMIZATION */
+ if (!((s->dispc.l[1].attr | s->dispc.l[2].attr) & 1)) {
+ fprintf(stderr, "%s: Overlay Optimization when no overlay "
+ "region effectively exists leads to "
+ "unpredictable behaviour!\n", __func__);
+ }
+ if (value & (1 << 6)) { /* GODIGITAL */
+ /* XXX: Shadowed fields are:
+ * s->dispc.config
+ * s->dispc.capable
+ * s->dispc.bg[0]
+ * s->dispc.bg[1]
+ * s->dispc.trans[0]
+ * s->dispc.trans[1]
+ * s->dispc.line
+ * s->dispc.timing[0]
+ * s->dispc.timing[1]
+ * s->dispc.timing[2]
+ * s->dispc.timing[3]
+ * s->lcd.nx
+ * s->lcd.ny
+ * s->dig.nx
+ * s->dig.ny
+ * s->dispc.l[0].addr[0]
+ * s->dispc.l[0].addr[1]
+ * s->dispc.l[0].addr[2]
+ * s->dispc.l[0].posx
+ * s->dispc.l[0].posy
+ * s->dispc.l[0].nx
+ * s->dispc.l[0].ny
+ * s->dispc.l[0].tresh
+ * s->dispc.l[0].rowinc
+ * s->dispc.l[0].colinc
+ * s->dispc.l[0].wininc
+ * All they need to be loaded here from their shadow registers.
+ */
+ }
+ if (value & (1 << 5)) { /* GOLCD */
+ /* XXX: Likewise for LCD here. */
+ }
+ s->dispc.invalidate = 1;
+ break;
+
+ case 0x044: /* DISPC_CONFIG */
+ s->dispc.config = value & 0x3fff;
+ /* XXX:
+ * bits 2:1 (LOADMODE) reset to 0 after set to 1 and palette loaded
+ * bits 2:1 (LOADMODE) reset to 2 after set to 3 and palette loaded
+ */
+ s->dispc.invalidate = 1;
+ break;
+
+ case 0x048: /* DISPC_CAPABLE */
+ s->dispc.capable = value & 0x3ff;
+ break;
+
+ case 0x04c: /* DISPC_DEFAULT_COLOR0 */
+ s->dispc.bg[0] = value & 0xffffff;
+ s->dispc.invalidate = 1;
+ break;
+ case 0x050: /* DISPC_DEFAULT_COLOR1 */
+ s->dispc.bg[1] = value & 0xffffff;
+ s->dispc.invalidate = 1;
+ break;
+ case 0x054: /* DISPC_TRANS_COLOR0 */
+ s->dispc.trans[0] = value & 0xffffff;
+ s->dispc.invalidate = 1;
+ break;
+ case 0x058: /* DISPC_TRANS_COLOR1 */
+ s->dispc.trans[1] = value & 0xffffff;
+ s->dispc.invalidate = 1;
+ break;
+
+ case 0x060: /* DISPC_LINE_NUMBER */
+ s->dispc.line = value & 0x7ff;
+ break;
+
+ case 0x064: /* DISPC_TIMING_H */
+ s->dispc.timing[0] = value & 0x0ff0ff3f;
+ break;
+ case 0x068: /* DISPC_TIMING_V */
+ s->dispc.timing[1] = value & 0x0ff0ff3f;
+ break;
+ case 0x06c: /* DISPC_POL_FREQ */
+ s->dispc.timing[2] = value & 0x0003ffff;
+ break;
+ case 0x070: /* DISPC_DIVISOR */
+ s->dispc.timing[3] = value & 0x00ff00ff;
+ break;
+
+ case 0x078: /* DISPC_SIZE_DIG */
+ s->dig.nx = ((value >> 0) & 0x7ff) + 1; /* PPL */
+ s->dig.ny = ((value >> 16) & 0x7ff) + 1; /* LPP */
+ s->dispc.invalidate = 1;
+ break;
+ case 0x07c: /* DISPC_SIZE_LCD */
+ s->lcd.nx = ((value >> 0) & 0x7ff) + 1; /* PPL */
+ s->lcd.ny = ((value >> 16) & 0x7ff) + 1; /* LPP */
+ s->dispc.invalidate = 1;
+ break;
+ case 0x080: /* DISPC_GFX_BA0 */
+ s->dispc.l[0].addr[0] = (hwaddr) value;
+ s->dispc.invalidate = 1;
+ break;
+ case 0x084: /* DISPC_GFX_BA1 */
+ s->dispc.l[0].addr[1] = (hwaddr) value;
+ s->dispc.invalidate = 1;
+ break;
+ case 0x088: /* DISPC_GFX_POSITION */
+ s->dispc.l[0].posx = ((value >> 0) & 0x7ff); /* GFXPOSX */
+ s->dispc.l[0].posy = ((value >> 16) & 0x7ff); /* GFXPOSY */
+ s->dispc.invalidate = 1;
+ break;
+ case 0x08c: /* DISPC_GFX_SIZE */
+ s->dispc.l[0].nx = ((value >> 0) & 0x7ff) + 1; /* GFXSIZEX */
+ s->dispc.l[0].ny = ((value >> 16) & 0x7ff) + 1; /* GFXSIZEY */
+ s->dispc.invalidate = 1;
+ break;
+ case 0x0a0: /* DISPC_GFX_ATTRIBUTES */
+ s->dispc.l[0].attr = value & 0x7ff;
+ if (value & (3 << 9))
+ fprintf(stderr, "%s: Big-endian pixel format not supported\n",
+ __func__);
+ s->dispc.l[0].enable = value & 1;
+ s->dispc.l[0].bpp = (value >> 1) & 0xf;
+ s->dispc.invalidate = 1;
+ break;
+ case 0x0a4: /* DISPC_GFX_FIFO_TRESHOLD */
+ s->dispc.l[0].tresh = value & 0x01ff01ff;
+ break;
+ case 0x0ac: /* DISPC_GFX_ROW_INC */
+ s->dispc.l[0].rowinc = value;
+ s->dispc.invalidate = 1;
+ break;
+ case 0x0b0: /* DISPC_GFX_PIXEL_INC */
+ s->dispc.l[0].colinc = value;
+ s->dispc.invalidate = 1;
+ break;
+ case 0x0b4: /* DISPC_GFX_WINDOW_SKIP */
+ s->dispc.l[0].wininc = value;
+ break;
+ case 0x0b8: /* DISPC_GFX_TABLE_BA */
+ s->dispc.l[0].addr[2] = (hwaddr) value;
+ s->dispc.invalidate = 1;
+ break;
+
+ case 0x0bc: /* DISPC_VID1_BA0 */
+ case 0x0c0: /* DISPC_VID1_BA1 */
+ case 0x0c4: /* DISPC_VID1_POSITION */
+ case 0x0c8: /* DISPC_VID1_SIZE */
+ case 0x0cc: /* DISPC_VID1_ATTRIBUTES */
+ case 0x0d0: /* DISPC_VID1_FIFO_TRESHOLD */
+ case 0x0d8: /* DISPC_VID1_ROW_INC */
+ case 0x0dc: /* DISPC_VID1_PIXEL_INC */
+ case 0x0e0: /* DISPC_VID1_FIR */
+ case 0x0e4: /* DISPC_VID1_PICTURE_SIZE */
+ case 0x0e8: /* DISPC_VID1_ACCU0 */
+ case 0x0ec: /* DISPC_VID1_ACCU1 */
+ case 0x0f0 ... 0x140: /* DISPC_VID1_FIR_COEF, DISPC_VID1_CONV_COEF */
+ case 0x14c: /* DISPC_VID2_BA0 */
+ case 0x150: /* DISPC_VID2_BA1 */
+ case 0x154: /* DISPC_VID2_POSITION */
+ case 0x158: /* DISPC_VID2_SIZE */
+ case 0x15c: /* DISPC_VID2_ATTRIBUTES */
+ case 0x160: /* DISPC_VID2_FIFO_TRESHOLD */
+ case 0x168: /* DISPC_VID2_ROW_INC */
+ case 0x16c: /* DISPC_VID2_PIXEL_INC */
+ case 0x170: /* DISPC_VID2_FIR */
+ case 0x174: /* DISPC_VID2_PICTURE_SIZE */
+ case 0x178: /* DISPC_VID2_ACCU0 */
+ case 0x17c: /* DISPC_VID2_ACCU1 */
+ case 0x180 ... 0x1d0: /* DISPC_VID2_FIR_COEF, DISPC_VID2_CONV_COEF */
+ case 0x1d4: /* DISPC_DATA_CYCLE1 */
+ case 0x1d8: /* DISPC_DATA_CYCLE2 */
+ case 0x1dc: /* DISPC_DATA_CYCLE3 */
+ break;
+
+ default:
+ OMAP_BAD_REG(addr);
+ }
+}
+
+static const MemoryRegionOps omap_disc_ops = {
+ .read = omap_disc_read,
+ .write = omap_disc_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void omap_rfbi_transfer_stop(struct omap_dss_s *s)
+{
+ if (!s->rfbi.busy)
+ return;
+
+ /* TODO: in non-Bypass mode we probably need to just deassert the DRQ. */
+
+ s->rfbi.busy = 0;
+}
+
+static void omap_rfbi_transfer_start(struct omap_dss_s *s)
+{
+ void *data;
+ hwaddr len;
+ hwaddr data_addr;
+ int pitch;
+ static void *bounce_buffer;
+ static hwaddr bounce_len;
+
+ if (!s->rfbi.enable || s->rfbi.busy)
+ return;
+
+ if (s->rfbi.control & (1 << 1)) { /* BYPASS */
+ /* TODO: in non-Bypass mode we probably need to just assert the
+ * DRQ and wait for DMA to write the pixels. */
+ qemu_log_mask(LOG_UNIMP, "%s: Bypass mode unimplemented\n", __func__);
+ return;
+ }
+
+ if (!(s->dispc.control & (1 << 11))) /* RFBIMODE */
+ return;
+ /* TODO: check that LCD output is enabled in DISPC. */
+
+ s->rfbi.busy = 1;
+
+ len = s->rfbi.pixels * 2;
+
+ data_addr = s->dispc.l[0].addr[0];
+ data = cpu_physical_memory_map(data_addr, &len, false);
+ if (data && len != s->rfbi.pixels * 2) {
+ cpu_physical_memory_unmap(data, len, 0, 0);
+ data = NULL;
+ len = s->rfbi.pixels * 2;
+ }
+ if (!data) {
+ if (len > bounce_len) {
+ bounce_buffer = g_realloc(bounce_buffer, len);
+ }
+ data = bounce_buffer;
+ cpu_physical_memory_read(data_addr, data, len);
+ }
+
+ /* TODO bpp */
+ s->rfbi.pixels = 0;
+
+ /* TODO: negative values */
+ pitch = s->dispc.l[0].nx + (s->dispc.l[0].rowinc - 1) / 2;
+
+ if ((s->rfbi.control & (1 << 2)) && s->rfbi.chip[0])
+ s->rfbi.chip[0]->block(s->rfbi.chip[0]->opaque, 1, data, len, pitch);
+ if ((s->rfbi.control & (1 << 3)) && s->rfbi.chip[1])
+ s->rfbi.chip[1]->block(s->rfbi.chip[1]->opaque, 1, data, len, pitch);
+
+ if (data != bounce_buffer) {
+ cpu_physical_memory_unmap(data, len, 0, len);
+ }
+
+ omap_rfbi_transfer_stop(s);
+
+ /* TODO */
+ s->dispc.irqst |= 1; /* FRAMEDONE */
+ omap_dispc_interrupt_update(s);
+}
+
+static uint64_t omap_rfbi_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_dss_s *s = (struct omap_dss_s *) opaque;
+
+ if (size != 4) {
+ return omap_badwidth_read32(opaque, addr);
+ }
+
+ switch (addr) {
+ case 0x00: /* RFBI_REVISION */
+ return 0x10;
+
+ case 0x10: /* RFBI_SYSCONFIG */
+ return s->rfbi.idlemode;
+
+ case 0x14: /* RFBI_SYSSTATUS */
+ return 1 | (s->rfbi.busy << 8); /* RESETDONE */
+
+ case 0x40: /* RFBI_CONTROL */
+ return s->rfbi.control;
+
+ case 0x44: /* RFBI_PIXELCNT */
+ return s->rfbi.pixels;
+
+ case 0x48: /* RFBI_LINE_NUMBER */
+ return s->rfbi.skiplines;
+
+ case 0x58: /* RFBI_READ */
+ case 0x5c: /* RFBI_STATUS */
+ return s->rfbi.rxbuf;
+
+ case 0x60: /* RFBI_CONFIG0 */
+ return s->rfbi.config[0];
+ case 0x64: /* RFBI_ONOFF_TIME0 */
+ return s->rfbi.time[0];
+ case 0x68: /* RFBI_CYCLE_TIME0 */
+ return s->rfbi.time[1];
+ case 0x6c: /* RFBI_DATA_CYCLE1_0 */
+ return s->rfbi.data[0];
+ case 0x70: /* RFBI_DATA_CYCLE2_0 */
+ return s->rfbi.data[1];
+ case 0x74: /* RFBI_DATA_CYCLE3_0 */
+ return s->rfbi.data[2];
+
+ case 0x78: /* RFBI_CONFIG1 */
+ return s->rfbi.config[1];
+ case 0x7c: /* RFBI_ONOFF_TIME1 */
+ return s->rfbi.time[2];
+ case 0x80: /* RFBI_CYCLE_TIME1 */
+ return s->rfbi.time[3];
+ case 0x84: /* RFBI_DATA_CYCLE1_1 */
+ return s->rfbi.data[3];
+ case 0x88: /* RFBI_DATA_CYCLE2_1 */
+ return s->rfbi.data[4];
+ case 0x8c: /* RFBI_DATA_CYCLE3_1 */
+ return s->rfbi.data[5];
+
+ case 0x90: /* RFBI_VSYNC_WIDTH */
+ return s->rfbi.vsync;
+ case 0x94: /* RFBI_HSYNC_WIDTH */
+ return s->rfbi.hsync;
+ }
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_rfbi_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap_dss_s *s = (struct omap_dss_s *) opaque;
+
+ if (size != 4) {
+ omap_badwidth_write32(opaque, addr, value);
+ return;
+ }
+
+ switch (addr) {
+ case 0x10: /* RFBI_SYSCONFIG */
+ if (value & 2) /* SOFTRESET */
+ omap_rfbi_reset(s);
+ s->rfbi.idlemode = value & 0x19;
+ break;
+
+ case 0x40: /* RFBI_CONTROL */
+ s->rfbi.control = value & 0xf;
+ s->rfbi.enable = value & 1;
+ if (value & (1 << 4) && /* ITE */
+ !(s->rfbi.config[0] & s->rfbi.config[1] & 0xc))
+ omap_rfbi_transfer_start(s);
+ break;
+
+ case 0x44: /* RFBI_PIXELCNT */
+ s->rfbi.pixels = value;
+ break;
+
+ case 0x48: /* RFBI_LINE_NUMBER */
+ s->rfbi.skiplines = value & 0x7ff;
+ break;
+
+ case 0x4c: /* RFBI_CMD */
+ if ((s->rfbi.control & (1 << 2)) && s->rfbi.chip[0])
+ s->rfbi.chip[0]->write(s->rfbi.chip[0]->opaque, 0, value & 0xffff);
+ if ((s->rfbi.control & (1 << 3)) && s->rfbi.chip[1])
+ s->rfbi.chip[1]->write(s->rfbi.chip[1]->opaque, 0, value & 0xffff);
+ break;
+ case 0x50: /* RFBI_PARAM */
+ if ((s->rfbi.control & (1 << 2)) && s->rfbi.chip[0])
+ s->rfbi.chip[0]->write(s->rfbi.chip[0]->opaque, 1, value & 0xffff);
+ if ((s->rfbi.control & (1 << 3)) && s->rfbi.chip[1])
+ s->rfbi.chip[1]->write(s->rfbi.chip[1]->opaque, 1, value & 0xffff);
+ break;
+ case 0x54: /* RFBI_DATA */
+ /* TODO: take into account the format set up in s->rfbi.config[?] and
+ * s->rfbi.data[?], but special-case the most usual scenario so that
+ * speed doesn't suffer. */
+ if ((s->rfbi.control & (1 << 2)) && s->rfbi.chip[0]) {
+ s->rfbi.chip[0]->write(s->rfbi.chip[0]->opaque, 1, value & 0xffff);
+ s->rfbi.chip[0]->write(s->rfbi.chip[0]->opaque, 1, value >> 16);
+ }
+ if ((s->rfbi.control & (1 << 3)) && s->rfbi.chip[1]) {
+ s->rfbi.chip[1]->write(s->rfbi.chip[1]->opaque, 1, value & 0xffff);
+ s->rfbi.chip[1]->write(s->rfbi.chip[1]->opaque, 1, value >> 16);
+ }
+ if (!-- s->rfbi.pixels)
+ omap_rfbi_transfer_stop(s);
+ break;
+ case 0x58: /* RFBI_READ */
+ if ((s->rfbi.control & (1 << 2)) && s->rfbi.chip[0])
+ s->rfbi.rxbuf = s->rfbi.chip[0]->read(s->rfbi.chip[0]->opaque, 1);
+ else if ((s->rfbi.control & (1 << 3)) && s->rfbi.chip[1])
+ s->rfbi.rxbuf = s->rfbi.chip[1]->read(s->rfbi.chip[1]->opaque, 1);
+ if (!-- s->rfbi.pixels)
+ omap_rfbi_transfer_stop(s);
+ break;
+
+ case 0x5c: /* RFBI_STATUS */
+ if ((s->rfbi.control & (1 << 2)) && s->rfbi.chip[0])
+ s->rfbi.rxbuf = s->rfbi.chip[0]->read(s->rfbi.chip[0]->opaque, 0);
+ else if ((s->rfbi.control & (1 << 3)) && s->rfbi.chip[1])
+ s->rfbi.rxbuf = s->rfbi.chip[1]->read(s->rfbi.chip[1]->opaque, 0);
+ if (!-- s->rfbi.pixels)
+ omap_rfbi_transfer_stop(s);
+ break;
+
+ case 0x60: /* RFBI_CONFIG0 */
+ s->rfbi.config[0] = value & 0x003f1fff;
+ break;
+
+ case 0x64: /* RFBI_ONOFF_TIME0 */
+ s->rfbi.time[0] = value & 0x3fffffff;
+ break;
+ case 0x68: /* RFBI_CYCLE_TIME0 */
+ s->rfbi.time[1] = value & 0x0fffffff;
+ break;
+ case 0x6c: /* RFBI_DATA_CYCLE1_0 */
+ s->rfbi.data[0] = value & 0x0f1f0f1f;
+ break;
+ case 0x70: /* RFBI_DATA_CYCLE2_0 */
+ s->rfbi.data[1] = value & 0x0f1f0f1f;
+ break;
+ case 0x74: /* RFBI_DATA_CYCLE3_0 */
+ s->rfbi.data[2] = value & 0x0f1f0f1f;
+ break;
+ case 0x78: /* RFBI_CONFIG1 */
+ s->rfbi.config[1] = value & 0x003f1fff;
+ break;
+
+ case 0x7c: /* RFBI_ONOFF_TIME1 */
+ s->rfbi.time[2] = value & 0x3fffffff;
+ break;
+ case 0x80: /* RFBI_CYCLE_TIME1 */
+ s->rfbi.time[3] = value & 0x0fffffff;
+ break;
+ case 0x84: /* RFBI_DATA_CYCLE1_1 */
+ s->rfbi.data[3] = value & 0x0f1f0f1f;
+ break;
+ case 0x88: /* RFBI_DATA_CYCLE2_1 */
+ s->rfbi.data[4] = value & 0x0f1f0f1f;
+ break;
+ case 0x8c: /* RFBI_DATA_CYCLE3_1 */
+ s->rfbi.data[5] = value & 0x0f1f0f1f;
+ break;
+
+ case 0x90: /* RFBI_VSYNC_WIDTH */
+ s->rfbi.vsync = value & 0xffff;
+ break;
+ case 0x94: /* RFBI_HSYNC_WIDTH */
+ s->rfbi.hsync = value & 0xffff;
+ break;
+
+ default:
+ OMAP_BAD_REG(addr);
+ }
+}
+
+static const MemoryRegionOps omap_rfbi_ops = {
+ .read = omap_rfbi_read,
+ .write = omap_rfbi_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static uint64_t omap_venc_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ if (size != 4) {
+ return omap_badwidth_read32(opaque, addr);
+ }
+
+ switch (addr) {
+ case 0x00: /* REV_ID */
+ case 0x04: /* STATUS */
+ case 0x08: /* F_CONTROL */
+ case 0x10: /* VIDOUT_CTRL */
+ case 0x14: /* SYNC_CTRL */
+ case 0x1c: /* LLEN */
+ case 0x20: /* FLENS */
+ case 0x24: /* HFLTR_CTRL */
+ case 0x28: /* CC_CARR_WSS_CARR */
+ case 0x2c: /* C_PHASE */
+ case 0x30: /* GAIN_U */
+ case 0x34: /* GAIN_V */
+ case 0x38: /* GAIN_Y */
+ case 0x3c: /* BLACK_LEVEL */
+ case 0x40: /* BLANK_LEVEL */
+ case 0x44: /* X_COLOR */
+ case 0x48: /* M_CONTROL */
+ case 0x4c: /* BSTAMP_WSS_DATA */
+ case 0x50: /* S_CARR */
+ case 0x54: /* LINE21 */
+ case 0x58: /* LN_SEL */
+ case 0x5c: /* L21__WC_CTL */
+ case 0x60: /* HTRIGGER_VTRIGGER */
+ case 0x64: /* SAVID__EAVID */
+ case 0x68: /* FLEN__FAL */
+ case 0x6c: /* LAL__PHASE_RESET */
+ case 0x70: /* HS_INT_START_STOP_X */
+ case 0x74: /* HS_EXT_START_STOP_X */
+ case 0x78: /* VS_INT_START_X */
+ case 0x7c: /* VS_INT_STOP_X__VS_INT_START_Y */
+ case 0x80: /* VS_INT_STOP_Y__VS_INT_START_X */
+ case 0x84: /* VS_EXT_STOP_X__VS_EXT_START_Y */
+ case 0x88: /* VS_EXT_STOP_Y */
+ case 0x90: /* AVID_START_STOP_X */
+ case 0x94: /* AVID_START_STOP_Y */
+ case 0xa0: /* FID_INT_START_X__FID_INT_START_Y */
+ case 0xa4: /* FID_INT_OFFSET_Y__FID_EXT_START_X */
+ case 0xa8: /* FID_EXT_START_Y__FID_EXT_OFFSET_Y */
+ case 0xb0: /* TVDETGP_INT_START_STOP_X */
+ case 0xb4: /* TVDETGP_INT_START_STOP_Y */
+ case 0xb8: /* GEN_CTRL */
+ case 0xc4: /* DAC_TST__DAC_A */
+ case 0xc8: /* DAC_B__DAC_C */
+ return 0;
+
+ default:
+ break;
+ }
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_venc_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ if (size != 4) {
+ omap_badwidth_write32(opaque, addr, size);
+ return;
+ }
+
+ switch (addr) {
+ case 0x08: /* F_CONTROL */
+ case 0x10: /* VIDOUT_CTRL */
+ case 0x14: /* SYNC_CTRL */
+ case 0x1c: /* LLEN */
+ case 0x20: /* FLENS */
+ case 0x24: /* HFLTR_CTRL */
+ case 0x28: /* CC_CARR_WSS_CARR */
+ case 0x2c: /* C_PHASE */
+ case 0x30: /* GAIN_U */
+ case 0x34: /* GAIN_V */
+ case 0x38: /* GAIN_Y */
+ case 0x3c: /* BLACK_LEVEL */
+ case 0x40: /* BLANK_LEVEL */
+ case 0x44: /* X_COLOR */
+ case 0x48: /* M_CONTROL */
+ case 0x4c: /* BSTAMP_WSS_DATA */
+ case 0x50: /* S_CARR */
+ case 0x54: /* LINE21 */
+ case 0x58: /* LN_SEL */
+ case 0x5c: /* L21__WC_CTL */
+ case 0x60: /* HTRIGGER_VTRIGGER */
+ case 0x64: /* SAVID__EAVID */
+ case 0x68: /* FLEN__FAL */
+ case 0x6c: /* LAL__PHASE_RESET */
+ case 0x70: /* HS_INT_START_STOP_X */
+ case 0x74: /* HS_EXT_START_STOP_X */
+ case 0x78: /* VS_INT_START_X */
+ case 0x7c: /* VS_INT_STOP_X__VS_INT_START_Y */
+ case 0x80: /* VS_INT_STOP_Y__VS_INT_START_X */
+ case 0x84: /* VS_EXT_STOP_X__VS_EXT_START_Y */
+ case 0x88: /* VS_EXT_STOP_Y */
+ case 0x90: /* AVID_START_STOP_X */
+ case 0x94: /* AVID_START_STOP_Y */
+ case 0xa0: /* FID_INT_START_X__FID_INT_START_Y */
+ case 0xa4: /* FID_INT_OFFSET_Y__FID_EXT_START_X */
+ case 0xa8: /* FID_EXT_START_Y__FID_EXT_OFFSET_Y */
+ case 0xb0: /* TVDETGP_INT_START_STOP_X */
+ case 0xb4: /* TVDETGP_INT_START_STOP_Y */
+ case 0xb8: /* GEN_CTRL */
+ case 0xc4: /* DAC_TST__DAC_A */
+ case 0xc8: /* DAC_B__DAC_C */
+ break;
+
+ default:
+ OMAP_BAD_REG(addr);
+ }
+}
+
+static const MemoryRegionOps omap_venc_ops = {
+ .read = omap_venc_read,
+ .write = omap_venc_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static uint64_t omap_im3_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ if (size != 4) {
+ return omap_badwidth_read32(opaque, addr);
+ }
+
+ switch (addr) {
+ case 0x0a8: /* SBIMERRLOGA */
+ case 0x0b0: /* SBIMERRLOG */
+ case 0x190: /* SBIMSTATE */
+ case 0x198: /* SBTMSTATE_L */
+ case 0x19c: /* SBTMSTATE_H */
+ case 0x1a8: /* SBIMCONFIG_L */
+ case 0x1ac: /* SBIMCONFIG_H */
+ case 0x1f8: /* SBID_L */
+ case 0x1fc: /* SBID_H */
+ return 0;
+
+ default:
+ break;
+ }
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_im3_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ if (size != 4) {
+ omap_badwidth_write32(opaque, addr, value);
+ return;
+ }
+
+ switch (addr) {
+ case 0x0b0: /* SBIMERRLOG */
+ case 0x190: /* SBIMSTATE */
+ case 0x198: /* SBTMSTATE_L */
+ case 0x19c: /* SBTMSTATE_H */
+ case 0x1a8: /* SBIMCONFIG_L */
+ case 0x1ac: /* SBIMCONFIG_H */
+ break;
+
+ default:
+ OMAP_BAD_REG(addr);
+ }
+}
+
+static const MemoryRegionOps omap_im3_ops = {
+ .read = omap_im3_read,
+ .write = omap_im3_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+struct omap_dss_s *omap_dss_init(struct omap_target_agent_s *ta,
+ MemoryRegion *sysmem,
+ hwaddr l3_base,
+ qemu_irq irq, qemu_irq drq,
+ omap_clk fck1, omap_clk fck2, omap_clk ck54m,
+ omap_clk ick1, omap_clk ick2)
+{
+ struct omap_dss_s *s = g_new0(struct omap_dss_s, 1);
+
+ s->irq = irq;
+ s->drq = drq;
+ omap_dss_reset(s);
+
+ memory_region_init_io(&s->iomem_diss1, NULL, &omap_diss_ops, s, "omap.diss1",
+ omap_l4_region_size(ta, 0));
+ memory_region_init_io(&s->iomem_disc1, NULL, &omap_disc_ops, s, "omap.disc1",
+ omap_l4_region_size(ta, 1));
+ memory_region_init_io(&s->iomem_rfbi1, NULL, &omap_rfbi_ops, s, "omap.rfbi1",
+ omap_l4_region_size(ta, 2));
+ memory_region_init_io(&s->iomem_venc1, NULL, &omap_venc_ops, s, "omap.venc1",
+ omap_l4_region_size(ta, 3));
+ memory_region_init_io(&s->iomem_im3, NULL, &omap_im3_ops, s,
+ "omap.im3", 0x1000);
+
+ omap_l4_attach(ta, 0, &s->iomem_diss1);
+ omap_l4_attach(ta, 1, &s->iomem_disc1);
+ omap_l4_attach(ta, 2, &s->iomem_rfbi1);
+ omap_l4_attach(ta, 3, &s->iomem_venc1);
+ memory_region_add_subregion(sysmem, l3_base, &s->iomem_im3);
+
+#if 0
+ s->state = graphic_console_init(omap_update_display,
+ omap_invalidate_display, omap_screen_dump, s);
+#endif
+
+ return s;
+}
+
+void omap_rfbi_attach(struct omap_dss_s *s, int cs, struct rfbi_chip_s *chip)
+{
+ if (cs < 0 || cs > 1)
+ hw_error("%s: wrong CS %i\n", __func__, cs);
+ s->rfbi.chip[cs] = chip;
+}
diff --git a/hw/display/omap_lcdc.c b/hw/display/omap_lcdc.c
new file mode 100644
index 000000000..0ba42ef63
--- /dev/null
+++ b/hw/display/omap_lcdc.c
@@ -0,0 +1,508 @@
+/*
+ * OMAP LCD controller.
+ *
+ * Copyright (C) 2006-2007 Andrzej Zaborowski <balrog@zabor.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/irq.h"
+#include "ui/console.h"
+#include "hw/arm/omap.h"
+#include "framebuffer.h"
+#include "ui/pixel_ops.h"
+
+struct omap_lcd_panel_s {
+ MemoryRegion *sysmem;
+ MemoryRegion iomem;
+ MemoryRegionSection fbsection;
+ qemu_irq irq;
+ QemuConsole *con;
+
+ int plm;
+ int tft;
+ int mono;
+ int enable;
+ int width;
+ int height;
+ int interrupts;
+ uint32_t timing[3];
+ uint32_t subpanel;
+ uint32_t ctrl;
+
+ struct omap_dma_lcd_channel_s *dma;
+ uint16_t palette[256];
+ int palette_done;
+ int frame_done;
+ int invalidate;
+ int sync_error;
+};
+
+static void omap_lcd_interrupts(struct omap_lcd_panel_s *s)
+{
+ if (s->frame_done && (s->interrupts & 1)) {
+ qemu_irq_raise(s->irq);
+ return;
+ }
+
+ if (s->palette_done && (s->interrupts & 2)) {
+ qemu_irq_raise(s->irq);
+ return;
+ }
+
+ if (s->sync_error) {
+ qemu_irq_raise(s->irq);
+ return;
+ }
+
+ qemu_irq_lower(s->irq);
+}
+
+/*
+ * 2-bit colour
+ */
+static void draw_line2_32(void *opaque, uint8_t *d, const uint8_t *s,
+ int width, int deststep)
+{
+ uint16_t *pal = opaque;
+ uint8_t v, r, g, b;
+
+ do {
+ v = ldub_p((void *) s);
+ r = (pal[v & 3] >> 4) & 0xf0;
+ g = pal[v & 3] & 0xf0;
+ b = (pal[v & 3] << 4) & 0xf0;
+ ((uint32_t *) d)[0] = rgb_to_pixel32(r, g, b);
+ d += 4;
+ v >>= 2;
+ r = (pal[v & 3] >> 4) & 0xf0;
+ g = pal[v & 3] & 0xf0;
+ b = (pal[v & 3] << 4) & 0xf0;
+ ((uint32_t *) d)[0] = rgb_to_pixel32(r, g, b);
+ d += 4;
+ v >>= 2;
+ r = (pal[v & 3] >> 4) & 0xf0;
+ g = pal[v & 3] & 0xf0;
+ b = (pal[v & 3] << 4) & 0xf0;
+ ((uint32_t *) d)[0] = rgb_to_pixel32(r, g, b);
+ d += 4;
+ v >>= 2;
+ r = (pal[v & 3] >> 4) & 0xf0;
+ g = pal[v & 3] & 0xf0;
+ b = (pal[v & 3] << 4) & 0xf0;
+ ((uint32_t *) d)[0] = rgb_to_pixel32(r, g, b);
+ d += 4;
+ s++;
+ width -= 4;
+ } while (width > 0);
+}
+
+/*
+ * 4-bit colour
+ */
+static void draw_line4_32(void *opaque, uint8_t *d, const uint8_t *s,
+ int width, int deststep)
+{
+ uint16_t *pal = opaque;
+ uint8_t v, r, g, b;
+
+ do {
+ v = ldub_p((void *) s);
+ r = (pal[v & 0xf] >> 4) & 0xf0;
+ g = pal[v & 0xf] & 0xf0;
+ b = (pal[v & 0xf] << 4) & 0xf0;
+ ((uint32_t *) d)[0] = rgb_to_pixel32(r, g, b);
+ d += 4;
+ v >>= 4;
+ r = (pal[v & 0xf] >> 4) & 0xf0;
+ g = pal[v & 0xf] & 0xf0;
+ b = (pal[v & 0xf] << 4) & 0xf0;
+ ((uint32_t *) d)[0] = rgb_to_pixel32(r, g, b);
+ d += 4;
+ s++;
+ width -= 2;
+ } while (width > 0);
+}
+
+/*
+ * 8-bit colour
+ */
+static void draw_line8_32(void *opaque, uint8_t *d, const uint8_t *s,
+ int width, int deststep)
+{
+ uint16_t *pal = opaque;
+ uint8_t v, r, g, b;
+
+ do {
+ v = ldub_p((void *) s);
+ r = (pal[v] >> 4) & 0xf0;
+ g = pal[v] & 0xf0;
+ b = (pal[v] << 4) & 0xf0;
+ ((uint32_t *) d)[0] = rgb_to_pixel32(r, g, b);
+ s++;
+ d += 4;
+ } while (-- width != 0);
+}
+
+/*
+ * 12-bit colour
+ */
+static void draw_line12_32(void *opaque, uint8_t *d, const uint8_t *s,
+ int width, int deststep)
+{
+ uint16_t v;
+ uint8_t r, g, b;
+
+ do {
+ v = lduw_le_p((void *) s);
+ r = (v >> 4) & 0xf0;
+ g = v & 0xf0;
+ b = (v << 4) & 0xf0;
+ ((uint32_t *) d)[0] = rgb_to_pixel32(r, g, b);
+ s += 2;
+ d += 4;
+ } while (-- width != 0);
+}
+
+/*
+ * 16-bit colour
+ */
+static void draw_line16_32(void *opaque, uint8_t *d, const uint8_t *s,
+ int width, int deststep)
+{
+ uint16_t v;
+ uint8_t r, g, b;
+
+ do {
+ v = lduw_le_p((void *) s);
+ r = (v >> 8) & 0xf8;
+ g = (v >> 3) & 0xfc;
+ b = (v << 3) & 0xf8;
+ ((uint32_t *) d)[0] = rgb_to_pixel32(r, g, b);
+ s += 2;
+ d += 4;
+ } while (-- width != 0);
+}
+
+static void omap_update_display(void *opaque)
+{
+ struct omap_lcd_panel_s *omap_lcd = (struct omap_lcd_panel_s *) opaque;
+ DisplaySurface *surface;
+ drawfn draw_line;
+ int size, height, first, last;
+ int width, linesize, step, bpp, frame_offset;
+ hwaddr frame_base;
+
+ if (!omap_lcd || omap_lcd->plm == 1 || !omap_lcd->enable) {
+ return;
+ }
+
+ surface = qemu_console_surface(omap_lcd->con);
+ if (!surface_bits_per_pixel(surface)) {
+ return;
+ }
+
+ frame_offset = 0;
+ if (omap_lcd->plm != 2) {
+ cpu_physical_memory_read(
+ omap_lcd->dma->phys_framebuffer[omap_lcd->dma->current_frame],
+ omap_lcd->palette, 0x200);
+ switch (omap_lcd->palette[0] >> 12 & 7) {
+ case 3 ... 7:
+ frame_offset += 0x200;
+ break;
+ default:
+ frame_offset += 0x20;
+ }
+ }
+
+ /* Colour depth */
+ switch ((omap_lcd->palette[0] >> 12) & 7) {
+ case 1:
+ draw_line = draw_line2_32;
+ bpp = 2;
+ break;
+
+ case 2:
+ draw_line = draw_line4_32;
+ bpp = 4;
+ break;
+
+ case 3:
+ draw_line = draw_line8_32;
+ bpp = 8;
+ break;
+
+ case 4 ... 7:
+ if (!omap_lcd->tft)
+ draw_line = draw_line12_32;
+ else
+ draw_line = draw_line16_32;
+ bpp = 16;
+ break;
+
+ default:
+ /* Unsupported at the moment. */
+ return;
+ }
+
+ /* Resolution */
+ width = omap_lcd->width;
+ if (width != surface_width(surface) ||
+ omap_lcd->height != surface_height(surface)) {
+ qemu_console_resize(omap_lcd->con,
+ omap_lcd->width, omap_lcd->height);
+ surface = qemu_console_surface(omap_lcd->con);
+ omap_lcd->invalidate = 1;
+ }
+
+ if (omap_lcd->dma->current_frame == 0)
+ size = omap_lcd->dma->src_f1_bottom - omap_lcd->dma->src_f1_top;
+ else
+ size = omap_lcd->dma->src_f2_bottom - omap_lcd->dma->src_f2_top;
+
+ if (frame_offset + ((width * omap_lcd->height * bpp) >> 3) > size + 2) {
+ omap_lcd->sync_error = 1;
+ omap_lcd_interrupts(omap_lcd);
+ omap_lcd->enable = 0;
+ return;
+ }
+
+ /* Content */
+ frame_base = omap_lcd->dma->phys_framebuffer[
+ omap_lcd->dma->current_frame] + frame_offset;
+ omap_lcd->dma->condition |= 1 << omap_lcd->dma->current_frame;
+ if (omap_lcd->dma->interrupts & 1)
+ qemu_irq_raise(omap_lcd->dma->irq);
+ if (omap_lcd->dma->dual)
+ omap_lcd->dma->current_frame ^= 1;
+
+ if (!surface_bits_per_pixel(surface)) {
+ return;
+ }
+
+ first = 0;
+ height = omap_lcd->height;
+ if (omap_lcd->subpanel & (1 << 31)) {
+ if (omap_lcd->subpanel & (1 << 29))
+ first = (omap_lcd->subpanel >> 16) & 0x3ff;
+ else
+ height = (omap_lcd->subpanel >> 16) & 0x3ff;
+ /* TODO: fill the rest of the panel with DPD */
+ }
+
+ step = width * bpp >> 3;
+ linesize = surface_stride(surface);
+ if (omap_lcd->invalidate) {
+ framebuffer_update_memory_section(&omap_lcd->fbsection,
+ omap_lcd->sysmem, frame_base,
+ height, step);
+ }
+
+ framebuffer_update_display(surface, &omap_lcd->fbsection,
+ width, height,
+ step, linesize, 0,
+ omap_lcd->invalidate,
+ draw_line, omap_lcd->palette,
+ &first, &last);
+
+ if (first >= 0) {
+ dpy_gfx_update(omap_lcd->con, 0, first, width, last - first + 1);
+ }
+ omap_lcd->invalidate = 0;
+}
+
+static void omap_invalidate_display(void *opaque) {
+ struct omap_lcd_panel_s *omap_lcd = opaque;
+ omap_lcd->invalidate = 1;
+}
+
+static void omap_lcd_update(struct omap_lcd_panel_s *s) {
+ if (!s->enable) {
+ s->dma->current_frame = -1;
+ s->sync_error = 0;
+ if (s->plm != 1)
+ s->frame_done = 1;
+ omap_lcd_interrupts(s);
+ return;
+ }
+
+ if (s->dma->current_frame == -1) {
+ s->frame_done = 0;
+ s->palette_done = 0;
+ s->dma->current_frame = 0;
+ }
+
+ if (!s->dma->mpu->port[s->dma->src].addr_valid(s->dma->mpu,
+ s->dma->src_f1_top) ||
+ !s->dma->mpu->port[
+ s->dma->src].addr_valid(s->dma->mpu,
+ s->dma->src_f1_bottom) ||
+ (s->dma->dual &&
+ (!s->dma->mpu->port[
+ s->dma->src].addr_valid(s->dma->mpu,
+ s->dma->src_f2_top) ||
+ !s->dma->mpu->port[
+ s->dma->src].addr_valid(s->dma->mpu,
+ s->dma->src_f2_bottom)))) {
+ s->dma->condition |= 1 << 2;
+ if (s->dma->interrupts & (1 << 1))
+ qemu_irq_raise(s->dma->irq);
+ s->enable = 0;
+ return;
+ }
+
+ s->dma->phys_framebuffer[0] = s->dma->src_f1_top;
+ s->dma->phys_framebuffer[1] = s->dma->src_f2_top;
+
+ if (s->plm != 2 && !s->palette_done) {
+ cpu_physical_memory_read(
+ s->dma->phys_framebuffer[s->dma->current_frame],
+ s->palette, 0x200);
+ s->palette_done = 1;
+ omap_lcd_interrupts(s);
+ }
+}
+
+static uint64_t omap_lcdc_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_lcd_panel_s *s = (struct omap_lcd_panel_s *) opaque;
+
+ switch (addr) {
+ case 0x00: /* LCD_CONTROL */
+ return (s->tft << 23) | (s->plm << 20) |
+ (s->tft << 7) | (s->interrupts << 3) |
+ (s->mono << 1) | s->enable | s->ctrl | 0xfe000c34;
+
+ case 0x04: /* LCD_TIMING0 */
+ return (s->timing[0] << 10) | (s->width - 1) | 0x0000000f;
+
+ case 0x08: /* LCD_TIMING1 */
+ return (s->timing[1] << 10) | (s->height - 1);
+
+ case 0x0c: /* LCD_TIMING2 */
+ return s->timing[2] | 0xfc000000;
+
+ case 0x10: /* LCD_STATUS */
+ return (s->palette_done << 6) | (s->sync_error << 2) | s->frame_done;
+
+ case 0x14: /* LCD_SUBPANEL */
+ return s->subpanel;
+
+ default:
+ break;
+ }
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_lcdc_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap_lcd_panel_s *s = (struct omap_lcd_panel_s *) opaque;
+
+ switch (addr) {
+ case 0x00: /* LCD_CONTROL */
+ s->plm = (value >> 20) & 3;
+ s->tft = (value >> 7) & 1;
+ s->interrupts = (value >> 3) & 3;
+ s->mono = (value >> 1) & 1;
+ s->ctrl = value & 0x01cff300;
+ if (s->enable != (value & 1)) {
+ s->enable = value & 1;
+ omap_lcd_update(s);
+ }
+ break;
+
+ case 0x04: /* LCD_TIMING0 */
+ s->timing[0] = value >> 10;
+ s->width = (value & 0x3ff) + 1;
+ break;
+
+ case 0x08: /* LCD_TIMING1 */
+ s->timing[1] = value >> 10;
+ s->height = (value & 0x3ff) + 1;
+ break;
+
+ case 0x0c: /* LCD_TIMING2 */
+ s->timing[2] = value;
+ break;
+
+ case 0x10: /* LCD_STATUS */
+ break;
+
+ case 0x14: /* LCD_SUBPANEL */
+ s->subpanel = value & 0xa1ffffff;
+ break;
+
+ default:
+ OMAP_BAD_REG(addr);
+ }
+}
+
+static const MemoryRegionOps omap_lcdc_ops = {
+ .read = omap_lcdc_read,
+ .write = omap_lcdc_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+void omap_lcdc_reset(struct omap_lcd_panel_s *s)
+{
+ s->dma->current_frame = -1;
+ s->plm = 0;
+ s->tft = 0;
+ s->mono = 0;
+ s->enable = 0;
+ s->width = 0;
+ s->height = 0;
+ s->interrupts = 0;
+ s->timing[0] = 0;
+ s->timing[1] = 0;
+ s->timing[2] = 0;
+ s->subpanel = 0;
+ s->palette_done = 0;
+ s->frame_done = 0;
+ s->sync_error = 0;
+ s->invalidate = 1;
+ s->subpanel = 0;
+ s->ctrl = 0;
+}
+
+static const GraphicHwOps omap_ops = {
+ .invalidate = omap_invalidate_display,
+ .gfx_update = omap_update_display,
+};
+
+struct omap_lcd_panel_s *omap_lcdc_init(MemoryRegion *sysmem,
+ hwaddr base,
+ qemu_irq irq,
+ struct omap_dma_lcd_channel_s *dma,
+ omap_clk clk)
+{
+ struct omap_lcd_panel_s *s = g_new0(struct omap_lcd_panel_s, 1);
+
+ s->irq = irq;
+ s->dma = dma;
+ s->sysmem = sysmem;
+ omap_lcdc_reset(s);
+
+ memory_region_init_io(&s->iomem, NULL, &omap_lcdc_ops, s, "omap.lcdc", 0x100);
+ memory_region_add_subregion(sysmem, base, &s->iomem);
+
+ s->con = graphic_console_init(NULL, 0, &omap_ops, s);
+
+ return s;
+}
diff --git a/hw/display/pl110.c b/hw/display/pl110.c
new file mode 100644
index 000000000..4bf15c1da
--- /dev/null
+++ b/hw/display/pl110.c
@@ -0,0 +1,609 @@
+/*
+ * Arm PrimeCell PL110 Color LCD Controller
+ *
+ * Copyright (c) 2005-2009 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the GNU LGPL
+ */
+
+#include "qemu/osdep.h"
+#include "hw/irq.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "ui/console.h"
+#include "framebuffer.h"
+#include "ui/pixel_ops.h"
+#include "qemu/timer.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qom/object.h"
+
+#define PL110_CR_EN 0x001
+#define PL110_CR_BGR 0x100
+#define PL110_CR_BEBO 0x200
+#define PL110_CR_BEPO 0x400
+#define PL110_CR_PWR 0x800
+#define PL110_IE_NB 0x004
+#define PL110_IE_VC 0x008
+
+enum pl110_bppmode
+{
+ BPP_1,
+ BPP_2,
+ BPP_4,
+ BPP_8,
+ BPP_16,
+ BPP_32,
+ BPP_16_565, /* PL111 only */
+ BPP_12 /* PL111 only */
+};
+
+
+/* The Versatile/PB uses a slightly modified PL110 controller. */
+enum pl110_version
+{
+ VERSION_PL110,
+ VERSION_PL110_VERSATILE,
+ VERSION_PL111
+};
+
+#define TYPE_PL110 "pl110"
+OBJECT_DECLARE_SIMPLE_TYPE(PL110State, PL110)
+
+struct PL110State {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ MemoryRegionSection fbsection;
+ QemuConsole *con;
+ QEMUTimer *vblank_timer;
+
+ int version;
+ uint32_t timing[4];
+ uint32_t cr;
+ uint32_t upbase;
+ uint32_t lpbase;
+ uint32_t int_status;
+ uint32_t int_mask;
+ int cols;
+ int rows;
+ enum pl110_bppmode bpp;
+ int invalidate;
+ uint32_t mux_ctrl;
+ uint32_t palette[256];
+ uint32_t raw_palette[128];
+ qemu_irq irq;
+};
+
+static int vmstate_pl110_post_load(void *opaque, int version_id);
+
+static const VMStateDescription vmstate_pl110 = {
+ .name = "pl110",
+ .version_id = 2,
+ .minimum_version_id = 1,
+ .post_load = vmstate_pl110_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(version, PL110State),
+ VMSTATE_UINT32_ARRAY(timing, PL110State, 4),
+ VMSTATE_UINT32(cr, PL110State),
+ VMSTATE_UINT32(upbase, PL110State),
+ VMSTATE_UINT32(lpbase, PL110State),
+ VMSTATE_UINT32(int_status, PL110State),
+ VMSTATE_UINT32(int_mask, PL110State),
+ VMSTATE_INT32(cols, PL110State),
+ VMSTATE_INT32(rows, PL110State),
+ VMSTATE_UINT32(bpp, PL110State),
+ VMSTATE_INT32(invalidate, PL110State),
+ VMSTATE_UINT32_ARRAY(palette, PL110State, 256),
+ VMSTATE_UINT32_ARRAY(raw_palette, PL110State, 128),
+ VMSTATE_UINT32_V(mux_ctrl, PL110State, 2),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const unsigned char pl110_id[] =
+{ 0x10, 0x11, 0x04, 0x00, 0x0d, 0xf0, 0x05, 0xb1 };
+
+static const unsigned char pl111_id[] = {
+ 0x11, 0x11, 0x24, 0x00, 0x0d, 0xf0, 0x05, 0xb1
+};
+
+
+/* Indexed by pl110_version */
+static const unsigned char *idregs[] = {
+ pl110_id,
+ /* The ARM documentation (DDI0224C) says the CLCDC on the Versatile board
+ * has a different ID (0x93, 0x10, 0x04, 0x00, ...). However the hardware
+ * itself has the same ID values as a stock PL110, and guests (in
+ * particular Linux) rely on this. We emulate what the hardware does,
+ * rather than what the docs claim it ought to do.
+ */
+ pl110_id,
+ pl111_id
+};
+
+#define COPY_PIXEL(to, from) do { *(uint32_t *)to = from; to += 4; } while (0)
+
+#undef RGB
+#define BORDER bgr
+#define ORDER 0
+#include "pl110_template.h"
+#define ORDER 1
+#include "pl110_template.h"
+#define ORDER 2
+#include "pl110_template.h"
+#undef BORDER
+#define RGB
+#define BORDER rgb
+#define ORDER 0
+#include "pl110_template.h"
+#define ORDER 1
+#include "pl110_template.h"
+#define ORDER 2
+#include "pl110_template.h"
+#undef BORDER
+
+#undef COPY_PIXEL
+
+static drawfn pl110_draw_fn_32[48] = {
+ pl110_draw_line1_lblp_bgr,
+ pl110_draw_line2_lblp_bgr,
+ pl110_draw_line4_lblp_bgr,
+ pl110_draw_line8_lblp_bgr,
+ pl110_draw_line16_555_lblp_bgr,
+ pl110_draw_line32_lblp_bgr,
+ pl110_draw_line16_lblp_bgr,
+ pl110_draw_line12_lblp_bgr,
+
+ pl110_draw_line1_bbbp_bgr,
+ pl110_draw_line2_bbbp_bgr,
+ pl110_draw_line4_bbbp_bgr,
+ pl110_draw_line8_bbbp_bgr,
+ pl110_draw_line16_555_bbbp_bgr,
+ pl110_draw_line32_bbbp_bgr,
+ pl110_draw_line16_bbbp_bgr,
+ pl110_draw_line12_bbbp_bgr,
+
+ pl110_draw_line1_lbbp_bgr,
+ pl110_draw_line2_lbbp_bgr,
+ pl110_draw_line4_lbbp_bgr,
+ pl110_draw_line8_lbbp_bgr,
+ pl110_draw_line16_555_lbbp_bgr,
+ pl110_draw_line32_lbbp_bgr,
+ pl110_draw_line16_lbbp_bgr,
+ pl110_draw_line12_lbbp_bgr,
+
+ pl110_draw_line1_lblp_rgb,
+ pl110_draw_line2_lblp_rgb,
+ pl110_draw_line4_lblp_rgb,
+ pl110_draw_line8_lblp_rgb,
+ pl110_draw_line16_555_lblp_rgb,
+ pl110_draw_line32_lblp_rgb,
+ pl110_draw_line16_lblp_rgb,
+ pl110_draw_line12_lblp_rgb,
+
+ pl110_draw_line1_bbbp_rgb,
+ pl110_draw_line2_bbbp_rgb,
+ pl110_draw_line4_bbbp_rgb,
+ pl110_draw_line8_bbbp_rgb,
+ pl110_draw_line16_555_bbbp_rgb,
+ pl110_draw_line32_bbbp_rgb,
+ pl110_draw_line16_bbbp_rgb,
+ pl110_draw_line12_bbbp_rgb,
+
+ pl110_draw_line1_lbbp_rgb,
+ pl110_draw_line2_lbbp_rgb,
+ pl110_draw_line4_lbbp_rgb,
+ pl110_draw_line8_lbbp_rgb,
+ pl110_draw_line16_555_lbbp_rgb,
+ pl110_draw_line32_lbbp_rgb,
+ pl110_draw_line16_lbbp_rgb,
+ pl110_draw_line12_lbbp_rgb,
+};
+
+static int pl110_enabled(PL110State *s)
+{
+ return (s->cr & PL110_CR_EN) && (s->cr & PL110_CR_PWR);
+}
+
+static void pl110_update_display(void *opaque)
+{
+ PL110State *s = (PL110State *)opaque;
+ SysBusDevice *sbd;
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ drawfn fn;
+ int src_width;
+ int bpp_offset;
+ int first;
+ int last;
+
+ if (!pl110_enabled(s)) {
+ return;
+ }
+
+ sbd = SYS_BUS_DEVICE(s);
+
+ if (s->cr & PL110_CR_BGR)
+ bpp_offset = 0;
+ else
+ bpp_offset = 24;
+
+ if ((s->version != VERSION_PL111) && (s->bpp == BPP_16)) {
+ /* The PL110's native 16 bit mode is 5551; however
+ * most boards with a PL110 implement an external
+ * mux which allows bits to be reshuffled to give
+ * 565 format. The mux is typically controlled by
+ * an external system register.
+ * This is controlled by a GPIO input pin
+ * so boards can wire it up to their register.
+ *
+ * The PL111 straightforwardly implements both
+ * 5551 and 565 under control of the bpp field
+ * in the LCDControl register.
+ */
+ switch (s->mux_ctrl) {
+ case 3: /* 565 BGR */
+ bpp_offset = (BPP_16_565 - BPP_16);
+ break;
+ case 1: /* 5551 */
+ break;
+ case 0: /* 888; also if we have loaded vmstate from an old version */
+ case 2: /* 565 RGB */
+ default:
+ /* treat as 565 but honour BGR bit */
+ bpp_offset += (BPP_16_565 - BPP_16);
+ break;
+ }
+ }
+
+ if (s->cr & PL110_CR_BEBO) {
+ fn = pl110_draw_fn_32[s->bpp + 8 + bpp_offset];
+ } else if (s->cr & PL110_CR_BEPO) {
+ fn = pl110_draw_fn_32[s->bpp + 16 + bpp_offset];
+ } else {
+ fn = pl110_draw_fn_32[s->bpp + bpp_offset];
+ }
+
+ src_width = s->cols;
+ switch (s->bpp) {
+ case BPP_1:
+ src_width >>= 3;
+ break;
+ case BPP_2:
+ src_width >>= 2;
+ break;
+ case BPP_4:
+ src_width >>= 1;
+ break;
+ case BPP_8:
+ break;
+ case BPP_16:
+ case BPP_16_565:
+ case BPP_12:
+ src_width <<= 1;
+ break;
+ case BPP_32:
+ src_width <<= 2;
+ break;
+ }
+ first = 0;
+ if (s->invalidate) {
+ framebuffer_update_memory_section(&s->fbsection,
+ sysbus_address_space(sbd),
+ s->upbase,
+ s->rows, src_width);
+ }
+
+ framebuffer_update_display(surface, &s->fbsection,
+ s->cols, s->rows,
+ src_width, s->cols * 4, 0,
+ s->invalidate,
+ fn, s->palette,
+ &first, &last);
+
+ if (first >= 0) {
+ dpy_gfx_update(s->con, 0, first, s->cols, last - first + 1);
+ }
+ s->invalidate = 0;
+}
+
+static void pl110_invalidate_display(void * opaque)
+{
+ PL110State *s = (PL110State *)opaque;
+ s->invalidate = 1;
+ if (pl110_enabled(s)) {
+ qemu_console_resize(s->con, s->cols, s->rows);
+ }
+}
+
+static void pl110_update_palette(PL110State *s, int n)
+{
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ int i;
+ uint32_t raw;
+ unsigned int r, g, b;
+
+ raw = s->raw_palette[n];
+ n <<= 1;
+ for (i = 0; i < 2; i++) {
+ r = (raw & 0x1f) << 3;
+ raw >>= 5;
+ g = (raw & 0x1f) << 3;
+ raw >>= 5;
+ b = (raw & 0x1f) << 3;
+ /* The I bit is ignored. */
+ raw >>= 6;
+ switch (surface_bits_per_pixel(surface)) {
+ case 8:
+ s->palette[n] = rgb_to_pixel8(r, g, b);
+ break;
+ case 15:
+ s->palette[n] = rgb_to_pixel15(r, g, b);
+ break;
+ case 16:
+ s->palette[n] = rgb_to_pixel16(r, g, b);
+ break;
+ case 24:
+ case 32:
+ s->palette[n] = rgb_to_pixel32(r, g, b);
+ break;
+ }
+ n++;
+ }
+}
+
+static void pl110_resize(PL110State *s, int width, int height)
+{
+ if (width != s->cols || height != s->rows) {
+ if (pl110_enabled(s)) {
+ qemu_console_resize(s->con, width, height);
+ }
+ }
+ s->cols = width;
+ s->rows = height;
+}
+
+/* Update interrupts. */
+static void pl110_update(PL110State *s)
+{
+ /* Raise IRQ if enabled and any status bit is 1 */
+ if (s->int_status & s->int_mask) {
+ qemu_irq_raise(s->irq);
+ } else {
+ qemu_irq_lower(s->irq);
+ }
+}
+
+static void pl110_vblank_interrupt(void *opaque)
+{
+ PL110State *s = opaque;
+
+ /* Fire the vertical compare and next base IRQs and re-arm */
+ s->int_status |= (PL110_IE_NB | PL110_IE_VC);
+ timer_mod(s->vblank_timer,
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
+ NANOSECONDS_PER_SECOND / 60);
+ pl110_update(s);
+}
+
+static uint64_t pl110_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ PL110State *s = (PL110State *)opaque;
+
+ if (offset >= 0xfe0 && offset < 0x1000) {
+ return idregs[s->version][(offset - 0xfe0) >> 2];
+ }
+ if (offset >= 0x200 && offset < 0x400) {
+ return s->raw_palette[(offset - 0x200) >> 2];
+ }
+ switch (offset >> 2) {
+ case 0: /* LCDTiming0 */
+ return s->timing[0];
+ case 1: /* LCDTiming1 */
+ return s->timing[1];
+ case 2: /* LCDTiming2 */
+ return s->timing[2];
+ case 3: /* LCDTiming3 */
+ return s->timing[3];
+ case 4: /* LCDUPBASE */
+ return s->upbase;
+ case 5: /* LCDLPBASE */
+ return s->lpbase;
+ case 6: /* LCDIMSC */
+ if (s->version != VERSION_PL110) {
+ return s->cr;
+ }
+ return s->int_mask;
+ case 7: /* LCDControl */
+ if (s->version != VERSION_PL110) {
+ return s->int_mask;
+ }
+ return s->cr;
+ case 8: /* LCDRIS */
+ return s->int_status;
+ case 9: /* LCDMIS */
+ return s->int_status & s->int_mask;
+ case 11: /* LCDUPCURR */
+ /* TODO: Implement vertical refresh. */
+ return s->upbase;
+ case 12: /* LCDLPCURR */
+ return s->lpbase;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "pl110_read: Bad offset %x\n", (int)offset);
+ return 0;
+ }
+}
+
+static void pl110_write(void *opaque, hwaddr offset,
+ uint64_t val, unsigned size)
+{
+ PL110State *s = (PL110State *)opaque;
+ int n;
+
+ /* For simplicity invalidate the display whenever a control register
+ is written to. */
+ s->invalidate = 1;
+ if (offset >= 0x200 && offset < 0x400) {
+ /* Palette. */
+ n = (offset - 0x200) >> 2;
+ s->raw_palette[(offset - 0x200) >> 2] = val;
+ pl110_update_palette(s, n);
+ return;
+ }
+ switch (offset >> 2) {
+ case 0: /* LCDTiming0 */
+ s->timing[0] = val;
+ n = ((val & 0xfc) + 4) * 4;
+ pl110_resize(s, n, s->rows);
+ break;
+ case 1: /* LCDTiming1 */
+ s->timing[1] = val;
+ n = (val & 0x3ff) + 1;
+ pl110_resize(s, s->cols, n);
+ break;
+ case 2: /* LCDTiming2 */
+ s->timing[2] = val;
+ break;
+ case 3: /* LCDTiming3 */
+ s->timing[3] = val;
+ break;
+ case 4: /* LCDUPBASE */
+ s->upbase = val;
+ break;
+ case 5: /* LCDLPBASE */
+ s->lpbase = val;
+ break;
+ case 6: /* LCDIMSC */
+ if (s->version != VERSION_PL110) {
+ goto control;
+ }
+ imsc:
+ s->int_mask = val;
+ pl110_update(s);
+ break;
+ case 7: /* LCDControl */
+ if (s->version != VERSION_PL110) {
+ goto imsc;
+ }
+ control:
+ s->cr = val;
+ s->bpp = (val >> 1) & 7;
+ if (pl110_enabled(s)) {
+ qemu_console_resize(s->con, s->cols, s->rows);
+ timer_mod(s->vblank_timer,
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
+ NANOSECONDS_PER_SECOND / 60);
+ } else {
+ timer_del(s->vblank_timer);
+ }
+ break;
+ case 10: /* LCDICR */
+ s->int_status &= ~val;
+ pl110_update(s);
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "pl110_write: Bad offset %x\n", (int)offset);
+ }
+}
+
+static const MemoryRegionOps pl110_ops = {
+ .read = pl110_read,
+ .write = pl110_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void pl110_mux_ctrl_set(void *opaque, int line, int level)
+{
+ PL110State *s = (PL110State *)opaque;
+ s->mux_ctrl = level;
+}
+
+static int vmstate_pl110_post_load(void *opaque, int version_id)
+{
+ PL110State *s = opaque;
+ /* Make sure we redraw, and at the right size */
+ pl110_invalidate_display(s);
+ return 0;
+}
+
+static const GraphicHwOps pl110_gfx_ops = {
+ .invalidate = pl110_invalidate_display,
+ .gfx_update = pl110_update_display,
+};
+
+static void pl110_realize(DeviceState *dev, Error **errp)
+{
+ PL110State *s = PL110(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &pl110_ops, s, "pl110", 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+ sysbus_init_irq(sbd, &s->irq);
+ s->vblank_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
+ pl110_vblank_interrupt, s);
+ qdev_init_gpio_in(dev, pl110_mux_ctrl_set, 1);
+ s->con = graphic_console_init(dev, 0, &pl110_gfx_ops, s);
+}
+
+static void pl110_init(Object *obj)
+{
+ PL110State *s = PL110(obj);
+
+ s->version = VERSION_PL110;
+}
+
+static void pl110_versatile_init(Object *obj)
+{
+ PL110State *s = PL110(obj);
+
+ s->version = VERSION_PL110_VERSATILE;
+}
+
+static void pl111_init(Object *obj)
+{
+ PL110State *s = PL110(obj);
+
+ s->version = VERSION_PL111;
+}
+
+static void pl110_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories);
+ dc->vmsd = &vmstate_pl110;
+ dc->realize = pl110_realize;
+}
+
+static const TypeInfo pl110_info = {
+ .name = TYPE_PL110,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(PL110State),
+ .instance_init = pl110_init,
+ .class_init = pl110_class_init,
+};
+
+static const TypeInfo pl110_versatile_info = {
+ .name = "pl110_versatile",
+ .parent = TYPE_PL110,
+ .instance_init = pl110_versatile_init,
+};
+
+static const TypeInfo pl111_info = {
+ .name = "pl111",
+ .parent = TYPE_PL110,
+ .instance_init = pl111_init,
+};
+
+static void pl110_register_types(void)
+{
+ type_register_static(&pl110_info);
+ type_register_static(&pl110_versatile_info);
+ type_register_static(&pl111_info);
+}
+
+type_init(pl110_register_types)
diff --git a/hw/display/pl110_template.h b/hw/display/pl110_template.h
new file mode 100644
index 000000000..877419aa8
--- /dev/null
+++ b/hw/display/pl110_template.h
@@ -0,0 +1,301 @@
+/*
+ * Arm PrimeCell PL110 Color LCD Controller
+ *
+ * Copyright (c) 2005 CodeSourcery, LLC.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the GNU LGPL
+ *
+ * Framebuffer format conversion routines.
+ */
+
+#ifndef ORDER
+#error "pl110_template.h is only for inclusion by pl110.c"
+#endif
+
+#if ORDER == 0
+#define NAME glue(lblp_, BORDER)
+#ifdef HOST_WORDS_BIGENDIAN
+#define SWAP_WORDS 1
+#endif
+#elif ORDER == 1
+#define NAME glue(bbbp_, BORDER)
+#ifndef HOST_WORDS_BIGENDIAN
+#define SWAP_WORDS 1
+#endif
+#else
+#define SWAP_PIXELS 1
+#define NAME glue(lbbp_, BORDER)
+#ifdef HOST_WORDS_BIGENDIAN
+#define SWAP_WORDS 1
+#endif
+#endif
+
+#define FN_2(x, y) FN(x, y) FN(x+1, y)
+#define FN_4(x, y) FN_2(x, y) FN_2(x+2, y)
+#define FN_8(y) FN_4(0, y) FN_4(4, y)
+
+static void glue(pl110_draw_line1_,NAME)(void *opaque, uint8_t *d, const uint8_t *src, int width, int deststep)
+{
+ uint32_t *palette = opaque;
+ uint32_t data;
+ while (width > 0) {
+ data = *(uint32_t *)src;
+#ifdef SWAP_PIXELS
+#define FN(x, y) COPY_PIXEL(d, palette[(data >> (y + 7 - (x))) & 1]);
+#else
+#define FN(x, y) COPY_PIXEL(d, palette[(data >> ((x) + y)) & 1]);
+#endif
+#ifdef SWAP_WORDS
+ FN_8(24)
+ FN_8(16)
+ FN_8(8)
+ FN_8(0)
+#else
+ FN_8(0)
+ FN_8(8)
+ FN_8(16)
+ FN_8(24)
+#endif
+#undef FN
+ width -= 32;
+ src += 4;
+ }
+}
+
+static void glue(pl110_draw_line2_,NAME)(void *opaque, uint8_t *d, const uint8_t *src, int width, int deststep)
+{
+ uint32_t *palette = opaque;
+ uint32_t data;
+ while (width > 0) {
+ data = *(uint32_t *)src;
+#ifdef SWAP_PIXELS
+#define FN(x, y) COPY_PIXEL(d, palette[(data >> (y + 6 - (x)*2)) & 3]);
+#else
+#define FN(x, y) COPY_PIXEL(d, palette[(data >> ((x)*2 + y)) & 3]);
+#endif
+#ifdef SWAP_WORDS
+ FN_4(0, 24)
+ FN_4(0, 16)
+ FN_4(0, 8)
+ FN_4(0, 0)
+#else
+ FN_4(0, 0)
+ FN_4(0, 8)
+ FN_4(0, 16)
+ FN_4(0, 24)
+#endif
+#undef FN
+ width -= 16;
+ src += 4;
+ }
+}
+
+static void glue(pl110_draw_line4_,NAME)(void *opaque, uint8_t *d, const uint8_t *src, int width, int deststep)
+{
+ uint32_t *palette = opaque;
+ uint32_t data;
+ while (width > 0) {
+ data = *(uint32_t *)src;
+#ifdef SWAP_PIXELS
+#define FN(x, y) COPY_PIXEL(d, palette[(data >> (y + 4 - (x)*4)) & 0xf]);
+#else
+#define FN(x, y) COPY_PIXEL(d, palette[(data >> ((x)*4 + y)) & 0xf]);
+#endif
+#ifdef SWAP_WORDS
+ FN_2(0, 24)
+ FN_2(0, 16)
+ FN_2(0, 8)
+ FN_2(0, 0)
+#else
+ FN_2(0, 0)
+ FN_2(0, 8)
+ FN_2(0, 16)
+ FN_2(0, 24)
+#endif
+#undef FN
+ width -= 8;
+ src += 4;
+ }
+}
+
+static void glue(pl110_draw_line8_,NAME)(void *opaque, uint8_t *d, const uint8_t *src, int width, int deststep)
+{
+ uint32_t *palette = opaque;
+ uint32_t data;
+ while (width > 0) {
+ data = *(uint32_t *)src;
+#define FN(x) COPY_PIXEL(d, palette[(data >> (x)) & 0xff]);
+#ifdef SWAP_WORDS
+ FN(24)
+ FN(16)
+ FN(8)
+ FN(0)
+#else
+ FN(0)
+ FN(8)
+ FN(16)
+ FN(24)
+#endif
+#undef FN
+ width -= 4;
+ src += 4;
+ }
+}
+
+static void glue(pl110_draw_line16_,NAME)(void *opaque, uint8_t *d, const uint8_t *src, int width, int deststep)
+{
+ uint32_t data;
+ unsigned int r, g, b;
+ while (width > 0) {
+ data = *(uint32_t *)src;
+#ifdef SWAP_WORDS
+ data = bswap32(data);
+#endif
+#ifdef RGB
+#define LSB r
+#define MSB b
+#else
+#define LSB b
+#define MSB r
+#endif
+#if 0
+ LSB = data & 0x1f;
+ data >>= 5;
+ g = data & 0x3f;
+ data >>= 6;
+ MSB = data & 0x1f;
+ data >>= 5;
+#else
+ LSB = (data & 0x1f) << 3;
+ data >>= 5;
+ g = (data & 0x3f) << 2;
+ data >>= 6;
+ MSB = (data & 0x1f) << 3;
+ data >>= 5;
+#endif
+ COPY_PIXEL(d, rgb_to_pixel32(r, g, b));
+ LSB = (data & 0x1f) << 3;
+ data >>= 5;
+ g = (data & 0x3f) << 2;
+ data >>= 6;
+ MSB = (data & 0x1f) << 3;
+ data >>= 5;
+ COPY_PIXEL(d, rgb_to_pixel32(r, g, b));
+#undef MSB
+#undef LSB
+ width -= 2;
+ src += 4;
+ }
+}
+
+static void glue(pl110_draw_line32_,NAME)(void *opaque, uint8_t *d, const uint8_t *src, int width, int deststep)
+{
+ uint32_t data;
+ unsigned int r, g, b;
+ while (width > 0) {
+ data = *(uint32_t *)src;
+#ifdef RGB
+#define LSB r
+#define MSB b
+#else
+#define LSB b
+#define MSB r
+#endif
+#ifndef SWAP_WORDS
+ LSB = data & 0xff;
+ g = (data >> 8) & 0xff;
+ MSB = (data >> 16) & 0xff;
+#else
+ LSB = (data >> 24) & 0xff;
+ g = (data >> 16) & 0xff;
+ MSB = (data >> 8) & 0xff;
+#endif
+ COPY_PIXEL(d, rgb_to_pixel32(r, g, b));
+#undef MSB
+#undef LSB
+ width--;
+ src += 4;
+ }
+}
+
+static void glue(pl110_draw_line16_555_,NAME)(void *opaque, uint8_t *d, const uint8_t *src, int width, int deststep)
+{
+ /* RGB 555 plus an intensity bit (which we ignore) */
+ uint32_t data;
+ unsigned int r, g, b;
+ while (width > 0) {
+ data = *(uint32_t *)src;
+#ifdef SWAP_WORDS
+ data = bswap32(data);
+#endif
+#ifdef RGB
+#define LSB r
+#define MSB b
+#else
+#define LSB b
+#define MSB r
+#endif
+ LSB = (data & 0x1f) << 3;
+ data >>= 5;
+ g = (data & 0x1f) << 3;
+ data >>= 5;
+ MSB = (data & 0x1f) << 3;
+ data >>= 5;
+ COPY_PIXEL(d, rgb_to_pixel32(r, g, b));
+ LSB = (data & 0x1f) << 3;
+ data >>= 5;
+ g = (data & 0x1f) << 3;
+ data >>= 5;
+ MSB = (data & 0x1f) << 3;
+ data >>= 6;
+ COPY_PIXEL(d, rgb_to_pixel32(r, g, b));
+#undef MSB
+#undef LSB
+ width -= 2;
+ src += 4;
+ }
+}
+
+static void glue(pl110_draw_line12_,NAME)(void *opaque, uint8_t *d, const uint8_t *src, int width, int deststep)
+{
+ /* RGB 444 with 4 bits of zeroes at the top of each halfword */
+ uint32_t data;
+ unsigned int r, g, b;
+ while (width > 0) {
+ data = *(uint32_t *)src;
+#ifdef SWAP_WORDS
+ data = bswap32(data);
+#endif
+#ifdef RGB
+#define LSB r
+#define MSB b
+#else
+#define LSB b
+#define MSB r
+#endif
+ LSB = (data & 0xf) << 4;
+ data >>= 4;
+ g = (data & 0xf) << 4;
+ data >>= 4;
+ MSB = (data & 0xf) << 4;
+ data >>= 8;
+ COPY_PIXEL(d, rgb_to_pixel32(r, g, b));
+ LSB = (data & 0xf) << 4;
+ data >>= 4;
+ g = (data & 0xf) << 4;
+ data >>= 4;
+ MSB = (data & 0xf) << 4;
+ data >>= 8;
+ COPY_PIXEL(d, rgb_to_pixel32(r, g, b));
+#undef MSB
+#undef LSB
+ width -= 2;
+ src += 4;
+ }
+}
+
+#undef SWAP_PIXELS
+#undef NAME
+#undef SWAP_WORDS
+#undef ORDER
diff --git a/hw/display/pxa2xx_lcd.c b/hw/display/pxa2xx_lcd.c
new file mode 100644
index 000000000..2887ce496
--- /dev/null
+++ b/hw/display/pxa2xx_lcd.c
@@ -0,0 +1,1451 @@
+/*
+ * Intel XScale PXA255/270 LCDC emulation.
+ *
+ * Copyright (c) 2006 Openedhand Ltd.
+ * Written by Andrzej Zaborowski <balrog@zabor.org>
+ *
+ * This code is licensed under the GPLv2.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "hw/irq.h"
+#include "migration/vmstate.h"
+#include "ui/console.h"
+#include "hw/arm/pxa.h"
+#include "ui/pixel_ops.h"
+#include "hw/boards.h"
+/* FIXME: For graphic_rotate. Should probably be done in common code. */
+#include "sysemu/sysemu.h"
+#include "framebuffer.h"
+
+struct DMAChannel {
+ uint32_t branch;
+ uint8_t up;
+ uint8_t palette[1024];
+ uint8_t pbuffer[1024];
+ void (*redraw)(PXA2xxLCDState *s, hwaddr addr,
+ int *miny, int *maxy);
+
+ uint32_t descriptor;
+ uint32_t source;
+ uint32_t id;
+ uint32_t command;
+};
+
+struct PXA2xxLCDState {
+ MemoryRegion *sysmem;
+ MemoryRegion iomem;
+ MemoryRegionSection fbsection;
+ qemu_irq irq;
+ int irqlevel;
+
+ int invalidated;
+ QemuConsole *con;
+ int dest_width;
+ int xres, yres;
+ int pal_for;
+ int transp;
+ enum {
+ pxa_lcdc_2bpp = 1,
+ pxa_lcdc_4bpp = 2,
+ pxa_lcdc_8bpp = 3,
+ pxa_lcdc_16bpp = 4,
+ pxa_lcdc_18bpp = 5,
+ pxa_lcdc_18pbpp = 6,
+ pxa_lcdc_19bpp = 7,
+ pxa_lcdc_19pbpp = 8,
+ pxa_lcdc_24bpp = 9,
+ pxa_lcdc_25bpp = 10,
+ } bpp;
+
+ uint32_t control[6];
+ uint32_t status[2];
+ uint32_t ovl1c[2];
+ uint32_t ovl2c[2];
+ uint32_t ccr;
+ uint32_t cmdcr;
+ uint32_t trgbr;
+ uint32_t tcr;
+ uint32_t liidr;
+ uint8_t bscntr;
+
+ struct DMAChannel dma_ch[7];
+
+ qemu_irq vsync_cb;
+ int orientation;
+};
+
+typedef struct QEMU_PACKED {
+ uint32_t fdaddr;
+ uint32_t fsaddr;
+ uint32_t fidr;
+ uint32_t ldcmd;
+} PXAFrameDescriptor;
+
+#define LCCR0 0x000 /* LCD Controller Control register 0 */
+#define LCCR1 0x004 /* LCD Controller Control register 1 */
+#define LCCR2 0x008 /* LCD Controller Control register 2 */
+#define LCCR3 0x00c /* LCD Controller Control register 3 */
+#define LCCR4 0x010 /* LCD Controller Control register 4 */
+#define LCCR5 0x014 /* LCD Controller Control register 5 */
+
+#define FBR0 0x020 /* DMA Channel 0 Frame Branch register */
+#define FBR1 0x024 /* DMA Channel 1 Frame Branch register */
+#define FBR2 0x028 /* DMA Channel 2 Frame Branch register */
+#define FBR3 0x02c /* DMA Channel 3 Frame Branch register */
+#define FBR4 0x030 /* DMA Channel 4 Frame Branch register */
+#define FBR5 0x110 /* DMA Channel 5 Frame Branch register */
+#define FBR6 0x114 /* DMA Channel 6 Frame Branch register */
+
+#define LCSR1 0x034 /* LCD Controller Status register 1 */
+#define LCSR0 0x038 /* LCD Controller Status register 0 */
+#define LIIDR 0x03c /* LCD Controller Interrupt ID register */
+
+#define TRGBR 0x040 /* TMED RGB Seed register */
+#define TCR 0x044 /* TMED Control register */
+
+#define OVL1C1 0x050 /* Overlay 1 Control register 1 */
+#define OVL1C2 0x060 /* Overlay 1 Control register 2 */
+#define OVL2C1 0x070 /* Overlay 2 Control register 1 */
+#define OVL2C2 0x080 /* Overlay 2 Control register 2 */
+#define CCR 0x090 /* Cursor Control register */
+
+#define CMDCR 0x100 /* Command Control register */
+#define PRSR 0x104 /* Panel Read Status register */
+
+#define PXA_LCDDMA_CHANS 7
+#define DMA_FDADR 0x00 /* Frame Descriptor Address register */
+#define DMA_FSADR 0x04 /* Frame Source Address register */
+#define DMA_FIDR 0x08 /* Frame ID register */
+#define DMA_LDCMD 0x0c /* Command register */
+
+/* LCD Buffer Strength Control register */
+#define BSCNTR 0x04000054
+
+/* Bitfield masks */
+#define LCCR0_ENB (1 << 0)
+#define LCCR0_CMS (1 << 1)
+#define LCCR0_SDS (1 << 2)
+#define LCCR0_LDM (1 << 3)
+#define LCCR0_SOFM0 (1 << 4)
+#define LCCR0_IUM (1 << 5)
+#define LCCR0_EOFM0 (1 << 6)
+#define LCCR0_PAS (1 << 7)
+#define LCCR0_DPD (1 << 9)
+#define LCCR0_DIS (1 << 10)
+#define LCCR0_QDM (1 << 11)
+#define LCCR0_PDD (0xff << 12)
+#define LCCR0_BSM0 (1 << 20)
+#define LCCR0_OUM (1 << 21)
+#define LCCR0_LCDT (1 << 22)
+#define LCCR0_RDSTM (1 << 23)
+#define LCCR0_CMDIM (1 << 24)
+#define LCCR0_OUC (1 << 25)
+#define LCCR0_LDDALT (1 << 26)
+#define LCCR1_PPL(x) ((x) & 0x3ff)
+#define LCCR2_LPP(x) ((x) & 0x3ff)
+#define LCCR3_API (15 << 16)
+#define LCCR3_BPP(x) ((((x) >> 24) & 7) | (((x) >> 26) & 8))
+#define LCCR3_PDFOR(x) (((x) >> 30) & 3)
+#define LCCR4_K1(x) (((x) >> 0) & 7)
+#define LCCR4_K2(x) (((x) >> 3) & 7)
+#define LCCR4_K3(x) (((x) >> 6) & 7)
+#define LCCR4_PALFOR(x) (((x) >> 15) & 3)
+#define LCCR5_SOFM(ch) (1 << (ch - 1))
+#define LCCR5_EOFM(ch) (1 << (ch + 7))
+#define LCCR5_BSM(ch) (1 << (ch + 15))
+#define LCCR5_IUM(ch) (1 << (ch + 23))
+#define OVLC1_EN (1 << 31)
+#define CCR_CEN (1 << 31)
+#define FBR_BRA (1 << 0)
+#define FBR_BINT (1 << 1)
+#define FBR_SRCADDR (0xfffffff << 4)
+#define LCSR0_LDD (1 << 0)
+#define LCSR0_SOF0 (1 << 1)
+#define LCSR0_BER (1 << 2)
+#define LCSR0_ABC (1 << 3)
+#define LCSR0_IU0 (1 << 4)
+#define LCSR0_IU1 (1 << 5)
+#define LCSR0_OU (1 << 6)
+#define LCSR0_QD (1 << 7)
+#define LCSR0_EOF0 (1 << 8)
+#define LCSR0_BS0 (1 << 9)
+#define LCSR0_SINT (1 << 10)
+#define LCSR0_RDST (1 << 11)
+#define LCSR0_CMDINT (1 << 12)
+#define LCSR0_BERCH(x) (((x) & 7) << 28)
+#define LCSR1_SOF(ch) (1 << (ch - 1))
+#define LCSR1_EOF(ch) (1 << (ch + 7))
+#define LCSR1_BS(ch) (1 << (ch + 15))
+#define LCSR1_IU(ch) (1 << (ch + 23))
+#define LDCMD_LENGTH(x) ((x) & 0x001ffffc)
+#define LDCMD_EOFINT (1 << 21)
+#define LDCMD_SOFINT (1 << 22)
+#define LDCMD_PAL (1 << 26)
+
+/* Size of a pixel in the QEMU UI output surface, in bytes */
+#define DEST_PIXEL_WIDTH 4
+
+/* Line drawing code to handle the various possible guest pixel formats */
+
+# define SKIP_PIXEL(to) do { to += deststep; } while (0)
+# define COPY_PIXEL(to, from) \
+ do { \
+ *(uint32_t *) to = from; \
+ SKIP_PIXEL(to); \
+ } while (0)
+
+#ifdef HOST_WORDS_BIGENDIAN
+# define SWAP_WORDS 1
+#endif
+
+#define FN_2(x) FN(x + 1) FN(x)
+#define FN_4(x) FN_2(x + 2) FN_2(x)
+
+static void pxa2xx_draw_line2(void *opaque, uint8_t *dest, const uint8_t *src,
+ int width, int deststep)
+{
+ uint32_t *palette = opaque;
+ uint32_t data;
+ while (width > 0) {
+ data = *(uint32_t *) src;
+#define FN(x) COPY_PIXEL(dest, palette[(data >> ((x) * 2)) & 3]);
+#ifdef SWAP_WORDS
+ FN_4(12)
+ FN_4(8)
+ FN_4(4)
+ FN_4(0)
+#else
+ FN_4(0)
+ FN_4(4)
+ FN_4(8)
+ FN_4(12)
+#endif
+#undef FN
+ width -= 16;
+ src += 4;
+ }
+}
+
+static void pxa2xx_draw_line4(void *opaque, uint8_t *dest, const uint8_t *src,
+ int width, int deststep)
+{
+ uint32_t *palette = opaque;
+ uint32_t data;
+ while (width > 0) {
+ data = *(uint32_t *) src;
+#define FN(x) COPY_PIXEL(dest, palette[(data >> ((x) * 4)) & 0xf]);
+#ifdef SWAP_WORDS
+ FN_2(6)
+ FN_2(4)
+ FN_2(2)
+ FN_2(0)
+#else
+ FN_2(0)
+ FN_2(2)
+ FN_2(4)
+ FN_2(6)
+#endif
+#undef FN
+ width -= 8;
+ src += 4;
+ }
+}
+
+static void pxa2xx_draw_line8(void *opaque, uint8_t *dest, const uint8_t *src,
+ int width, int deststep)
+{
+ uint32_t *palette = opaque;
+ uint32_t data;
+ while (width > 0) {
+ data = *(uint32_t *) src;
+#define FN(x) COPY_PIXEL(dest, palette[(data >> (x)) & 0xff]);
+#ifdef SWAP_WORDS
+ FN(24)
+ FN(16)
+ FN(8)
+ FN(0)
+#else
+ FN(0)
+ FN(8)
+ FN(16)
+ FN(24)
+#endif
+#undef FN
+ width -= 4;
+ src += 4;
+ }
+}
+
+static void pxa2xx_draw_line16(void *opaque, uint8_t *dest, const uint8_t *src,
+ int width, int deststep)
+{
+ uint32_t data;
+ unsigned int r, g, b;
+ while (width > 0) {
+ data = *(uint32_t *) src;
+#ifdef SWAP_WORDS
+ data = bswap32(data);
+#endif
+ b = (data & 0x1f) << 3;
+ data >>= 5;
+ g = (data & 0x3f) << 2;
+ data >>= 6;
+ r = (data & 0x1f) << 3;
+ data >>= 5;
+ COPY_PIXEL(dest, rgb_to_pixel32(r, g, b));
+ b = (data & 0x1f) << 3;
+ data >>= 5;
+ g = (data & 0x3f) << 2;
+ data >>= 6;
+ r = (data & 0x1f) << 3;
+ COPY_PIXEL(dest, rgb_to_pixel32(r, g, b));
+ width -= 2;
+ src += 4;
+ }
+}
+
+static void pxa2xx_draw_line16t(void *opaque, uint8_t *dest, const uint8_t *src,
+ int width, int deststep)
+{
+ uint32_t data;
+ unsigned int r, g, b;
+ while (width > 0) {
+ data = *(uint32_t *) src;
+#ifdef SWAP_WORDS
+ data = bswap32(data);
+#endif
+ b = (data & 0x1f) << 3;
+ data >>= 5;
+ g = (data & 0x1f) << 3;
+ data >>= 5;
+ r = (data & 0x1f) << 3;
+ data >>= 5;
+ if (data & 1) {
+ SKIP_PIXEL(dest);
+ } else {
+ COPY_PIXEL(dest, rgb_to_pixel32(r, g, b));
+ }
+ data >>= 1;
+ b = (data & 0x1f) << 3;
+ data >>= 5;
+ g = (data & 0x1f) << 3;
+ data >>= 5;
+ r = (data & 0x1f) << 3;
+ data >>= 5;
+ if (data & 1) {
+ SKIP_PIXEL(dest);
+ } else {
+ COPY_PIXEL(dest, rgb_to_pixel32(r, g, b));
+ }
+ width -= 2;
+ src += 4;
+ }
+}
+
+static void pxa2xx_draw_line18(void *opaque, uint8_t *dest, const uint8_t *src,
+ int width, int deststep)
+{
+ uint32_t data;
+ unsigned int r, g, b;
+ while (width > 0) {
+ data = *(uint32_t *) src;
+#ifdef SWAP_WORDS
+ data = bswap32(data);
+#endif
+ b = (data & 0x3f) << 2;
+ data >>= 6;
+ g = (data & 0x3f) << 2;
+ data >>= 6;
+ r = (data & 0x3f) << 2;
+ COPY_PIXEL(dest, rgb_to_pixel32(r, g, b));
+ width -= 1;
+ src += 4;
+ }
+}
+
+/* The wicked packed format */
+static void pxa2xx_draw_line18p(void *opaque, uint8_t *dest, const uint8_t *src,
+ int width, int deststep)
+{
+ uint32_t data[3];
+ unsigned int r, g, b;
+ while (width > 0) {
+ data[0] = *(uint32_t *) src;
+ src += 4;
+ data[1] = *(uint32_t *) src;
+ src += 4;
+ data[2] = *(uint32_t *) src;
+ src += 4;
+#ifdef SWAP_WORDS
+ data[0] = bswap32(data[0]);
+ data[1] = bswap32(data[1]);
+ data[2] = bswap32(data[2]);
+#endif
+ b = (data[0] & 0x3f) << 2;
+ data[0] >>= 6;
+ g = (data[0] & 0x3f) << 2;
+ data[0] >>= 6;
+ r = (data[0] & 0x3f) << 2;
+ data[0] >>= 12;
+ COPY_PIXEL(dest, rgb_to_pixel32(r, g, b));
+ b = (data[0] & 0x3f) << 2;
+ data[0] >>= 6;
+ g = ((data[1] & 0xf) << 4) | (data[0] << 2);
+ data[1] >>= 4;
+ r = (data[1] & 0x3f) << 2;
+ data[1] >>= 12;
+ COPY_PIXEL(dest, rgb_to_pixel32(r, g, b));
+ b = (data[1] & 0x3f) << 2;
+ data[1] >>= 6;
+ g = (data[1] & 0x3f) << 2;
+ data[1] >>= 6;
+ r = ((data[2] & 0x3) << 6) | (data[1] << 2);
+ data[2] >>= 8;
+ COPY_PIXEL(dest, rgb_to_pixel32(r, g, b));
+ b = (data[2] & 0x3f) << 2;
+ data[2] >>= 6;
+ g = (data[2] & 0x3f) << 2;
+ data[2] >>= 6;
+ r = data[2] << 2;
+ COPY_PIXEL(dest, rgb_to_pixel32(r, g, b));
+ width -= 4;
+ }
+}
+
+static void pxa2xx_draw_line19(void *opaque, uint8_t *dest, const uint8_t *src,
+ int width, int deststep)
+{
+ uint32_t data;
+ unsigned int r, g, b;
+ while (width > 0) {
+ data = *(uint32_t *) src;
+#ifdef SWAP_WORDS
+ data = bswap32(data);
+#endif
+ b = (data & 0x3f) << 2;
+ data >>= 6;
+ g = (data & 0x3f) << 2;
+ data >>= 6;
+ r = (data & 0x3f) << 2;
+ data >>= 6;
+ if (data & 1) {
+ SKIP_PIXEL(dest);
+ } else {
+ COPY_PIXEL(dest, rgb_to_pixel32(r, g, b));
+ }
+ width -= 1;
+ src += 4;
+ }
+}
+
+/* The wicked packed format */
+static void pxa2xx_draw_line19p(void *opaque, uint8_t *dest, const uint8_t *src,
+ int width, int deststep)
+{
+ uint32_t data[3];
+ unsigned int r, g, b;
+ while (width > 0) {
+ data[0] = *(uint32_t *) src;
+ src += 4;
+ data[1] = *(uint32_t *) src;
+ src += 4;
+ data[2] = *(uint32_t *) src;
+ src += 4;
+# ifdef SWAP_WORDS
+ data[0] = bswap32(data[0]);
+ data[1] = bswap32(data[1]);
+ data[2] = bswap32(data[2]);
+# endif
+ b = (data[0] & 0x3f) << 2;
+ data[0] >>= 6;
+ g = (data[0] & 0x3f) << 2;
+ data[0] >>= 6;
+ r = (data[0] & 0x3f) << 2;
+ data[0] >>= 6;
+ if (data[0] & 1) {
+ SKIP_PIXEL(dest);
+ } else {
+ COPY_PIXEL(dest, rgb_to_pixel32(r, g, b));
+ }
+ data[0] >>= 6;
+ b = (data[0] & 0x3f) << 2;
+ data[0] >>= 6;
+ g = ((data[1] & 0xf) << 4) | (data[0] << 2);
+ data[1] >>= 4;
+ r = (data[1] & 0x3f) << 2;
+ data[1] >>= 6;
+ if (data[1] & 1) {
+ SKIP_PIXEL(dest);
+ } else {
+ COPY_PIXEL(dest, rgb_to_pixel32(r, g, b));
+ }
+ data[1] >>= 6;
+ b = (data[1] & 0x3f) << 2;
+ data[1] >>= 6;
+ g = (data[1] & 0x3f) << 2;
+ data[1] >>= 6;
+ r = ((data[2] & 0x3) << 6) | (data[1] << 2);
+ data[2] >>= 2;
+ if (data[2] & 1) {
+ SKIP_PIXEL(dest);
+ } else {
+ COPY_PIXEL(dest, rgb_to_pixel32(r, g, b));
+ }
+ data[2] >>= 6;
+ b = (data[2] & 0x3f) << 2;
+ data[2] >>= 6;
+ g = (data[2] & 0x3f) << 2;
+ data[2] >>= 6;
+ r = data[2] << 2;
+ data[2] >>= 6;
+ if (data[2] & 1) {
+ SKIP_PIXEL(dest);
+ } else {
+ COPY_PIXEL(dest, rgb_to_pixel32(r, g, b));
+ }
+ width -= 4;
+ }
+}
+
+static void pxa2xx_draw_line24(void *opaque, uint8_t *dest, const uint8_t *src,
+ int width, int deststep)
+{
+ uint32_t data;
+ unsigned int r, g, b;
+ while (width > 0) {
+ data = *(uint32_t *) src;
+#ifdef SWAP_WORDS
+ data = bswap32(data);
+#endif
+ b = data & 0xff;
+ data >>= 8;
+ g = data & 0xff;
+ data >>= 8;
+ r = data & 0xff;
+ COPY_PIXEL(dest, rgb_to_pixel32(r, g, b));
+ width -= 1;
+ src += 4;
+ }
+}
+
+static void pxa2xx_draw_line24t(void *opaque, uint8_t *dest, const uint8_t *src,
+ int width, int deststep)
+{
+ uint32_t data;
+ unsigned int r, g, b;
+ while (width > 0) {
+ data = *(uint32_t *) src;
+#ifdef SWAP_WORDS
+ data = bswap32(data);
+#endif
+ b = (data & 0x7f) << 1;
+ data >>= 7;
+ g = data & 0xff;
+ data >>= 8;
+ r = data & 0xff;
+ data >>= 8;
+ if (data & 1) {
+ SKIP_PIXEL(dest);
+ } else {
+ COPY_PIXEL(dest, rgb_to_pixel32(r, g, b));
+ }
+ width -= 1;
+ src += 4;
+ }
+}
+
+static void pxa2xx_draw_line25(void *opaque, uint8_t *dest, const uint8_t *src,
+ int width, int deststep)
+{
+ uint32_t data;
+ unsigned int r, g, b;
+ while (width > 0) {
+ data = *(uint32_t *) src;
+#ifdef SWAP_WORDS
+ data = bswap32(data);
+#endif
+ b = data & 0xff;
+ data >>= 8;
+ g = data & 0xff;
+ data >>= 8;
+ r = data & 0xff;
+ data >>= 8;
+ if (data & 1) {
+ SKIP_PIXEL(dest);
+ } else {
+ COPY_PIXEL(dest, rgb_to_pixel32(r, g, b));
+ }
+ width -= 1;
+ src += 4;
+ }
+}
+
+/* Overlay planes disabled, no transparency */
+static drawfn pxa2xx_draw_fn_32[16] = {
+ [0 ... 0xf] = NULL,
+ [pxa_lcdc_2bpp] = pxa2xx_draw_line2,
+ [pxa_lcdc_4bpp] = pxa2xx_draw_line4,
+ [pxa_lcdc_8bpp] = pxa2xx_draw_line8,
+ [pxa_lcdc_16bpp] = pxa2xx_draw_line16,
+ [pxa_lcdc_18bpp] = pxa2xx_draw_line18,
+ [pxa_lcdc_18pbpp] = pxa2xx_draw_line18p,
+ [pxa_lcdc_24bpp] = pxa2xx_draw_line24,
+};
+
+/* Overlay planes enabled, transparency used */
+static drawfn pxa2xx_draw_fn_32t[16] = {
+ [0 ... 0xf] = NULL,
+ [pxa_lcdc_4bpp] = pxa2xx_draw_line4,
+ [pxa_lcdc_8bpp] = pxa2xx_draw_line8,
+ [pxa_lcdc_16bpp] = pxa2xx_draw_line16t,
+ [pxa_lcdc_19bpp] = pxa2xx_draw_line19,
+ [pxa_lcdc_19pbpp] = pxa2xx_draw_line19p,
+ [pxa_lcdc_24bpp] = pxa2xx_draw_line24t,
+ [pxa_lcdc_25bpp] = pxa2xx_draw_line25,
+};
+
+#undef COPY_PIXEL
+#undef SKIP_PIXEL
+
+#ifdef SWAP_WORDS
+# undef SWAP_WORDS
+#endif
+
+/* Route internal interrupt lines to the global IC */
+static void pxa2xx_lcdc_int_update(PXA2xxLCDState *s)
+{
+ int level = 0;
+ level |= (s->status[0] & LCSR0_LDD) && !(s->control[0] & LCCR0_LDM);
+ level |= (s->status[0] & LCSR0_SOF0) && !(s->control[0] & LCCR0_SOFM0);
+ level |= (s->status[0] & LCSR0_IU0) && !(s->control[0] & LCCR0_IUM);
+ level |= (s->status[0] & LCSR0_IU1) && !(s->control[5] & LCCR5_IUM(1));
+ level |= (s->status[0] & LCSR0_OU) && !(s->control[0] & LCCR0_OUM);
+ level |= (s->status[0] & LCSR0_QD) && !(s->control[0] & LCCR0_QDM);
+ level |= (s->status[0] & LCSR0_EOF0) && !(s->control[0] & LCCR0_EOFM0);
+ level |= (s->status[0] & LCSR0_BS0) && !(s->control[0] & LCCR0_BSM0);
+ level |= (s->status[0] & LCSR0_RDST) && !(s->control[0] & LCCR0_RDSTM);
+ level |= (s->status[0] & LCSR0_CMDINT) && !(s->control[0] & LCCR0_CMDIM);
+ level |= (s->status[1] & ~s->control[5]);
+
+ qemu_set_irq(s->irq, !!level);
+ s->irqlevel = level;
+}
+
+/* Set Branch Status interrupt high and poke associated registers */
+static inline void pxa2xx_dma_bs_set(PXA2xxLCDState *s, int ch)
+{
+ int unmasked;
+ if (ch == 0) {
+ s->status[0] |= LCSR0_BS0;
+ unmasked = !(s->control[0] & LCCR0_BSM0);
+ } else {
+ s->status[1] |= LCSR1_BS(ch);
+ unmasked = !(s->control[5] & LCCR5_BSM(ch));
+ }
+
+ if (unmasked) {
+ if (s->irqlevel)
+ s->status[0] |= LCSR0_SINT;
+ else
+ s->liidr = s->dma_ch[ch].id;
+ }
+}
+
+/* Set Start Of Frame Status interrupt high and poke associated registers */
+static inline void pxa2xx_dma_sof_set(PXA2xxLCDState *s, int ch)
+{
+ int unmasked;
+ if (!(s->dma_ch[ch].command & LDCMD_SOFINT))
+ return;
+
+ if (ch == 0) {
+ s->status[0] |= LCSR0_SOF0;
+ unmasked = !(s->control[0] & LCCR0_SOFM0);
+ } else {
+ s->status[1] |= LCSR1_SOF(ch);
+ unmasked = !(s->control[5] & LCCR5_SOFM(ch));
+ }
+
+ if (unmasked) {
+ if (s->irqlevel)
+ s->status[0] |= LCSR0_SINT;
+ else
+ s->liidr = s->dma_ch[ch].id;
+ }
+}
+
+/* Set End Of Frame Status interrupt high and poke associated registers */
+static inline void pxa2xx_dma_eof_set(PXA2xxLCDState *s, int ch)
+{
+ int unmasked;
+ if (!(s->dma_ch[ch].command & LDCMD_EOFINT))
+ return;
+
+ if (ch == 0) {
+ s->status[0] |= LCSR0_EOF0;
+ unmasked = !(s->control[0] & LCCR0_EOFM0);
+ } else {
+ s->status[1] |= LCSR1_EOF(ch);
+ unmasked = !(s->control[5] & LCCR5_EOFM(ch));
+ }
+
+ if (unmasked) {
+ if (s->irqlevel)
+ s->status[0] |= LCSR0_SINT;
+ else
+ s->liidr = s->dma_ch[ch].id;
+ }
+}
+
+/* Set Bus Error Status interrupt high and poke associated registers */
+static inline void pxa2xx_dma_ber_set(PXA2xxLCDState *s, int ch)
+{
+ s->status[0] |= LCSR0_BERCH(ch) | LCSR0_BER;
+ if (s->irqlevel)
+ s->status[0] |= LCSR0_SINT;
+ else
+ s->liidr = s->dma_ch[ch].id;
+}
+
+/* Load new Frame Descriptors from DMA */
+static void pxa2xx_descriptor_load(PXA2xxLCDState *s)
+{
+ PXAFrameDescriptor desc;
+ hwaddr descptr;
+ int i;
+
+ for (i = 0; i < PXA_LCDDMA_CHANS; i ++) {
+ s->dma_ch[i].source = 0;
+
+ if (!s->dma_ch[i].up)
+ continue;
+
+ if (s->dma_ch[i].branch & FBR_BRA) {
+ descptr = s->dma_ch[i].branch & FBR_SRCADDR;
+ if (s->dma_ch[i].branch & FBR_BINT)
+ pxa2xx_dma_bs_set(s, i);
+ s->dma_ch[i].branch &= ~FBR_BRA;
+ } else
+ descptr = s->dma_ch[i].descriptor;
+
+ if (!((descptr >= PXA2XX_SDRAM_BASE && descptr +
+ sizeof(desc) <= PXA2XX_SDRAM_BASE + current_machine->ram_size) ||
+ (descptr >= PXA2XX_INTERNAL_BASE && descptr + sizeof(desc) <=
+ PXA2XX_INTERNAL_BASE + PXA2XX_INTERNAL_SIZE))) {
+ continue;
+ }
+
+ cpu_physical_memory_read(descptr, &desc, sizeof(desc));
+ s->dma_ch[i].descriptor = le32_to_cpu(desc.fdaddr);
+ s->dma_ch[i].source = le32_to_cpu(desc.fsaddr);
+ s->dma_ch[i].id = le32_to_cpu(desc.fidr);
+ s->dma_ch[i].command = le32_to_cpu(desc.ldcmd);
+ }
+}
+
+static uint64_t pxa2xx_lcdc_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ PXA2xxLCDState *s = (PXA2xxLCDState *) opaque;
+ int ch;
+
+ switch (offset) {
+ case LCCR0:
+ return s->control[0];
+ case LCCR1:
+ return s->control[1];
+ case LCCR2:
+ return s->control[2];
+ case LCCR3:
+ return s->control[3];
+ case LCCR4:
+ return s->control[4];
+ case LCCR5:
+ return s->control[5];
+
+ case OVL1C1:
+ return s->ovl1c[0];
+ case OVL1C2:
+ return s->ovl1c[1];
+ case OVL2C1:
+ return s->ovl2c[0];
+ case OVL2C2:
+ return s->ovl2c[1];
+
+ case CCR:
+ return s->ccr;
+
+ case CMDCR:
+ return s->cmdcr;
+
+ case TRGBR:
+ return s->trgbr;
+ case TCR:
+ return s->tcr;
+
+ case 0x200 ... 0x1000: /* DMA per-channel registers */
+ ch = (offset - 0x200) >> 4;
+ if (!(ch >= 0 && ch < PXA_LCDDMA_CHANS))
+ goto fail;
+
+ switch (offset & 0xf) {
+ case DMA_FDADR:
+ return s->dma_ch[ch].descriptor;
+ case DMA_FSADR:
+ return s->dma_ch[ch].source;
+ case DMA_FIDR:
+ return s->dma_ch[ch].id;
+ case DMA_LDCMD:
+ return s->dma_ch[ch].command;
+ default:
+ goto fail;
+ }
+
+ case FBR0:
+ return s->dma_ch[0].branch;
+ case FBR1:
+ return s->dma_ch[1].branch;
+ case FBR2:
+ return s->dma_ch[2].branch;
+ case FBR3:
+ return s->dma_ch[3].branch;
+ case FBR4:
+ return s->dma_ch[4].branch;
+ case FBR5:
+ return s->dma_ch[5].branch;
+ case FBR6:
+ return s->dma_ch[6].branch;
+
+ case BSCNTR:
+ return s->bscntr;
+
+ case PRSR:
+ return 0;
+
+ case LCSR0:
+ return s->status[0];
+ case LCSR1:
+ return s->status[1];
+ case LIIDR:
+ return s->liidr;
+
+ default:
+ fail:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIX "\n",
+ __func__, offset);
+ }
+
+ return 0;
+}
+
+static void pxa2xx_lcdc_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ PXA2xxLCDState *s = (PXA2xxLCDState *) opaque;
+ int ch;
+
+ switch (offset) {
+ case LCCR0:
+ /* ACK Quick Disable done */
+ if ((s->control[0] & LCCR0_ENB) && !(value & LCCR0_ENB))
+ s->status[0] |= LCSR0_QD;
+
+ if (!(s->control[0] & LCCR0_LCDT) && (value & LCCR0_LCDT)) {
+ qemu_log_mask(LOG_UNIMP,
+ "%s: internal frame buffer unsupported\n", __func__);
+ }
+ if ((s->control[3] & LCCR3_API) &&
+ (value & LCCR0_ENB) && !(value & LCCR0_LCDT))
+ s->status[0] |= LCSR0_ABC;
+
+ s->control[0] = value & 0x07ffffff;
+ pxa2xx_lcdc_int_update(s);
+
+ s->dma_ch[0].up = !!(value & LCCR0_ENB);
+ s->dma_ch[1].up = (s->ovl1c[0] & OVLC1_EN) || (value & LCCR0_SDS);
+ break;
+
+ case LCCR1:
+ s->control[1] = value;
+ break;
+
+ case LCCR2:
+ s->control[2] = value;
+ break;
+
+ case LCCR3:
+ s->control[3] = value & 0xefffffff;
+ s->bpp = LCCR3_BPP(value);
+ break;
+
+ case LCCR4:
+ s->control[4] = value & 0x83ff81ff;
+ break;
+
+ case LCCR5:
+ s->control[5] = value & 0x3f3f3f3f;
+ break;
+
+ case OVL1C1:
+ if (!(s->ovl1c[0] & OVLC1_EN) && (value & OVLC1_EN)) {
+ qemu_log_mask(LOG_UNIMP, "%s: Overlay 1 not supported\n", __func__);
+ }
+ s->ovl1c[0] = value & 0x80ffffff;
+ s->dma_ch[1].up = (value & OVLC1_EN) || (s->control[0] & LCCR0_SDS);
+ break;
+
+ case OVL1C2:
+ s->ovl1c[1] = value & 0x000fffff;
+ break;
+
+ case OVL2C1:
+ if (!(s->ovl2c[0] & OVLC1_EN) && (value & OVLC1_EN)) {
+ qemu_log_mask(LOG_UNIMP, "%s: Overlay 2 not supported\n", __func__);
+ }
+ s->ovl2c[0] = value & 0x80ffffff;
+ s->dma_ch[2].up = !!(value & OVLC1_EN);
+ s->dma_ch[3].up = !!(value & OVLC1_EN);
+ s->dma_ch[4].up = !!(value & OVLC1_EN);
+ break;
+
+ case OVL2C2:
+ s->ovl2c[1] = value & 0x007fffff;
+ break;
+
+ case CCR:
+ if (!(s->ccr & CCR_CEN) && (value & CCR_CEN)) {
+ qemu_log_mask(LOG_UNIMP,
+ "%s: Hardware cursor unimplemented\n", __func__);
+ }
+ s->ccr = value & 0x81ffffe7;
+ s->dma_ch[5].up = !!(value & CCR_CEN);
+ break;
+
+ case CMDCR:
+ s->cmdcr = value & 0xff;
+ break;
+
+ case TRGBR:
+ s->trgbr = value & 0x00ffffff;
+ break;
+
+ case TCR:
+ s->tcr = value & 0x7fff;
+ break;
+
+ case 0x200 ... 0x1000: /* DMA per-channel registers */
+ ch = (offset - 0x200) >> 4;
+ if (!(ch >= 0 && ch < PXA_LCDDMA_CHANS))
+ goto fail;
+
+ switch (offset & 0xf) {
+ case DMA_FDADR:
+ s->dma_ch[ch].descriptor = value & 0xfffffff0;
+ break;
+
+ default:
+ goto fail;
+ }
+ break;
+
+ case FBR0:
+ s->dma_ch[0].branch = value & 0xfffffff3;
+ break;
+ case FBR1:
+ s->dma_ch[1].branch = value & 0xfffffff3;
+ break;
+ case FBR2:
+ s->dma_ch[2].branch = value & 0xfffffff3;
+ break;
+ case FBR3:
+ s->dma_ch[3].branch = value & 0xfffffff3;
+ break;
+ case FBR4:
+ s->dma_ch[4].branch = value & 0xfffffff3;
+ break;
+ case FBR5:
+ s->dma_ch[5].branch = value & 0xfffffff3;
+ break;
+ case FBR6:
+ s->dma_ch[6].branch = value & 0xfffffff3;
+ break;
+
+ case BSCNTR:
+ s->bscntr = value & 0xf;
+ break;
+
+ case PRSR:
+ break;
+
+ case LCSR0:
+ s->status[0] &= ~(value & 0xfff);
+ if (value & LCSR0_BER)
+ s->status[0] &= ~LCSR0_BERCH(7);
+ break;
+
+ case LCSR1:
+ s->status[1] &= ~(value & 0x3e3f3f);
+ break;
+
+ default:
+ fail:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIX "\n",
+ __func__, offset);
+ }
+}
+
+static const MemoryRegionOps pxa2xx_lcdc_ops = {
+ .read = pxa2xx_lcdc_read,
+ .write = pxa2xx_lcdc_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+/* Load new palette for a given DMA channel, convert to internal format */
+static void pxa2xx_palette_parse(PXA2xxLCDState *s, int ch, int bpp)
+{
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ int i, n, format, r, g, b, alpha;
+ uint32_t *dest;
+ uint8_t *src;
+ s->pal_for = LCCR4_PALFOR(s->control[4]);
+ format = s->pal_for;
+
+ switch (bpp) {
+ case pxa_lcdc_2bpp:
+ n = 4;
+ break;
+ case pxa_lcdc_4bpp:
+ n = 16;
+ break;
+ case pxa_lcdc_8bpp:
+ n = 256;
+ break;
+ default:
+ return;
+ }
+
+ src = (uint8_t *) s->dma_ch[ch].pbuffer;
+ dest = (uint32_t *) s->dma_ch[ch].palette;
+ alpha = r = g = b = 0;
+
+ for (i = 0; i < n; i ++) {
+ switch (format) {
+ case 0: /* 16 bpp, no transparency */
+ alpha = 0;
+ if (s->control[0] & LCCR0_CMS) {
+ r = g = b = *(uint16_t *) src & 0xff;
+ }
+ else {
+ r = (*(uint16_t *) src & 0xf800) >> 8;
+ g = (*(uint16_t *) src & 0x07e0) >> 3;
+ b = (*(uint16_t *) src & 0x001f) << 3;
+ }
+ src += 2;
+ break;
+ case 1: /* 16 bpp plus transparency */
+ alpha = *(uint32_t *) src & (1 << 24);
+ if (s->control[0] & LCCR0_CMS)
+ r = g = b = *(uint32_t *) src & 0xff;
+ else {
+ r = (*(uint32_t *) src & 0xf80000) >> 16;
+ g = (*(uint32_t *) src & 0x00fc00) >> 8;
+ b = (*(uint32_t *) src & 0x0000f8);
+ }
+ src += 4;
+ break;
+ case 2: /* 18 bpp plus transparency */
+ alpha = *(uint32_t *) src & (1 << 24);
+ if (s->control[0] & LCCR0_CMS)
+ r = g = b = *(uint32_t *) src & 0xff;
+ else {
+ r = (*(uint32_t *) src & 0xfc0000) >> 16;
+ g = (*(uint32_t *) src & 0x00fc00) >> 8;
+ b = (*(uint32_t *) src & 0x0000fc);
+ }
+ src += 4;
+ break;
+ case 3: /* 24 bpp plus transparency */
+ alpha = *(uint32_t *) src & (1 << 24);
+ if (s->control[0] & LCCR0_CMS)
+ r = g = b = *(uint32_t *) src & 0xff;
+ else {
+ r = (*(uint32_t *) src & 0xff0000) >> 16;
+ g = (*(uint32_t *) src & 0x00ff00) >> 8;
+ b = (*(uint32_t *) src & 0x0000ff);
+ }
+ src += 4;
+ break;
+ }
+ switch (surface_bits_per_pixel(surface)) {
+ case 8:
+ *dest = rgb_to_pixel8(r, g, b) | alpha;
+ break;
+ case 15:
+ *dest = rgb_to_pixel15(r, g, b) | alpha;
+ break;
+ case 16:
+ *dest = rgb_to_pixel16(r, g, b) | alpha;
+ break;
+ case 24:
+ *dest = rgb_to_pixel24(r, g, b) | alpha;
+ break;
+ case 32:
+ *dest = rgb_to_pixel32(r, g, b) | alpha;
+ break;
+ }
+ dest ++;
+ }
+}
+
+static inline drawfn pxa2xx_drawfn(PXA2xxLCDState *s)
+{
+ if (s->transp) {
+ return pxa2xx_draw_fn_32t[s->bpp];
+ } else {
+ return pxa2xx_draw_fn_32[s->bpp];
+ }
+}
+
+static void pxa2xx_lcdc_dma0_redraw_rot0(PXA2xxLCDState *s,
+ hwaddr addr, int *miny, int *maxy)
+{
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ int src_width, dest_width;
+ drawfn fn = pxa2xx_drawfn(s);
+ if (!fn)
+ return;
+
+ src_width = (s->xres + 3) & ~3; /* Pad to a 4 pixels multiple */
+ if (s->bpp == pxa_lcdc_19pbpp || s->bpp == pxa_lcdc_18pbpp)
+ src_width *= 3;
+ else if (s->bpp > pxa_lcdc_16bpp)
+ src_width *= 4;
+ else if (s->bpp > pxa_lcdc_8bpp)
+ src_width *= 2;
+
+ dest_width = s->xres * DEST_PIXEL_WIDTH;
+ *miny = 0;
+ if (s->invalidated) {
+ framebuffer_update_memory_section(&s->fbsection, s->sysmem,
+ addr, s->yres, src_width);
+ }
+ framebuffer_update_display(surface, &s->fbsection, s->xres, s->yres,
+ src_width, dest_width, DEST_PIXEL_WIDTH,
+ s->invalidated,
+ fn, s->dma_ch[0].palette, miny, maxy);
+}
+
+static void pxa2xx_lcdc_dma0_redraw_rot90(PXA2xxLCDState *s,
+ hwaddr addr, int *miny, int *maxy)
+{
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ int src_width, dest_width;
+ drawfn fn = pxa2xx_drawfn(s);
+ if (!fn)
+ return;
+
+ src_width = (s->xres + 3) & ~3; /* Pad to a 4 pixels multiple */
+ if (s->bpp == pxa_lcdc_19pbpp || s->bpp == pxa_lcdc_18pbpp)
+ src_width *= 3;
+ else if (s->bpp > pxa_lcdc_16bpp)
+ src_width *= 4;
+ else if (s->bpp > pxa_lcdc_8bpp)
+ src_width *= 2;
+
+ dest_width = s->yres * DEST_PIXEL_WIDTH;
+ *miny = 0;
+ if (s->invalidated) {
+ framebuffer_update_memory_section(&s->fbsection, s->sysmem,
+ addr, s->yres, src_width);
+ }
+ framebuffer_update_display(surface, &s->fbsection, s->xres, s->yres,
+ src_width, DEST_PIXEL_WIDTH, -dest_width,
+ s->invalidated,
+ fn, s->dma_ch[0].palette,
+ miny, maxy);
+}
+
+static void pxa2xx_lcdc_dma0_redraw_rot180(PXA2xxLCDState *s,
+ hwaddr addr, int *miny, int *maxy)
+{
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ int src_width, dest_width;
+ drawfn fn = pxa2xx_drawfn(s);
+ if (!fn) {
+ return;
+ }
+
+ src_width = (s->xres + 3) & ~3; /* Pad to a 4 pixels multiple */
+ if (s->bpp == pxa_lcdc_19pbpp || s->bpp == pxa_lcdc_18pbpp) {
+ src_width *= 3;
+ } else if (s->bpp > pxa_lcdc_16bpp) {
+ src_width *= 4;
+ } else if (s->bpp > pxa_lcdc_8bpp) {
+ src_width *= 2;
+ }
+
+ dest_width = s->xres * DEST_PIXEL_WIDTH;
+ *miny = 0;
+ if (s->invalidated) {
+ framebuffer_update_memory_section(&s->fbsection, s->sysmem,
+ addr, s->yres, src_width);
+ }
+ framebuffer_update_display(surface, &s->fbsection, s->xres, s->yres,
+ src_width, -dest_width, -DEST_PIXEL_WIDTH,
+ s->invalidated,
+ fn, s->dma_ch[0].palette, miny, maxy);
+}
+
+static void pxa2xx_lcdc_dma0_redraw_rot270(PXA2xxLCDState *s,
+ hwaddr addr, int *miny, int *maxy)
+{
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ int src_width, dest_width;
+ drawfn fn = pxa2xx_drawfn(s);
+ if (!fn) {
+ return;
+ }
+
+ src_width = (s->xres + 3) & ~3; /* Pad to a 4 pixels multiple */
+ if (s->bpp == pxa_lcdc_19pbpp || s->bpp == pxa_lcdc_18pbpp) {
+ src_width *= 3;
+ } else if (s->bpp > pxa_lcdc_16bpp) {
+ src_width *= 4;
+ } else if (s->bpp > pxa_lcdc_8bpp) {
+ src_width *= 2;
+ }
+
+ dest_width = s->yres * DEST_PIXEL_WIDTH;
+ *miny = 0;
+ if (s->invalidated) {
+ framebuffer_update_memory_section(&s->fbsection, s->sysmem,
+ addr, s->yres, src_width);
+ }
+ framebuffer_update_display(surface, &s->fbsection, s->xres, s->yres,
+ src_width, -DEST_PIXEL_WIDTH, dest_width,
+ s->invalidated,
+ fn, s->dma_ch[0].palette,
+ miny, maxy);
+}
+
+static void pxa2xx_lcdc_resize(PXA2xxLCDState *s)
+{
+ int width, height;
+ if (!(s->control[0] & LCCR0_ENB))
+ return;
+
+ width = LCCR1_PPL(s->control[1]) + 1;
+ height = LCCR2_LPP(s->control[2]) + 1;
+
+ if (width != s->xres || height != s->yres) {
+ if (s->orientation == 90 || s->orientation == 270) {
+ qemu_console_resize(s->con, height, width);
+ } else {
+ qemu_console_resize(s->con, width, height);
+ }
+ s->invalidated = 1;
+ s->xres = width;
+ s->yres = height;
+ }
+}
+
+static void pxa2xx_update_display(void *opaque)
+{
+ PXA2xxLCDState *s = (PXA2xxLCDState *) opaque;
+ hwaddr fbptr;
+ int miny, maxy;
+ int ch;
+ if (!(s->control[0] & LCCR0_ENB))
+ return;
+
+ pxa2xx_descriptor_load(s);
+
+ pxa2xx_lcdc_resize(s);
+ miny = s->yres;
+ maxy = 0;
+ s->transp = s->dma_ch[2].up || s->dma_ch[3].up;
+ /* Note: With overlay planes the order depends on LCCR0 bit 25. */
+ for (ch = 0; ch < PXA_LCDDMA_CHANS; ch ++)
+ if (s->dma_ch[ch].up) {
+ if (!s->dma_ch[ch].source) {
+ pxa2xx_dma_ber_set(s, ch);
+ continue;
+ }
+ fbptr = s->dma_ch[ch].source;
+ if (!((fbptr >= PXA2XX_SDRAM_BASE &&
+ fbptr <= PXA2XX_SDRAM_BASE + current_machine->ram_size) ||
+ (fbptr >= PXA2XX_INTERNAL_BASE &&
+ fbptr <= PXA2XX_INTERNAL_BASE + PXA2XX_INTERNAL_SIZE))) {
+ pxa2xx_dma_ber_set(s, ch);
+ continue;
+ }
+
+ if (s->dma_ch[ch].command & LDCMD_PAL) {
+ cpu_physical_memory_read(fbptr, s->dma_ch[ch].pbuffer,
+ MAX(LDCMD_LENGTH(s->dma_ch[ch].command),
+ sizeof(s->dma_ch[ch].pbuffer)));
+ pxa2xx_palette_parse(s, ch, s->bpp);
+ } else {
+ /* Do we need to reparse palette */
+ if (LCCR4_PALFOR(s->control[4]) != s->pal_for)
+ pxa2xx_palette_parse(s, ch, s->bpp);
+
+ /* ACK frame start */
+ pxa2xx_dma_sof_set(s, ch);
+
+ s->dma_ch[ch].redraw(s, fbptr, &miny, &maxy);
+ s->invalidated = 0;
+
+ /* ACK frame completed */
+ pxa2xx_dma_eof_set(s, ch);
+ }
+ }
+
+ if (s->control[0] & LCCR0_DIS) {
+ /* ACK last frame completed */
+ s->control[0] &= ~LCCR0_ENB;
+ s->status[0] |= LCSR0_LDD;
+ }
+
+ if (miny >= 0) {
+ switch (s->orientation) {
+ case 0:
+ dpy_gfx_update(s->con, 0, miny, s->xres, maxy - miny + 1);
+ break;
+ case 90:
+ dpy_gfx_update(s->con, miny, 0, maxy - miny + 1, s->xres);
+ break;
+ case 180:
+ maxy = s->yres - maxy - 1;
+ miny = s->yres - miny - 1;
+ dpy_gfx_update(s->con, 0, maxy, s->xres, miny - maxy + 1);
+ break;
+ case 270:
+ maxy = s->yres - maxy - 1;
+ miny = s->yres - miny - 1;
+ dpy_gfx_update(s->con, maxy, 0, miny - maxy + 1, s->xres);
+ break;
+ }
+ }
+ pxa2xx_lcdc_int_update(s);
+
+ qemu_irq_raise(s->vsync_cb);
+}
+
+static void pxa2xx_invalidate_display(void *opaque)
+{
+ PXA2xxLCDState *s = (PXA2xxLCDState *) opaque;
+ s->invalidated = 1;
+}
+
+static void pxa2xx_lcdc_orientation(void *opaque, int angle)
+{
+ PXA2xxLCDState *s = (PXA2xxLCDState *) opaque;
+
+ switch (angle) {
+ case 0:
+ s->dma_ch[0].redraw = pxa2xx_lcdc_dma0_redraw_rot0;
+ break;
+ case 90:
+ s->dma_ch[0].redraw = pxa2xx_lcdc_dma0_redraw_rot90;
+ break;
+ case 180:
+ s->dma_ch[0].redraw = pxa2xx_lcdc_dma0_redraw_rot180;
+ break;
+ case 270:
+ s->dma_ch[0].redraw = pxa2xx_lcdc_dma0_redraw_rot270;
+ break;
+ }
+
+ s->orientation = angle;
+ s->xres = s->yres = -1;
+ pxa2xx_lcdc_resize(s);
+}
+
+static const VMStateDescription vmstate_dma_channel = {
+ .name = "dma_channel",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(branch, struct DMAChannel),
+ VMSTATE_UINT8(up, struct DMAChannel),
+ VMSTATE_BUFFER(pbuffer, struct DMAChannel),
+ VMSTATE_UINT32(descriptor, struct DMAChannel),
+ VMSTATE_UINT32(source, struct DMAChannel),
+ VMSTATE_UINT32(id, struct DMAChannel),
+ VMSTATE_UINT32(command, struct DMAChannel),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static int pxa2xx_lcdc_post_load(void *opaque, int version_id)
+{
+ PXA2xxLCDState *s = opaque;
+
+ s->bpp = LCCR3_BPP(s->control[3]);
+ s->xres = s->yres = s->pal_for = -1;
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_pxa2xx_lcdc = {
+ .name = "pxa2xx_lcdc",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .post_load = pxa2xx_lcdc_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(irqlevel, PXA2xxLCDState),
+ VMSTATE_INT32(transp, PXA2xxLCDState),
+ VMSTATE_UINT32_ARRAY(control, PXA2xxLCDState, 6),
+ VMSTATE_UINT32_ARRAY(status, PXA2xxLCDState, 2),
+ VMSTATE_UINT32_ARRAY(ovl1c, PXA2xxLCDState, 2),
+ VMSTATE_UINT32_ARRAY(ovl2c, PXA2xxLCDState, 2),
+ VMSTATE_UINT32(ccr, PXA2xxLCDState),
+ VMSTATE_UINT32(cmdcr, PXA2xxLCDState),
+ VMSTATE_UINT32(trgbr, PXA2xxLCDState),
+ VMSTATE_UINT32(tcr, PXA2xxLCDState),
+ VMSTATE_UINT32(liidr, PXA2xxLCDState),
+ VMSTATE_UINT8(bscntr, PXA2xxLCDState),
+ VMSTATE_STRUCT_ARRAY(dma_ch, PXA2xxLCDState, 7, 0,
+ vmstate_dma_channel, struct DMAChannel),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const GraphicHwOps pxa2xx_ops = {
+ .invalidate = pxa2xx_invalidate_display,
+ .gfx_update = pxa2xx_update_display,
+};
+
+PXA2xxLCDState *pxa2xx_lcdc_init(MemoryRegion *sysmem,
+ hwaddr base, qemu_irq irq)
+{
+ PXA2xxLCDState *s;
+
+ s = (PXA2xxLCDState *) g_malloc0(sizeof(PXA2xxLCDState));
+ s->invalidated = 1;
+ s->irq = irq;
+ s->sysmem = sysmem;
+
+ pxa2xx_lcdc_orientation(s, graphic_rotate);
+
+ memory_region_init_io(&s->iomem, NULL, &pxa2xx_lcdc_ops, s,
+ "pxa2xx-lcd-controller", 0x00100000);
+ memory_region_add_subregion(sysmem, base, &s->iomem);
+
+ s->con = graphic_console_init(NULL, 0, &pxa2xx_ops, s);
+
+ vmstate_register(NULL, 0, &vmstate_pxa2xx_lcdc, s);
+
+ return s;
+}
+
+void pxa2xx_lcd_vsync_notifier(PXA2xxLCDState *s, qemu_irq handler)
+{
+ s->vsync_cb = handler;
+}
diff --git a/hw/display/qxl-logger.c b/hw/display/qxl-logger.c
new file mode 100644
index 000000000..68bfa4756
--- /dev/null
+++ b/hw/display/qxl-logger.c
@@ -0,0 +1,274 @@
+/*
+ * qxl command logging -- for debug purposes
+ *
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * maintained by Gerd Hoffmann <kraxel@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/timer.h"
+#include "qxl.h"
+
+static const char *const qxl_type[] = {
+ [ QXL_CMD_NOP ] = "nop",
+ [ QXL_CMD_DRAW ] = "draw",
+ [ QXL_CMD_UPDATE ] = "update",
+ [ QXL_CMD_CURSOR ] = "cursor",
+ [ QXL_CMD_MESSAGE ] = "message",
+ [ QXL_CMD_SURFACE ] = "surface",
+};
+
+static const char *const qxl_draw_type[] = {
+ [ QXL_DRAW_NOP ] = "nop",
+ [ QXL_DRAW_FILL ] = "fill",
+ [ QXL_DRAW_OPAQUE ] = "opaque",
+ [ QXL_DRAW_COPY ] = "copy",
+ [ QXL_COPY_BITS ] = "copy-bits",
+ [ QXL_DRAW_BLEND ] = "blend",
+ [ QXL_DRAW_BLACKNESS ] = "blackness",
+ [ QXL_DRAW_WHITENESS ] = "whitemess",
+ [ QXL_DRAW_INVERS ] = "invers",
+ [ QXL_DRAW_ROP3 ] = "rop3",
+ [ QXL_DRAW_STROKE ] = "stroke",
+ [ QXL_DRAW_TEXT ] = "text",
+ [ QXL_DRAW_TRANSPARENT ] = "transparent",
+ [ QXL_DRAW_ALPHA_BLEND ] = "alpha-blend",
+};
+
+static const char *const qxl_draw_effect[] = {
+ [ QXL_EFFECT_BLEND ] = "blend",
+ [ QXL_EFFECT_OPAQUE ] = "opaque",
+ [ QXL_EFFECT_REVERT_ON_DUP ] = "revert-on-dup",
+ [ QXL_EFFECT_BLACKNESS_ON_DUP ] = "blackness-on-dup",
+ [ QXL_EFFECT_WHITENESS_ON_DUP ] = "whiteness-on-dup",
+ [ QXL_EFFECT_NOP_ON_DUP ] = "nop-on-dup",
+ [ QXL_EFFECT_NOP ] = "nop",
+ [ QXL_EFFECT_OPAQUE_BRUSH ] = "opaque-brush",
+};
+
+static const char *const qxl_surface_cmd[] = {
+ [ QXL_SURFACE_CMD_CREATE ] = "create",
+ [ QXL_SURFACE_CMD_DESTROY ] = "destroy",
+};
+
+static const char *const spice_surface_fmt[] = {
+ [ SPICE_SURFACE_FMT_INVALID ] = "invalid",
+ [ SPICE_SURFACE_FMT_1_A ] = "alpha/1",
+ [ SPICE_SURFACE_FMT_8_A ] = "alpha/8",
+ [ SPICE_SURFACE_FMT_16_555 ] = "555/16",
+ [ SPICE_SURFACE_FMT_16_565 ] = "565/16",
+ [ SPICE_SURFACE_FMT_32_xRGB ] = "xRGB/32",
+ [ SPICE_SURFACE_FMT_32_ARGB ] = "ARGB/32",
+};
+
+static const char *const qxl_cursor_cmd[] = {
+ [ QXL_CURSOR_SET ] = "set",
+ [ QXL_CURSOR_MOVE ] = "move",
+ [ QXL_CURSOR_HIDE ] = "hide",
+ [ QXL_CURSOR_TRAIL ] = "trail",
+};
+
+static const char *const spice_cursor_type[] = {
+ [ SPICE_CURSOR_TYPE_ALPHA ] = "alpha",
+ [ SPICE_CURSOR_TYPE_MONO ] = "mono",
+ [ SPICE_CURSOR_TYPE_COLOR4 ] = "color4",
+ [ SPICE_CURSOR_TYPE_COLOR8 ] = "color8",
+ [ SPICE_CURSOR_TYPE_COLOR16 ] = "color16",
+ [ SPICE_CURSOR_TYPE_COLOR24 ] = "color24",
+ [ SPICE_CURSOR_TYPE_COLOR32 ] = "color32",
+};
+
+static const char *qxl_v2n(const char *const n[], size_t l, int v)
+{
+ if (v >= l || !n[v]) {
+ return "???";
+ }
+ return n[v];
+}
+#define qxl_name(_list, _value) qxl_v2n(_list, ARRAY_SIZE(_list), _value)
+
+static int qxl_log_image(PCIQXLDevice *qxl, QXLPHYSICAL addr, int group_id)
+{
+ QXLImage *image;
+ QXLImageDescriptor *desc;
+
+ image = qxl_phys2virt(qxl, addr, group_id);
+ if (!image) {
+ return 1;
+ }
+ desc = &image->descriptor;
+ fprintf(stderr, " (id %" PRIx64 " type %d flags %d width %d height %d",
+ desc->id, desc->type, desc->flags, desc->width, desc->height);
+ switch (desc->type) {
+ case SPICE_IMAGE_TYPE_BITMAP:
+ fprintf(stderr, ", fmt %d flags %d x %d y %d stride %d"
+ " palette %" PRIx64 " data %" PRIx64,
+ image->bitmap.format, image->bitmap.flags,
+ image->bitmap.x, image->bitmap.y,
+ image->bitmap.stride,
+ image->bitmap.palette, image->bitmap.data);
+ break;
+ }
+ fprintf(stderr, ")");
+ return 0;
+}
+
+static void qxl_log_rect(QXLRect *rect)
+{
+ fprintf(stderr, " %dx%d+%d+%d",
+ rect->right - rect->left,
+ rect->bottom - rect->top,
+ rect->left, rect->top);
+}
+
+static int qxl_log_cmd_draw_copy(PCIQXLDevice *qxl, QXLCopy *copy,
+ int group_id)
+{
+ int ret;
+
+ fprintf(stderr, " src %" PRIx64,
+ copy->src_bitmap);
+ ret = qxl_log_image(qxl, copy->src_bitmap, group_id);
+ if (ret != 0) {
+ return ret;
+ }
+ fprintf(stderr, " area");
+ qxl_log_rect(&copy->src_area);
+ fprintf(stderr, " rop %d", copy->rop_descriptor);
+ return 0;
+}
+
+static int qxl_log_cmd_draw(PCIQXLDevice *qxl, QXLDrawable *draw, int group_id)
+{
+ fprintf(stderr, ": surface_id %d type %s effect %s",
+ draw->surface_id,
+ qxl_name(qxl_draw_type, draw->type),
+ qxl_name(qxl_draw_effect, draw->effect));
+ switch (draw->type) {
+ case QXL_DRAW_COPY:
+ return qxl_log_cmd_draw_copy(qxl, &draw->u.copy, group_id);
+ }
+ return 0;
+}
+
+static int qxl_log_cmd_draw_compat(PCIQXLDevice *qxl, QXLCompatDrawable *draw,
+ int group_id)
+{
+ fprintf(stderr, ": type %s effect %s",
+ qxl_name(qxl_draw_type, draw->type),
+ qxl_name(qxl_draw_effect, draw->effect));
+ if (draw->bitmap_offset) {
+ fprintf(stderr, ": bitmap %d",
+ draw->bitmap_offset);
+ qxl_log_rect(&draw->bitmap_area);
+ }
+ switch (draw->type) {
+ case QXL_DRAW_COPY:
+ return qxl_log_cmd_draw_copy(qxl, &draw->u.copy, group_id);
+ }
+ return 0;
+}
+
+static void qxl_log_cmd_surface(PCIQXLDevice *qxl, QXLSurfaceCmd *cmd)
+{
+ fprintf(stderr, ": %s id %d",
+ qxl_name(qxl_surface_cmd, cmd->type),
+ cmd->surface_id);
+ if (cmd->type == QXL_SURFACE_CMD_CREATE) {
+ fprintf(stderr, " size %dx%d stride %d format %s (count %u, max %u)",
+ cmd->u.surface_create.width,
+ cmd->u.surface_create.height,
+ cmd->u.surface_create.stride,
+ qxl_name(spice_surface_fmt, cmd->u.surface_create.format),
+ qxl->guest_surfaces.count, qxl->guest_surfaces.max);
+ }
+ if (cmd->type == QXL_SURFACE_CMD_DESTROY) {
+ fprintf(stderr, " (count %u)", qxl->guest_surfaces.count);
+ }
+}
+
+int qxl_log_cmd_cursor(PCIQXLDevice *qxl, QXLCursorCmd *cmd, int group_id)
+{
+ QXLCursor *cursor;
+
+ fprintf(stderr, ": %s",
+ qxl_name(qxl_cursor_cmd, cmd->type));
+ switch (cmd->type) {
+ case QXL_CURSOR_SET:
+ fprintf(stderr, " +%d+%d visible %s, shape @ 0x%" PRIx64,
+ cmd->u.set.position.x,
+ cmd->u.set.position.y,
+ cmd->u.set.visible ? "yes" : "no",
+ cmd->u.set.shape);
+ cursor = qxl_phys2virt(qxl, cmd->u.set.shape, group_id);
+ if (!cursor) {
+ return 1;
+ }
+ fprintf(stderr, " type %s size %dx%d hot-spot +%d+%d"
+ " unique 0x%" PRIx64 " data-size %d",
+ qxl_name(spice_cursor_type, cursor->header.type),
+ cursor->header.width, cursor->header.height,
+ cursor->header.hot_spot_x, cursor->header.hot_spot_y,
+ cursor->header.unique, cursor->data_size);
+ break;
+ case QXL_CURSOR_MOVE:
+ fprintf(stderr, " +%d+%d", cmd->u.position.x, cmd->u.position.y);
+ break;
+ }
+ return 0;
+}
+
+int qxl_log_command(PCIQXLDevice *qxl, const char *ring, QXLCommandExt *ext)
+{
+ bool compat = ext->flags & QXL_COMMAND_FLAG_COMPAT;
+ void *data;
+ int ret;
+
+ if (!qxl->cmdlog) {
+ return 0;
+ }
+ fprintf(stderr, "%" PRId64 " qxl-%d/%s:", qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL),
+ qxl->id, ring);
+ fprintf(stderr, " cmd @ 0x%" PRIx64 " %s%s", ext->cmd.data,
+ qxl_name(qxl_type, ext->cmd.type),
+ compat ? "(compat)" : "");
+
+ data = qxl_phys2virt(qxl, ext->cmd.data, ext->group_id);
+ if (!data) {
+ return 1;
+ }
+ switch (ext->cmd.type) {
+ case QXL_CMD_DRAW:
+ if (!compat) {
+ ret = qxl_log_cmd_draw(qxl, data, ext->group_id);
+ } else {
+ ret = qxl_log_cmd_draw_compat(qxl, data, ext->group_id);
+ }
+ if (ret) {
+ return ret;
+ }
+ break;
+ case QXL_CMD_SURFACE:
+ qxl_log_cmd_surface(qxl, data);
+ break;
+ case QXL_CMD_CURSOR:
+ qxl_log_cmd_cursor(qxl, data, ext->group_id);
+ break;
+ }
+ fprintf(stderr, "\n");
+ return 0;
+}
diff --git a/hw/display/qxl-render.c b/hw/display/qxl-render.c
new file mode 100644
index 000000000..d28849b12
--- /dev/null
+++ b/hw/display/qxl-render.c
@@ -0,0 +1,337 @@
+/*
+ * qxl local rendering (aka display on sdl/vnc)
+ *
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * maintained by Gerd Hoffmann <kraxel@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "qxl.h"
+#include "sysemu/runstate.h"
+#include "trace.h"
+
+static void qxl_blit(PCIQXLDevice *qxl, QXLRect *rect)
+{
+ DisplaySurface *surface = qemu_console_surface(qxl->vga.con);
+ uint8_t *dst = surface_data(surface);
+ uint8_t *src;
+ int len, i;
+
+ if (is_buffer_shared(surface)) {
+ return;
+ }
+ trace_qxl_render_blit(qxl->guest_primary.qxl_stride,
+ rect->left, rect->right, rect->top, rect->bottom);
+ src = qxl->guest_primary.data;
+ if (qxl->guest_primary.qxl_stride < 0) {
+ /* qxl surface is upside down, walk src scanlines
+ * in reverse order to flip it */
+ src += (qxl->guest_primary.surface.height - rect->top - 1) *
+ qxl->guest_primary.abs_stride;
+ } else {
+ src += rect->top * qxl->guest_primary.abs_stride;
+ }
+ dst += rect->top * qxl->guest_primary.abs_stride;
+ src += rect->left * qxl->guest_primary.bytes_pp;
+ dst += rect->left * qxl->guest_primary.bytes_pp;
+ len = (rect->right - rect->left) * qxl->guest_primary.bytes_pp;
+
+ for (i = rect->top; i < rect->bottom; i++) {
+ memcpy(dst, src, len);
+ dst += qxl->guest_primary.abs_stride;
+ src += qxl->guest_primary.qxl_stride;
+ }
+}
+
+void qxl_render_resize(PCIQXLDevice *qxl)
+{
+ QXLSurfaceCreate *sc = &qxl->guest_primary.surface;
+
+ qxl->guest_primary.qxl_stride = sc->stride;
+ qxl->guest_primary.abs_stride = abs(sc->stride);
+ qxl->guest_primary.resized++;
+ switch (sc->format) {
+ case SPICE_SURFACE_FMT_16_555:
+ qxl->guest_primary.bytes_pp = 2;
+ qxl->guest_primary.bits_pp = 15;
+ break;
+ case SPICE_SURFACE_FMT_16_565:
+ qxl->guest_primary.bytes_pp = 2;
+ qxl->guest_primary.bits_pp = 16;
+ break;
+ case SPICE_SURFACE_FMT_32_xRGB:
+ case SPICE_SURFACE_FMT_32_ARGB:
+ qxl->guest_primary.bytes_pp = 4;
+ qxl->guest_primary.bits_pp = 32;
+ break;
+ default:
+ fprintf(stderr, "%s: unhandled format: %x\n", __func__,
+ qxl->guest_primary.surface.format);
+ qxl->guest_primary.bytes_pp = 4;
+ qxl->guest_primary.bits_pp = 32;
+ break;
+ }
+}
+
+static void qxl_set_rect_to_surface(PCIQXLDevice *qxl, QXLRect *area)
+{
+ area->left = 0;
+ area->right = qxl->guest_primary.surface.width;
+ area->top = 0;
+ area->bottom = qxl->guest_primary.surface.height;
+}
+
+static void qxl_render_update_area_unlocked(PCIQXLDevice *qxl)
+{
+ VGACommonState *vga = &qxl->vga;
+ DisplaySurface *surface;
+ int width = qxl->guest_head0_width ?: qxl->guest_primary.surface.width;
+ int height = qxl->guest_head0_height ?: qxl->guest_primary.surface.height;
+ int i;
+
+ if (qxl->guest_primary.resized) {
+ qxl->guest_primary.resized = 0;
+ qxl->guest_primary.data = qxl_phys2virt(qxl,
+ qxl->guest_primary.surface.mem,
+ MEMSLOT_GROUP_GUEST);
+ if (!qxl->guest_primary.data) {
+ goto end;
+ }
+ qxl_set_rect_to_surface(qxl, &qxl->dirty[0]);
+ qxl->num_dirty_rects = 1;
+ trace_qxl_render_guest_primary_resized(
+ width,
+ height,
+ qxl->guest_primary.qxl_stride,
+ qxl->guest_primary.bytes_pp,
+ qxl->guest_primary.bits_pp);
+ if (qxl->guest_primary.qxl_stride > 0) {
+ pixman_format_code_t format =
+ qemu_default_pixman_format(qxl->guest_primary.bits_pp, true);
+ surface = qemu_create_displaysurface_from
+ (width,
+ height,
+ format,
+ qxl->guest_primary.abs_stride,
+ qxl->guest_primary.data);
+ } else {
+ surface = qemu_create_displaysurface
+ (width,
+ height);
+ }
+ dpy_gfx_replace_surface(vga->con, surface);
+ }
+
+ if (!qxl->guest_primary.data) {
+ goto end;
+ }
+ for (i = 0; i < qxl->num_dirty_rects; i++) {
+ if (qemu_spice_rect_is_empty(qxl->dirty+i)) {
+ break;
+ }
+ if (qxl->dirty[i].left < 0 ||
+ qxl->dirty[i].top < 0 ||
+ qxl->dirty[i].left > qxl->dirty[i].right ||
+ qxl->dirty[i].top > qxl->dirty[i].bottom ||
+ qxl->dirty[i].right > width ||
+ qxl->dirty[i].bottom > height) {
+ continue;
+ }
+ qxl_blit(qxl, qxl->dirty+i);
+ dpy_gfx_update(vga->con,
+ qxl->dirty[i].left, qxl->dirty[i].top,
+ qxl->dirty[i].right - qxl->dirty[i].left,
+ qxl->dirty[i].bottom - qxl->dirty[i].top);
+ }
+ qxl->num_dirty_rects = 0;
+
+end:
+ if (qxl->render_update_cookie_num == 0) {
+ graphic_hw_update_done(qxl->ssd.dcl.con);
+ }
+}
+
+/*
+ * use ssd.lock to protect render_update_cookie_num.
+ * qxl_render_update is called by io thread or vcpu thread, and the completion
+ * callbacks are called by spice_server thread, deferring to bh called from the
+ * io thread.
+ */
+void qxl_render_update(PCIQXLDevice *qxl)
+{
+ QXLCookie *cookie;
+
+ qemu_mutex_lock(&qxl->ssd.lock);
+
+ if (!runstate_is_running() || !qxl->guest_primary.commands ||
+ qxl->mode == QXL_MODE_UNDEFINED) {
+ qxl_render_update_area_unlocked(qxl);
+ qemu_mutex_unlock(&qxl->ssd.lock);
+ graphic_hw_update_done(qxl->ssd.dcl.con);
+ return;
+ }
+
+ qxl->guest_primary.commands = 0;
+ qxl->render_update_cookie_num++;
+ qemu_mutex_unlock(&qxl->ssd.lock);
+ cookie = qxl_cookie_new(QXL_COOKIE_TYPE_RENDER_UPDATE_AREA,
+ 0);
+ qxl_set_rect_to_surface(qxl, &cookie->u.render.area);
+ qxl_spice_update_area(qxl, 0, &cookie->u.render.area, NULL,
+ 0, 1 /* clear_dirty_region */, QXL_ASYNC, cookie);
+}
+
+void qxl_render_update_area_bh(void *opaque)
+{
+ PCIQXLDevice *qxl = opaque;
+
+ qemu_mutex_lock(&qxl->ssd.lock);
+ qxl_render_update_area_unlocked(qxl);
+ qemu_mutex_unlock(&qxl->ssd.lock);
+}
+
+void qxl_render_update_area_done(PCIQXLDevice *qxl, QXLCookie *cookie)
+{
+ qemu_mutex_lock(&qxl->ssd.lock);
+ trace_qxl_render_update_area_done(cookie);
+ qemu_bh_schedule(qxl->update_area_bh);
+ qxl->render_update_cookie_num--;
+ qemu_mutex_unlock(&qxl->ssd.lock);
+ g_free(cookie);
+}
+
+static void qxl_unpack_chunks(void *dest, size_t size, PCIQXLDevice *qxl,
+ QXLDataChunk *chunk, uint32_t group_id)
+{
+ uint32_t max_chunks = 32;
+ size_t offset = 0;
+ size_t bytes;
+
+ for (;;) {
+ bytes = MIN(size - offset, chunk->data_size);
+ memcpy(dest + offset, chunk->data, bytes);
+ offset += bytes;
+ if (offset == size) {
+ return;
+ }
+ chunk = qxl_phys2virt(qxl, chunk->next_chunk, group_id);
+ if (!chunk) {
+ return;
+ }
+ max_chunks--;
+ if (max_chunks == 0) {
+ return;
+ }
+ }
+}
+
+static QEMUCursor *qxl_cursor(PCIQXLDevice *qxl, QXLCursor *cursor,
+ uint32_t group_id)
+{
+ QEMUCursor *c;
+ uint8_t *and_mask, *xor_mask;
+ size_t size;
+
+ c = cursor_alloc(cursor->header.width, cursor->header.height);
+ c->hot_x = cursor->header.hot_spot_x;
+ c->hot_y = cursor->header.hot_spot_y;
+ switch (cursor->header.type) {
+ case SPICE_CURSOR_TYPE_MONO:
+ /* Assume that the full cursor is available in a single chunk. */
+ size = 2 * cursor_get_mono_bpl(c) * c->height;
+ if (size != cursor->data_size) {
+ fprintf(stderr, "%s: bad monochrome cursor %ux%u with size %u\n",
+ __func__, c->width, c->height, cursor->data_size);
+ goto fail;
+ }
+ and_mask = cursor->chunk.data;
+ xor_mask = and_mask + cursor_get_mono_bpl(c) * c->height;
+ cursor_set_mono(c, 0xffffff, 0x000000, xor_mask, 1, and_mask);
+ if (qxl->debug > 2) {
+ cursor_print_ascii_art(c, "qxl/mono");
+ }
+ break;
+ case SPICE_CURSOR_TYPE_ALPHA:
+ size = sizeof(uint32_t) * cursor->header.width * cursor->header.height;
+ qxl_unpack_chunks(c->data, size, qxl, &cursor->chunk, group_id);
+ if (qxl->debug > 2) {
+ cursor_print_ascii_art(c, "qxl/alpha");
+ }
+ break;
+ default:
+ fprintf(stderr, "%s: not implemented: type %d\n",
+ __func__, cursor->header.type);
+ goto fail;
+ }
+ return c;
+
+fail:
+ cursor_put(c);
+ return NULL;
+}
+
+
+/* called from spice server thread context only */
+int qxl_render_cursor(PCIQXLDevice *qxl, QXLCommandExt *ext)
+{
+ QXLCursorCmd *cmd = qxl_phys2virt(qxl, ext->cmd.data, ext->group_id);
+ QXLCursor *cursor;
+ QEMUCursor *c;
+
+ if (!cmd) {
+ return 1;
+ }
+
+ if (!dpy_cursor_define_supported(qxl->vga.con)) {
+ return 0;
+ }
+
+ if (qxl->debug > 1 && cmd->type != QXL_CURSOR_MOVE) {
+ fprintf(stderr, "%s", __func__);
+ qxl_log_cmd_cursor(qxl, cmd, ext->group_id);
+ fprintf(stderr, "\n");
+ }
+ switch (cmd->type) {
+ case QXL_CURSOR_SET:
+ cursor = qxl_phys2virt(qxl, cmd->u.set.shape, ext->group_id);
+ if (!cursor) {
+ return 1;
+ }
+ c = qxl_cursor(qxl, cursor, ext->group_id);
+ if (c == NULL) {
+ c = cursor_builtin_left_ptr();
+ }
+ qemu_mutex_lock(&qxl->ssd.lock);
+ if (qxl->ssd.cursor) {
+ cursor_put(qxl->ssd.cursor);
+ }
+ qxl->ssd.cursor = c;
+ qxl->ssd.mouse_x = cmd->u.set.position.x;
+ qxl->ssd.mouse_y = cmd->u.set.position.y;
+ qemu_mutex_unlock(&qxl->ssd.lock);
+ qemu_bh_schedule(qxl->ssd.cursor_bh);
+ break;
+ case QXL_CURSOR_MOVE:
+ qemu_mutex_lock(&qxl->ssd.lock);
+ qxl->ssd.mouse_x = cmd->u.position.x;
+ qxl->ssd.mouse_y = cmd->u.position.y;
+ qemu_mutex_unlock(&qxl->ssd.lock);
+ qemu_bh_schedule(qxl->ssd.cursor_bh);
+ break;
+ }
+ return 0;
+}
diff --git a/hw/display/qxl.c b/hw/display/qxl.c
new file mode 100644
index 000000000..29c80b428
--- /dev/null
+++ b/hw/display/qxl.c
@@ -0,0 +1,2524 @@
+/*
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * written by Yaniv Kamay, Izik Eidus, Gerd Hoffmann
+ * maintained by Gerd Hoffmann <kraxel@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/units.h"
+#include <zlib.h>
+
+#include "qapi/error.h"
+#include "qemu/timer.h"
+#include "qemu/queue.h"
+#include "qemu/atomic.h"
+#include "qemu/main-loop.h"
+#include "qemu/module.h"
+#include "hw/qdev-properties.h"
+#include "sysemu/runstate.h"
+#include "migration/vmstate.h"
+#include "trace.h"
+
+#include "qxl.h"
+
+#undef SPICE_RING_CONS_ITEM
+#define SPICE_RING_CONS_ITEM(qxl, r, ret) { \
+ uint32_t cons = (r)->cons & SPICE_RING_INDEX_MASK(r); \
+ if (cons >= ARRAY_SIZE((r)->items)) { \
+ qxl_set_guest_bug(qxl, "SPICE_RING_CONS_ITEM indices mismatch " \
+ "%u >= %zu", cons, ARRAY_SIZE((r)->items)); \
+ ret = NULL; \
+ } else { \
+ ret = &(r)->items[cons].el; \
+ } \
+ }
+
+#undef ALIGN
+#define ALIGN(a, b) (((a) + ((b) - 1)) & ~((b) - 1))
+
+#define PIXEL_SIZE 0.2936875 //1280x1024 is 14.8" x 11.9"
+
+#define QXL_MODE(_x, _y, _b, _o) \
+ { .x_res = _x, \
+ .y_res = _y, \
+ .bits = _b, \
+ .stride = (_x) * (_b) / 8, \
+ .x_mili = PIXEL_SIZE * (_x), \
+ .y_mili = PIXEL_SIZE * (_y), \
+ .orientation = _o, \
+ }
+
+#define QXL_MODE_16_32(x_res, y_res, orientation) \
+ QXL_MODE(x_res, y_res, 16, orientation), \
+ QXL_MODE(x_res, y_res, 32, orientation)
+
+#define QXL_MODE_EX(x_res, y_res) \
+ QXL_MODE_16_32(x_res, y_res, 0), \
+ QXL_MODE_16_32(x_res, y_res, 1)
+
+static QXLMode qxl_modes[] = {
+ QXL_MODE_EX(640, 480),
+ QXL_MODE_EX(800, 480),
+ QXL_MODE_EX(800, 600),
+ QXL_MODE_EX(832, 624),
+ QXL_MODE_EX(960, 640),
+ QXL_MODE_EX(1024, 600),
+ QXL_MODE_EX(1024, 768),
+ QXL_MODE_EX(1152, 864),
+ QXL_MODE_EX(1152, 870),
+ QXL_MODE_EX(1280, 720),
+ QXL_MODE_EX(1280, 760),
+ QXL_MODE_EX(1280, 768),
+ QXL_MODE_EX(1280, 800),
+ QXL_MODE_EX(1280, 960),
+ QXL_MODE_EX(1280, 1024),
+ QXL_MODE_EX(1360, 768),
+ QXL_MODE_EX(1366, 768),
+ QXL_MODE_EX(1400, 1050),
+ QXL_MODE_EX(1440, 900),
+ QXL_MODE_EX(1600, 900),
+ QXL_MODE_EX(1600, 1200),
+ QXL_MODE_EX(1680, 1050),
+ QXL_MODE_EX(1920, 1080),
+ /* these modes need more than 8 MB video memory */
+ QXL_MODE_EX(1920, 1200),
+ QXL_MODE_EX(1920, 1440),
+ QXL_MODE_EX(2000, 2000),
+ QXL_MODE_EX(2048, 1536),
+ QXL_MODE_EX(2048, 2048),
+ QXL_MODE_EX(2560, 1440),
+ QXL_MODE_EX(2560, 1600),
+ /* these modes need more than 16 MB video memory */
+ QXL_MODE_EX(2560, 2048),
+ QXL_MODE_EX(2800, 2100),
+ QXL_MODE_EX(3200, 2400),
+ /* these modes need more than 32 MB video memory */
+ QXL_MODE_EX(3840, 2160), /* 4k mainstream */
+ QXL_MODE_EX(4096, 2160), /* 4k */
+ /* these modes need more than 64 MB video memory */
+ QXL_MODE_EX(7680, 4320), /* 8k mainstream */
+ /* these modes need more than 128 MB video memory */
+ QXL_MODE_EX(8192, 4320), /* 8k */
+};
+
+static void qxl_send_events(PCIQXLDevice *d, uint32_t events);
+static int qxl_destroy_primary(PCIQXLDevice *d, qxl_async_io async);
+static void qxl_reset_memslots(PCIQXLDevice *d);
+static void qxl_reset_surfaces(PCIQXLDevice *d);
+static void qxl_ring_set_dirty(PCIQXLDevice *qxl);
+
+static void qxl_hw_update(void *opaque);
+
+void qxl_set_guest_bug(PCIQXLDevice *qxl, const char *msg, ...)
+{
+ trace_qxl_set_guest_bug(qxl->id);
+ qxl_send_events(qxl, QXL_INTERRUPT_ERROR);
+ qxl->guest_bug = 1;
+ if (qxl->guestdebug) {
+ va_list ap;
+ va_start(ap, msg);
+ fprintf(stderr, "qxl-%d: guest bug: ", qxl->id);
+ vfprintf(stderr, msg, ap);
+ fprintf(stderr, "\n");
+ va_end(ap);
+ }
+}
+
+static void qxl_clear_guest_bug(PCIQXLDevice *qxl)
+{
+ qxl->guest_bug = 0;
+}
+
+void qxl_spice_update_area(PCIQXLDevice *qxl, uint32_t surface_id,
+ struct QXLRect *area, struct QXLRect *dirty_rects,
+ uint32_t num_dirty_rects,
+ uint32_t clear_dirty_region,
+ qxl_async_io async, struct QXLCookie *cookie)
+{
+ trace_qxl_spice_update_area(qxl->id, surface_id, area->left, area->right,
+ area->top, area->bottom);
+ trace_qxl_spice_update_area_rest(qxl->id, num_dirty_rects,
+ clear_dirty_region);
+ if (async == QXL_SYNC) {
+ spice_qxl_update_area(&qxl->ssd.qxl, surface_id, area,
+ dirty_rects, num_dirty_rects, clear_dirty_region);
+ } else {
+ assert(cookie != NULL);
+ spice_qxl_update_area_async(&qxl->ssd.qxl, surface_id, area,
+ clear_dirty_region, (uintptr_t)cookie);
+ }
+}
+
+static void qxl_spice_destroy_surface_wait_complete(PCIQXLDevice *qxl,
+ uint32_t id)
+{
+ trace_qxl_spice_destroy_surface_wait_complete(qxl->id, id);
+ qemu_mutex_lock(&qxl->track_lock);
+ qxl->guest_surfaces.cmds[id] = 0;
+ qxl->guest_surfaces.count--;
+ qemu_mutex_unlock(&qxl->track_lock);
+}
+
+static void qxl_spice_destroy_surface_wait(PCIQXLDevice *qxl, uint32_t id,
+ qxl_async_io async)
+{
+ QXLCookie *cookie;
+
+ trace_qxl_spice_destroy_surface_wait(qxl->id, id, async);
+ if (async) {
+ cookie = qxl_cookie_new(QXL_COOKIE_TYPE_IO,
+ QXL_IO_DESTROY_SURFACE_ASYNC);
+ cookie->u.surface_id = id;
+ spice_qxl_destroy_surface_async(&qxl->ssd.qxl, id, (uintptr_t)cookie);
+ } else {
+ spice_qxl_destroy_surface_wait(&qxl->ssd.qxl, id);
+ qxl_spice_destroy_surface_wait_complete(qxl, id);
+ }
+}
+
+static void qxl_spice_flush_surfaces_async(PCIQXLDevice *qxl)
+{
+ trace_qxl_spice_flush_surfaces_async(qxl->id, qxl->guest_surfaces.count,
+ qxl->num_free_res);
+ spice_qxl_flush_surfaces_async(&qxl->ssd.qxl,
+ (uintptr_t)qxl_cookie_new(QXL_COOKIE_TYPE_IO,
+ QXL_IO_FLUSH_SURFACES_ASYNC));
+}
+
+void qxl_spice_loadvm_commands(PCIQXLDevice *qxl, struct QXLCommandExt *ext,
+ uint32_t count)
+{
+ trace_qxl_spice_loadvm_commands(qxl->id, ext, count);
+ spice_qxl_loadvm_commands(&qxl->ssd.qxl, ext, count);
+}
+
+void qxl_spice_oom(PCIQXLDevice *qxl)
+{
+ trace_qxl_spice_oom(qxl->id);
+ spice_qxl_oom(&qxl->ssd.qxl);
+}
+
+void qxl_spice_reset_memslots(PCIQXLDevice *qxl)
+{
+ trace_qxl_spice_reset_memslots(qxl->id);
+ spice_qxl_reset_memslots(&qxl->ssd.qxl);
+}
+
+static void qxl_spice_destroy_surfaces_complete(PCIQXLDevice *qxl)
+{
+ trace_qxl_spice_destroy_surfaces_complete(qxl->id);
+ qemu_mutex_lock(&qxl->track_lock);
+ memset(qxl->guest_surfaces.cmds, 0,
+ sizeof(qxl->guest_surfaces.cmds[0]) * qxl->ssd.num_surfaces);
+ qxl->guest_surfaces.count = 0;
+ qemu_mutex_unlock(&qxl->track_lock);
+}
+
+static void qxl_spice_destroy_surfaces(PCIQXLDevice *qxl, qxl_async_io async)
+{
+ trace_qxl_spice_destroy_surfaces(qxl->id, async);
+ if (async) {
+ spice_qxl_destroy_surfaces_async(&qxl->ssd.qxl,
+ (uintptr_t)qxl_cookie_new(QXL_COOKIE_TYPE_IO,
+ QXL_IO_DESTROY_ALL_SURFACES_ASYNC));
+ } else {
+ spice_qxl_destroy_surfaces(&qxl->ssd.qxl);
+ qxl_spice_destroy_surfaces_complete(qxl);
+ }
+}
+
+static void qxl_spice_monitors_config_async(PCIQXLDevice *qxl, int replay)
+{
+ QXLMonitorsConfig *cfg;
+
+ trace_qxl_spice_monitors_config(qxl->id);
+ if (replay) {
+ /*
+ * don't use QXL_COOKIE_TYPE_IO:
+ * - we are not running yet (post_load), we will assert
+ * in send_events
+ * - this is not a guest io, but a reply, so async_io isn't set.
+ */
+ spice_qxl_monitors_config_async(&qxl->ssd.qxl,
+ qxl->guest_monitors_config,
+ MEMSLOT_GROUP_GUEST,
+ (uintptr_t)qxl_cookie_new(
+ QXL_COOKIE_TYPE_POST_LOAD_MONITORS_CONFIG,
+ 0));
+ } else {
+/* >= release 0.12.6, < release 0.14.2 */
+#if SPICE_SERVER_VERSION >= 0x000c06 && SPICE_SERVER_VERSION < 0x000e02
+ if (qxl->max_outputs) {
+ spice_qxl_set_max_monitors(&qxl->ssd.qxl, qxl->max_outputs);
+ }
+#endif
+ qxl->guest_monitors_config = qxl->ram->monitors_config;
+ spice_qxl_monitors_config_async(&qxl->ssd.qxl,
+ qxl->ram->monitors_config,
+ MEMSLOT_GROUP_GUEST,
+ (uintptr_t)qxl_cookie_new(QXL_COOKIE_TYPE_IO,
+ QXL_IO_MONITORS_CONFIG_ASYNC));
+ }
+
+ cfg = qxl_phys2virt(qxl, qxl->guest_monitors_config, MEMSLOT_GROUP_GUEST);
+ if (cfg != NULL && cfg->count == 1) {
+ qxl->guest_primary.resized = 1;
+ qxl->guest_head0_width = cfg->heads[0].width;
+ qxl->guest_head0_height = cfg->heads[0].height;
+ } else {
+ qxl->guest_head0_width = 0;
+ qxl->guest_head0_height = 0;
+ }
+}
+
+void qxl_spice_reset_image_cache(PCIQXLDevice *qxl)
+{
+ trace_qxl_spice_reset_image_cache(qxl->id);
+ spice_qxl_reset_image_cache(&qxl->ssd.qxl);
+}
+
+void qxl_spice_reset_cursor(PCIQXLDevice *qxl)
+{
+ trace_qxl_spice_reset_cursor(qxl->id);
+ spice_qxl_reset_cursor(&qxl->ssd.qxl);
+ qemu_mutex_lock(&qxl->track_lock);
+ qxl->guest_cursor = 0;
+ qemu_mutex_unlock(&qxl->track_lock);
+ if (qxl->ssd.cursor) {
+ cursor_put(qxl->ssd.cursor);
+ }
+ qxl->ssd.cursor = cursor_builtin_hidden();
+}
+
+static uint32_t qxl_crc32(const uint8_t *p, unsigned len)
+{
+ /*
+ * zlib xors the seed with 0xffffffff, and xors the result
+ * again with 0xffffffff; Both are not done with linux's crc32,
+ * which we want to be compatible with, so undo that.
+ */
+ return crc32(0xffffffff, p, len) ^ 0xffffffff;
+}
+
+static ram_addr_t qxl_rom_size(void)
+{
+#define QXL_REQUIRED_SZ (sizeof(QXLRom) + sizeof(QXLModes) + sizeof(qxl_modes))
+#define QXL_ROM_SZ 8192
+
+ QEMU_BUILD_BUG_ON(QXL_REQUIRED_SZ > QXL_ROM_SZ);
+ return QEMU_ALIGN_UP(QXL_REQUIRED_SZ, qemu_real_host_page_size);
+}
+
+static void init_qxl_rom(PCIQXLDevice *d)
+{
+ QXLRom *rom = memory_region_get_ram_ptr(&d->rom_bar);
+ QXLModes *modes = (QXLModes *)(rom + 1);
+ uint32_t ram_header_size;
+ uint32_t surface0_area_size;
+ uint32_t num_pages;
+ uint32_t fb;
+ int i, n;
+
+ memset(rom, 0, d->rom_size);
+
+ rom->magic = cpu_to_le32(QXL_ROM_MAGIC);
+ rom->id = cpu_to_le32(d->id);
+ rom->log_level = cpu_to_le32(d->guestdebug);
+ rom->modes_offset = cpu_to_le32(sizeof(QXLRom));
+
+ rom->slot_gen_bits = MEMSLOT_GENERATION_BITS;
+ rom->slot_id_bits = MEMSLOT_SLOT_BITS;
+ rom->slots_start = 1;
+ rom->slots_end = NUM_MEMSLOTS - 1;
+ rom->n_surfaces = cpu_to_le32(d->ssd.num_surfaces);
+
+ for (i = 0, n = 0; i < ARRAY_SIZE(qxl_modes); i++) {
+ fb = qxl_modes[i].y_res * qxl_modes[i].stride;
+ if (fb > d->vgamem_size) {
+ continue;
+ }
+ modes->modes[n].id = cpu_to_le32(i);
+ modes->modes[n].x_res = cpu_to_le32(qxl_modes[i].x_res);
+ modes->modes[n].y_res = cpu_to_le32(qxl_modes[i].y_res);
+ modes->modes[n].bits = cpu_to_le32(qxl_modes[i].bits);
+ modes->modes[n].stride = cpu_to_le32(qxl_modes[i].stride);
+ modes->modes[n].x_mili = cpu_to_le32(qxl_modes[i].x_mili);
+ modes->modes[n].y_mili = cpu_to_le32(qxl_modes[i].y_mili);
+ modes->modes[n].orientation = cpu_to_le32(qxl_modes[i].orientation);
+ n++;
+ }
+ modes->n_modes = cpu_to_le32(n);
+
+ ram_header_size = ALIGN(sizeof(QXLRam), 4096);
+ surface0_area_size = ALIGN(d->vgamem_size, 4096);
+ num_pages = d->vga.vram_size;
+ num_pages -= ram_header_size;
+ num_pages -= surface0_area_size;
+ num_pages = num_pages / QXL_PAGE_SIZE;
+
+ assert(ram_header_size + surface0_area_size <= d->vga.vram_size);
+
+ rom->draw_area_offset = cpu_to_le32(0);
+ rom->surface0_area_size = cpu_to_le32(surface0_area_size);
+ rom->pages_offset = cpu_to_le32(surface0_area_size);
+ rom->num_pages = cpu_to_le32(num_pages);
+ rom->ram_header_offset = cpu_to_le32(d->vga.vram_size - ram_header_size);
+
+ if (d->xres && d->yres) {
+ /* needs linux kernel 4.12+ to work */
+ rom->client_monitors_config.count = 1;
+ rom->client_monitors_config.heads[0].left = 0;
+ rom->client_monitors_config.heads[0].top = 0;
+ rom->client_monitors_config.heads[0].right = cpu_to_le32(d->xres);
+ rom->client_monitors_config.heads[0].bottom = cpu_to_le32(d->yres);
+ rom->client_monitors_config_crc = qxl_crc32(
+ (const uint8_t *)&rom->client_monitors_config,
+ sizeof(rom->client_monitors_config));
+ }
+
+ d->shadow_rom = *rom;
+ d->rom = rom;
+ d->modes = modes;
+}
+
+static void init_qxl_ram(PCIQXLDevice *d)
+{
+ uint8_t *buf;
+ uint32_t prod;
+ QXLReleaseRing *ring;
+
+ buf = d->vga.vram_ptr;
+ d->ram = (QXLRam *)(buf + le32_to_cpu(d->shadow_rom.ram_header_offset));
+ d->ram->magic = cpu_to_le32(QXL_RAM_MAGIC);
+ d->ram->int_pending = cpu_to_le32(0);
+ d->ram->int_mask = cpu_to_le32(0);
+ d->ram->update_surface = 0;
+ d->ram->monitors_config = 0;
+ SPICE_RING_INIT(&d->ram->cmd_ring);
+ SPICE_RING_INIT(&d->ram->cursor_ring);
+ SPICE_RING_INIT(&d->ram->release_ring);
+
+ ring = &d->ram->release_ring;
+ prod = ring->prod & SPICE_RING_INDEX_MASK(ring);
+ assert(prod < ARRAY_SIZE(ring->items));
+ ring->items[prod].el = 0;
+
+ qxl_ring_set_dirty(d);
+}
+
+/* can be called from spice server thread context */
+static void qxl_set_dirty(MemoryRegion *mr, ram_addr_t addr, ram_addr_t end)
+{
+ memory_region_set_dirty(mr, addr, end - addr);
+}
+
+static void qxl_rom_set_dirty(PCIQXLDevice *qxl)
+{
+ qxl_set_dirty(&qxl->rom_bar, 0, qxl->rom_size);
+}
+
+/* called from spice server thread context only */
+static void qxl_ram_set_dirty(PCIQXLDevice *qxl, void *ptr)
+{
+ void *base = qxl->vga.vram_ptr;
+ intptr_t offset;
+
+ offset = ptr - base;
+ assert(offset < qxl->vga.vram_size);
+ qxl_set_dirty(&qxl->vga.vram, offset, offset + 3);
+}
+
+/* can be called from spice server thread context */
+static void qxl_ring_set_dirty(PCIQXLDevice *qxl)
+{
+ ram_addr_t addr = qxl->shadow_rom.ram_header_offset;
+ ram_addr_t end = qxl->vga.vram_size;
+ qxl_set_dirty(&qxl->vga.vram, addr, end);
+}
+
+/*
+ * keep track of some command state, for savevm/loadvm.
+ * called from spice server thread context only
+ */
+static int qxl_track_command(PCIQXLDevice *qxl, struct QXLCommandExt *ext)
+{
+ switch (le32_to_cpu(ext->cmd.type)) {
+ case QXL_CMD_SURFACE:
+ {
+ QXLSurfaceCmd *cmd = qxl_phys2virt(qxl, ext->cmd.data, ext->group_id);
+
+ if (!cmd) {
+ return 1;
+ }
+ uint32_t id = le32_to_cpu(cmd->surface_id);
+
+ if (id >= qxl->ssd.num_surfaces) {
+ qxl_set_guest_bug(qxl, "QXL_CMD_SURFACE id %d >= %d", id,
+ qxl->ssd.num_surfaces);
+ return 1;
+ }
+ if (cmd->type == QXL_SURFACE_CMD_CREATE &&
+ (cmd->u.surface_create.stride & 0x03) != 0) {
+ qxl_set_guest_bug(qxl, "QXL_CMD_SURFACE stride = %d %% 4 != 0\n",
+ cmd->u.surface_create.stride);
+ return 1;
+ }
+ WITH_QEMU_LOCK_GUARD(&qxl->track_lock) {
+ if (cmd->type == QXL_SURFACE_CMD_CREATE) {
+ qxl->guest_surfaces.cmds[id] = ext->cmd.data;
+ qxl->guest_surfaces.count++;
+ if (qxl->guest_surfaces.max < qxl->guest_surfaces.count) {
+ qxl->guest_surfaces.max = qxl->guest_surfaces.count;
+ }
+ }
+ if (cmd->type == QXL_SURFACE_CMD_DESTROY) {
+ qxl->guest_surfaces.cmds[id] = 0;
+ qxl->guest_surfaces.count--;
+ }
+ }
+ break;
+ }
+ case QXL_CMD_CURSOR:
+ {
+ QXLCursorCmd *cmd = qxl_phys2virt(qxl, ext->cmd.data, ext->group_id);
+
+ if (!cmd) {
+ return 1;
+ }
+ if (cmd->type == QXL_CURSOR_SET) {
+ qemu_mutex_lock(&qxl->track_lock);
+ qxl->guest_cursor = ext->cmd.data;
+ qemu_mutex_unlock(&qxl->track_lock);
+ }
+ if (cmd->type == QXL_CURSOR_HIDE) {
+ qemu_mutex_lock(&qxl->track_lock);
+ qxl->guest_cursor = 0;
+ qemu_mutex_unlock(&qxl->track_lock);
+ }
+ break;
+ }
+ }
+ return 0;
+}
+
+/* spice display interface callbacks */
+
+static void interface_attach_worker(QXLInstance *sin, QXLWorker *qxl_worker)
+{
+ PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl);
+
+ trace_qxl_interface_attach_worker(qxl->id);
+}
+
+static void interface_set_compression_level(QXLInstance *sin, int level)
+{
+ PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl);
+
+ trace_qxl_interface_set_compression_level(qxl->id, level);
+ qxl->shadow_rom.compression_level = cpu_to_le32(level);
+ qxl->rom->compression_level = cpu_to_le32(level);
+ qxl_rom_set_dirty(qxl);
+}
+
+#if SPICE_NEEDS_SET_MM_TIME
+static void interface_set_mm_time(QXLInstance *sin, uint32_t mm_time)
+{
+ PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl);
+
+ if (!qemu_spice_display_is_running(&qxl->ssd)) {
+ return;
+ }
+
+ trace_qxl_interface_set_mm_time(qxl->id, mm_time);
+ qxl->shadow_rom.mm_clock = cpu_to_le32(mm_time);
+ qxl->rom->mm_clock = cpu_to_le32(mm_time);
+ qxl_rom_set_dirty(qxl);
+}
+#endif
+
+static void interface_get_init_info(QXLInstance *sin, QXLDevInitInfo *info)
+{
+ PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl);
+
+ trace_qxl_interface_get_init_info(qxl->id);
+ info->memslot_gen_bits = MEMSLOT_GENERATION_BITS;
+ info->memslot_id_bits = MEMSLOT_SLOT_BITS;
+ info->num_memslots = NUM_MEMSLOTS;
+ info->num_memslots_groups = NUM_MEMSLOTS_GROUPS;
+ info->internal_groupslot_id = 0;
+ info->qxl_ram_size =
+ le32_to_cpu(qxl->shadow_rom.num_pages) << QXL_PAGE_BITS;
+ info->n_surfaces = qxl->ssd.num_surfaces;
+}
+
+static const char *qxl_mode_to_string(int mode)
+{
+ switch (mode) {
+ case QXL_MODE_COMPAT:
+ return "compat";
+ case QXL_MODE_NATIVE:
+ return "native";
+ case QXL_MODE_UNDEFINED:
+ return "undefined";
+ case QXL_MODE_VGA:
+ return "vga";
+ }
+ return "INVALID";
+}
+
+static const char *io_port_to_string(uint32_t io_port)
+{
+ if (io_port >= QXL_IO_RANGE_SIZE) {
+ return "out of range";
+ }
+ static const char *io_port_to_string[QXL_IO_RANGE_SIZE + 1] = {
+ [QXL_IO_NOTIFY_CMD] = "QXL_IO_NOTIFY_CMD",
+ [QXL_IO_NOTIFY_CURSOR] = "QXL_IO_NOTIFY_CURSOR",
+ [QXL_IO_UPDATE_AREA] = "QXL_IO_UPDATE_AREA",
+ [QXL_IO_UPDATE_IRQ] = "QXL_IO_UPDATE_IRQ",
+ [QXL_IO_NOTIFY_OOM] = "QXL_IO_NOTIFY_OOM",
+ [QXL_IO_RESET] = "QXL_IO_RESET",
+ [QXL_IO_SET_MODE] = "QXL_IO_SET_MODE",
+ [QXL_IO_LOG] = "QXL_IO_LOG",
+ [QXL_IO_MEMSLOT_ADD] = "QXL_IO_MEMSLOT_ADD",
+ [QXL_IO_MEMSLOT_DEL] = "QXL_IO_MEMSLOT_DEL",
+ [QXL_IO_DETACH_PRIMARY] = "QXL_IO_DETACH_PRIMARY",
+ [QXL_IO_ATTACH_PRIMARY] = "QXL_IO_ATTACH_PRIMARY",
+ [QXL_IO_CREATE_PRIMARY] = "QXL_IO_CREATE_PRIMARY",
+ [QXL_IO_DESTROY_PRIMARY] = "QXL_IO_DESTROY_PRIMARY",
+ [QXL_IO_DESTROY_SURFACE_WAIT] = "QXL_IO_DESTROY_SURFACE_WAIT",
+ [QXL_IO_DESTROY_ALL_SURFACES] = "QXL_IO_DESTROY_ALL_SURFACES",
+ [QXL_IO_UPDATE_AREA_ASYNC] = "QXL_IO_UPDATE_AREA_ASYNC",
+ [QXL_IO_MEMSLOT_ADD_ASYNC] = "QXL_IO_MEMSLOT_ADD_ASYNC",
+ [QXL_IO_CREATE_PRIMARY_ASYNC] = "QXL_IO_CREATE_PRIMARY_ASYNC",
+ [QXL_IO_DESTROY_PRIMARY_ASYNC] = "QXL_IO_DESTROY_PRIMARY_ASYNC",
+ [QXL_IO_DESTROY_SURFACE_ASYNC] = "QXL_IO_DESTROY_SURFACE_ASYNC",
+ [QXL_IO_DESTROY_ALL_SURFACES_ASYNC]
+ = "QXL_IO_DESTROY_ALL_SURFACES_ASYNC",
+ [QXL_IO_FLUSH_SURFACES_ASYNC] = "QXL_IO_FLUSH_SURFACES_ASYNC",
+ [QXL_IO_FLUSH_RELEASE] = "QXL_IO_FLUSH_RELEASE",
+ [QXL_IO_MONITORS_CONFIG_ASYNC] = "QXL_IO_MONITORS_CONFIG_ASYNC",
+ };
+ return io_port_to_string[io_port];
+}
+
+/* called from spice server thread context only */
+static int interface_get_command(QXLInstance *sin, struct QXLCommandExt *ext)
+{
+ PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl);
+ SimpleSpiceUpdate *update;
+ QXLCommandRing *ring;
+ QXLCommand *cmd;
+ int notify, ret;
+
+ trace_qxl_ring_command_check(qxl->id, qxl_mode_to_string(qxl->mode));
+
+ switch (qxl->mode) {
+ case QXL_MODE_VGA:
+ ret = false;
+ qemu_mutex_lock(&qxl->ssd.lock);
+ update = QTAILQ_FIRST(&qxl->ssd.updates);
+ if (update != NULL) {
+ QTAILQ_REMOVE(&qxl->ssd.updates, update, next);
+ *ext = update->ext;
+ ret = true;
+ }
+ qemu_mutex_unlock(&qxl->ssd.lock);
+ if (ret) {
+ trace_qxl_ring_command_get(qxl->id, qxl_mode_to_string(qxl->mode));
+ qxl_log_command(qxl, "vga", ext);
+ }
+ return ret;
+ case QXL_MODE_COMPAT:
+ case QXL_MODE_NATIVE:
+ case QXL_MODE_UNDEFINED:
+ ring = &qxl->ram->cmd_ring;
+ if (qxl->guest_bug || SPICE_RING_IS_EMPTY(ring)) {
+ return false;
+ }
+ SPICE_RING_CONS_ITEM(qxl, ring, cmd);
+ if (!cmd) {
+ return false;
+ }
+ ext->cmd = *cmd;
+ ext->group_id = MEMSLOT_GROUP_GUEST;
+ ext->flags = qxl->cmdflags;
+ SPICE_RING_POP(ring, notify);
+ qxl_ring_set_dirty(qxl);
+ if (notify) {
+ qxl_send_events(qxl, QXL_INTERRUPT_DISPLAY);
+ }
+ qxl->guest_primary.commands++;
+ qxl_track_command(qxl, ext);
+ qxl_log_command(qxl, "cmd", ext);
+ trace_qxl_ring_command_get(qxl->id, qxl_mode_to_string(qxl->mode));
+ return true;
+ default:
+ return false;
+ }
+}
+
+/* called from spice server thread context only */
+static int interface_req_cmd_notification(QXLInstance *sin)
+{
+ PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl);
+ int wait = 1;
+
+ trace_qxl_ring_command_req_notification(qxl->id);
+ switch (qxl->mode) {
+ case QXL_MODE_COMPAT:
+ case QXL_MODE_NATIVE:
+ case QXL_MODE_UNDEFINED:
+ SPICE_RING_CONS_WAIT(&qxl->ram->cmd_ring, wait);
+ qxl_ring_set_dirty(qxl);
+ break;
+ default:
+ /* nothing */
+ break;
+ }
+ return wait;
+}
+
+/* called from spice server thread context only */
+static inline void qxl_push_free_res(PCIQXLDevice *d, int flush)
+{
+ QXLReleaseRing *ring = &d->ram->release_ring;
+ uint32_t prod;
+ int notify;
+
+#define QXL_FREE_BUNCH_SIZE 32
+
+ if (ring->prod - ring->cons + 1 == ring->num_items) {
+ /* ring full -- can't push */
+ return;
+ }
+ if (!flush && d->oom_running) {
+ /* collect everything from oom handler before pushing */
+ return;
+ }
+ if (!flush && d->num_free_res < QXL_FREE_BUNCH_SIZE) {
+ /* collect a bit more before pushing */
+ return;
+ }
+
+ SPICE_RING_PUSH(ring, notify);
+ trace_qxl_ring_res_push(d->id, qxl_mode_to_string(d->mode),
+ d->guest_surfaces.count, d->num_free_res,
+ d->last_release, notify ? "yes" : "no");
+ trace_qxl_ring_res_push_rest(d->id, ring->prod - ring->cons,
+ ring->num_items, ring->prod, ring->cons);
+ if (notify) {
+ qxl_send_events(d, QXL_INTERRUPT_DISPLAY);
+ }
+
+ ring = &d->ram->release_ring;
+ prod = ring->prod & SPICE_RING_INDEX_MASK(ring);
+ if (prod >= ARRAY_SIZE(ring->items)) {
+ qxl_set_guest_bug(d, "SPICE_RING_PROD_ITEM indices mismatch "
+ "%u >= %zu", prod, ARRAY_SIZE(ring->items));
+ return;
+ }
+ ring->items[prod].el = 0;
+ d->num_free_res = 0;
+ d->last_release = NULL;
+ qxl_ring_set_dirty(d);
+}
+
+/* called from spice server thread context only */
+static void interface_release_resource(QXLInstance *sin,
+ QXLReleaseInfoExt ext)
+{
+ PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl);
+ QXLReleaseRing *ring;
+ uint32_t prod;
+ uint64_t id;
+
+ if (!ext.info) {
+ return;
+ }
+ if (ext.group_id == MEMSLOT_GROUP_HOST) {
+ /* host group -> vga mode update request */
+ QXLCommandExt *cmdext = (void *)(intptr_t)(ext.info->id);
+ SimpleSpiceUpdate *update;
+ g_assert(cmdext->cmd.type == QXL_CMD_DRAW);
+ update = container_of(cmdext, SimpleSpiceUpdate, ext);
+ qemu_spice_destroy_update(&qxl->ssd, update);
+ return;
+ }
+
+ /*
+ * ext->info points into guest-visible memory
+ * pci bar 0, $command.release_info
+ */
+ ring = &qxl->ram->release_ring;
+ prod = ring->prod & SPICE_RING_INDEX_MASK(ring);
+ if (prod >= ARRAY_SIZE(ring->items)) {
+ qxl_set_guest_bug(qxl, "SPICE_RING_PROD_ITEM indices mismatch "
+ "%u >= %zu", prod, ARRAY_SIZE(ring->items));
+ return;
+ }
+ if (ring->items[prod].el == 0) {
+ /* stick head into the ring */
+ id = ext.info->id;
+ ext.info->next = 0;
+ qxl_ram_set_dirty(qxl, &ext.info->next);
+ ring->items[prod].el = id;
+ qxl_ring_set_dirty(qxl);
+ } else {
+ /* append item to the list */
+ qxl->last_release->next = ext.info->id;
+ qxl_ram_set_dirty(qxl, &qxl->last_release->next);
+ ext.info->next = 0;
+ qxl_ram_set_dirty(qxl, &ext.info->next);
+ }
+ qxl->last_release = ext.info;
+ qxl->num_free_res++;
+ trace_qxl_ring_res_put(qxl->id, qxl->num_free_res);
+ qxl_push_free_res(qxl, 0);
+}
+
+/* called from spice server thread context only */
+static int interface_get_cursor_command(QXLInstance *sin, struct QXLCommandExt *ext)
+{
+ PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl);
+ QXLCursorRing *ring;
+ QXLCommand *cmd;
+ int notify;
+
+ trace_qxl_ring_cursor_check(qxl->id, qxl_mode_to_string(qxl->mode));
+
+ switch (qxl->mode) {
+ case QXL_MODE_COMPAT:
+ case QXL_MODE_NATIVE:
+ case QXL_MODE_UNDEFINED:
+ ring = &qxl->ram->cursor_ring;
+ if (SPICE_RING_IS_EMPTY(ring)) {
+ return false;
+ }
+ SPICE_RING_CONS_ITEM(qxl, ring, cmd);
+ if (!cmd) {
+ return false;
+ }
+ ext->cmd = *cmd;
+ ext->group_id = MEMSLOT_GROUP_GUEST;
+ ext->flags = qxl->cmdflags;
+ SPICE_RING_POP(ring, notify);
+ qxl_ring_set_dirty(qxl);
+ if (notify) {
+ qxl_send_events(qxl, QXL_INTERRUPT_CURSOR);
+ }
+ qxl->guest_primary.commands++;
+ qxl_track_command(qxl, ext);
+ qxl_log_command(qxl, "csr", ext);
+ if (qxl->have_vga) {
+ qxl_render_cursor(qxl, ext);
+ }
+ trace_qxl_ring_cursor_get(qxl->id, qxl_mode_to_string(qxl->mode));
+ return true;
+ default:
+ return false;
+ }
+}
+
+/* called from spice server thread context only */
+static int interface_req_cursor_notification(QXLInstance *sin)
+{
+ PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl);
+ int wait = 1;
+
+ trace_qxl_ring_cursor_req_notification(qxl->id);
+ switch (qxl->mode) {
+ case QXL_MODE_COMPAT:
+ case QXL_MODE_NATIVE:
+ case QXL_MODE_UNDEFINED:
+ SPICE_RING_CONS_WAIT(&qxl->ram->cursor_ring, wait);
+ qxl_ring_set_dirty(qxl);
+ break;
+ default:
+ /* nothing */
+ break;
+ }
+ return wait;
+}
+
+/* called from spice server thread context */
+static void interface_notify_update(QXLInstance *sin, uint32_t update_id)
+{
+ /*
+ * Called by spice-server as a result of a QXL_CMD_UPDATE which is not in
+ * use by xf86-video-qxl and is defined out in the qxl windows driver.
+ * Probably was at some earlier version that is prior to git start (2009),
+ * and is still guest trigerrable.
+ */
+ fprintf(stderr, "%s: deprecated\n", __func__);
+}
+
+/* called from spice server thread context only */
+static int interface_flush_resources(QXLInstance *sin)
+{
+ PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl);
+ int ret;
+
+ ret = qxl->num_free_res;
+ if (ret) {
+ qxl_push_free_res(qxl, 1);
+ }
+ return ret;
+}
+
+static void qxl_create_guest_primary_complete(PCIQXLDevice *d);
+
+/* called from spice server thread context only */
+static void interface_async_complete_io(PCIQXLDevice *qxl, QXLCookie *cookie)
+{
+ uint32_t current_async;
+
+ qemu_mutex_lock(&qxl->async_lock);
+ current_async = qxl->current_async;
+ qxl->current_async = QXL_UNDEFINED_IO;
+ qemu_mutex_unlock(&qxl->async_lock);
+
+ trace_qxl_interface_async_complete_io(qxl->id, current_async, cookie);
+ if (!cookie) {
+ fprintf(stderr, "qxl: %s: error, cookie is NULL\n", __func__);
+ return;
+ }
+ if (cookie && current_async != cookie->io) {
+ fprintf(stderr,
+ "qxl: %s: error: current_async = %d != %"
+ PRId64 " = cookie->io\n", __func__, current_async, cookie->io);
+ }
+ switch (current_async) {
+ case QXL_IO_MEMSLOT_ADD_ASYNC:
+ case QXL_IO_DESTROY_PRIMARY_ASYNC:
+ case QXL_IO_UPDATE_AREA_ASYNC:
+ case QXL_IO_FLUSH_SURFACES_ASYNC:
+ case QXL_IO_MONITORS_CONFIG_ASYNC:
+ break;
+ case QXL_IO_CREATE_PRIMARY_ASYNC:
+ qxl_create_guest_primary_complete(qxl);
+ break;
+ case QXL_IO_DESTROY_ALL_SURFACES_ASYNC:
+ qxl_spice_destroy_surfaces_complete(qxl);
+ break;
+ case QXL_IO_DESTROY_SURFACE_ASYNC:
+ qxl_spice_destroy_surface_wait_complete(qxl, cookie->u.surface_id);
+ break;
+ default:
+ fprintf(stderr, "qxl: %s: unexpected current_async %u\n", __func__,
+ current_async);
+ }
+ qxl_send_events(qxl, QXL_INTERRUPT_IO_CMD);
+}
+
+/* called from spice server thread context only */
+static void interface_update_area_complete(QXLInstance *sin,
+ uint32_t surface_id,
+ QXLRect *dirty, uint32_t num_updated_rects)
+{
+ PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl);
+ int i;
+ int qxl_i;
+
+ QEMU_LOCK_GUARD(&qxl->ssd.lock);
+ if (surface_id != 0 || !num_updated_rects ||
+ !qxl->render_update_cookie_num) {
+ return;
+ }
+ trace_qxl_interface_update_area_complete(qxl->id, surface_id, dirty->left,
+ dirty->right, dirty->top, dirty->bottom);
+ trace_qxl_interface_update_area_complete_rest(qxl->id, num_updated_rects);
+ if (qxl->num_dirty_rects + num_updated_rects > QXL_NUM_DIRTY_RECTS) {
+ /*
+ * overflow - treat this as a full update. Not expected to be common.
+ */
+ trace_qxl_interface_update_area_complete_overflow(qxl->id,
+ QXL_NUM_DIRTY_RECTS);
+ qxl->guest_primary.resized = 1;
+ }
+ if (qxl->guest_primary.resized) {
+ /*
+ * Don't bother copying or scheduling the bh since we will flip
+ * the whole area anyway on completion of the update_area async call
+ */
+ return;
+ }
+ qxl_i = qxl->num_dirty_rects;
+ for (i = 0; i < num_updated_rects; i++) {
+ qxl->dirty[qxl_i++] = dirty[i];
+ }
+ qxl->num_dirty_rects += num_updated_rects;
+ trace_qxl_interface_update_area_complete_schedule_bh(qxl->id,
+ qxl->num_dirty_rects);
+ qemu_bh_schedule(qxl->update_area_bh);
+}
+
+/* called from spice server thread context only */
+static void interface_async_complete(QXLInstance *sin, uint64_t cookie_token)
+{
+ PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl);
+ QXLCookie *cookie = (QXLCookie *)(uintptr_t)cookie_token;
+
+ switch (cookie->type) {
+ case QXL_COOKIE_TYPE_IO:
+ interface_async_complete_io(qxl, cookie);
+ g_free(cookie);
+ break;
+ case QXL_COOKIE_TYPE_RENDER_UPDATE_AREA:
+ qxl_render_update_area_done(qxl, cookie);
+ break;
+ case QXL_COOKIE_TYPE_POST_LOAD_MONITORS_CONFIG:
+ break;
+ default:
+ fprintf(stderr, "qxl: %s: unexpected cookie type %d\n",
+ __func__, cookie->type);
+ g_free(cookie);
+ }
+}
+
+/* called from spice server thread context only */
+static void interface_set_client_capabilities(QXLInstance *sin,
+ uint8_t client_present,
+ uint8_t caps[58])
+{
+ PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl);
+
+ if (qxl->revision < 4) {
+ trace_qxl_set_client_capabilities_unsupported_by_revision(qxl->id,
+ qxl->revision);
+ return;
+ }
+
+ if (runstate_check(RUN_STATE_INMIGRATE) ||
+ runstate_check(RUN_STATE_POSTMIGRATE)) {
+ return;
+ }
+
+ qxl->shadow_rom.client_present = client_present;
+ memcpy(qxl->shadow_rom.client_capabilities, caps,
+ sizeof(qxl->shadow_rom.client_capabilities));
+ qxl->rom->client_present = client_present;
+ memcpy(qxl->rom->client_capabilities, caps,
+ sizeof(qxl->rom->client_capabilities));
+ qxl_rom_set_dirty(qxl);
+
+ qxl_send_events(qxl, QXL_INTERRUPT_CLIENT);
+}
+
+static bool qxl_rom_monitors_config_changed(QXLRom *rom,
+ VDAgentMonitorsConfig *monitors_config,
+ unsigned int max_outputs)
+{
+ int i;
+ unsigned int monitors_count;
+
+ monitors_count = MIN(monitors_config->num_of_monitors, max_outputs);
+
+ if (rom->client_monitors_config.count != monitors_count) {
+ return true;
+ }
+
+ for (i = 0 ; i < rom->client_monitors_config.count ; ++i) {
+ VDAgentMonConfig *monitor = &monitors_config->monitors[i];
+ QXLURect *rect = &rom->client_monitors_config.heads[i];
+ /* monitor->depth ignored */
+ if ((rect->left != monitor->x) ||
+ (rect->top != monitor->y) ||
+ (rect->right != monitor->x + monitor->width) ||
+ (rect->bottom != monitor->y + monitor->height)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/* called from main context only */
+static int interface_client_monitors_config(QXLInstance *sin,
+ VDAgentMonitorsConfig *monitors_config)
+{
+ PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl);
+ QXLRom *rom = memory_region_get_ram_ptr(&qxl->rom_bar);
+ int i;
+ unsigned max_outputs = ARRAY_SIZE(rom->client_monitors_config.heads);
+ bool config_changed = false;
+
+ if (qxl->revision < 4) {
+ trace_qxl_client_monitors_config_unsupported_by_device(qxl->id,
+ qxl->revision);
+ return 0;
+ }
+ /*
+ * Older windows drivers set int_mask to 0 when their ISR is called,
+ * then later set it to ~0. So it doesn't relate to the actual interrupts
+ * handled. However, they are old, so clearly they don't support this
+ * interrupt
+ */
+ if (qxl->ram->int_mask == 0 || qxl->ram->int_mask == ~0 ||
+ !(qxl->ram->int_mask & QXL_INTERRUPT_CLIENT_MONITORS_CONFIG)) {
+ trace_qxl_client_monitors_config_unsupported_by_guest(qxl->id,
+ qxl->ram->int_mask,
+ monitors_config);
+ return 0;
+ }
+ if (!monitors_config) {
+ return 1;
+ }
+
+#if SPICE_SERVER_VERSION >= 0x000c06 /* release 0.12.6 */
+ /* limit number of outputs based on setting limit */
+ if (qxl->max_outputs && qxl->max_outputs <= max_outputs) {
+ max_outputs = qxl->max_outputs;
+ }
+#endif
+
+ config_changed = qxl_rom_monitors_config_changed(rom,
+ monitors_config,
+ max_outputs);
+
+ memset(&rom->client_monitors_config, 0,
+ sizeof(rom->client_monitors_config));
+ rom->client_monitors_config.count = monitors_config->num_of_monitors;
+ /* monitors_config->flags ignored */
+ if (rom->client_monitors_config.count >= max_outputs) {
+ trace_qxl_client_monitors_config_capped(qxl->id,
+ monitors_config->num_of_monitors,
+ max_outputs);
+ rom->client_monitors_config.count = max_outputs;
+ }
+ for (i = 0 ; i < rom->client_monitors_config.count ; ++i) {
+ VDAgentMonConfig *monitor = &monitors_config->monitors[i];
+ QXLURect *rect = &rom->client_monitors_config.heads[i];
+ /* monitor->depth ignored */
+ rect->left = monitor->x;
+ rect->top = monitor->y;
+ rect->right = monitor->x + monitor->width;
+ rect->bottom = monitor->y + monitor->height;
+ }
+ rom->client_monitors_config_crc = qxl_crc32(
+ (const uint8_t *)&rom->client_monitors_config,
+ sizeof(rom->client_monitors_config));
+ trace_qxl_client_monitors_config_crc(qxl->id,
+ sizeof(rom->client_monitors_config),
+ rom->client_monitors_config_crc);
+
+ trace_qxl_interrupt_client_monitors_config(qxl->id,
+ rom->client_monitors_config.count,
+ rom->client_monitors_config.heads);
+ if (config_changed) {
+ qxl_send_events(qxl, QXL_INTERRUPT_CLIENT_MONITORS_CONFIG);
+ }
+ return 1;
+}
+
+static const QXLInterface qxl_interface = {
+ .base.type = SPICE_INTERFACE_QXL,
+ .base.description = "qxl gpu",
+ .base.major_version = SPICE_INTERFACE_QXL_MAJOR,
+ .base.minor_version = SPICE_INTERFACE_QXL_MINOR,
+
+ .attache_worker = interface_attach_worker,
+ .set_compression_level = interface_set_compression_level,
+#if SPICE_NEEDS_SET_MM_TIME
+ .set_mm_time = interface_set_mm_time,
+#endif
+ .get_init_info = interface_get_init_info,
+
+ /* the callbacks below are called from spice server thread context */
+ .get_command = interface_get_command,
+ .req_cmd_notification = interface_req_cmd_notification,
+ .release_resource = interface_release_resource,
+ .get_cursor_command = interface_get_cursor_command,
+ .req_cursor_notification = interface_req_cursor_notification,
+ .notify_update = interface_notify_update,
+ .flush_resources = interface_flush_resources,
+ .async_complete = interface_async_complete,
+ .update_area_complete = interface_update_area_complete,
+ .set_client_capabilities = interface_set_client_capabilities,
+ .client_monitors_config = interface_client_monitors_config,
+};
+
+static const GraphicHwOps qxl_ops = {
+ .gfx_update = qxl_hw_update,
+ .gfx_update_async = true,
+};
+
+static void qxl_enter_vga_mode(PCIQXLDevice *d)
+{
+ if (d->mode == QXL_MODE_VGA) {
+ return;
+ }
+ trace_qxl_enter_vga_mode(d->id);
+ spice_qxl_driver_unload(&d->ssd.qxl);
+ graphic_console_set_hwops(d->ssd.dcl.con, d->vga.hw_ops, &d->vga);
+ update_displaychangelistener(&d->ssd.dcl, GUI_REFRESH_INTERVAL_DEFAULT);
+ qemu_spice_create_host_primary(&d->ssd);
+ d->mode = QXL_MODE_VGA;
+ qemu_spice_display_switch(&d->ssd, d->ssd.ds);
+ vga_dirty_log_start(&d->vga);
+ graphic_hw_update(d->vga.con);
+}
+
+static void qxl_exit_vga_mode(PCIQXLDevice *d)
+{
+ if (d->mode != QXL_MODE_VGA) {
+ return;
+ }
+ trace_qxl_exit_vga_mode(d->id);
+ graphic_console_set_hwops(d->ssd.dcl.con, &qxl_ops, d);
+ update_displaychangelistener(&d->ssd.dcl, GUI_REFRESH_INTERVAL_IDLE);
+ vga_dirty_log_stop(&d->vga);
+ qxl_destroy_primary(d, QXL_SYNC);
+}
+
+static void qxl_update_irq(PCIQXLDevice *d)
+{
+ uint32_t pending = le32_to_cpu(d->ram->int_pending);
+ uint32_t mask = le32_to_cpu(d->ram->int_mask);
+ int level = !!(pending & mask);
+ pci_set_irq(&d->pci, level);
+ qxl_ring_set_dirty(d);
+}
+
+static void qxl_check_state(PCIQXLDevice *d)
+{
+ QXLRam *ram = d->ram;
+ int spice_display_running = qemu_spice_display_is_running(&d->ssd);
+
+ assert(!spice_display_running || SPICE_RING_IS_EMPTY(&ram->cmd_ring));
+ assert(!spice_display_running || SPICE_RING_IS_EMPTY(&ram->cursor_ring));
+}
+
+static void qxl_reset_state(PCIQXLDevice *d)
+{
+ QXLRom *rom = d->rom;
+
+ qxl_check_state(d);
+ d->shadow_rom.update_id = cpu_to_le32(0);
+ *rom = d->shadow_rom;
+ qxl_rom_set_dirty(d);
+ init_qxl_ram(d);
+ d->num_free_res = 0;
+ d->last_release = NULL;
+ memset(&d->ssd.dirty, 0, sizeof(d->ssd.dirty));
+ qxl_update_irq(d);
+}
+
+static void qxl_soft_reset(PCIQXLDevice *d)
+{
+ trace_qxl_soft_reset(d->id);
+ qxl_check_state(d);
+ qxl_clear_guest_bug(d);
+ qemu_mutex_lock(&d->async_lock);
+ d->current_async = QXL_UNDEFINED_IO;
+ qemu_mutex_unlock(&d->async_lock);
+
+ if (d->have_vga) {
+ qxl_enter_vga_mode(d);
+ } else {
+ d->mode = QXL_MODE_UNDEFINED;
+ }
+}
+
+static void qxl_hard_reset(PCIQXLDevice *d, int loadvm)
+{
+ bool startstop = qemu_spice_display_is_running(&d->ssd);
+
+ trace_qxl_hard_reset(d->id, loadvm);
+
+ if (startstop) {
+ qemu_spice_display_stop();
+ }
+
+ qxl_spice_reset_cursor(d);
+ qxl_spice_reset_image_cache(d);
+ qxl_reset_surfaces(d);
+ qxl_reset_memslots(d);
+
+ /* pre loadvm reset must not touch QXLRam. This lives in
+ * device memory, is migrated together with RAM and thus
+ * already loaded at this point */
+ if (!loadvm) {
+ qxl_reset_state(d);
+ }
+ qemu_spice_create_host_memslot(&d->ssd);
+ qxl_soft_reset(d);
+
+ if (startstop) {
+ qemu_spice_display_start();
+ }
+}
+
+static void qxl_reset_handler(DeviceState *dev)
+{
+ PCIQXLDevice *d = PCI_QXL(PCI_DEVICE(dev));
+
+ qxl_hard_reset(d, 0);
+}
+
+static void qxl_vga_ioport_write(void *opaque, uint32_t addr, uint32_t val)
+{
+ VGACommonState *vga = opaque;
+ PCIQXLDevice *qxl = container_of(vga, PCIQXLDevice, vga);
+
+ trace_qxl_io_write_vga(qxl->id, qxl_mode_to_string(qxl->mode), addr, val);
+ if (qxl->mode != QXL_MODE_VGA &&
+ qxl->revision <= QXL_REVISION_STABLE_V12) {
+ qxl_destroy_primary(qxl, QXL_SYNC);
+ qxl_soft_reset(qxl);
+ }
+ vga_ioport_write(opaque, addr, val);
+}
+
+static const MemoryRegionPortio qxl_vga_portio_list[] = {
+ { 0x04, 2, 1, .read = vga_ioport_read,
+ .write = qxl_vga_ioport_write }, /* 3b4 */
+ { 0x0a, 1, 1, .read = vga_ioport_read,
+ .write = qxl_vga_ioport_write }, /* 3ba */
+ { 0x10, 16, 1, .read = vga_ioport_read,
+ .write = qxl_vga_ioport_write }, /* 3c0 */
+ { 0x24, 2, 1, .read = vga_ioport_read,
+ .write = qxl_vga_ioport_write }, /* 3d4 */
+ { 0x2a, 1, 1, .read = vga_ioport_read,
+ .write = qxl_vga_ioport_write }, /* 3da */
+ PORTIO_END_OF_LIST(),
+};
+
+static int qxl_add_memslot(PCIQXLDevice *d, uint32_t slot_id, uint64_t delta,
+ qxl_async_io async)
+{
+ static const int regions[] = {
+ QXL_RAM_RANGE_INDEX,
+ QXL_VRAM_RANGE_INDEX,
+ QXL_VRAM64_RANGE_INDEX,
+ };
+ uint64_t guest_start;
+ uint64_t guest_end;
+ int pci_region;
+ pcibus_t pci_start;
+ pcibus_t pci_end;
+ MemoryRegion *mr;
+ intptr_t virt_start;
+ QXLDevMemSlot memslot;
+ int i;
+
+ guest_start = le64_to_cpu(d->guest_slots[slot_id].slot.mem_start);
+ guest_end = le64_to_cpu(d->guest_slots[slot_id].slot.mem_end);
+
+ trace_qxl_memslot_add_guest(d->id, slot_id, guest_start, guest_end);
+
+ if (slot_id >= NUM_MEMSLOTS) {
+ qxl_set_guest_bug(d, "%s: slot_id >= NUM_MEMSLOTS %d >= %d", __func__,
+ slot_id, NUM_MEMSLOTS);
+ return 1;
+ }
+ if (guest_start > guest_end) {
+ qxl_set_guest_bug(d, "%s: guest_start > guest_end 0x%" PRIx64
+ " > 0x%" PRIx64, __func__, guest_start, guest_end);
+ return 1;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(regions); i++) {
+ pci_region = regions[i];
+ pci_start = d->pci.io_regions[pci_region].addr;
+ pci_end = pci_start + d->pci.io_regions[pci_region].size;
+ /* mapped? */
+ if (pci_start == -1) {
+ continue;
+ }
+ /* start address in range ? */
+ if (guest_start < pci_start || guest_start > pci_end) {
+ continue;
+ }
+ /* end address in range ? */
+ if (guest_end > pci_end) {
+ continue;
+ }
+ /* passed */
+ break;
+ }
+ if (i == ARRAY_SIZE(regions)) {
+ qxl_set_guest_bug(d, "%s: finished loop without match", __func__);
+ return 1;
+ }
+
+ switch (pci_region) {
+ case QXL_RAM_RANGE_INDEX:
+ mr = &d->vga.vram;
+ break;
+ case QXL_VRAM_RANGE_INDEX:
+ case 4 /* vram 64bit */:
+ mr = &d->vram_bar;
+ break;
+ default:
+ /* should not happen */
+ qxl_set_guest_bug(d, "%s: pci_region = %d", __func__, pci_region);
+ return 1;
+ }
+
+ virt_start = (intptr_t)memory_region_get_ram_ptr(mr);
+ memslot.slot_id = slot_id;
+ memslot.slot_group_id = MEMSLOT_GROUP_GUEST; /* guest group */
+ memslot.virt_start = virt_start + (guest_start - pci_start);
+ memslot.virt_end = virt_start + (guest_end - pci_start);
+ memslot.addr_delta = memslot.virt_start - delta;
+ memslot.generation = d->rom->slot_generation = 0;
+ qxl_rom_set_dirty(d);
+
+ qemu_spice_add_memslot(&d->ssd, &memslot, async);
+ d->guest_slots[slot_id].mr = mr;
+ d->guest_slots[slot_id].offset = memslot.virt_start - virt_start;
+ d->guest_slots[slot_id].size = memslot.virt_end - memslot.virt_start;
+ d->guest_slots[slot_id].delta = delta;
+ d->guest_slots[slot_id].active = 1;
+ return 0;
+}
+
+static void qxl_del_memslot(PCIQXLDevice *d, uint32_t slot_id)
+{
+ qemu_spice_del_memslot(&d->ssd, MEMSLOT_GROUP_HOST, slot_id);
+ d->guest_slots[slot_id].active = 0;
+}
+
+static void qxl_reset_memslots(PCIQXLDevice *d)
+{
+ qxl_spice_reset_memslots(d);
+ memset(&d->guest_slots, 0, sizeof(d->guest_slots));
+}
+
+static void qxl_reset_surfaces(PCIQXLDevice *d)
+{
+ trace_qxl_reset_surfaces(d->id);
+ d->mode = QXL_MODE_UNDEFINED;
+ qxl_spice_destroy_surfaces(d, QXL_SYNC);
+}
+
+/* can be also called from spice server thread context */
+static bool qxl_get_check_slot_offset(PCIQXLDevice *qxl, QXLPHYSICAL pqxl,
+ uint32_t *s, uint64_t *o)
+{
+ uint64_t phys = le64_to_cpu(pqxl);
+ uint32_t slot = (phys >> (64 - 8)) & 0xff;
+ uint64_t offset = phys & 0xffffffffffff;
+
+ if (slot >= NUM_MEMSLOTS) {
+ qxl_set_guest_bug(qxl, "slot too large %d >= %d", slot,
+ NUM_MEMSLOTS);
+ return false;
+ }
+ if (!qxl->guest_slots[slot].active) {
+ qxl_set_guest_bug(qxl, "inactive slot %d\n", slot);
+ return false;
+ }
+ if (offset < qxl->guest_slots[slot].delta) {
+ qxl_set_guest_bug(qxl,
+ "slot %d offset %"PRIu64" < delta %"PRIu64"\n",
+ slot, offset, qxl->guest_slots[slot].delta);
+ return false;
+ }
+ offset -= qxl->guest_slots[slot].delta;
+ if (offset > qxl->guest_slots[slot].size) {
+ qxl_set_guest_bug(qxl,
+ "slot %d offset %"PRIu64" > size %"PRIu64"\n",
+ slot, offset, qxl->guest_slots[slot].size);
+ return false;
+ }
+
+ *s = slot;
+ *o = offset;
+ return true;
+}
+
+/* can be also called from spice server thread context */
+void *qxl_phys2virt(PCIQXLDevice *qxl, QXLPHYSICAL pqxl, int group_id)
+{
+ uint64_t offset;
+ uint32_t slot;
+ void *ptr;
+
+ switch (group_id) {
+ case MEMSLOT_GROUP_HOST:
+ offset = le64_to_cpu(pqxl) & 0xffffffffffff;
+ return (void *)(intptr_t)offset;
+ case MEMSLOT_GROUP_GUEST:
+ if (!qxl_get_check_slot_offset(qxl, pqxl, &slot, &offset)) {
+ return NULL;
+ }
+ ptr = memory_region_get_ram_ptr(qxl->guest_slots[slot].mr);
+ ptr += qxl->guest_slots[slot].offset;
+ ptr += offset;
+ return ptr;
+ }
+ return NULL;
+}
+
+static void qxl_create_guest_primary_complete(PCIQXLDevice *qxl)
+{
+ /* for local rendering */
+ qxl_render_resize(qxl);
+}
+
+static void qxl_create_guest_primary(PCIQXLDevice *qxl, int loadvm,
+ qxl_async_io async)
+{
+ QXLDevSurfaceCreate surface;
+ QXLSurfaceCreate *sc = &qxl->guest_primary.surface;
+ uint32_t requested_height = le32_to_cpu(sc->height);
+ int requested_stride = le32_to_cpu(sc->stride);
+
+ if (requested_stride == INT32_MIN ||
+ abs(requested_stride) * (uint64_t)requested_height
+ > qxl->vgamem_size) {
+ qxl_set_guest_bug(qxl, "%s: requested primary larger than framebuffer"
+ " stride %d x height %" PRIu32 " > %" PRIu32,
+ __func__, requested_stride, requested_height,
+ qxl->vgamem_size);
+ return;
+ }
+
+ if (qxl->mode == QXL_MODE_NATIVE) {
+ qxl_set_guest_bug(qxl, "%s: nop since already in QXL_MODE_NATIVE",
+ __func__);
+ }
+ qxl_exit_vga_mode(qxl);
+
+ surface.format = le32_to_cpu(sc->format);
+ surface.height = le32_to_cpu(sc->height);
+ surface.mem = le64_to_cpu(sc->mem);
+ surface.position = le32_to_cpu(sc->position);
+ surface.stride = le32_to_cpu(sc->stride);
+ surface.width = le32_to_cpu(sc->width);
+ surface.type = le32_to_cpu(sc->type);
+ surface.flags = le32_to_cpu(sc->flags);
+ trace_qxl_create_guest_primary(qxl->id, sc->width, sc->height, sc->mem,
+ sc->format, sc->position);
+ trace_qxl_create_guest_primary_rest(qxl->id, sc->stride, sc->type,
+ sc->flags);
+
+ if ((surface.stride & 0x3) != 0) {
+ qxl_set_guest_bug(qxl, "primary surface stride = %d %% 4 != 0",
+ surface.stride);
+ return;
+ }
+
+ surface.mouse_mode = true;
+ surface.group_id = MEMSLOT_GROUP_GUEST;
+ if (loadvm) {
+ surface.flags |= QXL_SURF_FLAG_KEEP_DATA;
+ }
+
+ qxl->mode = QXL_MODE_NATIVE;
+ qxl->cmdflags = 0;
+ qemu_spice_create_primary_surface(&qxl->ssd, 0, &surface, async);
+
+ if (async == QXL_SYNC) {
+ qxl_create_guest_primary_complete(qxl);
+ }
+}
+
+/* return 1 if surface destoy was initiated (in QXL_ASYNC case) or
+ * done (in QXL_SYNC case), 0 otherwise. */
+static int qxl_destroy_primary(PCIQXLDevice *d, qxl_async_io async)
+{
+ if (d->mode == QXL_MODE_UNDEFINED) {
+ return 0;
+ }
+ trace_qxl_destroy_primary(d->id);
+ d->mode = QXL_MODE_UNDEFINED;
+ qemu_spice_destroy_primary_surface(&d->ssd, 0, async);
+ qxl_spice_reset_cursor(d);
+ return 1;
+}
+
+static void qxl_set_mode(PCIQXLDevice *d, unsigned int modenr, int loadvm)
+{
+ pcibus_t start = d->pci.io_regions[QXL_RAM_RANGE_INDEX].addr;
+ pcibus_t end = d->pci.io_regions[QXL_RAM_RANGE_INDEX].size + start;
+ QXLMode *mode = d->modes->modes + modenr;
+ uint64_t devmem = d->pci.io_regions[QXL_RAM_RANGE_INDEX].addr;
+ QXLMemSlot slot = {
+ .mem_start = start,
+ .mem_end = end
+ };
+
+ if (modenr >= d->modes->n_modes) {
+ qxl_set_guest_bug(d, "mode number out of range");
+ return;
+ }
+
+ QXLSurfaceCreate surface = {
+ .width = mode->x_res,
+ .height = mode->y_res,
+ .stride = -mode->x_res * 4,
+ .format = SPICE_SURFACE_FMT_32_xRGB,
+ .flags = loadvm ? QXL_SURF_FLAG_KEEP_DATA : 0,
+ .mouse_mode = true,
+ .mem = devmem + d->shadow_rom.draw_area_offset,
+ };
+
+ trace_qxl_set_mode(d->id, modenr, mode->x_res, mode->y_res, mode->bits,
+ devmem);
+ if (!loadvm) {
+ qxl_hard_reset(d, 0);
+ }
+
+ d->guest_slots[0].slot = slot;
+ assert(qxl_add_memslot(d, 0, devmem, QXL_SYNC) == 0);
+
+ d->guest_primary.surface = surface;
+ qxl_create_guest_primary(d, 0, QXL_SYNC);
+
+ d->mode = QXL_MODE_COMPAT;
+ d->cmdflags = QXL_COMMAND_FLAG_COMPAT;
+ if (mode->bits == 16) {
+ d->cmdflags |= QXL_COMMAND_FLAG_COMPAT_16BPP;
+ }
+ d->shadow_rom.mode = cpu_to_le32(modenr);
+ d->rom->mode = cpu_to_le32(modenr);
+ qxl_rom_set_dirty(d);
+}
+
+static void ioport_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ PCIQXLDevice *d = opaque;
+ uint32_t io_port = addr;
+ qxl_async_io async = QXL_SYNC;
+ uint32_t orig_io_port;
+
+ if (d->guest_bug && io_port != QXL_IO_RESET) {
+ return;
+ }
+
+ if (d->revision <= QXL_REVISION_STABLE_V10 &&
+ io_port > QXL_IO_FLUSH_RELEASE) {
+ qxl_set_guest_bug(d, "unsupported io %d for revision %d\n",
+ io_port, d->revision);
+ return;
+ }
+
+ switch (io_port) {
+ case QXL_IO_RESET:
+ case QXL_IO_SET_MODE:
+ case QXL_IO_MEMSLOT_ADD:
+ case QXL_IO_MEMSLOT_DEL:
+ case QXL_IO_CREATE_PRIMARY:
+ case QXL_IO_UPDATE_IRQ:
+ case QXL_IO_LOG:
+ case QXL_IO_MEMSLOT_ADD_ASYNC:
+ case QXL_IO_CREATE_PRIMARY_ASYNC:
+ break;
+ default:
+ if (d->mode != QXL_MODE_VGA) {
+ break;
+ }
+ trace_qxl_io_unexpected_vga_mode(d->id,
+ addr, val, io_port_to_string(io_port));
+ /* be nice to buggy guest drivers */
+ if (io_port >= QXL_IO_UPDATE_AREA_ASYNC &&
+ io_port < QXL_IO_RANGE_SIZE) {
+ qxl_send_events(d, QXL_INTERRUPT_IO_CMD);
+ }
+ return;
+ }
+
+ /* we change the io_port to avoid ifdeffery in the main switch */
+ orig_io_port = io_port;
+ switch (io_port) {
+ case QXL_IO_UPDATE_AREA_ASYNC:
+ io_port = QXL_IO_UPDATE_AREA;
+ goto async_common;
+ case QXL_IO_MEMSLOT_ADD_ASYNC:
+ io_port = QXL_IO_MEMSLOT_ADD;
+ goto async_common;
+ case QXL_IO_CREATE_PRIMARY_ASYNC:
+ io_port = QXL_IO_CREATE_PRIMARY;
+ goto async_common;
+ case QXL_IO_DESTROY_PRIMARY_ASYNC:
+ io_port = QXL_IO_DESTROY_PRIMARY;
+ goto async_common;
+ case QXL_IO_DESTROY_SURFACE_ASYNC:
+ io_port = QXL_IO_DESTROY_SURFACE_WAIT;
+ goto async_common;
+ case QXL_IO_DESTROY_ALL_SURFACES_ASYNC:
+ io_port = QXL_IO_DESTROY_ALL_SURFACES;
+ goto async_common;
+ case QXL_IO_FLUSH_SURFACES_ASYNC:
+ case QXL_IO_MONITORS_CONFIG_ASYNC:
+async_common:
+ async = QXL_ASYNC;
+ WITH_QEMU_LOCK_GUARD(&d->async_lock) {
+ if (d->current_async != QXL_UNDEFINED_IO) {
+ qxl_set_guest_bug(d, "%d async started before last (%d) complete",
+ io_port, d->current_async);
+ return;
+ }
+ d->current_async = orig_io_port;
+ }
+ break;
+ default:
+ break;
+ }
+ trace_qxl_io_write(d->id, qxl_mode_to_string(d->mode),
+ addr, io_port_to_string(addr),
+ val, size, async);
+
+ switch (io_port) {
+ case QXL_IO_UPDATE_AREA:
+ {
+ QXLCookie *cookie = NULL;
+ QXLRect update = d->ram->update_area;
+
+ if (d->ram->update_surface > d->ssd.num_surfaces) {
+ qxl_set_guest_bug(d, "QXL_IO_UPDATE_AREA: invalid surface id %d\n",
+ d->ram->update_surface);
+ break;
+ }
+ if (update.left >= update.right || update.top >= update.bottom ||
+ update.left < 0 || update.top < 0) {
+ qxl_set_guest_bug(d,
+ "QXL_IO_UPDATE_AREA: invalid area (%ux%u)x(%ux%u)\n",
+ update.left, update.top, update.right, update.bottom);
+ if (update.left == update.right || update.top == update.bottom) {
+ /* old drivers may provide empty area, keep going */
+ qxl_clear_guest_bug(d);
+ goto cancel_async;
+ }
+ break;
+ }
+ if (async == QXL_ASYNC) {
+ cookie = qxl_cookie_new(QXL_COOKIE_TYPE_IO,
+ QXL_IO_UPDATE_AREA_ASYNC);
+ cookie->u.area = update;
+ }
+ qxl_spice_update_area(d, d->ram->update_surface,
+ cookie ? &cookie->u.area : &update,
+ NULL, 0, 0, async, cookie);
+ break;
+ }
+ case QXL_IO_NOTIFY_CMD:
+ qemu_spice_wakeup(&d->ssd);
+ break;
+ case QXL_IO_NOTIFY_CURSOR:
+ qemu_spice_wakeup(&d->ssd);
+ break;
+ case QXL_IO_UPDATE_IRQ:
+ qxl_update_irq(d);
+ break;
+ case QXL_IO_NOTIFY_OOM:
+ if (!SPICE_RING_IS_EMPTY(&d->ram->release_ring)) {
+ break;
+ }
+ d->oom_running = 1;
+ qxl_spice_oom(d);
+ d->oom_running = 0;
+ break;
+ case QXL_IO_SET_MODE:
+ qxl_set_mode(d, val, 0);
+ break;
+ case QXL_IO_LOG:
+#ifdef CONFIG_MODULES
+ /*
+ * FIXME
+ * trace_event_get_state_backends() does not work for modules,
+ * it leads to "undefined symbol: qemu_qxl_io_log_semaphore"
+ */
+ if (true) {
+#else
+ if (trace_event_get_state_backends(TRACE_QXL_IO_LOG) || d->guestdebug) {
+#endif
+ /* We cannot trust the guest to NUL terminate d->ram->log_buf */
+ char *log_buf = g_strndup((const char *)d->ram->log_buf,
+ sizeof(d->ram->log_buf));
+ trace_qxl_io_log(d->id, log_buf);
+ if (d->guestdebug) {
+ fprintf(stderr, "qxl/guest-%d: %" PRId64 ": %s", d->id,
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), log_buf);
+ }
+ g_free(log_buf);
+ }
+ break;
+ case QXL_IO_RESET:
+ qxl_hard_reset(d, 0);
+ break;
+ case QXL_IO_MEMSLOT_ADD:
+ if (val >= NUM_MEMSLOTS) {
+ qxl_set_guest_bug(d, "QXL_IO_MEMSLOT_ADD: val out of range");
+ break;
+ }
+ if (d->guest_slots[val].active) {
+ qxl_set_guest_bug(d,
+ "QXL_IO_MEMSLOT_ADD: memory slot already active");
+ break;
+ }
+ d->guest_slots[val].slot = d->ram->mem_slot;
+ qxl_add_memslot(d, val, 0, async);
+ break;
+ case QXL_IO_MEMSLOT_DEL:
+ if (val >= NUM_MEMSLOTS) {
+ qxl_set_guest_bug(d, "QXL_IO_MEMSLOT_DEL: val out of range");
+ break;
+ }
+ qxl_del_memslot(d, val);
+ break;
+ case QXL_IO_CREATE_PRIMARY:
+ if (val != 0) {
+ qxl_set_guest_bug(d, "QXL_IO_CREATE_PRIMARY (async=%d): val != 0",
+ async);
+ goto cancel_async;
+ }
+ d->guest_primary.surface = d->ram->create_surface;
+ qxl_create_guest_primary(d, 0, async);
+ break;
+ case QXL_IO_DESTROY_PRIMARY:
+ if (val != 0) {
+ qxl_set_guest_bug(d, "QXL_IO_DESTROY_PRIMARY (async=%d): val != 0",
+ async);
+ goto cancel_async;
+ }
+ if (!qxl_destroy_primary(d, async)) {
+ trace_qxl_io_destroy_primary_ignored(d->id,
+ qxl_mode_to_string(d->mode));
+ goto cancel_async;
+ }
+ break;
+ case QXL_IO_DESTROY_SURFACE_WAIT:
+ if (val >= d->ssd.num_surfaces) {
+ qxl_set_guest_bug(d, "QXL_IO_DESTROY_SURFACE (async=%d):"
+ "%" PRIu64 " >= NUM_SURFACES", async, val);
+ goto cancel_async;
+ }
+ qxl_spice_destroy_surface_wait(d, val, async);
+ break;
+ case QXL_IO_FLUSH_RELEASE: {
+ QXLReleaseRing *ring = &d->ram->release_ring;
+ if (ring->prod - ring->cons + 1 == ring->num_items) {
+ fprintf(stderr,
+ "ERROR: no flush, full release ring [p%d,%dc]\n",
+ ring->prod, ring->cons);
+ }
+ qxl_push_free_res(d, 1 /* flush */);
+ break;
+ }
+ case QXL_IO_FLUSH_SURFACES_ASYNC:
+ qxl_spice_flush_surfaces_async(d);
+ break;
+ case QXL_IO_DESTROY_ALL_SURFACES:
+ d->mode = QXL_MODE_UNDEFINED;
+ qxl_spice_destroy_surfaces(d, async);
+ break;
+ case QXL_IO_MONITORS_CONFIG_ASYNC:
+ qxl_spice_monitors_config_async(d, 0);
+ break;
+ default:
+ qxl_set_guest_bug(d, "%s: unexpected ioport=0x%x\n", __func__, io_port);
+ }
+ return;
+cancel_async:
+ if (async) {
+ qxl_send_events(d, QXL_INTERRUPT_IO_CMD);
+ qemu_mutex_lock(&d->async_lock);
+ d->current_async = QXL_UNDEFINED_IO;
+ qemu_mutex_unlock(&d->async_lock);
+ }
+}
+
+static uint64_t ioport_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ PCIQXLDevice *qxl = opaque;
+
+ trace_qxl_io_read_unexpected(qxl->id);
+ return 0xff;
+}
+
+static const MemoryRegionOps qxl_io_ops = {
+ .read = ioport_read,
+ .write = ioport_write,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static void qxl_update_irq_bh(void *opaque)
+{
+ PCIQXLDevice *d = opaque;
+ qxl_update_irq(d);
+}
+
+static void qxl_send_events(PCIQXLDevice *d, uint32_t events)
+{
+ uint32_t old_pending;
+ uint32_t le_events = cpu_to_le32(events);
+
+ trace_qxl_send_events(d->id, events);
+ if (!qemu_spice_display_is_running(&d->ssd)) {
+ /* spice-server tracks guest running state and should not do this */
+ fprintf(stderr, "%s: spice-server bug: guest stopped, ignoring\n",
+ __func__);
+ trace_qxl_send_events_vm_stopped(d->id, events);
+ return;
+ }
+ /*
+ * Older versions of Spice forgot to define the QXLRam struct
+ * with the '__aligned__(4)' attribute. clang 7 and newer will
+ * thus warn that qatomic_fetch_or(&d->ram->int_pending, ...)
+ * might be a misaligned atomic access, and will generate an
+ * out-of-line call for it, which results in a link error since
+ * we don't currently link against libatomic.
+ *
+ * In fact we set up d->ram in init_qxl_ram() so it always starts
+ * at a 4K boundary, so we know that &d->ram->int_pending is
+ * naturally aligned for a uint32_t. Newer Spice versions
+ * (with Spice commit beda5ec7a6848be20c0cac2a9a8ef2a41e8069c1)
+ * will fix the bug directly. To deal with older versions,
+ * we tell the compiler to assume the address really is aligned.
+ * Any compiler which cares about the misalignment will have
+ * __builtin_assume_aligned.
+ */
+#ifdef HAS_ASSUME_ALIGNED
+#define ALIGNED_UINT32_PTR(P) ((uint32_t *)__builtin_assume_aligned(P, 4))
+#else
+#define ALIGNED_UINT32_PTR(P) ((uint32_t *)P)
+#endif
+
+ old_pending = qatomic_fetch_or(ALIGNED_UINT32_PTR(&d->ram->int_pending),
+ le_events);
+ if ((old_pending & le_events) == le_events) {
+ return;
+ }
+ qemu_bh_schedule(d->update_irq);
+}
+
+/* graphics console */
+
+static void qxl_hw_update(void *opaque)
+{
+ PCIQXLDevice *qxl = opaque;
+
+ qxl_render_update(qxl);
+}
+
+static void qxl_dirty_one_surface(PCIQXLDevice *qxl, QXLPHYSICAL pqxl,
+ uint32_t height, int32_t stride)
+{
+ uint64_t offset, size;
+ uint32_t slot;
+ bool rc;
+
+ rc = qxl_get_check_slot_offset(qxl, pqxl, &slot, &offset);
+ assert(rc == true);
+ size = (uint64_t)height * abs(stride);
+ trace_qxl_surfaces_dirty(qxl->id, offset, size);
+ qxl_set_dirty(qxl->guest_slots[slot].mr,
+ qxl->guest_slots[slot].offset + offset,
+ qxl->guest_slots[slot].offset + offset + size);
+}
+
+static void qxl_dirty_surfaces(PCIQXLDevice *qxl)
+{
+ int i;
+
+ if (qxl->mode != QXL_MODE_NATIVE && qxl->mode != QXL_MODE_COMPAT) {
+ return;
+ }
+
+ /* dirty the primary surface */
+ qxl_dirty_one_surface(qxl, qxl->guest_primary.surface.mem,
+ qxl->guest_primary.surface.height,
+ qxl->guest_primary.surface.stride);
+
+ /* dirty the off-screen surfaces */
+ for (i = 0; i < qxl->ssd.num_surfaces; i++) {
+ QXLSurfaceCmd *cmd;
+
+ if (qxl->guest_surfaces.cmds[i] == 0) {
+ continue;
+ }
+
+ cmd = qxl_phys2virt(qxl, qxl->guest_surfaces.cmds[i],
+ MEMSLOT_GROUP_GUEST);
+ assert(cmd);
+ assert(cmd->type == QXL_SURFACE_CMD_CREATE);
+ qxl_dirty_one_surface(qxl, cmd->u.surface_create.data,
+ cmd->u.surface_create.height,
+ cmd->u.surface_create.stride);
+ }
+}
+
+static void qxl_vm_change_state_handler(void *opaque, bool running,
+ RunState state)
+{
+ PCIQXLDevice *qxl = opaque;
+
+ if (running) {
+ /*
+ * if qxl_send_events was called from spice server context before
+ * migration ended, qxl_update_irq for these events might not have been
+ * called
+ */
+ qxl_update_irq(qxl);
+ } else {
+ /* make sure surfaces are saved before migration */
+ qxl_dirty_surfaces(qxl);
+ }
+}
+
+/* display change listener */
+
+static void display_update(DisplayChangeListener *dcl,
+ int x, int y, int w, int h)
+{
+ PCIQXLDevice *qxl = container_of(dcl, PCIQXLDevice, ssd.dcl);
+
+ if (qxl->mode == QXL_MODE_VGA) {
+ qemu_spice_display_update(&qxl->ssd, x, y, w, h);
+ }
+}
+
+static void display_switch(DisplayChangeListener *dcl,
+ struct DisplaySurface *surface)
+{
+ PCIQXLDevice *qxl = container_of(dcl, PCIQXLDevice, ssd.dcl);
+
+ qxl->ssd.ds = surface;
+ if (qxl->mode == QXL_MODE_VGA) {
+ qemu_spice_display_switch(&qxl->ssd, surface);
+ }
+}
+
+static void display_refresh(DisplayChangeListener *dcl)
+{
+ PCIQXLDevice *qxl = container_of(dcl, PCIQXLDevice, ssd.dcl);
+
+ if (qxl->mode == QXL_MODE_VGA) {
+ qemu_spice_display_refresh(&qxl->ssd);
+ }
+}
+
+static DisplayChangeListenerOps display_listener_ops = {
+ .dpy_name = "spice/qxl",
+ .dpy_gfx_update = display_update,
+ .dpy_gfx_switch = display_switch,
+ .dpy_refresh = display_refresh,
+};
+
+static void qxl_init_ramsize(PCIQXLDevice *qxl)
+{
+ /* vga mode framebuffer / primary surface (bar 0, first part) */
+ if (qxl->vgamem_size_mb < 8) {
+ qxl->vgamem_size_mb = 8;
+ }
+ /* XXX: we round vgamem_size_mb up to a nearest power of two and it must be
+ * less than vga_common_init()'s maximum on qxl->vga.vram_size (512 now).
+ */
+ if (qxl->vgamem_size_mb > 256) {
+ qxl->vgamem_size_mb = 256;
+ }
+ qxl->vgamem_size = qxl->vgamem_size_mb * MiB;
+
+ /* vga ram (bar 0, total) */
+ if (qxl->ram_size_mb != -1) {
+ qxl->vga.vram_size = qxl->ram_size_mb * MiB;
+ }
+ if (qxl->vga.vram_size < qxl->vgamem_size * 2) {
+ qxl->vga.vram_size = qxl->vgamem_size * 2;
+ }
+
+ /* vram32 (surfaces, 32bit, bar 1) */
+ if (qxl->vram32_size_mb != -1) {
+ qxl->vram32_size = qxl->vram32_size_mb * MiB;
+ }
+ if (qxl->vram32_size < 4096) {
+ qxl->vram32_size = 4096;
+ }
+
+ /* vram (surfaces, 64bit, bar 4+5) */
+ if (qxl->vram_size_mb != -1) {
+ qxl->vram_size = (uint64_t)qxl->vram_size_mb * MiB;
+ }
+ if (qxl->vram_size < qxl->vram32_size) {
+ qxl->vram_size = qxl->vram32_size;
+ }
+
+ if (qxl->revision == 1) {
+ qxl->vram32_size = 4096;
+ qxl->vram_size = 4096;
+ }
+ qxl->vgamem_size = pow2ceil(qxl->vgamem_size);
+ qxl->vga.vram_size = pow2ceil(qxl->vga.vram_size);
+ qxl->vram32_size = pow2ceil(qxl->vram32_size);
+ qxl->vram_size = pow2ceil(qxl->vram_size);
+}
+
+static void qxl_realize_common(PCIQXLDevice *qxl, Error **errp)
+{
+ uint8_t* config = qxl->pci.config;
+ uint32_t pci_device_rev;
+ uint32_t io_size;
+
+ qemu_spice_display_init_common(&qxl->ssd);
+ qxl->mode = QXL_MODE_UNDEFINED;
+ qxl->num_memslots = NUM_MEMSLOTS;
+ qemu_mutex_init(&qxl->track_lock);
+ qemu_mutex_init(&qxl->async_lock);
+ qxl->current_async = QXL_UNDEFINED_IO;
+ qxl->guest_bug = 0;
+
+ switch (qxl->revision) {
+ case 1: /* spice 0.4 -- qxl-1 */
+ pci_device_rev = QXL_REVISION_STABLE_V04;
+ io_size = 8;
+ break;
+ case 2: /* spice 0.6 -- qxl-2 */
+ pci_device_rev = QXL_REVISION_STABLE_V06;
+ io_size = 16;
+ break;
+ case 3: /* qxl-3 */
+ pci_device_rev = QXL_REVISION_STABLE_V10;
+ io_size = 32; /* PCI region size must be pow2 */
+ break;
+ case 4: /* qxl-4 */
+ pci_device_rev = QXL_REVISION_STABLE_V12;
+ io_size = pow2ceil(QXL_IO_RANGE_SIZE);
+ break;
+ case 5: /* qxl-5 */
+ pci_device_rev = QXL_REVISION_STABLE_V12 + 1;
+ io_size = pow2ceil(QXL_IO_RANGE_SIZE);
+ break;
+ default:
+ error_setg(errp, "Invalid revision %d for qxl device (max %d)",
+ qxl->revision, QXL_DEFAULT_REVISION);
+ return;
+ }
+
+ pci_set_byte(&config[PCI_REVISION_ID], pci_device_rev);
+ pci_set_byte(&config[PCI_INTERRUPT_PIN], 1);
+
+ qxl->rom_size = qxl_rom_size();
+ memory_region_init_rom(&qxl->rom_bar, OBJECT(qxl), "qxl.vrom",
+ qxl->rom_size, &error_fatal);
+ init_qxl_rom(qxl);
+ init_qxl_ram(qxl);
+
+ qxl->guest_surfaces.cmds = g_new0(QXLPHYSICAL, qxl->ssd.num_surfaces);
+ memory_region_init_ram(&qxl->vram_bar, OBJECT(qxl), "qxl.vram",
+ qxl->vram_size, &error_fatal);
+ memory_region_init_alias(&qxl->vram32_bar, OBJECT(qxl), "qxl.vram32",
+ &qxl->vram_bar, 0, qxl->vram32_size);
+
+ memory_region_init_io(&qxl->io_bar, OBJECT(qxl), &qxl_io_ops, qxl,
+ "qxl-ioports", io_size);
+ if (qxl->have_vga) {
+ vga_dirty_log_start(&qxl->vga);
+ }
+ memory_region_set_flush_coalesced(&qxl->io_bar);
+
+
+ pci_register_bar(&qxl->pci, QXL_IO_RANGE_INDEX,
+ PCI_BASE_ADDRESS_SPACE_IO, &qxl->io_bar);
+
+ pci_register_bar(&qxl->pci, QXL_ROM_RANGE_INDEX,
+ PCI_BASE_ADDRESS_SPACE_MEMORY, &qxl->rom_bar);
+
+ pci_register_bar(&qxl->pci, QXL_RAM_RANGE_INDEX,
+ PCI_BASE_ADDRESS_SPACE_MEMORY, &qxl->vga.vram);
+
+ pci_register_bar(&qxl->pci, QXL_VRAM_RANGE_INDEX,
+ PCI_BASE_ADDRESS_SPACE_MEMORY, &qxl->vram32_bar);
+
+ if (qxl->vram32_size < qxl->vram_size) {
+ /*
+ * Make the 64bit vram bar show up only in case it is
+ * configured to be larger than the 32bit vram bar.
+ */
+ pci_register_bar(&qxl->pci, QXL_VRAM64_RANGE_INDEX,
+ PCI_BASE_ADDRESS_SPACE_MEMORY |
+ PCI_BASE_ADDRESS_MEM_TYPE_64 |
+ PCI_BASE_ADDRESS_MEM_PREFETCH,
+ &qxl->vram_bar);
+ }
+
+ /* print pci bar details */
+ dprint(qxl, 1, "ram/%s: %" PRId64 " MB [region 0]\n",
+ qxl->have_vga ? "pri" : "sec", qxl->vga.vram_size / MiB);
+ dprint(qxl, 1, "vram/32: %" PRIx64 " MB [region 1]\n",
+ qxl->vram32_size / MiB);
+ dprint(qxl, 1, "vram/64: %" PRIx64 " MB %s\n",
+ qxl->vram_size / MiB,
+ qxl->vram32_size < qxl->vram_size ? "[region 4]" : "[unmapped]");
+
+ qxl->ssd.qxl.base.sif = &qxl_interface.base;
+ if (qemu_spice_add_display_interface(&qxl->ssd.qxl, qxl->vga.con) != 0) {
+ error_setg(errp, "qxl interface %d.%d not supported by spice-server",
+ SPICE_INTERFACE_QXL_MAJOR, SPICE_INTERFACE_QXL_MINOR);
+ return;
+ }
+
+#if SPICE_SERVER_VERSION >= 0x000e02 /* release 0.14.2 */
+ char device_address[256] = "";
+ if (qemu_spice_fill_device_address(qxl->vga.con, device_address, 256)) {
+ spice_qxl_set_device_info(&qxl->ssd.qxl,
+ device_address,
+ 0,
+ qxl->max_outputs);
+ }
+#endif
+
+ qemu_add_vm_change_state_handler(qxl_vm_change_state_handler, qxl);
+
+ qxl->update_irq = qemu_bh_new(qxl_update_irq_bh, qxl);
+ qxl_reset_state(qxl);
+
+ qxl->update_area_bh = qemu_bh_new(qxl_render_update_area_bh, qxl);
+ qxl->ssd.cursor_bh = qemu_bh_new(qemu_spice_cursor_refresh_bh, &qxl->ssd);
+}
+
+static void qxl_realize_primary(PCIDevice *dev, Error **errp)
+{
+ PCIQXLDevice *qxl = PCI_QXL(dev);
+ VGACommonState *vga = &qxl->vga;
+ Error *local_err = NULL;
+
+ qxl_init_ramsize(qxl);
+ vga->vbe_size = qxl->vgamem_size;
+ vga->vram_size_mb = qxl->vga.vram_size / MiB;
+ vga_common_init(vga, OBJECT(dev));
+ vga_init(vga, OBJECT(dev),
+ pci_address_space(dev), pci_address_space_io(dev), false);
+ portio_list_init(&qxl->vga_port_list, OBJECT(dev), qxl_vga_portio_list,
+ vga, "vga");
+ portio_list_set_flush_coalesced(&qxl->vga_port_list);
+ portio_list_add(&qxl->vga_port_list, pci_address_space_io(dev), 0x3b0);
+ qxl->have_vga = true;
+
+ vga->con = graphic_console_init(DEVICE(dev), 0, &qxl_ops, qxl);
+ qxl->id = qemu_console_get_index(vga->con); /* == channel_id */
+ if (qxl->id != 0) {
+ error_setg(errp, "primary qxl-vga device must be console 0 "
+ "(first display device on the command line)");
+ return;
+ }
+
+ qxl_realize_common(qxl, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+
+ qxl->ssd.dcl.ops = &display_listener_ops;
+ qxl->ssd.dcl.con = vga->con;
+ register_displaychangelistener(&qxl->ssd.dcl);
+}
+
+static void qxl_realize_secondary(PCIDevice *dev, Error **errp)
+{
+ PCIQXLDevice *qxl = PCI_QXL(dev);
+
+ qxl_init_ramsize(qxl);
+ memory_region_init_ram(&qxl->vga.vram, OBJECT(dev), "qxl.vgavram",
+ qxl->vga.vram_size, &error_fatal);
+ qxl->vga.vram_ptr = memory_region_get_ram_ptr(&qxl->vga.vram);
+ qxl->vga.con = graphic_console_init(DEVICE(dev), 0, &qxl_ops, qxl);
+ qxl->ssd.dcl.con = qxl->vga.con;
+ qxl->id = qemu_console_get_index(qxl->vga.con); /* == channel_id */
+
+ qxl_realize_common(qxl, errp);
+}
+
+static int qxl_pre_save(void *opaque)
+{
+ PCIQXLDevice* d = opaque;
+ uint8_t *ram_start = d->vga.vram_ptr;
+
+ trace_qxl_pre_save(d->id);
+ if (d->last_release == NULL) {
+ d->last_release_offset = 0;
+ } else {
+ d->last_release_offset = (uint8_t *)d->last_release - ram_start;
+ }
+ if (d->last_release_offset >= d->vga.vram_size) {
+ return 1;
+ }
+
+ return 0;
+}
+
+static int qxl_pre_load(void *opaque)
+{
+ PCIQXLDevice* d = opaque;
+
+ trace_qxl_pre_load(d->id);
+ qxl_hard_reset(d, 1);
+ qxl_exit_vga_mode(d);
+ return 0;
+}
+
+static void qxl_create_memslots(PCIQXLDevice *d)
+{
+ int i;
+
+ for (i = 0; i < NUM_MEMSLOTS; i++) {
+ if (!d->guest_slots[i].active) {
+ continue;
+ }
+ qxl_add_memslot(d, i, 0, QXL_SYNC);
+ }
+}
+
+static int qxl_post_load(void *opaque, int version)
+{
+ PCIQXLDevice* d = opaque;
+ uint8_t *ram_start = d->vga.vram_ptr;
+ QXLCommandExt *cmds;
+ int in, out, newmode;
+
+ assert(d->last_release_offset < d->vga.vram_size);
+ if (d->last_release_offset == 0) {
+ d->last_release = NULL;
+ } else {
+ d->last_release = (QXLReleaseInfo *)(ram_start + d->last_release_offset);
+ }
+
+ d->modes = (QXLModes*)((uint8_t*)d->rom + d->rom->modes_offset);
+
+ trace_qxl_post_load(d->id, qxl_mode_to_string(d->mode));
+ newmode = d->mode;
+ d->mode = QXL_MODE_UNDEFINED;
+
+ switch (newmode) {
+ case QXL_MODE_UNDEFINED:
+ qxl_create_memslots(d);
+ break;
+ case QXL_MODE_VGA:
+ qxl_create_memslots(d);
+ qxl_enter_vga_mode(d);
+ break;
+ case QXL_MODE_NATIVE:
+ qxl_create_memslots(d);
+ qxl_create_guest_primary(d, 1, QXL_SYNC);
+
+ /* replay surface-create and cursor-set commands */
+ cmds = g_new0(QXLCommandExt, d->ssd.num_surfaces + 1);
+ for (in = 0, out = 0; in < d->ssd.num_surfaces; in++) {
+ if (d->guest_surfaces.cmds[in] == 0) {
+ continue;
+ }
+ cmds[out].cmd.data = d->guest_surfaces.cmds[in];
+ cmds[out].cmd.type = QXL_CMD_SURFACE;
+ cmds[out].group_id = MEMSLOT_GROUP_GUEST;
+ out++;
+ }
+ if (d->guest_cursor) {
+ cmds[out].cmd.data = d->guest_cursor;
+ cmds[out].cmd.type = QXL_CMD_CURSOR;
+ cmds[out].group_id = MEMSLOT_GROUP_GUEST;
+ out++;
+ }
+ qxl_spice_loadvm_commands(d, cmds, out);
+ g_free(cmds);
+ if (d->guest_monitors_config) {
+ qxl_spice_monitors_config_async(d, 1);
+ }
+ break;
+ case QXL_MODE_COMPAT:
+ /* note: no need to call qxl_create_memslots, qxl_set_mode
+ * creates the mem slot. */
+ qxl_set_mode(d, d->shadow_rom.mode, 1);
+ break;
+ }
+ return 0;
+}
+
+#define QXL_SAVE_VERSION 21
+
+static bool qxl_monitors_config_needed(void *opaque)
+{
+ PCIQXLDevice *qxl = opaque;
+
+ return qxl->guest_monitors_config != 0;
+}
+
+
+static const VMStateDescription qxl_memslot = {
+ .name = "qxl-memslot",
+ .version_id = QXL_SAVE_VERSION,
+ .minimum_version_id = QXL_SAVE_VERSION,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT64(slot.mem_start, struct guest_slots),
+ VMSTATE_UINT64(slot.mem_end, struct guest_slots),
+ VMSTATE_UINT32(active, struct guest_slots),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription qxl_surface = {
+ .name = "qxl-surface",
+ .version_id = QXL_SAVE_VERSION,
+ .minimum_version_id = QXL_SAVE_VERSION,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(width, QXLSurfaceCreate),
+ VMSTATE_UINT32(height, QXLSurfaceCreate),
+ VMSTATE_INT32(stride, QXLSurfaceCreate),
+ VMSTATE_UINT32(format, QXLSurfaceCreate),
+ VMSTATE_UINT32(position, QXLSurfaceCreate),
+ VMSTATE_UINT32(mouse_mode, QXLSurfaceCreate),
+ VMSTATE_UINT32(flags, QXLSurfaceCreate),
+ VMSTATE_UINT32(type, QXLSurfaceCreate),
+ VMSTATE_UINT64(mem, QXLSurfaceCreate),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription qxl_vmstate_monitors_config = {
+ .name = "qxl/monitors-config",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = qxl_monitors_config_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT64(guest_monitors_config, PCIQXLDevice),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static const VMStateDescription qxl_vmstate = {
+ .name = "qxl",
+ .version_id = QXL_SAVE_VERSION,
+ .minimum_version_id = QXL_SAVE_VERSION,
+ .pre_save = qxl_pre_save,
+ .pre_load = qxl_pre_load,
+ .post_load = qxl_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(pci, PCIQXLDevice),
+ VMSTATE_STRUCT(vga, PCIQXLDevice, 0, vmstate_vga_common, VGACommonState),
+ VMSTATE_UINT32(shadow_rom.mode, PCIQXLDevice),
+ VMSTATE_UINT32(num_free_res, PCIQXLDevice),
+ VMSTATE_UINT32(last_release_offset, PCIQXLDevice),
+ VMSTATE_UINT32(mode, PCIQXLDevice),
+ VMSTATE_UINT32(ssd.unique, PCIQXLDevice),
+ VMSTATE_INT32_EQUAL(num_memslots, PCIQXLDevice, NULL),
+ VMSTATE_STRUCT_ARRAY(guest_slots, PCIQXLDevice, NUM_MEMSLOTS, 0,
+ qxl_memslot, struct guest_slots),
+ VMSTATE_STRUCT(guest_primary.surface, PCIQXLDevice, 0,
+ qxl_surface, QXLSurfaceCreate),
+ VMSTATE_INT32_EQUAL(ssd.num_surfaces, PCIQXLDevice, NULL),
+ VMSTATE_VARRAY_INT32(guest_surfaces.cmds, PCIQXLDevice,
+ ssd.num_surfaces, 0,
+ vmstate_info_uint64, uint64_t),
+ VMSTATE_UINT64(guest_cursor, PCIQXLDevice),
+ VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription*[]) {
+ &qxl_vmstate_monitors_config,
+ NULL
+ }
+};
+
+static Property qxl_properties[] = {
+ DEFINE_PROP_UINT32("ram_size", PCIQXLDevice, vga.vram_size, 64 * MiB),
+ DEFINE_PROP_UINT64("vram_size", PCIQXLDevice, vram32_size, 64 * MiB),
+ DEFINE_PROP_UINT32("revision", PCIQXLDevice, revision,
+ QXL_DEFAULT_REVISION),
+ DEFINE_PROP_UINT32("debug", PCIQXLDevice, debug, 0),
+ DEFINE_PROP_UINT32("guestdebug", PCIQXLDevice, guestdebug, 0),
+ DEFINE_PROP_UINT32("cmdlog", PCIQXLDevice, cmdlog, 0),
+ DEFINE_PROP_UINT32("ram_size_mb", PCIQXLDevice, ram_size_mb, -1),
+ DEFINE_PROP_UINT32("vram_size_mb", PCIQXLDevice, vram32_size_mb, -1),
+ DEFINE_PROP_UINT32("vram64_size_mb", PCIQXLDevice, vram_size_mb, -1),
+ DEFINE_PROP_UINT32("vgamem_mb", PCIQXLDevice, vgamem_size_mb, 16),
+ DEFINE_PROP_INT32("surfaces", PCIQXLDevice, ssd.num_surfaces, 1024),
+#if SPICE_SERVER_VERSION >= 0x000c06 /* release 0.12.6 */
+ DEFINE_PROP_UINT16("max_outputs", PCIQXLDevice, max_outputs, 0),
+#endif
+ DEFINE_PROP_UINT32("xres", PCIQXLDevice, xres, 0),
+ DEFINE_PROP_UINT32("yres", PCIQXLDevice, yres, 0),
+ DEFINE_PROP_BOOL("global-vmstate", PCIQXLDevice, vga.global_vmstate, false),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void qxl_pci_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->vendor_id = REDHAT_PCI_VENDOR_ID;
+ k->device_id = QXL_DEVICE_ID_STABLE;
+ set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories);
+ dc->reset = qxl_reset_handler;
+ dc->vmsd = &qxl_vmstate;
+ device_class_set_props(dc, qxl_properties);
+}
+
+static const TypeInfo qxl_pci_type_info = {
+ .name = TYPE_PCI_QXL,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(PCIQXLDevice),
+ .abstract = true,
+ .class_init = qxl_pci_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { INTERFACE_CONVENTIONAL_PCI_DEVICE },
+ { },
+ },
+};
+
+static void qxl_primary_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = qxl_realize_primary;
+ k->romfile = "vgabios-qxl.bin";
+ k->class_id = PCI_CLASS_DISPLAY_VGA;
+ dc->desc = "Spice QXL GPU (primary, vga compatible)";
+ dc->hotpluggable = false;
+}
+
+static const TypeInfo qxl_primary_info = {
+ .name = "qxl-vga",
+ .parent = TYPE_PCI_QXL,
+ .class_init = qxl_primary_class_init,
+};
+module_obj("qxl-vga");
+
+static void qxl_secondary_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = qxl_realize_secondary;
+ k->class_id = PCI_CLASS_DISPLAY_OTHER;
+ dc->desc = "Spice QXL GPU (secondary)";
+}
+
+static const TypeInfo qxl_secondary_info = {
+ .name = "qxl",
+ .parent = TYPE_PCI_QXL,
+ .class_init = qxl_secondary_class_init,
+};
+module_obj("qxl");
+
+static void qxl_register_types(void)
+{
+ type_register_static(&qxl_pci_type_info);
+ type_register_static(&qxl_primary_info);
+ type_register_static(&qxl_secondary_info);
+}
+
+type_init(qxl_register_types)
+
+module_dep("ui-spice-core");
diff --git a/hw/display/qxl.h b/hw/display/qxl.h
new file mode 100644
index 000000000..30d21f4d0
--- /dev/null
+++ b/hw/display/qxl.h
@@ -0,0 +1,177 @@
+#ifndef HW_QXL_H
+#define HW_QXL_H
+
+
+#include "hw/pci/pci.h"
+#include "vga_int.h"
+#include "qemu/thread.h"
+
+#include "ui/qemu-spice.h"
+#include "ui/spice-display.h"
+#include "qom/object.h"
+
+enum qxl_mode {
+ QXL_MODE_UNDEFINED,
+ QXL_MODE_VGA,
+ QXL_MODE_COMPAT, /* spice 0.4.x */
+ QXL_MODE_NATIVE,
+};
+
+#ifndef QXL_VRAM64_RANGE_INDEX
+#define QXL_VRAM64_RANGE_INDEX 4
+#endif
+
+#define QXL_UNDEFINED_IO UINT32_MAX
+
+#define QXL_NUM_DIRTY_RECTS 64
+
+#define QXL_PAGE_BITS 12
+#define QXL_PAGE_SIZE (1 << QXL_PAGE_BITS);
+
+struct PCIQXLDevice {
+ PCIDevice pci;
+ PortioList vga_port_list;
+ SimpleSpiceDisplay ssd;
+ int id;
+ bool have_vga;
+ uint32_t debug;
+ uint32_t guestdebug;
+ uint32_t cmdlog;
+
+ uint32_t guest_bug;
+
+ enum qxl_mode mode;
+ uint32_t cmdflags;
+ uint32_t revision;
+
+ int32_t num_memslots;
+
+ uint32_t current_async;
+ QemuMutex async_lock;
+
+ struct guest_slots {
+ QXLMemSlot slot;
+ MemoryRegion *mr;
+ uint64_t offset;
+ uint64_t size;
+ uint64_t delta;
+ uint32_t active;
+ } guest_slots[NUM_MEMSLOTS];
+
+ struct guest_primary {
+ QXLSurfaceCreate surface;
+ uint32_t commands;
+ uint32_t resized;
+ int32_t qxl_stride;
+ uint32_t abs_stride;
+ uint32_t bits_pp;
+ uint32_t bytes_pp;
+ uint8_t *data;
+ } guest_primary;
+
+ struct surfaces {
+ QXLPHYSICAL *cmds;
+ uint32_t count;
+ uint32_t max;
+ } guest_surfaces;
+ QXLPHYSICAL guest_cursor;
+
+ QXLPHYSICAL guest_monitors_config;
+ uint32_t guest_head0_width;
+ uint32_t guest_head0_height;
+
+ QemuMutex track_lock;
+
+ /* thread signaling */
+ QEMUBH *update_irq;
+
+ /* ram pci bar */
+ QXLRam *ram;
+ VGACommonState vga;
+ uint32_t num_free_res;
+ QXLReleaseInfo *last_release;
+ uint32_t last_release_offset;
+ uint32_t oom_running;
+ uint32_t vgamem_size;
+
+ /* rom pci bar */
+ QXLRom shadow_rom;
+ QXLRom *rom;
+ QXLModes *modes;
+ uint32_t rom_size;
+ MemoryRegion rom_bar;
+#if SPICE_SERVER_VERSION >= 0x000c06 /* release 0.12.6 */
+ uint16_t max_outputs;
+#endif
+
+ /* vram pci bar */
+ uint64_t vram_size;
+ MemoryRegion vram_bar;
+ uint64_t vram32_size;
+ MemoryRegion vram32_bar;
+
+ /* io bar */
+ MemoryRegion io_bar;
+
+ /* user-friendly properties (in megabytes) */
+ uint32_t ram_size_mb;
+ uint32_t vram_size_mb;
+ uint32_t vram32_size_mb;
+ uint32_t vgamem_size_mb;
+ uint32_t xres;
+ uint32_t yres;
+
+ /* qxl_render_update state */
+ int render_update_cookie_num;
+ int num_dirty_rects;
+ QXLRect dirty[QXL_NUM_DIRTY_RECTS];
+ QEMUBH *update_area_bh;
+};
+
+#define TYPE_PCI_QXL "pci-qxl"
+OBJECT_DECLARE_SIMPLE_TYPE(PCIQXLDevice, PCI_QXL)
+
+#define PANIC_ON(x) if ((x)) { \
+ printf("%s: PANIC %s failed\n", __func__, #x); \
+ abort(); \
+}
+
+#define dprint(_qxl, _level, _fmt, ...) \
+ do { \
+ if (_qxl->debug >= _level) { \
+ fprintf(stderr, "qxl-%d: ", _qxl->id); \
+ fprintf(stderr, _fmt, ## __VA_ARGS__); \
+ } \
+ } while (0)
+
+#define QXL_DEFAULT_REVISION (QXL_REVISION_STABLE_V12 + 1)
+
+/* qxl.c */
+void *qxl_phys2virt(PCIQXLDevice *qxl, QXLPHYSICAL phys, int group_id);
+void qxl_set_guest_bug(PCIQXLDevice *qxl, const char *msg, ...)
+ GCC_FMT_ATTR(2, 3);
+
+void qxl_spice_update_area(PCIQXLDevice *qxl, uint32_t surface_id,
+ struct QXLRect *area, struct QXLRect *dirty_rects,
+ uint32_t num_dirty_rects,
+ uint32_t clear_dirty_region,
+ qxl_async_io async, QXLCookie *cookie);
+void qxl_spice_loadvm_commands(PCIQXLDevice *qxl, struct QXLCommandExt *ext,
+ uint32_t count);
+void qxl_spice_oom(PCIQXLDevice *qxl);
+void qxl_spice_reset_memslots(PCIQXLDevice *qxl);
+void qxl_spice_reset_image_cache(PCIQXLDevice *qxl);
+void qxl_spice_reset_cursor(PCIQXLDevice *qxl);
+
+/* qxl-logger.c */
+int qxl_log_cmd_cursor(PCIQXLDevice *qxl, QXLCursorCmd *cmd, int group_id);
+int qxl_log_command(PCIQXLDevice *qxl, const char *ring, QXLCommandExt *ext);
+
+/* qxl-render.c */
+void qxl_render_resize(PCIQXLDevice *qxl);
+void qxl_render_update(PCIQXLDevice *qxl);
+int qxl_render_cursor(PCIQXLDevice *qxl, QXLCommandExt *ext);
+void qxl_render_update_area_done(PCIQXLDevice *qxl, QXLCookie *cookie);
+void qxl_render_update_area_bh(void *opaque);
+
+#endif
diff --git a/hw/display/ramfb-standalone.c b/hw/display/ramfb-standalone.c
new file mode 100644
index 000000000..8c0094397
--- /dev/null
+++ b/hw/display/ramfb-standalone.c
@@ -0,0 +1,65 @@
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "qemu/module.h"
+#include "hw/loader.h"
+#include "hw/qdev-properties.h"
+#include "hw/display/ramfb.h"
+#include "ui/console.h"
+#include "qom/object.h"
+
+typedef struct RAMFBStandaloneState RAMFBStandaloneState;
+DECLARE_INSTANCE_CHECKER(RAMFBStandaloneState, RAMFB,
+ TYPE_RAMFB_DEVICE)
+
+struct RAMFBStandaloneState {
+ SysBusDevice parent_obj;
+ QemuConsole *con;
+ RAMFBState *state;
+};
+
+static void display_update_wrapper(void *dev)
+{
+ RAMFBStandaloneState *ramfb = RAMFB(dev);
+
+ if (0 /* native driver active */) {
+ /* non-standalone device would run native display update here */;
+ } else {
+ ramfb_display_update(ramfb->con, ramfb->state);
+ }
+}
+
+static const GraphicHwOps wrapper_ops = {
+ .gfx_update = display_update_wrapper,
+};
+
+static void ramfb_realizefn(DeviceState *dev, Error **errp)
+{
+ RAMFBStandaloneState *ramfb = RAMFB(dev);
+
+ ramfb->con = graphic_console_init(dev, 0, &wrapper_ops, dev);
+ ramfb->state = ramfb_setup(errp);
+}
+
+static void ramfb_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories);
+ dc->realize = ramfb_realizefn;
+ dc->desc = "ram framebuffer standalone device";
+ dc->user_creatable = true;
+}
+
+static const TypeInfo ramfb_info = {
+ .name = TYPE_RAMFB_DEVICE,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(RAMFBStandaloneState),
+ .class_init = ramfb_class_initfn,
+};
+
+static void ramfb_register_types(void)
+{
+ type_register_static(&ramfb_info);
+}
+
+type_init(ramfb_register_types)
diff --git a/hw/display/ramfb.c b/hw/display/ramfb.c
new file mode 100644
index 000000000..79b9754a5
--- /dev/null
+++ b/hw/display/ramfb.c
@@ -0,0 +1,135 @@
+/*
+ * early boot framebuffer in guest ram
+ * configured using fw_cfg
+ *
+ * Copyright Red Hat, Inc. 2017
+ *
+ * Author:
+ * Gerd Hoffmann <kraxel@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "hw/loader.h"
+#include "hw/display/ramfb.h"
+#include "hw/display/bochs-vbe.h" /* for limits */
+#include "ui/console.h"
+#include "sysemu/reset.h"
+
+struct QEMU_PACKED RAMFBCfg {
+ uint64_t addr;
+ uint32_t fourcc;
+ uint32_t flags;
+ uint32_t width;
+ uint32_t height;
+ uint32_t stride;
+};
+
+struct RAMFBState {
+ DisplaySurface *ds;
+ uint32_t width, height;
+ struct RAMFBCfg cfg;
+};
+
+static void ramfb_unmap_display_surface(pixman_image_t *image, void *unused)
+{
+ void *data = pixman_image_get_data(image);
+ uint32_t size = pixman_image_get_stride(image) *
+ pixman_image_get_height(image);
+ cpu_physical_memory_unmap(data, size, 0, 0);
+}
+
+static DisplaySurface *ramfb_create_display_surface(int width, int height,
+ pixman_format_code_t format,
+ hwaddr stride, hwaddr addr)
+{
+ DisplaySurface *surface;
+ hwaddr size, mapsize, linesize;
+ void *data;
+
+ if (width < 16 || width > VBE_DISPI_MAX_XRES ||
+ height < 16 || height > VBE_DISPI_MAX_YRES ||
+ format == 0 /* unknown format */)
+ return NULL;
+
+ linesize = width * PIXMAN_FORMAT_BPP(format) / 8;
+ if (stride == 0) {
+ stride = linesize;
+ }
+
+ mapsize = size = stride * (height - 1) + linesize;
+ data = cpu_physical_memory_map(addr, &mapsize, false);
+ if (size != mapsize) {
+ cpu_physical_memory_unmap(data, mapsize, 0, 0);
+ return NULL;
+ }
+
+ surface = qemu_create_displaysurface_from(width, height,
+ format, stride, data);
+ pixman_image_set_destroy_function(surface->image,
+ ramfb_unmap_display_surface, NULL);
+
+ return surface;
+}
+
+static void ramfb_fw_cfg_write(void *dev, off_t offset, size_t len)
+{
+ RAMFBState *s = dev;
+ DisplaySurface *surface;
+ uint32_t fourcc, format, width, height;
+ hwaddr stride, addr;
+
+ width = be32_to_cpu(s->cfg.width);
+ height = be32_to_cpu(s->cfg.height);
+ stride = be32_to_cpu(s->cfg.stride);
+ fourcc = be32_to_cpu(s->cfg.fourcc);
+ addr = be64_to_cpu(s->cfg.addr);
+ format = qemu_drm_format_to_pixman(fourcc);
+
+ surface = ramfb_create_display_surface(width, height,
+ format, stride, addr);
+ if (!surface) {
+ return;
+ }
+
+ s->width = width;
+ s->height = height;
+ s->ds = surface;
+}
+
+void ramfb_display_update(QemuConsole *con, RAMFBState *s)
+{
+ if (!s->width || !s->height) {
+ return;
+ }
+
+ if (s->ds) {
+ dpy_gfx_replace_surface(con, s->ds);
+ s->ds = NULL;
+ }
+
+ /* simple full screen update */
+ dpy_gfx_update_full(con);
+}
+
+RAMFBState *ramfb_setup(Error **errp)
+{
+ FWCfgState *fw_cfg = fw_cfg_find();
+ RAMFBState *s;
+
+ if (!fw_cfg || !fw_cfg->dma_enabled) {
+ error_setg(errp, "ramfb device requires fw_cfg with DMA");
+ return NULL;
+ }
+
+ s = g_new0(RAMFBState, 1);
+
+ rom_add_vga("vgabios-ramfb.bin");
+ fw_cfg_add_file_callback(fw_cfg, "etc/ramfb",
+ NULL, ramfb_fw_cfg_write, s,
+ &s->cfg, sizeof(s->cfg), false);
+ return s;
+}
diff --git a/hw/display/sii9022.c b/hw/display/sii9022.c
new file mode 100644
index 000000000..b591a5878
--- /dev/null
+++ b/hw/display/sii9022.c
@@ -0,0 +1,193 @@
+/*
+ * Silicon Image SiI9022
+ *
+ * This is a pretty hollow emulation: all we do is acknowledge that we
+ * exist (chip ID) and confirm that we get switched over into DDC mode
+ * so the emulated host can proceed to read out EDID data. All subsequent
+ * set-up of connectors etc will be acknowledged and ignored.
+ *
+ * Copyright (C) 2018 Linus Walleij
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/module.h"
+#include "hw/i2c/i2c.h"
+#include "migration/vmstate.h"
+#include "hw/display/i2c-ddc.h"
+#include "trace.h"
+#include "qom/object.h"
+
+#define SII9022_SYS_CTRL_DATA 0x1a
+#define SII9022_SYS_CTRL_PWR_DWN 0x10
+#define SII9022_SYS_CTRL_AV_MUTE 0x08
+#define SII9022_SYS_CTRL_DDC_BUS_REQ 0x04
+#define SII9022_SYS_CTRL_DDC_BUS_GRTD 0x02
+#define SII9022_SYS_CTRL_OUTPUT_MODE 0x01
+#define SII9022_SYS_CTRL_OUTPUT_HDMI 1
+#define SII9022_SYS_CTRL_OUTPUT_DVI 0
+#define SII9022_REG_CHIPID 0x1b
+#define SII9022_INT_ENABLE 0x3c
+#define SII9022_INT_STATUS 0x3d
+#define SII9022_INT_STATUS_HOTPLUG 0x01;
+#define SII9022_INT_STATUS_PLUGGED 0x04;
+
+#define TYPE_SII9022 "sii9022"
+OBJECT_DECLARE_SIMPLE_TYPE(sii9022_state, SII9022)
+
+struct sii9022_state {
+ I2CSlave parent_obj;
+ uint8_t ptr;
+ bool addr_byte;
+ bool ddc_req;
+ bool ddc_skip_finish;
+ bool ddc;
+};
+
+static const VMStateDescription vmstate_sii9022 = {
+ .name = "sii9022",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_I2C_SLAVE(parent_obj, sii9022_state),
+ VMSTATE_UINT8(ptr, sii9022_state),
+ VMSTATE_BOOL(addr_byte, sii9022_state),
+ VMSTATE_BOOL(ddc_req, sii9022_state),
+ VMSTATE_BOOL(ddc_skip_finish, sii9022_state),
+ VMSTATE_BOOL(ddc, sii9022_state),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static int sii9022_event(I2CSlave *i2c, enum i2c_event event)
+{
+ sii9022_state *s = SII9022(i2c);
+
+ switch (event) {
+ case I2C_START_SEND:
+ s->addr_byte = true;
+ break;
+ case I2C_START_RECV:
+ break;
+ case I2C_FINISH:
+ break;
+ case I2C_NACK:
+ break;
+ }
+
+ return 0;
+}
+
+static uint8_t sii9022_rx(I2CSlave *i2c)
+{
+ sii9022_state *s = SII9022(i2c);
+ uint8_t res = 0x00;
+
+ switch (s->ptr) {
+ case SII9022_SYS_CTRL_DATA:
+ if (s->ddc_req) {
+ /* Acknowledge DDC bus request */
+ res = SII9022_SYS_CTRL_DDC_BUS_GRTD | SII9022_SYS_CTRL_DDC_BUS_REQ;
+ }
+ break;
+ case SII9022_REG_CHIPID:
+ res = 0xb0;
+ break;
+ case SII9022_INT_STATUS:
+ /* Something is cold-plugged in, no interrupts */
+ res = SII9022_INT_STATUS_PLUGGED;
+ break;
+ default:
+ break;
+ }
+
+ trace_sii9022_read_reg(s->ptr, res);
+ s->ptr++;
+
+ return res;
+}
+
+static int sii9022_tx(I2CSlave *i2c, uint8_t data)
+{
+ sii9022_state *s = SII9022(i2c);
+
+ if (s->addr_byte) {
+ s->ptr = data;
+ s->addr_byte = false;
+ return 0;
+ }
+
+ switch (s->ptr) {
+ case SII9022_SYS_CTRL_DATA:
+ if (data & SII9022_SYS_CTRL_DDC_BUS_REQ) {
+ s->ddc_req = true;
+ if (data & SII9022_SYS_CTRL_DDC_BUS_GRTD) {
+ s->ddc = true;
+ /* Skip this finish since we just switched to DDC */
+ s->ddc_skip_finish = true;
+ trace_sii9022_switch_mode("DDC");
+ }
+ } else {
+ s->ddc_req = false;
+ s->ddc = false;
+ trace_sii9022_switch_mode("normal");
+ }
+ break;
+ default:
+ break;
+ }
+
+ trace_sii9022_write_reg(s->ptr, data);
+ s->ptr++;
+
+ return 0;
+}
+
+static void sii9022_reset(DeviceState *dev)
+{
+ sii9022_state *s = SII9022(dev);
+
+ s->ptr = 0;
+ s->addr_byte = false;
+ s->ddc_req = false;
+ s->ddc_skip_finish = false;
+ s->ddc = false;
+}
+
+static void sii9022_realize(DeviceState *dev, Error **errp)
+{
+ I2CBus *bus;
+
+ bus = I2C_BUS(qdev_get_parent_bus(dev));
+ i2c_slave_create_simple(bus, TYPE_I2CDDC, 0x50);
+}
+
+static void sii9022_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
+
+ k->event = sii9022_event;
+ k->recv = sii9022_rx;
+ k->send = sii9022_tx;
+ dc->reset = sii9022_reset;
+ dc->realize = sii9022_realize;
+ dc->vmsd = &vmstate_sii9022;
+}
+
+static const TypeInfo sii9022_info = {
+ .name = TYPE_SII9022,
+ .parent = TYPE_I2C_SLAVE,
+ .instance_size = sizeof(sii9022_state),
+ .class_init = sii9022_class_init,
+};
+
+static void sii9022_register_types(void)
+{
+ type_register_static(&sii9022_info);
+}
+
+type_init(sii9022_register_types)
diff --git a/hw/display/sm501.c b/hw/display/sm501.c
new file mode 100644
index 000000000..663c37e7f
--- /dev/null
+++ b/hw/display/sm501.c
@@ -0,0 +1,2124 @@
+/*
+ * QEMU SM501 Device
+ *
+ * Copyright (c) 2008 Shin-ichiro KAWASAKI
+ * Copyright (c) 2016-2020 BALATON Zoltan
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/units.h"
+#include "qapi/error.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "hw/char/serial.h"
+#include "ui/console.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "hw/pci/pci.h"
+#include "hw/qdev-properties.h"
+#include "hw/i2c/i2c.h"
+#include "hw/display/i2c-ddc.h"
+#include "qemu/range.h"
+#include "ui/pixel_ops.h"
+#include "qemu/bswap.h"
+#include "trace.h"
+#include "qom/object.h"
+
+#define MMIO_BASE_OFFSET 0x3e00000
+#define MMIO_SIZE 0x200000
+#define DC_PALETTE_ENTRIES (0x400 * 3)
+
+/* SM501 register definitions taken from "linux/include/linux/sm501-regs.h" */
+
+/* System Configuration area */
+/* System config base */
+#define SM501_SYS_CONFIG (0x000000)
+
+/* config 1 */
+#define SM501_SYSTEM_CONTROL (0x000000)
+
+#define SM501_SYSCTRL_PANEL_TRISTATE (1 << 0)
+#define SM501_SYSCTRL_MEM_TRISTATE (1 << 1)
+#define SM501_SYSCTRL_CRT_TRISTATE (1 << 2)
+
+#define SM501_SYSCTRL_PCI_SLAVE_BURST_MASK (3 << 4)
+#define SM501_SYSCTRL_PCI_SLAVE_BURST_1 (0 << 4)
+#define SM501_SYSCTRL_PCI_SLAVE_BURST_2 (1 << 4)
+#define SM501_SYSCTRL_PCI_SLAVE_BURST_4 (2 << 4)
+#define SM501_SYSCTRL_PCI_SLAVE_BURST_8 (3 << 4)
+
+#define SM501_SYSCTRL_PCI_CLOCK_RUN_EN (1 << 6)
+#define SM501_SYSCTRL_PCI_RETRY_DISABLE (1 << 7)
+#define SM501_SYSCTRL_PCI_SUBSYS_LOCK (1 << 11)
+#define SM501_SYSCTRL_PCI_BURST_READ_EN (1 << 15)
+
+/* miscellaneous control */
+
+#define SM501_MISC_CONTROL (0x000004)
+
+#define SM501_MISC_BUS_SH (0x0)
+#define SM501_MISC_BUS_PCI (0x1)
+#define SM501_MISC_BUS_XSCALE (0x2)
+#define SM501_MISC_BUS_NEC (0x6)
+#define SM501_MISC_BUS_MASK (0x7)
+
+#define SM501_MISC_VR_62MB (1 << 3)
+#define SM501_MISC_CDR_RESET (1 << 7)
+#define SM501_MISC_USB_LB (1 << 8)
+#define SM501_MISC_USB_SLAVE (1 << 9)
+#define SM501_MISC_BL_1 (1 << 10)
+#define SM501_MISC_MC (1 << 11)
+#define SM501_MISC_DAC_POWER (1 << 12)
+#define SM501_MISC_IRQ_INVERT (1 << 16)
+#define SM501_MISC_SH (1 << 17)
+
+#define SM501_MISC_HOLD_EMPTY (0 << 18)
+#define SM501_MISC_HOLD_8 (1 << 18)
+#define SM501_MISC_HOLD_16 (2 << 18)
+#define SM501_MISC_HOLD_24 (3 << 18)
+#define SM501_MISC_HOLD_32 (4 << 18)
+#define SM501_MISC_HOLD_MASK (7 << 18)
+
+#define SM501_MISC_FREQ_12 (1 << 24)
+#define SM501_MISC_PNL_24BIT (1 << 25)
+#define SM501_MISC_8051_LE (1 << 26)
+
+
+
+#define SM501_GPIO31_0_CONTROL (0x000008)
+#define SM501_GPIO63_32_CONTROL (0x00000C)
+#define SM501_DRAM_CONTROL (0x000010)
+
+/* command list */
+#define SM501_ARBTRTN_CONTROL (0x000014)
+
+/* command list */
+#define SM501_COMMAND_LIST_STATUS (0x000024)
+
+/* interrupt debug */
+#define SM501_RAW_IRQ_STATUS (0x000028)
+#define SM501_RAW_IRQ_CLEAR (0x000028)
+#define SM501_IRQ_STATUS (0x00002C)
+#define SM501_IRQ_MASK (0x000030)
+#define SM501_DEBUG_CONTROL (0x000034)
+
+/* power management */
+#define SM501_POWERMODE_P2X_SRC (1 << 29)
+#define SM501_POWERMODE_V2X_SRC (1 << 20)
+#define SM501_POWERMODE_M_SRC (1 << 12)
+#define SM501_POWERMODE_M1_SRC (1 << 4)
+
+#define SM501_CURRENT_GATE (0x000038)
+#define SM501_CURRENT_CLOCK (0x00003C)
+#define SM501_POWER_MODE_0_GATE (0x000040)
+#define SM501_POWER_MODE_0_CLOCK (0x000044)
+#define SM501_POWER_MODE_1_GATE (0x000048)
+#define SM501_POWER_MODE_1_CLOCK (0x00004C)
+#define SM501_SLEEP_MODE_GATE (0x000050)
+#define SM501_POWER_MODE_CONTROL (0x000054)
+
+/* power gates for units within the 501 */
+#define SM501_GATE_HOST (0)
+#define SM501_GATE_MEMORY (1)
+#define SM501_GATE_DISPLAY (2)
+#define SM501_GATE_2D_ENGINE (3)
+#define SM501_GATE_CSC (4)
+#define SM501_GATE_ZVPORT (5)
+#define SM501_GATE_GPIO (6)
+#define SM501_GATE_UART0 (7)
+#define SM501_GATE_UART1 (8)
+#define SM501_GATE_SSP (10)
+#define SM501_GATE_USB_HOST (11)
+#define SM501_GATE_USB_GADGET (12)
+#define SM501_GATE_UCONTROLLER (17)
+#define SM501_GATE_AC97 (18)
+
+/* panel clock */
+#define SM501_CLOCK_P2XCLK (24)
+/* crt clock */
+#define SM501_CLOCK_V2XCLK (16)
+/* main clock */
+#define SM501_CLOCK_MCLK (8)
+/* SDRAM controller clock */
+#define SM501_CLOCK_M1XCLK (0)
+
+/* config 2 */
+#define SM501_PCI_MASTER_BASE (0x000058)
+#define SM501_ENDIAN_CONTROL (0x00005C)
+#define SM501_DEVICEID (0x000060)
+/* 0x050100A0 */
+
+#define SM501_DEVICEID_SM501 (0x05010000)
+#define SM501_DEVICEID_IDMASK (0xffff0000)
+#define SM501_DEVICEID_REVMASK (0x000000ff)
+
+#define SM501_PLLCLOCK_COUNT (0x000064)
+#define SM501_MISC_TIMING (0x000068)
+#define SM501_CURRENT_SDRAM_CLOCK (0x00006C)
+
+#define SM501_PROGRAMMABLE_PLL_CONTROL (0x000074)
+
+/* GPIO base */
+#define SM501_GPIO (0x010000)
+#define SM501_GPIO_DATA_LOW (0x00)
+#define SM501_GPIO_DATA_HIGH (0x04)
+#define SM501_GPIO_DDR_LOW (0x08)
+#define SM501_GPIO_DDR_HIGH (0x0C)
+#define SM501_GPIO_IRQ_SETUP (0x10)
+#define SM501_GPIO_IRQ_STATUS (0x14)
+#define SM501_GPIO_IRQ_RESET (0x14)
+
+/* I2C controller base */
+#define SM501_I2C (0x010040)
+#define SM501_I2C_BYTE_COUNT (0x00)
+#define SM501_I2C_CONTROL (0x01)
+#define SM501_I2C_STATUS (0x02)
+#define SM501_I2C_RESET (0x02)
+#define SM501_I2C_SLAVE_ADDRESS (0x03)
+#define SM501_I2C_DATA (0x04)
+
+#define SM501_I2C_CONTROL_START (1 << 2)
+#define SM501_I2C_CONTROL_ENABLE (1 << 0)
+
+#define SM501_I2C_STATUS_COMPLETE (1 << 3)
+#define SM501_I2C_STATUS_ERROR (1 << 2)
+
+#define SM501_I2C_RESET_ERROR (1 << 2)
+
+/* SSP base */
+#define SM501_SSP (0x020000)
+
+/* Uart 0 base */
+#define SM501_UART0 (0x030000)
+
+/* Uart 1 base */
+#define SM501_UART1 (0x030020)
+
+/* USB host port base */
+#define SM501_USB_HOST (0x040000)
+
+/* USB slave/gadget base */
+#define SM501_USB_GADGET (0x060000)
+
+/* USB slave/gadget data port base */
+#define SM501_USB_GADGET_DATA (0x070000)
+
+/* Display controller/video engine base */
+#define SM501_DC (0x080000)
+
+/* common defines for the SM501 address registers */
+#define SM501_ADDR_FLIP (1 << 31)
+#define SM501_ADDR_EXT (1 << 27)
+#define SM501_ADDR_CS1 (1 << 26)
+#define SM501_ADDR_MASK (0x3f << 26)
+
+#define SM501_FIFO_MASK (0x3 << 16)
+#define SM501_FIFO_1 (0x0 << 16)
+#define SM501_FIFO_3 (0x1 << 16)
+#define SM501_FIFO_7 (0x2 << 16)
+#define SM501_FIFO_11 (0x3 << 16)
+
+/* common registers for panel and the crt */
+#define SM501_OFF_DC_H_TOT (0x000)
+#define SM501_OFF_DC_V_TOT (0x008)
+#define SM501_OFF_DC_H_SYNC (0x004)
+#define SM501_OFF_DC_V_SYNC (0x00C)
+
+#define SM501_DC_PANEL_CONTROL (0x000)
+
+#define SM501_DC_PANEL_CONTROL_FPEN (1 << 27)
+#define SM501_DC_PANEL_CONTROL_BIAS (1 << 26)
+#define SM501_DC_PANEL_CONTROL_DATA (1 << 25)
+#define SM501_DC_PANEL_CONTROL_VDD (1 << 24)
+#define SM501_DC_PANEL_CONTROL_DP (1 << 23)
+
+#define SM501_DC_PANEL_CONTROL_TFT_888 (0 << 21)
+#define SM501_DC_PANEL_CONTROL_TFT_333 (1 << 21)
+#define SM501_DC_PANEL_CONTROL_TFT_444 (2 << 21)
+
+#define SM501_DC_PANEL_CONTROL_DE (1 << 20)
+
+#define SM501_DC_PANEL_CONTROL_LCD_TFT (0 << 18)
+#define SM501_DC_PANEL_CONTROL_LCD_STN8 (1 << 18)
+#define SM501_DC_PANEL_CONTROL_LCD_STN12 (2 << 18)
+
+#define SM501_DC_PANEL_CONTROL_CP (1 << 14)
+#define SM501_DC_PANEL_CONTROL_VSP (1 << 13)
+#define SM501_DC_PANEL_CONTROL_HSP (1 << 12)
+#define SM501_DC_PANEL_CONTROL_CK (1 << 9)
+#define SM501_DC_PANEL_CONTROL_TE (1 << 8)
+#define SM501_DC_PANEL_CONTROL_VPD (1 << 7)
+#define SM501_DC_PANEL_CONTROL_VP (1 << 6)
+#define SM501_DC_PANEL_CONTROL_HPD (1 << 5)
+#define SM501_DC_PANEL_CONTROL_HP (1 << 4)
+#define SM501_DC_PANEL_CONTROL_GAMMA (1 << 3)
+#define SM501_DC_PANEL_CONTROL_EN (1 << 2)
+
+#define SM501_DC_PANEL_CONTROL_8BPP (0 << 0)
+#define SM501_DC_PANEL_CONTROL_16BPP (1 << 0)
+#define SM501_DC_PANEL_CONTROL_32BPP (2 << 0)
+
+
+#define SM501_DC_PANEL_PANNING_CONTROL (0x004)
+#define SM501_DC_PANEL_COLOR_KEY (0x008)
+#define SM501_DC_PANEL_FB_ADDR (0x00C)
+#define SM501_DC_PANEL_FB_OFFSET (0x010)
+#define SM501_DC_PANEL_FB_WIDTH (0x014)
+#define SM501_DC_PANEL_FB_HEIGHT (0x018)
+#define SM501_DC_PANEL_TL_LOC (0x01C)
+#define SM501_DC_PANEL_BR_LOC (0x020)
+#define SM501_DC_PANEL_H_TOT (0x024)
+#define SM501_DC_PANEL_H_SYNC (0x028)
+#define SM501_DC_PANEL_V_TOT (0x02C)
+#define SM501_DC_PANEL_V_SYNC (0x030)
+#define SM501_DC_PANEL_CUR_LINE (0x034)
+
+#define SM501_DC_VIDEO_CONTROL (0x040)
+#define SM501_DC_VIDEO_FB0_ADDR (0x044)
+#define SM501_DC_VIDEO_FB_WIDTH (0x048)
+#define SM501_DC_VIDEO_FB0_LAST_ADDR (0x04C)
+#define SM501_DC_VIDEO_TL_LOC (0x050)
+#define SM501_DC_VIDEO_BR_LOC (0x054)
+#define SM501_DC_VIDEO_SCALE (0x058)
+#define SM501_DC_VIDEO_INIT_SCALE (0x05C)
+#define SM501_DC_VIDEO_YUV_CONSTANTS (0x060)
+#define SM501_DC_VIDEO_FB1_ADDR (0x064)
+#define SM501_DC_VIDEO_FB1_LAST_ADDR (0x068)
+
+#define SM501_DC_VIDEO_ALPHA_CONTROL (0x080)
+#define SM501_DC_VIDEO_ALPHA_FB_ADDR (0x084)
+#define SM501_DC_VIDEO_ALPHA_FB_OFFSET (0x088)
+#define SM501_DC_VIDEO_ALPHA_FB_LAST_ADDR (0x08C)
+#define SM501_DC_VIDEO_ALPHA_TL_LOC (0x090)
+#define SM501_DC_VIDEO_ALPHA_BR_LOC (0x094)
+#define SM501_DC_VIDEO_ALPHA_SCALE (0x098)
+#define SM501_DC_VIDEO_ALPHA_INIT_SCALE (0x09C)
+#define SM501_DC_VIDEO_ALPHA_CHROMA_KEY (0x0A0)
+#define SM501_DC_VIDEO_ALPHA_COLOR_LOOKUP (0x0A4)
+
+#define SM501_DC_PANEL_HWC_BASE (0x0F0)
+#define SM501_DC_PANEL_HWC_ADDR (0x0F0)
+#define SM501_DC_PANEL_HWC_LOC (0x0F4)
+#define SM501_DC_PANEL_HWC_COLOR_1_2 (0x0F8)
+#define SM501_DC_PANEL_HWC_COLOR_3 (0x0FC)
+
+#define SM501_HWC_EN (1 << 31)
+
+#define SM501_OFF_HWC_ADDR (0x00)
+#define SM501_OFF_HWC_LOC (0x04)
+#define SM501_OFF_HWC_COLOR_1_2 (0x08)
+#define SM501_OFF_HWC_COLOR_3 (0x0C)
+
+#define SM501_DC_ALPHA_CONTROL (0x100)
+#define SM501_DC_ALPHA_FB_ADDR (0x104)
+#define SM501_DC_ALPHA_FB_OFFSET (0x108)
+#define SM501_DC_ALPHA_TL_LOC (0x10C)
+#define SM501_DC_ALPHA_BR_LOC (0x110)
+#define SM501_DC_ALPHA_CHROMA_KEY (0x114)
+#define SM501_DC_ALPHA_COLOR_LOOKUP (0x118)
+
+#define SM501_DC_CRT_CONTROL (0x200)
+
+#define SM501_DC_CRT_CONTROL_TVP (1 << 15)
+#define SM501_DC_CRT_CONTROL_CP (1 << 14)
+#define SM501_DC_CRT_CONTROL_VSP (1 << 13)
+#define SM501_DC_CRT_CONTROL_HSP (1 << 12)
+#define SM501_DC_CRT_CONTROL_VS (1 << 11)
+#define SM501_DC_CRT_CONTROL_BLANK (1 << 10)
+#define SM501_DC_CRT_CONTROL_SEL (1 << 9)
+#define SM501_DC_CRT_CONTROL_TE (1 << 8)
+#define SM501_DC_CRT_CONTROL_PIXEL_MASK (0xF << 4)
+#define SM501_DC_CRT_CONTROL_GAMMA (1 << 3)
+#define SM501_DC_CRT_CONTROL_ENABLE (1 << 2)
+
+#define SM501_DC_CRT_CONTROL_8BPP (0 << 0)
+#define SM501_DC_CRT_CONTROL_16BPP (1 << 0)
+#define SM501_DC_CRT_CONTROL_32BPP (2 << 0)
+
+#define SM501_DC_CRT_FB_ADDR (0x204)
+#define SM501_DC_CRT_FB_OFFSET (0x208)
+#define SM501_DC_CRT_H_TOT (0x20C)
+#define SM501_DC_CRT_H_SYNC (0x210)
+#define SM501_DC_CRT_V_TOT (0x214)
+#define SM501_DC_CRT_V_SYNC (0x218)
+#define SM501_DC_CRT_SIGNATURE_ANALYZER (0x21C)
+#define SM501_DC_CRT_CUR_LINE (0x220)
+#define SM501_DC_CRT_MONITOR_DETECT (0x224)
+
+#define SM501_DC_CRT_HWC_BASE (0x230)
+#define SM501_DC_CRT_HWC_ADDR (0x230)
+#define SM501_DC_CRT_HWC_LOC (0x234)
+#define SM501_DC_CRT_HWC_COLOR_1_2 (0x238)
+#define SM501_DC_CRT_HWC_COLOR_3 (0x23C)
+
+#define SM501_DC_PANEL_PALETTE (0x400)
+
+#define SM501_DC_VIDEO_PALETTE (0x800)
+
+#define SM501_DC_CRT_PALETTE (0xC00)
+
+/* Zoom Video port base */
+#define SM501_ZVPORT (0x090000)
+
+/* AC97/I2S base */
+#define SM501_AC97 (0x0A0000)
+
+/* 8051 micro controller base */
+#define SM501_UCONTROLLER (0x0B0000)
+
+/* 8051 micro controller SRAM base */
+#define SM501_UCONTROLLER_SRAM (0x0C0000)
+
+/* DMA base */
+#define SM501_DMA (0x0D0000)
+
+/* 2d engine base */
+#define SM501_2D_ENGINE (0x100000)
+#define SM501_2D_SOURCE (0x00)
+#define SM501_2D_DESTINATION (0x04)
+#define SM501_2D_DIMENSION (0x08)
+#define SM501_2D_CONTROL (0x0C)
+#define SM501_2D_PITCH (0x10)
+#define SM501_2D_FOREGROUND (0x14)
+#define SM501_2D_BACKGROUND (0x18)
+#define SM501_2D_STRETCH (0x1C)
+#define SM501_2D_COLOR_COMPARE (0x20)
+#define SM501_2D_COLOR_COMPARE_MASK (0x24)
+#define SM501_2D_MASK (0x28)
+#define SM501_2D_CLIP_TL (0x2C)
+#define SM501_2D_CLIP_BR (0x30)
+#define SM501_2D_MONO_PATTERN_LOW (0x34)
+#define SM501_2D_MONO_PATTERN_HIGH (0x38)
+#define SM501_2D_WINDOW_WIDTH (0x3C)
+#define SM501_2D_SOURCE_BASE (0x40)
+#define SM501_2D_DESTINATION_BASE (0x44)
+#define SM501_2D_ALPHA (0x48)
+#define SM501_2D_WRAP (0x4C)
+#define SM501_2D_STATUS (0x50)
+
+#define SM501_CSC_Y_SOURCE_BASE (0xC8)
+#define SM501_CSC_CONSTANTS (0xCC)
+#define SM501_CSC_Y_SOURCE_X (0xD0)
+#define SM501_CSC_Y_SOURCE_Y (0xD4)
+#define SM501_CSC_U_SOURCE_BASE (0xD8)
+#define SM501_CSC_V_SOURCE_BASE (0xDC)
+#define SM501_CSC_SOURCE_DIMENSION (0xE0)
+#define SM501_CSC_SOURCE_PITCH (0xE4)
+#define SM501_CSC_DESTINATION (0xE8)
+#define SM501_CSC_DESTINATION_DIMENSION (0xEC)
+#define SM501_CSC_DESTINATION_PITCH (0xF0)
+#define SM501_CSC_SCALE_FACTOR (0xF4)
+#define SM501_CSC_DESTINATION_BASE (0xF8)
+#define SM501_CSC_CONTROL (0xFC)
+
+/* 2d engine data port base */
+#define SM501_2D_ENGINE_DATA (0x110000)
+
+/* end of register definitions */
+
+#define SM501_HWC_WIDTH (64)
+#define SM501_HWC_HEIGHT (64)
+
+/* SM501 local memory size taken from "linux/drivers/mfd/sm501.c" */
+static const uint32_t sm501_mem_local_size[] = {
+ [0] = 4 * MiB,
+ [1] = 8 * MiB,
+ [2] = 16 * MiB,
+ [3] = 32 * MiB,
+ [4] = 64 * MiB,
+ [5] = 2 * MiB,
+};
+#define get_local_mem_size(s) sm501_mem_local_size[(s)->local_mem_size_index]
+
+typedef struct SM501State {
+ /* graphic console status */
+ QemuConsole *con;
+
+ /* status & internal resources */
+ uint32_t local_mem_size_index;
+ uint8_t *local_mem;
+ MemoryRegion local_mem_region;
+ MemoryRegion mmio_region;
+ MemoryRegion system_config_region;
+ MemoryRegion i2c_region;
+ MemoryRegion disp_ctrl_region;
+ MemoryRegion twoD_engine_region;
+ uint32_t last_width;
+ uint32_t last_height;
+ bool do_full_update; /* perform a full update next time */
+ I2CBus *i2c_bus;
+
+ /* mmio registers */
+ uint32_t system_control;
+ uint32_t misc_control;
+ uint32_t gpio_31_0_control;
+ uint32_t gpio_63_32_control;
+ uint32_t dram_control;
+ uint32_t arbitration_control;
+ uint32_t irq_mask;
+ uint32_t misc_timing;
+ uint32_t power_mode_control;
+
+ uint8_t i2c_byte_count;
+ uint8_t i2c_status;
+ uint8_t i2c_addr;
+ uint8_t i2c_data[16];
+
+ uint32_t uart0_ier;
+ uint32_t uart0_lcr;
+ uint32_t uart0_mcr;
+ uint32_t uart0_scr;
+
+ uint8_t dc_palette[DC_PALETTE_ENTRIES];
+
+ uint32_t dc_panel_control;
+ uint32_t dc_panel_panning_control;
+ uint32_t dc_panel_fb_addr;
+ uint32_t dc_panel_fb_offset;
+ uint32_t dc_panel_fb_width;
+ uint32_t dc_panel_fb_height;
+ uint32_t dc_panel_tl_location;
+ uint32_t dc_panel_br_location;
+ uint32_t dc_panel_h_total;
+ uint32_t dc_panel_h_sync;
+ uint32_t dc_panel_v_total;
+ uint32_t dc_panel_v_sync;
+
+ uint32_t dc_panel_hwc_addr;
+ uint32_t dc_panel_hwc_location;
+ uint32_t dc_panel_hwc_color_1_2;
+ uint32_t dc_panel_hwc_color_3;
+
+ uint32_t dc_video_control;
+
+ uint32_t dc_crt_control;
+ uint32_t dc_crt_fb_addr;
+ uint32_t dc_crt_fb_offset;
+ uint32_t dc_crt_h_total;
+ uint32_t dc_crt_h_sync;
+ uint32_t dc_crt_v_total;
+ uint32_t dc_crt_v_sync;
+
+ uint32_t dc_crt_hwc_addr;
+ uint32_t dc_crt_hwc_location;
+ uint32_t dc_crt_hwc_color_1_2;
+ uint32_t dc_crt_hwc_color_3;
+
+ uint32_t twoD_source;
+ uint32_t twoD_destination;
+ uint32_t twoD_dimension;
+ uint32_t twoD_control;
+ uint32_t twoD_pitch;
+ uint32_t twoD_foreground;
+ uint32_t twoD_background;
+ uint32_t twoD_stretch;
+ uint32_t twoD_color_compare;
+ uint32_t twoD_color_compare_mask;
+ uint32_t twoD_mask;
+ uint32_t twoD_clip_tl;
+ uint32_t twoD_clip_br;
+ uint32_t twoD_mono_pattern_low;
+ uint32_t twoD_mono_pattern_high;
+ uint32_t twoD_window_width;
+ uint32_t twoD_source_base;
+ uint32_t twoD_destination_base;
+ uint32_t twoD_alpha;
+ uint32_t twoD_wrap;
+} SM501State;
+
+static uint32_t get_local_mem_size_index(uint32_t size)
+{
+ uint32_t norm_size = 0;
+ int i, index = 0;
+
+ for (i = 0; i < ARRAY_SIZE(sm501_mem_local_size); i++) {
+ uint32_t new_size = sm501_mem_local_size[i];
+ if (new_size >= size) {
+ if (norm_size == 0 || norm_size > new_size) {
+ norm_size = new_size;
+ index = i;
+ }
+ }
+ }
+
+ return index;
+}
+
+static ram_addr_t get_fb_addr(SM501State *s, int crt)
+{
+ return (crt ? s->dc_crt_fb_addr : s->dc_panel_fb_addr) & 0x3FFFFF0;
+}
+
+static inline int get_width(SM501State *s, int crt)
+{
+ int width = crt ? s->dc_crt_h_total : s->dc_panel_h_total;
+ return (width & 0x00000FFF) + 1;
+}
+
+static inline int get_height(SM501State *s, int crt)
+{
+ int height = crt ? s->dc_crt_v_total : s->dc_panel_v_total;
+ return (height & 0x00000FFF) + 1;
+}
+
+static inline int get_bpp(SM501State *s, int crt)
+{
+ int bpp = crt ? s->dc_crt_control : s->dc_panel_control;
+ return 1 << (bpp & 3);
+}
+
+/**
+ * Check the availability of hardware cursor.
+ * @param crt 0 for PANEL, 1 for CRT.
+ */
+static inline int is_hwc_enabled(SM501State *state, int crt)
+{
+ uint32_t addr = crt ? state->dc_crt_hwc_addr : state->dc_panel_hwc_addr;
+ return addr & SM501_HWC_EN;
+}
+
+/**
+ * Get the address which holds cursor pattern data.
+ * @param crt 0 for PANEL, 1 for CRT.
+ */
+static inline uint8_t *get_hwc_address(SM501State *state, int crt)
+{
+ uint32_t addr = crt ? state->dc_crt_hwc_addr : state->dc_panel_hwc_addr;
+ return state->local_mem + (addr & 0x03FFFFF0);
+}
+
+/**
+ * Get the cursor position in y coordinate.
+ * @param crt 0 for PANEL, 1 for CRT.
+ */
+static inline uint32_t get_hwc_y(SM501State *state, int crt)
+{
+ uint32_t location = crt ? state->dc_crt_hwc_location
+ : state->dc_panel_hwc_location;
+ return (location & 0x07FF0000) >> 16;
+}
+
+/**
+ * Get the cursor position in x coordinate.
+ * @param crt 0 for PANEL, 1 for CRT.
+ */
+static inline uint32_t get_hwc_x(SM501State *state, int crt)
+{
+ uint32_t location = crt ? state->dc_crt_hwc_location
+ : state->dc_panel_hwc_location;
+ return location & 0x000007FF;
+}
+
+/**
+ * Get the hardware cursor palette.
+ * @param crt 0 for PANEL, 1 for CRT.
+ * @param palette pointer to a [3 * 3] array to store color values in
+ */
+static inline void get_hwc_palette(SM501State *state, int crt, uint8_t *palette)
+{
+ int i;
+ uint32_t color_reg;
+ uint16_t rgb565;
+
+ for (i = 0; i < 3; i++) {
+ if (i + 1 == 3) {
+ color_reg = crt ? state->dc_crt_hwc_color_3
+ : state->dc_panel_hwc_color_3;
+ } else {
+ color_reg = crt ? state->dc_crt_hwc_color_1_2
+ : state->dc_panel_hwc_color_1_2;
+ }
+
+ if (i + 1 == 2) {
+ rgb565 = (color_reg >> 16) & 0xFFFF;
+ } else {
+ rgb565 = color_reg & 0xFFFF;
+ }
+ palette[i * 3 + 0] = ((rgb565 >> 11) * 527 + 23) >> 6; /* r */
+ palette[i * 3 + 1] = (((rgb565 >> 5) & 0x3f) * 259 + 33) >> 6; /* g */
+ palette[i * 3 + 2] = ((rgb565 & 0x1f) * 527 + 23) >> 6; /* b */
+ }
+}
+
+static inline void hwc_invalidate(SM501State *s, int crt)
+{
+ int w = get_width(s, crt);
+ int h = get_height(s, crt);
+ int bpp = get_bpp(s, crt);
+ int start = get_hwc_y(s, crt);
+ int end = MIN(h, start + SM501_HWC_HEIGHT) + 1;
+
+ start *= w * bpp;
+ end *= w * bpp;
+
+ memory_region_set_dirty(&s->local_mem_region,
+ get_fb_addr(s, crt) + start, end - start);
+}
+
+static void sm501_2d_operation(SM501State *s)
+{
+ int cmd = (s->twoD_control >> 16) & 0x1F;
+ int rtl = s->twoD_control & BIT(27);
+ int format = (s->twoD_stretch >> 20) & 3;
+ int bypp = 1 << format; /* bytes per pixel */
+ int rop_mode = (s->twoD_control >> 15) & 1; /* 1 for rop2, else rop3 */
+ /* 1 if rop2 source is the pattern, otherwise the source is the bitmap */
+ int rop2_source_is_pattern = (s->twoD_control >> 14) & 1;
+ int rop = s->twoD_control & 0xFF;
+ unsigned int dst_x = (s->twoD_destination >> 16) & 0x01FFF;
+ unsigned int dst_y = s->twoD_destination & 0xFFFF;
+ unsigned int width = (s->twoD_dimension >> 16) & 0x1FFF;
+ unsigned int height = s->twoD_dimension & 0xFFFF;
+ uint32_t dst_base = s->twoD_destination_base & 0x03FFFFFF;
+ unsigned int dst_pitch = (s->twoD_pitch >> 16) & 0x1FFF;
+ int crt = (s->dc_crt_control & SM501_DC_CRT_CONTROL_SEL) ? 1 : 0;
+ int fb_len = get_width(s, crt) * get_height(s, crt) * get_bpp(s, crt);
+ bool overlap = false;
+
+ if ((s->twoD_stretch >> 16) & 0xF) {
+ qemu_log_mask(LOG_UNIMP, "sm501: only XY addressing is supported.\n");
+ return;
+ }
+
+ if (s->twoD_source_base & BIT(27) || s->twoD_destination_base & BIT(27)) {
+ qemu_log_mask(LOG_UNIMP, "sm501: only local memory is supported.\n");
+ return;
+ }
+
+ if (!dst_pitch) {
+ qemu_log_mask(LOG_GUEST_ERROR, "sm501: Zero dest pitch.\n");
+ return;
+ }
+
+ if (!width || !height) {
+ qemu_log_mask(LOG_GUEST_ERROR, "sm501: Zero size 2D op.\n");
+ return;
+ }
+
+ if (rtl) {
+ dst_x -= width - 1;
+ dst_y -= height - 1;
+ }
+
+ if (dst_base >= get_local_mem_size(s) ||
+ dst_base + (dst_x + width + (dst_y + height) * dst_pitch) * bypp >=
+ get_local_mem_size(s)) {
+ qemu_log_mask(LOG_GUEST_ERROR, "sm501: 2D op dest is outside vram.\n");
+ return;
+ }
+
+ switch (cmd) {
+ case 0: /* BitBlt */
+ {
+ static uint32_t tmp_buf[16384];
+ unsigned int src_x = (s->twoD_source >> 16) & 0x01FFF;
+ unsigned int src_y = s->twoD_source & 0xFFFF;
+ uint32_t src_base = s->twoD_source_base & 0x03FFFFFF;
+ unsigned int src_pitch = s->twoD_pitch & 0x1FFF;
+
+ if (!src_pitch) {
+ qemu_log_mask(LOG_GUEST_ERROR, "sm501: Zero src pitch.\n");
+ return;
+ }
+
+ if (rtl) {
+ src_x -= width - 1;
+ src_y -= height - 1;
+ }
+
+ if (src_base >= get_local_mem_size(s) ||
+ src_base + (src_x + width + (src_y + height) * src_pitch) * bypp >=
+ get_local_mem_size(s)) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "sm501: 2D op src is outside vram.\n");
+ return;
+ }
+
+ if ((rop_mode && rop == 0x5) || (!rop_mode && rop == 0x55)) {
+ /* Invert dest, is there a way to do this with pixman? */
+ unsigned int x, y, i;
+ uint8_t *d = s->local_mem + dst_base;
+
+ for (y = 0; y < height; y++) {
+ i = (dst_x + (dst_y + y) * dst_pitch) * bypp;
+ for (x = 0; x < width; x++, i += bypp) {
+ stn_he_p(&d[i], bypp, ~ldn_he_p(&d[i], bypp));
+ }
+ }
+ } else {
+ /* Do copy src for unimplemented ops, better than unpainted area */
+ if ((rop_mode && (rop != 0xc || rop2_source_is_pattern)) ||
+ (!rop_mode && rop != 0xcc)) {
+ qemu_log_mask(LOG_UNIMP,
+ "sm501: rop%d op %x%s not implemented\n",
+ (rop_mode ? 2 : 3), rop,
+ (rop2_source_is_pattern ?
+ " with pattern source" : ""));
+ }
+ /* Ignore no-op blits, some guests seem to do this */
+ if (src_base == dst_base && src_pitch == dst_pitch &&
+ src_x == dst_x && src_y == dst_y) {
+ break;
+ }
+ /* Some clients also do 1 pixel blits, avoid overhead for these */
+ if (width == 1 && height == 1) {
+ unsigned int si = (src_x + src_y * src_pitch) * bypp;
+ unsigned int di = (dst_x + dst_y * dst_pitch) * bypp;
+ stn_he_p(&s->local_mem[dst_base + di], bypp,
+ ldn_he_p(&s->local_mem[src_base + si], bypp));
+ break;
+ }
+ /* If reverse blit do simple check for overlaps */
+ if (rtl && src_base == dst_base && src_pitch == dst_pitch) {
+ overlap = (src_x < dst_x + width && src_x + width > dst_x &&
+ src_y < dst_y + height && src_y + height > dst_y);
+ } else if (rtl) {
+ unsigned int sb, se, db, de;
+ sb = src_base + (src_x + src_y * src_pitch) * bypp;
+ se = sb + (width + (height - 1) * src_pitch) * bypp;
+ db = dst_base + (dst_x + dst_y * dst_pitch) * bypp;
+ de = db + (width + (height - 1) * dst_pitch) * bypp;
+ overlap = (db < se && sb < de);
+ }
+ if (overlap) {
+ /* pixman can't do reverse blit: copy via temporary */
+ int tmp_stride = DIV_ROUND_UP(width * bypp, sizeof(uint32_t));
+ uint32_t *tmp = tmp_buf;
+
+ if (tmp_stride * sizeof(uint32_t) * height > sizeof(tmp_buf)) {
+ tmp = g_malloc(tmp_stride * sizeof(uint32_t) * height);
+ }
+ pixman_blt((uint32_t *)&s->local_mem[src_base], tmp,
+ src_pitch * bypp / sizeof(uint32_t),
+ tmp_stride, 8 * bypp, 8 * bypp,
+ src_x, src_y, 0, 0, width, height);
+ pixman_blt(tmp, (uint32_t *)&s->local_mem[dst_base],
+ tmp_stride,
+ dst_pitch * bypp / sizeof(uint32_t),
+ 8 * bypp, 8 * bypp,
+ 0, 0, dst_x, dst_y, width, height);
+ if (tmp != tmp_buf) {
+ g_free(tmp);
+ }
+ } else {
+ pixman_blt((uint32_t *)&s->local_mem[src_base],
+ (uint32_t *)&s->local_mem[dst_base],
+ src_pitch * bypp / sizeof(uint32_t),
+ dst_pitch * bypp / sizeof(uint32_t),
+ 8 * bypp, 8 * bypp,
+ src_x, src_y, dst_x, dst_y, width, height);
+ }
+ }
+ break;
+ }
+ case 1: /* Rectangle Fill */
+ {
+ uint32_t color = s->twoD_foreground;
+
+ if (format == 2) {
+ color = cpu_to_le32(color);
+ } else if (format == 1) {
+ color = cpu_to_le16(color);
+ }
+
+ if (width == 1 && height == 1) {
+ unsigned int i = (dst_x + dst_y * dst_pitch) * bypp;
+ stn_he_p(&s->local_mem[dst_base + i], bypp, color);
+ } else {
+ pixman_fill((uint32_t *)&s->local_mem[dst_base],
+ dst_pitch * bypp / sizeof(uint32_t),
+ 8 * bypp, dst_x, dst_y, width, height, color);
+ }
+ break;
+ }
+ default:
+ qemu_log_mask(LOG_UNIMP, "sm501: not implemented 2D operation: %d\n",
+ cmd);
+ return;
+ }
+
+ if (dst_base >= get_fb_addr(s, crt) &&
+ dst_base <= get_fb_addr(s, crt) + fb_len) {
+ int dst_len = MIN(fb_len, ((dst_y + height - 1) * dst_pitch +
+ dst_x + width) * bypp);
+ if (dst_len) {
+ memory_region_set_dirty(&s->local_mem_region, dst_base, dst_len);
+ }
+ }
+}
+
+static uint64_t sm501_system_config_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ SM501State *s = (SM501State *)opaque;
+ uint32_t ret = 0;
+
+ switch (addr) {
+ case SM501_SYSTEM_CONTROL:
+ ret = s->system_control;
+ break;
+ case SM501_MISC_CONTROL:
+ ret = s->misc_control;
+ break;
+ case SM501_GPIO31_0_CONTROL:
+ ret = s->gpio_31_0_control;
+ break;
+ case SM501_GPIO63_32_CONTROL:
+ ret = s->gpio_63_32_control;
+ break;
+ case SM501_DEVICEID:
+ ret = 0x050100A0;
+ break;
+ case SM501_DRAM_CONTROL:
+ ret = (s->dram_control & 0x07F107C0) | s->local_mem_size_index << 13;
+ break;
+ case SM501_ARBTRTN_CONTROL:
+ ret = s->arbitration_control;
+ break;
+ case SM501_COMMAND_LIST_STATUS:
+ ret = 0x00180002; /* FIFOs are empty, everything idle */
+ break;
+ case SM501_IRQ_MASK:
+ ret = s->irq_mask;
+ break;
+ case SM501_MISC_TIMING:
+ /* TODO : simulate gate control */
+ ret = s->misc_timing;
+ break;
+ case SM501_CURRENT_GATE:
+ /* TODO : simulate gate control */
+ ret = 0x00021807;
+ break;
+ case SM501_CURRENT_CLOCK:
+ ret = 0x2A1A0A09;
+ break;
+ case SM501_POWER_MODE_CONTROL:
+ ret = s->power_mode_control;
+ break;
+ case SM501_ENDIAN_CONTROL:
+ ret = 0; /* Only default little endian mode is supported */
+ break;
+
+ default:
+ qemu_log_mask(LOG_UNIMP, "sm501: not implemented system config"
+ "register read. addr=%" HWADDR_PRIx "\n", addr);
+ }
+ trace_sm501_system_config_read(addr, ret);
+ return ret;
+}
+
+static void sm501_system_config_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ SM501State *s = (SM501State *)opaque;
+
+ trace_sm501_system_config_write((uint32_t)addr, (uint32_t)value);
+ switch (addr) {
+ case SM501_SYSTEM_CONTROL:
+ s->system_control &= 0x10DB0000;
+ s->system_control |= value & 0xEF00B8F7;
+ break;
+ case SM501_MISC_CONTROL:
+ s->misc_control &= 0xEF;
+ s->misc_control |= value & 0xFF7FFF10;
+ break;
+ case SM501_GPIO31_0_CONTROL:
+ s->gpio_31_0_control = value;
+ break;
+ case SM501_GPIO63_32_CONTROL:
+ s->gpio_63_32_control = value & 0xFF80FFFF;
+ break;
+ case SM501_DRAM_CONTROL:
+ s->local_mem_size_index = (value >> 13) & 0x7;
+ /* TODO : check validity of size change */
+ s->dram_control &= 0x80000000;
+ s->dram_control |= value & 0x7FFFFFC3;
+ break;
+ case SM501_ARBTRTN_CONTROL:
+ s->arbitration_control = value & 0x37777777;
+ break;
+ case SM501_IRQ_MASK:
+ s->irq_mask = value & 0xFFDF3F5F;
+ break;
+ case SM501_MISC_TIMING:
+ s->misc_timing = value & 0xF31F1FFF;
+ break;
+ case SM501_POWER_MODE_0_GATE:
+ case SM501_POWER_MODE_1_GATE:
+ case SM501_POWER_MODE_0_CLOCK:
+ case SM501_POWER_MODE_1_CLOCK:
+ /* TODO : simulate gate & clock control */
+ break;
+ case SM501_POWER_MODE_CONTROL:
+ s->power_mode_control = value & 0x00000003;
+ break;
+ case SM501_ENDIAN_CONTROL:
+ if (value & 0x00000001) {
+ qemu_log_mask(LOG_UNIMP, "sm501: system config big endian mode not"
+ " implemented.\n");
+ }
+ break;
+
+ default:
+ qemu_log_mask(LOG_UNIMP, "sm501: not implemented system config"
+ "register write. addr=%" HWADDR_PRIx
+ ", val=%" PRIx64 "\n", addr, value);
+ }
+}
+
+static const MemoryRegionOps sm501_system_config_ops = {
+ .read = sm501_system_config_read,
+ .write = sm501_system_config_write,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static uint64_t sm501_i2c_read(void *opaque, hwaddr addr, unsigned size)
+{
+ SM501State *s = (SM501State *)opaque;
+ uint8_t ret = 0;
+
+ switch (addr) {
+ case SM501_I2C_BYTE_COUNT:
+ ret = s->i2c_byte_count;
+ break;
+ case SM501_I2C_STATUS:
+ ret = s->i2c_status;
+ break;
+ case SM501_I2C_SLAVE_ADDRESS:
+ ret = s->i2c_addr;
+ break;
+ case SM501_I2C_DATA ... SM501_I2C_DATA + 15:
+ ret = s->i2c_data[addr - SM501_I2C_DATA];
+ break;
+ default:
+ qemu_log_mask(LOG_UNIMP, "sm501 i2c : not implemented register read."
+ " addr=0x%" HWADDR_PRIx "\n", addr);
+ }
+ trace_sm501_i2c_read((uint32_t)addr, ret);
+ return ret;
+}
+
+static void sm501_i2c_write(void *opaque, hwaddr addr, uint64_t value,
+ unsigned size)
+{
+ SM501State *s = (SM501State *)opaque;
+
+ trace_sm501_i2c_write((uint32_t)addr, (uint32_t)value);
+ switch (addr) {
+ case SM501_I2C_BYTE_COUNT:
+ s->i2c_byte_count = value & 0xf;
+ break;
+ case SM501_I2C_CONTROL:
+ if (value & SM501_I2C_CONTROL_ENABLE) {
+ if (value & SM501_I2C_CONTROL_START) {
+ bool is_recv = s->i2c_addr & 1;
+ int res = i2c_start_transfer(s->i2c_bus,
+ s->i2c_addr >> 1,
+ is_recv);
+ if (res) {
+ s->i2c_status |= SM501_I2C_STATUS_ERROR;
+ } else {
+ int i;
+ for (i = 0; i <= s->i2c_byte_count; i++) {
+ if (is_recv) {
+ s->i2c_data[i] = i2c_recv(s->i2c_bus);
+ } else if (i2c_send(s->i2c_bus, s->i2c_data[i]) < 0) {
+ s->i2c_status |= SM501_I2C_STATUS_ERROR;
+ return;
+ }
+ }
+ if (i) {
+ s->i2c_status = SM501_I2C_STATUS_COMPLETE;
+ }
+ }
+ } else {
+ i2c_end_transfer(s->i2c_bus);
+ s->i2c_status &= ~SM501_I2C_STATUS_ERROR;
+ }
+ }
+ break;
+ case SM501_I2C_RESET:
+ if ((value & SM501_I2C_RESET_ERROR) == 0) {
+ s->i2c_status &= ~SM501_I2C_STATUS_ERROR;
+ }
+ break;
+ case SM501_I2C_SLAVE_ADDRESS:
+ s->i2c_addr = value & 0xff;
+ break;
+ case SM501_I2C_DATA ... SM501_I2C_DATA + 15:
+ s->i2c_data[addr - SM501_I2C_DATA] = value & 0xff;
+ break;
+ default:
+ qemu_log_mask(LOG_UNIMP, "sm501 i2c : not implemented register write. "
+ "addr=0x%" HWADDR_PRIx " val=%" PRIx64 "\n", addr, value);
+ }
+}
+
+static const MemoryRegionOps sm501_i2c_ops = {
+ .read = sm501_i2c_read,
+ .write = sm501_i2c_write,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static uint32_t sm501_palette_read(void *opaque, hwaddr addr)
+{
+ SM501State *s = (SM501State *)opaque;
+
+ trace_sm501_palette_read((uint32_t)addr);
+
+ /* TODO : consider BYTE/WORD access */
+ /* TODO : consider endian */
+
+ assert(range_covers_byte(0, 0x400 * 3, addr));
+ return *(uint32_t *)&s->dc_palette[addr];
+}
+
+static void sm501_palette_write(void *opaque, hwaddr addr,
+ uint32_t value)
+{
+ SM501State *s = (SM501State *)opaque;
+
+ trace_sm501_palette_write((uint32_t)addr, value);
+
+ /* TODO : consider BYTE/WORD access */
+ /* TODO : consider endian */
+
+ assert(range_covers_byte(0, 0x400 * 3, addr));
+ *(uint32_t *)&s->dc_palette[addr] = value;
+ s->do_full_update = true;
+}
+
+static uint64_t sm501_disp_ctrl_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ SM501State *s = (SM501State *)opaque;
+ uint32_t ret = 0;
+
+ switch (addr) {
+
+ case SM501_DC_PANEL_CONTROL:
+ ret = s->dc_panel_control;
+ break;
+ case SM501_DC_PANEL_PANNING_CONTROL:
+ ret = s->dc_panel_panning_control;
+ break;
+ case SM501_DC_PANEL_COLOR_KEY:
+ /* Not implemented yet */
+ break;
+ case SM501_DC_PANEL_FB_ADDR:
+ ret = s->dc_panel_fb_addr;
+ break;
+ case SM501_DC_PANEL_FB_OFFSET:
+ ret = s->dc_panel_fb_offset;
+ break;
+ case SM501_DC_PANEL_FB_WIDTH:
+ ret = s->dc_panel_fb_width;
+ break;
+ case SM501_DC_PANEL_FB_HEIGHT:
+ ret = s->dc_panel_fb_height;
+ break;
+ case SM501_DC_PANEL_TL_LOC:
+ ret = s->dc_panel_tl_location;
+ break;
+ case SM501_DC_PANEL_BR_LOC:
+ ret = s->dc_panel_br_location;
+ break;
+
+ case SM501_DC_PANEL_H_TOT:
+ ret = s->dc_panel_h_total;
+ break;
+ case SM501_DC_PANEL_H_SYNC:
+ ret = s->dc_panel_h_sync;
+ break;
+ case SM501_DC_PANEL_V_TOT:
+ ret = s->dc_panel_v_total;
+ break;
+ case SM501_DC_PANEL_V_SYNC:
+ ret = s->dc_panel_v_sync;
+ break;
+
+ case SM501_DC_PANEL_HWC_ADDR:
+ ret = s->dc_panel_hwc_addr;
+ break;
+ case SM501_DC_PANEL_HWC_LOC:
+ ret = s->dc_panel_hwc_location;
+ break;
+ case SM501_DC_PANEL_HWC_COLOR_1_2:
+ ret = s->dc_panel_hwc_color_1_2;
+ break;
+ case SM501_DC_PANEL_HWC_COLOR_3:
+ ret = s->dc_panel_hwc_color_3;
+ break;
+
+ case SM501_DC_VIDEO_CONTROL:
+ ret = s->dc_video_control;
+ break;
+
+ case SM501_DC_CRT_CONTROL:
+ ret = s->dc_crt_control;
+ break;
+ case SM501_DC_CRT_FB_ADDR:
+ ret = s->dc_crt_fb_addr;
+ break;
+ case SM501_DC_CRT_FB_OFFSET:
+ ret = s->dc_crt_fb_offset;
+ break;
+ case SM501_DC_CRT_H_TOT:
+ ret = s->dc_crt_h_total;
+ break;
+ case SM501_DC_CRT_H_SYNC:
+ ret = s->dc_crt_h_sync;
+ break;
+ case SM501_DC_CRT_V_TOT:
+ ret = s->dc_crt_v_total;
+ break;
+ case SM501_DC_CRT_V_SYNC:
+ ret = s->dc_crt_v_sync;
+ break;
+
+ case SM501_DC_CRT_HWC_ADDR:
+ ret = s->dc_crt_hwc_addr;
+ break;
+ case SM501_DC_CRT_HWC_LOC:
+ ret = s->dc_crt_hwc_location;
+ break;
+ case SM501_DC_CRT_HWC_COLOR_1_2:
+ ret = s->dc_crt_hwc_color_1_2;
+ break;
+ case SM501_DC_CRT_HWC_COLOR_3:
+ ret = s->dc_crt_hwc_color_3;
+ break;
+
+ case SM501_DC_PANEL_PALETTE ... SM501_DC_PANEL_PALETTE + 0x400 * 3 - 4:
+ ret = sm501_palette_read(opaque, addr - SM501_DC_PANEL_PALETTE);
+ break;
+
+ default:
+ qemu_log_mask(LOG_UNIMP, "sm501: not implemented disp ctrl register "
+ "read. addr=%" HWADDR_PRIx "\n", addr);
+ }
+ trace_sm501_disp_ctrl_read((uint32_t)addr, ret);
+ return ret;
+}
+
+static void sm501_disp_ctrl_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ SM501State *s = (SM501State *)opaque;
+
+ trace_sm501_disp_ctrl_write((uint32_t)addr, (uint32_t)value);
+ switch (addr) {
+ case SM501_DC_PANEL_CONTROL:
+ s->dc_panel_control = value & 0x0FFF73FF;
+ break;
+ case SM501_DC_PANEL_PANNING_CONTROL:
+ s->dc_panel_panning_control = value & 0xFF3FFF3F;
+ break;
+ case SM501_DC_PANEL_COLOR_KEY:
+ /* Not implemented yet */
+ break;
+ case SM501_DC_PANEL_FB_ADDR:
+ s->dc_panel_fb_addr = value & 0x8FFFFFF0;
+ if (value & 0x8000000) {
+ qemu_log_mask(LOG_UNIMP, "Panel external memory not supported\n");
+ }
+ s->do_full_update = true;
+ break;
+ case SM501_DC_PANEL_FB_OFFSET:
+ s->dc_panel_fb_offset = value & 0x3FF03FF0;
+ break;
+ case SM501_DC_PANEL_FB_WIDTH:
+ s->dc_panel_fb_width = value & 0x0FFF0FFF;
+ break;
+ case SM501_DC_PANEL_FB_HEIGHT:
+ s->dc_panel_fb_height = value & 0x0FFF0FFF;
+ break;
+ case SM501_DC_PANEL_TL_LOC:
+ s->dc_panel_tl_location = value & 0x07FF07FF;
+ break;
+ case SM501_DC_PANEL_BR_LOC:
+ s->dc_panel_br_location = value & 0x07FF07FF;
+ break;
+
+ case SM501_DC_PANEL_H_TOT:
+ s->dc_panel_h_total = value & 0x0FFF0FFF;
+ break;
+ case SM501_DC_PANEL_H_SYNC:
+ s->dc_panel_h_sync = value & 0x00FF0FFF;
+ break;
+ case SM501_DC_PANEL_V_TOT:
+ s->dc_panel_v_total = value & 0x0FFF0FFF;
+ break;
+ case SM501_DC_PANEL_V_SYNC:
+ s->dc_panel_v_sync = value & 0x003F0FFF;
+ break;
+
+ case SM501_DC_PANEL_HWC_ADDR:
+ value &= 0x8FFFFFF0;
+ if (value != s->dc_panel_hwc_addr) {
+ hwc_invalidate(s, 0);
+ s->dc_panel_hwc_addr = value;
+ }
+ break;
+ case SM501_DC_PANEL_HWC_LOC:
+ value &= 0x0FFF0FFF;
+ if (value != s->dc_panel_hwc_location) {
+ hwc_invalidate(s, 0);
+ s->dc_panel_hwc_location = value;
+ }
+ break;
+ case SM501_DC_PANEL_HWC_COLOR_1_2:
+ s->dc_panel_hwc_color_1_2 = value;
+ break;
+ case SM501_DC_PANEL_HWC_COLOR_3:
+ s->dc_panel_hwc_color_3 = value & 0x0000FFFF;
+ break;
+
+ case SM501_DC_VIDEO_CONTROL:
+ s->dc_video_control = value & 0x00037FFF;
+ break;
+
+ case SM501_DC_CRT_CONTROL:
+ s->dc_crt_control = value & 0x0003FFFF;
+ break;
+ case SM501_DC_CRT_FB_ADDR:
+ s->dc_crt_fb_addr = value & 0x8FFFFFF0;
+ if (value & 0x8000000) {
+ qemu_log_mask(LOG_UNIMP, "CRT external memory not supported\n");
+ }
+ s->do_full_update = true;
+ break;
+ case SM501_DC_CRT_FB_OFFSET:
+ s->dc_crt_fb_offset = value & 0x3FF03FF0;
+ break;
+ case SM501_DC_CRT_H_TOT:
+ s->dc_crt_h_total = value & 0x0FFF0FFF;
+ break;
+ case SM501_DC_CRT_H_SYNC:
+ s->dc_crt_h_sync = value & 0x00FF0FFF;
+ break;
+ case SM501_DC_CRT_V_TOT:
+ s->dc_crt_v_total = value & 0x0FFF0FFF;
+ break;
+ case SM501_DC_CRT_V_SYNC:
+ s->dc_crt_v_sync = value & 0x003F0FFF;
+ break;
+
+ case SM501_DC_CRT_HWC_ADDR:
+ value &= 0x8FFFFFF0;
+ if (value != s->dc_crt_hwc_addr) {
+ hwc_invalidate(s, 1);
+ s->dc_crt_hwc_addr = value;
+ }
+ break;
+ case SM501_DC_CRT_HWC_LOC:
+ value &= 0x0FFF0FFF;
+ if (value != s->dc_crt_hwc_location) {
+ hwc_invalidate(s, 1);
+ s->dc_crt_hwc_location = value;
+ }
+ break;
+ case SM501_DC_CRT_HWC_COLOR_1_2:
+ s->dc_crt_hwc_color_1_2 = value;
+ break;
+ case SM501_DC_CRT_HWC_COLOR_3:
+ s->dc_crt_hwc_color_3 = value & 0x0000FFFF;
+ break;
+
+ case SM501_DC_PANEL_PALETTE ... SM501_DC_PANEL_PALETTE + 0x400 * 3 - 4:
+ sm501_palette_write(opaque, addr - SM501_DC_PANEL_PALETTE, value);
+ break;
+
+ default:
+ qemu_log_mask(LOG_UNIMP, "sm501: not implemented disp ctrl register "
+ "write. addr=%" HWADDR_PRIx
+ ", val=%" PRIx64 "\n", addr, value);
+ }
+}
+
+static const MemoryRegionOps sm501_disp_ctrl_ops = {
+ .read = sm501_disp_ctrl_read,
+ .write = sm501_disp_ctrl_write,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static uint64_t sm501_2d_engine_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ SM501State *s = (SM501State *)opaque;
+ uint32_t ret = 0;
+
+ switch (addr) {
+ case SM501_2D_SOURCE:
+ ret = s->twoD_source;
+ break;
+ case SM501_2D_DESTINATION:
+ ret = s->twoD_destination;
+ break;
+ case SM501_2D_DIMENSION:
+ ret = s->twoD_dimension;
+ break;
+ case SM501_2D_CONTROL:
+ ret = s->twoD_control;
+ break;
+ case SM501_2D_PITCH:
+ ret = s->twoD_pitch;
+ break;
+ case SM501_2D_FOREGROUND:
+ ret = s->twoD_foreground;
+ break;
+ case SM501_2D_BACKGROUND:
+ ret = s->twoD_background;
+ break;
+ case SM501_2D_STRETCH:
+ ret = s->twoD_stretch;
+ break;
+ case SM501_2D_COLOR_COMPARE:
+ ret = s->twoD_color_compare;
+ break;
+ case SM501_2D_COLOR_COMPARE_MASK:
+ ret = s->twoD_color_compare_mask;
+ break;
+ case SM501_2D_MASK:
+ ret = s->twoD_mask;
+ break;
+ case SM501_2D_CLIP_TL:
+ ret = s->twoD_clip_tl;
+ break;
+ case SM501_2D_CLIP_BR:
+ ret = s->twoD_clip_br;
+ break;
+ case SM501_2D_MONO_PATTERN_LOW:
+ ret = s->twoD_mono_pattern_low;
+ break;
+ case SM501_2D_MONO_PATTERN_HIGH:
+ ret = s->twoD_mono_pattern_high;
+ break;
+ case SM501_2D_WINDOW_WIDTH:
+ ret = s->twoD_window_width;
+ break;
+ case SM501_2D_SOURCE_BASE:
+ ret = s->twoD_source_base;
+ break;
+ case SM501_2D_DESTINATION_BASE:
+ ret = s->twoD_destination_base;
+ break;
+ case SM501_2D_ALPHA:
+ ret = s->twoD_alpha;
+ break;
+ case SM501_2D_WRAP:
+ ret = s->twoD_wrap;
+ break;
+ case SM501_2D_STATUS:
+ ret = 0; /* Should return interrupt status */
+ break;
+ default:
+ qemu_log_mask(LOG_UNIMP, "sm501: not implemented disp ctrl register "
+ "read. addr=%" HWADDR_PRIx "\n", addr);
+ }
+ trace_sm501_2d_engine_read((uint32_t)addr, ret);
+ return ret;
+}
+
+static void sm501_2d_engine_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ SM501State *s = (SM501State *)opaque;
+
+ trace_sm501_2d_engine_write((uint32_t)addr, (uint32_t)value);
+ switch (addr) {
+ case SM501_2D_SOURCE:
+ s->twoD_source = value;
+ break;
+ case SM501_2D_DESTINATION:
+ s->twoD_destination = value;
+ break;
+ case SM501_2D_DIMENSION:
+ s->twoD_dimension = value;
+ break;
+ case SM501_2D_CONTROL:
+ s->twoD_control = value;
+
+ /* do 2d operation if start flag is set. */
+ if (value & 0x80000000) {
+ sm501_2d_operation(s);
+ s->twoD_control &= ~0x80000000; /* start flag down */
+ }
+
+ break;
+ case SM501_2D_PITCH:
+ s->twoD_pitch = value;
+ break;
+ case SM501_2D_FOREGROUND:
+ s->twoD_foreground = value;
+ break;
+ case SM501_2D_BACKGROUND:
+ s->twoD_background = value;
+ break;
+ case SM501_2D_STRETCH:
+ if (((value >> 20) & 3) == 3) {
+ value &= ~BIT(20);
+ }
+ s->twoD_stretch = value;
+ break;
+ case SM501_2D_COLOR_COMPARE:
+ s->twoD_color_compare = value;
+ break;
+ case SM501_2D_COLOR_COMPARE_MASK:
+ s->twoD_color_compare_mask = value;
+ break;
+ case SM501_2D_MASK:
+ s->twoD_mask = value;
+ break;
+ case SM501_2D_CLIP_TL:
+ s->twoD_clip_tl = value;
+ break;
+ case SM501_2D_CLIP_BR:
+ s->twoD_clip_br = value;
+ break;
+ case SM501_2D_MONO_PATTERN_LOW:
+ s->twoD_mono_pattern_low = value;
+ break;
+ case SM501_2D_MONO_PATTERN_HIGH:
+ s->twoD_mono_pattern_high = value;
+ break;
+ case SM501_2D_WINDOW_WIDTH:
+ s->twoD_window_width = value;
+ break;
+ case SM501_2D_SOURCE_BASE:
+ s->twoD_source_base = value;
+ break;
+ case SM501_2D_DESTINATION_BASE:
+ s->twoD_destination_base = value;
+ break;
+ case SM501_2D_ALPHA:
+ s->twoD_alpha = value;
+ break;
+ case SM501_2D_WRAP:
+ s->twoD_wrap = value;
+ break;
+ case SM501_2D_STATUS:
+ /* ignored, writing 0 should clear interrupt status */
+ break;
+ default:
+ qemu_log_mask(LOG_UNIMP, "sm501: not implemented 2d engine register "
+ "write. addr=%" HWADDR_PRIx
+ ", val=%" PRIx64 "\n", addr, value);
+ }
+}
+
+static const MemoryRegionOps sm501_2d_engine_ops = {
+ .read = sm501_2d_engine_read,
+ .write = sm501_2d_engine_write,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+/* draw line functions for all console modes */
+
+typedef void draw_line_func(uint8_t *d, const uint8_t *s,
+ int width, const uint32_t *pal);
+
+typedef void draw_hwc_line_func(uint8_t *d, const uint8_t *s,
+ int width, const uint8_t *palette,
+ int c_x, int c_y);
+
+static void draw_line8_32(uint8_t *d, const uint8_t *s, int width,
+ const uint32_t *pal)
+{
+ uint8_t v, r, g, b;
+ do {
+ v = ldub_p(s);
+ r = (pal[v] >> 16) & 0xff;
+ g = (pal[v] >> 8) & 0xff;
+ b = (pal[v] >> 0) & 0xff;
+ *(uint32_t *)d = rgb_to_pixel32(r, g, b);
+ s++;
+ d += 4;
+ } while (--width != 0);
+}
+
+static void draw_line16_32(uint8_t *d, const uint8_t *s, int width,
+ const uint32_t *pal)
+{
+ uint16_t rgb565;
+ uint8_t r, g, b;
+
+ do {
+ rgb565 = lduw_le_p(s);
+ r = (rgb565 >> 8) & 0xf8;
+ g = (rgb565 >> 3) & 0xfc;
+ b = (rgb565 << 3) & 0xf8;
+ *(uint32_t *)d = rgb_to_pixel32(r, g, b);
+ s += 2;
+ d += 4;
+ } while (--width != 0);
+}
+
+static void draw_line32_32(uint8_t *d, const uint8_t *s, int width,
+ const uint32_t *pal)
+{
+ uint8_t r, g, b;
+
+ do {
+ r = s[2];
+ g = s[1];
+ b = s[0];
+ *(uint32_t *)d = rgb_to_pixel32(r, g, b);
+ s += 4;
+ d += 4;
+ } while (--width != 0);
+}
+
+/**
+ * Draw hardware cursor image on the given line.
+ */
+static void draw_hwc_line_32(uint8_t *d, const uint8_t *s, int width,
+ const uint8_t *palette, int c_x, int c_y)
+{
+ int i;
+ uint8_t r, g, b, v, bitset = 0;
+
+ /* get cursor position */
+ assert(0 <= c_y && c_y < SM501_HWC_HEIGHT);
+ s += SM501_HWC_WIDTH * c_y / 4; /* 4 pixels per byte */
+ d += c_x * 4;
+
+ for (i = 0; i < SM501_HWC_WIDTH && c_x + i < width; i++) {
+ /* get pixel value */
+ if (i % 4 == 0) {
+ bitset = ldub_p(s);
+ s++;
+ }
+ v = bitset & 3;
+ bitset >>= 2;
+
+ /* write pixel */
+ if (v) {
+ v--;
+ r = palette[v * 3 + 0];
+ g = palette[v * 3 + 1];
+ b = palette[v * 3 + 2];
+ *(uint32_t *)d = rgb_to_pixel32(r, g, b);
+ }
+ d += 4;
+ }
+}
+
+static void sm501_update_display(void *opaque)
+{
+ SM501State *s = (SM501State *)opaque;
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ DirtyBitmapSnapshot *snap;
+ int y, c_x = 0, c_y = 0;
+ int crt = (s->dc_crt_control & SM501_DC_CRT_CONTROL_SEL) ? 1 : 0;
+ int width = get_width(s, crt);
+ int height = get_height(s, crt);
+ int src_bpp = get_bpp(s, crt);
+ int dst_bpp = surface_bytes_per_pixel(surface);
+ draw_line_func *draw_line = NULL;
+ draw_hwc_line_func *draw_hwc_line = NULL;
+ int full_update = 0;
+ int y_start = -1;
+ ram_addr_t offset;
+ uint32_t *palette;
+ uint8_t hwc_palette[3 * 3];
+ uint8_t *hwc_src = NULL;
+
+ assert(dst_bpp == 4); /* Output is always 32-bit RGB */
+
+ if (!((crt ? s->dc_crt_control : s->dc_panel_control)
+ & SM501_DC_CRT_CONTROL_ENABLE)) {
+ return;
+ }
+
+ palette = (uint32_t *)(crt ? &s->dc_palette[SM501_DC_CRT_PALETTE -
+ SM501_DC_PANEL_PALETTE]
+ : &s->dc_palette[0]);
+
+ /* choose draw_line function */
+ switch (src_bpp) {
+ case 1:
+ draw_line = draw_line8_32;
+ break;
+ case 2:
+ draw_line = draw_line16_32;
+ break;
+ case 4:
+ draw_line = draw_line32_32;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "sm501: update display"
+ "invalid control register value.\n");
+ return;
+ }
+
+ /* set up to draw hardware cursor */
+ if (is_hwc_enabled(s, crt)) {
+ /* choose cursor draw line function */
+ draw_hwc_line = draw_hwc_line_32;
+ hwc_src = get_hwc_address(s, crt);
+ c_x = get_hwc_x(s, crt);
+ c_y = get_hwc_y(s, crt);
+ get_hwc_palette(s, crt, hwc_palette);
+ }
+
+ /* adjust console size */
+ if (s->last_width != width || s->last_height != height) {
+ qemu_console_resize(s->con, width, height);
+ surface = qemu_console_surface(s->con);
+ s->last_width = width;
+ s->last_height = height;
+ full_update = 1;
+ }
+
+ /* someone else requested a full update */
+ if (s->do_full_update) {
+ s->do_full_update = false;
+ full_update = 1;
+ }
+
+ /* draw each line according to conditions */
+ offset = get_fb_addr(s, crt);
+ snap = memory_region_snapshot_and_clear_dirty(&s->local_mem_region,
+ offset, width * height * src_bpp, DIRTY_MEMORY_VGA);
+ for (y = 0; y < height; y++, offset += width * src_bpp) {
+ int update, update_hwc;
+
+ /* check if hardware cursor is enabled and we're within its range */
+ update_hwc = draw_hwc_line && c_y <= y && y < c_y + SM501_HWC_HEIGHT;
+ update = full_update || update_hwc;
+ /* check dirty flags for each line */
+ update |= memory_region_snapshot_get_dirty(&s->local_mem_region, snap,
+ offset, width * src_bpp);
+
+ /* draw line and change status */
+ if (update) {
+ uint8_t *d = surface_data(surface);
+ d += y * width * dst_bpp;
+
+ /* draw graphics layer */
+ draw_line(d, s->local_mem + offset, width, palette);
+
+ /* draw hardware cursor */
+ if (update_hwc) {
+ draw_hwc_line(d, hwc_src, width, hwc_palette, c_x, y - c_y);
+ }
+
+ if (y_start < 0) {
+ y_start = y;
+ }
+ } else {
+ if (y_start >= 0) {
+ /* flush to display */
+ dpy_gfx_update(s->con, 0, y_start, width, y - y_start);
+ y_start = -1;
+ }
+ }
+ }
+ g_free(snap);
+
+ /* complete flush to display */
+ if (y_start >= 0) {
+ dpy_gfx_update(s->con, 0, y_start, width, y - y_start);
+ }
+}
+
+static const GraphicHwOps sm501_ops = {
+ .gfx_update = sm501_update_display,
+};
+
+static void sm501_reset(SM501State *s)
+{
+ s->system_control = 0x00100000; /* 2D engine FIFO empty */
+ /* Bits 17 (SH), 7 (CDR), 6:5 (Test), 2:0 (Bus) are all supposed
+ * to be determined at reset by GPIO lines which set config bits.
+ * We hardwire them:
+ * SH = 0 : Hitachi Ready Polarity == Active Low
+ * CDR = 0 : do not reset clock divider
+ * TEST = 0 : Normal mode (not testing the silicon)
+ * BUS = 0 : Hitachi SH3/SH4
+ */
+ s->misc_control = SM501_MISC_DAC_POWER;
+ s->gpio_31_0_control = 0;
+ s->gpio_63_32_control = 0;
+ s->dram_control = 0;
+ s->arbitration_control = 0x05146732;
+ s->irq_mask = 0;
+ s->misc_timing = 0;
+ s->power_mode_control = 0;
+ s->i2c_byte_count = 0;
+ s->i2c_status = 0;
+ s->i2c_addr = 0;
+ memset(s->i2c_data, 0, 16);
+ s->dc_panel_control = 0x00010000; /* FIFO level 3 */
+ s->dc_video_control = 0;
+ s->dc_crt_control = 0x00010000;
+ s->twoD_source = 0;
+ s->twoD_destination = 0;
+ s->twoD_dimension = 0;
+ s->twoD_control = 0;
+ s->twoD_pitch = 0;
+ s->twoD_foreground = 0;
+ s->twoD_background = 0;
+ s->twoD_stretch = 0;
+ s->twoD_color_compare = 0;
+ s->twoD_color_compare_mask = 0;
+ s->twoD_mask = 0;
+ s->twoD_clip_tl = 0;
+ s->twoD_clip_br = 0;
+ s->twoD_mono_pattern_low = 0;
+ s->twoD_mono_pattern_high = 0;
+ s->twoD_window_width = 0;
+ s->twoD_source_base = 0;
+ s->twoD_destination_base = 0;
+ s->twoD_alpha = 0;
+ s->twoD_wrap = 0;
+}
+
+static void sm501_init(SM501State *s, DeviceState *dev,
+ uint32_t local_mem_bytes)
+{
+ s->local_mem_size_index = get_local_mem_size_index(local_mem_bytes);
+
+ /* local memory */
+ memory_region_init_ram(&s->local_mem_region, OBJECT(dev), "sm501.local",
+ get_local_mem_size(s), &error_fatal);
+ memory_region_set_log(&s->local_mem_region, true, DIRTY_MEMORY_VGA);
+ s->local_mem = memory_region_get_ram_ptr(&s->local_mem_region);
+
+ /* i2c */
+ s->i2c_bus = i2c_init_bus(dev, "sm501.i2c");
+ /* ddc */
+ I2CDDCState *ddc = I2CDDC(qdev_new(TYPE_I2CDDC));
+ i2c_slave_set_address(I2C_SLAVE(ddc), 0x50);
+ qdev_realize_and_unref(DEVICE(ddc), BUS(s->i2c_bus), &error_abort);
+
+ /* mmio */
+ memory_region_init(&s->mmio_region, OBJECT(dev), "sm501.mmio", MMIO_SIZE);
+ memory_region_init_io(&s->system_config_region, OBJECT(dev),
+ &sm501_system_config_ops, s,
+ "sm501-system-config", 0x6c);
+ memory_region_add_subregion(&s->mmio_region, SM501_SYS_CONFIG,
+ &s->system_config_region);
+ memory_region_init_io(&s->i2c_region, OBJECT(dev), &sm501_i2c_ops, s,
+ "sm501-i2c", 0x14);
+ memory_region_add_subregion(&s->mmio_region, SM501_I2C, &s->i2c_region);
+ memory_region_init_io(&s->disp_ctrl_region, OBJECT(dev),
+ &sm501_disp_ctrl_ops, s,
+ "sm501-disp-ctrl", 0x1000);
+ memory_region_add_subregion(&s->mmio_region, SM501_DC,
+ &s->disp_ctrl_region);
+ memory_region_init_io(&s->twoD_engine_region, OBJECT(dev),
+ &sm501_2d_engine_ops, s,
+ "sm501-2d-engine", 0x54);
+ memory_region_add_subregion(&s->mmio_region, SM501_2D_ENGINE,
+ &s->twoD_engine_region);
+
+ /* create qemu graphic console */
+ s->con = graphic_console_init(dev, 0, &sm501_ops, s);
+}
+
+static const VMStateDescription vmstate_sm501_state = {
+ .name = "sm501-state",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(local_mem_size_index, SM501State),
+ VMSTATE_UINT32(system_control, SM501State),
+ VMSTATE_UINT32(misc_control, SM501State),
+ VMSTATE_UINT32(gpio_31_0_control, SM501State),
+ VMSTATE_UINT32(gpio_63_32_control, SM501State),
+ VMSTATE_UINT32(dram_control, SM501State),
+ VMSTATE_UINT32(arbitration_control, SM501State),
+ VMSTATE_UINT32(irq_mask, SM501State),
+ VMSTATE_UINT32(misc_timing, SM501State),
+ VMSTATE_UINT32(power_mode_control, SM501State),
+ VMSTATE_UINT32(uart0_ier, SM501State),
+ VMSTATE_UINT32(uart0_lcr, SM501State),
+ VMSTATE_UINT32(uart0_mcr, SM501State),
+ VMSTATE_UINT32(uart0_scr, SM501State),
+ VMSTATE_UINT8_ARRAY(dc_palette, SM501State, DC_PALETTE_ENTRIES),
+ VMSTATE_UINT32(dc_panel_control, SM501State),
+ VMSTATE_UINT32(dc_panel_panning_control, SM501State),
+ VMSTATE_UINT32(dc_panel_fb_addr, SM501State),
+ VMSTATE_UINT32(dc_panel_fb_offset, SM501State),
+ VMSTATE_UINT32(dc_panel_fb_width, SM501State),
+ VMSTATE_UINT32(dc_panel_fb_height, SM501State),
+ VMSTATE_UINT32(dc_panel_tl_location, SM501State),
+ VMSTATE_UINT32(dc_panel_br_location, SM501State),
+ VMSTATE_UINT32(dc_panel_h_total, SM501State),
+ VMSTATE_UINT32(dc_panel_h_sync, SM501State),
+ VMSTATE_UINT32(dc_panel_v_total, SM501State),
+ VMSTATE_UINT32(dc_panel_v_sync, SM501State),
+ VMSTATE_UINT32(dc_panel_hwc_addr, SM501State),
+ VMSTATE_UINT32(dc_panel_hwc_location, SM501State),
+ VMSTATE_UINT32(dc_panel_hwc_color_1_2, SM501State),
+ VMSTATE_UINT32(dc_panel_hwc_color_3, SM501State),
+ VMSTATE_UINT32(dc_video_control, SM501State),
+ VMSTATE_UINT32(dc_crt_control, SM501State),
+ VMSTATE_UINT32(dc_crt_fb_addr, SM501State),
+ VMSTATE_UINT32(dc_crt_fb_offset, SM501State),
+ VMSTATE_UINT32(dc_crt_h_total, SM501State),
+ VMSTATE_UINT32(dc_crt_h_sync, SM501State),
+ VMSTATE_UINT32(dc_crt_v_total, SM501State),
+ VMSTATE_UINT32(dc_crt_v_sync, SM501State),
+ VMSTATE_UINT32(dc_crt_hwc_addr, SM501State),
+ VMSTATE_UINT32(dc_crt_hwc_location, SM501State),
+ VMSTATE_UINT32(dc_crt_hwc_color_1_2, SM501State),
+ VMSTATE_UINT32(dc_crt_hwc_color_3, SM501State),
+ VMSTATE_UINT32(twoD_source, SM501State),
+ VMSTATE_UINT32(twoD_destination, SM501State),
+ VMSTATE_UINT32(twoD_dimension, SM501State),
+ VMSTATE_UINT32(twoD_control, SM501State),
+ VMSTATE_UINT32(twoD_pitch, SM501State),
+ VMSTATE_UINT32(twoD_foreground, SM501State),
+ VMSTATE_UINT32(twoD_background, SM501State),
+ VMSTATE_UINT32(twoD_stretch, SM501State),
+ VMSTATE_UINT32(twoD_color_compare, SM501State),
+ VMSTATE_UINT32(twoD_color_compare_mask, SM501State),
+ VMSTATE_UINT32(twoD_mask, SM501State),
+ VMSTATE_UINT32(twoD_clip_tl, SM501State),
+ VMSTATE_UINT32(twoD_clip_br, SM501State),
+ VMSTATE_UINT32(twoD_mono_pattern_low, SM501State),
+ VMSTATE_UINT32(twoD_mono_pattern_high, SM501State),
+ VMSTATE_UINT32(twoD_window_width, SM501State),
+ VMSTATE_UINT32(twoD_source_base, SM501State),
+ VMSTATE_UINT32(twoD_destination_base, SM501State),
+ VMSTATE_UINT32(twoD_alpha, SM501State),
+ VMSTATE_UINT32(twoD_wrap, SM501State),
+ /* Added in version 2 */
+ VMSTATE_UINT8(i2c_byte_count, SM501State),
+ VMSTATE_UINT8(i2c_status, SM501State),
+ VMSTATE_UINT8(i2c_addr, SM501State),
+ VMSTATE_UINT8_ARRAY(i2c_data, SM501State, 16),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+#define TYPE_SYSBUS_SM501 "sysbus-sm501"
+OBJECT_DECLARE_SIMPLE_TYPE(SM501SysBusState, SYSBUS_SM501)
+
+struct SM501SysBusState {
+ /*< private >*/
+ SysBusDevice parent_obj;
+ /*< public >*/
+ SM501State state;
+ uint32_t vram_size;
+ uint32_t base;
+ SerialMM serial;
+};
+
+static void sm501_realize_sysbus(DeviceState *dev, Error **errp)
+{
+ SM501SysBusState *s = SYSBUS_SM501(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+ DeviceState *usb_dev;
+ MemoryRegion *mr;
+
+ sm501_init(&s->state, dev, s->vram_size);
+ if (get_local_mem_size(&s->state) != s->vram_size) {
+ error_setg(errp, "Invalid VRAM size, nearest valid size is %" PRIu32,
+ get_local_mem_size(&s->state));
+ return;
+ }
+ sysbus_init_mmio(sbd, &s->state.local_mem_region);
+ sysbus_init_mmio(sbd, &s->state.mmio_region);
+
+ /* bridge to usb host emulation module */
+ usb_dev = qdev_new("sysbus-ohci");
+ qdev_prop_set_uint32(usb_dev, "num-ports", 2);
+ qdev_prop_set_uint64(usb_dev, "dma-offset", s->base);
+ sysbus_realize_and_unref(SYS_BUS_DEVICE(usb_dev), &error_fatal);
+ memory_region_add_subregion(&s->state.mmio_region, SM501_USB_HOST,
+ sysbus_mmio_get_region(SYS_BUS_DEVICE(usb_dev), 0));
+ sysbus_pass_irq(sbd, SYS_BUS_DEVICE(usb_dev));
+
+ /* bridge to serial emulation module */
+ sysbus_realize(SYS_BUS_DEVICE(&s->serial), &error_fatal);
+ mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->serial), 0);
+ memory_region_add_subregion(&s->state.mmio_region, SM501_UART0, mr);
+ /* TODO : chain irq to IRL */
+}
+
+static Property sm501_sysbus_properties[] = {
+ DEFINE_PROP_UINT32("vram-size", SM501SysBusState, vram_size, 0),
+ DEFINE_PROP_UINT32("base", SM501SysBusState, base, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void sm501_reset_sysbus(DeviceState *dev)
+{
+ SM501SysBusState *s = SYSBUS_SM501(dev);
+ sm501_reset(&s->state);
+}
+
+static const VMStateDescription vmstate_sm501_sysbus = {
+ .name = TYPE_SYSBUS_SM501,
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(state, SM501SysBusState, 1,
+ vmstate_sm501_state, SM501State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void sm501_sysbus_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = sm501_realize_sysbus;
+ set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories);
+ dc->desc = "SM501 Multimedia Companion";
+ device_class_set_props(dc, sm501_sysbus_properties);
+ dc->reset = sm501_reset_sysbus;
+ dc->vmsd = &vmstate_sm501_sysbus;
+}
+
+static void sm501_sysbus_init(Object *o)
+{
+ SM501SysBusState *sm501 = SYSBUS_SM501(o);
+ SerialMM *smm = &sm501->serial;
+
+ object_initialize_child(o, "serial", smm, TYPE_SERIAL_MM);
+ qdev_set_legacy_instance_id(DEVICE(smm), SM501_UART0, 2);
+ qdev_prop_set_uint8(DEVICE(smm), "regshift", 2);
+ qdev_prop_set_uint8(DEVICE(smm), "endianness", DEVICE_LITTLE_ENDIAN);
+
+ object_property_add_alias(o, "chardev",
+ OBJECT(smm), "chardev");
+}
+
+static const TypeInfo sm501_sysbus_info = {
+ .name = TYPE_SYSBUS_SM501,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(SM501SysBusState),
+ .class_init = sm501_sysbus_class_init,
+ .instance_init = sm501_sysbus_init,
+};
+
+#define TYPE_PCI_SM501 "sm501"
+OBJECT_DECLARE_SIMPLE_TYPE(SM501PCIState, PCI_SM501)
+
+struct SM501PCIState {
+ /*< private >*/
+ PCIDevice parent_obj;
+ /*< public >*/
+ SM501State state;
+ uint32_t vram_size;
+};
+
+static void sm501_realize_pci(PCIDevice *dev, Error **errp)
+{
+ SM501PCIState *s = PCI_SM501(dev);
+
+ sm501_init(&s->state, DEVICE(dev), s->vram_size);
+ if (get_local_mem_size(&s->state) != s->vram_size) {
+ error_setg(errp, "Invalid VRAM size, nearest valid size is %" PRIu32,
+ get_local_mem_size(&s->state));
+ return;
+ }
+ pci_register_bar(dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY,
+ &s->state.local_mem_region);
+ pci_register_bar(dev, 1, PCI_BASE_ADDRESS_SPACE_MEMORY,
+ &s->state.mmio_region);
+}
+
+static Property sm501_pci_properties[] = {
+ DEFINE_PROP_UINT32("vram-size", SM501PCIState, vram_size, 64 * MiB),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void sm501_reset_pci(DeviceState *dev)
+{
+ SM501PCIState *s = PCI_SM501(dev);
+ sm501_reset(&s->state);
+ /* Bits 2:0 of misc_control register is 001 for PCI */
+ s->state.misc_control |= 1;
+}
+
+static const VMStateDescription vmstate_sm501_pci = {
+ .name = TYPE_PCI_SM501,
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(parent_obj, SM501PCIState),
+ VMSTATE_STRUCT(state, SM501PCIState, 1,
+ vmstate_sm501_state, SM501State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void sm501_pci_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = sm501_realize_pci;
+ k->vendor_id = PCI_VENDOR_ID_SILICON_MOTION;
+ k->device_id = PCI_DEVICE_ID_SM501;
+ k->class_id = PCI_CLASS_DISPLAY_OTHER;
+ set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories);
+ dc->desc = "SM501 Display Controller";
+ device_class_set_props(dc, sm501_pci_properties);
+ dc->reset = sm501_reset_pci;
+ dc->hotpluggable = false;
+ dc->vmsd = &vmstate_sm501_pci;
+}
+
+static const TypeInfo sm501_pci_info = {
+ .name = TYPE_PCI_SM501,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(SM501PCIState),
+ .class_init = sm501_pci_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { INTERFACE_CONVENTIONAL_PCI_DEVICE },
+ { },
+ },
+};
+
+static void sm501_register_types(void)
+{
+ type_register_static(&sm501_sysbus_info);
+ type_register_static(&sm501_pci_info);
+}
+
+type_init(sm501_register_types)
diff --git a/hw/display/ssd0303.c b/hw/display/ssd0303.c
new file mode 100644
index 000000000..aeae22da9
--- /dev/null
+++ b/hw/display/ssd0303.c
@@ -0,0 +1,336 @@
+/*
+ * SSD0303 OLED controller with OSRAM Pictiva 96x16 display.
+ *
+ * Copyright (c) 2006-2007 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the GPL.
+ */
+
+/* The controller can support a variety of different displays, but we only
+ implement one. Most of the commends relating to brightness and geometry
+ setup are ignored. */
+
+#include "qemu/osdep.h"
+#include "hw/i2c/i2c.h"
+#include "migration/vmstate.h"
+#include "qemu/module.h"
+#include "ui/console.h"
+#include "qom/object.h"
+
+//#define DEBUG_SSD0303 1
+
+#ifdef DEBUG_SSD0303
+#define DPRINTF(fmt, ...) \
+do { printf("ssd0303: " fmt , ## __VA_ARGS__); } while (0)
+#define BADF(fmt, ...) \
+do { fprintf(stderr, "ssd0303: error: " fmt , ## __VA_ARGS__); exit(1);} while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while(0)
+#define BADF(fmt, ...) \
+do { fprintf(stderr, "ssd0303: error: " fmt , ## __VA_ARGS__);} while (0)
+#endif
+
+/* Scaling factor for pixels. */
+#define MAGNIFY 4
+
+enum ssd0303_mode
+{
+ SSD0303_IDLE,
+ SSD0303_DATA,
+ SSD0303_CMD
+};
+
+enum ssd0303_cmd {
+ SSD0303_CMD_NONE,
+ SSD0303_CMD_SKIP1
+};
+
+#define TYPE_SSD0303 "ssd0303"
+OBJECT_DECLARE_SIMPLE_TYPE(ssd0303_state, SSD0303)
+
+struct ssd0303_state {
+ I2CSlave parent_obj;
+
+ QemuConsole *con;
+ int row;
+ int col;
+ int start_line;
+ int mirror;
+ int flash;
+ int enabled;
+ int inverse;
+ int redraw;
+ enum ssd0303_mode mode;
+ enum ssd0303_cmd cmd_state;
+ uint8_t framebuffer[132*8];
+};
+
+static uint8_t ssd0303_recv(I2CSlave *i2c)
+{
+ BADF("Reads not implemented\n");
+ return 0xff;
+}
+
+static int ssd0303_send(I2CSlave *i2c, uint8_t data)
+{
+ ssd0303_state *s = SSD0303(i2c);
+ enum ssd0303_cmd old_cmd_state;
+
+ switch (s->mode) {
+ case SSD0303_IDLE:
+ DPRINTF("byte 0x%02x\n", data);
+ if (data == 0x80)
+ s->mode = SSD0303_CMD;
+ else if (data == 0x40)
+ s->mode = SSD0303_DATA;
+ else
+ BADF("Unexpected byte 0x%x\n", data);
+ break;
+ case SSD0303_DATA:
+ DPRINTF("data 0x%02x\n", data);
+ if (s->col < 132) {
+ s->framebuffer[s->col + s->row * 132] = data;
+ s->col++;
+ s->redraw = 1;
+ }
+ break;
+ case SSD0303_CMD:
+ old_cmd_state = s->cmd_state;
+ s->cmd_state = SSD0303_CMD_NONE;
+ switch (old_cmd_state) {
+ case SSD0303_CMD_NONE:
+ DPRINTF("cmd 0x%02x\n", data);
+ s->mode = SSD0303_IDLE;
+ switch (data) {
+ case 0x00 ... 0x0f: /* Set lower column address. */
+ s->col = (s->col & 0xf0) | (data & 0xf);
+ break;
+ case 0x10 ... 0x20: /* Set higher column address. */
+ s->col = (s->col & 0x0f) | ((data & 0xf) << 4);
+ break;
+ case 0x40 ... 0x7f: /* Set start line. */
+ s->start_line = 0;
+ break;
+ case 0x81: /* Set contrast (Ignored). */
+ s->cmd_state = SSD0303_CMD_SKIP1;
+ break;
+ case 0xa0: /* Mirror off. */
+ s->mirror = 0;
+ break;
+ case 0xa1: /* Mirror off. */
+ s->mirror = 1;
+ break;
+ case 0xa4: /* Entire display off. */
+ s->flash = 0;
+ break;
+ case 0xa5: /* Entire display on. */
+ s->flash = 1;
+ break;
+ case 0xa6: /* Inverse off. */
+ s->inverse = 0;
+ break;
+ case 0xa7: /* Inverse on. */
+ s->inverse = 1;
+ break;
+ case 0xa8: /* Set multiplied ratio (Ignored). */
+ s->cmd_state = SSD0303_CMD_SKIP1;
+ break;
+ case 0xad: /* DC-DC power control. */
+ s->cmd_state = SSD0303_CMD_SKIP1;
+ break;
+ case 0xae: /* Display off. */
+ s->enabled = 0;
+ break;
+ case 0xaf: /* Display on. */
+ s->enabled = 1;
+ break;
+ case 0xb0 ... 0xbf: /* Set Page address. */
+ s->row = data & 7;
+ break;
+ case 0xc0 ... 0xc8: /* Set COM output direction (Ignored). */
+ break;
+ case 0xd3: /* Set display offset (Ignored). */
+ s->cmd_state = SSD0303_CMD_SKIP1;
+ break;
+ case 0xd5: /* Set display clock (Ignored). */
+ s->cmd_state = SSD0303_CMD_SKIP1;
+ break;
+ case 0xd8: /* Set color and power mode (Ignored). */
+ s->cmd_state = SSD0303_CMD_SKIP1;
+ break;
+ case 0xd9: /* Set pre-charge period (Ignored). */
+ s->cmd_state = SSD0303_CMD_SKIP1;
+ break;
+ case 0xda: /* Set COM pin configuration (Ignored). */
+ s->cmd_state = SSD0303_CMD_SKIP1;
+ break;
+ case 0xdb: /* Set VCOM dselect level (Ignored). */
+ s->cmd_state = SSD0303_CMD_SKIP1;
+ break;
+ case 0xe3: /* no-op. */
+ break;
+ default:
+ BADF("Unknown command: 0x%x\n", data);
+ }
+ break;
+ case SSD0303_CMD_SKIP1:
+ DPRINTF("skip 0x%02x\n", data);
+ break;
+ }
+ break;
+ }
+ return 0;
+}
+
+static int ssd0303_event(I2CSlave *i2c, enum i2c_event event)
+{
+ ssd0303_state *s = SSD0303(i2c);
+
+ switch (event) {
+ case I2C_FINISH:
+ s->mode = SSD0303_IDLE;
+ break;
+ case I2C_START_RECV:
+ case I2C_START_SEND:
+ case I2C_NACK:
+ /* Nothing to do. */
+ break;
+ }
+
+ return 0;
+}
+
+static void ssd0303_update_display(void *opaque)
+{
+ ssd0303_state *s = (ssd0303_state *)opaque;
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ uint8_t *dest;
+ uint8_t *src;
+ int x;
+ int y;
+ int line;
+ char *colors[2];
+ char colortab[MAGNIFY * 8];
+ int dest_width;
+ uint8_t mask;
+
+ if (!s->redraw)
+ return;
+
+ switch (surface_bits_per_pixel(surface)) {
+ case 0:
+ return;
+ case 15:
+ dest_width = 2;
+ break;
+ case 16:
+ dest_width = 2;
+ break;
+ case 24:
+ dest_width = 3;
+ break;
+ case 32:
+ dest_width = 4;
+ break;
+ default:
+ BADF("Bad color depth\n");
+ return;
+ }
+ dest_width *= MAGNIFY;
+ memset(colortab, 0xff, dest_width);
+ memset(colortab + dest_width, 0, dest_width);
+ if (s->flash) {
+ colors[0] = colortab;
+ colors[1] = colortab;
+ } else if (s->inverse) {
+ colors[0] = colortab;
+ colors[1] = colortab + dest_width;
+ } else {
+ colors[0] = colortab + dest_width;
+ colors[1] = colortab;
+ }
+ dest = surface_data(surface);
+ for (y = 0; y < 16; y++) {
+ line = (y + s->start_line) & 63;
+ src = s->framebuffer + 132 * (line >> 3) + 36;
+ mask = 1 << (line & 7);
+ for (x = 0; x < 96; x++) {
+ memcpy(dest, colors[(*src & mask) != 0], dest_width);
+ dest += dest_width;
+ src++;
+ }
+ for (x = 1; x < MAGNIFY; x++) {
+ memcpy(dest, dest - dest_width * 96, dest_width * 96);
+ dest += dest_width * 96;
+ }
+ }
+ s->redraw = 0;
+ dpy_gfx_update(s->con, 0, 0, 96 * MAGNIFY, 16 * MAGNIFY);
+}
+
+static void ssd0303_invalidate_display(void * opaque)
+{
+ ssd0303_state *s = (ssd0303_state *)opaque;
+ s->redraw = 1;
+}
+
+static const VMStateDescription vmstate_ssd0303 = {
+ .name = "ssd0303_oled",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(row, ssd0303_state),
+ VMSTATE_INT32(col, ssd0303_state),
+ VMSTATE_INT32(start_line, ssd0303_state),
+ VMSTATE_INT32(mirror, ssd0303_state),
+ VMSTATE_INT32(flash, ssd0303_state),
+ VMSTATE_INT32(enabled, ssd0303_state),
+ VMSTATE_INT32(inverse, ssd0303_state),
+ VMSTATE_INT32(redraw, ssd0303_state),
+ VMSTATE_UINT32(mode, ssd0303_state),
+ VMSTATE_UINT32(cmd_state, ssd0303_state),
+ VMSTATE_BUFFER(framebuffer, ssd0303_state),
+ VMSTATE_I2C_SLAVE(parent_obj, ssd0303_state),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const GraphicHwOps ssd0303_ops = {
+ .invalidate = ssd0303_invalidate_display,
+ .gfx_update = ssd0303_update_display,
+};
+
+static void ssd0303_realize(DeviceState *dev, Error **errp)
+{
+ ssd0303_state *s = SSD0303(dev);
+
+ s->con = graphic_console_init(dev, 0, &ssd0303_ops, s);
+ qemu_console_resize(s->con, 96 * MAGNIFY, 16 * MAGNIFY);
+}
+
+static void ssd0303_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
+
+ dc->realize = ssd0303_realize;
+ k->event = ssd0303_event;
+ k->recv = ssd0303_recv;
+ k->send = ssd0303_send;
+ dc->vmsd = &vmstate_ssd0303;
+}
+
+static const TypeInfo ssd0303_info = {
+ .name = TYPE_SSD0303,
+ .parent = TYPE_I2C_SLAVE,
+ .instance_size = sizeof(ssd0303_state),
+ .class_init = ssd0303_class_init,
+};
+
+static void ssd0303_register_types(void)
+{
+ type_register_static(&ssd0303_info);
+}
+
+type_init(ssd0303_register_types)
diff --git a/hw/display/ssd0323.c b/hw/display/ssd0323.c
new file mode 100644
index 000000000..ab229d32b
--- /dev/null
+++ b/hw/display/ssd0323.c
@@ -0,0 +1,388 @@
+/*
+ * SSD0323 OLED controller with OSRAM Pictiva 128x64 display.
+ *
+ * Copyright (c) 2006-2007 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the GPL.
+ */
+
+/* The controller can support a variety of different displays, but we only
+ implement one. Most of the commends relating to brightness and geometry
+ setup are ignored. */
+
+#include "qemu/osdep.h"
+#include "hw/ssi/ssi.h"
+#include "migration/vmstate.h"
+#include "qemu/module.h"
+#include "ui/console.h"
+#include "qom/object.h"
+
+//#define DEBUG_SSD0323 1
+
+#ifdef DEBUG_SSD0323
+#define DPRINTF(fmt, ...) \
+do { printf("ssd0323: " fmt , ## __VA_ARGS__); } while (0)
+#define BADF(fmt, ...) \
+do { \
+ fprintf(stderr, "ssd0323: error: " fmt , ## __VA_ARGS__); abort(); \
+} while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while(0)
+#define BADF(fmt, ...) \
+do { fprintf(stderr, "ssd0323: error: " fmt , ## __VA_ARGS__);} while (0)
+#endif
+
+/* Scaling factor for pixels. */
+#define MAGNIFY 4
+
+#define REMAP_SWAP_COLUMN 0x01
+#define REMAP_SWAP_NYBBLE 0x02
+#define REMAP_VERTICAL 0x04
+#define REMAP_SWAP_COM 0x10
+#define REMAP_SPLIT_COM 0x40
+
+enum ssd0323_mode
+{
+ SSD0323_CMD,
+ SSD0323_DATA
+};
+
+struct ssd0323_state {
+ SSIPeripheral ssidev;
+ QemuConsole *con;
+
+ uint32_t cmd_len;
+ int32_t cmd;
+ int32_t cmd_data[8];
+ int32_t row;
+ int32_t row_start;
+ int32_t row_end;
+ int32_t col;
+ int32_t col_start;
+ int32_t col_end;
+ int32_t redraw;
+ int32_t remap;
+ uint32_t mode;
+ uint8_t framebuffer[128 * 80 / 2];
+};
+
+#define TYPE_SSD0323 "ssd0323"
+OBJECT_DECLARE_SIMPLE_TYPE(ssd0323_state, SSD0323)
+
+
+static uint32_t ssd0323_transfer(SSIPeripheral *dev, uint32_t data)
+{
+ ssd0323_state *s = SSD0323(dev);
+
+ switch (s->mode) {
+ case SSD0323_DATA:
+ DPRINTF("data 0x%02x\n", data);
+ s->framebuffer[s->col + s->row * 64] = data;
+ if (s->remap & REMAP_VERTICAL) {
+ s->row++;
+ if (s->row > s->row_end) {
+ s->row = s->row_start;
+ s->col++;
+ }
+ if (s->col > s->col_end) {
+ s->col = s->col_start;
+ }
+ } else {
+ s->col++;
+ if (s->col > s->col_end) {
+ s->row++;
+ s->col = s->col_start;
+ }
+ if (s->row > s->row_end) {
+ s->row = s->row_start;
+ }
+ }
+ s->redraw = 1;
+ break;
+ case SSD0323_CMD:
+ DPRINTF("cmd 0x%02x\n", data);
+ if (s->cmd_len == 0) {
+ s->cmd = data;
+ } else {
+ s->cmd_data[s->cmd_len - 1] = data;
+ }
+ s->cmd_len++;
+ switch (s->cmd) {
+#define DATA(x) if (s->cmd_len <= (x)) return 0
+ case 0x15: /* Set column. */
+ DATA(2);
+ s->col = s->col_start = s->cmd_data[0] % 64;
+ s->col_end = s->cmd_data[1] % 64;
+ break;
+ case 0x75: /* Set row. */
+ DATA(2);
+ s->row = s->row_start = s->cmd_data[0] % 80;
+ s->row_end = s->cmd_data[1] % 80;
+ break;
+ case 0x81: /* Set contrast */
+ DATA(1);
+ break;
+ case 0x84: case 0x85: case 0x86: /* Max current. */
+ DATA(0);
+ break;
+ case 0xa0: /* Set remapping. */
+ /* FIXME: Implement this. */
+ DATA(1);
+ s->remap = s->cmd_data[0];
+ break;
+ case 0xa1: /* Set display start line. */
+ case 0xa2: /* Set display offset. */
+ /* FIXME: Implement these. */
+ DATA(1);
+ break;
+ case 0xa4: /* Normal mode. */
+ case 0xa5: /* All on. */
+ case 0xa6: /* All off. */
+ case 0xa7: /* Inverse. */
+ /* FIXME: Implement these. */
+ DATA(0);
+ break;
+ case 0xa8: /* Set multiplex ratio. */
+ case 0xad: /* Set DC-DC converter. */
+ DATA(1);
+ /* Ignored. Don't care. */
+ break;
+ case 0xae: /* Display off. */
+ case 0xaf: /* Display on. */
+ DATA(0);
+ /* TODO: Implement power control. */
+ break;
+ case 0xb1: /* Set phase length. */
+ case 0xb2: /* Set row period. */
+ case 0xb3: /* Set clock rate. */
+ case 0xbc: /* Set precharge. */
+ case 0xbe: /* Set VCOMH. */
+ case 0xbf: /* Set segment low. */
+ DATA(1);
+ /* Ignored. Don't care. */
+ break;
+ case 0xb8: /* Set grey scale table. */
+ /* FIXME: Implement this. */
+ DATA(8);
+ break;
+ case 0xe3: /* NOP. */
+ DATA(0);
+ break;
+ case 0xff: /* Nasty hack because we don't handle chip selects
+ properly. */
+ break;
+ default:
+ BADF("Unknown command: 0x%x\n", data);
+ }
+ s->cmd_len = 0;
+ return 0;
+ }
+ return 0;
+}
+
+static void ssd0323_update_display(void *opaque)
+{
+ ssd0323_state *s = (ssd0323_state *)opaque;
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ uint8_t *dest;
+ uint8_t *src;
+ int x;
+ int y;
+ int i;
+ int line;
+ char *colors[16];
+ char colortab[MAGNIFY * 64];
+ char *p;
+ int dest_width;
+
+ if (!s->redraw)
+ return;
+
+ switch (surface_bits_per_pixel(surface)) {
+ case 0:
+ return;
+ case 15:
+ dest_width = 2;
+ break;
+ case 16:
+ dest_width = 2;
+ break;
+ case 24:
+ dest_width = 3;
+ break;
+ case 32:
+ dest_width = 4;
+ break;
+ default:
+ BADF("Bad color depth\n");
+ return;
+ }
+ p = colortab;
+ for (i = 0; i < 16; i++) {
+ int n;
+ colors[i] = p;
+ switch (surface_bits_per_pixel(surface)) {
+ case 15:
+ n = i * 2 + (i >> 3);
+ p[0] = n | (n << 5);
+ p[1] = (n << 2) | (n >> 3);
+ break;
+ case 16:
+ n = i * 2 + (i >> 3);
+ p[0] = n | (n << 6) | ((n << 1) & 0x20);
+ p[1] = (n << 3) | (n >> 2);
+ break;
+ case 24:
+ case 32:
+ n = (i << 4) | i;
+ p[0] = p[1] = p[2] = n;
+ break;
+ default:
+ BADF("Bad color depth\n");
+ return;
+ }
+ p += dest_width;
+ }
+ /* TODO: Implement row/column remapping. */
+ dest = surface_data(surface);
+ for (y = 0; y < 64; y++) {
+ line = y;
+ src = s->framebuffer + 64 * line;
+ for (x = 0; x < 64; x++) {
+ int val;
+ val = *src >> 4;
+ for (i = 0; i < MAGNIFY; i++) {
+ memcpy(dest, colors[val], dest_width);
+ dest += dest_width;
+ }
+ val = *src & 0xf;
+ for (i = 0; i < MAGNIFY; i++) {
+ memcpy(dest, colors[val], dest_width);
+ dest += dest_width;
+ }
+ src++;
+ }
+ for (i = 1; i < MAGNIFY; i++) {
+ memcpy(dest, dest - dest_width * MAGNIFY * 128,
+ dest_width * 128 * MAGNIFY);
+ dest += dest_width * 128 * MAGNIFY;
+ }
+ }
+ s->redraw = 0;
+ dpy_gfx_update(s->con, 0, 0, 128 * MAGNIFY, 64 * MAGNIFY);
+}
+
+static void ssd0323_invalidate_display(void * opaque)
+{
+ ssd0323_state *s = (ssd0323_state *)opaque;
+ s->redraw = 1;
+}
+
+/* Command/data input. */
+static void ssd0323_cd(void *opaque, int n, int level)
+{
+ ssd0323_state *s = (ssd0323_state *)opaque;
+ DPRINTF("%s mode\n", level ? "Data" : "Command");
+ s->mode = level ? SSD0323_DATA : SSD0323_CMD;
+}
+
+static int ssd0323_post_load(void *opaque, int version_id)
+{
+ ssd0323_state *s = (ssd0323_state *)opaque;
+
+ if (s->cmd_len > ARRAY_SIZE(s->cmd_data)) {
+ return -EINVAL;
+ }
+ if (s->row < 0 || s->row >= 80) {
+ return -EINVAL;
+ }
+ if (s->row_start < 0 || s->row_start >= 80) {
+ return -EINVAL;
+ }
+ if (s->row_end < 0 || s->row_end >= 80) {
+ return -EINVAL;
+ }
+ if (s->col < 0 || s->col >= 64) {
+ return -EINVAL;
+ }
+ if (s->col_start < 0 || s->col_start >= 64) {
+ return -EINVAL;
+ }
+ if (s->col_end < 0 || s->col_end >= 64) {
+ return -EINVAL;
+ }
+ if (s->mode != SSD0323_CMD && s->mode != SSD0323_DATA) {
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_ssd0323 = {
+ .name = "ssd0323_oled",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .post_load = ssd0323_post_load,
+ .fields = (VMStateField []) {
+ VMSTATE_UINT32(cmd_len, ssd0323_state),
+ VMSTATE_INT32(cmd, ssd0323_state),
+ VMSTATE_INT32_ARRAY(cmd_data, ssd0323_state, 8),
+ VMSTATE_INT32(row, ssd0323_state),
+ VMSTATE_INT32(row_start, ssd0323_state),
+ VMSTATE_INT32(row_end, ssd0323_state),
+ VMSTATE_INT32(col, ssd0323_state),
+ VMSTATE_INT32(col_start, ssd0323_state),
+ VMSTATE_INT32(col_end, ssd0323_state),
+ VMSTATE_INT32(redraw, ssd0323_state),
+ VMSTATE_INT32(remap, ssd0323_state),
+ VMSTATE_UINT32(mode, ssd0323_state),
+ VMSTATE_BUFFER(framebuffer, ssd0323_state),
+ VMSTATE_SSI_PERIPHERAL(ssidev, ssd0323_state),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const GraphicHwOps ssd0323_ops = {
+ .invalidate = ssd0323_invalidate_display,
+ .gfx_update = ssd0323_update_display,
+};
+
+static void ssd0323_realize(SSIPeripheral *d, Error **errp)
+{
+ DeviceState *dev = DEVICE(d);
+ ssd0323_state *s = SSD0323(d);
+
+ s->col_end = 63;
+ s->row_end = 79;
+ s->con = graphic_console_init(dev, 0, &ssd0323_ops, s);
+ qemu_console_resize(s->con, 128 * MAGNIFY, 64 * MAGNIFY);
+
+ qdev_init_gpio_in(dev, ssd0323_cd, 1);
+}
+
+static void ssd0323_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SSIPeripheralClass *k = SSI_PERIPHERAL_CLASS(klass);
+
+ k->realize = ssd0323_realize;
+ k->transfer = ssd0323_transfer;
+ k->cs_polarity = SSI_CS_HIGH;
+ dc->vmsd = &vmstate_ssd0323;
+ set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories);
+}
+
+static const TypeInfo ssd0323_info = {
+ .name = TYPE_SSD0323,
+ .parent = TYPE_SSI_PERIPHERAL,
+ .instance_size = sizeof(ssd0323_state),
+ .class_init = ssd0323_class_init,
+};
+
+static void ssd03232_register_types(void)
+{
+ type_register_static(&ssd0323_info);
+}
+
+type_init(ssd03232_register_types)
diff --git a/hw/display/tc6393xb.c b/hw/display/tc6393xb.c
new file mode 100644
index 000000000..1f28223c7
--- /dev/null
+++ b/hw/display/tc6393xb.c
@@ -0,0 +1,568 @@
+/*
+ * Toshiba TC6393XB I/O Controller.
+ * Found in Sharp Zaurus SL-6000 (tosa) or some
+ * Toshiba e-Series PDAs.
+ *
+ * Most features are currently unsupported!!!
+ *
+ * This code is licensed under the GNU GPL v2.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "qemu/host-utils.h"
+#include "hw/irq.h"
+#include "hw/display/tc6393xb.h"
+#include "exec/memory.h"
+#include "hw/block/flash.h"
+#include "ui/console.h"
+#include "ui/pixel_ops.h"
+#include "sysemu/blockdev.h"
+
+#define IRQ_TC6393_NAND 0
+#define IRQ_TC6393_MMC 1
+#define IRQ_TC6393_OHCI 2
+#define IRQ_TC6393_SERIAL 3
+#define IRQ_TC6393_FB 4
+
+#define TC6393XB_NR_IRQS 8
+
+#define TC6393XB_GPIOS 16
+
+#define SCR_REVID 0x08 /* b Revision ID */
+#define SCR_ISR 0x50 /* b Interrupt Status */
+#define SCR_IMR 0x52 /* b Interrupt Mask */
+#define SCR_IRR 0x54 /* b Interrupt Routing */
+#define SCR_GPER 0x60 /* w GP Enable */
+#define SCR_GPI_SR(i) (0x64 + (i)) /* b3 GPI Status */
+#define SCR_GPI_IMR(i) (0x68 + (i)) /* b3 GPI INT Mask */
+#define SCR_GPI_EDER(i) (0x6c + (i)) /* b3 GPI Edge Detect Enable */
+#define SCR_GPI_LIR(i) (0x70 + (i)) /* b3 GPI Level Invert */
+#define SCR_GPO_DSR(i) (0x78 + (i)) /* b3 GPO Data Set */
+#define SCR_GPO_DOECR(i) (0x7c + (i)) /* b3 GPO Data OE Control */
+#define SCR_GP_IARCR(i) (0x80 + (i)) /* b3 GP Internal Active Register Control */
+#define SCR_GP_IARLCR(i) (0x84 + (i)) /* b3 GP INTERNAL Active Register Level Control */
+#define SCR_GPI_BCR(i) (0x88 + (i)) /* b3 GPI Buffer Control */
+#define SCR_GPA_IARCR 0x8c /* w GPa Internal Active Register Control */
+#define SCR_GPA_IARLCR 0x90 /* w GPa Internal Active Register Level Control */
+#define SCR_GPA_BCR 0x94 /* w GPa Buffer Control */
+#define SCR_CCR 0x98 /* w Clock Control */
+#define SCR_PLL2CR 0x9a /* w PLL2 Control */
+#define SCR_PLL1CR 0x9c /* l PLL1 Control */
+#define SCR_DIARCR 0xa0 /* b Device Internal Active Register Control */
+#define SCR_DBOCR 0xa1 /* b Device Buffer Off Control */
+#define SCR_FER 0xe0 /* b Function Enable */
+#define SCR_MCR 0xe4 /* w Mode Control */
+#define SCR_CONFIG 0xfc /* b Configuration Control */
+#define SCR_DEBUG 0xff /* b Debug */
+
+#define NAND_CFG_COMMAND 0x04 /* w Command */
+#define NAND_CFG_BASE 0x10 /* l Control Base Address */
+#define NAND_CFG_INTP 0x3d /* b Interrupt Pin */
+#define NAND_CFG_INTE 0x48 /* b Int Enable */
+#define NAND_CFG_EC 0x4a /* b Event Control */
+#define NAND_CFG_ICC 0x4c /* b Internal Clock Control */
+#define NAND_CFG_ECCC 0x5b /* b ECC Control */
+#define NAND_CFG_NFTC 0x60 /* b NAND Flash Transaction Control */
+#define NAND_CFG_NFM 0x61 /* b NAND Flash Monitor */
+#define NAND_CFG_NFPSC 0x62 /* b NAND Flash Power Supply Control */
+#define NAND_CFG_NFDC 0x63 /* b NAND Flash Detect Control */
+
+#define NAND_DATA 0x00 /* l Data */
+#define NAND_MODE 0x04 /* b Mode */
+#define NAND_STATUS 0x05 /* b Status */
+#define NAND_ISR 0x06 /* b Interrupt Status */
+#define NAND_IMR 0x07 /* b Interrupt Mask */
+
+#define NAND_MODE_WP 0x80
+#define NAND_MODE_CE 0x10
+#define NAND_MODE_ALE 0x02
+#define NAND_MODE_CLE 0x01
+#define NAND_MODE_ECC_MASK 0x60
+#define NAND_MODE_ECC_EN 0x20
+#define NAND_MODE_ECC_READ 0x40
+#define NAND_MODE_ECC_RST 0x60
+
+struct TC6393xbState {
+ MemoryRegion iomem;
+ qemu_irq irq;
+ qemu_irq *sub_irqs;
+ struct {
+ uint8_t ISR;
+ uint8_t IMR;
+ uint8_t IRR;
+ uint16_t GPER;
+ uint8_t GPI_SR[3];
+ uint8_t GPI_IMR[3];
+ uint8_t GPI_EDER[3];
+ uint8_t GPI_LIR[3];
+ uint8_t GP_IARCR[3];
+ uint8_t GP_IARLCR[3];
+ uint8_t GPI_BCR[3];
+ uint16_t GPA_IARCR;
+ uint16_t GPA_IARLCR;
+ uint16_t CCR;
+ uint16_t PLL2CR;
+ uint32_t PLL1CR;
+ uint8_t DIARCR;
+ uint8_t DBOCR;
+ uint8_t FER;
+ uint16_t MCR;
+ uint8_t CONFIG;
+ uint8_t DEBUG;
+ } scr;
+ uint32_t gpio_dir;
+ uint32_t gpio_level;
+ uint32_t prev_level;
+ qemu_irq handler[TC6393XB_GPIOS];
+ qemu_irq *gpio_in;
+
+ struct {
+ uint8_t mode;
+ uint8_t isr;
+ uint8_t imr;
+ } nand;
+ int nand_enable;
+ uint32_t nand_phys;
+ DeviceState *flash;
+ ECCState ecc;
+
+ QemuConsole *con;
+ MemoryRegion vram;
+ uint16_t *vram_ptr;
+ uint32_t scr_width, scr_height; /* in pixels */
+ qemu_irq l3v;
+ unsigned blank : 1,
+ blanked : 1;
+};
+
+static void tc6393xb_gpio_set(void *opaque, int line, int level)
+{
+// TC6393xbState *s = opaque;
+
+ if (line > TC6393XB_GPIOS) {
+ printf("%s: No GPIO pin %i\n", __func__, line);
+ return;
+ }
+
+ // FIXME: how does the chip reflect the GPIO input level change?
+}
+
+static void tc6393xb_gpio_handler_update(TC6393xbState *s)
+{
+ uint32_t level, diff;
+ int bit;
+
+ level = s->gpio_level & s->gpio_dir;
+ level &= MAKE_64BIT_MASK(0, TC6393XB_GPIOS);
+
+ for (diff = s->prev_level ^ level; diff; diff ^= 1 << bit) {
+ bit = ctz32(diff);
+ qemu_set_irq(s->handler[bit], (level >> bit) & 1);
+ }
+
+ s->prev_level = level;
+}
+
+qemu_irq tc6393xb_l3v_get(TC6393xbState *s)
+{
+ return s->l3v;
+}
+
+static void tc6393xb_l3v(void *opaque, int line, int level)
+{
+ TC6393xbState *s = opaque;
+ s->blank = !level;
+ fprintf(stderr, "L3V: %d\n", level);
+}
+
+static void tc6393xb_sub_irq(void *opaque, int line, int level) {
+ TC6393xbState *s = opaque;
+ uint8_t isr = s->scr.ISR;
+ if (level)
+ isr |= 1 << line;
+ else
+ isr &= ~(1 << line);
+ s->scr.ISR = isr;
+ qemu_set_irq(s->irq, isr & s->scr.IMR);
+}
+
+#define SCR_REG_B(N) \
+ case SCR_ ##N: return s->scr.N
+#define SCR_REG_W(N) \
+ case SCR_ ##N: return s->scr.N; \
+ case SCR_ ##N + 1: return s->scr.N >> 8;
+#define SCR_REG_L(N) \
+ case SCR_ ##N: return s->scr.N; \
+ case SCR_ ##N + 1: return s->scr.N >> 8; \
+ case SCR_ ##N + 2: return s->scr.N >> 16; \
+ case SCR_ ##N + 3: return s->scr.N >> 24;
+#define SCR_REG_A(N) \
+ case SCR_ ##N(0): return s->scr.N[0]; \
+ case SCR_ ##N(1): return s->scr.N[1]; \
+ case SCR_ ##N(2): return s->scr.N[2]
+
+static uint32_t tc6393xb_scr_readb(TC6393xbState *s, hwaddr addr)
+{
+ switch (addr) {
+ case SCR_REVID:
+ return 3;
+ case SCR_REVID+1:
+ return 0;
+ SCR_REG_B(ISR);
+ SCR_REG_B(IMR);
+ SCR_REG_B(IRR);
+ SCR_REG_W(GPER);
+ SCR_REG_A(GPI_SR);
+ SCR_REG_A(GPI_IMR);
+ SCR_REG_A(GPI_EDER);
+ SCR_REG_A(GPI_LIR);
+ case SCR_GPO_DSR(0):
+ case SCR_GPO_DSR(1):
+ case SCR_GPO_DSR(2):
+ return (s->gpio_level >> ((addr - SCR_GPO_DSR(0)) * 8)) & 0xff;
+ case SCR_GPO_DOECR(0):
+ case SCR_GPO_DOECR(1):
+ case SCR_GPO_DOECR(2):
+ return (s->gpio_dir >> ((addr - SCR_GPO_DOECR(0)) * 8)) & 0xff;
+ SCR_REG_A(GP_IARCR);
+ SCR_REG_A(GP_IARLCR);
+ SCR_REG_A(GPI_BCR);
+ SCR_REG_W(GPA_IARCR);
+ SCR_REG_W(GPA_IARLCR);
+ SCR_REG_W(CCR);
+ SCR_REG_W(PLL2CR);
+ SCR_REG_L(PLL1CR);
+ SCR_REG_B(DIARCR);
+ SCR_REG_B(DBOCR);
+ SCR_REG_B(FER);
+ SCR_REG_W(MCR);
+ SCR_REG_B(CONFIG);
+ SCR_REG_B(DEBUG);
+ }
+ fprintf(stderr, "tc6393xb_scr: unhandled read at %08x\n", (uint32_t) addr);
+ return 0;
+}
+#undef SCR_REG_B
+#undef SCR_REG_W
+#undef SCR_REG_L
+#undef SCR_REG_A
+
+#define SCR_REG_B(N) \
+ case SCR_ ##N: s->scr.N = value; return;
+#define SCR_REG_W(N) \
+ case SCR_ ##N: s->scr.N = (s->scr.N & ~0xff) | (value & 0xff); return; \
+ case SCR_ ##N + 1: s->scr.N = (s->scr.N & 0xff) | (value << 8); return
+#define SCR_REG_L(N) \
+ case SCR_ ##N: s->scr.N = (s->scr.N & ~0xff) | (value & 0xff); return; \
+ case SCR_ ##N + 1: s->scr.N = (s->scr.N & ~(0xff << 8)) | (value & (0xff << 8)); return; \
+ case SCR_ ##N + 2: s->scr.N = (s->scr.N & ~(0xff << 16)) | (value & (0xff << 16)); return; \
+ case SCR_ ##N + 3: s->scr.N = (s->scr.N & ~(0xff << 24)) | (value & (0xff << 24)); return;
+#define SCR_REG_A(N) \
+ case SCR_ ##N(0): s->scr.N[0] = value; return; \
+ case SCR_ ##N(1): s->scr.N[1] = value; return; \
+ case SCR_ ##N(2): s->scr.N[2] = value; return
+
+static void tc6393xb_scr_writeb(TC6393xbState *s, hwaddr addr, uint32_t value)
+{
+ switch (addr) {
+ SCR_REG_B(ISR);
+ SCR_REG_B(IMR);
+ SCR_REG_B(IRR);
+ SCR_REG_W(GPER);
+ SCR_REG_A(GPI_SR);
+ SCR_REG_A(GPI_IMR);
+ SCR_REG_A(GPI_EDER);
+ SCR_REG_A(GPI_LIR);
+ case SCR_GPO_DSR(0):
+ case SCR_GPO_DSR(1):
+ case SCR_GPO_DSR(2):
+ s->gpio_level = (s->gpio_level & ~(0xff << ((addr - SCR_GPO_DSR(0))*8))) | ((value & 0xff) << ((addr - SCR_GPO_DSR(0))*8));
+ tc6393xb_gpio_handler_update(s);
+ return;
+ case SCR_GPO_DOECR(0):
+ case SCR_GPO_DOECR(1):
+ case SCR_GPO_DOECR(2):
+ s->gpio_dir = (s->gpio_dir & ~(0xff << ((addr - SCR_GPO_DOECR(0))*8))) | ((value & 0xff) << ((addr - SCR_GPO_DOECR(0))*8));
+ tc6393xb_gpio_handler_update(s);
+ return;
+ SCR_REG_A(GP_IARCR);
+ SCR_REG_A(GP_IARLCR);
+ SCR_REG_A(GPI_BCR);
+ SCR_REG_W(GPA_IARCR);
+ SCR_REG_W(GPA_IARLCR);
+ SCR_REG_W(CCR);
+ SCR_REG_W(PLL2CR);
+ SCR_REG_L(PLL1CR);
+ SCR_REG_B(DIARCR);
+ SCR_REG_B(DBOCR);
+ SCR_REG_B(FER);
+ SCR_REG_W(MCR);
+ SCR_REG_B(CONFIG);
+ SCR_REG_B(DEBUG);
+ }
+ fprintf(stderr, "tc6393xb_scr: unhandled write at %08x: %02x\n",
+ (uint32_t) addr, value & 0xff);
+}
+#undef SCR_REG_B
+#undef SCR_REG_W
+#undef SCR_REG_L
+#undef SCR_REG_A
+
+static void tc6393xb_nand_irq(TC6393xbState *s) {
+ qemu_set_irq(s->sub_irqs[IRQ_TC6393_NAND],
+ (s->nand.imr & 0x80) && (s->nand.imr & s->nand.isr));
+}
+
+static uint32_t tc6393xb_nand_cfg_readb(TC6393xbState *s, hwaddr addr) {
+ switch (addr) {
+ case NAND_CFG_COMMAND:
+ return s->nand_enable ? 2 : 0;
+ case NAND_CFG_BASE:
+ case NAND_CFG_BASE + 1:
+ case NAND_CFG_BASE + 2:
+ case NAND_CFG_BASE + 3:
+ return s->nand_phys >> (addr - NAND_CFG_BASE);
+ }
+ fprintf(stderr, "tc6393xb_nand_cfg: unhandled read at %08x\n", (uint32_t) addr);
+ return 0;
+}
+static void tc6393xb_nand_cfg_writeb(TC6393xbState *s, hwaddr addr, uint32_t value) {
+ switch (addr) {
+ case NAND_CFG_COMMAND:
+ s->nand_enable = (value & 0x2);
+ return;
+ case NAND_CFG_BASE:
+ case NAND_CFG_BASE + 1:
+ case NAND_CFG_BASE + 2:
+ case NAND_CFG_BASE + 3:
+ s->nand_phys &= ~(0xff << ((addr - NAND_CFG_BASE) * 8));
+ s->nand_phys |= (value & 0xff) << ((addr - NAND_CFG_BASE) * 8);
+ return;
+ }
+ fprintf(stderr, "tc6393xb_nand_cfg: unhandled write at %08x: %02x\n",
+ (uint32_t) addr, value & 0xff);
+}
+
+static uint32_t tc6393xb_nand_readb(TC6393xbState *s, hwaddr addr) {
+ switch (addr) {
+ case NAND_DATA + 0:
+ case NAND_DATA + 1:
+ case NAND_DATA + 2:
+ case NAND_DATA + 3:
+ return nand_getio(s->flash);
+ case NAND_MODE:
+ return s->nand.mode;
+ case NAND_STATUS:
+ return 0x14;
+ case NAND_ISR:
+ return s->nand.isr;
+ case NAND_IMR:
+ return s->nand.imr;
+ }
+ fprintf(stderr, "tc6393xb_nand: unhandled read at %08x\n", (uint32_t) addr);
+ return 0;
+}
+static void tc6393xb_nand_writeb(TC6393xbState *s, hwaddr addr, uint32_t value) {
+// fprintf(stderr, "tc6393xb_nand: write at %08x: %02x\n",
+// (uint32_t) addr, value & 0xff);
+ switch (addr) {
+ case NAND_DATA + 0:
+ case NAND_DATA + 1:
+ case NAND_DATA + 2:
+ case NAND_DATA + 3:
+ nand_setio(s->flash, value);
+ s->nand.isr |= 1;
+ tc6393xb_nand_irq(s);
+ return;
+ case NAND_MODE:
+ s->nand.mode = value;
+ nand_setpins(s->flash,
+ value & NAND_MODE_CLE,
+ value & NAND_MODE_ALE,
+ !(value & NAND_MODE_CE),
+ value & NAND_MODE_WP,
+ 0); // FIXME: gnd
+ switch (value & NAND_MODE_ECC_MASK) {
+ case NAND_MODE_ECC_RST:
+ ecc_reset(&s->ecc);
+ break;
+ case NAND_MODE_ECC_READ:
+ // FIXME
+ break;
+ case NAND_MODE_ECC_EN:
+ ecc_reset(&s->ecc);
+ }
+ return;
+ case NAND_ISR:
+ s->nand.isr = value;
+ tc6393xb_nand_irq(s);
+ return;
+ case NAND_IMR:
+ s->nand.imr = value;
+ tc6393xb_nand_irq(s);
+ return;
+ }
+ fprintf(stderr, "tc6393xb_nand: unhandled write at %08x: %02x\n",
+ (uint32_t) addr, value & 0xff);
+}
+
+static void tc6393xb_draw_graphic(TC6393xbState *s, int full_update)
+{
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ int i;
+ uint16_t *data_buffer;
+ uint8_t *data_display;
+
+ data_buffer = s->vram_ptr;
+ data_display = surface_data(surface);
+ for (i = 0; i < s->scr_height; i++) {
+ int j;
+ for (j = 0; j < s->scr_width; j++, data_display += 4, data_buffer++) {
+ uint16_t color = *data_buffer;
+ uint32_t dest_color = rgb_to_pixel32(
+ ((color & 0xf800) * 0x108) >> 11,
+ ((color & 0x7e0) * 0x41) >> 9,
+ ((color & 0x1f) * 0x21) >> 2
+ );
+ *(uint32_t *)data_display = dest_color;
+ }
+ }
+ dpy_gfx_update_full(s->con);
+}
+
+static void tc6393xb_draw_blank(TC6393xbState *s, int full_update)
+{
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ int i, w;
+ uint8_t *d;
+
+ if (!full_update)
+ return;
+
+ w = s->scr_width * surface_bytes_per_pixel(surface);
+ d = surface_data(surface);
+ for(i = 0; i < s->scr_height; i++) {
+ memset(d, 0, w);
+ d += surface_stride(surface);
+ }
+
+ dpy_gfx_update_full(s->con);
+}
+
+static void tc6393xb_update_display(void *opaque)
+{
+ TC6393xbState *s = opaque;
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ int full_update;
+
+ if (s->scr_width == 0 || s->scr_height == 0)
+ return;
+
+ full_update = 0;
+ if (s->blanked != s->blank) {
+ s->blanked = s->blank;
+ full_update = 1;
+ }
+ if (s->scr_width != surface_width(surface) ||
+ s->scr_height != surface_height(surface)) {
+ qemu_console_resize(s->con, s->scr_width, s->scr_height);
+ full_update = 1;
+ }
+ if (s->blanked)
+ tc6393xb_draw_blank(s, full_update);
+ else
+ tc6393xb_draw_graphic(s, full_update);
+}
+
+
+static uint64_t tc6393xb_readb(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ TC6393xbState *s = opaque;
+
+ switch (addr >> 8) {
+ case 0:
+ return tc6393xb_scr_readb(s, addr & 0xff);
+ case 1:
+ return tc6393xb_nand_cfg_readb(s, addr & 0xff);
+ };
+
+ if ((addr &~0xff) == s->nand_phys && s->nand_enable) {
+// return tc6393xb_nand_readb(s, addr & 0xff);
+ uint8_t d = tc6393xb_nand_readb(s, addr & 0xff);
+// fprintf(stderr, "tc6393xb_nand: read at %08x: %02hhx\n", (uint32_t) addr, d);
+ return d;
+ }
+
+// fprintf(stderr, "tc6393xb: unhandled read at %08x\n", (uint32_t) addr);
+ return 0;
+}
+
+static void tc6393xb_writeb(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size) {
+ TC6393xbState *s = opaque;
+
+ switch (addr >> 8) {
+ case 0:
+ tc6393xb_scr_writeb(s, addr & 0xff, value);
+ return;
+ case 1:
+ tc6393xb_nand_cfg_writeb(s, addr & 0xff, value);
+ return;
+ };
+
+ if ((addr &~0xff) == s->nand_phys && s->nand_enable)
+ tc6393xb_nand_writeb(s, addr & 0xff, value);
+ else
+ fprintf(stderr, "tc6393xb: unhandled write at %08x: %02x\n",
+ (uint32_t) addr, (int)value & 0xff);
+}
+
+static const GraphicHwOps tc6393xb_gfx_ops = {
+ .gfx_update = tc6393xb_update_display,
+};
+
+TC6393xbState *tc6393xb_init(MemoryRegion *sysmem, uint32_t base, qemu_irq irq)
+{
+ TC6393xbState *s;
+ DriveInfo *nand;
+ static const MemoryRegionOps tc6393xb_ops = {
+ .read = tc6393xb_readb,
+ .write = tc6393xb_writeb,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+ };
+
+ s = (TC6393xbState *) g_malloc0(sizeof(TC6393xbState));
+ s->irq = irq;
+ s->gpio_in = qemu_allocate_irqs(tc6393xb_gpio_set, s, TC6393XB_GPIOS);
+
+ s->l3v = qemu_allocate_irq(tc6393xb_l3v, s, 0);
+ s->blanked = 1;
+
+ s->sub_irqs = qemu_allocate_irqs(tc6393xb_sub_irq, s, TC6393XB_NR_IRQS);
+
+ nand = drive_get(IF_MTD, 0, 0);
+ s->flash = nand_init(nand ? blk_by_legacy_dinfo(nand) : NULL,
+ NAND_MFR_TOSHIBA, 0x76);
+
+ memory_region_init_io(&s->iomem, NULL, &tc6393xb_ops, s, "tc6393xb", 0x10000);
+ memory_region_add_subregion(sysmem, base, &s->iomem);
+
+ memory_region_init_ram(&s->vram, NULL, "tc6393xb.vram", 0x100000,
+ &error_fatal);
+ s->vram_ptr = memory_region_get_ram_ptr(&s->vram);
+ memory_region_add_subregion(sysmem, base + 0x100000, &s->vram);
+ s->scr_width = 480;
+ s->scr_height = 640;
+ s->con = graphic_console_init(NULL, 0, &tc6393xb_gfx_ops, s);
+
+ return s;
+}
diff --git a/hw/display/tcx.c b/hw/display/tcx.c
new file mode 100644
index 000000000..d4d09d0df
--- /dev/null
+++ b/hw/display/tcx.c
@@ -0,0 +1,914 @@
+/*
+ * QEMU TCX Frame buffer
+ *
+ * Copyright (c) 2003-2005 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu-common.h"
+#include "qemu/datadir.h"
+#include "qapi/error.h"
+#include "ui/console.h"
+#include "ui/pixel_ops.h"
+#include "hw/loader.h"
+#include "hw/qdev-properties.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "qemu/error-report.h"
+#include "qemu/module.h"
+#include "qom/object.h"
+
+#define TCX_ROM_FILE "QEMU,tcx.bin"
+#define FCODE_MAX_ROM_SIZE 0x10000
+
+#define MAXX 1024
+#define MAXY 768
+#define TCX_DAC_NREGS 16
+#define TCX_THC_NREGS 0x1000
+#define TCX_DHC_NREGS 0x4000
+#define TCX_TEC_NREGS 0x1000
+#define TCX_ALT_NREGS 0x8000
+#define TCX_STIP_NREGS 0x800000
+#define TCX_BLIT_NREGS 0x800000
+#define TCX_RSTIP_NREGS 0x800000
+#define TCX_RBLIT_NREGS 0x800000
+
+#define TCX_THC_MISC 0x818
+#define TCX_THC_CURSXY 0x8fc
+#define TCX_THC_CURSMASK 0x900
+#define TCX_THC_CURSBITS 0x980
+
+#define TYPE_TCX "sun-tcx"
+OBJECT_DECLARE_SIMPLE_TYPE(TCXState, TCX)
+
+struct TCXState {
+ SysBusDevice parent_obj;
+
+ QemuConsole *con;
+ qemu_irq irq;
+ uint8_t *vram;
+ uint32_t *vram24, *cplane;
+ hwaddr prom_addr;
+ MemoryRegion rom;
+ MemoryRegion vram_mem;
+ MemoryRegion vram_8bit;
+ MemoryRegion vram_24bit;
+ MemoryRegion stip;
+ MemoryRegion blit;
+ MemoryRegion vram_cplane;
+ MemoryRegion rstip;
+ MemoryRegion rblit;
+ MemoryRegion tec;
+ MemoryRegion dac;
+ MemoryRegion thc;
+ MemoryRegion dhc;
+ MemoryRegion alt;
+ MemoryRegion thc24;
+
+ ram_addr_t vram24_offset, cplane_offset;
+ uint32_t tmpblit;
+ uint32_t vram_size;
+ uint32_t palette[260];
+ uint8_t r[260], g[260], b[260];
+ uint16_t width, height, depth;
+ uint8_t dac_index, dac_state;
+ uint32_t thcmisc;
+ uint32_t cursmask[32];
+ uint32_t cursbits[32];
+ uint16_t cursx;
+ uint16_t cursy;
+};
+
+static void tcx_set_dirty(TCXState *s, ram_addr_t addr, int len)
+{
+ memory_region_set_dirty(&s->vram_mem, addr, len);
+
+ if (s->depth == 24) {
+ memory_region_set_dirty(&s->vram_mem, s->vram24_offset + addr * 4,
+ len * 4);
+ memory_region_set_dirty(&s->vram_mem, s->cplane_offset + addr * 4,
+ len * 4);
+ }
+}
+
+static int tcx_check_dirty(TCXState *s, DirtyBitmapSnapshot *snap,
+ ram_addr_t addr, int len)
+{
+ int ret;
+
+ ret = memory_region_snapshot_get_dirty(&s->vram_mem, snap, addr, len);
+
+ if (s->depth == 24) {
+ ret |= memory_region_snapshot_get_dirty(&s->vram_mem, snap,
+ s->vram24_offset + addr * 4, len * 4);
+ ret |= memory_region_snapshot_get_dirty(&s->vram_mem, snap,
+ s->cplane_offset + addr * 4, len * 4);
+ }
+
+ return ret;
+}
+
+static void update_palette_entries(TCXState *s, int start, int end)
+{
+ int i;
+
+ for (i = start; i < end; i++) {
+ s->palette[i] = rgb_to_pixel32(s->r[i], s->g[i], s->b[i]);
+ }
+ tcx_set_dirty(s, 0, memory_region_size(&s->vram_mem));
+}
+
+static void tcx_draw_line32(TCXState *s1, uint8_t *d,
+ const uint8_t *s, int width)
+{
+ int x;
+ uint8_t val;
+ uint32_t *p = (uint32_t *)d;
+
+ for (x = 0; x < width; x++) {
+ val = *s++;
+ *p++ = s1->palette[val];
+ }
+}
+
+static void tcx_draw_cursor32(TCXState *s1, uint8_t *d,
+ int y, int width)
+{
+ int x, len;
+ uint32_t mask, bits;
+ uint32_t *p = (uint32_t *)d;
+
+ y = y - s1->cursy;
+ mask = s1->cursmask[y];
+ bits = s1->cursbits[y];
+ len = MIN(width - s1->cursx, 32);
+ p = &p[s1->cursx];
+ for (x = 0; x < len; x++) {
+ if (mask & 0x80000000) {
+ if (bits & 0x80000000) {
+ *p = s1->palette[259];
+ } else {
+ *p = s1->palette[258];
+ }
+ }
+ p++;
+ mask <<= 1;
+ bits <<= 1;
+ }
+}
+
+/*
+ * XXX Could be much more optimal:
+ * detect if line/page/whole screen is in 24 bit mode
+ */
+static inline void tcx24_draw_line32(TCXState *s1, uint8_t *d,
+ const uint8_t *s, int width,
+ const uint32_t *cplane,
+ const uint32_t *s24)
+{
+ int x, r, g, b;
+ uint8_t val, *p8;
+ uint32_t *p = (uint32_t *)d;
+ uint32_t dval;
+ for(x = 0; x < width; x++, s++, s24++) {
+ if (be32_to_cpu(*cplane) & 0x03000000) {
+ /* 24-bit direct, BGR order */
+ p8 = (uint8_t *)s24;
+ p8++;
+ b = *p8++;
+ g = *p8++;
+ r = *p8;
+ dval = rgb_to_pixel32(r, g, b);
+ } else {
+ /* 8-bit pseudocolor */
+ val = *s;
+ dval = s1->palette[val];
+ }
+ *p++ = dval;
+ cplane++;
+ }
+}
+
+/* Fixed line length 1024 allows us to do nice tricks not possible on
+ VGA... */
+
+static void tcx_update_display(void *opaque)
+{
+ TCXState *ts = opaque;
+ DisplaySurface *surface = qemu_console_surface(ts->con);
+ ram_addr_t page;
+ DirtyBitmapSnapshot *snap = NULL;
+ int y, y_start, dd, ds;
+ uint8_t *d, *s;
+
+ assert(surface_bits_per_pixel(surface) == 32);
+
+ page = 0;
+ y_start = -1;
+ d = surface_data(surface);
+ s = ts->vram;
+ dd = surface_stride(surface);
+ ds = 1024;
+
+ snap = memory_region_snapshot_and_clear_dirty(&ts->vram_mem, 0x0,
+ memory_region_size(&ts->vram_mem),
+ DIRTY_MEMORY_VGA);
+
+ for (y = 0; y < ts->height; y++, page += ds) {
+ if (tcx_check_dirty(ts, snap, page, ds)) {
+ if (y_start < 0)
+ y_start = y;
+
+ tcx_draw_line32(ts, d, s, ts->width);
+ if (y >= ts->cursy && y < ts->cursy + 32 && ts->cursx < ts->width) {
+ tcx_draw_cursor32(ts, d, y, ts->width);
+ }
+ } else {
+ if (y_start >= 0) {
+ /* flush to display */
+ dpy_gfx_update(ts->con, 0, y_start,
+ ts->width, y - y_start);
+ y_start = -1;
+ }
+ }
+ s += ds;
+ d += dd;
+ }
+ if (y_start >= 0) {
+ /* flush to display */
+ dpy_gfx_update(ts->con, 0, y_start,
+ ts->width, y - y_start);
+ }
+ g_free(snap);
+}
+
+static void tcx24_update_display(void *opaque)
+{
+ TCXState *ts = opaque;
+ DisplaySurface *surface = qemu_console_surface(ts->con);
+ ram_addr_t page;
+ DirtyBitmapSnapshot *snap = NULL;
+ int y, y_start, dd, ds;
+ uint8_t *d, *s;
+ uint32_t *cptr, *s24;
+
+ assert(surface_bits_per_pixel(surface) == 32);
+
+ page = 0;
+ y_start = -1;
+ d = surface_data(surface);
+ s = ts->vram;
+ s24 = ts->vram24;
+ cptr = ts->cplane;
+ dd = surface_stride(surface);
+ ds = 1024;
+
+ snap = memory_region_snapshot_and_clear_dirty(&ts->vram_mem, 0x0,
+ memory_region_size(&ts->vram_mem),
+ DIRTY_MEMORY_VGA);
+
+ for (y = 0; y < ts->height; y++, page += ds) {
+ if (tcx_check_dirty(ts, snap, page, ds)) {
+ if (y_start < 0)
+ y_start = y;
+
+ tcx24_draw_line32(ts, d, s, ts->width, cptr, s24);
+ if (y >= ts->cursy && y < ts->cursy+32 && ts->cursx < ts->width) {
+ tcx_draw_cursor32(ts, d, y, ts->width);
+ }
+ } else {
+ if (y_start >= 0) {
+ /* flush to display */
+ dpy_gfx_update(ts->con, 0, y_start,
+ ts->width, y - y_start);
+ y_start = -1;
+ }
+ }
+ d += dd;
+ s += ds;
+ cptr += ds;
+ s24 += ds;
+ }
+ if (y_start >= 0) {
+ /* flush to display */
+ dpy_gfx_update(ts->con, 0, y_start,
+ ts->width, y - y_start);
+ }
+ g_free(snap);
+}
+
+static void tcx_invalidate_display(void *opaque)
+{
+ TCXState *s = opaque;
+
+ tcx_set_dirty(s, 0, memory_region_size(&s->vram_mem));
+ qemu_console_resize(s->con, s->width, s->height);
+}
+
+static void tcx24_invalidate_display(void *opaque)
+{
+ TCXState *s = opaque;
+
+ tcx_set_dirty(s, 0, memory_region_size(&s->vram_mem));
+ qemu_console_resize(s->con, s->width, s->height);
+}
+
+static int vmstate_tcx_post_load(void *opaque, int version_id)
+{
+ TCXState *s = opaque;
+
+ update_palette_entries(s, 0, 256);
+ tcx_set_dirty(s, 0, memory_region_size(&s->vram_mem));
+ return 0;
+}
+
+static const VMStateDescription vmstate_tcx = {
+ .name ="tcx",
+ .version_id = 4,
+ .minimum_version_id = 4,
+ .post_load = vmstate_tcx_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT16(height, TCXState),
+ VMSTATE_UINT16(width, TCXState),
+ VMSTATE_UINT16(depth, TCXState),
+ VMSTATE_BUFFER(r, TCXState),
+ VMSTATE_BUFFER(g, TCXState),
+ VMSTATE_BUFFER(b, TCXState),
+ VMSTATE_UINT8(dac_index, TCXState),
+ VMSTATE_UINT8(dac_state, TCXState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void tcx_reset(DeviceState *d)
+{
+ TCXState *s = TCX(d);
+
+ /* Initialize palette */
+ memset(s->r, 0, 260);
+ memset(s->g, 0, 260);
+ memset(s->b, 0, 260);
+ s->r[255] = s->g[255] = s->b[255] = 255;
+ s->r[256] = s->g[256] = s->b[256] = 255;
+ s->r[258] = s->g[258] = s->b[258] = 255;
+ update_palette_entries(s, 0, 260);
+ memset(s->vram, 0, MAXX*MAXY);
+ memory_region_reset_dirty(&s->vram_mem, 0, MAXX * MAXY * (1 + 4 + 4),
+ DIRTY_MEMORY_VGA);
+ s->dac_index = 0;
+ s->dac_state = 0;
+ s->cursx = 0xf000; /* Put cursor off screen */
+ s->cursy = 0xf000;
+}
+
+static uint64_t tcx_dac_readl(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ TCXState *s = opaque;
+ uint32_t val = 0;
+
+ switch (s->dac_state) {
+ case 0:
+ val = s->r[s->dac_index] << 24;
+ s->dac_state++;
+ break;
+ case 1:
+ val = s->g[s->dac_index] << 24;
+ s->dac_state++;
+ break;
+ case 2:
+ val = s->b[s->dac_index] << 24;
+ s->dac_index = (s->dac_index + 1) & 0xff; /* Index autoincrement */
+ /* fall through */
+ default:
+ s->dac_state = 0;
+ break;
+ }
+
+ return val;
+}
+
+static void tcx_dac_writel(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ TCXState *s = opaque;
+ unsigned index;
+
+ switch (addr) {
+ case 0: /* Address */
+ s->dac_index = val >> 24;
+ s->dac_state = 0;
+ break;
+ case 4: /* Pixel colours */
+ case 12: /* Overlay (cursor) colours */
+ if (addr & 8) {
+ index = (s->dac_index & 3) + 256;
+ } else {
+ index = s->dac_index;
+ }
+ switch (s->dac_state) {
+ case 0:
+ s->r[index] = val >> 24;
+ update_palette_entries(s, index, index + 1);
+ s->dac_state++;
+ break;
+ case 1:
+ s->g[index] = val >> 24;
+ update_palette_entries(s, index, index + 1);
+ s->dac_state++;
+ break;
+ case 2:
+ s->b[index] = val >> 24;
+ update_palette_entries(s, index, index + 1);
+ s->dac_index = (s->dac_index + 1) & 0xff; /* Index autoincrement */
+ /* fall through */
+ default:
+ s->dac_state = 0;
+ break;
+ }
+ break;
+ default: /* Control registers */
+ break;
+ }
+}
+
+static const MemoryRegionOps tcx_dac_ops = {
+ .read = tcx_dac_readl,
+ .write = tcx_dac_writel,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static uint64_t tcx_stip_readl(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ return 0;
+}
+
+static void tcx_stip_writel(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ TCXState *s = opaque;
+ int i;
+ uint32_t col;
+
+ if (!(addr & 4)) {
+ s->tmpblit = val;
+ } else {
+ addr = (addr >> 3) & 0xfffff;
+ col = cpu_to_be32(s->tmpblit);
+ if (s->depth == 24) {
+ for (i = 0; i < 32; i++) {
+ if (val & 0x80000000) {
+ s->vram[addr + i] = s->tmpblit;
+ s->vram24[addr + i] = col;
+ }
+ val <<= 1;
+ }
+ } else {
+ for (i = 0; i < 32; i++) {
+ if (val & 0x80000000) {
+ s->vram[addr + i] = s->tmpblit;
+ }
+ val <<= 1;
+ }
+ }
+ tcx_set_dirty(s, addr, 32);
+ }
+}
+
+static void tcx_rstip_writel(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ TCXState *s = opaque;
+ int i;
+ uint32_t col;
+
+ if (!(addr & 4)) {
+ s->tmpblit = val;
+ } else {
+ addr = (addr >> 3) & 0xfffff;
+ col = cpu_to_be32(s->tmpblit);
+ if (s->depth == 24) {
+ for (i = 0; i < 32; i++) {
+ if (val & 0x80000000) {
+ s->vram[addr + i] = s->tmpblit;
+ s->vram24[addr + i] = col;
+ s->cplane[addr + i] = col;
+ }
+ val <<= 1;
+ }
+ } else {
+ for (i = 0; i < 32; i++) {
+ if (val & 0x80000000) {
+ s->vram[addr + i] = s->tmpblit;
+ }
+ val <<= 1;
+ }
+ }
+ tcx_set_dirty(s, addr, 32);
+ }
+}
+
+static const MemoryRegionOps tcx_stip_ops = {
+ .read = tcx_stip_readl,
+ .write = tcx_stip_writel,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 8,
+ },
+};
+
+static const MemoryRegionOps tcx_rstip_ops = {
+ .read = tcx_stip_readl,
+ .write = tcx_rstip_writel,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 8,
+ },
+};
+
+static uint64_t tcx_blit_readl(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ return 0;
+}
+
+static void tcx_blit_writel(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ TCXState *s = opaque;
+ uint32_t adsr, len;
+ int i;
+
+ if (!(addr & 4)) {
+ s->tmpblit = val;
+ } else {
+ addr = (addr >> 3) & 0xfffff;
+ adsr = val & 0xffffff;
+ len = ((val >> 24) & 0x1f) + 1;
+ if (adsr == 0xffffff) {
+ memset(&s->vram[addr], s->tmpblit, len);
+ if (s->depth == 24) {
+ val = s->tmpblit & 0xffffff;
+ val = cpu_to_be32(val);
+ for (i = 0; i < len; i++) {
+ s->vram24[addr + i] = val;
+ }
+ }
+ } else {
+ memcpy(&s->vram[addr], &s->vram[adsr], len);
+ if (s->depth == 24) {
+ memcpy(&s->vram24[addr], &s->vram24[adsr], len * 4);
+ }
+ }
+ tcx_set_dirty(s, addr, len);
+ }
+}
+
+static void tcx_rblit_writel(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ TCXState *s = opaque;
+ uint32_t adsr, len;
+ int i;
+
+ if (!(addr & 4)) {
+ s->tmpblit = val;
+ } else {
+ addr = (addr >> 3) & 0xfffff;
+ adsr = val & 0xffffff;
+ len = ((val >> 24) & 0x1f) + 1;
+ if (adsr == 0xffffff) {
+ memset(&s->vram[addr], s->tmpblit, len);
+ if (s->depth == 24) {
+ val = s->tmpblit & 0xffffff;
+ val = cpu_to_be32(val);
+ for (i = 0; i < len; i++) {
+ s->vram24[addr + i] = val;
+ s->cplane[addr + i] = val;
+ }
+ }
+ } else {
+ memcpy(&s->vram[addr], &s->vram[adsr], len);
+ if (s->depth == 24) {
+ memcpy(&s->vram24[addr], &s->vram24[adsr], len * 4);
+ memcpy(&s->cplane[addr], &s->cplane[adsr], len * 4);
+ }
+ }
+ tcx_set_dirty(s, addr, len);
+ }
+}
+
+static const MemoryRegionOps tcx_blit_ops = {
+ .read = tcx_blit_readl,
+ .write = tcx_blit_writel,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 8,
+ },
+};
+
+static const MemoryRegionOps tcx_rblit_ops = {
+ .read = tcx_blit_readl,
+ .write = tcx_rblit_writel,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 8,
+ },
+};
+
+static void tcx_invalidate_cursor_position(TCXState *s)
+{
+ int ymin, ymax, start, end;
+
+ /* invalidate only near the cursor */
+ ymin = s->cursy;
+ if (ymin >= s->height) {
+ return;
+ }
+ ymax = MIN(s->height, ymin + 32);
+ start = ymin * 1024;
+ end = ymax * 1024;
+
+ tcx_set_dirty(s, start, end - start);
+}
+
+static uint64_t tcx_thc_readl(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ TCXState *s = opaque;
+ uint64_t val;
+
+ if (addr == TCX_THC_MISC) {
+ val = s->thcmisc | 0x02000000;
+ } else {
+ val = 0;
+ }
+ return val;
+}
+
+static void tcx_thc_writel(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ TCXState *s = opaque;
+
+ if (addr == TCX_THC_CURSXY) {
+ tcx_invalidate_cursor_position(s);
+ s->cursx = val >> 16;
+ s->cursy = val;
+ tcx_invalidate_cursor_position(s);
+ } else if (addr >= TCX_THC_CURSMASK && addr < TCX_THC_CURSMASK + 128) {
+ s->cursmask[(addr - TCX_THC_CURSMASK) >> 2] = val;
+ tcx_invalidate_cursor_position(s);
+ } else if (addr >= TCX_THC_CURSBITS && addr < TCX_THC_CURSBITS + 128) {
+ s->cursbits[(addr - TCX_THC_CURSBITS) >> 2] = val;
+ tcx_invalidate_cursor_position(s);
+ } else if (addr == TCX_THC_MISC) {
+ s->thcmisc = val;
+ }
+
+}
+
+static const MemoryRegionOps tcx_thc_ops = {
+ .read = tcx_thc_readl,
+ .write = tcx_thc_writel,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static uint64_t tcx_dummy_readl(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ return 0;
+}
+
+static void tcx_dummy_writel(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ return;
+}
+
+static const MemoryRegionOps tcx_dummy_ops = {
+ .read = tcx_dummy_readl,
+ .write = tcx_dummy_writel,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static const GraphicHwOps tcx_ops = {
+ .invalidate = tcx_invalidate_display,
+ .gfx_update = tcx_update_display,
+};
+
+static const GraphicHwOps tcx24_ops = {
+ .invalidate = tcx24_invalidate_display,
+ .gfx_update = tcx24_update_display,
+};
+
+static void tcx_initfn(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ TCXState *s = TCX(obj);
+
+ memory_region_init_rom_nomigrate(&s->rom, obj, "tcx.prom",
+ FCODE_MAX_ROM_SIZE, &error_fatal);
+ sysbus_init_mmio(sbd, &s->rom);
+
+ /* 2/STIP : Stippler */
+ memory_region_init_io(&s->stip, obj, &tcx_stip_ops, s, "tcx.stip",
+ TCX_STIP_NREGS);
+ sysbus_init_mmio(sbd, &s->stip);
+
+ /* 3/BLIT : Blitter */
+ memory_region_init_io(&s->blit, obj, &tcx_blit_ops, s, "tcx.blit",
+ TCX_BLIT_NREGS);
+ sysbus_init_mmio(sbd, &s->blit);
+
+ /* 5/RSTIP : Raw Stippler */
+ memory_region_init_io(&s->rstip, obj, &tcx_rstip_ops, s, "tcx.rstip",
+ TCX_RSTIP_NREGS);
+ sysbus_init_mmio(sbd, &s->rstip);
+
+ /* 6/RBLIT : Raw Blitter */
+ memory_region_init_io(&s->rblit, obj, &tcx_rblit_ops, s, "tcx.rblit",
+ TCX_RBLIT_NREGS);
+ sysbus_init_mmio(sbd, &s->rblit);
+
+ /* 7/TEC : ??? */
+ memory_region_init_io(&s->tec, obj, &tcx_dummy_ops, s, "tcx.tec",
+ TCX_TEC_NREGS);
+ sysbus_init_mmio(sbd, &s->tec);
+
+ /* 8/CMAP : DAC */
+ memory_region_init_io(&s->dac, obj, &tcx_dac_ops, s, "tcx.dac",
+ TCX_DAC_NREGS);
+ sysbus_init_mmio(sbd, &s->dac);
+
+ /* 9/THC : Cursor */
+ memory_region_init_io(&s->thc, obj, &tcx_thc_ops, s, "tcx.thc",
+ TCX_THC_NREGS);
+ sysbus_init_mmio(sbd, &s->thc);
+
+ /* 11/DHC : ??? */
+ memory_region_init_io(&s->dhc, obj, &tcx_dummy_ops, s, "tcx.dhc",
+ TCX_DHC_NREGS);
+ sysbus_init_mmio(sbd, &s->dhc);
+
+ /* 12/ALT : ??? */
+ memory_region_init_io(&s->alt, obj, &tcx_dummy_ops, s, "tcx.alt",
+ TCX_ALT_NREGS);
+ sysbus_init_mmio(sbd, &s->alt);
+}
+
+static void tcx_realizefn(DeviceState *dev, Error **errp)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+ TCXState *s = TCX(dev);
+ ram_addr_t vram_offset = 0;
+ int size, ret;
+ uint8_t *vram_base;
+ char *fcode_filename;
+
+ memory_region_init_ram_nomigrate(&s->vram_mem, OBJECT(s), "tcx.vram",
+ s->vram_size * (1 + 4 + 4), &error_fatal);
+ vmstate_register_ram_global(&s->vram_mem);
+ memory_region_set_log(&s->vram_mem, true, DIRTY_MEMORY_VGA);
+ vram_base = memory_region_get_ram_ptr(&s->vram_mem);
+
+ /* 10/ROM : FCode ROM */
+ vmstate_register_ram_global(&s->rom);
+ fcode_filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, TCX_ROM_FILE);
+ if (fcode_filename) {
+ ret = load_image_mr(fcode_filename, &s->rom);
+ g_free(fcode_filename);
+ if (ret < 0 || ret > FCODE_MAX_ROM_SIZE) {
+ warn_report("tcx: could not load prom '%s'", TCX_ROM_FILE);
+ }
+ }
+
+ /* 0/DFB8 : 8-bit plane */
+ s->vram = vram_base;
+ size = s->vram_size;
+ memory_region_init_alias(&s->vram_8bit, OBJECT(s), "tcx.vram.8bit",
+ &s->vram_mem, vram_offset, size);
+ sysbus_init_mmio(sbd, &s->vram_8bit);
+ vram_offset += size;
+ vram_base += size;
+
+ /* 1/DFB24 : 24bit plane */
+ size = s->vram_size * 4;
+ s->vram24 = (uint32_t *)vram_base;
+ s->vram24_offset = vram_offset;
+ memory_region_init_alias(&s->vram_24bit, OBJECT(s), "tcx.vram.24bit",
+ &s->vram_mem, vram_offset, size);
+ sysbus_init_mmio(sbd, &s->vram_24bit);
+ vram_offset += size;
+ vram_base += size;
+
+ /* 4/RDFB32 : Raw Framebuffer */
+ size = s->vram_size * 4;
+ s->cplane = (uint32_t *)vram_base;
+ s->cplane_offset = vram_offset;
+ memory_region_init_alias(&s->vram_cplane, OBJECT(s), "tcx.vram.cplane",
+ &s->vram_mem, vram_offset, size);
+ sysbus_init_mmio(sbd, &s->vram_cplane);
+
+ /* 9/THC24bits : NetBSD writes here even with 8-bit display: dummy */
+ if (s->depth == 8) {
+ memory_region_init_io(&s->thc24, OBJECT(s), &tcx_dummy_ops, s,
+ "tcx.thc24", TCX_THC_NREGS);
+ sysbus_init_mmio(sbd, &s->thc24);
+ }
+
+ sysbus_init_irq(sbd, &s->irq);
+
+ if (s->depth == 8) {
+ s->con = graphic_console_init(dev, 0, &tcx_ops, s);
+ } else {
+ s->con = graphic_console_init(dev, 0, &tcx24_ops, s);
+ }
+ s->thcmisc = 0;
+
+ qemu_console_resize(s->con, s->width, s->height);
+}
+
+static Property tcx_properties[] = {
+ DEFINE_PROP_UINT32("vram_size", TCXState, vram_size, -1),
+ DEFINE_PROP_UINT16("width", TCXState, width, -1),
+ DEFINE_PROP_UINT16("height", TCXState, height, -1),
+ DEFINE_PROP_UINT16("depth", TCXState, depth, -1),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void tcx_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = tcx_realizefn;
+ dc->reset = tcx_reset;
+ dc->vmsd = &vmstate_tcx;
+ device_class_set_props(dc, tcx_properties);
+}
+
+static const TypeInfo tcx_info = {
+ .name = TYPE_TCX,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(TCXState),
+ .instance_init = tcx_initfn,
+ .class_init = tcx_class_init,
+};
+
+static void tcx_register_types(void)
+{
+ type_register_static(&tcx_info);
+}
+
+type_init(tcx_register_types)
diff --git a/hw/display/trace-events b/hw/display/trace-events
new file mode 100644
index 000000000..3a7a2c957
--- /dev/null
+++ b/hw/display/trace-events
@@ -0,0 +1,176 @@
+# See docs/devel/tracing.rst for syntax documentation.
+
+# jazz_led.c
+jazz_led_read(uint64_t addr, uint8_t val) "read addr=0x%"PRIx64": 0x%x"
+jazz_led_write(uint64_t addr, uint8_t new) "write addr=0x%"PRIx64": 0x%x"
+
+# xenfb.c
+xenfb_mouse_event(void *opaque, int dx, int dy, int dz, int button_state, int abs_pointer_wanted) "%p x %d y %d z %d bs 0x%x abs %d"
+xenfb_key_event(void *opaque, int scancode, int button_state) "%p scancode %d bs 0x%x"
+xenfb_input_connected(void *xendev, int abs_pointer_wanted) "%p abs %d"
+
+# g364fb.c
+g364fb_read(uint64_t addr, uint32_t val) "read addr=0x%"PRIx64": 0x%x"
+g364fb_write(uint64_t addr, uint32_t new) "write addr=0x%"PRIx64": 0x%x"
+
+# vmware_vga.c
+vmware_value_read(uint32_t index, uint32_t value) "index %d, value 0x%x"
+vmware_value_write(uint32_t index, uint32_t value) "index %d, value 0x%x"
+vmware_palette_read(uint32_t index, uint32_t value) "index %d, value 0x%x"
+vmware_palette_write(uint32_t index, uint32_t value) "index %d, value 0x%x"
+vmware_scratch_read(uint32_t index, uint32_t value) "index %d, value 0x%x"
+vmware_scratch_write(uint32_t index, uint32_t value) "index %d, value 0x%x"
+vmware_setmode(uint32_t w, uint32_t h, uint32_t bpp) "%dx%d @ %d bpp"
+
+# virtio-gpu-base.c
+virtio_gpu_features(bool virgl) "virgl %d"
+
+# virtio-gpu-3d.c
+# virtio-gpu.c
+virtio_gpu_cmd_get_display_info(void) ""
+virtio_gpu_cmd_get_edid(uint32_t scanout) "scanout %d"
+virtio_gpu_cmd_set_scanout(uint32_t id, uint32_t res, uint32_t w, uint32_t h, uint32_t x, uint32_t y) "id %d, res 0x%x, w %d, h %d, x %d, y %d"
+virtio_gpu_cmd_set_scanout_blob(uint32_t id, uint32_t res, uint32_t w, uint32_t h, uint32_t x, uint32_t y) "id %d, res 0x%x, w %d, h %d, x %d, y %d"
+virtio_gpu_cmd_res_create_2d(uint32_t res, uint32_t fmt, uint32_t w, uint32_t h) "res 0x%x, fmt 0x%x, w %d, h %d"
+virtio_gpu_cmd_res_create_3d(uint32_t res, uint32_t fmt, uint32_t w, uint32_t h, uint32_t d) "res 0x%x, fmt 0x%x, w %d, h %d, d %d"
+virtio_gpu_cmd_res_create_blob(uint32_t res, uint64_t size) "res 0x%x, size %" PRId64
+virtio_gpu_cmd_res_unref(uint32_t res) "res 0x%x"
+virtio_gpu_cmd_res_back_attach(uint32_t res) "res 0x%x"
+virtio_gpu_cmd_res_back_detach(uint32_t res) "res 0x%x"
+virtio_gpu_cmd_res_xfer_toh_2d(uint32_t res) "res 0x%x"
+virtio_gpu_cmd_res_xfer_toh_3d(uint32_t res) "res 0x%x"
+virtio_gpu_cmd_res_xfer_fromh_3d(uint32_t res) "res 0x%x"
+virtio_gpu_cmd_res_flush(uint32_t res, uint32_t w, uint32_t h, uint32_t x, uint32_t y) "res 0x%x, w %d, h %d, x %d, y %d"
+virtio_gpu_cmd_ctx_create(uint32_t ctx, const char *name) "ctx 0x%x, name %s"
+virtio_gpu_cmd_ctx_destroy(uint32_t ctx) "ctx 0x%x"
+virtio_gpu_cmd_ctx_res_attach(uint32_t ctx, uint32_t res) "ctx 0x%x, res 0x%x"
+virtio_gpu_cmd_ctx_res_detach(uint32_t ctx, uint32_t res) "ctx 0x%x, res 0x%x"
+virtio_gpu_cmd_ctx_submit(uint32_t ctx, uint32_t size) "ctx 0x%x, size %d"
+virtio_gpu_update_cursor(uint32_t scanout, uint32_t x, uint32_t y, const char *type, uint32_t res) "scanout %d, x %d, y %d, %s, res 0x%x"
+virtio_gpu_fence_ctrl(uint64_t fence, uint32_t type) "fence 0x%" PRIx64 ", type 0x%x"
+virtio_gpu_fence_resp(uint64_t fence) "fence 0x%" PRIx64
+
+# qxl.c
+disable qxl_interface_set_mm_time(int qid, uint32_t mm_time) "%d %d"
+disable qxl_io_write_vga(int qid, const char *mode, uint32_t addr, uint32_t val) "%d %s addr=%u val=%u"
+qxl_create_guest_primary(int qid, uint32_t width, uint32_t height, uint64_t mem, uint32_t format, uint32_t position) "%d %ux%u mem=0x%" PRIx64 " %u,%u"
+qxl_create_guest_primary_rest(int qid, int32_t stride, uint32_t type, uint32_t flags) "%d %d,%d,%d"
+qxl_destroy_primary(int qid) "%d"
+qxl_enter_vga_mode(int qid) "%d"
+qxl_exit_vga_mode(int qid) "%d"
+qxl_hard_reset(int qid, int64_t loadvm) "%d loadvm=%"PRId64
+qxl_interface_async_complete_io(int qid, uint32_t current_async, void *cookie) "%d current=%d cookie=%p"
+qxl_interface_attach_worker(int qid) "%d"
+qxl_interface_get_init_info(int qid) "%d"
+qxl_interface_set_compression_level(int qid, int64_t level) "%d %"PRId64
+qxl_interface_update_area_complete(int qid, uint32_t surface_id, uint32_t dirty_left, uint32_t dirty_right, uint32_t dirty_top, uint32_t dirty_bottom) "%d surface=%d [%d,%d,%d,%d]"
+qxl_interface_update_area_complete_rest(int qid, uint32_t num_updated_rects) "%d #=%d"
+qxl_interface_update_area_complete_overflow(int qid, int max) "%d max=%d"
+qxl_interface_update_area_complete_schedule_bh(int qid, uint32_t num_dirty) "%d #dirty=%d"
+qxl_io_destroy_primary_ignored(int qid, const char *mode) "%d %s"
+qxl_io_log(int qid, const char *log_buf) "%d %s"
+qxl_io_read_unexpected(int qid) "%d"
+qxl_io_unexpected_vga_mode(int qid, uint64_t addr, uint64_t val, const char *desc) "%d 0x%"PRIx64"=%"PRIu64" (%s)"
+qxl_io_write(int qid, const char *mode, uint64_t addr, const char *aname, uint64_t val, unsigned size, int async) "%d %s addr=%"PRIu64 " (%s) val=%"PRIu64" size=%u async=%d"
+qxl_memslot_add_guest(int qid, uint32_t slot_id, uint64_t guest_start, uint64_t guest_end) "%d %u: guest phys 0x%"PRIx64 " - 0x%" PRIx64
+qxl_post_load(int qid, const char *mode) "%d %s"
+qxl_pre_load(int qid) "%d"
+qxl_pre_save(int qid) "%d"
+qxl_reset_surfaces(int qid) "%d"
+qxl_ring_command_check(int qid, const char *mode) "%d %s"
+qxl_ring_command_get(int qid, const char *mode) "%d %s"
+qxl_ring_command_req_notification(int qid) "%d"
+qxl_ring_cursor_check(int qid, const char *mode) "%d %s"
+qxl_ring_cursor_get(int qid, const char *mode) "%d %s"
+qxl_ring_cursor_req_notification(int qid) "%d"
+qxl_ring_res_push(int qid, const char *mode, uint32_t surface_count, uint32_t free_res, void *last_release, const char *notify) "%d %s s#=%d res#=%d last=%p notify=%s"
+qxl_ring_res_push_rest(int qid, uint32_t ring_has, uint32_t ring_size, uint32_t prod, uint32_t cons) "%d ring %d/%d [%d,%d]"
+qxl_ring_res_put(int qid, uint32_t free_res) "%d #res=%d"
+qxl_set_mode(int qid, int modenr, uint32_t x_res, uint32_t y_res, uint32_t bits, uint64_t devmem) "%d mode=%d [ x=%d y=%d @ bpp=%d devmem=0x%" PRIx64 " ]"
+qxl_soft_reset(int qid) "%d"
+qxl_spice_destroy_surfaces_complete(int qid) "%d"
+qxl_spice_destroy_surfaces(int qid, int async) "%d async=%d"
+qxl_spice_destroy_surface_wait_complete(int qid, uint32_t id) "%d sid=%d"
+qxl_spice_destroy_surface_wait(int qid, uint32_t id, int async) "%d sid=%d async=%d"
+qxl_spice_flush_surfaces_async(int qid, uint32_t surface_count, uint32_t num_free_res) "%d s#=%d, res#=%d"
+qxl_spice_monitors_config(int qid) "%d"
+qxl_spice_loadvm_commands(int qid, void *ext, uint32_t count) "%d ext=%p count=%d"
+qxl_spice_oom(int qid) "%d"
+qxl_spice_reset_cursor(int qid) "%d"
+qxl_spice_reset_image_cache(int qid) "%d"
+qxl_spice_reset_memslots(int qid) "%d"
+qxl_spice_update_area(int qid, uint32_t surface_id, uint32_t left, uint32_t right, uint32_t top, uint32_t bottom) "%d sid=%d [%d,%d,%d,%d]"
+qxl_spice_update_area_rest(int qid, uint32_t num_dirty_rects, uint32_t clear_dirty_region) "%d #d=%d clear=%d"
+qxl_surfaces_dirty(int qid, uint64_t offset, uint64_t size) "%d offset=0x%"PRIx64" size=0x%"PRIx64
+qxl_send_events(int qid, uint32_t events) "%d %d"
+qxl_send_events_vm_stopped(int qid, uint32_t events) "%d %d"
+qxl_set_guest_bug(int qid) "%d"
+qxl_interrupt_client_monitors_config(int qid, int num_heads, void *heads) "%d %d %p"
+qxl_client_monitors_config_unsupported_by_guest(int qid, uint32_t int_mask, void *client_monitors_config) "%d 0x%X %p"
+qxl_client_monitors_config_unsupported_by_device(int qid, int revision) "%d revision=%d"
+qxl_client_monitors_config_capped(int qid, int requested, int limit) "%d %d %d"
+qxl_client_monitors_config_crc(int qid, unsigned size, uint32_t crc32) "%d %u %u"
+qxl_set_client_capabilities_unsupported_by_revision(int qid, int revision) "%d revision=%d"
+
+# qxl-render.c
+qxl_render_blit(int32_t stride, int32_t left, int32_t right, int32_t top, int32_t bottom) "stride=%d [%d, %d, %d, %d]"
+qxl_render_guest_primary_resized(int32_t width, int32_t height, int32_t stride, int32_t bytes_pp, int32_t bits_pp) "%dx%d, stride %d, bpp %d, depth %d"
+qxl_render_update_area_done(void *cookie) "%p"
+
+# vga.c
+vga_std_read_io(uint32_t addr, uint32_t val) "addr 0x%x, val 0x%x"
+vga_std_write_io(uint32_t addr, uint32_t val) "addr 0x%x, val 0x%x"
+vga_vbe_read(uint32_t index, uint32_t val) "index 0x%x, val 0x%x"
+vga_vbe_write(uint32_t index, uint32_t val) "index 0x%x, val 0x%x"
+
+# cirrus_vga.c
+vga_cirrus_read_io(uint32_t addr, uint32_t val) "addr 0x%x, val 0x%x"
+vga_cirrus_write_io(uint32_t addr, uint32_t val) "addr 0x%x, val 0x%x"
+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"
+
+# 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"
+sii9022_switch_mode(const char *mode) "mode: %s"
+
+# ati.c
+ati_mm_read(unsigned int size, uint64_t addr, const char *name, uint64_t val) "%u 0x%"PRIx64 " %s -> 0x%"PRIx64
+ati_mm_write(unsigned int size, uint64_t addr, const char *name, uint64_t val) "%u 0x%"PRIx64 " %s <- 0x%"PRIx64
+
+# artist.c
+artist_reg_read(unsigned int size, uint64_t addr, const char *name, uint64_t val) "%u 0x%"PRIx64 "%s -> 0x%"PRIx64
+artist_reg_write(unsigned int size, uint64_t addr, const char *name, uint64_t val) "%u 0x%"PRIx64 "%s <- 0x%"PRIx64
+artist_vram_read(unsigned int size, uint64_t addr, int posx, int posy, uint64_t val) "%u 0x%"PRIx64 " %ux%u-> 0x%"PRIx64
+artist_vram_write(unsigned int size, uint64_t addr, uint64_t val) "%u 0x%"PRIx64 " <- 0x%"PRIx64
+artist_fill_window(unsigned int start_x, unsigned int start_y, unsigned int width, unsigned int height, uint32_t op, uint32_t ctlpln) "start=%ux%u length=%ux%u op=0x%08x ctlpln=0x%08x"
+artist_block_move(unsigned int start_x, unsigned int start_y, unsigned int dest_x, unsigned int dest_y, unsigned int width, unsigned int height) "source %ux%u -> dest %ux%u size %ux%u"
+artist_draw_line(unsigned int start_x, unsigned int start_y, unsigned int end_x, unsigned int end_y) "%ux%u %ux%u"
+
+# cg3.c
+cg3_read(uint32_t addr, uint32_t val, unsigned size) "read addr:0x%06"PRIx32" val:0x%08"PRIx32" size:%u"
+cg3_write(uint32_t addr, uint32_t val, unsigned size) "write addr:0x%06"PRIx32" val:0x%08"PRIx32" size:%u"
+
+# dpcd.c
+dpcd_read(uint32_t addr, uint8_t val) "read addr:0x%"PRIx32" val:0x%02x"
+dpcd_write(uint32_t addr, uint8_t val) "write addr:0x%"PRIx32" val:0x%02x"
+
+# sm501.c
+sm501_system_config_read(uint32_t addr, uint32_t val) "addr=0x%x, val=0x%x"
+sm501_system_config_write(uint32_t addr, uint32_t val) "addr=0x%x, val=0x%x"
+sm501_i2c_read(uint32_t addr, uint8_t val) "addr=0x%x, val=0x%x"
+sm501_i2c_write(uint32_t addr, uint32_t val) "addr=0x%x, val=0x%x"
+sm501_palette_read(uint32_t addr) "addr=0x%x"
+sm501_palette_write(uint32_t addr, uint32_t val) "addr=0x%x, val=0x%x"
+sm501_disp_ctrl_read(uint32_t addr, uint32_t val) "addr=0x%x, val=0x%x"
+sm501_disp_ctrl_write(uint32_t addr, uint32_t val) "addr=0x%x, val=0x%x"
+sm501_2d_engine_read(uint32_t addr, uint32_t val) "addr=0x%x, val=0x%x"
+sm501_2d_engine_write(uint32_t addr, uint32_t val) "addr=0x%x, val=0x%x"
+
+# macfb.c
+macfb_ctrl_read(uint64_t addr, uint64_t value, unsigned int size) "addr 0x%"PRIx64 " value 0x%"PRIx64 " size %u"
+macfb_ctrl_write(uint64_t addr, uint64_t value, unsigned int size) "addr 0x%"PRIx64 " value 0x%"PRIx64 " size %u"
+macfb_sense_read(uint32_t value) "video sense: 0x%"PRIx32
+macfb_sense_write(uint32_t value) "video sense: 0x%"PRIx32
+macfb_update_mode(uint32_t width, uint32_t height, uint8_t depth) "setting mode to width %"PRId32 " height %"PRId32 " size %d"
diff --git a/hw/display/trace.h b/hw/display/trace.h
new file mode 100644
index 000000000..4ed0e9165
--- /dev/null
+++ b/hw/display/trace.h
@@ -0,0 +1 @@
+#include "trace/trace-hw_display.h"
diff --git a/hw/display/vga-access.h b/hw/display/vga-access.h
new file mode 100644
index 000000000..c0fbd9958
--- /dev/null
+++ b/hw/display/vga-access.h
@@ -0,0 +1,49 @@
+/*
+ * QEMU VGA Emulator templates
+ *
+ * Copyright (c) 2003 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+static inline uint8_t vga_read_byte(VGACommonState *vga, uint32_t addr)
+{
+ return vga->vram_ptr[addr & vga->vbe_size_mask];
+}
+
+static inline uint16_t vga_read_word_le(VGACommonState *vga, uint32_t addr)
+{
+ uint32_t offset = addr & vga->vbe_size_mask & ~1;
+ uint16_t *ptr = (uint16_t *)(vga->vram_ptr + offset);
+ return lduw_le_p(ptr);
+}
+
+static inline uint16_t vga_read_word_be(VGACommonState *vga, uint32_t addr)
+{
+ uint32_t offset = addr & vga->vbe_size_mask & ~1;
+ uint16_t *ptr = (uint16_t *)(vga->vram_ptr + offset);
+ return lduw_be_p(ptr);
+}
+
+static inline uint32_t vga_read_dword_le(VGACommonState *vga, uint32_t addr)
+{
+ uint32_t offset = addr & vga->vbe_size_mask & ~3;
+ uint32_t *ptr = (uint32_t *)(vga->vram_ptr + offset);
+ return ldl_le_p(ptr);
+}
diff --git a/hw/display/vga-helpers.h b/hw/display/vga-helpers.h
new file mode 100644
index 000000000..10e9cfd40
--- /dev/null
+++ b/hw/display/vga-helpers.h
@@ -0,0 +1,431 @@
+/*
+ * QEMU VGA Emulator templates
+ *
+ * Copyright (c) 2003 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+static inline void vga_draw_glyph_line(uint8_t *d, uint32_t font_data,
+ uint32_t xorcol, uint32_t bgcol)
+{
+ ((uint32_t *)d)[0] = (-((font_data >> 7)) & xorcol) ^ bgcol;
+ ((uint32_t *)d)[1] = (-((font_data >> 6) & 1) & xorcol) ^ bgcol;
+ ((uint32_t *)d)[2] = (-((font_data >> 5) & 1) & xorcol) ^ bgcol;
+ ((uint32_t *)d)[3] = (-((font_data >> 4) & 1) & xorcol) ^ bgcol;
+ ((uint32_t *)d)[4] = (-((font_data >> 3) & 1) & xorcol) ^ bgcol;
+ ((uint32_t *)d)[5] = (-((font_data >> 2) & 1) & xorcol) ^ bgcol;
+ ((uint32_t *)d)[6] = (-((font_data >> 1) & 1) & xorcol) ^ bgcol;
+ ((uint32_t *)d)[7] = (-((font_data >> 0) & 1) & xorcol) ^ bgcol;
+}
+
+static void vga_draw_glyph8(uint8_t *d, int linesize,
+ const uint8_t *font_ptr, int h,
+ uint32_t fgcol, uint32_t bgcol)
+{
+ uint32_t font_data, xorcol;
+
+ xorcol = bgcol ^ fgcol;
+ do {
+ font_data = font_ptr[0];
+ vga_draw_glyph_line(d, font_data, xorcol, bgcol);
+ font_ptr += 4;
+ d += linesize;
+ } while (--h);
+}
+
+static void vga_draw_glyph16(uint8_t *d, int linesize,
+ const uint8_t *font_ptr, int h,
+ uint32_t fgcol, uint32_t bgcol)
+{
+ uint32_t font_data, xorcol;
+
+ xorcol = bgcol ^ fgcol;
+ do {
+ font_data = font_ptr[0];
+ vga_draw_glyph_line(d, expand4to8[font_data >> 4],
+ xorcol, bgcol);
+ vga_draw_glyph_line(d + 32, expand4to8[font_data & 0x0f],
+ xorcol, bgcol);
+ font_ptr += 4;
+ d += linesize;
+ } while (--h);
+}
+
+static void vga_draw_glyph9(uint8_t *d, int linesize,
+ const uint8_t *font_ptr, int h,
+ uint32_t fgcol, uint32_t bgcol, int dup9)
+{
+ uint32_t font_data, xorcol, v;
+
+ xorcol = bgcol ^ fgcol;
+ do {
+ font_data = font_ptr[0];
+ ((uint32_t *)d)[0] = (-((font_data >> 7)) & xorcol) ^ bgcol;
+ ((uint32_t *)d)[1] = (-((font_data >> 6) & 1) & xorcol) ^ bgcol;
+ ((uint32_t *)d)[2] = (-((font_data >> 5) & 1) & xorcol) ^ bgcol;
+ ((uint32_t *)d)[3] = (-((font_data >> 4) & 1) & xorcol) ^ bgcol;
+ ((uint32_t *)d)[4] = (-((font_data >> 3) & 1) & xorcol) ^ bgcol;
+ ((uint32_t *)d)[5] = (-((font_data >> 2) & 1) & xorcol) ^ bgcol;
+ ((uint32_t *)d)[6] = (-((font_data >> 1) & 1) & xorcol) ^ bgcol;
+ v = (-((font_data >> 0) & 1) & xorcol) ^ bgcol;
+ ((uint32_t *)d)[7] = v;
+ if (dup9)
+ ((uint32_t *)d)[8] = v;
+ else
+ ((uint32_t *)d)[8] = bgcol;
+ font_ptr += 4;
+ d += linesize;
+ } while (--h);
+}
+
+/*
+ * 4 color mode
+ */
+static void vga_draw_line2(VGACommonState *vga, uint8_t *d,
+ uint32_t addr, int width)
+{
+ uint32_t plane_mask, *palette, data, v;
+ int x;
+
+ palette = vga->last_palette;
+ plane_mask = mask16[vga->ar[VGA_ATC_PLANE_ENABLE] & 0xf];
+ width >>= 3;
+ for(x = 0; x < width; x++) {
+ data = vga_read_dword_le(vga, addr);
+ data &= plane_mask;
+ v = expand2[GET_PLANE(data, 0)];
+ v |= expand2[GET_PLANE(data, 2)] << 2;
+ ((uint32_t *)d)[0] = palette[v >> 12];
+ ((uint32_t *)d)[1] = palette[(v >> 8) & 0xf];
+ ((uint32_t *)d)[2] = palette[(v >> 4) & 0xf];
+ ((uint32_t *)d)[3] = palette[(v >> 0) & 0xf];
+
+ v = expand2[GET_PLANE(data, 1)];
+ v |= expand2[GET_PLANE(data, 3)] << 2;
+ ((uint32_t *)d)[4] = palette[v >> 12];
+ ((uint32_t *)d)[5] = palette[(v >> 8) & 0xf];
+ ((uint32_t *)d)[6] = palette[(v >> 4) & 0xf];
+ ((uint32_t *)d)[7] = palette[(v >> 0) & 0xf];
+ d += 32;
+ addr += 4;
+ }
+}
+
+#define PUT_PIXEL2(d, n, v) \
+((uint32_t *)d)[2*(n)] = ((uint32_t *)d)[2*(n)+1] = (v)
+
+/*
+ * 4 color mode, dup2 horizontal
+ */
+static void vga_draw_line2d2(VGACommonState *vga, uint8_t *d,
+ uint32_t addr, int width)
+{
+ uint32_t plane_mask, *palette, data, v;
+ int x;
+
+ palette = vga->last_palette;
+ plane_mask = mask16[vga->ar[VGA_ATC_PLANE_ENABLE] & 0xf];
+ width >>= 3;
+ for(x = 0; x < width; x++) {
+ data = vga_read_dword_le(vga, addr);
+ data &= plane_mask;
+ v = expand2[GET_PLANE(data, 0)];
+ v |= expand2[GET_PLANE(data, 2)] << 2;
+ PUT_PIXEL2(d, 0, palette[v >> 12]);
+ PUT_PIXEL2(d, 1, palette[(v >> 8) & 0xf]);
+ PUT_PIXEL2(d, 2, palette[(v >> 4) & 0xf]);
+ PUT_PIXEL2(d, 3, palette[(v >> 0) & 0xf]);
+
+ v = expand2[GET_PLANE(data, 1)];
+ v |= expand2[GET_PLANE(data, 3)] << 2;
+ PUT_PIXEL2(d, 4, palette[v >> 12]);
+ PUT_PIXEL2(d, 5, palette[(v >> 8) & 0xf]);
+ PUT_PIXEL2(d, 6, palette[(v >> 4) & 0xf]);
+ PUT_PIXEL2(d, 7, palette[(v >> 0) & 0xf]);
+ d += 64;
+ addr += 4;
+ }
+}
+
+/*
+ * 16 color mode
+ */
+static void vga_draw_line4(VGACommonState *vga, uint8_t *d,
+ uint32_t addr, int width)
+{
+ uint32_t plane_mask, data, v, *palette;
+ int x;
+
+ palette = vga->last_palette;
+ plane_mask = mask16[vga->ar[VGA_ATC_PLANE_ENABLE] & 0xf];
+ width >>= 3;
+ for(x = 0; x < width; x++) {
+ data = vga_read_dword_le(vga, addr);
+ data &= plane_mask;
+ v = expand4[GET_PLANE(data, 0)];
+ v |= expand4[GET_PLANE(data, 1)] << 1;
+ v |= expand4[GET_PLANE(data, 2)] << 2;
+ v |= expand4[GET_PLANE(data, 3)] << 3;
+ ((uint32_t *)d)[0] = palette[v >> 28];
+ ((uint32_t *)d)[1] = palette[(v >> 24) & 0xf];
+ ((uint32_t *)d)[2] = palette[(v >> 20) & 0xf];
+ ((uint32_t *)d)[3] = palette[(v >> 16) & 0xf];
+ ((uint32_t *)d)[4] = palette[(v >> 12) & 0xf];
+ ((uint32_t *)d)[5] = palette[(v >> 8) & 0xf];
+ ((uint32_t *)d)[6] = palette[(v >> 4) & 0xf];
+ ((uint32_t *)d)[7] = palette[(v >> 0) & 0xf];
+ d += 32;
+ addr += 4;
+ }
+}
+
+/*
+ * 16 color mode, dup2 horizontal
+ */
+static void vga_draw_line4d2(VGACommonState *vga, uint8_t *d,
+ uint32_t addr, int width)
+{
+ uint32_t plane_mask, data, v, *palette;
+ int x;
+
+ palette = vga->last_palette;
+ plane_mask = mask16[vga->ar[VGA_ATC_PLANE_ENABLE] & 0xf];
+ width >>= 3;
+ for(x = 0; x < width; x++) {
+ data = vga_read_dword_le(vga, addr);
+ data &= plane_mask;
+ v = expand4[GET_PLANE(data, 0)];
+ v |= expand4[GET_PLANE(data, 1)] << 1;
+ v |= expand4[GET_PLANE(data, 2)] << 2;
+ v |= expand4[GET_PLANE(data, 3)] << 3;
+ PUT_PIXEL2(d, 0, palette[v >> 28]);
+ PUT_PIXEL2(d, 1, palette[(v >> 24) & 0xf]);
+ PUT_PIXEL2(d, 2, palette[(v >> 20) & 0xf]);
+ PUT_PIXEL2(d, 3, palette[(v >> 16) & 0xf]);
+ PUT_PIXEL2(d, 4, palette[(v >> 12) & 0xf]);
+ PUT_PIXEL2(d, 5, palette[(v >> 8) & 0xf]);
+ PUT_PIXEL2(d, 6, palette[(v >> 4) & 0xf]);
+ PUT_PIXEL2(d, 7, palette[(v >> 0) & 0xf]);
+ d += 64;
+ addr += 4;
+ }
+}
+
+/*
+ * 256 color mode, double pixels
+ *
+ * XXX: add plane_mask support (never used in standard VGA modes)
+ */
+static void vga_draw_line8d2(VGACommonState *vga, uint8_t *d,
+ uint32_t addr, int width)
+{
+ uint32_t *palette;
+ int x;
+
+ palette = vga->last_palette;
+ width >>= 3;
+ for(x = 0; x < width; x++) {
+ PUT_PIXEL2(d, 0, palette[vga_read_byte(vga, addr + 0)]);
+ PUT_PIXEL2(d, 1, palette[vga_read_byte(vga, addr + 1)]);
+ PUT_PIXEL2(d, 2, palette[vga_read_byte(vga, addr + 2)]);
+ PUT_PIXEL2(d, 3, palette[vga_read_byte(vga, addr + 3)]);
+ d += 32;
+ addr += 4;
+ }
+}
+
+/*
+ * standard 256 color mode
+ *
+ * XXX: add plane_mask support (never used in standard VGA modes)
+ */
+static void vga_draw_line8(VGACommonState *vga, uint8_t *d,
+ uint32_t addr, int width)
+{
+ uint32_t *palette;
+ int x;
+
+ palette = vga->last_palette;
+ width >>= 3;
+ for(x = 0; x < width; x++) {
+ ((uint32_t *)d)[0] = palette[vga_read_byte(vga, addr + 0)];
+ ((uint32_t *)d)[1] = palette[vga_read_byte(vga, addr + 1)];
+ ((uint32_t *)d)[2] = palette[vga_read_byte(vga, addr + 2)];
+ ((uint32_t *)d)[3] = palette[vga_read_byte(vga, addr + 3)];
+ ((uint32_t *)d)[4] = palette[vga_read_byte(vga, addr + 4)];
+ ((uint32_t *)d)[5] = palette[vga_read_byte(vga, addr + 5)];
+ ((uint32_t *)d)[6] = palette[vga_read_byte(vga, addr + 6)];
+ ((uint32_t *)d)[7] = palette[vga_read_byte(vga, addr + 7)];
+ d += 32;
+ addr += 8;
+ }
+}
+
+/*
+ * 15 bit color
+ */
+static void vga_draw_line15_le(VGACommonState *vga, uint8_t *d,
+ uint32_t addr, int width)
+{
+ int w;
+ uint32_t v, r, g, b;
+
+ w = width;
+ do {
+ v = vga_read_word_le(vga, addr);
+ r = (v >> 7) & 0xf8;
+ g = (v >> 2) & 0xf8;
+ b = (v << 3) & 0xf8;
+ ((uint32_t *)d)[0] = rgb_to_pixel32(r, g, b);
+ addr += 2;
+ d += 4;
+ } while (--w != 0);
+}
+
+static void vga_draw_line15_be(VGACommonState *vga, uint8_t *d,
+ uint32_t addr, int width)
+{
+ int w;
+ uint32_t v, r, g, b;
+
+ w = width;
+ do {
+ v = vga_read_word_be(vga, addr);
+ r = (v >> 7) & 0xf8;
+ g = (v >> 2) & 0xf8;
+ b = (v << 3) & 0xf8;
+ ((uint32_t *)d)[0] = rgb_to_pixel32(r, g, b);
+ addr += 2;
+ d += 4;
+ } while (--w != 0);
+}
+
+/*
+ * 16 bit color
+ */
+static void vga_draw_line16_le(VGACommonState *vga, uint8_t *d,
+ uint32_t addr, int width)
+{
+ int w;
+ uint32_t v, r, g, b;
+
+ w = width;
+ do {
+ v = vga_read_word_le(vga, addr);
+ r = (v >> 8) & 0xf8;
+ g = (v >> 3) & 0xfc;
+ b = (v << 3) & 0xf8;
+ ((uint32_t *)d)[0] = rgb_to_pixel32(r, g, b);
+ addr += 2;
+ d += 4;
+ } while (--w != 0);
+}
+
+static void vga_draw_line16_be(VGACommonState *vga, uint8_t *d,
+ uint32_t addr, int width)
+{
+ int w;
+ uint32_t v, r, g, b;
+
+ w = width;
+ do {
+ v = vga_read_word_be(vga, addr);
+ r = (v >> 8) & 0xf8;
+ g = (v >> 3) & 0xfc;
+ b = (v << 3) & 0xf8;
+ ((uint32_t *)d)[0] = rgb_to_pixel32(r, g, b);
+ addr += 2;
+ d += 4;
+ } while (--w != 0);
+}
+
+/*
+ * 24 bit color
+ */
+static void vga_draw_line24_le(VGACommonState *vga, uint8_t *d,
+ uint32_t addr, int width)
+{
+ int w;
+ uint32_t r, g, b;
+
+ w = width;
+ do {
+ b = vga_read_byte(vga, addr + 0);
+ g = vga_read_byte(vga, addr + 1);
+ r = vga_read_byte(vga, addr + 2);
+ ((uint32_t *)d)[0] = rgb_to_pixel32(r, g, b);
+ addr += 3;
+ d += 4;
+ } while (--w != 0);
+}
+
+static void vga_draw_line24_be(VGACommonState *vga, uint8_t *d,
+ uint32_t addr, int width)
+{
+ int w;
+ uint32_t r, g, b;
+
+ w = width;
+ do {
+ r = vga_read_byte(vga, addr + 0);
+ g = vga_read_byte(vga, addr + 1);
+ b = vga_read_byte(vga, addr + 2);
+ ((uint32_t *)d)[0] = rgb_to_pixel32(r, g, b);
+ addr += 3;
+ d += 4;
+ } while (--w != 0);
+}
+
+/*
+ * 32 bit color
+ */
+static void vga_draw_line32_le(VGACommonState *vga, uint8_t *d,
+ uint32_t addr, int width)
+{
+ int w;
+ uint32_t r, g, b;
+
+ w = width;
+ do {
+ b = vga_read_byte(vga, addr + 0);
+ g = vga_read_byte(vga, addr + 1);
+ r = vga_read_byte(vga, addr + 2);
+ ((uint32_t *)d)[0] = rgb_to_pixel32(r, g, b);
+ addr += 4;
+ d += 4;
+ } while (--w != 0);
+}
+
+static void vga_draw_line32_be(VGACommonState *vga, uint8_t *d,
+ uint32_t addr, int width)
+{
+ int w;
+ uint32_t r, g, b;
+
+ w = width;
+ do {
+ r = vga_read_byte(vga, addr + 1);
+ g = vga_read_byte(vga, addr + 2);
+ b = vga_read_byte(vga, addr + 3);
+ ((uint32_t *)d)[0] = rgb_to_pixel32(r, g, b);
+ addr += 4;
+ d += 4;
+ } while (--w != 0);
+}
diff --git a/hw/display/vga-isa-mm.c b/hw/display/vga-isa-mm.c
new file mode 100644
index 000000000..7321b7a06
--- /dev/null
+++ b/hw/display/vga-isa-mm.c
@@ -0,0 +1,114 @@
+/*
+ * QEMU ISA MM VGA Emulator.
+ *
+ * Copyright (c) 2003 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/bitops.h"
+#include "qemu/units.h"
+#include "migration/vmstate.h"
+#include "hw/display/vga.h"
+#include "vga_int.h"
+#include "ui/pixel_ops.h"
+
+#define VGA_RAM_SIZE (8 * MiB)
+
+typedef struct ISAVGAMMState {
+ VGACommonState vga;
+ int it_shift;
+} ISAVGAMMState;
+
+/* Memory mapped interface */
+static uint64_t vga_mm_read(void *opaque, hwaddr addr, unsigned size)
+{
+ ISAVGAMMState *s = opaque;
+
+ return vga_ioport_read(&s->vga, addr >> s->it_shift) &
+ MAKE_64BIT_MASK(0, size * 8);
+}
+
+static void vga_mm_write(void *opaque, hwaddr addr, uint64_t value,
+ unsigned size)
+{
+ ISAVGAMMState *s = opaque;
+
+ vga_ioport_write(&s->vga, addr >> s->it_shift,
+ value & MAKE_64BIT_MASK(0, size * 8));
+}
+
+static const MemoryRegionOps vga_mm_ctrl_ops = {
+ .read = vga_mm_read,
+ .write = vga_mm_write,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 4,
+ .impl.min_access_size = 1,
+ .impl.max_access_size = 4,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void vga_mm_init(ISAVGAMMState *s, hwaddr vram_base,
+ hwaddr ctrl_base, int it_shift,
+ MemoryRegion *address_space)
+{
+ MemoryRegion *s_ioport_ctrl, *vga_io_memory;
+
+ s->it_shift = it_shift;
+ s_ioport_ctrl = g_malloc(sizeof(*s_ioport_ctrl));
+ memory_region_init_io(s_ioport_ctrl, NULL, &vga_mm_ctrl_ops, s,
+ "vga-mm-ctrl", 0x100000);
+ memory_region_set_flush_coalesced(s_ioport_ctrl);
+
+ vga_io_memory = g_malloc(sizeof(*vga_io_memory));
+ /* XXX: endianness? */
+ memory_region_init_io(vga_io_memory, NULL, &vga_mem_ops, &s->vga,
+ "vga-mem", 0x20000);
+
+ vmstate_register(NULL, 0, &vmstate_vga_common, s);
+
+ memory_region_add_subregion(address_space, ctrl_base, s_ioport_ctrl);
+ s->vga.bank_offset = 0;
+ memory_region_add_subregion(address_space,
+ vram_base + 0x000a0000, vga_io_memory);
+ memory_region_set_coalescing(vga_io_memory);
+}
+
+int isa_vga_mm_init(hwaddr vram_base,
+ hwaddr ctrl_base, int it_shift,
+ MemoryRegion *address_space)
+{
+ ISAVGAMMState *s;
+
+ s = g_malloc0(sizeof(*s));
+
+ s->vga.vram_size_mb = VGA_RAM_SIZE / MiB;
+ s->vga.global_vmstate = true;
+ vga_common_init(&s->vga, NULL);
+ vga_mm_init(s, vram_base, ctrl_base, it_shift, address_space);
+
+ s->vga.con = graphic_console_init(NULL, 0, s->vga.hw_ops, s);
+
+ memory_region_add_subregion(address_space,
+ VBE_DISPI_LFB_PHYSICAL_ADDRESS,
+ &s->vga.vram);
+
+ return 0;
+}
diff --git a/hw/display/vga-isa.c b/hw/display/vga-isa.c
new file mode 100644
index 000000000..90851e730
--- /dev/null
+++ b/hw/display/vga-isa.c
@@ -0,0 +1,115 @@
+/*
+ * QEMU ISA VGA Emulator.
+ *
+ * see docs/specs/standard-vga.txt for virtual hardware specs.
+ *
+ * Copyright (c) 2003 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/isa/isa.h"
+#include "vga_int.h"
+#include "ui/pixel_ops.h"
+#include "qemu/module.h"
+#include "qemu/timer.h"
+#include "hw/loader.h"
+#include "hw/qdev-properties.h"
+#include "qom/object.h"
+
+#define TYPE_ISA_VGA "isa-vga"
+OBJECT_DECLARE_SIMPLE_TYPE(ISAVGAState, ISA_VGA)
+
+struct ISAVGAState {
+ ISADevice parent_obj;
+
+ struct VGACommonState state;
+ PortioList portio_vga;
+ PortioList portio_vbe;
+};
+
+static void vga_isa_reset(DeviceState *dev)
+{
+ ISAVGAState *d = ISA_VGA(dev);
+ VGACommonState *s = &d->state;
+
+ vga_common_reset(s);
+}
+
+static void vga_isa_realizefn(DeviceState *dev, Error **errp)
+{
+ ISADevice *isadev = ISA_DEVICE(dev);
+ ISAVGAState *d = ISA_VGA(dev);
+ VGACommonState *s = &d->state;
+ MemoryRegion *vga_io_memory;
+ const MemoryRegionPortio *vga_ports, *vbe_ports;
+
+ s->global_vmstate = true;
+ vga_common_init(s, OBJECT(dev));
+ s->legacy_address_space = isa_address_space(isadev);
+ vga_io_memory = vga_init_io(s, OBJECT(dev), &vga_ports, &vbe_ports);
+ isa_register_portio_list(isadev, &d->portio_vga,
+ 0x3b0, vga_ports, s, "vga");
+ if (vbe_ports) {
+ isa_register_portio_list(isadev, &d->portio_vbe,
+ 0x1ce, vbe_ports, s, "vbe");
+ }
+ memory_region_add_subregion_overlap(isa_address_space(isadev),
+ 0x000a0000,
+ vga_io_memory, 1);
+ memory_region_set_coalescing(vga_io_memory);
+ s->con = graphic_console_init(dev, 0, s->hw_ops, s);
+
+ memory_region_add_subregion(isa_address_space(isadev),
+ VBE_DISPI_LFB_PHYSICAL_ADDRESS,
+ &s->vram);
+ /* ROM BIOS */
+ rom_add_vga(VGABIOS_FILENAME);
+}
+
+static Property vga_isa_properties[] = {
+ DEFINE_PROP_UINT32("vgamem_mb", ISAVGAState, state.vram_size_mb, 8),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void vga_isa_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = vga_isa_realizefn;
+ dc->reset = vga_isa_reset;
+ dc->vmsd = &vmstate_vga_common;
+ device_class_set_props(dc, vga_isa_properties);
+ set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories);
+}
+
+static const TypeInfo vga_isa_info = {
+ .name = TYPE_ISA_VGA,
+ .parent = TYPE_ISA_DEVICE,
+ .instance_size = sizeof(ISAVGAState),
+ .class_init = vga_isa_class_initfn,
+};
+
+static void vga_isa_register_types(void)
+{
+ type_register_static(&vga_isa_info);
+}
+
+type_init(vga_isa_register_types)
diff --git a/hw/display/vga-pci.c b/hw/display/vga-pci.c
new file mode 100644
index 000000000..62fb5c38c
--- /dev/null
+++ b/hw/display/vga-pci.c
@@ -0,0 +1,420 @@
+/*
+ * QEMU PCI VGA Emulator.
+ *
+ * see docs/specs/standard-vga.txt for virtual hardware specs.
+ *
+ * Copyright (c) 2003 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/pci/pci.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "vga_int.h"
+#include "ui/pixel_ops.h"
+#include "qemu/module.h"
+#include "qemu/timer.h"
+#include "hw/loader.h"
+#include "hw/display/edid.h"
+#include "qom/object.h"
+
+enum vga_pci_flags {
+ PCI_VGA_FLAG_ENABLE_MMIO = 1,
+ PCI_VGA_FLAG_ENABLE_QEXT = 2,
+ PCI_VGA_FLAG_ENABLE_EDID = 3,
+};
+
+struct PCIVGAState {
+ PCIDevice dev;
+ VGACommonState vga;
+ uint32_t flags;
+ qemu_edid_info edid_info;
+ MemoryRegion mmio;
+ MemoryRegion mrs[4];
+ uint8_t edid[384];
+};
+
+#define TYPE_PCI_VGA "pci-vga"
+OBJECT_DECLARE_SIMPLE_TYPE(PCIVGAState, PCI_VGA)
+
+static const VMStateDescription vmstate_vga_pci = {
+ .name = "vga",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(dev, PCIVGAState),
+ VMSTATE_STRUCT(vga, PCIVGAState, 0, vmstate_vga_common, VGACommonState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static uint64_t pci_vga_ioport_read(void *ptr, hwaddr addr,
+ unsigned size)
+{
+ VGACommonState *s = ptr;
+ uint64_t ret = 0;
+
+ switch (size) {
+ case 1:
+ ret = vga_ioport_read(s, addr + 0x3c0);
+ break;
+ case 2:
+ ret = vga_ioport_read(s, addr + 0x3c0);
+ ret |= vga_ioport_read(s, addr + 0x3c1) << 8;
+ break;
+ }
+ return ret;
+}
+
+static void pci_vga_ioport_write(void *ptr, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ VGACommonState *s = ptr;
+
+ switch (size) {
+ case 1:
+ vga_ioport_write(s, addr + 0x3c0, val);
+ break;
+ case 2:
+ /*
+ * Update bytes in little endian order. Allows to update
+ * indexed registers with a single word write because the
+ * index byte is updated first.
+ */
+ vga_ioport_write(s, addr + 0x3c0, val & 0xff);
+ vga_ioport_write(s, addr + 0x3c1, (val >> 8) & 0xff);
+ break;
+ }
+}
+
+static const MemoryRegionOps pci_vga_ioport_ops = {
+ .read = pci_vga_ioport_read,
+ .write = pci_vga_ioport_write,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 4,
+ .impl.min_access_size = 1,
+ .impl.max_access_size = 2,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static uint64_t pci_vga_bochs_read(void *ptr, hwaddr addr,
+ unsigned size)
+{
+ VGACommonState *s = ptr;
+ int index = addr >> 1;
+
+ vbe_ioport_write_index(s, 0, index);
+ return vbe_ioport_read_data(s, 0);
+}
+
+static void pci_vga_bochs_write(void *ptr, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ VGACommonState *s = ptr;
+ int index = addr >> 1;
+
+ vbe_ioport_write_index(s, 0, index);
+ vbe_ioport_write_data(s, 0, val);
+}
+
+static const MemoryRegionOps pci_vga_bochs_ops = {
+ .read = pci_vga_bochs_read,
+ .write = pci_vga_bochs_write,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 4,
+ .impl.min_access_size = 2,
+ .impl.max_access_size = 2,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static uint64_t pci_vga_qext_read(void *ptr, hwaddr addr, unsigned size)
+{
+ VGACommonState *s = ptr;
+
+ switch (addr) {
+ case PCI_VGA_QEXT_REG_SIZE:
+ return PCI_VGA_QEXT_SIZE;
+ case PCI_VGA_QEXT_REG_BYTEORDER:
+ return s->big_endian_fb ?
+ PCI_VGA_QEXT_BIG_ENDIAN : PCI_VGA_QEXT_LITTLE_ENDIAN;
+ default:
+ return 0;
+ }
+}
+
+static void pci_vga_qext_write(void *ptr, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ VGACommonState *s = ptr;
+
+ switch (addr) {
+ case PCI_VGA_QEXT_REG_BYTEORDER:
+ if (val == PCI_VGA_QEXT_BIG_ENDIAN) {
+ s->big_endian_fb = true;
+ }
+ if (val == PCI_VGA_QEXT_LITTLE_ENDIAN) {
+ s->big_endian_fb = false;
+ }
+ break;
+ }
+}
+
+static bool vga_get_big_endian_fb(Object *obj, Error **errp)
+{
+ PCIVGAState *d = PCI_VGA(PCI_DEVICE(obj));
+
+ return d->vga.big_endian_fb;
+}
+
+static void vga_set_big_endian_fb(Object *obj, bool value, Error **errp)
+{
+ PCIVGAState *d = PCI_VGA(PCI_DEVICE(obj));
+
+ d->vga.big_endian_fb = value;
+}
+
+static const MemoryRegionOps pci_vga_qext_ops = {
+ .read = pci_vga_qext_read,
+ .write = pci_vga_qext_write,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 4,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+void pci_std_vga_mmio_region_init(VGACommonState *s,
+ Object *owner,
+ MemoryRegion *parent,
+ MemoryRegion *subs,
+ bool qext, bool edid)
+{
+ PCIVGAState *d = container_of(s, PCIVGAState, vga);
+
+ memory_region_init_io(&subs[0], owner, &pci_vga_ioport_ops, s,
+ "vga ioports remapped", PCI_VGA_IOPORT_SIZE);
+ memory_region_add_subregion(parent, PCI_VGA_IOPORT_OFFSET,
+ &subs[0]);
+
+ memory_region_init_io(&subs[1], owner, &pci_vga_bochs_ops, s,
+ "bochs dispi interface", PCI_VGA_BOCHS_SIZE);
+ memory_region_add_subregion(parent, PCI_VGA_BOCHS_OFFSET,
+ &subs[1]);
+
+ if (qext) {
+ memory_region_init_io(&subs[2], owner, &pci_vga_qext_ops, s,
+ "qemu extended regs", PCI_VGA_QEXT_SIZE);
+ memory_region_add_subregion(parent, PCI_VGA_QEXT_OFFSET,
+ &subs[2]);
+ }
+
+ if (edid) {
+ qemu_edid_generate(d->edid, sizeof(d->edid), &d->edid_info);
+ qemu_edid_region_io(&subs[3], owner, d->edid, sizeof(d->edid));
+ memory_region_add_subregion(parent, 0, &subs[3]);
+ }
+}
+
+static void pci_std_vga_realize(PCIDevice *dev, Error **errp)
+{
+ PCIVGAState *d = PCI_VGA(dev);
+ VGACommonState *s = &d->vga;
+ bool qext = false;
+ bool edid = false;
+
+ /* vga + console init */
+ vga_common_init(s, OBJECT(dev));
+ vga_init(s, OBJECT(dev), pci_address_space(dev), pci_address_space_io(dev),
+ true);
+
+ s->con = graphic_console_init(DEVICE(dev), 0, s->hw_ops, s);
+
+ /* XXX: VGA_RAM_SIZE must be a power of two */
+ pci_register_bar(&d->dev, 0, PCI_BASE_ADDRESS_MEM_PREFETCH, &s->vram);
+
+ /* mmio bar for vga register access */
+ if (d->flags & (1 << PCI_VGA_FLAG_ENABLE_MMIO)) {
+ memory_region_init_io(&d->mmio, OBJECT(dev), &unassigned_io_ops, NULL,
+ "vga.mmio", PCI_VGA_MMIO_SIZE);
+
+ if (d->flags & (1 << PCI_VGA_FLAG_ENABLE_QEXT)) {
+ qext = true;
+ pci_set_byte(&d->dev.config[PCI_REVISION_ID], 2);
+ }
+ if (d->flags & (1 << PCI_VGA_FLAG_ENABLE_EDID)) {
+ edid = true;
+ }
+ pci_std_vga_mmio_region_init(s, OBJECT(dev), &d->mmio, d->mrs,
+ qext, edid);
+
+ pci_register_bar(&d->dev, 2, PCI_BASE_ADDRESS_SPACE_MEMORY, &d->mmio);
+ }
+}
+
+static void pci_secondary_vga_realize(PCIDevice *dev, Error **errp)
+{
+ PCIVGAState *d = PCI_VGA(dev);
+ VGACommonState *s = &d->vga;
+ bool qext = false;
+ bool edid = false;
+
+ /* vga + console init */
+ vga_common_init(s, OBJECT(dev));
+ s->con = graphic_console_init(DEVICE(dev), 0, s->hw_ops, s);
+
+ /* mmio bar */
+ memory_region_init_io(&d->mmio, OBJECT(dev), &unassigned_io_ops, NULL,
+ "vga.mmio", PCI_VGA_MMIO_SIZE);
+
+ if (d->flags & (1 << PCI_VGA_FLAG_ENABLE_QEXT)) {
+ qext = true;
+ pci_set_byte(&d->dev.config[PCI_REVISION_ID], 2);
+ }
+ if (d->flags & (1 << PCI_VGA_FLAG_ENABLE_EDID)) {
+ edid = true;
+ }
+ pci_std_vga_mmio_region_init(s, OBJECT(dev), &d->mmio, d->mrs, qext, edid);
+
+ pci_register_bar(&d->dev, 0, PCI_BASE_ADDRESS_MEM_PREFETCH, &s->vram);
+ pci_register_bar(&d->dev, 2, PCI_BASE_ADDRESS_SPACE_MEMORY, &d->mmio);
+}
+
+static void pci_secondary_vga_exit(PCIDevice *dev)
+{
+ PCIVGAState *d = PCI_VGA(dev);
+ VGACommonState *s = &d->vga;
+
+ graphic_console_close(s->con);
+ memory_region_del_subregion(&d->mmio, &d->mrs[0]);
+ memory_region_del_subregion(&d->mmio, &d->mrs[1]);
+ if (d->flags & (1 << PCI_VGA_FLAG_ENABLE_QEXT)) {
+ memory_region_del_subregion(&d->mmio, &d->mrs[2]);
+ }
+ if (d->flags & (1 << PCI_VGA_FLAG_ENABLE_EDID)) {
+ memory_region_del_subregion(&d->mmio, &d->mrs[3]);
+ }
+}
+
+static void pci_secondary_vga_init(Object *obj)
+{
+ /* Expose framebuffer byteorder via QOM */
+ object_property_add_bool(obj, "big-endian-framebuffer",
+ vga_get_big_endian_fb, vga_set_big_endian_fb);
+}
+
+static void pci_secondary_vga_reset(DeviceState *dev)
+{
+ PCIVGAState *d = PCI_VGA(PCI_DEVICE(dev));
+ vga_common_reset(&d->vga);
+}
+
+static Property vga_pci_properties[] = {
+ DEFINE_PROP_UINT32("vgamem_mb", PCIVGAState, vga.vram_size_mb, 16),
+ DEFINE_PROP_BIT("mmio", PCIVGAState, flags, PCI_VGA_FLAG_ENABLE_MMIO, true),
+ DEFINE_PROP_BIT("qemu-extended-regs",
+ PCIVGAState, flags, PCI_VGA_FLAG_ENABLE_QEXT, true),
+ DEFINE_PROP_BIT("edid",
+ PCIVGAState, flags, PCI_VGA_FLAG_ENABLE_EDID, true),
+ DEFINE_EDID_PROPERTIES(PCIVGAState, edid_info),
+ DEFINE_PROP_BOOL("global-vmstate", PCIVGAState, vga.global_vmstate, false),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static Property secondary_pci_properties[] = {
+ DEFINE_PROP_UINT32("vgamem_mb", PCIVGAState, vga.vram_size_mb, 16),
+ DEFINE_PROP_BIT("qemu-extended-regs",
+ PCIVGAState, flags, PCI_VGA_FLAG_ENABLE_QEXT, true),
+ DEFINE_PROP_BIT("edid",
+ PCIVGAState, flags, PCI_VGA_FLAG_ENABLE_EDID, true),
+ DEFINE_EDID_PROPERTIES(PCIVGAState, edid_info),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void vga_pci_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->vendor_id = PCI_VENDOR_ID_QEMU;
+ k->device_id = PCI_DEVICE_ID_QEMU_VGA;
+ dc->vmsd = &vmstate_vga_pci;
+ set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories);
+}
+
+static const TypeInfo vga_pci_type_info = {
+ .name = TYPE_PCI_VGA,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(PCIVGAState),
+ .abstract = true,
+ .class_init = vga_pci_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { INTERFACE_CONVENTIONAL_PCI_DEVICE },
+ { },
+ },
+};
+
+static void vga_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = pci_std_vga_realize;
+ k->romfile = "vgabios-stdvga.bin";
+ k->class_id = PCI_CLASS_DISPLAY_VGA;
+ device_class_set_props(dc, vga_pci_properties);
+ dc->hotpluggable = false;
+
+ /* Expose framebuffer byteorder via QOM */
+ object_class_property_add_bool(klass, "big-endian-framebuffer",
+ vga_get_big_endian_fb, vga_set_big_endian_fb);
+}
+
+static void secondary_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = pci_secondary_vga_realize;
+ k->exit = pci_secondary_vga_exit;
+ k->class_id = PCI_CLASS_DISPLAY_OTHER;
+ device_class_set_props(dc, secondary_pci_properties);
+ dc->reset = pci_secondary_vga_reset;
+}
+
+static const TypeInfo vga_info = {
+ .name = "VGA",
+ .parent = TYPE_PCI_VGA,
+ .class_init = vga_class_init,
+};
+
+static const TypeInfo secondary_info = {
+ .name = "secondary-vga",
+ .parent = TYPE_PCI_VGA,
+ .instance_init = pci_secondary_vga_init,
+ .class_init = secondary_class_init,
+};
+
+static void vga_register_types(void)
+{
+ type_register_static(&vga_pci_type_info);
+ type_register_static(&vga_info);
+ type_register_static(&secondary_info);
+}
+
+type_init(vga_register_types)
diff --git a/hw/display/vga.c b/hw/display/vga.c
new file mode 100644
index 000000000..9d1f66af4
--- /dev/null
+++ b/hw/display/vga.c
@@ -0,0 +1,2305 @@
+/*
+ * QEMU VGA Emulator.
+ *
+ * Copyright (c) 2003 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/units.h"
+#include "sysemu/reset.h"
+#include "qapi/error.h"
+#include "hw/display/vga.h"
+#include "hw/pci/pci.h"
+#include "vga_int.h"
+#include "vga_regs.h"
+#include "ui/pixel_ops.h"
+#include "qemu/timer.h"
+#include "hw/xen/xen.h"
+#include "migration/vmstate.h"
+#include "trace.h"
+
+//#define DEBUG_VGA_MEM
+//#define DEBUG_VGA_REG
+
+bool have_vga = true;
+
+/* 16 state changes per vertical frame @60 Hz */
+#define VGA_TEXT_CURSOR_PERIOD_MS (1000 * 2 * 16 / 60)
+
+/*
+ * Video Graphics Array (VGA)
+ *
+ * Chipset docs for original IBM VGA:
+ * http://www.mcamafia.de/pdf/ibm_vgaxga_trm2.pdf
+ *
+ * FreeVGA site:
+ * http://www.osdever.net/FreeVGA/home.htm
+ *
+ * Standard VGA features and Bochs VBE extensions are implemented.
+ */
+
+/* force some bits to zero */
+const uint8_t sr_mask[8] = {
+ 0x03,
+ 0x3d,
+ 0x0f,
+ 0x3f,
+ 0x0e,
+ 0x00,
+ 0x00,
+ 0xff,
+};
+
+const uint8_t gr_mask[16] = {
+ 0x0f, /* 0x00 */
+ 0x0f, /* 0x01 */
+ 0x0f, /* 0x02 */
+ 0x1f, /* 0x03 */
+ 0x03, /* 0x04 */
+ 0x7b, /* 0x05 */
+ 0x0f, /* 0x06 */
+ 0x0f, /* 0x07 */
+ 0xff, /* 0x08 */
+ 0x00, /* 0x09 */
+ 0x00, /* 0x0a */
+ 0x00, /* 0x0b */
+ 0x00, /* 0x0c */
+ 0x00, /* 0x0d */
+ 0x00, /* 0x0e */
+ 0x00, /* 0x0f */
+};
+
+#define cbswap_32(__x) \
+((uint32_t)( \
+ (((uint32_t)(__x) & (uint32_t)0x000000ffUL) << 24) | \
+ (((uint32_t)(__x) & (uint32_t)0x0000ff00UL) << 8) | \
+ (((uint32_t)(__x) & (uint32_t)0x00ff0000UL) >> 8) | \
+ (((uint32_t)(__x) & (uint32_t)0xff000000UL) >> 24) ))
+
+#ifdef HOST_WORDS_BIGENDIAN
+#define PAT(x) cbswap_32(x)
+#else
+#define PAT(x) (x)
+#endif
+
+#ifdef HOST_WORDS_BIGENDIAN
+#define BIG 1
+#else
+#define BIG 0
+#endif
+
+#ifdef HOST_WORDS_BIGENDIAN
+#define GET_PLANE(data, p) (((data) >> (24 - (p) * 8)) & 0xff)
+#else
+#define GET_PLANE(data, p) (((data) >> ((p) * 8)) & 0xff)
+#endif
+
+static const uint32_t mask16[16] = {
+ PAT(0x00000000),
+ PAT(0x000000ff),
+ PAT(0x0000ff00),
+ PAT(0x0000ffff),
+ PAT(0x00ff0000),
+ PAT(0x00ff00ff),
+ PAT(0x00ffff00),
+ PAT(0x00ffffff),
+ PAT(0xff000000),
+ PAT(0xff0000ff),
+ PAT(0xff00ff00),
+ PAT(0xff00ffff),
+ PAT(0xffff0000),
+ PAT(0xffff00ff),
+ PAT(0xffffff00),
+ PAT(0xffffffff),
+};
+
+#undef PAT
+
+#ifdef HOST_WORDS_BIGENDIAN
+#define PAT(x) (x)
+#else
+#define PAT(x) cbswap_32(x)
+#endif
+
+static uint32_t expand4[256];
+static uint16_t expand2[256];
+static uint8_t expand4to8[16];
+
+static void vbe_update_vgaregs(VGACommonState *s);
+
+static inline bool vbe_enabled(VGACommonState *s)
+{
+ return s->vbe_regs[VBE_DISPI_INDEX_ENABLE] & VBE_DISPI_ENABLED;
+}
+
+static inline uint8_t sr(VGACommonState *s, int idx)
+{
+ return vbe_enabled(s) ? s->sr_vbe[idx] : s->sr[idx];
+}
+
+static void vga_update_memory_access(VGACommonState *s)
+{
+ hwaddr base, offset, size;
+
+ if (s->legacy_address_space == NULL) {
+ return;
+ }
+
+ if (s->has_chain4_alias) {
+ memory_region_del_subregion(s->legacy_address_space, &s->chain4_alias);
+ object_unparent(OBJECT(&s->chain4_alias));
+ s->has_chain4_alias = false;
+ s->plane_updated = 0xf;
+ }
+ if ((sr(s, VGA_SEQ_PLANE_WRITE) & VGA_SR02_ALL_PLANES) ==
+ VGA_SR02_ALL_PLANES && sr(s, VGA_SEQ_MEMORY_MODE) & VGA_SR04_CHN_4M) {
+ offset = 0;
+ switch ((s->gr[VGA_GFX_MISC] >> 2) & 3) {
+ case 0:
+ base = 0xa0000;
+ size = 0x20000;
+ break;
+ case 1:
+ base = 0xa0000;
+ size = 0x10000;
+ offset = s->bank_offset;
+ break;
+ case 2:
+ base = 0xb0000;
+ size = 0x8000;
+ break;
+ case 3:
+ default:
+ base = 0xb8000;
+ size = 0x8000;
+ break;
+ }
+ assert(offset + size <= s->vram_size);
+ memory_region_init_alias(&s->chain4_alias, memory_region_owner(&s->vram),
+ "vga.chain4", &s->vram, offset, size);
+ memory_region_add_subregion_overlap(s->legacy_address_space, base,
+ &s->chain4_alias, 2);
+ s->has_chain4_alias = true;
+ }
+}
+
+static void vga_dumb_update_retrace_info(VGACommonState *s)
+{
+ (void) s;
+}
+
+static void vga_precise_update_retrace_info(VGACommonState *s)
+{
+ int htotal_chars;
+ int hretr_start_char;
+ int hretr_skew_chars;
+ int hretr_end_char;
+
+ int vtotal_lines;
+ int vretr_start_line;
+ int vretr_end_line;
+
+ int dots;
+#if 0
+ int div2, sldiv2;
+#endif
+ int clocking_mode;
+ int clock_sel;
+ const int clk_hz[] = {25175000, 28322000, 25175000, 25175000};
+ int64_t chars_per_sec;
+ struct vga_precise_retrace *r = &s->retrace_info.precise;
+
+ htotal_chars = s->cr[VGA_CRTC_H_TOTAL] + 5;
+ hretr_start_char = s->cr[VGA_CRTC_H_SYNC_START];
+ hretr_skew_chars = (s->cr[VGA_CRTC_H_SYNC_END] >> 5) & 3;
+ hretr_end_char = s->cr[VGA_CRTC_H_SYNC_END] & 0x1f;
+
+ vtotal_lines = (s->cr[VGA_CRTC_V_TOTAL] |
+ (((s->cr[VGA_CRTC_OVERFLOW] & 1) |
+ ((s->cr[VGA_CRTC_OVERFLOW] >> 4) & 2)) << 8)) + 2;
+ vretr_start_line = s->cr[VGA_CRTC_V_SYNC_START] |
+ ((((s->cr[VGA_CRTC_OVERFLOW] >> 2) & 1) |
+ ((s->cr[VGA_CRTC_OVERFLOW] >> 6) & 2)) << 8);
+ vretr_end_line = s->cr[VGA_CRTC_V_SYNC_END] & 0xf;
+
+ clocking_mode = (sr(s, VGA_SEQ_CLOCK_MODE) >> 3) & 1;
+ clock_sel = (s->msr >> 2) & 3;
+ dots = (s->msr & 1) ? 8 : 9;
+
+ chars_per_sec = clk_hz[clock_sel] / dots;
+
+ htotal_chars <<= clocking_mode;
+
+ r->total_chars = vtotal_lines * htotal_chars;
+ if (r->freq) {
+ r->ticks_per_char = NANOSECONDS_PER_SECOND / (r->total_chars * r->freq);
+ } else {
+ r->ticks_per_char = NANOSECONDS_PER_SECOND / chars_per_sec;
+ }
+
+ r->vstart = vretr_start_line;
+ r->vend = r->vstart + vretr_end_line + 1;
+
+ r->hstart = hretr_start_char + hretr_skew_chars;
+ r->hend = r->hstart + hretr_end_char + 1;
+ r->htotal = htotal_chars;
+
+#if 0
+ div2 = (s->cr[VGA_CRTC_MODE] >> 2) & 1;
+ sldiv2 = (s->cr[VGA_CRTC_MODE] >> 3) & 1;
+ printf (
+ "hz=%f\n"
+ "htotal = %d\n"
+ "hretr_start = %d\n"
+ "hretr_skew = %d\n"
+ "hretr_end = %d\n"
+ "vtotal = %d\n"
+ "vretr_start = %d\n"
+ "vretr_end = %d\n"
+ "div2 = %d sldiv2 = %d\n"
+ "clocking_mode = %d\n"
+ "clock_sel = %d %d\n"
+ "dots = %d\n"
+ "ticks/char = %" PRId64 "\n"
+ "\n",
+ (double) NANOSECONDS_PER_SECOND / (r->ticks_per_char * r->total_chars),
+ htotal_chars,
+ hretr_start_char,
+ hretr_skew_chars,
+ hretr_end_char,
+ vtotal_lines,
+ vretr_start_line,
+ vretr_end_line,
+ div2, sldiv2,
+ clocking_mode,
+ clock_sel,
+ clk_hz[clock_sel],
+ dots,
+ r->ticks_per_char
+ );
+#endif
+}
+
+static uint8_t vga_precise_retrace(VGACommonState *s)
+{
+ struct vga_precise_retrace *r = &s->retrace_info.precise;
+ uint8_t val = s->st01 & ~(ST01_V_RETRACE | ST01_DISP_ENABLE);
+
+ if (r->total_chars) {
+ int cur_line, cur_line_char, cur_char;
+ int64_t cur_tick;
+
+ cur_tick = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+
+ cur_char = (cur_tick / r->ticks_per_char) % r->total_chars;
+ cur_line = cur_char / r->htotal;
+
+ if (cur_line >= r->vstart && cur_line <= r->vend) {
+ val |= ST01_V_RETRACE | ST01_DISP_ENABLE;
+ } else {
+ cur_line_char = cur_char % r->htotal;
+ if (cur_line_char >= r->hstart && cur_line_char <= r->hend) {
+ val |= ST01_DISP_ENABLE;
+ }
+ }
+
+ return val;
+ } else {
+ return s->st01 ^ (ST01_V_RETRACE | ST01_DISP_ENABLE);
+ }
+}
+
+static uint8_t vga_dumb_retrace(VGACommonState *s)
+{
+ return s->st01 ^ (ST01_V_RETRACE | ST01_DISP_ENABLE);
+}
+
+int vga_ioport_invalid(VGACommonState *s, uint32_t addr)
+{
+ if (s->msr & VGA_MIS_COLOR) {
+ /* Color */
+ return (addr >= 0x3b0 && addr <= 0x3bf);
+ } else {
+ /* Monochrome */
+ return (addr >= 0x3d0 && addr <= 0x3df);
+ }
+}
+
+uint32_t vga_ioport_read(void *opaque, uint32_t addr)
+{
+ VGACommonState *s = opaque;
+ int val, index;
+
+ if (vga_ioport_invalid(s, addr)) {
+ val = 0xff;
+ } else {
+ switch(addr) {
+ case VGA_ATT_W:
+ if (s->ar_flip_flop == 0) {
+ val = s->ar_index;
+ } else {
+ val = 0;
+ }
+ break;
+ case VGA_ATT_R:
+ index = s->ar_index & 0x1f;
+ if (index < VGA_ATT_C) {
+ val = s->ar[index];
+ } else {
+ val = 0;
+ }
+ break;
+ case VGA_MIS_W:
+ val = s->st00;
+ break;
+ case VGA_SEQ_I:
+ val = s->sr_index;
+ break;
+ case VGA_SEQ_D:
+ val = s->sr[s->sr_index];
+#ifdef DEBUG_VGA_REG
+ printf("vga: read SR%x = 0x%02x\n", s->sr_index, val);
+#endif
+ break;
+ case VGA_PEL_IR:
+ val = s->dac_state;
+ break;
+ case VGA_PEL_IW:
+ val = s->dac_write_index;
+ break;
+ case VGA_PEL_D:
+ val = s->palette[s->dac_read_index * 3 + s->dac_sub_index];
+ if (++s->dac_sub_index == 3) {
+ s->dac_sub_index = 0;
+ s->dac_read_index++;
+ }
+ break;
+ case VGA_FTC_R:
+ val = s->fcr;
+ break;
+ case VGA_MIS_R:
+ val = s->msr;
+ break;
+ case VGA_GFX_I:
+ val = s->gr_index;
+ break;
+ case VGA_GFX_D:
+ val = s->gr[s->gr_index];
+#ifdef DEBUG_VGA_REG
+ printf("vga: read GR%x = 0x%02x\n", s->gr_index, val);
+#endif
+ break;
+ case VGA_CRT_IM:
+ case VGA_CRT_IC:
+ val = s->cr_index;
+ break;
+ case VGA_CRT_DM:
+ case VGA_CRT_DC:
+ val = s->cr[s->cr_index];
+#ifdef DEBUG_VGA_REG
+ printf("vga: read CR%x = 0x%02x\n", s->cr_index, val);
+#endif
+ break;
+ case VGA_IS1_RM:
+ case VGA_IS1_RC:
+ /* just toggle to fool polling */
+ val = s->st01 = s->retrace(s);
+ s->ar_flip_flop = 0;
+ break;
+ default:
+ val = 0x00;
+ break;
+ }
+ }
+ trace_vga_std_read_io(addr, val);
+ return val;
+}
+
+void vga_ioport_write(void *opaque, uint32_t addr, uint32_t val)
+{
+ VGACommonState *s = opaque;
+ int index;
+
+ /* check port range access depending on color/monochrome mode */
+ if (vga_ioport_invalid(s, addr)) {
+ return;
+ }
+ trace_vga_std_write_io(addr, val);
+
+ switch(addr) {
+ case VGA_ATT_W:
+ if (s->ar_flip_flop == 0) {
+ val &= 0x3f;
+ s->ar_index = val;
+ } else {
+ index = s->ar_index & 0x1f;
+ switch(index) {
+ case VGA_ATC_PALETTE0 ... VGA_ATC_PALETTEF:
+ s->ar[index] = val & 0x3f;
+ break;
+ case VGA_ATC_MODE:
+ s->ar[index] = val & ~0x10;
+ break;
+ case VGA_ATC_OVERSCAN:
+ s->ar[index] = val;
+ break;
+ case VGA_ATC_PLANE_ENABLE:
+ s->ar[index] = val & ~0xc0;
+ break;
+ case VGA_ATC_PEL:
+ s->ar[index] = val & ~0xf0;
+ break;
+ case VGA_ATC_COLOR_PAGE:
+ s->ar[index] = val & ~0xf0;
+ break;
+ default:
+ break;
+ }
+ }
+ s->ar_flip_flop ^= 1;
+ break;
+ case VGA_MIS_W:
+ s->msr = val & ~0x10;
+ s->update_retrace_info(s);
+ break;
+ case VGA_SEQ_I:
+ s->sr_index = val & 7;
+ break;
+ case VGA_SEQ_D:
+#ifdef DEBUG_VGA_REG
+ printf("vga: write SR%x = 0x%02x\n", s->sr_index, val);
+#endif
+ s->sr[s->sr_index] = val & sr_mask[s->sr_index];
+ if (s->sr_index == VGA_SEQ_CLOCK_MODE) {
+ s->update_retrace_info(s);
+ }
+ vga_update_memory_access(s);
+ break;
+ case VGA_PEL_IR:
+ s->dac_read_index = val;
+ s->dac_sub_index = 0;
+ s->dac_state = 3;
+ break;
+ case VGA_PEL_IW:
+ s->dac_write_index = val;
+ s->dac_sub_index = 0;
+ s->dac_state = 0;
+ break;
+ case VGA_PEL_D:
+ s->dac_cache[s->dac_sub_index] = val;
+ if (++s->dac_sub_index == 3) {
+ memcpy(&s->palette[s->dac_write_index * 3], s->dac_cache, 3);
+ s->dac_sub_index = 0;
+ s->dac_write_index++;
+ }
+ break;
+ case VGA_GFX_I:
+ s->gr_index = val & 0x0f;
+ break;
+ case VGA_GFX_D:
+#ifdef DEBUG_VGA_REG
+ printf("vga: write GR%x = 0x%02x\n", s->gr_index, val);
+#endif
+ s->gr[s->gr_index] = val & gr_mask[s->gr_index];
+ vbe_update_vgaregs(s);
+ vga_update_memory_access(s);
+ break;
+ case VGA_CRT_IM:
+ case VGA_CRT_IC:
+ s->cr_index = val;
+ break;
+ case VGA_CRT_DM:
+ case VGA_CRT_DC:
+#ifdef DEBUG_VGA_REG
+ printf("vga: write CR%x = 0x%02x\n", s->cr_index, val);
+#endif
+ /* handle CR0-7 protection */
+ if ((s->cr[VGA_CRTC_V_SYNC_END] & VGA_CR11_LOCK_CR0_CR7) &&
+ s->cr_index <= VGA_CRTC_OVERFLOW) {
+ /* can always write bit 4 of CR7 */
+ if (s->cr_index == VGA_CRTC_OVERFLOW) {
+ s->cr[VGA_CRTC_OVERFLOW] = (s->cr[VGA_CRTC_OVERFLOW] & ~0x10) |
+ (val & 0x10);
+ vbe_update_vgaregs(s);
+ }
+ return;
+ }
+ s->cr[s->cr_index] = val;
+ vbe_update_vgaregs(s);
+
+ switch(s->cr_index) {
+ case VGA_CRTC_H_TOTAL:
+ case VGA_CRTC_H_SYNC_START:
+ case VGA_CRTC_H_SYNC_END:
+ case VGA_CRTC_V_TOTAL:
+ case VGA_CRTC_OVERFLOW:
+ case VGA_CRTC_V_SYNC_END:
+ case VGA_CRTC_MODE:
+ s->update_retrace_info(s);
+ break;
+ }
+ break;
+ case VGA_IS1_RM:
+ case VGA_IS1_RC:
+ s->fcr = val & 0x10;
+ break;
+ }
+}
+
+/*
+ * Sanity check vbe register writes.
+ *
+ * As we don't have a way to signal errors to the guest in the bochs
+ * dispi interface we'll go adjust the registers to the closest valid
+ * value.
+ */
+static void vbe_fixup_regs(VGACommonState *s)
+{
+ uint16_t *r = s->vbe_regs;
+ uint32_t bits, linelength, maxy, offset;
+
+ if (!vbe_enabled(s)) {
+ /* vbe is turned off -- nothing to do */
+ return;
+ }
+
+ /* check depth */
+ switch (r[VBE_DISPI_INDEX_BPP]) {
+ case 4:
+ case 8:
+ case 16:
+ case 24:
+ case 32:
+ bits = r[VBE_DISPI_INDEX_BPP];
+ break;
+ case 15:
+ bits = 16;
+ break;
+ default:
+ bits = r[VBE_DISPI_INDEX_BPP] = 8;
+ break;
+ }
+
+ /* check width */
+ r[VBE_DISPI_INDEX_XRES] &= ~7u;
+ if (r[VBE_DISPI_INDEX_XRES] == 0) {
+ r[VBE_DISPI_INDEX_XRES] = 8;
+ }
+ if (r[VBE_DISPI_INDEX_XRES] > VBE_DISPI_MAX_XRES) {
+ r[VBE_DISPI_INDEX_XRES] = VBE_DISPI_MAX_XRES;
+ }
+ r[VBE_DISPI_INDEX_VIRT_WIDTH] &= ~7u;
+ if (r[VBE_DISPI_INDEX_VIRT_WIDTH] > VBE_DISPI_MAX_XRES) {
+ r[VBE_DISPI_INDEX_VIRT_WIDTH] = VBE_DISPI_MAX_XRES;
+ }
+ if (r[VBE_DISPI_INDEX_VIRT_WIDTH] < r[VBE_DISPI_INDEX_XRES]) {
+ r[VBE_DISPI_INDEX_VIRT_WIDTH] = r[VBE_DISPI_INDEX_XRES];
+ }
+
+ /* check height */
+ linelength = r[VBE_DISPI_INDEX_VIRT_WIDTH] * bits / 8;
+ maxy = s->vbe_size / linelength;
+ if (r[VBE_DISPI_INDEX_YRES] == 0) {
+ r[VBE_DISPI_INDEX_YRES] = 1;
+ }
+ if (r[VBE_DISPI_INDEX_YRES] > VBE_DISPI_MAX_YRES) {
+ r[VBE_DISPI_INDEX_YRES] = VBE_DISPI_MAX_YRES;
+ }
+ if (r[VBE_DISPI_INDEX_YRES] > maxy) {
+ r[VBE_DISPI_INDEX_YRES] = maxy;
+ }
+
+ /* check offset */
+ if (r[VBE_DISPI_INDEX_X_OFFSET] > VBE_DISPI_MAX_XRES) {
+ r[VBE_DISPI_INDEX_X_OFFSET] = VBE_DISPI_MAX_XRES;
+ }
+ if (r[VBE_DISPI_INDEX_Y_OFFSET] > VBE_DISPI_MAX_YRES) {
+ r[VBE_DISPI_INDEX_Y_OFFSET] = VBE_DISPI_MAX_YRES;
+ }
+ offset = r[VBE_DISPI_INDEX_X_OFFSET] * bits / 8;
+ offset += r[VBE_DISPI_INDEX_Y_OFFSET] * linelength;
+ if (offset + r[VBE_DISPI_INDEX_YRES] * linelength > s->vbe_size) {
+ r[VBE_DISPI_INDEX_Y_OFFSET] = 0;
+ offset = r[VBE_DISPI_INDEX_X_OFFSET] * bits / 8;
+ if (offset + r[VBE_DISPI_INDEX_YRES] * linelength > s->vbe_size) {
+ r[VBE_DISPI_INDEX_X_OFFSET] = 0;
+ offset = 0;
+ }
+ }
+
+ /* update vga state */
+ r[VBE_DISPI_INDEX_VIRT_HEIGHT] = maxy;
+ s->vbe_line_offset = linelength;
+ s->vbe_start_addr = offset / 4;
+}
+
+/* we initialize the VGA graphic mode */
+static void vbe_update_vgaregs(VGACommonState *s)
+{
+ int h, shift_control;
+
+ if (!vbe_enabled(s)) {
+ /* vbe is turned off -- nothing to do */
+ return;
+ }
+
+ /* graphic mode + memory map 1 */
+ s->gr[VGA_GFX_MISC] = (s->gr[VGA_GFX_MISC] & ~0x0c) | 0x04 |
+ VGA_GR06_GRAPHICS_MODE;
+ s->cr[VGA_CRTC_MODE] |= 3; /* no CGA modes */
+ s->cr[VGA_CRTC_OFFSET] = s->vbe_line_offset >> 3;
+ /* width */
+ s->cr[VGA_CRTC_H_DISP] =
+ (s->vbe_regs[VBE_DISPI_INDEX_XRES] >> 3) - 1;
+ /* height (only meaningful if < 1024) */
+ h = s->vbe_regs[VBE_DISPI_INDEX_YRES] - 1;
+ s->cr[VGA_CRTC_V_DISP_END] = h;
+ s->cr[VGA_CRTC_OVERFLOW] = (s->cr[VGA_CRTC_OVERFLOW] & ~0x42) |
+ ((h >> 7) & 0x02) | ((h >> 3) & 0x40);
+ /* line compare to 1023 */
+ s->cr[VGA_CRTC_LINE_COMPARE] = 0xff;
+ s->cr[VGA_CRTC_OVERFLOW] |= 0x10;
+ s->cr[VGA_CRTC_MAX_SCAN] |= 0x40;
+
+ if (s->vbe_regs[VBE_DISPI_INDEX_BPP] == 4) {
+ shift_control = 0;
+ s->sr_vbe[VGA_SEQ_CLOCK_MODE] &= ~8; /* no double line */
+ } else {
+ shift_control = 2;
+ /* set chain 4 mode */
+ s->sr_vbe[VGA_SEQ_MEMORY_MODE] |= VGA_SR04_CHN_4M;
+ /* activate all planes */
+ s->sr_vbe[VGA_SEQ_PLANE_WRITE] |= VGA_SR02_ALL_PLANES;
+ }
+ s->gr[VGA_GFX_MODE] = (s->gr[VGA_GFX_MODE] & ~0x60) |
+ (shift_control << 5);
+ s->cr[VGA_CRTC_MAX_SCAN] &= ~0x9f; /* no double scan */
+}
+
+static uint32_t vbe_ioport_read_index(void *opaque, uint32_t addr)
+{
+ VGACommonState *s = opaque;
+ return s->vbe_index;
+}
+
+uint32_t vbe_ioport_read_data(void *opaque, uint32_t addr)
+{
+ VGACommonState *s = opaque;
+ uint32_t val;
+
+ if (s->vbe_index < VBE_DISPI_INDEX_NB) {
+ if (s->vbe_regs[VBE_DISPI_INDEX_ENABLE] & VBE_DISPI_GETCAPS) {
+ switch(s->vbe_index) {
+ /* XXX: do not hardcode ? */
+ case VBE_DISPI_INDEX_XRES:
+ val = VBE_DISPI_MAX_XRES;
+ break;
+ case VBE_DISPI_INDEX_YRES:
+ val = VBE_DISPI_MAX_YRES;
+ break;
+ case VBE_DISPI_INDEX_BPP:
+ val = VBE_DISPI_MAX_BPP;
+ break;
+ default:
+ val = s->vbe_regs[s->vbe_index];
+ break;
+ }
+ } else {
+ val = s->vbe_regs[s->vbe_index];
+ }
+ } else if (s->vbe_index == VBE_DISPI_INDEX_VIDEO_MEMORY_64K) {
+ val = s->vbe_size / (64 * KiB);
+ } else {
+ val = 0;
+ }
+ trace_vga_vbe_read(s->vbe_index, val);
+ return val;
+}
+
+void vbe_ioport_write_index(void *opaque, uint32_t addr, uint32_t val)
+{
+ VGACommonState *s = opaque;
+ s->vbe_index = val;
+}
+
+void vbe_ioport_write_data(void *opaque, uint32_t addr, uint32_t val)
+{
+ VGACommonState *s = opaque;
+
+ if (s->vbe_index <= VBE_DISPI_INDEX_NB) {
+ trace_vga_vbe_write(s->vbe_index, val);
+ switch(s->vbe_index) {
+ case VBE_DISPI_INDEX_ID:
+ if (val == VBE_DISPI_ID0 ||
+ val == VBE_DISPI_ID1 ||
+ val == VBE_DISPI_ID2 ||
+ val == VBE_DISPI_ID3 ||
+ val == VBE_DISPI_ID4 ||
+ val == VBE_DISPI_ID5) {
+ s->vbe_regs[s->vbe_index] = val;
+ }
+ break;
+ case VBE_DISPI_INDEX_XRES:
+ case VBE_DISPI_INDEX_YRES:
+ case VBE_DISPI_INDEX_BPP:
+ case VBE_DISPI_INDEX_VIRT_WIDTH:
+ case VBE_DISPI_INDEX_X_OFFSET:
+ case VBE_DISPI_INDEX_Y_OFFSET:
+ s->vbe_regs[s->vbe_index] = val;
+ vbe_fixup_regs(s);
+ vbe_update_vgaregs(s);
+ break;
+ case VBE_DISPI_INDEX_BANK:
+ val &= s->vbe_bank_mask;
+ s->vbe_regs[s->vbe_index] = val;
+ s->bank_offset = (val << 16);
+ vga_update_memory_access(s);
+ break;
+ case VBE_DISPI_INDEX_ENABLE:
+ if ((val & VBE_DISPI_ENABLED) &&
+ !(s->vbe_regs[VBE_DISPI_INDEX_ENABLE] & VBE_DISPI_ENABLED)) {
+
+ s->vbe_regs[VBE_DISPI_INDEX_VIRT_WIDTH] = 0;
+ s->vbe_regs[VBE_DISPI_INDEX_X_OFFSET] = 0;
+ s->vbe_regs[VBE_DISPI_INDEX_Y_OFFSET] = 0;
+ s->vbe_regs[VBE_DISPI_INDEX_ENABLE] |= VBE_DISPI_ENABLED;
+ vbe_fixup_regs(s);
+ vbe_update_vgaregs(s);
+
+ /* clear the screen */
+ if (!(val & VBE_DISPI_NOCLEARMEM)) {
+ memset(s->vram_ptr, 0,
+ s->vbe_regs[VBE_DISPI_INDEX_YRES] * s->vbe_line_offset);
+ }
+ } else {
+ s->bank_offset = 0;
+ }
+ s->dac_8bit = (val & VBE_DISPI_8BIT_DAC) > 0;
+ s->vbe_regs[s->vbe_index] = val;
+ vga_update_memory_access(s);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+/* called for accesses between 0xa0000 and 0xc0000 */
+uint32_t vga_mem_readb(VGACommonState *s, hwaddr addr)
+{
+ int memory_map_mode, plane;
+ uint32_t ret;
+
+ /* convert to VGA memory offset */
+ memory_map_mode = (s->gr[VGA_GFX_MISC] >> 2) & 3;
+ addr &= 0x1ffff;
+ switch(memory_map_mode) {
+ case 0:
+ break;
+ case 1:
+ if (addr >= 0x10000)
+ return 0xff;
+ addr += s->bank_offset;
+ break;
+ case 2:
+ addr -= 0x10000;
+ if (addr >= 0x8000)
+ return 0xff;
+ break;
+ default:
+ case 3:
+ addr -= 0x18000;
+ if (addr >= 0x8000)
+ return 0xff;
+ break;
+ }
+
+ if (sr(s, VGA_SEQ_MEMORY_MODE) & VGA_SR04_CHN_4M) {
+ /* chain 4 mode : simplest access */
+ assert(addr < s->vram_size);
+ ret = s->vram_ptr[addr];
+ } else if (s->gr[VGA_GFX_MODE] & 0x10) {
+ /* odd/even mode (aka text mode mapping) */
+ plane = (s->gr[VGA_GFX_PLANE_READ] & 2) | (addr & 1);
+ addr = ((addr & ~1) << 1) | plane;
+ if (addr >= s->vram_size) {
+ return 0xff;
+ }
+ ret = s->vram_ptr[addr];
+ } else {
+ /* standard VGA latched access */
+ if (addr * sizeof(uint32_t) >= s->vram_size) {
+ return 0xff;
+ }
+ s->latch = ((uint32_t *)s->vram_ptr)[addr];
+
+ if (!(s->gr[VGA_GFX_MODE] & 0x08)) {
+ /* read mode 0 */
+ plane = s->gr[VGA_GFX_PLANE_READ];
+ ret = GET_PLANE(s->latch, plane);
+ } else {
+ /* read mode 1 */
+ ret = (s->latch ^ mask16[s->gr[VGA_GFX_COMPARE_VALUE]]) &
+ mask16[s->gr[VGA_GFX_COMPARE_MASK]];
+ ret |= ret >> 16;
+ ret |= ret >> 8;
+ ret = (~ret) & 0xff;
+ }
+ }
+ return ret;
+}
+
+/* called for accesses between 0xa0000 and 0xc0000 */
+void vga_mem_writeb(VGACommonState *s, hwaddr addr, uint32_t val)
+{
+ int memory_map_mode, plane, write_mode, b, func_select, mask;
+ uint32_t write_mask, bit_mask, set_mask;
+
+#ifdef DEBUG_VGA_MEM
+ printf("vga: [0x" TARGET_FMT_plx "] = 0x%02x\n", addr, val);
+#endif
+ /* convert to VGA memory offset */
+ memory_map_mode = (s->gr[VGA_GFX_MISC] >> 2) & 3;
+ addr &= 0x1ffff;
+ switch(memory_map_mode) {
+ case 0:
+ break;
+ case 1:
+ if (addr >= 0x10000)
+ return;
+ addr += s->bank_offset;
+ break;
+ case 2:
+ addr -= 0x10000;
+ if (addr >= 0x8000)
+ return;
+ break;
+ default:
+ case 3:
+ addr -= 0x18000;
+ if (addr >= 0x8000)
+ return;
+ break;
+ }
+
+ if (sr(s, VGA_SEQ_MEMORY_MODE) & VGA_SR04_CHN_4M) {
+ /* chain 4 mode : simplest access */
+ plane = addr & 3;
+ mask = (1 << plane);
+ if (sr(s, VGA_SEQ_PLANE_WRITE) & mask) {
+ assert(addr < s->vram_size);
+ s->vram_ptr[addr] = val;
+#ifdef DEBUG_VGA_MEM
+ printf("vga: chain4: [0x" TARGET_FMT_plx "]\n", addr);
+#endif
+ s->plane_updated |= mask; /* only used to detect font change */
+ memory_region_set_dirty(&s->vram, addr, 1);
+ }
+ } else if (s->gr[VGA_GFX_MODE] & 0x10) {
+ /* odd/even mode (aka text mode mapping) */
+ plane = (s->gr[VGA_GFX_PLANE_READ] & 2) | (addr & 1);
+ mask = (1 << plane);
+ if (sr(s, VGA_SEQ_PLANE_WRITE) & mask) {
+ addr = ((addr & ~1) << 1) | plane;
+ if (addr >= s->vram_size) {
+ return;
+ }
+ s->vram_ptr[addr] = val;
+#ifdef DEBUG_VGA_MEM
+ printf("vga: odd/even: [0x" TARGET_FMT_plx "]\n", addr);
+#endif
+ s->plane_updated |= mask; /* only used to detect font change */
+ memory_region_set_dirty(&s->vram, addr, 1);
+ }
+ } else {
+ /* standard VGA latched access */
+ write_mode = s->gr[VGA_GFX_MODE] & 3;
+ switch(write_mode) {
+ default:
+ case 0:
+ /* rotate */
+ b = s->gr[VGA_GFX_DATA_ROTATE] & 7;
+ val = ((val >> b) | (val << (8 - b))) & 0xff;
+ val |= val << 8;
+ val |= val << 16;
+
+ /* apply set/reset mask */
+ set_mask = mask16[s->gr[VGA_GFX_SR_ENABLE]];
+ val = (val & ~set_mask) |
+ (mask16[s->gr[VGA_GFX_SR_VALUE]] & set_mask);
+ bit_mask = s->gr[VGA_GFX_BIT_MASK];
+ break;
+ case 1:
+ val = s->latch;
+ goto do_write;
+ case 2:
+ val = mask16[val & 0x0f];
+ bit_mask = s->gr[VGA_GFX_BIT_MASK];
+ break;
+ case 3:
+ /* rotate */
+ b = s->gr[VGA_GFX_DATA_ROTATE] & 7;
+ val = (val >> b) | (val << (8 - b));
+
+ bit_mask = s->gr[VGA_GFX_BIT_MASK] & val;
+ val = mask16[s->gr[VGA_GFX_SR_VALUE]];
+ break;
+ }
+
+ /* apply logical operation */
+ func_select = s->gr[VGA_GFX_DATA_ROTATE] >> 3;
+ switch(func_select) {
+ case 0:
+ default:
+ /* nothing to do */
+ break;
+ case 1:
+ /* and */
+ val &= s->latch;
+ break;
+ case 2:
+ /* or */
+ val |= s->latch;
+ break;
+ case 3:
+ /* xor */
+ val ^= s->latch;
+ break;
+ }
+
+ /* apply bit mask */
+ bit_mask |= bit_mask << 8;
+ bit_mask |= bit_mask << 16;
+ val = (val & bit_mask) | (s->latch & ~bit_mask);
+
+ do_write:
+ /* mask data according to sr[2] */
+ mask = sr(s, VGA_SEQ_PLANE_WRITE);
+ s->plane_updated |= mask; /* only used to detect font change */
+ write_mask = mask16[mask];
+ if (addr * sizeof(uint32_t) >= s->vram_size) {
+ return;
+ }
+ ((uint32_t *)s->vram_ptr)[addr] =
+ (((uint32_t *)s->vram_ptr)[addr] & ~write_mask) |
+ (val & write_mask);
+#ifdef DEBUG_VGA_MEM
+ printf("vga: latch: [0x" TARGET_FMT_plx "] mask=0x%08x val=0x%08x\n",
+ addr * 4, write_mask, val);
+#endif
+ memory_region_set_dirty(&s->vram, addr << 2, sizeof(uint32_t));
+ }
+}
+
+typedef void vga_draw_line_func(VGACommonState *s1, uint8_t *d,
+ uint32_t srcaddr, int width);
+
+#include "vga-access.h"
+#include "vga-helpers.h"
+
+/* return true if the palette was modified */
+static int update_palette16(VGACommonState *s)
+{
+ int full_update, i;
+ uint32_t v, col, *palette;
+
+ full_update = 0;
+ palette = s->last_palette;
+ for(i = 0; i < 16; i++) {
+ v = s->ar[i];
+ if (s->ar[VGA_ATC_MODE] & 0x80) {
+ v = ((s->ar[VGA_ATC_COLOR_PAGE] & 0xf) << 4) | (v & 0xf);
+ } else {
+ v = ((s->ar[VGA_ATC_COLOR_PAGE] & 0xc) << 4) | (v & 0x3f);
+ }
+ v = v * 3;
+ col = rgb_to_pixel32(c6_to_8(s->palette[v]),
+ c6_to_8(s->palette[v + 1]),
+ c6_to_8(s->palette[v + 2]));
+ if (col != palette[i]) {
+ full_update = 1;
+ palette[i] = col;
+ }
+ }
+ return full_update;
+}
+
+/* return true if the palette was modified */
+static int update_palette256(VGACommonState *s)
+{
+ int full_update, i;
+ uint32_t v, col, *palette;
+
+ full_update = 0;
+ palette = s->last_palette;
+ v = 0;
+ for(i = 0; i < 256; i++) {
+ if (s->dac_8bit) {
+ col = rgb_to_pixel32(s->palette[v],
+ s->palette[v + 1],
+ s->palette[v + 2]);
+ } else {
+ col = rgb_to_pixel32(c6_to_8(s->palette[v]),
+ c6_to_8(s->palette[v + 1]),
+ c6_to_8(s->palette[v + 2]));
+ }
+ if (col != palette[i]) {
+ full_update = 1;
+ palette[i] = col;
+ }
+ v += 3;
+ }
+ return full_update;
+}
+
+static void vga_get_offsets(VGACommonState *s,
+ uint32_t *pline_offset,
+ uint32_t *pstart_addr,
+ uint32_t *pline_compare)
+{
+ uint32_t start_addr, line_offset, line_compare;
+
+ if (vbe_enabled(s)) {
+ line_offset = s->vbe_line_offset;
+ start_addr = s->vbe_start_addr;
+ line_compare = 65535;
+ } else {
+ /* compute line_offset in bytes */
+ line_offset = s->cr[VGA_CRTC_OFFSET];
+ line_offset <<= 3;
+
+ /* starting address */
+ start_addr = s->cr[VGA_CRTC_START_LO] |
+ (s->cr[VGA_CRTC_START_HI] << 8);
+
+ /* line compare */
+ line_compare = s->cr[VGA_CRTC_LINE_COMPARE] |
+ ((s->cr[VGA_CRTC_OVERFLOW] & 0x10) << 4) |
+ ((s->cr[VGA_CRTC_MAX_SCAN] & 0x40) << 3);
+ }
+ *pline_offset = line_offset;
+ *pstart_addr = start_addr;
+ *pline_compare = line_compare;
+}
+
+/* update start_addr and line_offset. Return TRUE if modified */
+static int update_basic_params(VGACommonState *s)
+{
+ int full_update;
+ uint32_t start_addr, line_offset, line_compare;
+
+ full_update = 0;
+
+ s->get_offsets(s, &line_offset, &start_addr, &line_compare);
+
+ if (line_offset != s->line_offset ||
+ start_addr != s->start_addr ||
+ line_compare != s->line_compare) {
+ s->line_offset = line_offset;
+ s->start_addr = start_addr;
+ s->line_compare = line_compare;
+ full_update = 1;
+ }
+ return full_update;
+}
+
+
+static const uint8_t cursor_glyph[32 * 4] = {
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+};
+
+static void vga_get_text_resolution(VGACommonState *s, int *pwidth, int *pheight,
+ int *pcwidth, int *pcheight)
+{
+ int width, cwidth, height, cheight;
+
+ /* total width & height */
+ cheight = (s->cr[VGA_CRTC_MAX_SCAN] & 0x1f) + 1;
+ cwidth = 8;
+ if (!(sr(s, VGA_SEQ_CLOCK_MODE) & VGA_SR01_CHAR_CLK_8DOTS)) {
+ cwidth = 9;
+ }
+ if (sr(s, VGA_SEQ_CLOCK_MODE) & 0x08) {
+ cwidth = 16; /* NOTE: no 18 pixel wide */
+ }
+ width = (s->cr[VGA_CRTC_H_DISP] + 1);
+ if (s->cr[VGA_CRTC_V_TOTAL] == 100) {
+ /* ugly hack for CGA 160x100x16 - explain me the logic */
+ height = 100;
+ } else {
+ height = s->cr[VGA_CRTC_V_DISP_END] |
+ ((s->cr[VGA_CRTC_OVERFLOW] & 0x02) << 7) |
+ ((s->cr[VGA_CRTC_OVERFLOW] & 0x40) << 3);
+ height = (height + 1) / cheight;
+ }
+
+ *pwidth = width;
+ *pheight = height;
+ *pcwidth = cwidth;
+ *pcheight = cheight;
+}
+
+/*
+ * Text mode update
+ * Missing:
+ * - double scan
+ * - double width
+ * - underline
+ * - flashing
+ */
+static void vga_draw_text(VGACommonState *s, int full_update)
+{
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ int cx, cy, cheight, cw, ch, cattr, height, width, ch_attr;
+ int cx_min, cx_max, linesize, x_incr, line, line1;
+ uint32_t offset, fgcol, bgcol, v, cursor_offset;
+ uint8_t *d1, *d, *src, *dest, *cursor_ptr;
+ const uint8_t *font_ptr, *font_base[2];
+ int dup9, line_offset;
+ uint32_t *palette;
+ uint32_t *ch_attr_ptr;
+ int64_t now = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL);
+
+ /* compute font data address (in plane 2) */
+ v = sr(s, VGA_SEQ_CHARACTER_MAP);
+ offset = (((v >> 4) & 1) | ((v << 1) & 6)) * 8192 * 4 + 2;
+ if (offset != s->font_offsets[0]) {
+ s->font_offsets[0] = offset;
+ full_update = 1;
+ }
+ font_base[0] = s->vram_ptr + offset;
+
+ offset = (((v >> 5) & 1) | ((v >> 1) & 6)) * 8192 * 4 + 2;
+ font_base[1] = s->vram_ptr + offset;
+ if (offset != s->font_offsets[1]) {
+ s->font_offsets[1] = offset;
+ full_update = 1;
+ }
+ if (s->plane_updated & (1 << 2) || s->has_chain4_alias) {
+ /* if the plane 2 was modified since the last display, it
+ indicates the font may have been modified */
+ s->plane_updated = 0;
+ full_update = 1;
+ }
+ full_update |= update_basic_params(s);
+
+ line_offset = s->line_offset;
+
+ vga_get_text_resolution(s, &width, &height, &cw, &cheight);
+ if ((height * width) <= 1) {
+ /* better than nothing: exit if transient size is too small */
+ return;
+ }
+ if ((height * width) > CH_ATTR_SIZE) {
+ /* better than nothing: exit if transient size is too big */
+ return;
+ }
+
+ if (width != s->last_width || height != s->last_height ||
+ cw != s->last_cw || cheight != s->last_ch || s->last_depth) {
+ s->last_scr_width = width * cw;
+ s->last_scr_height = height * cheight;
+ qemu_console_resize(s->con, s->last_scr_width, s->last_scr_height);
+ surface = qemu_console_surface(s->con);
+ dpy_text_resize(s->con, width, height);
+ s->last_depth = 0;
+ s->last_width = width;
+ s->last_height = height;
+ s->last_ch = cheight;
+ s->last_cw = cw;
+ full_update = 1;
+ }
+ full_update |= update_palette16(s);
+ palette = s->last_palette;
+ x_incr = cw * surface_bytes_per_pixel(surface);
+
+ if (full_update) {
+ s->full_update_text = 1;
+ }
+ if (s->full_update_gfx) {
+ s->full_update_gfx = 0;
+ full_update |= 1;
+ }
+
+ cursor_offset = ((s->cr[VGA_CRTC_CURSOR_HI] << 8) |
+ s->cr[VGA_CRTC_CURSOR_LO]) - s->start_addr;
+ if (cursor_offset != s->cursor_offset ||
+ s->cr[VGA_CRTC_CURSOR_START] != s->cursor_start ||
+ s->cr[VGA_CRTC_CURSOR_END] != s->cursor_end) {
+ /* if the cursor position changed, we update the old and new
+ chars */
+ if (s->cursor_offset < CH_ATTR_SIZE)
+ s->last_ch_attr[s->cursor_offset] = -1;
+ if (cursor_offset < CH_ATTR_SIZE)
+ s->last_ch_attr[cursor_offset] = -1;
+ s->cursor_offset = cursor_offset;
+ s->cursor_start = s->cr[VGA_CRTC_CURSOR_START];
+ s->cursor_end = s->cr[VGA_CRTC_CURSOR_END];
+ }
+ cursor_ptr = s->vram_ptr + (s->start_addr + cursor_offset) * 4;
+ if (now >= s->cursor_blink_time) {
+ s->cursor_blink_time = now + VGA_TEXT_CURSOR_PERIOD_MS / 2;
+ s->cursor_visible_phase = !s->cursor_visible_phase;
+ }
+
+ dest = surface_data(surface);
+ linesize = surface_stride(surface);
+ ch_attr_ptr = s->last_ch_attr;
+ line = 0;
+ offset = s->start_addr * 4;
+ for(cy = 0; cy < height; cy++) {
+ d1 = dest;
+ src = s->vram_ptr + offset;
+ cx_min = width;
+ cx_max = -1;
+ for(cx = 0; cx < width; cx++) {
+ if (src + sizeof(uint16_t) > s->vram_ptr + s->vram_size) {
+ break;
+ }
+ ch_attr = *(uint16_t *)src;
+ if (full_update || ch_attr != *ch_attr_ptr || src == cursor_ptr) {
+ if (cx < cx_min)
+ cx_min = cx;
+ if (cx > cx_max)
+ cx_max = cx;
+ *ch_attr_ptr = ch_attr;
+#ifdef HOST_WORDS_BIGENDIAN
+ ch = ch_attr >> 8;
+ cattr = ch_attr & 0xff;
+#else
+ ch = ch_attr & 0xff;
+ cattr = ch_attr >> 8;
+#endif
+ font_ptr = font_base[(cattr >> 3) & 1];
+ font_ptr += 32 * 4 * ch;
+ bgcol = palette[cattr >> 4];
+ fgcol = palette[cattr & 0x0f];
+ if (cw == 16) {
+ vga_draw_glyph16(d1, linesize,
+ font_ptr, cheight, fgcol, bgcol);
+ } else if (cw != 9) {
+ vga_draw_glyph8(d1, linesize,
+ font_ptr, cheight, fgcol, bgcol);
+ } else {
+ dup9 = 0;
+ if (ch >= 0xb0 && ch <= 0xdf &&
+ (s->ar[VGA_ATC_MODE] & 0x04)) {
+ dup9 = 1;
+ }
+ vga_draw_glyph9(d1, linesize,
+ font_ptr, cheight, fgcol, bgcol, dup9);
+ }
+ if (src == cursor_ptr &&
+ !(s->cr[VGA_CRTC_CURSOR_START] & 0x20) &&
+ s->cursor_visible_phase) {
+ int line_start, line_last, h;
+ /* draw the cursor */
+ line_start = s->cr[VGA_CRTC_CURSOR_START] & 0x1f;
+ line_last = s->cr[VGA_CRTC_CURSOR_END] & 0x1f;
+ /* XXX: check that */
+ if (line_last > cheight - 1)
+ line_last = cheight - 1;
+ if (line_last >= line_start && line_start < cheight) {
+ h = line_last - line_start + 1;
+ d = d1 + linesize * line_start;
+ if (cw == 16) {
+ vga_draw_glyph16(d, linesize,
+ cursor_glyph, h, fgcol, bgcol);
+ } else if (cw != 9) {
+ vga_draw_glyph8(d, linesize,
+ cursor_glyph, h, fgcol, bgcol);
+ } else {
+ vga_draw_glyph9(d, linesize,
+ cursor_glyph, h, fgcol, bgcol, 1);
+ }
+ }
+ }
+ }
+ d1 += x_incr;
+ src += 4;
+ ch_attr_ptr++;
+ }
+ if (cx_max != -1) {
+ dpy_gfx_update(s->con, cx_min * cw, cy * cheight,
+ (cx_max - cx_min + 1) * cw, cheight);
+ }
+ dest += linesize * cheight;
+ line1 = line + cheight;
+ offset += line_offset;
+ if (line < s->line_compare && line1 >= s->line_compare) {
+ offset = 0;
+ }
+ line = line1;
+ }
+}
+
+enum {
+ VGA_DRAW_LINE2,
+ VGA_DRAW_LINE2D2,
+ VGA_DRAW_LINE4,
+ VGA_DRAW_LINE4D2,
+ VGA_DRAW_LINE8D2,
+ VGA_DRAW_LINE8,
+ VGA_DRAW_LINE15_LE,
+ VGA_DRAW_LINE16_LE,
+ VGA_DRAW_LINE24_LE,
+ VGA_DRAW_LINE32_LE,
+ VGA_DRAW_LINE15_BE,
+ VGA_DRAW_LINE16_BE,
+ VGA_DRAW_LINE24_BE,
+ VGA_DRAW_LINE32_BE,
+ VGA_DRAW_LINE_NB,
+};
+
+static vga_draw_line_func * const vga_draw_line_table[VGA_DRAW_LINE_NB] = {
+ vga_draw_line2,
+ vga_draw_line2d2,
+ vga_draw_line4,
+ vga_draw_line4d2,
+ vga_draw_line8d2,
+ vga_draw_line8,
+ vga_draw_line15_le,
+ vga_draw_line16_le,
+ vga_draw_line24_le,
+ vga_draw_line32_le,
+ vga_draw_line15_be,
+ vga_draw_line16_be,
+ vga_draw_line24_be,
+ vga_draw_line32_be,
+};
+
+static int vga_get_bpp(VGACommonState *s)
+{
+ int ret;
+
+ if (vbe_enabled(s)) {
+ ret = s->vbe_regs[VBE_DISPI_INDEX_BPP];
+ } else {
+ ret = 0;
+ }
+ return ret;
+}
+
+static void vga_get_resolution(VGACommonState *s, int *pwidth, int *pheight)
+{
+ int width, height;
+
+ if (vbe_enabled(s)) {
+ width = s->vbe_regs[VBE_DISPI_INDEX_XRES];
+ height = s->vbe_regs[VBE_DISPI_INDEX_YRES];
+ } else {
+ width = (s->cr[VGA_CRTC_H_DISP] + 1) * 8;
+ height = s->cr[VGA_CRTC_V_DISP_END] |
+ ((s->cr[VGA_CRTC_OVERFLOW] & 0x02) << 7) |
+ ((s->cr[VGA_CRTC_OVERFLOW] & 0x40) << 3);
+ height = (height + 1);
+ }
+ *pwidth = width;
+ *pheight = height;
+}
+
+void vga_invalidate_scanlines(VGACommonState *s, int y1, int y2)
+{
+ int y;
+ if (y1 >= VGA_MAX_HEIGHT)
+ return;
+ if (y2 >= VGA_MAX_HEIGHT)
+ y2 = VGA_MAX_HEIGHT;
+ for(y = y1; y < y2; y++) {
+ s->invalidated_y_table[y >> 5] |= 1 << (y & 0x1f);
+ }
+}
+
+static bool vga_scanline_invalidated(VGACommonState *s, int y)
+{
+ if (y >= VGA_MAX_HEIGHT) {
+ return false;
+ }
+ return s->invalidated_y_table[y >> 5] & (1 << (y & 0x1f));
+}
+
+void vga_dirty_log_start(VGACommonState *s)
+{
+ memory_region_set_log(&s->vram, true, DIRTY_MEMORY_VGA);
+}
+
+void vga_dirty_log_stop(VGACommonState *s)
+{
+ memory_region_set_log(&s->vram, false, DIRTY_MEMORY_VGA);
+}
+
+/*
+ * graphic modes
+ */
+static void vga_draw_graphic(VGACommonState *s, int full_update)
+{
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ int y1, y, update, linesize, y_start, double_scan, mask, depth;
+ int width, height, shift_control, bwidth, bits;
+ ram_addr_t page0, page1, region_start, region_end;
+ DirtyBitmapSnapshot *snap = NULL;
+ int disp_width, multi_scan, multi_run;
+ uint8_t *d;
+ uint32_t v, addr1, addr;
+ vga_draw_line_func *vga_draw_line = NULL;
+ bool share_surface, force_shadow = false;
+ pixman_format_code_t format;
+#ifdef HOST_WORDS_BIGENDIAN
+ bool byteswap = !s->big_endian_fb;
+#else
+ bool byteswap = s->big_endian_fb;
+#endif
+
+ full_update |= update_basic_params(s);
+
+ s->get_resolution(s, &width, &height);
+ disp_width = width;
+ depth = s->get_bpp(s);
+
+ region_start = (s->start_addr * 4);
+ region_end = region_start + (ram_addr_t)s->line_offset * height;
+ region_end += width * depth / 8; /* scanline length */
+ region_end -= s->line_offset;
+ if (region_end > s->vbe_size || depth == 0 || depth == 15) {
+ /*
+ * We land here on:
+ * - wraps around (can happen with cirrus vbe modes)
+ * - depth == 0 (256 color palette video mode)
+ * - depth == 15
+ *
+ * Take the safe and slow route:
+ * - create a dirty bitmap snapshot for all vga memory.
+ * - force shadowing (so all vga memory access goes
+ * through vga_read_*() helpers).
+ *
+ * Given this affects only vga features which are pretty much
+ * unused by modern guests there should be no performance
+ * impact.
+ */
+ region_start = 0;
+ region_end = s->vbe_size;
+ force_shadow = true;
+ }
+
+ shift_control = (s->gr[VGA_GFX_MODE] >> 5) & 3;
+ double_scan = (s->cr[VGA_CRTC_MAX_SCAN] >> 7);
+ if (shift_control != 1) {
+ multi_scan = (((s->cr[VGA_CRTC_MAX_SCAN] & 0x1f) + 1) << double_scan)
+ - 1;
+ } else {
+ /* in CGA modes, multi_scan is ignored */
+ /* XXX: is it correct ? */
+ multi_scan = double_scan;
+ }
+ multi_run = multi_scan;
+ if (shift_control != s->shift_control ||
+ double_scan != s->double_scan) {
+ full_update = 1;
+ s->shift_control = shift_control;
+ s->double_scan = double_scan;
+ }
+
+ if (shift_control == 0) {
+ if (sr(s, VGA_SEQ_CLOCK_MODE) & 8) {
+ disp_width <<= 1;
+ }
+ } else if (shift_control == 1) {
+ if (sr(s, VGA_SEQ_CLOCK_MODE) & 8) {
+ disp_width <<= 1;
+ }
+ }
+
+ /*
+ * Check whether we can share the surface with the backend
+ * or whether we need a shadow surface. We share native
+ * endian surfaces for 15bpp and above and byteswapped
+ * surfaces for 24bpp and above.
+ */
+ format = qemu_default_pixman_format(depth, !byteswap);
+ if (format) {
+ share_surface = dpy_gfx_check_format(s->con, format)
+ && !s->force_shadow && !force_shadow;
+ } else {
+ share_surface = false;
+ }
+
+ if (s->line_offset != s->last_line_offset ||
+ disp_width != s->last_width ||
+ height != s->last_height ||
+ s->last_depth != depth ||
+ s->last_byteswap != byteswap ||
+ share_surface != is_buffer_shared(surface)) {
+ /* display parameters changed -> need new display surface */
+ s->last_scr_width = disp_width;
+ s->last_scr_height = height;
+ s->last_width = disp_width;
+ s->last_height = height;
+ s->last_line_offset = s->line_offset;
+ s->last_depth = depth;
+ s->last_byteswap = byteswap;
+ full_update = 1;
+ }
+ if (surface_data(surface) != s->vram_ptr + (s->start_addr * 4)
+ && is_buffer_shared(surface)) {
+ /* base address changed (page flip) -> shared display surfaces
+ * must be updated with the new base address */
+ full_update = 1;
+ }
+
+ if (full_update) {
+ if (share_surface) {
+ surface = qemu_create_displaysurface_from(disp_width,
+ height, format, s->line_offset,
+ s->vram_ptr + (s->start_addr * 4));
+ dpy_gfx_replace_surface(s->con, surface);
+ } else {
+ qemu_console_resize(s->con, disp_width, height);
+ surface = qemu_console_surface(s->con);
+ }
+ }
+
+ if (shift_control == 0) {
+ full_update |= update_palette16(s);
+ if (sr(s, VGA_SEQ_CLOCK_MODE) & 8) {
+ v = VGA_DRAW_LINE4D2;
+ } else {
+ v = VGA_DRAW_LINE4;
+ }
+ bits = 4;
+ } else if (shift_control == 1) {
+ full_update |= update_palette16(s);
+ if (sr(s, VGA_SEQ_CLOCK_MODE) & 8) {
+ v = VGA_DRAW_LINE2D2;
+ } else {
+ v = VGA_DRAW_LINE2;
+ }
+ bits = 4;
+ } else {
+ switch(s->get_bpp(s)) {
+ default:
+ case 0:
+ full_update |= update_palette256(s);
+ v = VGA_DRAW_LINE8D2;
+ bits = 4;
+ break;
+ case 8:
+ full_update |= update_palette256(s);
+ v = VGA_DRAW_LINE8;
+ bits = 8;
+ break;
+ case 15:
+ v = s->big_endian_fb ? VGA_DRAW_LINE15_BE : VGA_DRAW_LINE15_LE;
+ bits = 16;
+ break;
+ case 16:
+ v = s->big_endian_fb ? VGA_DRAW_LINE16_BE : VGA_DRAW_LINE16_LE;
+ bits = 16;
+ break;
+ case 24:
+ v = s->big_endian_fb ? VGA_DRAW_LINE24_BE : VGA_DRAW_LINE24_LE;
+ bits = 24;
+ break;
+ case 32:
+ v = s->big_endian_fb ? VGA_DRAW_LINE32_BE : VGA_DRAW_LINE32_LE;
+ bits = 32;
+ break;
+ }
+ }
+ vga_draw_line = vga_draw_line_table[v];
+
+ if (!is_buffer_shared(surface) && s->cursor_invalidate) {
+ s->cursor_invalidate(s);
+ }
+
+#if 0
+ printf("w=%d h=%d v=%d line_offset=%d cr[0x09]=0x%02x cr[0x17]=0x%02x linecmp=%d sr[0x01]=0x%02x\n",
+ width, height, v, line_offset, s->cr[9], s->cr[VGA_CRTC_MODE],
+ s->line_compare, sr(s, VGA_SEQ_CLOCK_MODE));
+#endif
+ addr1 = (s->start_addr * 4);
+ bwidth = DIV_ROUND_UP(width * bits, 8);
+ y_start = -1;
+ d = surface_data(surface);
+ linesize = surface_stride(surface);
+ y1 = 0;
+
+ if (!full_update) {
+ if (s->line_compare < height) {
+ /* split screen mode */
+ region_start = 0;
+ }
+ snap = memory_region_snapshot_and_clear_dirty(&s->vram, region_start,
+ region_end - region_start,
+ DIRTY_MEMORY_VGA);
+ }
+
+ for(y = 0; y < height; y++) {
+ addr = addr1;
+ if (!(s->cr[VGA_CRTC_MODE] & 1)) {
+ int shift;
+ /* CGA compatibility handling */
+ shift = 14 + ((s->cr[VGA_CRTC_MODE] >> 6) & 1);
+ addr = (addr & ~(1 << shift)) | ((y1 & 1) << shift);
+ }
+ if (!(s->cr[VGA_CRTC_MODE] & 2)) {
+ addr = (addr & ~0x8000) | ((y1 & 2) << 14);
+ }
+ page0 = addr & s->vbe_size_mask;
+ page1 = (addr + bwidth - 1) & s->vbe_size_mask;
+ if (full_update) {
+ update = 1;
+ } else if (page1 < page0) {
+ /* scanline wraps from end of video memory to the start */
+ assert(force_shadow);
+ update = memory_region_snapshot_get_dirty(&s->vram, snap,
+ page0, s->vbe_size - page0);
+ update |= memory_region_snapshot_get_dirty(&s->vram, snap,
+ 0, page1);
+ } else {
+ update = memory_region_snapshot_get_dirty(&s->vram, snap,
+ page0, page1 - page0);
+ }
+ /* explicit invalidation for the hardware cursor (cirrus only) */
+ update |= vga_scanline_invalidated(s, y);
+ if (update) {
+ if (y_start < 0)
+ y_start = y;
+ if (!(is_buffer_shared(surface))) {
+ vga_draw_line(s, d, addr, width);
+ if (s->cursor_draw_line)
+ s->cursor_draw_line(s, d, y);
+ }
+ } else {
+ if (y_start >= 0) {
+ /* flush to display */
+ dpy_gfx_update(s->con, 0, y_start,
+ disp_width, y - y_start);
+ y_start = -1;
+ }
+ }
+ if (!multi_run) {
+ mask = (s->cr[VGA_CRTC_MODE] & 3) ^ 3;
+ if ((y1 & mask) == mask)
+ addr1 += s->line_offset;
+ y1++;
+ multi_run = multi_scan;
+ } else {
+ multi_run--;
+ }
+ /* line compare acts on the displayed lines */
+ if (y == s->line_compare)
+ addr1 = 0;
+ d += linesize;
+ }
+ if (y_start >= 0) {
+ /* flush to display */
+ dpy_gfx_update(s->con, 0, y_start,
+ disp_width, y - y_start);
+ }
+ g_free(snap);
+ memset(s->invalidated_y_table, 0, sizeof(s->invalidated_y_table));
+}
+
+static void vga_draw_blank(VGACommonState *s, int full_update)
+{
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ int i, w;
+ uint8_t *d;
+
+ if (!full_update)
+ return;
+ if (s->last_scr_width <= 0 || s->last_scr_height <= 0)
+ return;
+
+ w = s->last_scr_width * surface_bytes_per_pixel(surface);
+ d = surface_data(surface);
+ for(i = 0; i < s->last_scr_height; i++) {
+ memset(d, 0, w);
+ d += surface_stride(surface);
+ }
+ dpy_gfx_update_full(s->con);
+}
+
+#define GMODE_TEXT 0
+#define GMODE_GRAPH 1
+#define GMODE_BLANK 2
+
+static void vga_update_display(void *opaque)
+{
+ VGACommonState *s = opaque;
+ DisplaySurface *surface = qemu_console_surface(s->con);
+ int full_update, graphic_mode;
+
+ qemu_flush_coalesced_mmio_buffer();
+
+ if (surface_bits_per_pixel(surface) == 0) {
+ /* nothing to do */
+ } else {
+ full_update = 0;
+ if (!(s->ar_index & 0x20)) {
+ graphic_mode = GMODE_BLANK;
+ } else {
+ graphic_mode = s->gr[VGA_GFX_MISC] & VGA_GR06_GRAPHICS_MODE;
+ }
+ if (graphic_mode != s->graphic_mode) {
+ s->graphic_mode = graphic_mode;
+ s->cursor_blink_time = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL);
+ full_update = 1;
+ }
+ switch(graphic_mode) {
+ case GMODE_TEXT:
+ vga_draw_text(s, full_update);
+ break;
+ case GMODE_GRAPH:
+ vga_draw_graphic(s, full_update);
+ break;
+ case GMODE_BLANK:
+ default:
+ vga_draw_blank(s, full_update);
+ break;
+ }
+ }
+}
+
+/* force a full display refresh */
+static void vga_invalidate_display(void *opaque)
+{
+ VGACommonState *s = opaque;
+
+ s->last_width = -1;
+ s->last_height = -1;
+}
+
+void vga_common_reset(VGACommonState *s)
+{
+ s->sr_index = 0;
+ memset(s->sr, '\0', sizeof(s->sr));
+ memset(s->sr_vbe, '\0', sizeof(s->sr_vbe));
+ s->gr_index = 0;
+ memset(s->gr, '\0', sizeof(s->gr));
+ s->ar_index = 0;
+ memset(s->ar, '\0', sizeof(s->ar));
+ s->ar_flip_flop = 0;
+ s->cr_index = 0;
+ memset(s->cr, '\0', sizeof(s->cr));
+ s->msr = 0;
+ s->fcr = 0;
+ s->st00 = 0;
+ s->st01 = 0;
+ s->dac_state = 0;
+ s->dac_sub_index = 0;
+ s->dac_read_index = 0;
+ s->dac_write_index = 0;
+ memset(s->dac_cache, '\0', sizeof(s->dac_cache));
+ s->dac_8bit = 0;
+ memset(s->palette, '\0', sizeof(s->palette));
+ s->bank_offset = 0;
+ s->vbe_index = 0;
+ memset(s->vbe_regs, '\0', sizeof(s->vbe_regs));
+ s->vbe_regs[VBE_DISPI_INDEX_ID] = VBE_DISPI_ID5;
+ s->vbe_start_addr = 0;
+ s->vbe_line_offset = 0;
+ s->vbe_bank_mask = (s->vram_size >> 16) - 1;
+ memset(s->font_offsets, '\0', sizeof(s->font_offsets));
+ s->graphic_mode = -1; /* force full update */
+ s->shift_control = 0;
+ s->double_scan = 0;
+ s->line_offset = 0;
+ s->line_compare = 0;
+ s->start_addr = 0;
+ s->plane_updated = 0;
+ s->last_cw = 0;
+ s->last_ch = 0;
+ s->last_width = 0;
+ s->last_height = 0;
+ s->last_scr_width = 0;
+ s->last_scr_height = 0;
+ s->cursor_start = 0;
+ s->cursor_end = 0;
+ s->cursor_offset = 0;
+ s->big_endian_fb = s->default_endian_fb;
+ memset(s->invalidated_y_table, '\0', sizeof(s->invalidated_y_table));
+ memset(s->last_palette, '\0', sizeof(s->last_palette));
+ memset(s->last_ch_attr, '\0', sizeof(s->last_ch_attr));
+ switch (vga_retrace_method) {
+ case VGA_RETRACE_DUMB:
+ break;
+ case VGA_RETRACE_PRECISE:
+ memset(&s->retrace_info, 0, sizeof (s->retrace_info));
+ break;
+ }
+ vga_update_memory_access(s);
+}
+
+static void vga_reset(void *opaque)
+{
+ VGACommonState *s = opaque;
+ vga_common_reset(s);
+}
+
+#define TEXTMODE_X(x) ((x) % width)
+#define TEXTMODE_Y(x) ((x) / width)
+#define VMEM2CHTYPE(v) ((v & 0xff0007ff) | \
+ ((v & 0x00000800) << 10) | ((v & 0x00007000) >> 1))
+/* relay text rendering to the display driver
+ * instead of doing a full vga_update_display() */
+static void vga_update_text(void *opaque, console_ch_t *chardata)
+{
+ VGACommonState *s = opaque;
+ int graphic_mode, i, cursor_offset, cursor_visible;
+ int cw, cheight, width, height, size, c_min, c_max;
+ uint32_t *src;
+ console_ch_t *dst, val;
+ char msg_buffer[80];
+ int full_update = 0;
+
+ qemu_flush_coalesced_mmio_buffer();
+
+ if (!(s->ar_index & 0x20)) {
+ graphic_mode = GMODE_BLANK;
+ } else {
+ graphic_mode = s->gr[VGA_GFX_MISC] & VGA_GR06_GRAPHICS_MODE;
+ }
+ if (graphic_mode != s->graphic_mode) {
+ s->graphic_mode = graphic_mode;
+ full_update = 1;
+ }
+ if (s->last_width == -1) {
+ s->last_width = 0;
+ full_update = 1;
+ }
+
+ switch (graphic_mode) {
+ case GMODE_TEXT:
+ /* TODO: update palette */
+ full_update |= update_basic_params(s);
+
+ /* total width & height */
+ cheight = (s->cr[VGA_CRTC_MAX_SCAN] & 0x1f) + 1;
+ cw = 8;
+ if (!(sr(s, VGA_SEQ_CLOCK_MODE) & VGA_SR01_CHAR_CLK_8DOTS)) {
+ cw = 9;
+ }
+ if (sr(s, VGA_SEQ_CLOCK_MODE) & 0x08) {
+ cw = 16; /* NOTE: no 18 pixel wide */
+ }
+ width = (s->cr[VGA_CRTC_H_DISP] + 1);
+ if (s->cr[VGA_CRTC_V_TOTAL] == 100) {
+ /* ugly hack for CGA 160x100x16 - explain me the logic */
+ height = 100;
+ } else {
+ height = s->cr[VGA_CRTC_V_DISP_END] |
+ ((s->cr[VGA_CRTC_OVERFLOW] & 0x02) << 7) |
+ ((s->cr[VGA_CRTC_OVERFLOW] & 0x40) << 3);
+ height = (height + 1) / cheight;
+ }
+
+ size = (height * width);
+ if (size > CH_ATTR_SIZE) {
+ if (!full_update)
+ return;
+
+ snprintf(msg_buffer, sizeof(msg_buffer), "%i x %i Text mode",
+ width, height);
+ break;
+ }
+
+ if (width != s->last_width || height != s->last_height ||
+ cw != s->last_cw || cheight != s->last_ch) {
+ s->last_scr_width = width * cw;
+ s->last_scr_height = height * cheight;
+ qemu_console_resize(s->con, s->last_scr_width, s->last_scr_height);
+ dpy_text_resize(s->con, width, height);
+ s->last_depth = 0;
+ s->last_width = width;
+ s->last_height = height;
+ s->last_ch = cheight;
+ s->last_cw = cw;
+ full_update = 1;
+ }
+
+ if (full_update) {
+ s->full_update_gfx = 1;
+ }
+ if (s->full_update_text) {
+ s->full_update_text = 0;
+ full_update |= 1;
+ }
+
+ /* Update "hardware" cursor */
+ cursor_offset = ((s->cr[VGA_CRTC_CURSOR_HI] << 8) |
+ s->cr[VGA_CRTC_CURSOR_LO]) - s->start_addr;
+ if (cursor_offset != s->cursor_offset ||
+ s->cr[VGA_CRTC_CURSOR_START] != s->cursor_start ||
+ s->cr[VGA_CRTC_CURSOR_END] != s->cursor_end || full_update) {
+ cursor_visible = !(s->cr[VGA_CRTC_CURSOR_START] & 0x20);
+ if (cursor_visible && cursor_offset < size && cursor_offset >= 0)
+ dpy_text_cursor(s->con,
+ TEXTMODE_X(cursor_offset),
+ TEXTMODE_Y(cursor_offset));
+ else
+ dpy_text_cursor(s->con, -1, -1);
+ s->cursor_offset = cursor_offset;
+ s->cursor_start = s->cr[VGA_CRTC_CURSOR_START];
+ s->cursor_end = s->cr[VGA_CRTC_CURSOR_END];
+ }
+
+ src = (uint32_t *) s->vram_ptr + s->start_addr;
+ dst = chardata;
+
+ if (full_update) {
+ for (i = 0; i < size; src ++, dst ++, i ++)
+ console_write_ch(dst, VMEM2CHTYPE(le32_to_cpu(*src)));
+
+ dpy_text_update(s->con, 0, 0, width, height);
+ } else {
+ c_max = 0;
+
+ for (i = 0; i < size; src ++, dst ++, i ++) {
+ console_write_ch(&val, VMEM2CHTYPE(le32_to_cpu(*src)));
+ if (*dst != val) {
+ *dst = val;
+ c_max = i;
+ break;
+ }
+ }
+ c_min = i;
+ for (; i < size; src ++, dst ++, i ++) {
+ console_write_ch(&val, VMEM2CHTYPE(le32_to_cpu(*src)));
+ if (*dst != val) {
+ *dst = val;
+ c_max = i;
+ }
+ }
+
+ if (c_min <= c_max) {
+ i = TEXTMODE_Y(c_min);
+ dpy_text_update(s->con, 0, i, width, TEXTMODE_Y(c_max) - i + 1);
+ }
+ }
+
+ return;
+ case GMODE_GRAPH:
+ if (!full_update)
+ return;
+
+ s->get_resolution(s, &width, &height);
+ snprintf(msg_buffer, sizeof(msg_buffer), "%i x %i Graphic mode",
+ width, height);
+ break;
+ case GMODE_BLANK:
+ default:
+ if (!full_update)
+ return;
+
+ snprintf(msg_buffer, sizeof(msg_buffer), "VGA Blank mode");
+ break;
+ }
+
+ /* Display a message */
+ s->last_width = 60;
+ s->last_height = height = 3;
+ dpy_text_cursor(s->con, -1, -1);
+ dpy_text_resize(s->con, s->last_width, height);
+
+ for (dst = chardata, i = 0; i < s->last_width * height; i ++)
+ console_write_ch(dst ++, ' ');
+
+ size = strlen(msg_buffer);
+ width = (s->last_width - size) / 2;
+ dst = chardata + s->last_width + width;
+ for (i = 0; i < size; i ++)
+ console_write_ch(dst ++, ATTR2CHTYPE(msg_buffer[i], QEMU_COLOR_BLUE,
+ QEMU_COLOR_BLACK, 1));
+
+ dpy_text_update(s->con, 0, 0, s->last_width, height);
+}
+
+static uint64_t vga_mem_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ VGACommonState *s = opaque;
+
+ return vga_mem_readb(s, addr);
+}
+
+static void vga_mem_write(void *opaque, hwaddr addr,
+ uint64_t data, unsigned size)
+{
+ VGACommonState *s = opaque;
+
+ vga_mem_writeb(s, addr, data);
+}
+
+const MemoryRegionOps vga_mem_ops = {
+ .read = vga_mem_read,
+ .write = vga_mem_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static int vga_common_post_load(void *opaque, int version_id)
+{
+ VGACommonState *s = opaque;
+
+ /* force refresh */
+ s->graphic_mode = -1;
+ vbe_update_vgaregs(s);
+ vga_update_memory_access(s);
+ return 0;
+}
+
+static bool vga_endian_state_needed(void *opaque)
+{
+ VGACommonState *s = opaque;
+
+ /*
+ * Only send the endian state if it's different from the
+ * default one, thus ensuring backward compatibility for
+ * migration of the common case
+ */
+ return s->default_endian_fb != s->big_endian_fb;
+}
+
+static const VMStateDescription vmstate_vga_endian = {
+ .name = "vga.endian",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = vga_endian_state_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_BOOL(big_endian_fb, VGACommonState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+const VMStateDescription vmstate_vga_common = {
+ .name = "vga",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .post_load = vga_common_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(latch, VGACommonState),
+ VMSTATE_UINT8(sr_index, VGACommonState),
+ VMSTATE_PARTIAL_BUFFER(sr, VGACommonState, 8),
+ VMSTATE_UINT8(gr_index, VGACommonState),
+ VMSTATE_PARTIAL_BUFFER(gr, VGACommonState, 16),
+ VMSTATE_UINT8(ar_index, VGACommonState),
+ VMSTATE_BUFFER(ar, VGACommonState),
+ VMSTATE_INT32(ar_flip_flop, VGACommonState),
+ VMSTATE_UINT8(cr_index, VGACommonState),
+ VMSTATE_BUFFER(cr, VGACommonState),
+ VMSTATE_UINT8(msr, VGACommonState),
+ VMSTATE_UINT8(fcr, VGACommonState),
+ VMSTATE_UINT8(st00, VGACommonState),
+ VMSTATE_UINT8(st01, VGACommonState),
+
+ VMSTATE_UINT8(dac_state, VGACommonState),
+ VMSTATE_UINT8(dac_sub_index, VGACommonState),
+ VMSTATE_UINT8(dac_read_index, VGACommonState),
+ VMSTATE_UINT8(dac_write_index, VGACommonState),
+ VMSTATE_BUFFER(dac_cache, VGACommonState),
+ VMSTATE_BUFFER(palette, VGACommonState),
+
+ VMSTATE_INT32(bank_offset, VGACommonState),
+ VMSTATE_UINT8_EQUAL(is_vbe_vmstate, VGACommonState, NULL),
+ VMSTATE_UINT16(vbe_index, VGACommonState),
+ VMSTATE_UINT16_ARRAY(vbe_regs, VGACommonState, VBE_DISPI_INDEX_NB),
+ VMSTATE_UINT32(vbe_start_addr, VGACommonState),
+ VMSTATE_UINT32(vbe_line_offset, VGACommonState),
+ VMSTATE_UINT32(vbe_bank_mask, VGACommonState),
+ VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription*[]) {
+ &vmstate_vga_endian,
+ NULL
+ }
+};
+
+static const GraphicHwOps vga_ops = {
+ .invalidate = vga_invalidate_display,
+ .gfx_update = vga_update_display,
+ .text_update = vga_update_text,
+};
+
+static inline uint32_t uint_clamp(uint32_t val, uint32_t vmin, uint32_t vmax)
+{
+ if (val < vmin) {
+ return vmin;
+ }
+ if (val > vmax) {
+ return vmax;
+ }
+ return val;
+}
+
+void vga_common_init(VGACommonState *s, Object *obj)
+{
+ int i, j, v, b;
+
+ for(i = 0;i < 256; i++) {
+ v = 0;
+ for(j = 0; j < 8; j++) {
+ v |= ((i >> j) & 1) << (j * 4);
+ }
+ expand4[i] = v;
+
+ v = 0;
+ for(j = 0; j < 4; j++) {
+ v |= ((i >> (2 * j)) & 3) << (j * 4);
+ }
+ expand2[i] = v;
+ }
+ for(i = 0; i < 16; i++) {
+ v = 0;
+ for(j = 0; j < 4; j++) {
+ b = ((i >> j) & 1);
+ v |= b << (2 * j);
+ v |= b << (2 * j + 1);
+ }
+ expand4to8[i] = v;
+ }
+
+ s->vram_size_mb = uint_clamp(s->vram_size_mb, 1, 512);
+ s->vram_size_mb = pow2ceil(s->vram_size_mb);
+ s->vram_size = s->vram_size_mb * MiB;
+
+ if (!s->vbe_size) {
+ s->vbe_size = s->vram_size;
+ }
+ s->vbe_size_mask = s->vbe_size - 1;
+
+ s->is_vbe_vmstate = 1;
+ memory_region_init_ram_nomigrate(&s->vram, obj, "vga.vram", s->vram_size,
+ &error_fatal);
+ vmstate_register_ram(&s->vram, s->global_vmstate ? NULL : DEVICE(obj));
+ xen_register_framebuffer(&s->vram);
+ s->vram_ptr = memory_region_get_ram_ptr(&s->vram);
+ s->get_bpp = vga_get_bpp;
+ s->get_offsets = vga_get_offsets;
+ s->get_resolution = vga_get_resolution;
+ s->hw_ops = &vga_ops;
+ switch (vga_retrace_method) {
+ case VGA_RETRACE_DUMB:
+ s->retrace = vga_dumb_retrace;
+ s->update_retrace_info = vga_dumb_update_retrace_info;
+ break;
+
+ case VGA_RETRACE_PRECISE:
+ s->retrace = vga_precise_retrace;
+ s->update_retrace_info = vga_precise_update_retrace_info;
+ break;
+ }
+
+ /*
+ * Set default fb endian based on target, could probably be turned
+ * into a device attribute set by the machine/platform to remove
+ * all target endian dependencies from this file.
+ */
+#ifdef TARGET_WORDS_BIGENDIAN
+ s->default_endian_fb = true;
+#else
+ s->default_endian_fb = false;
+#endif
+ vga_dirty_log_start(s);
+}
+
+static const MemoryRegionPortio vga_portio_list[] = {
+ { 0x04, 2, 1, .read = vga_ioport_read, .write = vga_ioport_write }, /* 3b4 */
+ { 0x0a, 1, 1, .read = vga_ioport_read, .write = vga_ioport_write }, /* 3ba */
+ { 0x10, 16, 1, .read = vga_ioport_read, .write = vga_ioport_write }, /* 3c0 */
+ { 0x24, 2, 1, .read = vga_ioport_read, .write = vga_ioport_write }, /* 3d4 */
+ { 0x2a, 1, 1, .read = vga_ioport_read, .write = vga_ioport_write }, /* 3da */
+ PORTIO_END_OF_LIST(),
+};
+
+static const MemoryRegionPortio vbe_portio_list[] = {
+ { 0, 1, 2, .read = vbe_ioport_read_index, .write = vbe_ioport_write_index },
+# ifdef TARGET_I386
+ { 1, 1, 2, .read = vbe_ioport_read_data, .write = vbe_ioport_write_data },
+# endif
+ { 2, 1, 2, .read = vbe_ioport_read_data, .write = vbe_ioport_write_data },
+ PORTIO_END_OF_LIST(),
+};
+
+/* Used by both ISA and PCI */
+MemoryRegion *vga_init_io(VGACommonState *s, Object *obj,
+ const MemoryRegionPortio **vga_ports,
+ const MemoryRegionPortio **vbe_ports)
+{
+ MemoryRegion *vga_mem;
+
+ *vga_ports = vga_portio_list;
+ *vbe_ports = vbe_portio_list;
+
+ vga_mem = g_malloc(sizeof(*vga_mem));
+ memory_region_init_io(vga_mem, obj, &vga_mem_ops, s,
+ "vga-lowmem", 0x20000);
+ memory_region_set_flush_coalesced(vga_mem);
+
+ return vga_mem;
+}
+
+void vga_init(VGACommonState *s, Object *obj, MemoryRegion *address_space,
+ MemoryRegion *address_space_io, bool init_vga_ports)
+{
+ MemoryRegion *vga_io_memory;
+ const MemoryRegionPortio *vga_ports, *vbe_ports;
+
+ qemu_register_reset(vga_reset, s);
+
+ s->bank_offset = 0;
+
+ s->legacy_address_space = address_space;
+
+ vga_io_memory = vga_init_io(s, obj, &vga_ports, &vbe_ports);
+ memory_region_add_subregion_overlap(address_space,
+ 0x000a0000,
+ vga_io_memory,
+ 1);
+ memory_region_set_coalescing(vga_io_memory);
+ if (init_vga_ports) {
+ portio_list_init(&s->vga_port_list, obj, vga_ports, s, "vga");
+ portio_list_set_flush_coalesced(&s->vga_port_list);
+ portio_list_add(&s->vga_port_list, address_space_io, 0x3b0);
+ }
+ if (vbe_ports) {
+ portio_list_init(&s->vbe_port_list, obj, vbe_ports, s, "vbe");
+ portio_list_add(&s->vbe_port_list, address_space_io, 0x1ce);
+ }
+}
diff --git a/hw/display/vga_int.h b/hw/display/vga_int.h
new file mode 100644
index 000000000..847e784ca
--- /dev/null
+++ b/hw/display/vga_int.h
@@ -0,0 +1,198 @@
+/*
+ * QEMU internal VGA defines.
+ *
+ * Copyright (c) 2003-2004 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifndef HW_VGA_INT_H
+#define HW_VGA_INT_H
+
+#include "exec/ioport.h"
+#include "exec/memory.h"
+#include "ui/console.h"
+
+#include "hw/display/bochs-vbe.h"
+
+#define ST01_V_RETRACE 0x08
+#define ST01_DISP_ENABLE 0x01
+
+#define CH_ATTR_SIZE (160 * 100)
+#define VGA_MAX_HEIGHT 2048
+
+struct vga_precise_retrace {
+ int64_t ticks_per_char;
+ int64_t total_chars;
+ int htotal;
+ int hstart;
+ int hend;
+ int vstart;
+ int vend;
+ int freq;
+};
+
+union vga_retrace {
+ struct vga_precise_retrace precise;
+};
+
+struct VGACommonState;
+typedef uint8_t (* vga_retrace_fn)(struct VGACommonState *s);
+typedef void (* vga_update_retrace_info_fn)(struct VGACommonState *s);
+
+typedef struct VGACommonState {
+ MemoryRegion *legacy_address_space;
+ uint8_t *vram_ptr;
+ MemoryRegion vram;
+ uint32_t vram_size;
+ uint32_t vram_size_mb; /* property */
+ uint32_t vbe_size;
+ uint32_t vbe_size_mask;
+ uint32_t latch;
+ bool has_chain4_alias;
+ MemoryRegion chain4_alias;
+ uint8_t sr_index;
+ uint8_t sr[256];
+ uint8_t sr_vbe[256];
+ uint8_t gr_index;
+ uint8_t gr[256];
+ uint8_t ar_index;
+ uint8_t ar[21];
+ int ar_flip_flop;
+ uint8_t cr_index;
+ uint8_t cr[256]; /* CRT registers */
+ uint8_t msr; /* Misc Output Register */
+ uint8_t fcr; /* Feature Control Register */
+ uint8_t st00; /* status 0 */
+ uint8_t st01; /* status 1 */
+ uint8_t dac_state;
+ uint8_t dac_sub_index;
+ uint8_t dac_read_index;
+ uint8_t dac_write_index;
+ uint8_t dac_cache[3]; /* used when writing */
+ int dac_8bit;
+ uint8_t palette[768];
+ int32_t bank_offset;
+ int (*get_bpp)(struct VGACommonState *s);
+ void (*get_offsets)(struct VGACommonState *s,
+ uint32_t *pline_offset,
+ uint32_t *pstart_addr,
+ uint32_t *pline_compare);
+ void (*get_resolution)(struct VGACommonState *s,
+ int *pwidth,
+ int *pheight);
+ PortioList vga_port_list;
+ PortioList vbe_port_list;
+ /* bochs vbe state */
+ uint16_t vbe_index;
+ uint16_t vbe_regs[VBE_DISPI_INDEX_NB];
+ uint32_t vbe_start_addr;
+ uint32_t vbe_line_offset;
+ uint32_t vbe_bank_mask;
+ /* display refresh support */
+ QemuConsole *con;
+ uint32_t font_offsets[2];
+ int graphic_mode;
+ uint8_t shift_control;
+ uint8_t double_scan;
+ uint32_t line_offset;
+ uint32_t line_compare;
+ uint32_t start_addr;
+ uint32_t plane_updated;
+ uint32_t last_line_offset;
+ uint8_t last_cw, last_ch;
+ uint32_t last_width, last_height; /* in chars or pixels */
+ uint32_t last_scr_width, last_scr_height; /* in pixels */
+ uint32_t last_depth; /* in bits */
+ bool last_byteswap;
+ bool force_shadow;
+ uint8_t cursor_start, cursor_end;
+ bool cursor_visible_phase;
+ int64_t cursor_blink_time;
+ uint32_t cursor_offset;
+ const GraphicHwOps *hw_ops;
+ bool full_update_text;
+ bool full_update_gfx;
+ bool big_endian_fb;
+ bool default_endian_fb;
+ bool global_vmstate;
+ /* hardware mouse cursor support */
+ uint32_t invalidated_y_table[VGA_MAX_HEIGHT / 32];
+ uint32_t hw_cursor_x;
+ uint32_t hw_cursor_y;
+ void (*cursor_invalidate)(struct VGACommonState *s);
+ void (*cursor_draw_line)(struct VGACommonState *s, uint8_t *d, int y);
+ /* tell for each page if it has been updated since the last time */
+ uint32_t last_palette[256];
+ uint32_t last_ch_attr[CH_ATTR_SIZE]; /* XXX: make it dynamic */
+ /* retrace */
+ vga_retrace_fn retrace;
+ vga_update_retrace_info_fn update_retrace_info;
+ union vga_retrace retrace_info;
+ uint8_t is_vbe_vmstate;
+} VGACommonState;
+
+static inline int c6_to_8(int v)
+{
+ int b;
+ v &= 0x3f;
+ b = v & 1;
+ return (v << 2) | (b << 1) | b;
+}
+
+void vga_common_init(VGACommonState *s, Object *obj);
+void vga_init(VGACommonState *s, Object *obj, MemoryRegion *address_space,
+ MemoryRegion *address_space_io, bool init_vga_ports);
+MemoryRegion *vga_init_io(VGACommonState *s, Object *obj,
+ const MemoryRegionPortio **vga_ports,
+ const MemoryRegionPortio **vbe_ports);
+void vga_common_reset(VGACommonState *s);
+
+void vga_dirty_log_start(VGACommonState *s);
+void vga_dirty_log_stop(VGACommonState *s);
+
+extern const VMStateDescription vmstate_vga_common;
+uint32_t vga_ioport_read(void *opaque, uint32_t addr);
+void vga_ioport_write(void *opaque, uint32_t addr, uint32_t val);
+uint32_t vga_mem_readb(VGACommonState *s, hwaddr addr);
+void vga_mem_writeb(VGACommonState *s, hwaddr addr, uint32_t val);
+void vga_invalidate_scanlines(VGACommonState *s, int y1, int y2);
+
+int vga_ioport_invalid(VGACommonState *s, uint32_t addr);
+
+uint32_t vbe_ioport_read_data(void *opaque, uint32_t addr);
+void vbe_ioport_write_index(void *opaque, uint32_t addr, uint32_t val);
+void vbe_ioport_write_data(void *opaque, uint32_t addr, uint32_t val);
+
+extern const uint8_t sr_mask[8];
+extern const uint8_t gr_mask[16];
+
+#define VGABIOS_FILENAME "vgabios.bin"
+#define VGABIOS_CIRRUS_FILENAME "vgabios-cirrus.bin"
+
+extern const MemoryRegionOps vga_mem_ops;
+
+/* vga-pci.c */
+void pci_std_vga_mmio_region_init(VGACommonState *s,
+ Object *owner,
+ MemoryRegion *parent,
+ MemoryRegion *subs,
+ bool qext, bool edid);
+
+#endif
diff --git a/hw/display/vga_regs.h b/hw/display/vga_regs.h
new file mode 100644
index 000000000..30a98b873
--- /dev/null
+++ b/hw/display/vga_regs.h
@@ -0,0 +1,159 @@
+/*
+ * linux/include/video/vga.h -- standard VGA chipset interaction
+ *
+ * Copyright 1999 Jeff Garzik <jgarzik@pobox.com>
+ *
+ * Copyright history from vga16fb.c:
+ * Copyright 1999 Ben Pfaff and Petr Vandrovec
+ * Based on VGA info at http://www.osdever.net/FreeVGA/home.htm
+ * Based on VESA framebuffer (c) 1998 Gerd Knorr
+ *
+ * This file is subject to the terms and conditions of the GNU General
+ * Public License. See the file COPYING in the main directory of this
+ * archive for more details.
+ *
+ */
+
+#ifndef HW_VGA_REGS_H
+#define HW_VGA_REGS_H
+
+/* Some of the code below is taken from SVGAlib. The original,
+ unmodified copyright notice for that code is below. */
+/* VGAlib version 1.2 - (c) 1993 Tommy Frandsen */
+/* */
+/* This library is free software; you can redistribute it and/or */
+/* modify it without any restrictions. This library is distributed */
+/* in the hope that it will be useful, but without any warranty. */
+
+/* Multi-chipset support Copyright 1993 Harm Hanemaayer */
+/* partially copyrighted (C) 1993 by Hartmut Schirmer */
+
+/* VGA data register ports */
+#define VGA_CRT_DC 0x3D5 /* CRT Controller Data Register - color emulation */
+#define VGA_CRT_DM 0x3B5 /* CRT Controller Data Register - mono emulation */
+#define VGA_ATT_R 0x3C1 /* Attribute Controller Data Read Register */
+#define VGA_ATT_W 0x3C0 /* Attribute Controller Data Write Register */
+#define VGA_GFX_D 0x3CF /* Graphics Controller Data Register */
+#define VGA_SEQ_D 0x3C5 /* Sequencer Data Register */
+#define VGA_MIS_R 0x3CC /* Misc Output Read Register */
+#define VGA_MIS_W 0x3C2 /* Misc Output Write Register */
+#define VGA_FTC_R 0x3CA /* Feature Control Read Register */
+#define VGA_IS1_RC 0x3DA /* Input Status Register 1 - color emulation */
+#define VGA_IS1_RM 0x3BA /* Input Status Register 1 - mono emulation */
+#define VGA_PEL_D 0x3C9 /* PEL Data Register */
+#define VGA_PEL_MSK 0x3C6 /* PEL mask register */
+
+/* EGA-specific registers */
+#define EGA_GFX_E0 0x3CC /* Graphics enable processor 0 */
+#define EGA_GFX_E1 0x3CA /* Graphics enable processor 1 */
+
+/* VGA index register ports */
+#define VGA_CRT_IC 0x3D4 /* CRT Controller Index - color emulation */
+#define VGA_CRT_IM 0x3B4 /* CRT Controller Index - mono emulation */
+#define VGA_ATT_IW 0x3C0 /* Attribute Controller Index & Data Write Register */
+#define VGA_GFX_I 0x3CE /* Graphics Controller Index */
+#define VGA_SEQ_I 0x3C4 /* Sequencer Index */
+#define VGA_PEL_IW 0x3C8 /* PEL Write Index */
+#define VGA_PEL_IR 0x3C7 /* PEL Read Index */
+
+/* standard VGA indexes max counts */
+#define VGA_CRT_C 0x19 /* Number of CRT Controller Registers */
+#define VGA_ATT_C 0x15 /* Number of Attribute Controller Registers */
+#define VGA_GFX_C 0x09 /* Number of Graphics Controller Registers */
+#define VGA_SEQ_C 0x05 /* Number of Sequencer Registers */
+#define VGA_MIS_C 0x01 /* Number of Misc Output Register */
+
+/* VGA misc register bit masks */
+#define VGA_MIS_COLOR 0x01
+#define VGA_MIS_ENB_MEM_ACCESS 0x02
+#define VGA_MIS_DCLK_28322_720 0x04
+#define VGA_MIS_ENB_PLL_LOAD (0x04 | 0x08)
+#define VGA_MIS_SEL_HIGH_PAGE 0x20
+
+/* VGA CRT controller register indices */
+#define VGA_CRTC_H_TOTAL 0
+#define VGA_CRTC_H_DISP 1
+#define VGA_CRTC_H_BLANK_START 2
+#define VGA_CRTC_H_BLANK_END 3
+#define VGA_CRTC_H_SYNC_START 4
+#define VGA_CRTC_H_SYNC_END 5
+#define VGA_CRTC_V_TOTAL 6
+#define VGA_CRTC_OVERFLOW 7
+#define VGA_CRTC_PRESET_ROW 8
+#define VGA_CRTC_MAX_SCAN 9
+#define VGA_CRTC_CURSOR_START 0x0A
+#define VGA_CRTC_CURSOR_END 0x0B
+#define VGA_CRTC_START_HI 0x0C
+#define VGA_CRTC_START_LO 0x0D
+#define VGA_CRTC_CURSOR_HI 0x0E
+#define VGA_CRTC_CURSOR_LO 0x0F
+#define VGA_CRTC_V_SYNC_START 0x10
+#define VGA_CRTC_V_SYNC_END 0x11
+#define VGA_CRTC_V_DISP_END 0x12
+#define VGA_CRTC_OFFSET 0x13
+#define VGA_CRTC_UNDERLINE 0x14
+#define VGA_CRTC_V_BLANK_START 0x15
+#define VGA_CRTC_V_BLANK_END 0x16
+#define VGA_CRTC_MODE 0x17
+#define VGA_CRTC_LINE_COMPARE 0x18
+#define VGA_CRTC_REGS VGA_CRT_C
+
+/* VGA CRT controller bit masks */
+#define VGA_CR11_LOCK_CR0_CR7 0x80 /* lock writes to CR0 - CR7 */
+#define VGA_CR17_H_V_SIGNALS_ENABLED 0x80
+
+/* VGA attribute controller register indices */
+#define VGA_ATC_PALETTE0 0x00
+#define VGA_ATC_PALETTE1 0x01
+#define VGA_ATC_PALETTE2 0x02
+#define VGA_ATC_PALETTE3 0x03
+#define VGA_ATC_PALETTE4 0x04
+#define VGA_ATC_PALETTE5 0x05
+#define VGA_ATC_PALETTE6 0x06
+#define VGA_ATC_PALETTE7 0x07
+#define VGA_ATC_PALETTE8 0x08
+#define VGA_ATC_PALETTE9 0x09
+#define VGA_ATC_PALETTEA 0x0A
+#define VGA_ATC_PALETTEB 0x0B
+#define VGA_ATC_PALETTEC 0x0C
+#define VGA_ATC_PALETTED 0x0D
+#define VGA_ATC_PALETTEE 0x0E
+#define VGA_ATC_PALETTEF 0x0F
+#define VGA_ATC_MODE 0x10
+#define VGA_ATC_OVERSCAN 0x11
+#define VGA_ATC_PLANE_ENABLE 0x12
+#define VGA_ATC_PEL 0x13
+#define VGA_ATC_COLOR_PAGE 0x14
+
+#define VGA_AR_ENABLE_DISPLAY 0x20
+
+/* VGA sequencer register indices */
+#define VGA_SEQ_RESET 0x00
+#define VGA_SEQ_CLOCK_MODE 0x01
+#define VGA_SEQ_PLANE_WRITE 0x02
+#define VGA_SEQ_CHARACTER_MAP 0x03
+#define VGA_SEQ_MEMORY_MODE 0x04
+
+/* VGA sequencer register bit masks */
+#define VGA_SR01_CHAR_CLK_8DOTS 0x01 /* bit 0: character clocks 8 dots wide are generated */
+#define VGA_SR01_SCREEN_OFF 0x20 /* bit 5: Screen is off */
+#define VGA_SR02_ALL_PLANES 0x0F /* bits 3-0: enable access to all planes */
+#define VGA_SR04_EXT_MEM 0x02 /* bit 1: allows complete mem access to 256K */
+#define VGA_SR04_SEQ_MODE 0x04 /* bit 2: directs system to use a sequential addressing mode */
+#define VGA_SR04_CHN_4M 0x08 /* bit 3: selects modulo 4 addressing for CPU access to display memory */
+
+/* VGA graphics controller register indices */
+#define VGA_GFX_SR_VALUE 0x00
+#define VGA_GFX_SR_ENABLE 0x01
+#define VGA_GFX_COMPARE_VALUE 0x02
+#define VGA_GFX_DATA_ROTATE 0x03
+#define VGA_GFX_PLANE_READ 0x04
+#define VGA_GFX_MODE 0x05
+#define VGA_GFX_MISC 0x06
+#define VGA_GFX_COMPARE_MASK 0x07
+#define VGA_GFX_BIT_MASK 0x08
+
+/* VGA graphics controller bit masks */
+#define VGA_GR06_GRAPHICS_MODE 0x01
+
+#endif /* HW_VGA_REGS_H */
diff --git a/hw/display/vhost-user-gpu-pci.c b/hw/display/vhost-user-gpu-pci.c
new file mode 100644
index 000000000..daefcf710
--- /dev/null
+++ b/hw/display/vhost-user-gpu-pci.c
@@ -0,0 +1,53 @@
+/*
+ * vhost-user GPU PCI device
+ *
+ * Copyright Red Hat, Inc. 2018
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "hw/virtio/virtio-gpu-pci.h"
+#include "qom/object.h"
+
+#define TYPE_VHOST_USER_GPU_PCI "vhost-user-gpu-pci"
+typedef struct VhostUserGPUPCI VhostUserGPUPCI;
+DECLARE_INSTANCE_CHECKER(VhostUserGPUPCI, VHOST_USER_GPU_PCI,
+ TYPE_VHOST_USER_GPU_PCI)
+
+struct VhostUserGPUPCI {
+ VirtIOGPUPCIBase parent_obj;
+
+ VhostUserGPU vdev;
+};
+
+static void vhost_user_gpu_pci_initfn(Object *obj)
+{
+ VhostUserGPUPCI *dev = VHOST_USER_GPU_PCI(obj);
+
+ virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev),
+ TYPE_VHOST_USER_GPU);
+
+ VIRTIO_GPU_PCI_BASE(obj)->vgpu = VIRTIO_GPU_BASE(&dev->vdev);
+
+ object_property_add_alias(obj, "chardev",
+ OBJECT(&dev->vdev), "chardev");
+}
+
+static const VirtioPCIDeviceTypeInfo vhost_user_gpu_pci_info = {
+ .generic_name = TYPE_VHOST_USER_GPU_PCI,
+ .parent = TYPE_VIRTIO_GPU_PCI_BASE,
+ .instance_size = sizeof(VhostUserGPUPCI),
+ .instance_init = vhost_user_gpu_pci_initfn,
+};
+module_obj(TYPE_VHOST_USER_GPU_PCI);
+
+static void vhost_user_gpu_pci_register_types(void)
+{
+ virtio_pci_types_register(&vhost_user_gpu_pci_info);
+}
+
+type_init(vhost_user_gpu_pci_register_types)
diff --git a/hw/display/vhost-user-gpu.c b/hw/display/vhost-user-gpu.c
new file mode 100644
index 000000000..49df56cd1
--- /dev/null
+++ b/hw/display/vhost-user-gpu.c
@@ -0,0 +1,608 @@
+/*
+ * vhost-user GPU Device
+ *
+ * Copyright Red Hat, Inc. 2018
+ *
+ * Authors:
+ * Marc-André Lureau <marcandre.lureau@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/qdev-properties.h"
+#include "hw/virtio/virtio-gpu.h"
+#include "chardev/char-fe.h"
+#include "qapi/error.h"
+#include "migration/blocker.h"
+
+typedef enum VhostUserGpuRequest {
+ VHOST_USER_GPU_NONE = 0,
+ VHOST_USER_GPU_GET_PROTOCOL_FEATURES,
+ VHOST_USER_GPU_SET_PROTOCOL_FEATURES,
+ VHOST_USER_GPU_GET_DISPLAY_INFO,
+ VHOST_USER_GPU_CURSOR_POS,
+ VHOST_USER_GPU_CURSOR_POS_HIDE,
+ VHOST_USER_GPU_CURSOR_UPDATE,
+ VHOST_USER_GPU_SCANOUT,
+ VHOST_USER_GPU_UPDATE,
+ VHOST_USER_GPU_DMABUF_SCANOUT,
+ VHOST_USER_GPU_DMABUF_UPDATE,
+} VhostUserGpuRequest;
+
+typedef struct VhostUserGpuDisplayInfoReply {
+ struct virtio_gpu_resp_display_info info;
+} VhostUserGpuDisplayInfoReply;
+
+typedef struct VhostUserGpuCursorPos {
+ uint32_t scanout_id;
+ uint32_t x;
+ uint32_t y;
+} QEMU_PACKED VhostUserGpuCursorPos;
+
+typedef struct VhostUserGpuCursorUpdate {
+ VhostUserGpuCursorPos pos;
+ uint32_t hot_x;
+ uint32_t hot_y;
+ uint32_t data[64 * 64];
+} QEMU_PACKED VhostUserGpuCursorUpdate;
+
+typedef struct VhostUserGpuScanout {
+ uint32_t scanout_id;
+ uint32_t width;
+ uint32_t height;
+} QEMU_PACKED VhostUserGpuScanout;
+
+typedef struct VhostUserGpuUpdate {
+ uint32_t scanout_id;
+ uint32_t x;
+ uint32_t y;
+ uint32_t width;
+ uint32_t height;
+ uint8_t data[];
+} QEMU_PACKED VhostUserGpuUpdate;
+
+typedef struct VhostUserGpuDMABUFScanout {
+ uint32_t scanout_id;
+ uint32_t x;
+ uint32_t y;
+ uint32_t width;
+ uint32_t height;
+ uint32_t fd_width;
+ uint32_t fd_height;
+ uint32_t fd_stride;
+ uint32_t fd_flags;
+ int fd_drm_fourcc;
+} QEMU_PACKED VhostUserGpuDMABUFScanout;
+
+typedef struct VhostUserGpuMsg {
+ uint32_t request; /* VhostUserGpuRequest */
+ uint32_t flags;
+ uint32_t size; /* the following payload size */
+ union {
+ VhostUserGpuCursorPos cursor_pos;
+ VhostUserGpuCursorUpdate cursor_update;
+ VhostUserGpuScanout scanout;
+ VhostUserGpuUpdate update;
+ VhostUserGpuDMABUFScanout dmabuf_scanout;
+ struct virtio_gpu_resp_display_info display_info;
+ uint64_t u64;
+ } payload;
+} QEMU_PACKED VhostUserGpuMsg;
+
+static VhostUserGpuMsg m __attribute__ ((unused));
+#define VHOST_USER_GPU_HDR_SIZE \
+ (sizeof(m.request) + sizeof(m.size) + sizeof(m.flags))
+
+#define VHOST_USER_GPU_MSG_FLAG_REPLY 0x4
+
+static void vhost_user_gpu_update_blocked(VhostUserGPU *g, bool blocked);
+
+static void
+vhost_user_gpu_handle_cursor(VhostUserGPU *g, VhostUserGpuMsg *msg)
+{
+ VhostUserGpuCursorPos *pos = &msg->payload.cursor_pos;
+ struct virtio_gpu_scanout *s;
+
+ if (pos->scanout_id >= g->parent_obj.conf.max_outputs) {
+ return;
+ }
+ s = &g->parent_obj.scanout[pos->scanout_id];
+
+ if (msg->request == VHOST_USER_GPU_CURSOR_UPDATE) {
+ VhostUserGpuCursorUpdate *up = &msg->payload.cursor_update;
+ if (!s->current_cursor) {
+ s->current_cursor = cursor_alloc(64, 64);
+ }
+
+ s->current_cursor->hot_x = up->hot_x;
+ s->current_cursor->hot_y = up->hot_y;
+
+ memcpy(s->current_cursor->data, up->data,
+ 64 * 64 * sizeof(uint32_t));
+
+ dpy_cursor_define(s->con, s->current_cursor);
+ }
+
+ dpy_mouse_set(s->con, pos->x, pos->y,
+ msg->request != VHOST_USER_GPU_CURSOR_POS_HIDE);
+}
+
+static void
+vhost_user_gpu_send_msg(VhostUserGPU *g, const VhostUserGpuMsg *msg)
+{
+ qemu_chr_fe_write(&g->vhost_chr, (uint8_t *)msg,
+ VHOST_USER_GPU_HDR_SIZE + msg->size);
+}
+
+static void
+vhost_user_gpu_unblock(VhostUserGPU *g)
+{
+ VhostUserGpuMsg msg = {
+ .request = VHOST_USER_GPU_DMABUF_UPDATE,
+ .flags = VHOST_USER_GPU_MSG_FLAG_REPLY,
+ };
+
+ vhost_user_gpu_send_msg(g, &msg);
+}
+
+static void
+vhost_user_gpu_handle_display(VhostUserGPU *g, VhostUserGpuMsg *msg)
+{
+ QemuConsole *con = NULL;
+ struct virtio_gpu_scanout *s;
+
+ switch (msg->request) {
+ case VHOST_USER_GPU_GET_PROTOCOL_FEATURES: {
+ VhostUserGpuMsg reply = {
+ .request = msg->request,
+ .flags = VHOST_USER_GPU_MSG_FLAG_REPLY,
+ .size = sizeof(uint64_t),
+ };
+
+ vhost_user_gpu_send_msg(g, &reply);
+ break;
+ }
+ case VHOST_USER_GPU_SET_PROTOCOL_FEATURES: {
+ break;
+ }
+ case VHOST_USER_GPU_GET_DISPLAY_INFO: {
+ struct virtio_gpu_resp_display_info display_info = { {} };
+ VhostUserGpuMsg reply = {
+ .request = msg->request,
+ .flags = VHOST_USER_GPU_MSG_FLAG_REPLY,
+ .size = sizeof(struct virtio_gpu_resp_display_info),
+ };
+
+ display_info.hdr.type = VIRTIO_GPU_RESP_OK_DISPLAY_INFO;
+ virtio_gpu_base_fill_display_info(VIRTIO_GPU_BASE(g), &display_info);
+ memcpy(&reply.payload.display_info, &display_info,
+ sizeof(display_info));
+ vhost_user_gpu_send_msg(g, &reply);
+ break;
+ }
+ case VHOST_USER_GPU_SCANOUT: {
+ VhostUserGpuScanout *m = &msg->payload.scanout;
+
+ if (m->scanout_id >= g->parent_obj.conf.max_outputs) {
+ return;
+ }
+
+ g->parent_obj.enable = 1;
+ s = &g->parent_obj.scanout[m->scanout_id];
+ con = s->con;
+
+ if (m->width == 0) {
+ dpy_gfx_replace_surface(con, NULL);
+ } else {
+ s->ds = qemu_create_displaysurface(m->width, m->height);
+ /* replace surface on next update */
+ }
+
+ break;
+ }
+ case VHOST_USER_GPU_DMABUF_SCANOUT: {
+ VhostUserGpuDMABUFScanout *m = &msg->payload.dmabuf_scanout;
+ int fd = qemu_chr_fe_get_msgfd(&g->vhost_chr);
+ QemuDmaBuf *dmabuf;
+
+ if (m->scanout_id >= g->parent_obj.conf.max_outputs) {
+ error_report("invalid scanout: %d", m->scanout_id);
+ if (fd >= 0) {
+ close(fd);
+ }
+ break;
+ }
+
+ g->parent_obj.enable = 1;
+ con = g->parent_obj.scanout[m->scanout_id].con;
+ dmabuf = &g->dmabuf[m->scanout_id];
+ if (dmabuf->fd >= 0) {
+ close(dmabuf->fd);
+ dmabuf->fd = -1;
+ }
+ dpy_gl_release_dmabuf(con, dmabuf);
+ if (fd == -1) {
+ dpy_gl_scanout_disable(con);
+ break;
+ }
+ *dmabuf = (QemuDmaBuf) {
+ .fd = fd,
+ .width = m->fd_width,
+ .height = m->fd_height,
+ .stride = m->fd_stride,
+ .fourcc = m->fd_drm_fourcc,
+ .y0_top = m->fd_flags & VIRTIO_GPU_RESOURCE_FLAG_Y_0_TOP,
+ };
+ dpy_gl_scanout_dmabuf(con, dmabuf);
+ break;
+ }
+ case VHOST_USER_GPU_DMABUF_UPDATE: {
+ VhostUserGpuUpdate *m = &msg->payload.update;
+
+ if (m->scanout_id >= g->parent_obj.conf.max_outputs ||
+ !g->parent_obj.scanout[m->scanout_id].con) {
+ error_report("invalid scanout update: %d", m->scanout_id);
+ vhost_user_gpu_unblock(g);
+ break;
+ }
+
+ con = g->parent_obj.scanout[m->scanout_id].con;
+ if (!console_has_gl(con)) {
+ error_report("console doesn't support GL!");
+ vhost_user_gpu_unblock(g);
+ break;
+ }
+ dpy_gl_update(con, m->x, m->y, m->width, m->height);
+ g->backend_blocked = true;
+ break;
+ }
+ case VHOST_USER_GPU_UPDATE: {
+ VhostUserGpuUpdate *m = &msg->payload.update;
+
+ if (m->scanout_id >= g->parent_obj.conf.max_outputs) {
+ break;
+ }
+ s = &g->parent_obj.scanout[m->scanout_id];
+ con = s->con;
+ pixman_image_t *image =
+ pixman_image_create_bits(PIXMAN_x8r8g8b8,
+ m->width,
+ m->height,
+ (uint32_t *)m->data,
+ m->width * 4);
+
+ pixman_image_composite(PIXMAN_OP_SRC,
+ image, NULL, s->ds->image,
+ 0, 0, 0, 0, m->x, m->y, m->width, m->height);
+
+ pixman_image_unref(image);
+ if (qemu_console_surface(con) != s->ds) {
+ dpy_gfx_replace_surface(con, s->ds);
+ } else {
+ dpy_gfx_update(con, m->x, m->y, m->width, m->height);
+ }
+ break;
+ }
+ default:
+ g_warning("unhandled message %d %d", msg->request, msg->size);
+ }
+
+ if (con && qemu_console_is_gl_blocked(con)) {
+ vhost_user_gpu_update_blocked(g, true);
+ }
+}
+
+static void
+vhost_user_gpu_chr_read(void *opaque)
+{
+ VhostUserGPU *g = opaque;
+ VhostUserGpuMsg *msg = NULL;
+ VhostUserGpuRequest request;
+ uint32_t size, flags;
+ int r;
+
+ r = qemu_chr_fe_read_all(&g->vhost_chr,
+ (uint8_t *)&request, sizeof(uint32_t));
+ if (r != sizeof(uint32_t)) {
+ error_report("failed to read msg header: %d, %d", r, errno);
+ goto end;
+ }
+
+ r = qemu_chr_fe_read_all(&g->vhost_chr,
+ (uint8_t *)&flags, sizeof(uint32_t));
+ if (r != sizeof(uint32_t)) {
+ error_report("failed to read msg flags");
+ goto end;
+ }
+
+ r = qemu_chr_fe_read_all(&g->vhost_chr,
+ (uint8_t *)&size, sizeof(uint32_t));
+ if (r != sizeof(uint32_t)) {
+ error_report("failed to read msg size");
+ goto end;
+ }
+
+ msg = g_malloc(VHOST_USER_GPU_HDR_SIZE + size);
+
+ r = qemu_chr_fe_read_all(&g->vhost_chr,
+ (uint8_t *)&msg->payload, size);
+ if (r != size) {
+ error_report("failed to read msg payload %d != %d", r, size);
+ goto end;
+ }
+
+ msg->request = request;
+ msg->flags = size;
+ msg->size = size;
+
+ if (request == VHOST_USER_GPU_CURSOR_UPDATE ||
+ request == VHOST_USER_GPU_CURSOR_POS ||
+ request == VHOST_USER_GPU_CURSOR_POS_HIDE) {
+ vhost_user_gpu_handle_cursor(g, msg);
+ } else {
+ vhost_user_gpu_handle_display(g, msg);
+ }
+
+end:
+ g_free(msg);
+}
+
+static void
+vhost_user_gpu_update_blocked(VhostUserGPU *g, bool blocked)
+{
+ qemu_set_fd_handler(g->vhost_gpu_fd,
+ blocked ? NULL : vhost_user_gpu_chr_read, NULL, g);
+}
+
+static void
+vhost_user_gpu_gl_flushed(VirtIOGPUBase *b)
+{
+ VhostUserGPU *g = VHOST_USER_GPU(b);
+
+ if (g->backend_blocked) {
+ vhost_user_gpu_unblock(VHOST_USER_GPU(g));
+ g->backend_blocked = false;
+ }
+
+ vhost_user_gpu_update_blocked(VHOST_USER_GPU(g), false);
+}
+
+static bool
+vhost_user_gpu_do_set_socket(VhostUserGPU *g, Error **errp)
+{
+ Chardev *chr;
+ int sv[2];
+
+ if (socketpair(PF_UNIX, SOCK_STREAM, 0, sv) == -1) {
+ error_setg_errno(errp, errno, "socketpair() failed");
+ return false;
+ }
+
+ chr = CHARDEV(object_new(TYPE_CHARDEV_SOCKET));
+ if (!chr || qemu_chr_add_client(chr, sv[0]) == -1) {
+ error_setg(errp, "Failed to make socket chardev");
+ goto err;
+ }
+ if (!qemu_chr_fe_init(&g->vhost_chr, chr, errp)) {
+ goto err;
+ }
+ if (vhost_user_gpu_set_socket(&g->vhost->dev, sv[1]) < 0) {
+ error_setg(errp, "Failed to set vhost-user-gpu socket");
+ qemu_chr_fe_deinit(&g->vhost_chr, false);
+ goto err;
+ }
+
+ g->vhost_gpu_fd = sv[0];
+ vhost_user_gpu_update_blocked(g, false);
+ close(sv[1]);
+ return true;
+
+err:
+ close(sv[0]);
+ close(sv[1]);
+ if (chr) {
+ object_unref(OBJECT(chr));
+ }
+ return false;
+}
+
+static void
+vhost_user_gpu_get_config(VirtIODevice *vdev, uint8_t *config_data)
+{
+ VhostUserGPU *g = VHOST_USER_GPU(vdev);
+ VirtIOGPUBase *b = VIRTIO_GPU_BASE(vdev);
+ struct virtio_gpu_config *vgconfig =
+ (struct virtio_gpu_config *)config_data;
+ Error *local_err = NULL;
+ int ret;
+
+ memset(config_data, 0, sizeof(struct virtio_gpu_config));
+
+ ret = vhost_dev_get_config(&g->vhost->dev,
+ config_data, sizeof(struct virtio_gpu_config),
+ &local_err);
+ if (ret) {
+ error_report_err(local_err);
+ return;
+ }
+
+ /* those fields are managed by qemu */
+ vgconfig->num_scanouts = b->virtio_config.num_scanouts;
+ vgconfig->events_read = b->virtio_config.events_read;
+ vgconfig->events_clear = b->virtio_config.events_clear;
+}
+
+static void
+vhost_user_gpu_set_config(VirtIODevice *vdev,
+ const uint8_t *config_data)
+{
+ VhostUserGPU *g = VHOST_USER_GPU(vdev);
+ VirtIOGPUBase *b = VIRTIO_GPU_BASE(vdev);
+ const struct virtio_gpu_config *vgconfig =
+ (const struct virtio_gpu_config *)config_data;
+ int ret;
+
+ if (vgconfig->events_clear) {
+ b->virtio_config.events_read &= ~vgconfig->events_clear;
+ }
+
+ ret = vhost_dev_set_config(&g->vhost->dev, config_data,
+ 0, sizeof(struct virtio_gpu_config),
+ VHOST_SET_CONFIG_TYPE_MASTER);
+ if (ret) {
+ error_report("vhost-user-gpu: set device config space failed");
+ return;
+ }
+}
+
+static void
+vhost_user_gpu_set_status(VirtIODevice *vdev, uint8_t val)
+{
+ VhostUserGPU *g = VHOST_USER_GPU(vdev);
+ Error *err = NULL;
+
+ if (val & VIRTIO_CONFIG_S_DRIVER_OK && vdev->vm_running) {
+ if (!vhost_user_gpu_do_set_socket(g, &err)) {
+ error_report_err(err);
+ return;
+ }
+ vhost_user_backend_start(g->vhost);
+ } else {
+ /* unblock any wait and stop processing */
+ if (g->vhost_gpu_fd != -1) {
+ vhost_user_gpu_update_blocked(g, true);
+ qemu_chr_fe_deinit(&g->vhost_chr, true);
+ g->vhost_gpu_fd = -1;
+ }
+ vhost_user_backend_stop(g->vhost);
+ }
+}
+
+static bool
+vhost_user_gpu_guest_notifier_pending(VirtIODevice *vdev, int idx)
+{
+ VhostUserGPU *g = VHOST_USER_GPU(vdev);
+
+ return vhost_virtqueue_pending(&g->vhost->dev, idx);
+}
+
+static void
+vhost_user_gpu_guest_notifier_mask(VirtIODevice *vdev, int idx, bool mask)
+{
+ VhostUserGPU *g = VHOST_USER_GPU(vdev);
+
+ vhost_virtqueue_mask(&g->vhost->dev, vdev, idx, mask);
+}
+
+static void
+vhost_user_gpu_instance_init(Object *obj)
+{
+ VhostUserGPU *g = VHOST_USER_GPU(obj);
+
+ g->vhost = VHOST_USER_BACKEND(object_new(TYPE_VHOST_USER_BACKEND));
+ object_property_add_alias(obj, "chardev",
+ OBJECT(g->vhost), "chardev");
+}
+
+static void
+vhost_user_gpu_instance_finalize(Object *obj)
+{
+ VhostUserGPU *g = VHOST_USER_GPU(obj);
+
+ object_unref(OBJECT(g->vhost));
+}
+
+static void
+vhost_user_gpu_reset(VirtIODevice *vdev)
+{
+ VhostUserGPU *g = VHOST_USER_GPU(vdev);
+
+ virtio_gpu_base_reset(VIRTIO_GPU_BASE(vdev));
+
+ vhost_user_backend_stop(g->vhost);
+}
+
+static int
+vhost_user_gpu_config_change(struct vhost_dev *dev)
+{
+ error_report("vhost-user-gpu: unhandled backend config change");
+ return -1;
+}
+
+static const VhostDevConfigOps config_ops = {
+ .vhost_dev_config_notifier = vhost_user_gpu_config_change,
+};
+
+static void
+vhost_user_gpu_device_realize(DeviceState *qdev, Error **errp)
+{
+ VhostUserGPU *g = VHOST_USER_GPU(qdev);
+ VirtIODevice *vdev = VIRTIO_DEVICE(g);
+
+ vhost_dev_set_config_notifier(&g->vhost->dev, &config_ops);
+ if (vhost_user_backend_dev_init(g->vhost, vdev, 2, errp) < 0) {
+ return;
+ }
+
+ /* existing backend may send DMABUF, so let's add that requirement */
+ g->parent_obj.conf.flags |= 1 << VIRTIO_GPU_FLAG_DMABUF_ENABLED;
+ if (virtio_has_feature(g->vhost->dev.features, VIRTIO_GPU_F_VIRGL)) {
+ g->parent_obj.conf.flags |= 1 << VIRTIO_GPU_FLAG_VIRGL_ENABLED;
+ }
+ if (virtio_has_feature(g->vhost->dev.features, VIRTIO_GPU_F_EDID)) {
+ g->parent_obj.conf.flags |= 1 << VIRTIO_GPU_FLAG_EDID_ENABLED;
+ } else {
+ error_report("EDID requested but the backend doesn't support it.");
+ g->parent_obj.conf.flags &= ~(1 << VIRTIO_GPU_FLAG_EDID_ENABLED);
+ }
+
+ if (!virtio_gpu_base_device_realize(qdev, NULL, NULL, errp)) {
+ return;
+ }
+
+ g->vhost_gpu_fd = -1;
+}
+
+static Property vhost_user_gpu_properties[] = {
+ VIRTIO_GPU_BASE_PROPERTIES(VhostUserGPU, parent_obj.conf),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void
+vhost_user_gpu_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);
+ VirtIOGPUBaseClass *vgc = VIRTIO_GPU_BASE_CLASS(klass);
+
+ vgc->gl_flushed = vhost_user_gpu_gl_flushed;
+
+ vdc->realize = vhost_user_gpu_device_realize;
+ vdc->reset = vhost_user_gpu_reset;
+ vdc->set_status = vhost_user_gpu_set_status;
+ vdc->guest_notifier_mask = vhost_user_gpu_guest_notifier_mask;
+ vdc->guest_notifier_pending = vhost_user_gpu_guest_notifier_pending;
+ vdc->get_config = vhost_user_gpu_get_config;
+ vdc->set_config = vhost_user_gpu_set_config;
+
+ device_class_set_props(dc, vhost_user_gpu_properties);
+}
+
+static const TypeInfo vhost_user_gpu_info = {
+ .name = TYPE_VHOST_USER_GPU,
+ .parent = TYPE_VIRTIO_GPU_BASE,
+ .instance_size = sizeof(VhostUserGPU),
+ .instance_init = vhost_user_gpu_instance_init,
+ .instance_finalize = vhost_user_gpu_instance_finalize,
+ .class_init = vhost_user_gpu_class_init,
+};
+module_obj(TYPE_VHOST_USER_GPU);
+
+static void vhost_user_gpu_register_types(void)
+{
+ type_register_static(&vhost_user_gpu_info);
+}
+
+type_init(vhost_user_gpu_register_types)
diff --git a/hw/display/vhost-user-vga.c b/hw/display/vhost-user-vga.c
new file mode 100644
index 000000000..072c9c65b
--- /dev/null
+++ b/hw/display/vhost-user-vga.c
@@ -0,0 +1,54 @@
+/*
+ * vhost-user VGA device
+ *
+ * Copyright Red Hat, Inc. 2018
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "virtio-vga.h"
+#include "qom/object.h"
+
+#define TYPE_VHOST_USER_VGA "vhost-user-vga"
+
+typedef struct VhostUserVGA VhostUserVGA;
+DECLARE_INSTANCE_CHECKER(VhostUserVGA, VHOST_USER_VGA,
+ TYPE_VHOST_USER_VGA)
+
+struct VhostUserVGA {
+ VirtIOVGABase parent_obj;
+
+ VhostUserGPU vdev;
+};
+
+static void vhost_user_vga_inst_initfn(Object *obj)
+{
+ VhostUserVGA *dev = VHOST_USER_VGA(obj);
+
+ virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev),
+ TYPE_VHOST_USER_GPU);
+
+ VIRTIO_VGA_BASE(dev)->vgpu = VIRTIO_GPU_BASE(&dev->vdev);
+
+ object_property_add_alias(obj, "chardev",
+ OBJECT(&dev->vdev), "chardev");
+}
+
+static const VirtioPCIDeviceTypeInfo vhost_user_vga_info = {
+ .generic_name = TYPE_VHOST_USER_VGA,
+ .parent = TYPE_VIRTIO_VGA_BASE,
+ .instance_size = sizeof(VhostUserVGA),
+ .instance_init = vhost_user_vga_inst_initfn,
+};
+module_obj(TYPE_VHOST_USER_VGA);
+
+static void vhost_user_vga_register_types(void)
+{
+ virtio_pci_types_register(&vhost_user_vga_info);
+}
+
+type_init(vhost_user_vga_register_types)
diff --git a/hw/display/virtio-gpu-base.c b/hw/display/virtio-gpu-base.c
new file mode 100644
index 000000000..c8da4806e
--- /dev/null
+++ b/hw/display/virtio-gpu-base.c
@@ -0,0 +1,290 @@
+/*
+ * Virtio GPU Device
+ *
+ * Copyright Red Hat, Inc. 2013-2014
+ *
+ * Authors:
+ * Dave Airlie <airlied@redhat.com>
+ * Gerd Hoffmann <kraxel@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+
+#include "hw/virtio/virtio-gpu.h"
+#include "migration/blocker.h"
+#include "qapi/error.h"
+#include "qemu/error-report.h"
+#include "trace.h"
+
+void
+virtio_gpu_base_reset(VirtIOGPUBase *g)
+{
+ int i;
+
+ g->enable = 0;
+
+ for (i = 0; i < g->conf.max_outputs; i++) {
+ g->scanout[i].resource_id = 0;
+ g->scanout[i].width = 0;
+ g->scanout[i].height = 0;
+ g->scanout[i].x = 0;
+ g->scanout[i].y = 0;
+ g->scanout[i].ds = NULL;
+ }
+}
+
+void
+virtio_gpu_base_fill_display_info(VirtIOGPUBase *g,
+ struct virtio_gpu_resp_display_info *dpy_info)
+{
+ int i;
+
+ for (i = 0; i < g->conf.max_outputs; i++) {
+ if (g->enabled_output_bitmask & (1 << i)) {
+ dpy_info->pmodes[i].enabled = 1;
+ dpy_info->pmodes[i].r.width = cpu_to_le32(g->req_state[i].width);
+ dpy_info->pmodes[i].r.height = cpu_to_le32(g->req_state[i].height);
+ }
+ }
+}
+
+static void virtio_gpu_invalidate_display(void *opaque)
+{
+}
+
+static void virtio_gpu_update_display(void *opaque)
+{
+}
+
+static void virtio_gpu_text_update(void *opaque, console_ch_t *chardata)
+{
+}
+
+static void virtio_gpu_notify_event(VirtIOGPUBase *g, uint32_t event_type)
+{
+ g->virtio_config.events_read |= event_type;
+ virtio_notify_config(&g->parent_obj);
+}
+
+static int virtio_gpu_ui_info(void *opaque, uint32_t idx, QemuUIInfo *info)
+{
+ VirtIOGPUBase *g = opaque;
+
+ if (idx >= g->conf.max_outputs) {
+ return -1;
+ }
+
+ g->req_state[idx].x = info->xoff;
+ g->req_state[idx].y = info->yoff;
+ g->req_state[idx].width = info->width;
+ g->req_state[idx].height = info->height;
+ g->req_state[idx].width_mm = info->width_mm;
+ g->req_state[idx].height_mm = info->height_mm;
+
+ if (info->width && info->height) {
+ g->enabled_output_bitmask |= (1 << idx);
+ } else {
+ g->enabled_output_bitmask &= ~(1 << idx);
+ }
+
+ /* send event to guest */
+ virtio_gpu_notify_event(g, VIRTIO_GPU_EVENT_DISPLAY);
+ return 0;
+}
+
+static void
+virtio_gpu_gl_flushed(void *opaque)
+{
+ VirtIOGPUBase *g = opaque;
+ VirtIOGPUBaseClass *vgc = VIRTIO_GPU_BASE_GET_CLASS(g);
+
+ if (vgc->gl_flushed) {
+ vgc->gl_flushed(g);
+ }
+}
+
+static void
+virtio_gpu_gl_block(void *opaque, bool block)
+{
+ VirtIOGPUBase *g = opaque;
+
+ if (block) {
+ g->renderer_blocked++;
+ } else {
+ g->renderer_blocked--;
+ }
+ assert(g->renderer_blocked >= 0);
+}
+
+static int
+virtio_gpu_get_flags(void *opaque)
+{
+ VirtIOGPUBase *g = opaque;
+ int flags = GRAPHIC_FLAGS_NONE;
+
+ if (virtio_gpu_virgl_enabled(g->conf)) {
+ flags |= GRAPHIC_FLAGS_GL;
+ }
+
+ if (virtio_gpu_dmabuf_enabled(g->conf)) {
+ flags |= GRAPHIC_FLAGS_DMABUF;
+ }
+
+ return flags;
+}
+
+static const GraphicHwOps virtio_gpu_ops = {
+ .get_flags = virtio_gpu_get_flags,
+ .invalidate = virtio_gpu_invalidate_display,
+ .gfx_update = virtio_gpu_update_display,
+ .text_update = virtio_gpu_text_update,
+ .ui_info = virtio_gpu_ui_info,
+ .gl_block = virtio_gpu_gl_block,
+ .gl_flushed = virtio_gpu_gl_flushed,
+};
+
+bool
+virtio_gpu_base_device_realize(DeviceState *qdev,
+ VirtIOHandleOutput ctrl_cb,
+ VirtIOHandleOutput cursor_cb,
+ Error **errp)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(qdev);
+ VirtIOGPUBase *g = VIRTIO_GPU_BASE(qdev);
+ int i;
+
+ if (g->conf.max_outputs > VIRTIO_GPU_MAX_SCANOUTS) {
+ error_setg(errp, "invalid max_outputs > %d", VIRTIO_GPU_MAX_SCANOUTS);
+ return false;
+ }
+
+ if (virtio_gpu_virgl_enabled(g->conf)) {
+ error_setg(&g->migration_blocker, "virgl is not yet migratable");
+ if (migrate_add_blocker(g->migration_blocker, errp) < 0) {
+ error_free(g->migration_blocker);
+ return false;
+ }
+ }
+
+ g->virtio_config.num_scanouts = cpu_to_le32(g->conf.max_outputs);
+ virtio_init(VIRTIO_DEVICE(g), "virtio-gpu", VIRTIO_ID_GPU,
+ sizeof(struct virtio_gpu_config));
+
+ if (virtio_gpu_virgl_enabled(g->conf)) {
+ /* use larger control queue in 3d mode */
+ virtio_add_queue(vdev, 256, ctrl_cb);
+ virtio_add_queue(vdev, 16, cursor_cb);
+ } else {
+ virtio_add_queue(vdev, 64, ctrl_cb);
+ virtio_add_queue(vdev, 16, cursor_cb);
+ }
+
+ g->enabled_output_bitmask = 1;
+
+ g->req_state[0].width = g->conf.xres;
+ g->req_state[0].height = g->conf.yres;
+
+ g->hw_ops = &virtio_gpu_ops;
+ for (i = 0; i < g->conf.max_outputs; i++) {
+ g->scanout[i].con =
+ graphic_console_init(DEVICE(g), i, &virtio_gpu_ops, g);
+ }
+
+ return true;
+}
+
+static uint64_t
+virtio_gpu_base_get_features(VirtIODevice *vdev, uint64_t features,
+ Error **errp)
+{
+ VirtIOGPUBase *g = VIRTIO_GPU_BASE(vdev);
+
+ if (virtio_gpu_virgl_enabled(g->conf)) {
+ features |= (1 << VIRTIO_GPU_F_VIRGL);
+ }
+ if (virtio_gpu_edid_enabled(g->conf)) {
+ features |= (1 << VIRTIO_GPU_F_EDID);
+ }
+ if (virtio_gpu_blob_enabled(g->conf)) {
+ features |= (1 << VIRTIO_GPU_F_RESOURCE_BLOB);
+ }
+
+ return features;
+}
+
+static void
+virtio_gpu_base_set_features(VirtIODevice *vdev, uint64_t features)
+{
+ static const uint32_t virgl = (1 << VIRTIO_GPU_F_VIRGL);
+
+ trace_virtio_gpu_features(((features & virgl) == virgl));
+}
+
+static void
+virtio_gpu_base_device_unrealize(DeviceState *qdev)
+{
+ VirtIOGPUBase *g = VIRTIO_GPU_BASE(qdev);
+
+ if (g->migration_blocker) {
+ migrate_del_blocker(g->migration_blocker);
+ error_free(g->migration_blocker);
+ }
+}
+
+static void
+virtio_gpu_base_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);
+
+ vdc->unrealize = virtio_gpu_base_device_unrealize;
+ vdc->get_features = virtio_gpu_base_get_features;
+ vdc->set_features = virtio_gpu_base_set_features;
+
+ set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories);
+ dc->hotpluggable = false;
+}
+
+static const TypeInfo virtio_gpu_base_info = {
+ .name = TYPE_VIRTIO_GPU_BASE,
+ .parent = TYPE_VIRTIO_DEVICE,
+ .instance_size = sizeof(VirtIOGPUBase),
+ .class_size = sizeof(VirtIOGPUBaseClass),
+ .class_init = virtio_gpu_base_class_init,
+ .abstract = true
+};
+module_obj(TYPE_VIRTIO_GPU_BASE);
+
+static void
+virtio_register_types(void)
+{
+ type_register_static(&virtio_gpu_base_info);
+}
+
+type_init(virtio_register_types)
+
+QEMU_BUILD_BUG_ON(sizeof(struct virtio_gpu_ctrl_hdr) != 24);
+QEMU_BUILD_BUG_ON(sizeof(struct virtio_gpu_update_cursor) != 56);
+QEMU_BUILD_BUG_ON(sizeof(struct virtio_gpu_resource_unref) != 32);
+QEMU_BUILD_BUG_ON(sizeof(struct virtio_gpu_resource_create_2d) != 40);
+QEMU_BUILD_BUG_ON(sizeof(struct virtio_gpu_set_scanout) != 48);
+QEMU_BUILD_BUG_ON(sizeof(struct virtio_gpu_resource_flush) != 48);
+QEMU_BUILD_BUG_ON(sizeof(struct virtio_gpu_transfer_to_host_2d) != 56);
+QEMU_BUILD_BUG_ON(sizeof(struct virtio_gpu_mem_entry) != 16);
+QEMU_BUILD_BUG_ON(sizeof(struct virtio_gpu_resource_attach_backing) != 32);
+QEMU_BUILD_BUG_ON(sizeof(struct virtio_gpu_resource_detach_backing) != 32);
+QEMU_BUILD_BUG_ON(sizeof(struct virtio_gpu_resp_display_info) != 408);
+
+QEMU_BUILD_BUG_ON(sizeof(struct virtio_gpu_transfer_host_3d) != 72);
+QEMU_BUILD_BUG_ON(sizeof(struct virtio_gpu_resource_create_3d) != 72);
+QEMU_BUILD_BUG_ON(sizeof(struct virtio_gpu_ctx_create) != 96);
+QEMU_BUILD_BUG_ON(sizeof(struct virtio_gpu_ctx_destroy) != 24);
+QEMU_BUILD_BUG_ON(sizeof(struct virtio_gpu_ctx_resource) != 32);
+QEMU_BUILD_BUG_ON(sizeof(struct virtio_gpu_cmd_submit) != 32);
+QEMU_BUILD_BUG_ON(sizeof(struct virtio_gpu_get_capset_info) != 32);
+QEMU_BUILD_BUG_ON(sizeof(struct virtio_gpu_resp_capset_info) != 40);
+QEMU_BUILD_BUG_ON(sizeof(struct virtio_gpu_get_capset) != 32);
+QEMU_BUILD_BUG_ON(sizeof(struct virtio_gpu_resp_capset) != 24);
diff --git a/hw/display/virtio-gpu-gl.c b/hw/display/virtio-gpu-gl.c
new file mode 100644
index 000000000..6cc4313b1
--- /dev/null
+++ b/hw/display/virtio-gpu-gl.c
@@ -0,0 +1,171 @@
+/*
+ * Virtio GPU Device
+ *
+ * Copyright Red Hat, Inc. 2013-2014
+ *
+ * Authors:
+ * Dave Airlie <airlied@redhat.com>
+ * Gerd Hoffmann <kraxel@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/iov.h"
+#include "qemu/module.h"
+#include "qemu/error-report.h"
+#include "qapi/error.h"
+#include "sysemu/sysemu.h"
+#include "hw/virtio/virtio.h"
+#include "hw/virtio/virtio-gpu.h"
+#include "hw/virtio/virtio-gpu-bswap.h"
+#include "hw/virtio/virtio-gpu-pixman.h"
+#include "hw/qdev-properties.h"
+
+#include <virglrenderer.h>
+
+static void virtio_gpu_gl_update_cursor_data(VirtIOGPU *g,
+ struct virtio_gpu_scanout *s,
+ uint32_t resource_id)
+{
+ uint32_t width, height;
+ uint32_t pixels, *data;
+
+ data = virgl_renderer_get_cursor_data(resource_id, &width, &height);
+ if (!data) {
+ return;
+ }
+
+ if (width != s->current_cursor->width ||
+ height != s->current_cursor->height) {
+ free(data);
+ return;
+ }
+
+ pixels = s->current_cursor->width * s->current_cursor->height;
+ memcpy(s->current_cursor->data, data, pixels * sizeof(uint32_t));
+ free(data);
+}
+
+static void virtio_gpu_gl_flushed(VirtIOGPUBase *b)
+{
+ VirtIOGPU *g = VIRTIO_GPU(b);
+
+ virtio_gpu_process_cmdq(g);
+}
+
+static void virtio_gpu_gl_handle_ctrl(VirtIODevice *vdev, VirtQueue *vq)
+{
+ VirtIOGPU *g = VIRTIO_GPU(vdev);
+ VirtIOGPUGL *gl = VIRTIO_GPU_GL(vdev);
+ struct virtio_gpu_ctrl_command *cmd;
+
+ if (!virtio_queue_ready(vq)) {
+ return;
+ }
+
+ if (!gl->renderer_inited) {
+ virtio_gpu_virgl_init(g);
+ gl->renderer_inited = true;
+ }
+ if (gl->renderer_reset) {
+ gl->renderer_reset = false;
+ virtio_gpu_virgl_reset(g);
+ }
+
+ cmd = virtqueue_pop(vq, sizeof(struct virtio_gpu_ctrl_command));
+ while (cmd) {
+ cmd->vq = vq;
+ cmd->error = 0;
+ cmd->finished = false;
+ QTAILQ_INSERT_TAIL(&g->cmdq, cmd, next);
+ cmd = virtqueue_pop(vq, sizeof(struct virtio_gpu_ctrl_command));
+ }
+
+ virtio_gpu_process_cmdq(g);
+ virtio_gpu_virgl_fence_poll(g);
+}
+
+static void virtio_gpu_gl_reset(VirtIODevice *vdev)
+{
+ VirtIOGPU *g = VIRTIO_GPU(vdev);
+ VirtIOGPUGL *gl = VIRTIO_GPU_GL(vdev);
+
+ virtio_gpu_reset(vdev);
+
+ /*
+ * GL functions must be called with the associated GL context in main
+ * thread, and when the renderer is unblocked.
+ */
+ if (gl->renderer_inited && !gl->renderer_reset) {
+ virtio_gpu_virgl_reset_scanout(g);
+ gl->renderer_reset = true;
+ }
+}
+
+static void virtio_gpu_gl_device_realize(DeviceState *qdev, Error **errp)
+{
+ VirtIOGPU *g = VIRTIO_GPU(qdev);
+
+#if defined(HOST_WORDS_BIGENDIAN)
+ error_setg(errp, "virgl is not supported on bigendian platforms");
+ return;
+#endif
+
+ if (!object_resolve_path_type("", TYPE_VIRTIO_GPU_GL, NULL)) {
+ error_setg(errp, "at most one %s device is permitted", TYPE_VIRTIO_GPU_GL);
+ return;
+ }
+
+ if (!display_opengl) {
+ error_setg(errp, "opengl is not available");
+ return;
+ }
+
+ g->parent_obj.conf.flags |= (1 << VIRTIO_GPU_FLAG_VIRGL_ENABLED);
+ VIRTIO_GPU_BASE(g)->virtio_config.num_capsets =
+ virtio_gpu_virgl_get_num_capsets(g);
+
+ virtio_gpu_device_realize(qdev, errp);
+}
+
+static Property virtio_gpu_gl_properties[] = {
+ DEFINE_PROP_BIT("stats", VirtIOGPU, parent_obj.conf.flags,
+ VIRTIO_GPU_FLAG_STATS_ENABLED, false),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void virtio_gpu_gl_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);
+ VirtIOGPUBaseClass *vbc = VIRTIO_GPU_BASE_CLASS(klass);
+ VirtIOGPUClass *vgc = VIRTIO_GPU_CLASS(klass);
+
+ vbc->gl_flushed = virtio_gpu_gl_flushed;
+ vgc->handle_ctrl = virtio_gpu_gl_handle_ctrl;
+ vgc->process_cmd = virtio_gpu_virgl_process_cmd;
+ vgc->update_cursor_data = virtio_gpu_gl_update_cursor_data;
+
+ vdc->realize = virtio_gpu_gl_device_realize;
+ vdc->reset = virtio_gpu_gl_reset;
+ device_class_set_props(dc, virtio_gpu_gl_properties);
+}
+
+static const TypeInfo virtio_gpu_gl_info = {
+ .name = TYPE_VIRTIO_GPU_GL,
+ .parent = TYPE_VIRTIO_GPU,
+ .instance_size = sizeof(VirtIOGPUGL),
+ .class_init = virtio_gpu_gl_class_init,
+};
+module_obj(TYPE_VIRTIO_GPU_GL);
+
+static void virtio_register_types(void)
+{
+ type_register_static(&virtio_gpu_gl_info);
+}
+
+type_init(virtio_register_types)
+
+module_dep("hw-display-virtio-gpu");
diff --git a/hw/display/virtio-gpu-pci-gl.c b/hw/display/virtio-gpu-pci-gl.c
new file mode 100644
index 000000000..99b14a071
--- /dev/null
+++ b/hw/display/virtio-gpu-pci-gl.c
@@ -0,0 +1,58 @@
+/*
+ * Virtio video device
+ *
+ * Copyright Red Hat
+ *
+ * Authors:
+ * Dave Airlie
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "qemu/module.h"
+#include "hw/pci/pci.h"
+#include "hw/qdev-properties.h"
+#include "hw/virtio/virtio.h"
+#include "hw/virtio/virtio-bus.h"
+#include "hw/virtio/virtio-gpu-pci.h"
+#include "qom/object.h"
+
+#define TYPE_VIRTIO_GPU_GL_PCI "virtio-gpu-gl-pci"
+typedef struct VirtIOGPUGLPCI VirtIOGPUGLPCI;
+DECLARE_INSTANCE_CHECKER(VirtIOGPUGLPCI, VIRTIO_GPU_GL_PCI,
+ TYPE_VIRTIO_GPU_GL_PCI)
+
+struct VirtIOGPUGLPCI {
+ VirtIOGPUPCIBase parent_obj;
+ VirtIOGPUGL vdev;
+};
+
+static void virtio_gpu_gl_initfn(Object *obj)
+{
+ VirtIOGPUGLPCI *dev = VIRTIO_GPU_GL_PCI(obj);
+
+ virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev),
+ TYPE_VIRTIO_GPU_GL);
+ VIRTIO_GPU_PCI_BASE(obj)->vgpu = VIRTIO_GPU_BASE(&dev->vdev);
+}
+
+static const VirtioPCIDeviceTypeInfo virtio_gpu_gl_pci_info = {
+ .generic_name = TYPE_VIRTIO_GPU_GL_PCI,
+ .parent = TYPE_VIRTIO_GPU_PCI_BASE,
+ .instance_size = sizeof(VirtIOGPUGLPCI),
+ .instance_init = virtio_gpu_gl_initfn,
+};
+module_obj(TYPE_VIRTIO_GPU_GL_PCI);
+
+static void virtio_gpu_gl_pci_register_types(void)
+{
+ virtio_pci_types_register(&virtio_gpu_gl_pci_info);
+}
+
+type_init(virtio_gpu_gl_pci_register_types)
+
+module_dep("hw-display-virtio-gpu-pci");
diff --git a/hw/display/virtio-gpu-pci.c b/hw/display/virtio-gpu-pci.c
new file mode 100644
index 000000000..e36eee0c4
--- /dev/null
+++ b/hw/display/virtio-gpu-pci.c
@@ -0,0 +1,102 @@
+/*
+ * Virtio video device
+ *
+ * Copyright Red Hat
+ *
+ * Authors:
+ * Dave Airlie
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "qemu/module.h"
+#include "hw/pci/pci.h"
+#include "hw/qdev-properties.h"
+#include "hw/virtio/virtio.h"
+#include "hw/virtio/virtio-bus.h"
+#include "hw/virtio/virtio-gpu-pci.h"
+#include "qom/object.h"
+
+static Property virtio_gpu_pci_base_properties[] = {
+ DEFINE_VIRTIO_GPU_PCI_PROPERTIES(VirtIOPCIProxy),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void virtio_gpu_pci_base_realize(VirtIOPCIProxy *vpci_dev, Error **errp)
+{
+ VirtIOGPUPCIBase *vgpu = VIRTIO_GPU_PCI_BASE(vpci_dev);
+ VirtIOGPUBase *g = vgpu->vgpu;
+ DeviceState *vdev = DEVICE(g);
+ int i;
+
+ virtio_pci_force_virtio_1(vpci_dev);
+ if (!qdev_realize(vdev, BUS(&vpci_dev->bus), errp)) {
+ return;
+ }
+
+ for (i = 0; i < g->conf.max_outputs; i++) {
+ object_property_set_link(OBJECT(g->scanout[i].con), "device",
+ OBJECT(vpci_dev), &error_abort);
+ }
+}
+
+static void virtio_gpu_pci_base_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtioPCIClass *k = VIRTIO_PCI_CLASS(klass);
+ PCIDeviceClass *pcidev_k = PCI_DEVICE_CLASS(klass);
+
+ set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories);
+ device_class_set_props(dc, virtio_gpu_pci_base_properties);
+ dc->hotpluggable = false;
+ k->realize = virtio_gpu_pci_base_realize;
+ pcidev_k->class_id = PCI_CLASS_DISPLAY_OTHER;
+}
+
+static const TypeInfo virtio_gpu_pci_base_info = {
+ .name = TYPE_VIRTIO_GPU_PCI_BASE,
+ .parent = TYPE_VIRTIO_PCI,
+ .instance_size = sizeof(VirtIOGPUPCIBase),
+ .class_init = virtio_gpu_pci_base_class_init,
+ .abstract = true
+};
+module_obj(TYPE_VIRTIO_GPU_PCI_BASE);
+
+#define TYPE_VIRTIO_GPU_PCI "virtio-gpu-pci"
+typedef struct VirtIOGPUPCI VirtIOGPUPCI;
+DECLARE_INSTANCE_CHECKER(VirtIOGPUPCI, VIRTIO_GPU_PCI,
+ TYPE_VIRTIO_GPU_PCI)
+
+struct VirtIOGPUPCI {
+ VirtIOGPUPCIBase parent_obj;
+ VirtIOGPU vdev;
+};
+
+static void virtio_gpu_initfn(Object *obj)
+{
+ VirtIOGPUPCI *dev = VIRTIO_GPU_PCI(obj);
+
+ virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev),
+ TYPE_VIRTIO_GPU);
+ VIRTIO_GPU_PCI_BASE(obj)->vgpu = VIRTIO_GPU_BASE(&dev->vdev);
+}
+
+static const VirtioPCIDeviceTypeInfo virtio_gpu_pci_info = {
+ .generic_name = TYPE_VIRTIO_GPU_PCI,
+ .parent = TYPE_VIRTIO_GPU_PCI_BASE,
+ .instance_size = sizeof(VirtIOGPUPCI),
+ .instance_init = virtio_gpu_initfn,
+};
+module_obj(TYPE_VIRTIO_GPU_PCI);
+
+static void virtio_gpu_pci_register_types(void)
+{
+ type_register_static(&virtio_gpu_pci_base_info);
+ virtio_pci_types_register(&virtio_gpu_pci_info);
+}
+
+type_init(virtio_gpu_pci_register_types)
diff --git a/hw/display/virtio-gpu-udmabuf-stubs.c b/hw/display/virtio-gpu-udmabuf-stubs.c
new file mode 100644
index 000000000..f692e1351
--- /dev/null
+++ b/hw/display/virtio-gpu-udmabuf-stubs.c
@@ -0,0 +1,28 @@
+#include "qemu/osdep.h"
+#include "hw/virtio/virtio-gpu.h"
+
+bool virtio_gpu_have_udmabuf(void)
+{
+ /* nothing (stub) */
+ return false;
+}
+
+void virtio_gpu_init_udmabuf(struct virtio_gpu_simple_resource *res)
+{
+ /* nothing (stub) */
+}
+
+void virtio_gpu_fini_udmabuf(struct virtio_gpu_simple_resource *res)
+{
+ /* nothing (stub) */
+}
+
+int virtio_gpu_update_dmabuf(VirtIOGPU *g,
+ uint32_t scanout_id,
+ struct virtio_gpu_simple_resource *res,
+ struct virtio_gpu_framebuffer *fb,
+ struct virtio_gpu_rect *r)
+{
+ /* nothing (stub) */
+ return 0;
+}
diff --git a/hw/display/virtio-gpu-udmabuf.c b/hw/display/virtio-gpu-udmabuf.c
new file mode 100644
index 000000000..1597921c5
--- /dev/null
+++ b/hw/display/virtio-gpu-udmabuf.c
@@ -0,0 +1,230 @@
+/*
+ * Virtio GPU Device
+ *
+ * Copyright Red Hat, Inc. 2013-2014
+ *
+ * Authors:
+ * Dave Airlie <airlied@redhat.com>
+ * Gerd Hoffmann <kraxel@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/units.h"
+#include "qemu-common.h"
+#include "qemu/iov.h"
+#include "ui/console.h"
+#include "hw/virtio/virtio-gpu.h"
+#include "hw/virtio/virtio-gpu-pixman.h"
+#include "trace.h"
+#include "exec/ramblock.h"
+#include "sysemu/hostmem.h"
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <linux/memfd.h>
+#include "qemu/memfd.h"
+#include "standard-headers/linux/udmabuf.h"
+
+static void virtio_gpu_create_udmabuf(struct virtio_gpu_simple_resource *res)
+{
+ struct udmabuf_create_list *list;
+ RAMBlock *rb;
+ ram_addr_t offset;
+ int udmabuf, i;
+
+ udmabuf = udmabuf_fd();
+ if (udmabuf < 0) {
+ return;
+ }
+
+ list = g_malloc0(sizeof(struct udmabuf_create_list) +
+ sizeof(struct udmabuf_create_item) * res->iov_cnt);
+
+ for (i = 0; i < res->iov_cnt; i++) {
+ rcu_read_lock();
+ rb = qemu_ram_block_from_host(res->iov[i].iov_base, false, &offset);
+ rcu_read_unlock();
+
+ if (!rb || rb->fd < 0) {
+ g_free(list);
+ return;
+ }
+
+ list->list[i].memfd = rb->fd;
+ list->list[i].offset = offset;
+ list->list[i].size = res->iov[i].iov_len;
+ }
+
+ list->count = res->iov_cnt;
+ list->flags = UDMABUF_FLAGS_CLOEXEC;
+
+ res->dmabuf_fd = ioctl(udmabuf, UDMABUF_CREATE_LIST, list);
+ if (res->dmabuf_fd < 0) {
+ warn_report("%s: UDMABUF_CREATE_LIST: %s", __func__,
+ strerror(errno));
+ }
+ g_free(list);
+}
+
+static void virtio_gpu_remap_udmabuf(struct virtio_gpu_simple_resource *res)
+{
+ res->remapped = mmap(NULL, res->blob_size, PROT_READ,
+ MAP_SHARED, res->dmabuf_fd, 0);
+ if (res->remapped == MAP_FAILED) {
+ warn_report("%s: dmabuf mmap failed: %s", __func__,
+ strerror(errno));
+ res->remapped = NULL;
+ }
+}
+
+static void virtio_gpu_destroy_udmabuf(struct virtio_gpu_simple_resource *res)
+{
+ if (res->remapped) {
+ munmap(res->remapped, res->blob_size);
+ res->remapped = NULL;
+ }
+ if (res->dmabuf_fd >= 0) {
+ close(res->dmabuf_fd);
+ res->dmabuf_fd = -1;
+ }
+}
+
+static int find_memory_backend_type(Object *obj, void *opaque)
+{
+ bool *memfd_backend = opaque;
+ int ret;
+
+ if (object_dynamic_cast(obj, TYPE_MEMORY_BACKEND)) {
+ HostMemoryBackend *backend = MEMORY_BACKEND(obj);
+ RAMBlock *rb = backend->mr.ram_block;
+
+ if (rb && rb->fd > 0) {
+ ret = fcntl(rb->fd, F_GET_SEALS);
+ if (ret > 0) {
+ *memfd_backend = true;
+ }
+ }
+ }
+
+ return 0;
+}
+
+bool virtio_gpu_have_udmabuf(void)
+{
+ Object *memdev_root;
+ int udmabuf;
+ bool memfd_backend = false;
+
+ udmabuf = udmabuf_fd();
+ if (udmabuf < 0) {
+ return false;
+ }
+
+ memdev_root = object_resolve_path("/objects", NULL);
+ object_child_foreach(memdev_root, find_memory_backend_type, &memfd_backend);
+
+ return memfd_backend;
+}
+
+void virtio_gpu_init_udmabuf(struct virtio_gpu_simple_resource *res)
+{
+ void *pdata = NULL;
+
+ res->dmabuf_fd = -1;
+ if (res->iov_cnt == 1) {
+ pdata = res->iov[0].iov_base;
+ } else {
+ virtio_gpu_create_udmabuf(res);
+ if (res->dmabuf_fd < 0) {
+ return;
+ }
+ virtio_gpu_remap_udmabuf(res);
+ if (!res->remapped) {
+ return;
+ }
+ pdata = res->remapped;
+ }
+
+ res->blob = pdata;
+}
+
+void virtio_gpu_fini_udmabuf(struct virtio_gpu_simple_resource *res)
+{
+ if (res->remapped) {
+ virtio_gpu_destroy_udmabuf(res);
+ }
+}
+
+static void virtio_gpu_free_dmabuf(VirtIOGPU *g, VGPUDMABuf *dmabuf)
+{
+ struct virtio_gpu_scanout *scanout;
+
+ scanout = &g->parent_obj.scanout[dmabuf->scanout_id];
+ dpy_gl_release_dmabuf(scanout->con, &dmabuf->buf);
+ QTAILQ_REMOVE(&g->dmabuf.bufs, dmabuf, next);
+ g_free(dmabuf);
+}
+
+static VGPUDMABuf
+*virtio_gpu_create_dmabuf(VirtIOGPU *g,
+ uint32_t scanout_id,
+ struct virtio_gpu_simple_resource *res,
+ struct virtio_gpu_framebuffer *fb,
+ struct virtio_gpu_rect *r)
+{
+ VGPUDMABuf *dmabuf;
+
+ if (res->dmabuf_fd < 0) {
+ return NULL;
+ }
+
+ dmabuf = g_new0(VGPUDMABuf, 1);
+ dmabuf->buf.width = fb->width;
+ dmabuf->buf.height = fb->height;
+ dmabuf->buf.stride = fb->stride;
+ dmabuf->buf.x = r->x;
+ dmabuf->buf.y = r->y;
+ dmabuf->buf.scanout_width = r->width;
+ dmabuf->buf.scanout_height = r->height;
+ dmabuf->buf.fourcc = qemu_pixman_to_drm_format(fb->format);
+ dmabuf->buf.fd = res->dmabuf_fd;
+ dmabuf->buf.allow_fences = true;
+ dmabuf->buf.draw_submitted = false;
+ dmabuf->scanout_id = scanout_id;
+ QTAILQ_INSERT_HEAD(&g->dmabuf.bufs, dmabuf, next);
+
+ return dmabuf;
+}
+
+int virtio_gpu_update_dmabuf(VirtIOGPU *g,
+ uint32_t scanout_id,
+ struct virtio_gpu_simple_resource *res,
+ struct virtio_gpu_framebuffer *fb,
+ struct virtio_gpu_rect *r)
+{
+ struct virtio_gpu_scanout *scanout = &g->parent_obj.scanout[scanout_id];
+ VGPUDMABuf *new_primary, *old_primary = NULL;
+
+ new_primary = virtio_gpu_create_dmabuf(g, scanout_id, res, fb, r);
+ if (!new_primary) {
+ return -EINVAL;
+ }
+
+ if (g->dmabuf.primary[scanout_id]) {
+ old_primary = g->dmabuf.primary[scanout_id];
+ }
+
+ g->dmabuf.primary[scanout_id] = new_primary;
+ qemu_console_resize(scanout->con,
+ new_primary->buf.scanout_width,
+ new_primary->buf.scanout_height);
+ dpy_gl_scanout_dmabuf(scanout->con, &new_primary->buf);
+
+ if (old_primary) {
+ virtio_gpu_free_dmabuf(g, old_primary);
+ }
+
+ return 0;
+}
diff --git a/hw/display/virtio-gpu-virgl.c b/hw/display/virtio-gpu-virgl.c
new file mode 100644
index 000000000..18d054922
--- /dev/null
+++ b/hw/display/virtio-gpu-virgl.c
@@ -0,0 +1,634 @@
+/*
+ * Virtio GPU Device
+ *
+ * Copyright Red Hat, Inc. 2013-2014
+ *
+ * Authors:
+ * Dave Airlie <airlied@redhat.com>
+ * Gerd Hoffmann <kraxel@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/iov.h"
+#include "trace.h"
+#include "hw/virtio/virtio.h"
+#include "hw/virtio/virtio-gpu.h"
+
+#include <virglrenderer.h>
+
+static struct virgl_renderer_callbacks virtio_gpu_3d_cbs;
+
+static void virgl_cmd_create_resource_2d(VirtIOGPU *g,
+ struct virtio_gpu_ctrl_command *cmd)
+{
+ struct virtio_gpu_resource_create_2d c2d;
+ struct virgl_renderer_resource_create_args args;
+
+ VIRTIO_GPU_FILL_CMD(c2d);
+ trace_virtio_gpu_cmd_res_create_2d(c2d.resource_id, c2d.format,
+ c2d.width, c2d.height);
+
+ args.handle = c2d.resource_id;
+ args.target = 2;
+ args.format = c2d.format;
+ args.bind = (1 << 1);
+ args.width = c2d.width;
+ args.height = c2d.height;
+ args.depth = 1;
+ args.array_size = 1;
+ args.last_level = 0;
+ args.nr_samples = 0;
+ args.flags = VIRTIO_GPU_RESOURCE_FLAG_Y_0_TOP;
+ virgl_renderer_resource_create(&args, NULL, 0);
+}
+
+static void virgl_cmd_create_resource_3d(VirtIOGPU *g,
+ struct virtio_gpu_ctrl_command *cmd)
+{
+ struct virtio_gpu_resource_create_3d c3d;
+ struct virgl_renderer_resource_create_args args;
+
+ VIRTIO_GPU_FILL_CMD(c3d);
+ trace_virtio_gpu_cmd_res_create_3d(c3d.resource_id, c3d.format,
+ c3d.width, c3d.height, c3d.depth);
+
+ args.handle = c3d.resource_id;
+ args.target = c3d.target;
+ args.format = c3d.format;
+ args.bind = c3d.bind;
+ args.width = c3d.width;
+ args.height = c3d.height;
+ args.depth = c3d.depth;
+ args.array_size = c3d.array_size;
+ args.last_level = c3d.last_level;
+ args.nr_samples = c3d.nr_samples;
+ args.flags = c3d.flags;
+ virgl_renderer_resource_create(&args, NULL, 0);
+}
+
+static void virgl_cmd_resource_unref(VirtIOGPU *g,
+ struct virtio_gpu_ctrl_command *cmd)
+{
+ struct virtio_gpu_resource_unref unref;
+ struct iovec *res_iovs = NULL;
+ int num_iovs = 0;
+
+ VIRTIO_GPU_FILL_CMD(unref);
+ trace_virtio_gpu_cmd_res_unref(unref.resource_id);
+
+ virgl_renderer_resource_detach_iov(unref.resource_id,
+ &res_iovs,
+ &num_iovs);
+ if (res_iovs != NULL && num_iovs != 0) {
+ virtio_gpu_cleanup_mapping_iov(g, res_iovs, num_iovs);
+ }
+ virgl_renderer_resource_unref(unref.resource_id);
+}
+
+static void virgl_cmd_context_create(VirtIOGPU *g,
+ struct virtio_gpu_ctrl_command *cmd)
+{
+ struct virtio_gpu_ctx_create cc;
+
+ VIRTIO_GPU_FILL_CMD(cc);
+ trace_virtio_gpu_cmd_ctx_create(cc.hdr.ctx_id,
+ cc.debug_name);
+
+ virgl_renderer_context_create(cc.hdr.ctx_id, cc.nlen,
+ cc.debug_name);
+}
+
+static void virgl_cmd_context_destroy(VirtIOGPU *g,
+ struct virtio_gpu_ctrl_command *cmd)
+{
+ struct virtio_gpu_ctx_destroy cd;
+
+ VIRTIO_GPU_FILL_CMD(cd);
+ trace_virtio_gpu_cmd_ctx_destroy(cd.hdr.ctx_id);
+
+ virgl_renderer_context_destroy(cd.hdr.ctx_id);
+}
+
+static void virtio_gpu_rect_update(VirtIOGPU *g, int idx, int x, int y,
+ int width, int height)
+{
+ if (!g->parent_obj.scanout[idx].con) {
+ return;
+ }
+
+ dpy_gl_update(g->parent_obj.scanout[idx].con, x, y, width, height);
+}
+
+static void virgl_cmd_resource_flush(VirtIOGPU *g,
+ struct virtio_gpu_ctrl_command *cmd)
+{
+ struct virtio_gpu_resource_flush rf;
+ int i;
+
+ VIRTIO_GPU_FILL_CMD(rf);
+ trace_virtio_gpu_cmd_res_flush(rf.resource_id,
+ rf.r.width, rf.r.height, rf.r.x, rf.r.y);
+
+ for (i = 0; i < g->parent_obj.conf.max_outputs; i++) {
+ if (g->parent_obj.scanout[i].resource_id != rf.resource_id) {
+ continue;
+ }
+ virtio_gpu_rect_update(g, i, rf.r.x, rf.r.y, rf.r.width, rf.r.height);
+ }
+}
+
+static void virgl_cmd_set_scanout(VirtIOGPU *g,
+ struct virtio_gpu_ctrl_command *cmd)
+{
+ struct virtio_gpu_set_scanout ss;
+ struct virgl_renderer_resource_info info;
+ int ret;
+
+ VIRTIO_GPU_FILL_CMD(ss);
+ trace_virtio_gpu_cmd_set_scanout(ss.scanout_id, ss.resource_id,
+ ss.r.width, ss.r.height, ss.r.x, ss.r.y);
+
+ if (ss.scanout_id >= g->parent_obj.conf.max_outputs) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: illegal scanout id specified %d",
+ __func__, ss.scanout_id);
+ cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_SCANOUT_ID;
+ return;
+ }
+ g->parent_obj.enable = 1;
+
+ memset(&info, 0, sizeof(info));
+
+ if (ss.resource_id && ss.r.width && ss.r.height) {
+ ret = virgl_renderer_resource_get_info(ss.resource_id, &info);
+ if (ret == -1) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: illegal resource specified %d\n",
+ __func__, ss.resource_id);
+ cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID;
+ return;
+ }
+ qemu_console_resize(g->parent_obj.scanout[ss.scanout_id].con,
+ ss.r.width, ss.r.height);
+ virgl_renderer_force_ctx_0();
+ dpy_gl_scanout_texture(
+ g->parent_obj.scanout[ss.scanout_id].con, info.tex_id,
+ info.flags & 1 /* FIXME: Y_0_TOP */,
+ info.width, info.height,
+ ss.r.x, ss.r.y, ss.r.width, ss.r.height);
+ } else {
+ dpy_gfx_replace_surface(
+ g->parent_obj.scanout[ss.scanout_id].con, NULL);
+ dpy_gl_scanout_disable(g->parent_obj.scanout[ss.scanout_id].con);
+ }
+ g->parent_obj.scanout[ss.scanout_id].resource_id = ss.resource_id;
+}
+
+static void virgl_cmd_submit_3d(VirtIOGPU *g,
+ struct virtio_gpu_ctrl_command *cmd)
+{
+ struct virtio_gpu_cmd_submit cs;
+ void *buf;
+ size_t s;
+
+ VIRTIO_GPU_FILL_CMD(cs);
+ trace_virtio_gpu_cmd_ctx_submit(cs.hdr.ctx_id, cs.size);
+
+ buf = g_malloc(cs.size);
+ s = iov_to_buf(cmd->elem.out_sg, cmd->elem.out_num,
+ sizeof(cs), buf, cs.size);
+ if (s != cs.size) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: size mismatch (%zd/%d)",
+ __func__, s, cs.size);
+ cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ if (virtio_gpu_stats_enabled(g->parent_obj.conf)) {
+ g->stats.req_3d++;
+ g->stats.bytes_3d += cs.size;
+ }
+
+ virgl_renderer_submit_cmd(buf, cs.hdr.ctx_id, cs.size / 4);
+
+out:
+ g_free(buf);
+}
+
+static void virgl_cmd_transfer_to_host_2d(VirtIOGPU *g,
+ struct virtio_gpu_ctrl_command *cmd)
+{
+ struct virtio_gpu_transfer_to_host_2d t2d;
+ struct virtio_gpu_box box;
+
+ VIRTIO_GPU_FILL_CMD(t2d);
+ trace_virtio_gpu_cmd_res_xfer_toh_2d(t2d.resource_id);
+
+ box.x = t2d.r.x;
+ box.y = t2d.r.y;
+ box.z = 0;
+ box.w = t2d.r.width;
+ box.h = t2d.r.height;
+ box.d = 1;
+
+ virgl_renderer_transfer_write_iov(t2d.resource_id,
+ 0,
+ 0,
+ 0,
+ 0,
+ (struct virgl_box *)&box,
+ t2d.offset, NULL, 0);
+}
+
+static void virgl_cmd_transfer_to_host_3d(VirtIOGPU *g,
+ struct virtio_gpu_ctrl_command *cmd)
+{
+ struct virtio_gpu_transfer_host_3d t3d;
+
+ VIRTIO_GPU_FILL_CMD(t3d);
+ trace_virtio_gpu_cmd_res_xfer_toh_3d(t3d.resource_id);
+
+ virgl_renderer_transfer_write_iov(t3d.resource_id,
+ t3d.hdr.ctx_id,
+ t3d.level,
+ t3d.stride,
+ t3d.layer_stride,
+ (struct virgl_box *)&t3d.box,
+ t3d.offset, NULL, 0);
+}
+
+static void
+virgl_cmd_transfer_from_host_3d(VirtIOGPU *g,
+ struct virtio_gpu_ctrl_command *cmd)
+{
+ struct virtio_gpu_transfer_host_3d tf3d;
+
+ VIRTIO_GPU_FILL_CMD(tf3d);
+ trace_virtio_gpu_cmd_res_xfer_fromh_3d(tf3d.resource_id);
+
+ virgl_renderer_transfer_read_iov(tf3d.resource_id,
+ tf3d.hdr.ctx_id,
+ tf3d.level,
+ tf3d.stride,
+ tf3d.layer_stride,
+ (struct virgl_box *)&tf3d.box,
+ tf3d.offset, NULL, 0);
+}
+
+
+static void virgl_resource_attach_backing(VirtIOGPU *g,
+ struct virtio_gpu_ctrl_command *cmd)
+{
+ struct virtio_gpu_resource_attach_backing att_rb;
+ struct iovec *res_iovs;
+ uint32_t res_niov;
+ int ret;
+
+ VIRTIO_GPU_FILL_CMD(att_rb);
+ trace_virtio_gpu_cmd_res_back_attach(att_rb.resource_id);
+
+ ret = virtio_gpu_create_mapping_iov(g, att_rb.nr_entries, sizeof(att_rb),
+ cmd, NULL, &res_iovs, &res_niov);
+ if (ret != 0) {
+ cmd->error = VIRTIO_GPU_RESP_ERR_UNSPEC;
+ return;
+ }
+
+ ret = virgl_renderer_resource_attach_iov(att_rb.resource_id,
+ res_iovs, res_niov);
+
+ if (ret != 0)
+ virtio_gpu_cleanup_mapping_iov(g, res_iovs, res_niov);
+}
+
+static void virgl_resource_detach_backing(VirtIOGPU *g,
+ struct virtio_gpu_ctrl_command *cmd)
+{
+ struct virtio_gpu_resource_detach_backing detach_rb;
+ struct iovec *res_iovs = NULL;
+ int num_iovs = 0;
+
+ VIRTIO_GPU_FILL_CMD(detach_rb);
+ trace_virtio_gpu_cmd_res_back_detach(detach_rb.resource_id);
+
+ virgl_renderer_resource_detach_iov(detach_rb.resource_id,
+ &res_iovs,
+ &num_iovs);
+ if (res_iovs == NULL || num_iovs == 0) {
+ return;
+ }
+ virtio_gpu_cleanup_mapping_iov(g, res_iovs, num_iovs);
+}
+
+
+static void virgl_cmd_ctx_attach_resource(VirtIOGPU *g,
+ struct virtio_gpu_ctrl_command *cmd)
+{
+ struct virtio_gpu_ctx_resource att_res;
+
+ VIRTIO_GPU_FILL_CMD(att_res);
+ trace_virtio_gpu_cmd_ctx_res_attach(att_res.hdr.ctx_id,
+ att_res.resource_id);
+
+ virgl_renderer_ctx_attach_resource(att_res.hdr.ctx_id, att_res.resource_id);
+}
+
+static void virgl_cmd_ctx_detach_resource(VirtIOGPU *g,
+ struct virtio_gpu_ctrl_command *cmd)
+{
+ struct virtio_gpu_ctx_resource det_res;
+
+ VIRTIO_GPU_FILL_CMD(det_res);
+ trace_virtio_gpu_cmd_ctx_res_detach(det_res.hdr.ctx_id,
+ det_res.resource_id);
+
+ virgl_renderer_ctx_detach_resource(det_res.hdr.ctx_id, det_res.resource_id);
+}
+
+static void virgl_cmd_get_capset_info(VirtIOGPU *g,
+ struct virtio_gpu_ctrl_command *cmd)
+{
+ struct virtio_gpu_get_capset_info info;
+ struct virtio_gpu_resp_capset_info resp;
+
+ VIRTIO_GPU_FILL_CMD(info);
+
+ memset(&resp, 0, sizeof(resp));
+ if (info.capset_index == 0) {
+ resp.capset_id = VIRTIO_GPU_CAPSET_VIRGL;
+ virgl_renderer_get_cap_set(resp.capset_id,
+ &resp.capset_max_version,
+ &resp.capset_max_size);
+ } else if (info.capset_index == 1) {
+ resp.capset_id = VIRTIO_GPU_CAPSET_VIRGL2;
+ virgl_renderer_get_cap_set(resp.capset_id,
+ &resp.capset_max_version,
+ &resp.capset_max_size);
+ } else {
+ resp.capset_max_version = 0;
+ resp.capset_max_size = 0;
+ }
+ resp.hdr.type = VIRTIO_GPU_RESP_OK_CAPSET_INFO;
+ virtio_gpu_ctrl_response(g, cmd, &resp.hdr, sizeof(resp));
+}
+
+static void virgl_cmd_get_capset(VirtIOGPU *g,
+ struct virtio_gpu_ctrl_command *cmd)
+{
+ struct virtio_gpu_get_capset gc;
+ struct virtio_gpu_resp_capset *resp;
+ uint32_t max_ver, max_size;
+ VIRTIO_GPU_FILL_CMD(gc);
+
+ virgl_renderer_get_cap_set(gc.capset_id, &max_ver,
+ &max_size);
+ if (!max_size) {
+ cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
+ return;
+ }
+
+ resp = g_malloc0(sizeof(*resp) + max_size);
+ resp->hdr.type = VIRTIO_GPU_RESP_OK_CAPSET;
+ virgl_renderer_fill_caps(gc.capset_id,
+ gc.capset_version,
+ (void *)resp->capset_data);
+ virtio_gpu_ctrl_response(g, cmd, &resp->hdr, sizeof(*resp) + max_size);
+ g_free(resp);
+}
+
+void virtio_gpu_virgl_process_cmd(VirtIOGPU *g,
+ struct virtio_gpu_ctrl_command *cmd)
+{
+ VIRTIO_GPU_FILL_CMD(cmd->cmd_hdr);
+
+ virgl_renderer_force_ctx_0();
+ switch (cmd->cmd_hdr.type) {
+ case VIRTIO_GPU_CMD_CTX_CREATE:
+ virgl_cmd_context_create(g, cmd);
+ break;
+ case VIRTIO_GPU_CMD_CTX_DESTROY:
+ virgl_cmd_context_destroy(g, cmd);
+ break;
+ case VIRTIO_GPU_CMD_RESOURCE_CREATE_2D:
+ virgl_cmd_create_resource_2d(g, cmd);
+ break;
+ case VIRTIO_GPU_CMD_RESOURCE_CREATE_3D:
+ virgl_cmd_create_resource_3d(g, cmd);
+ break;
+ case VIRTIO_GPU_CMD_SUBMIT_3D:
+ virgl_cmd_submit_3d(g, cmd);
+ break;
+ case VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D:
+ virgl_cmd_transfer_to_host_2d(g, cmd);
+ break;
+ case VIRTIO_GPU_CMD_TRANSFER_TO_HOST_3D:
+ virgl_cmd_transfer_to_host_3d(g, cmd);
+ break;
+ case VIRTIO_GPU_CMD_TRANSFER_FROM_HOST_3D:
+ virgl_cmd_transfer_from_host_3d(g, cmd);
+ break;
+ case VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING:
+ virgl_resource_attach_backing(g, cmd);
+ break;
+ case VIRTIO_GPU_CMD_RESOURCE_DETACH_BACKING:
+ virgl_resource_detach_backing(g, cmd);
+ break;
+ case VIRTIO_GPU_CMD_SET_SCANOUT:
+ virgl_cmd_set_scanout(g, cmd);
+ break;
+ case VIRTIO_GPU_CMD_RESOURCE_FLUSH:
+ virgl_cmd_resource_flush(g, cmd);
+ break;
+ case VIRTIO_GPU_CMD_RESOURCE_UNREF:
+ virgl_cmd_resource_unref(g, cmd);
+ break;
+ case VIRTIO_GPU_CMD_CTX_ATTACH_RESOURCE:
+ /* TODO add security */
+ virgl_cmd_ctx_attach_resource(g, cmd);
+ break;
+ case VIRTIO_GPU_CMD_CTX_DETACH_RESOURCE:
+ /* TODO add security */
+ virgl_cmd_ctx_detach_resource(g, cmd);
+ break;
+ case VIRTIO_GPU_CMD_GET_CAPSET_INFO:
+ virgl_cmd_get_capset_info(g, cmd);
+ break;
+ case VIRTIO_GPU_CMD_GET_CAPSET:
+ virgl_cmd_get_capset(g, cmd);
+ break;
+ case VIRTIO_GPU_CMD_GET_DISPLAY_INFO:
+ virtio_gpu_get_display_info(g, cmd);
+ break;
+ case VIRTIO_GPU_CMD_GET_EDID:
+ virtio_gpu_get_edid(g, cmd);
+ break;
+ default:
+ cmd->error = VIRTIO_GPU_RESP_ERR_UNSPEC;
+ break;
+ }
+
+ if (cmd->finished) {
+ return;
+ }
+ if (cmd->error) {
+ fprintf(stderr, "%s: ctrl 0x%x, error 0x%x\n", __func__,
+ cmd->cmd_hdr.type, cmd->error);
+ virtio_gpu_ctrl_response_nodata(g, cmd, cmd->error);
+ return;
+ }
+ if (!(cmd->cmd_hdr.flags & VIRTIO_GPU_FLAG_FENCE)) {
+ virtio_gpu_ctrl_response_nodata(g, cmd, VIRTIO_GPU_RESP_OK_NODATA);
+ return;
+ }
+
+ trace_virtio_gpu_fence_ctrl(cmd->cmd_hdr.fence_id, cmd->cmd_hdr.type);
+ virgl_renderer_create_fence(cmd->cmd_hdr.fence_id, cmd->cmd_hdr.type);
+}
+
+static void virgl_write_fence(void *opaque, uint32_t fence)
+{
+ VirtIOGPU *g = opaque;
+ struct virtio_gpu_ctrl_command *cmd, *tmp;
+
+ QTAILQ_FOREACH_SAFE(cmd, &g->fenceq, next, tmp) {
+ /*
+ * the guest can end up emitting fences out of order
+ * so we should check all fenced cmds not just the first one.
+ */
+ if (cmd->cmd_hdr.fence_id > fence) {
+ continue;
+ }
+ trace_virtio_gpu_fence_resp(cmd->cmd_hdr.fence_id);
+ virtio_gpu_ctrl_response_nodata(g, cmd, VIRTIO_GPU_RESP_OK_NODATA);
+ QTAILQ_REMOVE(&g->fenceq, cmd, next);
+ g_free(cmd);
+ g->inflight--;
+ if (virtio_gpu_stats_enabled(g->parent_obj.conf)) {
+ fprintf(stderr, "inflight: %3d (-)\r", g->inflight);
+ }
+ }
+}
+
+static virgl_renderer_gl_context
+virgl_create_context(void *opaque, int scanout_idx,
+ struct virgl_renderer_gl_ctx_param *params)
+{
+ VirtIOGPU *g = opaque;
+ QEMUGLContext ctx;
+ QEMUGLParams qparams;
+
+ qparams.major_ver = params->major_ver;
+ qparams.minor_ver = params->minor_ver;
+
+ ctx = dpy_gl_ctx_create(g->parent_obj.scanout[scanout_idx].con, &qparams);
+ return (virgl_renderer_gl_context)ctx;
+}
+
+static void virgl_destroy_context(void *opaque, virgl_renderer_gl_context ctx)
+{
+ VirtIOGPU *g = opaque;
+ QEMUGLContext qctx = (QEMUGLContext)ctx;
+
+ dpy_gl_ctx_destroy(g->parent_obj.scanout[0].con, qctx);
+}
+
+static int virgl_make_context_current(void *opaque, int scanout_idx,
+ virgl_renderer_gl_context ctx)
+{
+ VirtIOGPU *g = opaque;
+ QEMUGLContext qctx = (QEMUGLContext)ctx;
+
+ return dpy_gl_ctx_make_current(g->parent_obj.scanout[scanout_idx].con,
+ qctx);
+}
+
+static struct virgl_renderer_callbacks virtio_gpu_3d_cbs = {
+ .version = 1,
+ .write_fence = virgl_write_fence,
+ .create_gl_context = virgl_create_context,
+ .destroy_gl_context = virgl_destroy_context,
+ .make_current = virgl_make_context_current,
+};
+
+static void virtio_gpu_print_stats(void *opaque)
+{
+ VirtIOGPU *g = opaque;
+
+ if (g->stats.requests) {
+ fprintf(stderr, "stats: vq req %4d, %3d -- 3D %4d (%5d)\n",
+ g->stats.requests,
+ g->stats.max_inflight,
+ g->stats.req_3d,
+ g->stats.bytes_3d);
+ g->stats.requests = 0;
+ g->stats.max_inflight = 0;
+ g->stats.req_3d = 0;
+ g->stats.bytes_3d = 0;
+ } else {
+ fprintf(stderr, "stats: idle\r");
+ }
+ timer_mod(g->print_stats, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + 1000);
+}
+
+static void virtio_gpu_fence_poll(void *opaque)
+{
+ VirtIOGPU *g = opaque;
+
+ virgl_renderer_poll();
+ virtio_gpu_process_cmdq(g);
+ if (!QTAILQ_EMPTY(&g->cmdq) || !QTAILQ_EMPTY(&g->fenceq)) {
+ timer_mod(g->fence_poll, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + 10);
+ }
+}
+
+void virtio_gpu_virgl_fence_poll(VirtIOGPU *g)
+{
+ virtio_gpu_fence_poll(g);
+}
+
+void virtio_gpu_virgl_reset_scanout(VirtIOGPU *g)
+{
+ int i;
+
+ for (i = 0; i < g->parent_obj.conf.max_outputs; i++) {
+ dpy_gfx_replace_surface(g->parent_obj.scanout[i].con, NULL);
+ dpy_gl_scanout_disable(g->parent_obj.scanout[i].con);
+ }
+}
+
+void virtio_gpu_virgl_reset(VirtIOGPU *g)
+{
+ virgl_renderer_reset();
+}
+
+int virtio_gpu_virgl_init(VirtIOGPU *g)
+{
+ int ret;
+
+ ret = virgl_renderer_init(g, 0, &virtio_gpu_3d_cbs);
+ if (ret != 0) {
+ return ret;
+ }
+
+ g->fence_poll = timer_new_ms(QEMU_CLOCK_VIRTUAL,
+ virtio_gpu_fence_poll, g);
+
+ if (virtio_gpu_stats_enabled(g->parent_obj.conf)) {
+ g->print_stats = timer_new_ms(QEMU_CLOCK_VIRTUAL,
+ virtio_gpu_print_stats, g);
+ timer_mod(g->print_stats, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + 1000);
+ }
+ return 0;
+}
+
+int virtio_gpu_virgl_get_num_capsets(VirtIOGPU *g)
+{
+ uint32_t capset2_max_ver, capset2_max_size;
+ virgl_renderer_get_cap_set(VIRTIO_GPU_CAPSET_VIRGL2,
+ &capset2_max_ver,
+ &capset2_max_size);
+
+ return capset2_max_ver ? 2 : 1;
+}
diff --git a/hw/display/virtio-gpu.c b/hw/display/virtio-gpu.c
new file mode 100644
index 000000000..d78b9700c
--- /dev/null
+++ b/hw/display/virtio-gpu.c
@@ -0,0 +1,1459 @@
+/*
+ * Virtio GPU Device
+ *
+ * Copyright Red Hat, Inc. 2013-2014
+ *
+ * Authors:
+ * Dave Airlie <airlied@redhat.com>
+ * Gerd Hoffmann <kraxel@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/units.h"
+#include "qemu/iov.h"
+#include "ui/console.h"
+#include "trace.h"
+#include "sysemu/dma.h"
+#include "sysemu/sysemu.h"
+#include "hw/virtio/virtio.h"
+#include "migration/qemu-file-types.h"
+#include "hw/virtio/virtio-gpu.h"
+#include "hw/virtio/virtio-gpu-bswap.h"
+#include "hw/virtio/virtio-gpu-pixman.h"
+#include "hw/virtio/virtio-bus.h"
+#include "hw/display/edid.h"
+#include "hw/qdev-properties.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qapi/error.h"
+#include "qemu/error-report.h"
+
+#define VIRTIO_GPU_VM_VERSION 1
+
+static struct virtio_gpu_simple_resource*
+virtio_gpu_find_resource(VirtIOGPU *g, uint32_t resource_id);
+static struct virtio_gpu_simple_resource *
+virtio_gpu_find_check_resource(VirtIOGPU *g, uint32_t resource_id,
+ bool require_backing,
+ const char *caller, uint32_t *error);
+
+static void virtio_gpu_cleanup_mapping(VirtIOGPU *g,
+ struct virtio_gpu_simple_resource *res);
+
+void virtio_gpu_update_cursor_data(VirtIOGPU *g,
+ struct virtio_gpu_scanout *s,
+ uint32_t resource_id)
+{
+ struct virtio_gpu_simple_resource *res;
+ uint32_t pixels;
+ void *data;
+
+ res = virtio_gpu_find_check_resource(g, resource_id, false,
+ __func__, NULL);
+ if (!res) {
+ return;
+ }
+
+ if (res->blob_size) {
+ if (res->blob_size < (s->current_cursor->width *
+ s->current_cursor->height * 4)) {
+ return;
+ }
+ data = res->blob;
+ } else {
+ if (pixman_image_get_width(res->image) != s->current_cursor->width ||
+ pixman_image_get_height(res->image) != s->current_cursor->height) {
+ return;
+ }
+ data = pixman_image_get_data(res->image);
+ }
+
+ pixels = s->current_cursor->width * s->current_cursor->height;
+ memcpy(s->current_cursor->data, data,
+ pixels * sizeof(uint32_t));
+}
+
+static void update_cursor(VirtIOGPU *g, struct virtio_gpu_update_cursor *cursor)
+{
+ struct virtio_gpu_scanout *s;
+ VirtIOGPUClass *vgc = VIRTIO_GPU_GET_CLASS(g);
+ bool move = cursor->hdr.type == VIRTIO_GPU_CMD_MOVE_CURSOR;
+
+ if (cursor->pos.scanout_id >= g->parent_obj.conf.max_outputs) {
+ return;
+ }
+ s = &g->parent_obj.scanout[cursor->pos.scanout_id];
+
+ trace_virtio_gpu_update_cursor(cursor->pos.scanout_id,
+ cursor->pos.x,
+ cursor->pos.y,
+ move ? "move" : "update",
+ cursor->resource_id);
+
+ if (!move) {
+ if (!s->current_cursor) {
+ s->current_cursor = cursor_alloc(64, 64);
+ }
+
+ s->current_cursor->hot_x = cursor->hot_x;
+ s->current_cursor->hot_y = cursor->hot_y;
+
+ if (cursor->resource_id > 0) {
+ vgc->update_cursor_data(g, s, cursor->resource_id);
+ }
+ dpy_cursor_define(s->con, s->current_cursor);
+
+ s->cursor = *cursor;
+ } else {
+ s->cursor.pos.x = cursor->pos.x;
+ s->cursor.pos.y = cursor->pos.y;
+ }
+ dpy_mouse_set(s->con, cursor->pos.x, cursor->pos.y,
+ cursor->resource_id ? 1 : 0);
+}
+
+static struct virtio_gpu_simple_resource *
+virtio_gpu_find_resource(VirtIOGPU *g, uint32_t resource_id)
+{
+ struct virtio_gpu_simple_resource *res;
+
+ QTAILQ_FOREACH(res, &g->reslist, next) {
+ if (res->resource_id == resource_id) {
+ return res;
+ }
+ }
+ return NULL;
+}
+
+static struct virtio_gpu_simple_resource *
+virtio_gpu_find_check_resource(VirtIOGPU *g, uint32_t resource_id,
+ bool require_backing,
+ const char *caller, uint32_t *error)
+{
+ struct virtio_gpu_simple_resource *res;
+
+ res = virtio_gpu_find_resource(g, resource_id);
+ if (!res) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid resource specified %d\n",
+ caller, resource_id);
+ if (error) {
+ *error = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID;
+ }
+ return NULL;
+ }
+
+ if (require_backing) {
+ if (!res->iov || (!res->image && !res->blob)) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: no backing storage %d\n",
+ caller, resource_id);
+ if (error) {
+ *error = VIRTIO_GPU_RESP_ERR_UNSPEC;
+ }
+ return NULL;
+ }
+ }
+
+ return res;
+}
+
+void virtio_gpu_ctrl_response(VirtIOGPU *g,
+ struct virtio_gpu_ctrl_command *cmd,
+ struct virtio_gpu_ctrl_hdr *resp,
+ size_t resp_len)
+{
+ size_t s;
+
+ if (cmd->cmd_hdr.flags & VIRTIO_GPU_FLAG_FENCE) {
+ resp->flags |= VIRTIO_GPU_FLAG_FENCE;
+ resp->fence_id = cmd->cmd_hdr.fence_id;
+ resp->ctx_id = cmd->cmd_hdr.ctx_id;
+ }
+ virtio_gpu_ctrl_hdr_bswap(resp);
+ s = iov_from_buf(cmd->elem.in_sg, cmd->elem.in_num, 0, resp, resp_len);
+ if (s != resp_len) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: response size incorrect %zu vs %zu\n",
+ __func__, s, resp_len);
+ }
+ virtqueue_push(cmd->vq, &cmd->elem, s);
+ virtio_notify(VIRTIO_DEVICE(g), cmd->vq);
+ cmd->finished = true;
+}
+
+void virtio_gpu_ctrl_response_nodata(VirtIOGPU *g,
+ struct virtio_gpu_ctrl_command *cmd,
+ enum virtio_gpu_ctrl_type type)
+{
+ struct virtio_gpu_ctrl_hdr resp;
+
+ memset(&resp, 0, sizeof(resp));
+ resp.type = type;
+ virtio_gpu_ctrl_response(g, cmd, &resp, sizeof(resp));
+}
+
+void virtio_gpu_get_display_info(VirtIOGPU *g,
+ struct virtio_gpu_ctrl_command *cmd)
+{
+ struct virtio_gpu_resp_display_info display_info;
+
+ trace_virtio_gpu_cmd_get_display_info();
+ memset(&display_info, 0, sizeof(display_info));
+ display_info.hdr.type = VIRTIO_GPU_RESP_OK_DISPLAY_INFO;
+ virtio_gpu_base_fill_display_info(VIRTIO_GPU_BASE(g), &display_info);
+ virtio_gpu_ctrl_response(g, cmd, &display_info.hdr,
+ sizeof(display_info));
+}
+
+static void
+virtio_gpu_generate_edid(VirtIOGPU *g, int scanout,
+ struct virtio_gpu_resp_edid *edid)
+{
+ VirtIOGPUBase *b = VIRTIO_GPU_BASE(g);
+ qemu_edid_info info = {
+ .width_mm = b->req_state[scanout].width_mm,
+ .height_mm = b->req_state[scanout].height_mm,
+ .prefx = b->req_state[scanout].width,
+ .prefy = b->req_state[scanout].height,
+ };
+
+ edid->size = cpu_to_le32(sizeof(edid->edid));
+ qemu_edid_generate(edid->edid, sizeof(edid->edid), &info);
+}
+
+void virtio_gpu_get_edid(VirtIOGPU *g,
+ struct virtio_gpu_ctrl_command *cmd)
+{
+ struct virtio_gpu_resp_edid edid;
+ struct virtio_gpu_cmd_get_edid get_edid;
+ VirtIOGPUBase *b = VIRTIO_GPU_BASE(g);
+
+ VIRTIO_GPU_FILL_CMD(get_edid);
+ virtio_gpu_bswap_32(&get_edid, sizeof(get_edid));
+
+ if (get_edid.scanout >= b->conf.max_outputs) {
+ cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
+ return;
+ }
+
+ trace_virtio_gpu_cmd_get_edid(get_edid.scanout);
+ memset(&edid, 0, sizeof(edid));
+ edid.hdr.type = VIRTIO_GPU_RESP_OK_EDID;
+ virtio_gpu_generate_edid(g, get_edid.scanout, &edid);
+ virtio_gpu_ctrl_response(g, cmd, &edid.hdr, sizeof(edid));
+}
+
+static uint32_t calc_image_hostmem(pixman_format_code_t pformat,
+ uint32_t width, uint32_t height)
+{
+ /* Copied from pixman/pixman-bits-image.c, skip integer overflow check.
+ * pixman_image_create_bits will fail in case it overflow.
+ */
+
+ int bpp = PIXMAN_FORMAT_BPP(pformat);
+ int stride = ((width * bpp + 0x1f) >> 5) * sizeof(uint32_t);
+ return height * stride;
+}
+
+static void virtio_gpu_resource_create_2d(VirtIOGPU *g,
+ struct virtio_gpu_ctrl_command *cmd)
+{
+ pixman_format_code_t pformat;
+ struct virtio_gpu_simple_resource *res;
+ struct virtio_gpu_resource_create_2d c2d;
+
+ VIRTIO_GPU_FILL_CMD(c2d);
+ virtio_gpu_bswap_32(&c2d, sizeof(c2d));
+ trace_virtio_gpu_cmd_res_create_2d(c2d.resource_id, c2d.format,
+ c2d.width, c2d.height);
+
+ if (c2d.resource_id == 0) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: resource id 0 is not allowed\n",
+ __func__);
+ cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID;
+ return;
+ }
+
+ res = virtio_gpu_find_resource(g, c2d.resource_id);
+ if (res) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: resource already exists %d\n",
+ __func__, c2d.resource_id);
+ cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID;
+ return;
+ }
+
+ res = g_new0(struct virtio_gpu_simple_resource, 1);
+
+ res->width = c2d.width;
+ res->height = c2d.height;
+ res->format = c2d.format;
+ res->resource_id = c2d.resource_id;
+
+ pformat = virtio_gpu_get_pixman_format(c2d.format);
+ if (!pformat) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: host couldn't handle guest format %d\n",
+ __func__, c2d.format);
+ g_free(res);
+ cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
+ return;
+ }
+
+ res->hostmem = calc_image_hostmem(pformat, c2d.width, c2d.height);
+ if (res->hostmem + g->hostmem < g->conf_max_hostmem) {
+ res->image = pixman_image_create_bits(pformat,
+ c2d.width,
+ c2d.height,
+ NULL, 0);
+ }
+
+ if (!res->image) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: resource creation failed %d %d %d\n",
+ __func__, c2d.resource_id, c2d.width, c2d.height);
+ g_free(res);
+ cmd->error = VIRTIO_GPU_RESP_ERR_OUT_OF_MEMORY;
+ return;
+ }
+
+ QTAILQ_INSERT_HEAD(&g->reslist, res, next);
+ g->hostmem += res->hostmem;
+}
+
+static void virtio_gpu_resource_create_blob(VirtIOGPU *g,
+ struct virtio_gpu_ctrl_command *cmd)
+{
+ struct virtio_gpu_simple_resource *res;
+ struct virtio_gpu_resource_create_blob cblob;
+ int ret;
+
+ VIRTIO_GPU_FILL_CMD(cblob);
+ virtio_gpu_create_blob_bswap(&cblob);
+ trace_virtio_gpu_cmd_res_create_blob(cblob.resource_id, cblob.size);
+
+ if (cblob.resource_id == 0) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: resource id 0 is not allowed\n",
+ __func__);
+ cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID;
+ return;
+ }
+
+ if (cblob.blob_mem != VIRTIO_GPU_BLOB_MEM_GUEST &&
+ cblob.blob_flags != VIRTIO_GPU_BLOB_FLAG_USE_SHAREABLE) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid memory type\n",
+ __func__);
+ cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
+ return;
+ }
+
+ if (virtio_gpu_find_resource(g, cblob.resource_id)) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: resource already exists %d\n",
+ __func__, cblob.resource_id);
+ cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID;
+ return;
+ }
+
+ res = g_new0(struct virtio_gpu_simple_resource, 1);
+ res->resource_id = cblob.resource_id;
+ res->blob_size = cblob.size;
+
+ ret = virtio_gpu_create_mapping_iov(g, cblob.nr_entries, sizeof(cblob),
+ cmd, &res->addrs, &res->iov,
+ &res->iov_cnt);
+ if (ret != 0) {
+ cmd->error = VIRTIO_GPU_RESP_ERR_UNSPEC;
+ g_free(res);
+ return;
+ }
+
+ virtio_gpu_init_udmabuf(res);
+ QTAILQ_INSERT_HEAD(&g->reslist, res, next);
+}
+
+static void virtio_gpu_disable_scanout(VirtIOGPU *g, int scanout_id)
+{
+ struct virtio_gpu_scanout *scanout = &g->parent_obj.scanout[scanout_id];
+ struct virtio_gpu_simple_resource *res;
+
+ if (scanout->resource_id == 0) {
+ return;
+ }
+
+ res = virtio_gpu_find_resource(g, scanout->resource_id);
+ if (res) {
+ res->scanout_bitmask &= ~(1 << scanout_id);
+ }
+
+ dpy_gfx_replace_surface(scanout->con, NULL);
+ scanout->resource_id = 0;
+ scanout->ds = NULL;
+ scanout->width = 0;
+ scanout->height = 0;
+}
+
+static void virtio_gpu_resource_destroy(VirtIOGPU *g,
+ struct virtio_gpu_simple_resource *res)
+{
+ int i;
+
+ if (res->scanout_bitmask) {
+ for (i = 0; i < g->parent_obj.conf.max_outputs; i++) {
+ if (res->scanout_bitmask & (1 << i)) {
+ virtio_gpu_disable_scanout(g, i);
+ }
+ }
+ }
+
+ qemu_pixman_image_unref(res->image);
+ virtio_gpu_cleanup_mapping(g, res);
+ QTAILQ_REMOVE(&g->reslist, res, next);
+ g->hostmem -= res->hostmem;
+ g_free(res);
+}
+
+static void virtio_gpu_resource_unref(VirtIOGPU *g,
+ struct virtio_gpu_ctrl_command *cmd)
+{
+ struct virtio_gpu_simple_resource *res;
+ struct virtio_gpu_resource_unref unref;
+
+ VIRTIO_GPU_FILL_CMD(unref);
+ virtio_gpu_bswap_32(&unref, sizeof(unref));
+ trace_virtio_gpu_cmd_res_unref(unref.resource_id);
+
+ res = virtio_gpu_find_resource(g, unref.resource_id);
+ if (!res) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: illegal resource specified %d\n",
+ __func__, unref.resource_id);
+ cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID;
+ return;
+ }
+ virtio_gpu_resource_destroy(g, res);
+}
+
+static void virtio_gpu_transfer_to_host_2d(VirtIOGPU *g,
+ struct virtio_gpu_ctrl_command *cmd)
+{
+ struct virtio_gpu_simple_resource *res;
+ int h;
+ uint32_t src_offset, dst_offset, stride;
+ int bpp;
+ pixman_format_code_t format;
+ struct virtio_gpu_transfer_to_host_2d t2d;
+
+ VIRTIO_GPU_FILL_CMD(t2d);
+ virtio_gpu_t2d_bswap(&t2d);
+ trace_virtio_gpu_cmd_res_xfer_toh_2d(t2d.resource_id);
+
+ res = virtio_gpu_find_check_resource(g, t2d.resource_id, true,
+ __func__, &cmd->error);
+ if (!res || res->blob) {
+ return;
+ }
+
+ if (t2d.r.x > res->width ||
+ t2d.r.y > res->height ||
+ t2d.r.width > res->width ||
+ t2d.r.height > res->height ||
+ t2d.r.x + t2d.r.width > res->width ||
+ t2d.r.y + t2d.r.height > res->height) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: transfer bounds outside resource"
+ " bounds for resource %d: %d %d %d %d vs %d %d\n",
+ __func__, t2d.resource_id, t2d.r.x, t2d.r.y,
+ t2d.r.width, t2d.r.height, res->width, res->height);
+ cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
+ return;
+ }
+
+ format = pixman_image_get_format(res->image);
+ bpp = DIV_ROUND_UP(PIXMAN_FORMAT_BPP(format), 8);
+ stride = pixman_image_get_stride(res->image);
+
+ if (t2d.offset || t2d.r.x || t2d.r.y ||
+ t2d.r.width != pixman_image_get_width(res->image)) {
+ void *img_data = pixman_image_get_data(res->image);
+ for (h = 0; h < t2d.r.height; h++) {
+ src_offset = t2d.offset + stride * h;
+ dst_offset = (t2d.r.y + h) * stride + (t2d.r.x * bpp);
+
+ iov_to_buf(res->iov, res->iov_cnt, src_offset,
+ (uint8_t *)img_data
+ + dst_offset, t2d.r.width * bpp);
+ }
+ } else {
+ iov_to_buf(res->iov, res->iov_cnt, 0,
+ pixman_image_get_data(res->image),
+ pixman_image_get_stride(res->image)
+ * pixman_image_get_height(res->image));
+ }
+}
+
+static void virtio_gpu_resource_flush(VirtIOGPU *g,
+ struct virtio_gpu_ctrl_command *cmd)
+{
+ struct virtio_gpu_simple_resource *res;
+ struct virtio_gpu_resource_flush rf;
+ struct virtio_gpu_scanout *scanout;
+ pixman_region16_t flush_region;
+ int i;
+
+ VIRTIO_GPU_FILL_CMD(rf);
+ virtio_gpu_bswap_32(&rf, sizeof(rf));
+ trace_virtio_gpu_cmd_res_flush(rf.resource_id,
+ rf.r.width, rf.r.height, rf.r.x, rf.r.y);
+
+ res = virtio_gpu_find_check_resource(g, rf.resource_id, false,
+ __func__, &cmd->error);
+ if (!res) {
+ return;
+ }
+
+ if (res->blob) {
+ for (i = 0; i < g->parent_obj.conf.max_outputs; i++) {
+ scanout = &g->parent_obj.scanout[i];
+ if (scanout->resource_id == res->resource_id &&
+ console_has_gl(scanout->con)) {
+ dpy_gl_update(scanout->con, 0, 0, scanout->width,
+ scanout->height);
+ }
+ }
+ return;
+ }
+
+ if (!res->blob &&
+ (rf.r.x > res->width ||
+ rf.r.y > res->height ||
+ rf.r.width > res->width ||
+ rf.r.height > res->height ||
+ rf.r.x + rf.r.width > res->width ||
+ rf.r.y + rf.r.height > res->height)) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: flush bounds outside resource"
+ " bounds for resource %d: %d %d %d %d vs %d %d\n",
+ __func__, rf.resource_id, rf.r.x, rf.r.y,
+ rf.r.width, rf.r.height, res->width, res->height);
+ cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
+ return;
+ }
+
+ pixman_region_init_rect(&flush_region,
+ rf.r.x, rf.r.y, rf.r.width, rf.r.height);
+ for (i = 0; i < g->parent_obj.conf.max_outputs; i++) {
+ pixman_region16_t region, finalregion;
+ pixman_box16_t *extents;
+
+ if (!(res->scanout_bitmask & (1 << i))) {
+ continue;
+ }
+ scanout = &g->parent_obj.scanout[i];
+
+ pixman_region_init(&finalregion);
+ pixman_region_init_rect(&region, scanout->x, scanout->y,
+ scanout->width, scanout->height);
+
+ pixman_region_intersect(&finalregion, &flush_region, &region);
+ pixman_region_translate(&finalregion, -scanout->x, -scanout->y);
+ extents = pixman_region_extents(&finalregion);
+ /* work out the area we need to update for each console */
+ dpy_gfx_update(g->parent_obj.scanout[i].con,
+ extents->x1, extents->y1,
+ extents->x2 - extents->x1,
+ extents->y2 - extents->y1);
+
+ pixman_region_fini(&region);
+ pixman_region_fini(&finalregion);
+ }
+ pixman_region_fini(&flush_region);
+}
+
+static void virtio_unref_resource(pixman_image_t *image, void *data)
+{
+ pixman_image_unref(data);
+}
+
+static void virtio_gpu_update_scanout(VirtIOGPU *g,
+ uint32_t scanout_id,
+ struct virtio_gpu_simple_resource *res,
+ struct virtio_gpu_rect *r)
+{
+ struct virtio_gpu_simple_resource *ores;
+ struct virtio_gpu_scanout *scanout;
+
+ scanout = &g->parent_obj.scanout[scanout_id];
+ ores = virtio_gpu_find_resource(g, scanout->resource_id);
+ if (ores) {
+ ores->scanout_bitmask &= ~(1 << scanout_id);
+ }
+
+ res->scanout_bitmask |= (1 << scanout_id);
+ scanout->resource_id = res->resource_id;
+ scanout->x = r->x;
+ scanout->y = r->y;
+ scanout->width = r->width;
+ scanout->height = r->height;
+}
+
+static void virtio_gpu_do_set_scanout(VirtIOGPU *g,
+ uint32_t scanout_id,
+ struct virtio_gpu_framebuffer *fb,
+ struct virtio_gpu_simple_resource *res,
+ struct virtio_gpu_rect *r,
+ uint32_t *error)
+{
+ struct virtio_gpu_scanout *scanout;
+ uint8_t *data;
+
+ scanout = &g->parent_obj.scanout[scanout_id];
+
+ if (r->x > fb->width ||
+ r->y > fb->height ||
+ r->width < 16 ||
+ r->height < 16 ||
+ r->width > fb->width ||
+ r->height > fb->height ||
+ r->x + r->width > fb->width ||
+ r->y + r->height > fb->height) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: illegal scanout %d bounds for"
+ " resource %d, rect (%d,%d)+%d,%d, fb %d %d\n",
+ __func__, scanout_id, res->resource_id,
+ r->x, r->y, r->width, r->height,
+ fb->width, fb->height);
+ *error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
+ return;
+ }
+
+ g->parent_obj.enable = 1;
+
+ if (res->blob) {
+ if (console_has_gl(scanout->con)) {
+ if (!virtio_gpu_update_dmabuf(g, scanout_id, res, fb, r)) {
+ virtio_gpu_update_scanout(g, scanout_id, res, r);
+ return;
+ }
+ }
+
+ data = res->blob;
+ } else {
+ data = (uint8_t *)pixman_image_get_data(res->image);
+ }
+
+ /* create a surface for this scanout */
+ if ((res->blob && !console_has_gl(scanout->con)) ||
+ !scanout->ds ||
+ surface_data(scanout->ds) != data + fb->offset ||
+ scanout->width != r->width ||
+ scanout->height != r->height) {
+ pixman_image_t *rect;
+ void *ptr = data + fb->offset;
+ rect = pixman_image_create_bits(fb->format, r->width, r->height,
+ ptr, fb->stride);
+
+ if (res->image) {
+ pixman_image_ref(res->image);
+ pixman_image_set_destroy_function(rect, virtio_unref_resource,
+ res->image);
+ }
+
+ /* realloc the surface ptr */
+ scanout->ds = qemu_create_displaysurface_pixman(rect);
+ if (!scanout->ds) {
+ *error = VIRTIO_GPU_RESP_ERR_UNSPEC;
+ return;
+ }
+
+ pixman_image_unref(rect);
+ dpy_gfx_replace_surface(g->parent_obj.scanout[scanout_id].con,
+ scanout->ds);
+ }
+
+ virtio_gpu_update_scanout(g, scanout_id, res, r);
+}
+
+static void virtio_gpu_set_scanout(VirtIOGPU *g,
+ struct virtio_gpu_ctrl_command *cmd)
+{
+ struct virtio_gpu_simple_resource *res;
+ struct virtio_gpu_framebuffer fb = { 0 };
+ struct virtio_gpu_set_scanout ss;
+
+ VIRTIO_GPU_FILL_CMD(ss);
+ virtio_gpu_bswap_32(&ss, sizeof(ss));
+ trace_virtio_gpu_cmd_set_scanout(ss.scanout_id, ss.resource_id,
+ ss.r.width, ss.r.height, ss.r.x, ss.r.y);
+
+ if (ss.scanout_id >= g->parent_obj.conf.max_outputs) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: illegal scanout id specified %d",
+ __func__, ss.scanout_id);
+ cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_SCANOUT_ID;
+ return;
+ }
+
+ if (ss.resource_id == 0) {
+ virtio_gpu_disable_scanout(g, ss.scanout_id);
+ return;
+ }
+
+ res = virtio_gpu_find_check_resource(g, ss.resource_id, true,
+ __func__, &cmd->error);
+ if (!res) {
+ return;
+ }
+
+ fb.format = pixman_image_get_format(res->image);
+ fb.bytes_pp = DIV_ROUND_UP(PIXMAN_FORMAT_BPP(fb.format), 8);
+ fb.width = pixman_image_get_width(res->image);
+ fb.height = pixman_image_get_height(res->image);
+ fb.stride = pixman_image_get_stride(res->image);
+ fb.offset = ss.r.x * fb.bytes_pp + ss.r.y * fb.stride;
+
+ virtio_gpu_do_set_scanout(g, ss.scanout_id,
+ &fb, res, &ss.r, &cmd->error);
+}
+
+static void virtio_gpu_set_scanout_blob(VirtIOGPU *g,
+ struct virtio_gpu_ctrl_command *cmd)
+{
+ struct virtio_gpu_simple_resource *res;
+ struct virtio_gpu_framebuffer fb = { 0 };
+ struct virtio_gpu_set_scanout_blob ss;
+ uint64_t fbend;
+
+ VIRTIO_GPU_FILL_CMD(ss);
+ virtio_gpu_scanout_blob_bswap(&ss);
+ trace_virtio_gpu_cmd_set_scanout_blob(ss.scanout_id, ss.resource_id,
+ ss.r.width, ss.r.height, ss.r.x,
+ ss.r.y);
+
+ if (ss.scanout_id >= g->parent_obj.conf.max_outputs) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: illegal scanout id specified %d",
+ __func__, ss.scanout_id);
+ cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_SCANOUT_ID;
+ return;
+ }
+
+ if (ss.resource_id == 0) {
+ virtio_gpu_disable_scanout(g, ss.scanout_id);
+ return;
+ }
+
+ res = virtio_gpu_find_check_resource(g, ss.resource_id, true,
+ __func__, &cmd->error);
+ if (!res) {
+ return;
+ }
+
+ fb.format = virtio_gpu_get_pixman_format(ss.format);
+ if (!fb.format) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: host couldn't handle guest format %d\n",
+ __func__, ss.format);
+ cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
+ return;
+ }
+
+ fb.bytes_pp = DIV_ROUND_UP(PIXMAN_FORMAT_BPP(fb.format), 8);
+ fb.width = ss.width;
+ fb.height = ss.height;
+ fb.stride = ss.strides[0];
+ fb.offset = ss.offsets[0] + ss.r.x * fb.bytes_pp + ss.r.y * fb.stride;
+
+ fbend = fb.offset;
+ fbend += fb.stride * (ss.r.height - 1);
+ fbend += fb.bytes_pp * ss.r.width;
+ if (fbend > res->blob_size) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: fb end out of range\n",
+ __func__);
+ cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
+ return;
+ }
+
+ virtio_gpu_do_set_scanout(g, ss.scanout_id,
+ &fb, res, &ss.r, &cmd->error);
+}
+
+int virtio_gpu_create_mapping_iov(VirtIOGPU *g,
+ uint32_t nr_entries, uint32_t offset,
+ struct virtio_gpu_ctrl_command *cmd,
+ uint64_t **addr, struct iovec **iov,
+ uint32_t *niov)
+{
+ struct virtio_gpu_mem_entry *ents;
+ size_t esize, s;
+ int e, v;
+
+ if (nr_entries > 16384) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: nr_entries is too big (%d > 16384)\n",
+ __func__, nr_entries);
+ return -1;
+ }
+
+ esize = sizeof(*ents) * nr_entries;
+ ents = g_malloc(esize);
+ s = iov_to_buf(cmd->elem.out_sg, cmd->elem.out_num,
+ offset, ents, esize);
+ if (s != esize) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: command data size incorrect %zu vs %zu\n",
+ __func__, s, esize);
+ g_free(ents);
+ return -1;
+ }
+
+ *iov = NULL;
+ if (addr) {
+ *addr = NULL;
+ }
+ for (e = 0, v = 0; e < nr_entries; e++) {
+ uint64_t a = le64_to_cpu(ents[e].addr);
+ uint32_t l = le32_to_cpu(ents[e].length);
+ hwaddr len;
+ void *map;
+
+ do {
+ len = l;
+ map = dma_memory_map(VIRTIO_DEVICE(g)->dma_as,
+ a, &len, DMA_DIRECTION_TO_DEVICE);
+ if (!map) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: failed to map MMIO memory for"
+ " element %d\n", __func__, e);
+ virtio_gpu_cleanup_mapping_iov(g, *iov, v);
+ g_free(ents);
+ *iov = NULL;
+ if (addr) {
+ g_free(*addr);
+ *addr = NULL;
+ }
+ return -1;
+ }
+
+ if (!(v % 16)) {
+ *iov = g_realloc(*iov, sizeof(struct iovec) * (v + 16));
+ if (addr) {
+ *addr = g_realloc(*addr, sizeof(uint64_t) * (v + 16));
+ }
+ }
+ (*iov)[v].iov_base = map;
+ (*iov)[v].iov_len = len;
+ if (addr) {
+ (*addr)[v] = a;
+ }
+
+ a += len;
+ l -= len;
+ v += 1;
+ } while (l > 0);
+ }
+ *niov = v;
+
+ g_free(ents);
+ return 0;
+}
+
+void virtio_gpu_cleanup_mapping_iov(VirtIOGPU *g,
+ struct iovec *iov, uint32_t count)
+{
+ int i;
+
+ for (i = 0; i < count; i++) {
+ dma_memory_unmap(VIRTIO_DEVICE(g)->dma_as,
+ iov[i].iov_base, iov[i].iov_len,
+ DMA_DIRECTION_TO_DEVICE,
+ iov[i].iov_len);
+ }
+ g_free(iov);
+}
+
+static void virtio_gpu_cleanup_mapping(VirtIOGPU *g,
+ struct virtio_gpu_simple_resource *res)
+{
+ virtio_gpu_cleanup_mapping_iov(g, res->iov, res->iov_cnt);
+ res->iov = NULL;
+ res->iov_cnt = 0;
+ g_free(res->addrs);
+ res->addrs = NULL;
+
+ if (res->blob) {
+ virtio_gpu_fini_udmabuf(res);
+ }
+}
+
+static void
+virtio_gpu_resource_attach_backing(VirtIOGPU *g,
+ struct virtio_gpu_ctrl_command *cmd)
+{
+ struct virtio_gpu_simple_resource *res;
+ struct virtio_gpu_resource_attach_backing ab;
+ int ret;
+
+ VIRTIO_GPU_FILL_CMD(ab);
+ virtio_gpu_bswap_32(&ab, sizeof(ab));
+ trace_virtio_gpu_cmd_res_back_attach(ab.resource_id);
+
+ res = virtio_gpu_find_resource(g, ab.resource_id);
+ if (!res) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: illegal resource specified %d\n",
+ __func__, ab.resource_id);
+ cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_RESOURCE_ID;
+ return;
+ }
+
+ if (res->iov) {
+ cmd->error = VIRTIO_GPU_RESP_ERR_UNSPEC;
+ return;
+ }
+
+ ret = virtio_gpu_create_mapping_iov(g, ab.nr_entries, sizeof(ab), cmd,
+ &res->addrs, &res->iov, &res->iov_cnt);
+ if (ret != 0) {
+ cmd->error = VIRTIO_GPU_RESP_ERR_UNSPEC;
+ return;
+ }
+}
+
+static void
+virtio_gpu_resource_detach_backing(VirtIOGPU *g,
+ struct virtio_gpu_ctrl_command *cmd)
+{
+ struct virtio_gpu_simple_resource *res;
+ struct virtio_gpu_resource_detach_backing detach;
+
+ VIRTIO_GPU_FILL_CMD(detach);
+ virtio_gpu_bswap_32(&detach, sizeof(detach));
+ trace_virtio_gpu_cmd_res_back_detach(detach.resource_id);
+
+ res = virtio_gpu_find_check_resource(g, detach.resource_id, true,
+ __func__, &cmd->error);
+ if (!res) {
+ return;
+ }
+ virtio_gpu_cleanup_mapping(g, res);
+}
+
+void virtio_gpu_simple_process_cmd(VirtIOGPU *g,
+ struct virtio_gpu_ctrl_command *cmd)
+{
+ VIRTIO_GPU_FILL_CMD(cmd->cmd_hdr);
+ virtio_gpu_ctrl_hdr_bswap(&cmd->cmd_hdr);
+
+ switch (cmd->cmd_hdr.type) {
+ case VIRTIO_GPU_CMD_GET_DISPLAY_INFO:
+ virtio_gpu_get_display_info(g, cmd);
+ break;
+ case VIRTIO_GPU_CMD_GET_EDID:
+ virtio_gpu_get_edid(g, cmd);
+ break;
+ case VIRTIO_GPU_CMD_RESOURCE_CREATE_2D:
+ virtio_gpu_resource_create_2d(g, cmd);
+ break;
+ case VIRTIO_GPU_CMD_RESOURCE_CREATE_BLOB:
+ if (!virtio_gpu_blob_enabled(g->parent_obj.conf)) {
+ cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
+ break;
+ }
+ virtio_gpu_resource_create_blob(g, cmd);
+ break;
+ case VIRTIO_GPU_CMD_RESOURCE_UNREF:
+ virtio_gpu_resource_unref(g, cmd);
+ break;
+ case VIRTIO_GPU_CMD_RESOURCE_FLUSH:
+ virtio_gpu_resource_flush(g, cmd);
+ break;
+ case VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D:
+ virtio_gpu_transfer_to_host_2d(g, cmd);
+ break;
+ case VIRTIO_GPU_CMD_SET_SCANOUT:
+ virtio_gpu_set_scanout(g, cmd);
+ break;
+ case VIRTIO_GPU_CMD_SET_SCANOUT_BLOB:
+ if (!virtio_gpu_blob_enabled(g->parent_obj.conf)) {
+ cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
+ break;
+ }
+ virtio_gpu_set_scanout_blob(g, cmd);
+ break;
+ case VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING:
+ virtio_gpu_resource_attach_backing(g, cmd);
+ break;
+ case VIRTIO_GPU_CMD_RESOURCE_DETACH_BACKING:
+ virtio_gpu_resource_detach_backing(g, cmd);
+ break;
+ default:
+ cmd->error = VIRTIO_GPU_RESP_ERR_UNSPEC;
+ break;
+ }
+ if (!cmd->finished) {
+ if (!g->parent_obj.renderer_blocked) {
+ virtio_gpu_ctrl_response_nodata(g, cmd, cmd->error ? cmd->error :
+ VIRTIO_GPU_RESP_OK_NODATA);
+ }
+ }
+}
+
+static void virtio_gpu_handle_ctrl_cb(VirtIODevice *vdev, VirtQueue *vq)
+{
+ VirtIOGPU *g = VIRTIO_GPU(vdev);
+ qemu_bh_schedule(g->ctrl_bh);
+}
+
+static void virtio_gpu_handle_cursor_cb(VirtIODevice *vdev, VirtQueue *vq)
+{
+ VirtIOGPU *g = VIRTIO_GPU(vdev);
+ qemu_bh_schedule(g->cursor_bh);
+}
+
+void virtio_gpu_process_cmdq(VirtIOGPU *g)
+{
+ struct virtio_gpu_ctrl_command *cmd;
+ VirtIOGPUClass *vgc = VIRTIO_GPU_GET_CLASS(g);
+
+ if (g->processing_cmdq) {
+ return;
+ }
+ g->processing_cmdq = true;
+ while (!QTAILQ_EMPTY(&g->cmdq)) {
+ cmd = QTAILQ_FIRST(&g->cmdq);
+
+ if (g->parent_obj.renderer_blocked) {
+ break;
+ }
+
+ /* process command */
+ vgc->process_cmd(g, cmd);
+
+ QTAILQ_REMOVE(&g->cmdq, cmd, next);
+ if (virtio_gpu_stats_enabled(g->parent_obj.conf)) {
+ g->stats.requests++;
+ }
+
+ if (!cmd->finished) {
+ QTAILQ_INSERT_TAIL(&g->fenceq, cmd, next);
+ g->inflight++;
+ if (virtio_gpu_stats_enabled(g->parent_obj.conf)) {
+ if (g->stats.max_inflight < g->inflight) {
+ g->stats.max_inflight = g->inflight;
+ }
+ fprintf(stderr, "inflight: %3d (+)\r", g->inflight);
+ }
+ } else {
+ g_free(cmd);
+ }
+ }
+ g->processing_cmdq = false;
+}
+
+static void virtio_gpu_process_fenceq(VirtIOGPU *g)
+{
+ struct virtio_gpu_ctrl_command *cmd, *tmp;
+
+ QTAILQ_FOREACH_SAFE(cmd, &g->fenceq, next, tmp) {
+ trace_virtio_gpu_fence_resp(cmd->cmd_hdr.fence_id);
+ virtio_gpu_ctrl_response_nodata(g, cmd, VIRTIO_GPU_RESP_OK_NODATA);
+ QTAILQ_REMOVE(&g->fenceq, cmd, next);
+ g_free(cmd);
+ g->inflight--;
+ if (virtio_gpu_stats_enabled(g->parent_obj.conf)) {
+ fprintf(stderr, "inflight: %3d (-)\r", g->inflight);
+ }
+ }
+}
+
+static void virtio_gpu_handle_gl_flushed(VirtIOGPUBase *b)
+{
+ VirtIOGPU *g = container_of(b, VirtIOGPU, parent_obj);
+
+ virtio_gpu_process_fenceq(g);
+ virtio_gpu_process_cmdq(g);
+}
+
+static void virtio_gpu_handle_ctrl(VirtIODevice *vdev, VirtQueue *vq)
+{
+ VirtIOGPU *g = VIRTIO_GPU(vdev);
+ struct virtio_gpu_ctrl_command *cmd;
+
+ if (!virtio_queue_ready(vq)) {
+ return;
+ }
+
+ cmd = virtqueue_pop(vq, sizeof(struct virtio_gpu_ctrl_command));
+ while (cmd) {
+ cmd->vq = vq;
+ cmd->error = 0;
+ cmd->finished = false;
+ QTAILQ_INSERT_TAIL(&g->cmdq, cmd, next);
+ cmd = virtqueue_pop(vq, sizeof(struct virtio_gpu_ctrl_command));
+ }
+
+ virtio_gpu_process_cmdq(g);
+}
+
+static void virtio_gpu_ctrl_bh(void *opaque)
+{
+ VirtIOGPU *g = opaque;
+ VirtIOGPUClass *vgc = VIRTIO_GPU_GET_CLASS(g);
+
+ vgc->handle_ctrl(&g->parent_obj.parent_obj, g->ctrl_vq);
+}
+
+static void virtio_gpu_handle_cursor(VirtIODevice *vdev, VirtQueue *vq)
+{
+ VirtIOGPU *g = VIRTIO_GPU(vdev);
+ VirtQueueElement *elem;
+ size_t s;
+ struct virtio_gpu_update_cursor cursor_info;
+
+ if (!virtio_queue_ready(vq)) {
+ return;
+ }
+ for (;;) {
+ elem = virtqueue_pop(vq, sizeof(VirtQueueElement));
+ if (!elem) {
+ break;
+ }
+
+ s = iov_to_buf(elem->out_sg, elem->out_num, 0,
+ &cursor_info, sizeof(cursor_info));
+ if (s != sizeof(cursor_info)) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: cursor size incorrect %zu vs %zu\n",
+ __func__, s, sizeof(cursor_info));
+ } else {
+ virtio_gpu_bswap_32(&cursor_info, sizeof(cursor_info));
+ update_cursor(g, &cursor_info);
+ }
+ virtqueue_push(vq, elem, 0);
+ virtio_notify(vdev, vq);
+ g_free(elem);
+ }
+}
+
+static void virtio_gpu_cursor_bh(void *opaque)
+{
+ VirtIOGPU *g = opaque;
+ virtio_gpu_handle_cursor(&g->parent_obj.parent_obj, g->cursor_vq);
+}
+
+static const VMStateDescription vmstate_virtio_gpu_scanout = {
+ .name = "virtio-gpu-one-scanout",
+ .version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(resource_id, struct virtio_gpu_scanout),
+ VMSTATE_UINT32(width, struct virtio_gpu_scanout),
+ VMSTATE_UINT32(height, struct virtio_gpu_scanout),
+ VMSTATE_INT32(x, struct virtio_gpu_scanout),
+ VMSTATE_INT32(y, struct virtio_gpu_scanout),
+ VMSTATE_UINT32(cursor.resource_id, struct virtio_gpu_scanout),
+ VMSTATE_UINT32(cursor.hot_x, struct virtio_gpu_scanout),
+ VMSTATE_UINT32(cursor.hot_y, struct virtio_gpu_scanout),
+ VMSTATE_UINT32(cursor.pos.x, struct virtio_gpu_scanout),
+ VMSTATE_UINT32(cursor.pos.y, struct virtio_gpu_scanout),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static const VMStateDescription vmstate_virtio_gpu_scanouts = {
+ .name = "virtio-gpu-scanouts",
+ .version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(parent_obj.enable, struct VirtIOGPU),
+ VMSTATE_UINT32_EQUAL(parent_obj.conf.max_outputs,
+ struct VirtIOGPU, NULL),
+ VMSTATE_STRUCT_VARRAY_UINT32(parent_obj.scanout, struct VirtIOGPU,
+ parent_obj.conf.max_outputs, 1,
+ vmstate_virtio_gpu_scanout,
+ struct virtio_gpu_scanout),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static int virtio_gpu_save(QEMUFile *f, void *opaque, size_t size,
+ const VMStateField *field, JSONWriter *vmdesc)
+{
+ VirtIOGPU *g = opaque;
+ struct virtio_gpu_simple_resource *res;
+ int i;
+
+ /* in 2d mode we should never find unprocessed commands here */
+ assert(QTAILQ_EMPTY(&g->cmdq));
+
+ QTAILQ_FOREACH(res, &g->reslist, next) {
+ qemu_put_be32(f, res->resource_id);
+ qemu_put_be32(f, res->width);
+ qemu_put_be32(f, res->height);
+ qemu_put_be32(f, res->format);
+ qemu_put_be32(f, res->iov_cnt);
+ for (i = 0; i < res->iov_cnt; i++) {
+ qemu_put_be64(f, res->addrs[i]);
+ qemu_put_be32(f, res->iov[i].iov_len);
+ }
+ qemu_put_buffer(f, (void *)pixman_image_get_data(res->image),
+ pixman_image_get_stride(res->image) * res->height);
+ }
+ qemu_put_be32(f, 0); /* end of list */
+
+ return vmstate_save_state(f, &vmstate_virtio_gpu_scanouts, g, NULL);
+}
+
+static int virtio_gpu_load(QEMUFile *f, void *opaque, size_t size,
+ const VMStateField *field)
+{
+ VirtIOGPU *g = opaque;
+ struct virtio_gpu_simple_resource *res;
+ struct virtio_gpu_scanout *scanout;
+ uint32_t resource_id, pformat;
+ int i;
+
+ g->hostmem = 0;
+
+ resource_id = qemu_get_be32(f);
+ while (resource_id != 0) {
+ res = virtio_gpu_find_resource(g, resource_id);
+ if (res) {
+ return -EINVAL;
+ }
+
+ res = g_new0(struct virtio_gpu_simple_resource, 1);
+ res->resource_id = resource_id;
+ res->width = qemu_get_be32(f);
+ res->height = qemu_get_be32(f);
+ res->format = qemu_get_be32(f);
+ res->iov_cnt = qemu_get_be32(f);
+
+ /* allocate */
+ pformat = virtio_gpu_get_pixman_format(res->format);
+ if (!pformat) {
+ g_free(res);
+ return -EINVAL;
+ }
+ res->image = pixman_image_create_bits(pformat,
+ res->width, res->height,
+ NULL, 0);
+ if (!res->image) {
+ g_free(res);
+ return -EINVAL;
+ }
+
+ res->hostmem = calc_image_hostmem(pformat, res->width, res->height);
+
+ res->addrs = g_new(uint64_t, res->iov_cnt);
+ res->iov = g_new(struct iovec, res->iov_cnt);
+
+ /* read data */
+ for (i = 0; i < res->iov_cnt; i++) {
+ res->addrs[i] = qemu_get_be64(f);
+ res->iov[i].iov_len = qemu_get_be32(f);
+ }
+ qemu_get_buffer(f, (void *)pixman_image_get_data(res->image),
+ pixman_image_get_stride(res->image) * res->height);
+
+ /* restore mapping */
+ for (i = 0; i < res->iov_cnt; i++) {
+ hwaddr len = res->iov[i].iov_len;
+ res->iov[i].iov_base =
+ dma_memory_map(VIRTIO_DEVICE(g)->dma_as,
+ res->addrs[i], &len, DMA_DIRECTION_TO_DEVICE);
+
+ if (!res->iov[i].iov_base || len != res->iov[i].iov_len) {
+ /* Clean up the half-a-mapping we just created... */
+ if (res->iov[i].iov_base) {
+ dma_memory_unmap(VIRTIO_DEVICE(g)->dma_as,
+ res->iov[i].iov_base,
+ len,
+ DMA_DIRECTION_TO_DEVICE,
+ 0);
+ }
+ /* ...and the mappings for previous loop iterations */
+ res->iov_cnt = i;
+ virtio_gpu_cleanup_mapping(g, res);
+ pixman_image_unref(res->image);
+ g_free(res);
+ return -EINVAL;
+ }
+ }
+
+ QTAILQ_INSERT_HEAD(&g->reslist, res, next);
+ g->hostmem += res->hostmem;
+
+ resource_id = qemu_get_be32(f);
+ }
+
+ /* load & apply scanout state */
+ vmstate_load_state(f, &vmstate_virtio_gpu_scanouts, g, 1);
+ for (i = 0; i < g->parent_obj.conf.max_outputs; i++) {
+ scanout = &g->parent_obj.scanout[i];
+ if (!scanout->resource_id) {
+ continue;
+ }
+ res = virtio_gpu_find_resource(g, scanout->resource_id);
+ if (!res) {
+ return -EINVAL;
+ }
+ scanout->ds = qemu_create_displaysurface_pixman(res->image);
+ if (!scanout->ds) {
+ return -EINVAL;
+ }
+
+ dpy_gfx_replace_surface(scanout->con, scanout->ds);
+ dpy_gfx_update_full(scanout->con);
+ if (scanout->cursor.resource_id) {
+ update_cursor(g, &scanout->cursor);
+ }
+ res->scanout_bitmask |= (1 << i);
+ }
+
+ return 0;
+}
+
+void virtio_gpu_device_realize(DeviceState *qdev, Error **errp)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(qdev);
+ VirtIOGPU *g = VIRTIO_GPU(qdev);
+
+ if (virtio_gpu_blob_enabled(g->parent_obj.conf)) {
+ if (!virtio_gpu_have_udmabuf()) {
+ error_setg(errp, "cannot enable blob resources without udmabuf");
+ return;
+ }
+
+ if (virtio_gpu_virgl_enabled(g->parent_obj.conf)) {
+ error_setg(errp, "blobs and virgl are not compatible (yet)");
+ return;
+ }
+ }
+
+ if (!virtio_gpu_base_device_realize(qdev,
+ virtio_gpu_handle_ctrl_cb,
+ virtio_gpu_handle_cursor_cb,
+ errp)) {
+ return;
+ }
+
+ g->ctrl_vq = virtio_get_queue(vdev, 0);
+ g->cursor_vq = virtio_get_queue(vdev, 1);
+ g->ctrl_bh = qemu_bh_new(virtio_gpu_ctrl_bh, g);
+ g->cursor_bh = qemu_bh_new(virtio_gpu_cursor_bh, g);
+ QTAILQ_INIT(&g->reslist);
+ QTAILQ_INIT(&g->cmdq);
+ QTAILQ_INIT(&g->fenceq);
+}
+
+void virtio_gpu_reset(VirtIODevice *vdev)
+{
+ VirtIOGPU *g = VIRTIO_GPU(vdev);
+ struct virtio_gpu_simple_resource *res, *tmp;
+ struct virtio_gpu_ctrl_command *cmd;
+
+ QTAILQ_FOREACH_SAFE(res, &g->reslist, next, tmp) {
+ virtio_gpu_resource_destroy(g, res);
+ }
+
+ while (!QTAILQ_EMPTY(&g->cmdq)) {
+ cmd = QTAILQ_FIRST(&g->cmdq);
+ QTAILQ_REMOVE(&g->cmdq, cmd, next);
+ g_free(cmd);
+ }
+
+ while (!QTAILQ_EMPTY(&g->fenceq)) {
+ cmd = QTAILQ_FIRST(&g->fenceq);
+ QTAILQ_REMOVE(&g->fenceq, cmd, next);
+ g->inflight--;
+ g_free(cmd);
+ }
+
+ virtio_gpu_base_reset(VIRTIO_GPU_BASE(vdev));
+}
+
+static void
+virtio_gpu_get_config(VirtIODevice *vdev, uint8_t *config)
+{
+ VirtIOGPUBase *g = VIRTIO_GPU_BASE(vdev);
+
+ memcpy(config, &g->virtio_config, sizeof(g->virtio_config));
+}
+
+static void
+virtio_gpu_set_config(VirtIODevice *vdev, const uint8_t *config)
+{
+ VirtIOGPUBase *g = VIRTIO_GPU_BASE(vdev);
+ const struct virtio_gpu_config *vgconfig =
+ (const struct virtio_gpu_config *)config;
+
+ if (vgconfig->events_clear) {
+ g->virtio_config.events_read &= ~vgconfig->events_clear;
+ }
+}
+
+/*
+ * For historical reasons virtio_gpu does not adhere to virtio migration
+ * scheme as described in doc/virtio-migration.txt, in a sense that no
+ * save/load callback are provided to the core. Instead the device data
+ * is saved/loaded after the core data.
+ *
+ * Because of this we need a special vmsd.
+ */
+static const VMStateDescription vmstate_virtio_gpu = {
+ .name = "virtio-gpu",
+ .minimum_version_id = VIRTIO_GPU_VM_VERSION,
+ .version_id = VIRTIO_GPU_VM_VERSION,
+ .fields = (VMStateField[]) {
+ VMSTATE_VIRTIO_DEVICE /* core */,
+ {
+ .name = "virtio-gpu",
+ .info = &(const VMStateInfo) {
+ .name = "virtio-gpu",
+ .get = virtio_gpu_load,
+ .put = virtio_gpu_save,
+ },
+ .flags = VMS_SINGLE,
+ } /* device */,
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static Property virtio_gpu_properties[] = {
+ VIRTIO_GPU_BASE_PROPERTIES(VirtIOGPU, parent_obj.conf),
+ DEFINE_PROP_SIZE("max_hostmem", VirtIOGPU, conf_max_hostmem,
+ 256 * MiB),
+ DEFINE_PROP_BIT("blob", VirtIOGPU, parent_obj.conf.flags,
+ VIRTIO_GPU_FLAG_BLOB_ENABLED, false),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void virtio_gpu_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);
+ VirtIOGPUClass *vgc = VIRTIO_GPU_CLASS(klass);
+ VirtIOGPUBaseClass *vgbc = &vgc->parent;
+
+ vgc->handle_ctrl = virtio_gpu_handle_ctrl;
+ vgc->process_cmd = virtio_gpu_simple_process_cmd;
+ vgc->update_cursor_data = virtio_gpu_update_cursor_data;
+ vgbc->gl_flushed = virtio_gpu_handle_gl_flushed;
+
+ vdc->realize = virtio_gpu_device_realize;
+ vdc->reset = virtio_gpu_reset;
+ vdc->get_config = virtio_gpu_get_config;
+ vdc->set_config = virtio_gpu_set_config;
+
+ dc->vmsd = &vmstate_virtio_gpu;
+ device_class_set_props(dc, virtio_gpu_properties);
+}
+
+static const TypeInfo virtio_gpu_info = {
+ .name = TYPE_VIRTIO_GPU,
+ .parent = TYPE_VIRTIO_GPU_BASE,
+ .instance_size = sizeof(VirtIOGPU),
+ .class_size = sizeof(VirtIOGPUClass),
+ .class_init = virtio_gpu_class_init,
+};
+module_obj(TYPE_VIRTIO_GPU);
+
+static void virtio_register_types(void)
+{
+ type_register_static(&virtio_gpu_info);
+}
+
+type_init(virtio_register_types)
diff --git a/hw/display/virtio-vga-gl.c b/hw/display/virtio-vga-gl.c
new file mode 100644
index 000000000..f22549097
--- /dev/null
+++ b/hw/display/virtio-vga-gl.c
@@ -0,0 +1,50 @@
+#include "qemu/osdep.h"
+#include "hw/pci/pci.h"
+#include "hw/qdev-properties.h"
+#include "hw/virtio/virtio-gpu.h"
+#include "hw/display/vga.h"
+#include "qapi/error.h"
+#include "qemu/module.h"
+#include "virtio-vga.h"
+#include "qom/object.h"
+
+#define TYPE_VIRTIO_VGA_GL "virtio-vga-gl"
+
+typedef struct VirtIOVGAGL VirtIOVGAGL;
+DECLARE_INSTANCE_CHECKER(VirtIOVGAGL, VIRTIO_VGA_GL,
+ TYPE_VIRTIO_VGA_GL)
+
+struct VirtIOVGAGL {
+ VirtIOVGABase parent_obj;
+
+ VirtIOGPUGL vdev;
+};
+
+static void virtio_vga_gl_inst_initfn(Object *obj)
+{
+ VirtIOVGAGL *dev = VIRTIO_VGA_GL(obj);
+
+ virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev),
+ TYPE_VIRTIO_GPU_GL);
+ VIRTIO_VGA_BASE(dev)->vgpu = VIRTIO_GPU_BASE(&dev->vdev);
+}
+
+
+static VirtioPCIDeviceTypeInfo virtio_vga_gl_info = {
+ .generic_name = TYPE_VIRTIO_VGA_GL,
+ .parent = TYPE_VIRTIO_VGA_BASE,
+ .instance_size = sizeof(VirtIOVGAGL),
+ .instance_init = virtio_vga_gl_inst_initfn,
+};
+module_obj(TYPE_VIRTIO_VGA_GL);
+
+static void virtio_vga_register_types(void)
+{
+ if (have_vga) {
+ virtio_pci_types_register(&virtio_vga_gl_info);
+ }
+}
+
+type_init(virtio_vga_register_types)
+
+module_dep("hw-display-virtio-vga");
diff --git a/hw/display/virtio-vga.c b/hw/display/virtio-vga.c
new file mode 100644
index 000000000..9e57f61e9
--- /dev/null
+++ b/hw/display/virtio-vga.c
@@ -0,0 +1,280 @@
+#include "qemu/osdep.h"
+#include "hw/pci/pci.h"
+#include "hw/qdev-properties.h"
+#include "hw/virtio/virtio-gpu.h"
+#include "qapi/error.h"
+#include "qemu/module.h"
+#include "virtio-vga.h"
+#include "qom/object.h"
+
+static void virtio_vga_base_invalidate_display(void *opaque)
+{
+ VirtIOVGABase *vvga = opaque;
+ VirtIOGPUBase *g = vvga->vgpu;
+
+ if (g->enable) {
+ g->hw_ops->invalidate(g);
+ } else {
+ vvga->vga.hw_ops->invalidate(&vvga->vga);
+ }
+}
+
+static void virtio_vga_base_update_display(void *opaque)
+{
+ VirtIOVGABase *vvga = opaque;
+ VirtIOGPUBase *g = vvga->vgpu;
+
+ if (g->enable) {
+ g->hw_ops->gfx_update(g);
+ } else {
+ vvga->vga.hw_ops->gfx_update(&vvga->vga);
+ }
+}
+
+static void virtio_vga_base_text_update(void *opaque, console_ch_t *chardata)
+{
+ VirtIOVGABase *vvga = opaque;
+ VirtIOGPUBase *g = vvga->vgpu;
+
+ if (g->enable) {
+ if (g->hw_ops->text_update) {
+ g->hw_ops->text_update(g, chardata);
+ }
+ } else {
+ if (vvga->vga.hw_ops->text_update) {
+ vvga->vga.hw_ops->text_update(&vvga->vga, chardata);
+ }
+ }
+}
+
+static int virtio_vga_base_ui_info(void *opaque, uint32_t idx, QemuUIInfo *info)
+{
+ VirtIOVGABase *vvga = opaque;
+ VirtIOGPUBase *g = vvga->vgpu;
+
+ if (g->hw_ops->ui_info) {
+ return g->hw_ops->ui_info(g, idx, info);
+ }
+ return -1;
+}
+
+static void virtio_vga_base_gl_block(void *opaque, bool block)
+{
+ VirtIOVGABase *vvga = opaque;
+ VirtIOGPUBase *g = vvga->vgpu;
+
+ if (g->hw_ops->gl_block) {
+ g->hw_ops->gl_block(g, block);
+ }
+}
+
+static void virtio_vga_base_gl_flushed(void *opaque)
+{
+ VirtIOVGABase *vvga = opaque;
+ VirtIOGPUBase *g = vvga->vgpu;
+
+ if (g->hw_ops->gl_flushed) {
+ g->hw_ops->gl_flushed(g);
+ }
+}
+
+static int virtio_vga_base_get_flags(void *opaque)
+{
+ VirtIOVGABase *vvga = opaque;
+ VirtIOGPUBase *g = vvga->vgpu;
+
+ return g->hw_ops->get_flags(g);
+}
+
+static const GraphicHwOps virtio_vga_base_ops = {
+ .get_flags = virtio_vga_base_get_flags,
+ .invalidate = virtio_vga_base_invalidate_display,
+ .gfx_update = virtio_vga_base_update_display,
+ .text_update = virtio_vga_base_text_update,
+ .ui_info = virtio_vga_base_ui_info,
+ .gl_block = virtio_vga_base_gl_block,
+ .gl_flushed = virtio_vga_base_gl_flushed,
+};
+
+static const VMStateDescription vmstate_virtio_vga_base = {
+ .name = "virtio-vga",
+ .version_id = 2,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ /* no pci stuff here, saving the virtio device will handle that */
+ VMSTATE_STRUCT(vga, VirtIOVGABase, 0,
+ vmstate_vga_common, VGACommonState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+/* VGA device wrapper around PCI device around virtio GPU */
+static void virtio_vga_base_realize(VirtIOPCIProxy *vpci_dev, Error **errp)
+{
+ VirtIOVGABase *vvga = VIRTIO_VGA_BASE(vpci_dev);
+ VirtIOGPUBase *g = vvga->vgpu;
+ VGACommonState *vga = &vvga->vga;
+ uint32_t offset;
+ int i;
+
+ /* init vga compat bits */
+ vga->vram_size_mb = 8;
+ vga_common_init(vga, OBJECT(vpci_dev));
+ vga_init(vga, OBJECT(vpci_dev), pci_address_space(&vpci_dev->pci_dev),
+ pci_address_space_io(&vpci_dev->pci_dev), true);
+ pci_register_bar(&vpci_dev->pci_dev, 0,
+ PCI_BASE_ADDRESS_MEM_PREFETCH, &vga->vram);
+
+ /*
+ * Configure virtio bar and regions
+ *
+ * We use bar #2 for the mmio regions, to be compatible with stdvga.
+ * virtio regions are moved to the end of bar #2, to make room for
+ * the stdvga mmio registers at the start of bar #2.
+ */
+ vpci_dev->modern_mem_bar_idx = 2;
+ vpci_dev->msix_bar_idx = 4;
+ vpci_dev->modern_io_bar_idx = 5;
+
+ if (!(vpci_dev->flags & VIRTIO_PCI_FLAG_PAGE_PER_VQ)) {
+ /*
+ * with page-per-vq=off there is no padding space we can use
+ * for the stdvga registers. Make the common and isr regions
+ * smaller then.
+ */
+ vpci_dev->common.size /= 2;
+ vpci_dev->isr.size /= 2;
+ }
+
+ offset = memory_region_size(&vpci_dev->modern_bar);
+ offset -= vpci_dev->notify.size;
+ vpci_dev->notify.offset = offset;
+ offset -= vpci_dev->device.size;
+ vpci_dev->device.offset = offset;
+ offset -= vpci_dev->isr.size;
+ vpci_dev->isr.offset = offset;
+ offset -= vpci_dev->common.size;
+ vpci_dev->common.offset = offset;
+
+ /* init virtio bits */
+ virtio_pci_force_virtio_1(vpci_dev);
+ if (!qdev_realize(DEVICE(g), BUS(&vpci_dev->bus), errp)) {
+ return;
+ }
+
+ /* add stdvga mmio regions */
+ pci_std_vga_mmio_region_init(vga, OBJECT(vvga), &vpci_dev->modern_bar,
+ vvga->vga_mrs, true, false);
+
+ vga->con = g->scanout[0].con;
+ graphic_console_set_hwops(vga->con, &virtio_vga_base_ops, vvga);
+
+ for (i = 0; i < g->conf.max_outputs; i++) {
+ object_property_set_link(OBJECT(g->scanout[i].con), "device",
+ OBJECT(vpci_dev), &error_abort);
+ }
+}
+
+static void virtio_vga_base_reset(DeviceState *dev)
+{
+ VirtIOVGABaseClass *klass = VIRTIO_VGA_BASE_GET_CLASS(dev);
+ VirtIOVGABase *vvga = VIRTIO_VGA_BASE(dev);
+
+ /* reset virtio-gpu */
+ klass->parent_reset(dev);
+
+ /* reset vga */
+ vga_common_reset(&vvga->vga);
+ vga_dirty_log_start(&vvga->vga);
+}
+
+static bool virtio_vga_get_big_endian_fb(Object *obj, Error **errp)
+{
+ VirtIOVGABase *d = VIRTIO_VGA_BASE(obj);
+
+ return d->vga.big_endian_fb;
+}
+
+static void virtio_vga_set_big_endian_fb(Object *obj, bool value, Error **errp)
+{
+ VirtIOVGABase *d = VIRTIO_VGA_BASE(obj);
+
+ d->vga.big_endian_fb = value;
+}
+
+static Property virtio_vga_base_properties[] = {
+ DEFINE_VIRTIO_GPU_PCI_PROPERTIES(VirtIOPCIProxy),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void virtio_vga_base_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtioPCIClass *k = VIRTIO_PCI_CLASS(klass);
+ VirtIOVGABaseClass *v = VIRTIO_VGA_BASE_CLASS(klass);
+ PCIDeviceClass *pcidev_k = PCI_DEVICE_CLASS(klass);
+
+ set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories);
+ device_class_set_props(dc, virtio_vga_base_properties);
+ dc->vmsd = &vmstate_virtio_vga_base;
+ dc->hotpluggable = false;
+ device_class_set_parent_reset(dc, virtio_vga_base_reset,
+ &v->parent_reset);
+
+ k->realize = virtio_vga_base_realize;
+ pcidev_k->romfile = "vgabios-virtio.bin";
+ pcidev_k->class_id = PCI_CLASS_DISPLAY_VGA;
+
+ /* Expose framebuffer byteorder via QOM */
+ object_class_property_add_bool(klass, "big-endian-framebuffer",
+ virtio_vga_get_big_endian_fb,
+ virtio_vga_set_big_endian_fb);
+}
+
+static TypeInfo virtio_vga_base_info = {
+ .name = TYPE_VIRTIO_VGA_BASE,
+ .parent = TYPE_VIRTIO_PCI,
+ .instance_size = sizeof(VirtIOVGABase),
+ .class_size = sizeof(VirtIOVGABaseClass),
+ .class_init = virtio_vga_base_class_init,
+ .abstract = true,
+};
+module_obj(TYPE_VIRTIO_VGA_BASE);
+
+#define TYPE_VIRTIO_VGA "virtio-vga"
+
+typedef struct VirtIOVGA VirtIOVGA;
+DECLARE_INSTANCE_CHECKER(VirtIOVGA, VIRTIO_VGA,
+ TYPE_VIRTIO_VGA)
+
+struct VirtIOVGA {
+ VirtIOVGABase parent_obj;
+
+ VirtIOGPU vdev;
+};
+
+static void virtio_vga_inst_initfn(Object *obj)
+{
+ VirtIOVGA *dev = VIRTIO_VGA(obj);
+
+ virtio_instance_init_common(obj, &dev->vdev, sizeof(dev->vdev),
+ TYPE_VIRTIO_GPU);
+ VIRTIO_VGA_BASE(dev)->vgpu = VIRTIO_GPU_BASE(&dev->vdev);
+}
+
+
+static VirtioPCIDeviceTypeInfo virtio_vga_info = {
+ .generic_name = TYPE_VIRTIO_VGA,
+ .parent = TYPE_VIRTIO_VGA_BASE,
+ .instance_size = sizeof(VirtIOVGA),
+ .instance_init = virtio_vga_inst_initfn,
+};
+module_obj(TYPE_VIRTIO_VGA);
+
+static void virtio_vga_register_types(void)
+{
+ type_register_static(&virtio_vga_base_info);
+ virtio_pci_types_register(&virtio_vga_info);
+}
+
+type_init(virtio_vga_register_types)
diff --git a/hw/display/virtio-vga.h b/hw/display/virtio-vga.h
new file mode 100644
index 000000000..977ad5edc
--- /dev/null
+++ b/hw/display/virtio-vga.h
@@ -0,0 +1,29 @@
+#ifndef VIRTIO_VGA_H
+#define VIRTIO_VGA_H
+
+#include "hw/virtio/virtio-gpu-pci.h"
+#include "vga_int.h"
+#include "qom/object.h"
+
+/*
+ * virtio-vga-base: This extends VirtioPCIProxy.
+ */
+#define TYPE_VIRTIO_VGA_BASE "virtio-vga-base"
+OBJECT_DECLARE_TYPE(VirtIOVGABase, VirtIOVGABaseClass,
+ VIRTIO_VGA_BASE)
+
+struct VirtIOVGABase {
+ VirtIOPCIProxy parent_obj;
+
+ VirtIOGPUBase *vgpu;
+ VGACommonState vga;
+ MemoryRegion vga_mrs[3];
+};
+
+struct VirtIOVGABaseClass {
+ VirtioPCIClass parent_class;
+
+ DeviceReset parent_reset;
+};
+
+#endif /* VIRTIO_VGA_H */
diff --git a/hw/display/vmware_vga.c b/hw/display/vmware_vga.c
new file mode 100644
index 000000000..e2969a6c8
--- /dev/null
+++ b/hw/display/vmware_vga.c
@@ -0,0 +1,1366 @@
+/*
+ * QEMU VMware-SVGA "chipset".
+ *
+ * Copyright (c) 2007 Andrzej Zaborowski <balrog@zabor.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/module.h"
+#include "qemu/units.h"
+#include "qapi/error.h"
+#include "qemu/log.h"
+#include "hw/loader.h"
+#include "trace.h"
+#include "hw/pci/pci.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "qom/object.h"
+
+#undef VERBOSE
+#define HW_RECT_ACCEL
+#define HW_FILL_ACCEL
+#define HW_MOUSE_ACCEL
+
+#include "vga_int.h"
+
+/* See http://vmware-svga.sf.net/ for some documentation on VMWare SVGA */
+
+struct vmsvga_state_s {
+ VGACommonState vga;
+
+ int invalidated;
+ int enable;
+ int config;
+ struct {
+ int id;
+ int x;
+ int y;
+ int on;
+ } cursor;
+
+ int index;
+ int scratch_size;
+ uint32_t *scratch;
+ int new_width;
+ int new_height;
+ int new_depth;
+ uint32_t guest;
+ uint32_t svgaid;
+ int syncing;
+
+ MemoryRegion fifo_ram;
+ uint8_t *fifo_ptr;
+ unsigned int fifo_size;
+
+ uint32_t *fifo;
+ uint32_t fifo_min;
+ uint32_t fifo_max;
+ uint32_t fifo_next;
+ uint32_t fifo_stop;
+
+#define REDRAW_FIFO_LEN 512
+ struct vmsvga_rect_s {
+ int x, y, w, h;
+ } redraw_fifo[REDRAW_FIFO_LEN];
+ int redraw_fifo_first, redraw_fifo_last;
+};
+
+#define TYPE_VMWARE_SVGA "vmware-svga"
+
+DECLARE_INSTANCE_CHECKER(struct pci_vmsvga_state_s, VMWARE_SVGA,
+ TYPE_VMWARE_SVGA)
+
+struct pci_vmsvga_state_s {
+ /*< private >*/
+ PCIDevice parent_obj;
+ /*< public >*/
+
+ struct vmsvga_state_s chip;
+ MemoryRegion io_bar;
+};
+
+#define SVGA_MAGIC 0x900000UL
+#define SVGA_MAKE_ID(ver) (SVGA_MAGIC << 8 | (ver))
+#define SVGA_ID_0 SVGA_MAKE_ID(0)
+#define SVGA_ID_1 SVGA_MAKE_ID(1)
+#define SVGA_ID_2 SVGA_MAKE_ID(2)
+
+#define SVGA_LEGACY_BASE_PORT 0x4560
+#define SVGA_INDEX_PORT 0x0
+#define SVGA_VALUE_PORT 0x1
+#define SVGA_BIOS_PORT 0x2
+
+#define SVGA_VERSION_2
+
+#ifdef SVGA_VERSION_2
+# define SVGA_ID SVGA_ID_2
+# define SVGA_IO_BASE SVGA_LEGACY_BASE_PORT
+# define SVGA_IO_MUL 1
+# define SVGA_FIFO_SIZE 0x10000
+# define SVGA_PCI_DEVICE_ID PCI_DEVICE_ID_VMWARE_SVGA2
+#else
+# define SVGA_ID SVGA_ID_1
+# define SVGA_IO_BASE SVGA_LEGACY_BASE_PORT
+# define SVGA_IO_MUL 4
+# define SVGA_FIFO_SIZE 0x10000
+# define SVGA_PCI_DEVICE_ID PCI_DEVICE_ID_VMWARE_SVGA
+#endif
+
+enum {
+ /* ID 0, 1 and 2 registers */
+ SVGA_REG_ID = 0,
+ SVGA_REG_ENABLE = 1,
+ SVGA_REG_WIDTH = 2,
+ SVGA_REG_HEIGHT = 3,
+ SVGA_REG_MAX_WIDTH = 4,
+ SVGA_REG_MAX_HEIGHT = 5,
+ SVGA_REG_DEPTH = 6,
+ SVGA_REG_BITS_PER_PIXEL = 7, /* Current bpp in the guest */
+ SVGA_REG_PSEUDOCOLOR = 8,
+ SVGA_REG_RED_MASK = 9,
+ SVGA_REG_GREEN_MASK = 10,
+ SVGA_REG_BLUE_MASK = 11,
+ SVGA_REG_BYTES_PER_LINE = 12,
+ SVGA_REG_FB_START = 13,
+ SVGA_REG_FB_OFFSET = 14,
+ SVGA_REG_VRAM_SIZE = 15,
+ SVGA_REG_FB_SIZE = 16,
+
+ /* ID 1 and 2 registers */
+ SVGA_REG_CAPABILITIES = 17,
+ SVGA_REG_MEM_START = 18, /* Memory for command FIFO */
+ SVGA_REG_MEM_SIZE = 19,
+ SVGA_REG_CONFIG_DONE = 20, /* Set when memory area configured */
+ SVGA_REG_SYNC = 21, /* Write to force synchronization */
+ SVGA_REG_BUSY = 22, /* Read to check if sync is done */
+ SVGA_REG_GUEST_ID = 23, /* Set guest OS identifier */
+ SVGA_REG_CURSOR_ID = 24, /* ID of cursor */
+ SVGA_REG_CURSOR_X = 25, /* Set cursor X position */
+ SVGA_REG_CURSOR_Y = 26, /* Set cursor Y position */
+ SVGA_REG_CURSOR_ON = 27, /* Turn cursor on/off */
+ SVGA_REG_HOST_BITS_PER_PIXEL = 28, /* Current bpp in the host */
+ SVGA_REG_SCRATCH_SIZE = 29, /* Number of scratch registers */
+ SVGA_REG_MEM_REGS = 30, /* Number of FIFO registers */
+ SVGA_REG_NUM_DISPLAYS = 31, /* Number of guest displays */
+ SVGA_REG_PITCHLOCK = 32, /* Fixed pitch for all modes */
+
+ SVGA_PALETTE_BASE = 1024, /* Base of SVGA color map */
+ SVGA_PALETTE_END = SVGA_PALETTE_BASE + 767,
+ SVGA_SCRATCH_BASE = SVGA_PALETTE_BASE + 768,
+};
+
+#define SVGA_CAP_NONE 0
+#define SVGA_CAP_RECT_FILL (1 << 0)
+#define SVGA_CAP_RECT_COPY (1 << 1)
+#define SVGA_CAP_RECT_PAT_FILL (1 << 2)
+#define SVGA_CAP_LEGACY_OFFSCREEN (1 << 3)
+#define SVGA_CAP_RASTER_OP (1 << 4)
+#define SVGA_CAP_CURSOR (1 << 5)
+#define SVGA_CAP_CURSOR_BYPASS (1 << 6)
+#define SVGA_CAP_CURSOR_BYPASS_2 (1 << 7)
+#define SVGA_CAP_8BIT_EMULATION (1 << 8)
+#define SVGA_CAP_ALPHA_CURSOR (1 << 9)
+#define SVGA_CAP_GLYPH (1 << 10)
+#define SVGA_CAP_GLYPH_CLIPPING (1 << 11)
+#define SVGA_CAP_OFFSCREEN_1 (1 << 12)
+#define SVGA_CAP_ALPHA_BLEND (1 << 13)
+#define SVGA_CAP_3D (1 << 14)
+#define SVGA_CAP_EXTENDED_FIFO (1 << 15)
+#define SVGA_CAP_MULTIMON (1 << 16)
+#define SVGA_CAP_PITCHLOCK (1 << 17)
+
+/*
+ * FIFO offsets (seen as an array of 32-bit words)
+ */
+enum {
+ /*
+ * The original defined FIFO offsets
+ */
+ SVGA_FIFO_MIN = 0,
+ SVGA_FIFO_MAX, /* The distance from MIN to MAX must be at least 10K */
+ SVGA_FIFO_NEXT,
+ SVGA_FIFO_STOP,
+
+ /*
+ * Additional offsets added as of SVGA_CAP_EXTENDED_FIFO
+ */
+ SVGA_FIFO_CAPABILITIES = 4,
+ SVGA_FIFO_FLAGS,
+ SVGA_FIFO_FENCE,
+ SVGA_FIFO_3D_HWVERSION,
+ SVGA_FIFO_PITCHLOCK,
+};
+
+#define SVGA_FIFO_CAP_NONE 0
+#define SVGA_FIFO_CAP_FENCE (1 << 0)
+#define SVGA_FIFO_CAP_ACCELFRONT (1 << 1)
+#define SVGA_FIFO_CAP_PITCHLOCK (1 << 2)
+
+#define SVGA_FIFO_FLAG_NONE 0
+#define SVGA_FIFO_FLAG_ACCELFRONT (1 << 0)
+
+/* These values can probably be changed arbitrarily. */
+#define SVGA_SCRATCH_SIZE 0x8000
+#define SVGA_MAX_WIDTH 2368
+#define SVGA_MAX_HEIGHT 1770
+
+#ifdef VERBOSE
+# define GUEST_OS_BASE 0x5001
+static const char *vmsvga_guest_id[] = {
+ [0x00] = "Dos",
+ [0x01] = "Windows 3.1",
+ [0x02] = "Windows 95",
+ [0x03] = "Windows 98",
+ [0x04] = "Windows ME",
+ [0x05] = "Windows NT",
+ [0x06] = "Windows 2000",
+ [0x07] = "Linux",
+ [0x08] = "OS/2",
+ [0x09] = "an unknown OS",
+ [0x0a] = "BSD",
+ [0x0b] = "Whistler",
+ [0x0c] = "an unknown OS",
+ [0x0d] = "an unknown OS",
+ [0x0e] = "an unknown OS",
+ [0x0f] = "an unknown OS",
+ [0x10] = "an unknown OS",
+ [0x11] = "an unknown OS",
+ [0x12] = "an unknown OS",
+ [0x13] = "an unknown OS",
+ [0x14] = "an unknown OS",
+ [0x15] = "Windows 2003",
+};
+#endif
+
+enum {
+ SVGA_CMD_INVALID_CMD = 0,
+ SVGA_CMD_UPDATE = 1,
+ SVGA_CMD_RECT_FILL = 2,
+ SVGA_CMD_RECT_COPY = 3,
+ SVGA_CMD_DEFINE_BITMAP = 4,
+ SVGA_CMD_DEFINE_BITMAP_SCANLINE = 5,
+ SVGA_CMD_DEFINE_PIXMAP = 6,
+ SVGA_CMD_DEFINE_PIXMAP_SCANLINE = 7,
+ SVGA_CMD_RECT_BITMAP_FILL = 8,
+ SVGA_CMD_RECT_PIXMAP_FILL = 9,
+ SVGA_CMD_RECT_BITMAP_COPY = 10,
+ SVGA_CMD_RECT_PIXMAP_COPY = 11,
+ SVGA_CMD_FREE_OBJECT = 12,
+ SVGA_CMD_RECT_ROP_FILL = 13,
+ SVGA_CMD_RECT_ROP_COPY = 14,
+ SVGA_CMD_RECT_ROP_BITMAP_FILL = 15,
+ SVGA_CMD_RECT_ROP_PIXMAP_FILL = 16,
+ SVGA_CMD_RECT_ROP_BITMAP_COPY = 17,
+ SVGA_CMD_RECT_ROP_PIXMAP_COPY = 18,
+ SVGA_CMD_DEFINE_CURSOR = 19,
+ SVGA_CMD_DISPLAY_CURSOR = 20,
+ SVGA_CMD_MOVE_CURSOR = 21,
+ SVGA_CMD_DEFINE_ALPHA_CURSOR = 22,
+ SVGA_CMD_DRAW_GLYPH = 23,
+ SVGA_CMD_DRAW_GLYPH_CLIPPED = 24,
+ SVGA_CMD_UPDATE_VERBOSE = 25,
+ SVGA_CMD_SURFACE_FILL = 26,
+ SVGA_CMD_SURFACE_COPY = 27,
+ SVGA_CMD_SURFACE_ALPHA_BLEND = 28,
+ SVGA_CMD_FRONT_ROP_FILL = 29,
+ SVGA_CMD_FENCE = 30,
+};
+
+/* Legal values for the SVGA_REG_CURSOR_ON register in cursor bypass mode */
+enum {
+ SVGA_CURSOR_ON_HIDE = 0,
+ SVGA_CURSOR_ON_SHOW = 1,
+ SVGA_CURSOR_ON_REMOVE_FROM_FB = 2,
+ SVGA_CURSOR_ON_RESTORE_TO_FB = 3,
+};
+
+static inline bool vmsvga_verify_rect(DisplaySurface *surface,
+ const char *name,
+ int x, int y, int w, int h)
+{
+ if (x < 0) {
+ fprintf(stderr, "%s: x was < 0 (%d)\n", name, x);
+ return false;
+ }
+ if (x > SVGA_MAX_WIDTH) {
+ fprintf(stderr, "%s: x was > %d (%d)\n", name, SVGA_MAX_WIDTH, x);
+ return false;
+ }
+ if (w < 0) {
+ fprintf(stderr, "%s: w was < 0 (%d)\n", name, w);
+ return false;
+ }
+ if (w > SVGA_MAX_WIDTH) {
+ fprintf(stderr, "%s: w was > %d (%d)\n", name, SVGA_MAX_WIDTH, w);
+ return false;
+ }
+ if (x + w > surface_width(surface)) {
+ fprintf(stderr, "%s: width was > %d (x: %d, w: %d)\n",
+ name, surface_width(surface), x, w);
+ return false;
+ }
+
+ if (y < 0) {
+ fprintf(stderr, "%s: y was < 0 (%d)\n", name, y);
+ return false;
+ }
+ if (y > SVGA_MAX_HEIGHT) {
+ fprintf(stderr, "%s: y was > %d (%d)\n", name, SVGA_MAX_HEIGHT, y);
+ return false;
+ }
+ if (h < 0) {
+ fprintf(stderr, "%s: h was < 0 (%d)\n", name, h);
+ return false;
+ }
+ if (h > SVGA_MAX_HEIGHT) {
+ fprintf(stderr, "%s: h was > %d (%d)\n", name, SVGA_MAX_HEIGHT, h);
+ return false;
+ }
+ if (y + h > surface_height(surface)) {
+ fprintf(stderr, "%s: update height > %d (y: %d, h: %d)\n",
+ name, surface_height(surface), y, h);
+ return false;
+ }
+
+ return true;
+}
+
+static inline void vmsvga_update_rect(struct vmsvga_state_s *s,
+ int x, int y, int w, int h)
+{
+ DisplaySurface *surface = qemu_console_surface(s->vga.con);
+ int line;
+ int bypl;
+ int width;
+ int start;
+ uint8_t *src;
+ uint8_t *dst;
+
+ if (!vmsvga_verify_rect(surface, __func__, x, y, w, h)) {
+ /* go for a fullscreen update as fallback */
+ x = 0;
+ y = 0;
+ w = surface_width(surface);
+ h = surface_height(surface);
+ }
+
+ bypl = surface_stride(surface);
+ width = surface_bytes_per_pixel(surface) * w;
+ start = surface_bytes_per_pixel(surface) * x + bypl * y;
+ src = s->vga.vram_ptr + start;
+ dst = surface_data(surface) + start;
+
+ for (line = h; line > 0; line--, src += bypl, dst += bypl) {
+ memcpy(dst, src, width);
+ }
+ dpy_gfx_update(s->vga.con, x, y, w, h);
+}
+
+static inline void vmsvga_update_rect_delayed(struct vmsvga_state_s *s,
+ int x, int y, int w, int h)
+{
+ struct vmsvga_rect_s *rect = &s->redraw_fifo[s->redraw_fifo_last++];
+
+ s->redraw_fifo_last &= REDRAW_FIFO_LEN - 1;
+ rect->x = x;
+ rect->y = y;
+ rect->w = w;
+ rect->h = h;
+}
+
+static inline void vmsvga_update_rect_flush(struct vmsvga_state_s *s)
+{
+ struct vmsvga_rect_s *rect;
+
+ if (s->invalidated) {
+ s->redraw_fifo_first = s->redraw_fifo_last;
+ return;
+ }
+ /* Overlapping region updates can be optimised out here - if someone
+ * knows a smart algorithm to do that, please share. */
+ while (s->redraw_fifo_first != s->redraw_fifo_last) {
+ rect = &s->redraw_fifo[s->redraw_fifo_first++];
+ s->redraw_fifo_first &= REDRAW_FIFO_LEN - 1;
+ vmsvga_update_rect(s, rect->x, rect->y, rect->w, rect->h);
+ }
+}
+
+#ifdef HW_RECT_ACCEL
+static inline int vmsvga_copy_rect(struct vmsvga_state_s *s,
+ int x0, int y0, int x1, int y1, int w, int h)
+{
+ DisplaySurface *surface = qemu_console_surface(s->vga.con);
+ uint8_t *vram = s->vga.vram_ptr;
+ int bypl = surface_stride(surface);
+ int bypp = surface_bytes_per_pixel(surface);
+ int width = bypp * w;
+ int line = h;
+ uint8_t *ptr[2];
+
+ if (!vmsvga_verify_rect(surface, "vmsvga_copy_rect/src", x0, y0, w, h)) {
+ return -1;
+ }
+ if (!vmsvga_verify_rect(surface, "vmsvga_copy_rect/dst", x1, y1, w, h)) {
+ return -1;
+ }
+
+ if (y1 > y0) {
+ ptr[0] = vram + bypp * x0 + bypl * (y0 + h - 1);
+ ptr[1] = vram + bypp * x1 + bypl * (y1 + h - 1);
+ for (; line > 0; line --, ptr[0] -= bypl, ptr[1] -= bypl) {
+ memmove(ptr[1], ptr[0], width);
+ }
+ } else {
+ ptr[0] = vram + bypp * x0 + bypl * y0;
+ ptr[1] = vram + bypp * x1 + bypl * y1;
+ for (; line > 0; line --, ptr[0] += bypl, ptr[1] += bypl) {
+ memmove(ptr[1], ptr[0], width);
+ }
+ }
+
+ vmsvga_update_rect_delayed(s, x1, y1, w, h);
+ return 0;
+}
+#endif
+
+#ifdef HW_FILL_ACCEL
+static inline int vmsvga_fill_rect(struct vmsvga_state_s *s,
+ uint32_t c, int x, int y, int w, int h)
+{
+ DisplaySurface *surface = qemu_console_surface(s->vga.con);
+ int bypl = surface_stride(surface);
+ int width = surface_bytes_per_pixel(surface) * w;
+ int line = h;
+ int column;
+ uint8_t *fst;
+ uint8_t *dst;
+ uint8_t *src;
+ uint8_t col[4];
+
+ if (!vmsvga_verify_rect(surface, __func__, x, y, w, h)) {
+ return -1;
+ }
+
+ col[0] = c;
+ col[1] = c >> 8;
+ col[2] = c >> 16;
+ col[3] = c >> 24;
+
+ fst = s->vga.vram_ptr + surface_bytes_per_pixel(surface) * x + bypl * y;
+
+ if (line--) {
+ dst = fst;
+ src = col;
+ for (column = width; column > 0; column--) {
+ *(dst++) = *(src++);
+ if (src - col == surface_bytes_per_pixel(surface)) {
+ src = col;
+ }
+ }
+ dst = fst;
+ for (; line > 0; line--) {
+ dst += bypl;
+ memcpy(dst, fst, width);
+ }
+ }
+
+ vmsvga_update_rect_delayed(s, x, y, w, h);
+ return 0;
+}
+#endif
+
+struct vmsvga_cursor_definition_s {
+ uint32_t width;
+ uint32_t height;
+ int id;
+ uint32_t bpp;
+ int hot_x;
+ int hot_y;
+ uint32_t mask[1024];
+ uint32_t image[4096];
+};
+
+#define SVGA_BITMAP_SIZE(w, h) ((((w) + 31) >> 5) * (h))
+#define SVGA_PIXMAP_SIZE(w, h, bpp) (((((w) * (bpp)) + 31) >> 5) * (h))
+
+#ifdef HW_MOUSE_ACCEL
+static inline void vmsvga_cursor_define(struct vmsvga_state_s *s,
+ struct vmsvga_cursor_definition_s *c)
+{
+ QEMUCursor *qc;
+ int i, pixels;
+
+ qc = cursor_alloc(c->width, c->height);
+ qc->hot_x = c->hot_x;
+ qc->hot_y = c->hot_y;
+ switch (c->bpp) {
+ case 1:
+ cursor_set_mono(qc, 0xffffff, 0x000000, (void *)c->image,
+ 1, (void *)c->mask);
+#ifdef DEBUG
+ cursor_print_ascii_art(qc, "vmware/mono");
+#endif
+ break;
+ case 32:
+ /* fill alpha channel from mask, set color to zero */
+ cursor_set_mono(qc, 0x000000, 0x000000, (void *)c->mask,
+ 1, (void *)c->mask);
+ /* add in rgb values */
+ pixels = c->width * c->height;
+ for (i = 0; i < pixels; i++) {
+ qc->data[i] |= c->image[i] & 0xffffff;
+ }
+#ifdef DEBUG
+ cursor_print_ascii_art(qc, "vmware/32bit");
+#endif
+ break;
+ default:
+ fprintf(stderr, "%s: unhandled bpp %d, using fallback cursor\n",
+ __func__, c->bpp);
+ cursor_put(qc);
+ qc = cursor_builtin_left_ptr();
+ }
+
+ dpy_cursor_define(s->vga.con, qc);
+ cursor_put(qc);
+}
+#endif
+
+static inline int vmsvga_fifo_length(struct vmsvga_state_s *s)
+{
+ int num;
+
+ if (!s->config || !s->enable) {
+ return 0;
+ }
+
+ s->fifo_min = le32_to_cpu(s->fifo[SVGA_FIFO_MIN]);
+ s->fifo_max = le32_to_cpu(s->fifo[SVGA_FIFO_MAX]);
+ s->fifo_next = le32_to_cpu(s->fifo[SVGA_FIFO_NEXT]);
+ s->fifo_stop = le32_to_cpu(s->fifo[SVGA_FIFO_STOP]);
+
+ /* Check range and alignment. */
+ if ((s->fifo_min | s->fifo_max | s->fifo_next | s->fifo_stop) & 3) {
+ return 0;
+ }
+ if (s->fifo_min < sizeof(uint32_t) * 4) {
+ return 0;
+ }
+ if (s->fifo_max > SVGA_FIFO_SIZE ||
+ s->fifo_min >= SVGA_FIFO_SIZE ||
+ s->fifo_stop >= SVGA_FIFO_SIZE ||
+ s->fifo_next >= SVGA_FIFO_SIZE) {
+ return 0;
+ }
+ if (s->fifo_max < s->fifo_min + 10 * KiB) {
+ return 0;
+ }
+
+ num = s->fifo_next - s->fifo_stop;
+ if (num < 0) {
+ num += s->fifo_max - s->fifo_min;
+ }
+ return num >> 2;
+}
+
+static inline uint32_t vmsvga_fifo_read_raw(struct vmsvga_state_s *s)
+{
+ uint32_t cmd = s->fifo[s->fifo_stop >> 2];
+
+ s->fifo_stop += 4;
+ if (s->fifo_stop >= s->fifo_max) {
+ s->fifo_stop = s->fifo_min;
+ }
+ s->fifo[SVGA_FIFO_STOP] = cpu_to_le32(s->fifo_stop);
+ return cmd;
+}
+
+static inline uint32_t vmsvga_fifo_read(struct vmsvga_state_s *s)
+{
+ return le32_to_cpu(vmsvga_fifo_read_raw(s));
+}
+
+static void vmsvga_fifo_run(struct vmsvga_state_s *s)
+{
+ uint32_t cmd, colour;
+ int args, len, maxloop = 1024;
+ int x, y, dx, dy, width, height;
+ struct vmsvga_cursor_definition_s cursor;
+ uint32_t cmd_start;
+
+ len = vmsvga_fifo_length(s);
+ while (len > 0 && --maxloop > 0) {
+ /* May need to go back to the start of the command if incomplete */
+ cmd_start = s->fifo_stop;
+
+ switch (cmd = vmsvga_fifo_read(s)) {
+ case SVGA_CMD_UPDATE:
+ case SVGA_CMD_UPDATE_VERBOSE:
+ len -= 5;
+ if (len < 0) {
+ goto rewind;
+ }
+
+ x = vmsvga_fifo_read(s);
+ y = vmsvga_fifo_read(s);
+ width = vmsvga_fifo_read(s);
+ height = vmsvga_fifo_read(s);
+ vmsvga_update_rect_delayed(s, x, y, width, height);
+ break;
+
+ case SVGA_CMD_RECT_FILL:
+ len -= 6;
+ if (len < 0) {
+ goto rewind;
+ }
+
+ colour = vmsvga_fifo_read(s);
+ x = vmsvga_fifo_read(s);
+ y = vmsvga_fifo_read(s);
+ width = vmsvga_fifo_read(s);
+ height = vmsvga_fifo_read(s);
+#ifdef HW_FILL_ACCEL
+ if (vmsvga_fill_rect(s, colour, x, y, width, height) == 0) {
+ break;
+ }
+#endif
+ args = 0;
+ goto badcmd;
+
+ case SVGA_CMD_RECT_COPY:
+ len -= 7;
+ if (len < 0) {
+ goto rewind;
+ }
+
+ x = vmsvga_fifo_read(s);
+ y = vmsvga_fifo_read(s);
+ dx = vmsvga_fifo_read(s);
+ dy = vmsvga_fifo_read(s);
+ width = vmsvga_fifo_read(s);
+ height = vmsvga_fifo_read(s);
+#ifdef HW_RECT_ACCEL
+ if (vmsvga_copy_rect(s, x, y, dx, dy, width, height) == 0) {
+ break;
+ }
+#endif
+ args = 0;
+ goto badcmd;
+
+ case SVGA_CMD_DEFINE_CURSOR:
+ len -= 8;
+ if (len < 0) {
+ goto rewind;
+ }
+
+ cursor.id = vmsvga_fifo_read(s);
+ cursor.hot_x = vmsvga_fifo_read(s);
+ cursor.hot_y = vmsvga_fifo_read(s);
+ cursor.width = x = vmsvga_fifo_read(s);
+ cursor.height = y = vmsvga_fifo_read(s);
+ vmsvga_fifo_read(s);
+ cursor.bpp = vmsvga_fifo_read(s);
+
+ args = SVGA_BITMAP_SIZE(x, y) + SVGA_PIXMAP_SIZE(x, y, cursor.bpp);
+ if (cursor.width > 256
+ || cursor.height > 256
+ || cursor.bpp > 32
+ || SVGA_BITMAP_SIZE(x, y) > ARRAY_SIZE(cursor.mask)
+ || SVGA_PIXMAP_SIZE(x, y, cursor.bpp)
+ > ARRAY_SIZE(cursor.image)) {
+ goto badcmd;
+ }
+
+ len -= args;
+ if (len < 0) {
+ goto rewind;
+ }
+
+ for (args = 0; args < SVGA_BITMAP_SIZE(x, y); args++) {
+ cursor.mask[args] = vmsvga_fifo_read_raw(s);
+ }
+ for (args = 0; args < SVGA_PIXMAP_SIZE(x, y, cursor.bpp); args++) {
+ cursor.image[args] = vmsvga_fifo_read_raw(s);
+ }
+#ifdef HW_MOUSE_ACCEL
+ vmsvga_cursor_define(s, &cursor);
+ break;
+#else
+ args = 0;
+ goto badcmd;
+#endif
+
+ /*
+ * Other commands that we at least know the number of arguments
+ * for so we can avoid FIFO desync if driver uses them illegally.
+ */
+ case SVGA_CMD_DEFINE_ALPHA_CURSOR:
+ len -= 6;
+ if (len < 0) {
+ goto rewind;
+ }
+ vmsvga_fifo_read(s);
+ vmsvga_fifo_read(s);
+ vmsvga_fifo_read(s);
+ x = vmsvga_fifo_read(s);
+ y = vmsvga_fifo_read(s);
+ args = x * y;
+ goto badcmd;
+ case SVGA_CMD_RECT_ROP_FILL:
+ args = 6;
+ goto badcmd;
+ case SVGA_CMD_RECT_ROP_COPY:
+ args = 7;
+ goto badcmd;
+ case SVGA_CMD_DRAW_GLYPH_CLIPPED:
+ len -= 4;
+ if (len < 0) {
+ goto rewind;
+ }
+ vmsvga_fifo_read(s);
+ vmsvga_fifo_read(s);
+ args = 7 + (vmsvga_fifo_read(s) >> 2);
+ goto badcmd;
+ case SVGA_CMD_SURFACE_ALPHA_BLEND:
+ args = 12;
+ goto badcmd;
+
+ /*
+ * Other commands that are not listed as depending on any
+ * CAPABILITIES bits, but are not described in the README either.
+ */
+ case SVGA_CMD_SURFACE_FILL:
+ case SVGA_CMD_SURFACE_COPY:
+ case SVGA_CMD_FRONT_ROP_FILL:
+ case SVGA_CMD_FENCE:
+ case SVGA_CMD_INVALID_CMD:
+ break; /* Nop */
+
+ default:
+ args = 0;
+ badcmd:
+ len -= args;
+ if (len < 0) {
+ goto rewind;
+ }
+ while (args--) {
+ vmsvga_fifo_read(s);
+ }
+ printf("%s: Unknown command 0x%02x in SVGA command FIFO\n",
+ __func__, cmd);
+ break;
+
+ rewind:
+ s->fifo_stop = cmd_start;
+ s->fifo[SVGA_FIFO_STOP] = cpu_to_le32(s->fifo_stop);
+ break;
+ }
+ }
+
+ s->syncing = 0;
+}
+
+static uint32_t vmsvga_index_read(void *opaque, uint32_t address)
+{
+ struct vmsvga_state_s *s = opaque;
+
+ return s->index;
+}
+
+static void vmsvga_index_write(void *opaque, uint32_t address, uint32_t index)
+{
+ struct vmsvga_state_s *s = opaque;
+
+ s->index = index;
+}
+
+static uint32_t vmsvga_value_read(void *opaque, uint32_t address)
+{
+ uint32_t caps;
+ struct vmsvga_state_s *s = opaque;
+ DisplaySurface *surface = qemu_console_surface(s->vga.con);
+ PixelFormat pf;
+ uint32_t ret;
+
+ switch (s->index) {
+ case SVGA_REG_ID:
+ ret = s->svgaid;
+ break;
+
+ case SVGA_REG_ENABLE:
+ ret = s->enable;
+ break;
+
+ case SVGA_REG_WIDTH:
+ ret = s->new_width ? s->new_width : surface_width(surface);
+ break;
+
+ case SVGA_REG_HEIGHT:
+ ret = s->new_height ? s->new_height : surface_height(surface);
+ break;
+
+ case SVGA_REG_MAX_WIDTH:
+ ret = SVGA_MAX_WIDTH;
+ break;
+
+ case SVGA_REG_MAX_HEIGHT:
+ ret = SVGA_MAX_HEIGHT;
+ break;
+
+ case SVGA_REG_DEPTH:
+ ret = (s->new_depth == 32) ? 24 : s->new_depth;
+ break;
+
+ case SVGA_REG_BITS_PER_PIXEL:
+ case SVGA_REG_HOST_BITS_PER_PIXEL:
+ ret = s->new_depth;
+ break;
+
+ case SVGA_REG_PSEUDOCOLOR:
+ ret = 0x0;
+ break;
+
+ case SVGA_REG_RED_MASK:
+ pf = qemu_default_pixelformat(s->new_depth);
+ ret = pf.rmask;
+ break;
+
+ case SVGA_REG_GREEN_MASK:
+ pf = qemu_default_pixelformat(s->new_depth);
+ ret = pf.gmask;
+ break;
+
+ case SVGA_REG_BLUE_MASK:
+ pf = qemu_default_pixelformat(s->new_depth);
+ ret = pf.bmask;
+ break;
+
+ case SVGA_REG_BYTES_PER_LINE:
+ if (s->new_width) {
+ ret = (s->new_depth * s->new_width) / 8;
+ } else {
+ ret = surface_stride(surface);
+ }
+ break;
+
+ case SVGA_REG_FB_START: {
+ struct pci_vmsvga_state_s *pci_vmsvga
+ = container_of(s, struct pci_vmsvga_state_s, chip);
+ ret = pci_get_bar_addr(PCI_DEVICE(pci_vmsvga), 1);
+ break;
+ }
+
+ case SVGA_REG_FB_OFFSET:
+ ret = 0x0;
+ break;
+
+ case SVGA_REG_VRAM_SIZE:
+ ret = s->vga.vram_size; /* No physical VRAM besides the framebuffer */
+ break;
+
+ case SVGA_REG_FB_SIZE:
+ ret = s->vga.vram_size;
+ break;
+
+ case SVGA_REG_CAPABILITIES:
+ caps = SVGA_CAP_NONE;
+#ifdef HW_RECT_ACCEL
+ caps |= SVGA_CAP_RECT_COPY;
+#endif
+#ifdef HW_FILL_ACCEL
+ caps |= SVGA_CAP_RECT_FILL;
+#endif
+#ifdef HW_MOUSE_ACCEL
+ if (dpy_cursor_define_supported(s->vga.con)) {
+ caps |= SVGA_CAP_CURSOR | SVGA_CAP_CURSOR_BYPASS_2 |
+ SVGA_CAP_CURSOR_BYPASS;
+ }
+#endif
+ ret = caps;
+ break;
+
+ case SVGA_REG_MEM_START: {
+ struct pci_vmsvga_state_s *pci_vmsvga
+ = container_of(s, struct pci_vmsvga_state_s, chip);
+ ret = pci_get_bar_addr(PCI_DEVICE(pci_vmsvga), 2);
+ break;
+ }
+
+ case SVGA_REG_MEM_SIZE:
+ ret = s->fifo_size;
+ break;
+
+ case SVGA_REG_CONFIG_DONE:
+ ret = s->config;
+ break;
+
+ case SVGA_REG_SYNC:
+ case SVGA_REG_BUSY:
+ ret = s->syncing;
+ break;
+
+ case SVGA_REG_GUEST_ID:
+ ret = s->guest;
+ break;
+
+ case SVGA_REG_CURSOR_ID:
+ ret = s->cursor.id;
+ break;
+
+ case SVGA_REG_CURSOR_X:
+ ret = s->cursor.x;
+ break;
+
+ case SVGA_REG_CURSOR_Y:
+ ret = s->cursor.y;
+ break;
+
+ case SVGA_REG_CURSOR_ON:
+ ret = s->cursor.on;
+ break;
+
+ case SVGA_REG_SCRATCH_SIZE:
+ ret = s->scratch_size;
+ break;
+
+ case SVGA_REG_MEM_REGS:
+ case SVGA_REG_NUM_DISPLAYS:
+ case SVGA_REG_PITCHLOCK:
+ case SVGA_PALETTE_BASE ... SVGA_PALETTE_END:
+ ret = 0;
+ break;
+
+ default:
+ if (s->index >= SVGA_SCRATCH_BASE &&
+ s->index < SVGA_SCRATCH_BASE + s->scratch_size) {
+ ret = s->scratch[s->index - SVGA_SCRATCH_BASE];
+ break;
+ }
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Bad register %02x\n", __func__, s->index);
+ ret = 0;
+ break;
+ }
+
+ if (s->index >= SVGA_SCRATCH_BASE) {
+ trace_vmware_scratch_read(s->index, ret);
+ } else if (s->index >= SVGA_PALETTE_BASE) {
+ trace_vmware_palette_read(s->index, ret);
+ } else {
+ trace_vmware_value_read(s->index, ret);
+ }
+ return ret;
+}
+
+static void vmsvga_value_write(void *opaque, uint32_t address, uint32_t value)
+{
+ struct vmsvga_state_s *s = opaque;
+
+ if (s->index >= SVGA_SCRATCH_BASE) {
+ trace_vmware_scratch_write(s->index, value);
+ } else if (s->index >= SVGA_PALETTE_BASE) {
+ trace_vmware_palette_write(s->index, value);
+ } else {
+ trace_vmware_value_write(s->index, value);
+ }
+ switch (s->index) {
+ case SVGA_REG_ID:
+ if (value == SVGA_ID_2 || value == SVGA_ID_1 || value == SVGA_ID_0) {
+ s->svgaid = value;
+ }
+ break;
+
+ case SVGA_REG_ENABLE:
+ s->enable = !!value;
+ s->invalidated = 1;
+ s->vga.hw_ops->invalidate(&s->vga);
+ if (s->enable && s->config) {
+ vga_dirty_log_stop(&s->vga);
+ } else {
+ vga_dirty_log_start(&s->vga);
+ }
+ break;
+
+ case SVGA_REG_WIDTH:
+ if (value <= SVGA_MAX_WIDTH) {
+ s->new_width = value;
+ s->invalidated = 1;
+ } else {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Bad width: %i\n", __func__, value);
+ }
+ break;
+
+ case SVGA_REG_HEIGHT:
+ if (value <= SVGA_MAX_HEIGHT) {
+ s->new_height = value;
+ s->invalidated = 1;
+ } else {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Bad height: %i\n", __func__, value);
+ }
+ break;
+
+ case SVGA_REG_BITS_PER_PIXEL:
+ if (value != 32) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Bad bits per pixel: %i bits\n", __func__, value);
+ s->config = 0;
+ s->invalidated = 1;
+ }
+ break;
+
+ case SVGA_REG_CONFIG_DONE:
+ if (value) {
+ s->fifo = (uint32_t *) s->fifo_ptr;
+ vga_dirty_log_stop(&s->vga);
+ }
+ s->config = !!value;
+ break;
+
+ case SVGA_REG_SYNC:
+ s->syncing = 1;
+ vmsvga_fifo_run(s); /* Or should we just wait for update_display? */
+ break;
+
+ case SVGA_REG_GUEST_ID:
+ s->guest = value;
+#ifdef VERBOSE
+ if (value >= GUEST_OS_BASE && value < GUEST_OS_BASE +
+ ARRAY_SIZE(vmsvga_guest_id)) {
+ printf("%s: guest runs %s.\n", __func__,
+ vmsvga_guest_id[value - GUEST_OS_BASE]);
+ }
+#endif
+ break;
+
+ case SVGA_REG_CURSOR_ID:
+ s->cursor.id = value;
+ break;
+
+ case SVGA_REG_CURSOR_X:
+ s->cursor.x = value;
+ break;
+
+ case SVGA_REG_CURSOR_Y:
+ s->cursor.y = value;
+ break;
+
+ case SVGA_REG_CURSOR_ON:
+ s->cursor.on |= (value == SVGA_CURSOR_ON_SHOW);
+ s->cursor.on &= (value != SVGA_CURSOR_ON_HIDE);
+#ifdef HW_MOUSE_ACCEL
+ if (value <= SVGA_CURSOR_ON_SHOW) {
+ dpy_mouse_set(s->vga.con, s->cursor.x, s->cursor.y, s->cursor.on);
+ }
+#endif
+ break;
+
+ case SVGA_REG_DEPTH:
+ case SVGA_REG_MEM_REGS:
+ case SVGA_REG_NUM_DISPLAYS:
+ case SVGA_REG_PITCHLOCK:
+ case SVGA_PALETTE_BASE ... SVGA_PALETTE_END:
+ break;
+
+ default:
+ if (s->index >= SVGA_SCRATCH_BASE &&
+ s->index < SVGA_SCRATCH_BASE + s->scratch_size) {
+ s->scratch[s->index - SVGA_SCRATCH_BASE] = value;
+ break;
+ }
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Bad register %02x\n", __func__, s->index);
+ }
+}
+
+static uint32_t vmsvga_bios_read(void *opaque, uint32_t address)
+{
+ printf("%s: what are we supposed to return?\n", __func__);
+ return 0xcafe;
+}
+
+static void vmsvga_bios_write(void *opaque, uint32_t address, uint32_t data)
+{
+ printf("%s: what are we supposed to do with (%08x)?\n", __func__, data);
+}
+
+static inline void vmsvga_check_size(struct vmsvga_state_s *s)
+{
+ DisplaySurface *surface = qemu_console_surface(s->vga.con);
+
+ if (s->new_width != surface_width(surface) ||
+ s->new_height != surface_height(surface) ||
+ s->new_depth != surface_bits_per_pixel(surface)) {
+ int stride = (s->new_depth * s->new_width) / 8;
+ pixman_format_code_t format =
+ qemu_default_pixman_format(s->new_depth, true);
+ trace_vmware_setmode(s->new_width, s->new_height, s->new_depth);
+ surface = qemu_create_displaysurface_from(s->new_width, s->new_height,
+ format, stride,
+ s->vga.vram_ptr);
+ dpy_gfx_replace_surface(s->vga.con, surface);
+ s->invalidated = 1;
+ }
+}
+
+static void vmsvga_update_display(void *opaque)
+{
+ struct vmsvga_state_s *s = opaque;
+
+ if (!s->enable || !s->config) {
+ /* in standard vga mode */
+ s->vga.hw_ops->gfx_update(&s->vga);
+ return;
+ }
+
+ vmsvga_check_size(s);
+
+ vmsvga_fifo_run(s);
+ vmsvga_update_rect_flush(s);
+
+ if (s->invalidated) {
+ s->invalidated = 0;
+ dpy_gfx_update_full(s->vga.con);
+ }
+}
+
+static void vmsvga_reset(DeviceState *dev)
+{
+ struct pci_vmsvga_state_s *pci = VMWARE_SVGA(dev);
+ struct vmsvga_state_s *s = &pci->chip;
+
+ s->index = 0;
+ s->enable = 0;
+ s->config = 0;
+ s->svgaid = SVGA_ID;
+ s->cursor.on = 0;
+ s->redraw_fifo_first = 0;
+ s->redraw_fifo_last = 0;
+ s->syncing = 0;
+
+ vga_dirty_log_start(&s->vga);
+}
+
+static void vmsvga_invalidate_display(void *opaque)
+{
+ struct vmsvga_state_s *s = opaque;
+ if (!s->enable) {
+ s->vga.hw_ops->invalidate(&s->vga);
+ return;
+ }
+
+ s->invalidated = 1;
+}
+
+static void vmsvga_text_update(void *opaque, console_ch_t *chardata)
+{
+ struct vmsvga_state_s *s = opaque;
+
+ if (s->vga.hw_ops->text_update) {
+ s->vga.hw_ops->text_update(&s->vga, chardata);
+ }
+}
+
+static int vmsvga_post_load(void *opaque, int version_id)
+{
+ struct vmsvga_state_s *s = opaque;
+
+ s->invalidated = 1;
+ if (s->config) {
+ s->fifo = (uint32_t *) s->fifo_ptr;
+ }
+ return 0;
+}
+
+static const VMStateDescription vmstate_vmware_vga_internal = {
+ .name = "vmware_vga_internal",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .post_load = vmsvga_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32_EQUAL(new_depth, struct vmsvga_state_s, NULL),
+ VMSTATE_INT32(enable, struct vmsvga_state_s),
+ VMSTATE_INT32(config, struct vmsvga_state_s),
+ VMSTATE_INT32(cursor.id, struct vmsvga_state_s),
+ VMSTATE_INT32(cursor.x, struct vmsvga_state_s),
+ VMSTATE_INT32(cursor.y, struct vmsvga_state_s),
+ VMSTATE_INT32(cursor.on, struct vmsvga_state_s),
+ VMSTATE_INT32(index, struct vmsvga_state_s),
+ VMSTATE_VARRAY_INT32(scratch, struct vmsvga_state_s,
+ scratch_size, 0, vmstate_info_uint32, uint32_t),
+ VMSTATE_INT32(new_width, struct vmsvga_state_s),
+ VMSTATE_INT32(new_height, struct vmsvga_state_s),
+ VMSTATE_UINT32(guest, struct vmsvga_state_s),
+ VMSTATE_UINT32(svgaid, struct vmsvga_state_s),
+ VMSTATE_INT32(syncing, struct vmsvga_state_s),
+ VMSTATE_UNUSED(4), /* was fb_size */
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_vmware_vga = {
+ .name = "vmware_vga",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(parent_obj, struct pci_vmsvga_state_s),
+ VMSTATE_STRUCT(chip, struct pci_vmsvga_state_s, 0,
+ vmstate_vmware_vga_internal, struct vmsvga_state_s),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const GraphicHwOps vmsvga_ops = {
+ .invalidate = vmsvga_invalidate_display,
+ .gfx_update = vmsvga_update_display,
+ .text_update = vmsvga_text_update,
+};
+
+static void vmsvga_init(DeviceState *dev, struct vmsvga_state_s *s,
+ MemoryRegion *address_space, MemoryRegion *io)
+{
+ s->scratch_size = SVGA_SCRATCH_SIZE;
+ s->scratch = g_malloc(s->scratch_size * 4);
+
+ s->vga.con = graphic_console_init(dev, 0, &vmsvga_ops, s);
+
+ s->fifo_size = SVGA_FIFO_SIZE;
+ memory_region_init_ram(&s->fifo_ram, NULL, "vmsvga.fifo", s->fifo_size,
+ &error_fatal);
+ s->fifo_ptr = memory_region_get_ram_ptr(&s->fifo_ram);
+
+ vga_common_init(&s->vga, OBJECT(dev));
+ vga_init(&s->vga, OBJECT(dev), address_space, io, true);
+ vmstate_register(NULL, 0, &vmstate_vga_common, &s->vga);
+ s->new_depth = 32;
+}
+
+static uint64_t vmsvga_io_read(void *opaque, hwaddr addr, unsigned size)
+{
+ struct vmsvga_state_s *s = opaque;
+
+ switch (addr) {
+ case SVGA_IO_MUL * SVGA_INDEX_PORT: return vmsvga_index_read(s, addr);
+ case SVGA_IO_MUL * SVGA_VALUE_PORT: return vmsvga_value_read(s, addr);
+ case SVGA_IO_MUL * SVGA_BIOS_PORT: return vmsvga_bios_read(s, addr);
+ default: return -1u;
+ }
+}
+
+static void vmsvga_io_write(void *opaque, hwaddr addr,
+ uint64_t data, unsigned size)
+{
+ struct vmsvga_state_s *s = opaque;
+
+ switch (addr) {
+ case SVGA_IO_MUL * SVGA_INDEX_PORT:
+ vmsvga_index_write(s, addr, data);
+ break;
+ case SVGA_IO_MUL * SVGA_VALUE_PORT:
+ vmsvga_value_write(s, addr, data);
+ break;
+ case SVGA_IO_MUL * SVGA_BIOS_PORT:
+ vmsvga_bios_write(s, addr, data);
+ break;
+ }
+}
+
+static const MemoryRegionOps vmsvga_io_ops = {
+ .read = vmsvga_io_read,
+ .write = vmsvga_io_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ .unaligned = true,
+ },
+ .impl = {
+ .unaligned = true,
+ },
+};
+
+static void pci_vmsvga_realize(PCIDevice *dev, Error **errp)
+{
+ struct pci_vmsvga_state_s *s = VMWARE_SVGA(dev);
+
+ dev->config[PCI_CACHE_LINE_SIZE] = 0x08;
+ dev->config[PCI_LATENCY_TIMER] = 0x40;
+ dev->config[PCI_INTERRUPT_LINE] = 0xff; /* End */
+
+ memory_region_init_io(&s->io_bar, OBJECT(dev), &vmsvga_io_ops, &s->chip,
+ "vmsvga-io", 0x10);
+ memory_region_set_flush_coalesced(&s->io_bar);
+ pci_register_bar(dev, 0, PCI_BASE_ADDRESS_SPACE_IO, &s->io_bar);
+
+ vmsvga_init(DEVICE(dev), &s->chip,
+ pci_address_space(dev), pci_address_space_io(dev));
+
+ pci_register_bar(dev, 1, PCI_BASE_ADDRESS_MEM_PREFETCH,
+ &s->chip.vga.vram);
+ pci_register_bar(dev, 2, PCI_BASE_ADDRESS_MEM_PREFETCH,
+ &s->chip.fifo_ram);
+}
+
+static Property vga_vmware_properties[] = {
+ DEFINE_PROP_UINT32("vgamem_mb", struct pci_vmsvga_state_s,
+ chip.vga.vram_size_mb, 16),
+ DEFINE_PROP_BOOL("global-vmstate", struct pci_vmsvga_state_s,
+ chip.vga.global_vmstate, false),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void vmsvga_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->realize = pci_vmsvga_realize;
+ k->romfile = "vgabios-vmware.bin";
+ k->vendor_id = PCI_VENDOR_ID_VMWARE;
+ k->device_id = SVGA_PCI_DEVICE_ID;
+ k->class_id = PCI_CLASS_DISPLAY_VGA;
+ k->subsystem_vendor_id = PCI_VENDOR_ID_VMWARE;
+ k->subsystem_id = SVGA_PCI_DEVICE_ID;
+ dc->reset = vmsvga_reset;
+ dc->vmsd = &vmstate_vmware_vga;
+ device_class_set_props(dc, vga_vmware_properties);
+ dc->hotpluggable = false;
+ set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories);
+}
+
+static const TypeInfo vmsvga_info = {
+ .name = TYPE_VMWARE_SVGA,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(struct pci_vmsvga_state_s),
+ .class_init = vmsvga_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { INTERFACE_CONVENTIONAL_PCI_DEVICE },
+ { },
+ },
+};
+
+static void vmsvga_register_types(void)
+{
+ type_register_static(&vmsvga_info);
+}
+
+type_init(vmsvga_register_types)
diff --git a/hw/display/xenfb.c b/hw/display/xenfb.c
new file mode 100644
index 000000000..838260b6a
--- /dev/null
+++ b/hw/display/xenfb.c
@@ -0,0 +1,987 @@
+/*
+ * xen paravirt framebuffer backend
+ *
+ * Copyright IBM, Corp. 2005-2006
+ * Copyright Red Hat, Inc. 2006-2008
+ *
+ * Authors:
+ * Anthony Liguori <aliguori@us.ibm.com>,
+ * Markus Armbruster <armbru@redhat.com>,
+ * Daniel P. Berrange <berrange@redhat.com>,
+ * Pat Campbell <plc@novell.com>,
+ * Gerd Hoffmann <kraxel@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; under version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/units.h"
+
+#include "ui/input.h"
+#include "ui/console.h"
+#include "hw/xen/xen-legacy-backend.h"
+
+#include "hw/xen/interface/io/fbif.h"
+#include "hw/xen/interface/io/kbdif.h"
+#include "hw/xen/interface/io/protocols.h"
+
+#include "trace.h"
+
+#ifndef BTN_LEFT
+#define BTN_LEFT 0x110 /* from <linux/input.h> */
+#endif
+
+/* -------------------------------------------------------------------- */
+
+struct common {
+ struct XenLegacyDevice xendev; /* must be first */
+ void *page;
+};
+
+struct XenInput {
+ struct common c;
+ int abs_pointer_wanted; /* Whether guest supports absolute pointer */
+ int raw_pointer_wanted; /* Whether guest supports raw (unscaled) pointer */
+ QemuInputHandlerState *qkbd;
+ QemuInputHandlerState *qmou;
+ int axis[INPUT_AXIS__MAX];
+ int wheel;
+};
+
+#define UP_QUEUE 8
+
+struct XenFB {
+ struct common c;
+ QemuConsole *con;
+ size_t fb_len;
+ int row_stride;
+ int depth;
+ int width;
+ int height;
+ int offset;
+ void *pixels;
+ int fbpages;
+ int feature_update;
+ int bug_trigger;
+ int do_resize;
+
+ struct {
+ int x,y,w,h;
+ } up_rects[UP_QUEUE];
+ int up_count;
+ int up_fullscreen;
+};
+static const GraphicHwOps xenfb_ops;
+
+/* -------------------------------------------------------------------- */
+
+static int common_bind(struct common *c)
+{
+ uint64_t val;
+ xen_pfn_t mfn;
+
+ if (xenstore_read_fe_uint64(&c->xendev, "page-ref", &val) == -1)
+ return -1;
+ mfn = (xen_pfn_t)val;
+ assert(val == mfn);
+
+ if (xenstore_read_fe_int(&c->xendev, "event-channel", &c->xendev.remote_port) == -1)
+ return -1;
+
+ c->page = xenforeignmemory_map(xen_fmem, c->xendev.dom,
+ PROT_READ | PROT_WRITE, 1, &mfn, NULL);
+ if (c->page == NULL)
+ return -1;
+
+ xen_be_bind_evtchn(&c->xendev);
+ xen_pv_printf(&c->xendev, 1,
+ "ring mfn %"PRI_xen_pfn", remote-port %d, local-port %d\n",
+ mfn, c->xendev.remote_port, c->xendev.local_port);
+
+ return 0;
+}
+
+static void common_unbind(struct common *c)
+{
+ xen_pv_unbind_evtchn(&c->xendev);
+ if (c->page) {
+ xenforeignmemory_unmap(xen_fmem, c->page, 1);
+ c->page = NULL;
+ }
+}
+
+/* -------------------------------------------------------------------- */
+/* Send an event to the keyboard frontend driver */
+static int xenfb_kbd_event(struct XenInput *xenfb,
+ union xenkbd_in_event *event)
+{
+ struct xenkbd_page *page = xenfb->c.page;
+ uint32_t prod;
+
+ if (xenfb->c.xendev.be_state != XenbusStateConnected)
+ return 0;
+ if (!page)
+ return 0;
+
+ prod = page->in_prod;
+ if (prod - page->in_cons == XENKBD_IN_RING_LEN) {
+ errno = EAGAIN;
+ return -1;
+ }
+
+ xen_mb(); /* ensure ring space available */
+ XENKBD_IN_RING_REF(page, prod) = *event;
+ xen_wmb(); /* ensure ring contents visible */
+ page->in_prod = prod + 1;
+ return xen_pv_send_notify(&xenfb->c.xendev);
+}
+
+/* Send a keyboard (or mouse button) event */
+static int xenfb_send_key(struct XenInput *xenfb, bool down, int keycode)
+{
+ union xenkbd_in_event event;
+
+ memset(&event, 0, XENKBD_IN_EVENT_SIZE);
+ event.type = XENKBD_TYPE_KEY;
+ event.key.pressed = down ? 1 : 0;
+ event.key.keycode = keycode;
+
+ return xenfb_kbd_event(xenfb, &event);
+}
+
+/* Send a relative mouse movement event */
+static int xenfb_send_motion(struct XenInput *xenfb,
+ int rel_x, int rel_y, int rel_z)
+{
+ union xenkbd_in_event event;
+
+ memset(&event, 0, XENKBD_IN_EVENT_SIZE);
+ event.type = XENKBD_TYPE_MOTION;
+ event.motion.rel_x = rel_x;
+ event.motion.rel_y = rel_y;
+ event.motion.rel_z = rel_z;
+
+ return xenfb_kbd_event(xenfb, &event);
+}
+
+/* Send an absolute mouse movement event */
+static int xenfb_send_position(struct XenInput *xenfb,
+ int abs_x, int abs_y, int z)
+{
+ union xenkbd_in_event event;
+
+ memset(&event, 0, XENKBD_IN_EVENT_SIZE);
+ event.type = XENKBD_TYPE_POS;
+ event.pos.abs_x = abs_x;
+ event.pos.abs_y = abs_y;
+ event.pos.rel_z = z;
+
+ return xenfb_kbd_event(xenfb, &event);
+}
+
+/*
+ * Send a key event from the client to the guest OS
+ * QEMU gives us a QCode.
+ * We have to turn this into a Linux Input layer keycode.
+ *
+ * Wish we could just send scancodes straight to the guest which
+ * already has code for dealing with this...
+ */
+static void xenfb_key_event(DeviceState *dev, QemuConsole *src,
+ InputEvent *evt)
+{
+ struct XenInput *xenfb = (struct XenInput *)dev;
+ InputKeyEvent *key = evt->u.key.data;
+ int qcode = qemu_input_key_value_to_qcode(key->key);
+ int lnx;
+
+ if (qcode < qemu_input_map_qcode_to_linux_len) {
+ lnx = qemu_input_map_qcode_to_linux[qcode];
+
+ if (lnx) {
+ trace_xenfb_key_event(xenfb, lnx, key->down);
+ xenfb_send_key(xenfb, key->down, lnx);
+ }
+ }
+}
+
+/*
+ * Send a mouse event from the client to the guest OS
+ *
+ * The QEMU mouse can be in either relative, or absolute mode.
+ * Movement is sent separately from button state, which has to
+ * be encoded as virtual key events. We also don't actually get
+ * given any button up/down events, so have to track changes in
+ * the button state.
+ */
+static void xenfb_mouse_event(DeviceState *dev, QemuConsole *src,
+ InputEvent *evt)
+{
+ struct XenInput *xenfb = (struct XenInput *)dev;
+ InputBtnEvent *btn;
+ InputMoveEvent *move;
+ QemuConsole *con;
+ DisplaySurface *surface;
+ int scale;
+
+ switch (evt->type) {
+ case INPUT_EVENT_KIND_BTN:
+ btn = evt->u.btn.data;
+ switch (btn->button) {
+ case INPUT_BUTTON_LEFT:
+ xenfb_send_key(xenfb, btn->down, BTN_LEFT);
+ break;
+ case INPUT_BUTTON_RIGHT:
+ xenfb_send_key(xenfb, btn->down, BTN_LEFT + 1);
+ break;
+ case INPUT_BUTTON_MIDDLE:
+ xenfb_send_key(xenfb, btn->down, BTN_LEFT + 2);
+ break;
+ case INPUT_BUTTON_WHEEL_UP:
+ if (btn->down) {
+ xenfb->wheel--;
+ }
+ break;
+ case INPUT_BUTTON_WHEEL_DOWN:
+ if (btn->down) {
+ xenfb->wheel++;
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case INPUT_EVENT_KIND_ABS:
+ move = evt->u.abs.data;
+ if (xenfb->raw_pointer_wanted) {
+ xenfb->axis[move->axis] = move->value;
+ } else {
+ con = qemu_console_lookup_by_index(0);
+ if (!con) {
+ xen_pv_printf(&xenfb->c.xendev, 0, "No QEMU console available");
+ return;
+ }
+ surface = qemu_console_surface(con);
+ switch (move->axis) {
+ case INPUT_AXIS_X:
+ scale = surface_width(surface) - 1;
+ break;
+ case INPUT_AXIS_Y:
+ scale = surface_height(surface) - 1;
+ break;
+ default:
+ scale = 0x8000;
+ break;
+ }
+ xenfb->axis[move->axis] = move->value * scale / 0x7fff;
+ }
+ break;
+
+ case INPUT_EVENT_KIND_REL:
+ move = evt->u.rel.data;
+ xenfb->axis[move->axis] += move->value;
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void xenfb_mouse_sync(DeviceState *dev)
+{
+ struct XenInput *xenfb = (struct XenInput *)dev;
+
+ trace_xenfb_mouse_event(xenfb, xenfb->axis[INPUT_AXIS_X],
+ xenfb->axis[INPUT_AXIS_Y],
+ xenfb->wheel, 0,
+ xenfb->abs_pointer_wanted);
+ if (xenfb->abs_pointer_wanted) {
+ xenfb_send_position(xenfb, xenfb->axis[INPUT_AXIS_X],
+ xenfb->axis[INPUT_AXIS_Y],
+ xenfb->wheel);
+ } else {
+ xenfb_send_motion(xenfb, xenfb->axis[INPUT_AXIS_X],
+ xenfb->axis[INPUT_AXIS_Y],
+ xenfb->wheel);
+ xenfb->axis[INPUT_AXIS_X] = 0;
+ xenfb->axis[INPUT_AXIS_Y] = 0;
+ }
+ xenfb->wheel = 0;
+}
+
+static QemuInputHandler xenfb_keyboard = {
+ .name = "Xen PV Keyboard",
+ .mask = INPUT_EVENT_MASK_KEY,
+ .event = xenfb_key_event,
+};
+
+static QemuInputHandler xenfb_abs_mouse = {
+ .name = "Xen PV Mouse",
+ .mask = INPUT_EVENT_MASK_BTN | INPUT_EVENT_MASK_ABS,
+ .event = xenfb_mouse_event,
+ .sync = xenfb_mouse_sync,
+};
+
+static QemuInputHandler xenfb_rel_mouse = {
+ .name = "Xen PV Mouse",
+ .mask = INPUT_EVENT_MASK_BTN | INPUT_EVENT_MASK_REL,
+ .event = xenfb_mouse_event,
+ .sync = xenfb_mouse_sync,
+};
+
+static int input_init(struct XenLegacyDevice *xendev)
+{
+ xenstore_write_be_int(xendev, "feature-abs-pointer", 1);
+ xenstore_write_be_int(xendev, "feature-raw-pointer", 1);
+ return 0;
+}
+
+static int input_initialise(struct XenLegacyDevice *xendev)
+{
+ struct XenInput *in = container_of(xendev, struct XenInput, c.xendev);
+ int rc;
+
+ rc = common_bind(&in->c);
+ if (rc != 0)
+ return rc;
+
+ return 0;
+}
+
+static void input_connected(struct XenLegacyDevice *xendev)
+{
+ struct XenInput *in = container_of(xendev, struct XenInput, c.xendev);
+
+ if (xenstore_read_fe_int(xendev, "request-abs-pointer",
+ &in->abs_pointer_wanted) == -1) {
+ in->abs_pointer_wanted = 0;
+ }
+ if (xenstore_read_fe_int(xendev, "request-raw-pointer",
+ &in->raw_pointer_wanted) == -1) {
+ in->raw_pointer_wanted = 0;
+ }
+ if (in->raw_pointer_wanted && in->abs_pointer_wanted == 0) {
+ xen_pv_printf(xendev, 0, "raw pointer set without abs pointer");
+ }
+
+ if (in->qkbd) {
+ qemu_input_handler_unregister(in->qkbd);
+ }
+ if (in->qmou) {
+ qemu_input_handler_unregister(in->qmou);
+ }
+ trace_xenfb_input_connected(xendev, in->abs_pointer_wanted);
+
+ in->qkbd = qemu_input_handler_register((DeviceState *)in, &xenfb_keyboard);
+ in->qmou = qemu_input_handler_register((DeviceState *)in,
+ in->abs_pointer_wanted ? &xenfb_abs_mouse : &xenfb_rel_mouse);
+
+ if (in->raw_pointer_wanted) {
+ qemu_input_handler_activate(in->qkbd);
+ qemu_input_handler_activate(in->qmou);
+ }
+}
+
+static void input_disconnect(struct XenLegacyDevice *xendev)
+{
+ struct XenInput *in = container_of(xendev, struct XenInput, c.xendev);
+
+ if (in->qkbd) {
+ qemu_input_handler_unregister(in->qkbd);
+ in->qkbd = NULL;
+ }
+ if (in->qmou) {
+ qemu_input_handler_unregister(in->qmou);
+ in->qmou = NULL;
+ }
+ common_unbind(&in->c);
+}
+
+static void input_event(struct XenLegacyDevice *xendev)
+{
+ struct XenInput *xenfb = container_of(xendev, struct XenInput, c.xendev);
+ struct xenkbd_page *page = xenfb->c.page;
+
+ /* We don't understand any keyboard events, so just ignore them. */
+ if (page->out_prod == page->out_cons)
+ return;
+ page->out_cons = page->out_prod;
+ xen_pv_send_notify(&xenfb->c.xendev);
+}
+
+/* -------------------------------------------------------------------- */
+
+static void xenfb_copy_mfns(int mode, int count, xen_pfn_t *dst, void *src)
+{
+ uint32_t *src32 = src;
+ uint64_t *src64 = src;
+ int i;
+
+ for (i = 0; i < count; i++)
+ dst[i] = (mode == 32) ? src32[i] : src64[i];
+}
+
+static int xenfb_map_fb(struct XenFB *xenfb)
+{
+ struct xenfb_page *page = xenfb->c.page;
+ char *protocol = xenfb->c.xendev.protocol;
+ int n_fbdirs;
+ xen_pfn_t *pgmfns = NULL;
+ xen_pfn_t *fbmfns = NULL;
+ void *map, *pd;
+ int mode, ret = -1;
+
+ /* default to native */
+ pd = page->pd;
+ mode = sizeof(unsigned long) * 8;
+
+ if (!protocol) {
+ /*
+ * Undefined protocol, some guesswork needed.
+ *
+ * Old frontends which don't set the protocol use
+ * one page directory only, thus pd[1] must be zero.
+ * pd[1] of the 32bit struct layout and the lower
+ * 32 bits of pd[0] of the 64bit struct layout have
+ * the same location, so we can check that ...
+ */
+ uint32_t *ptr32 = NULL;
+ uint32_t *ptr64 = NULL;
+#if defined(__i386__)
+ ptr32 = (void*)page->pd;
+ ptr64 = ((void*)page->pd) + 4;
+#elif defined(__x86_64__)
+ ptr32 = ((void*)page->pd) - 4;
+ ptr64 = (void*)page->pd;
+#endif
+ if (ptr32) {
+ if (ptr32[1] == 0) {
+ mode = 32;
+ pd = ptr32;
+ } else {
+ mode = 64;
+ pd = ptr64;
+ }
+ }
+#if defined(__x86_64__)
+ } else if (strcmp(protocol, XEN_IO_PROTO_ABI_X86_32) == 0) {
+ /* 64bit dom0, 32bit domU */
+ mode = 32;
+ pd = ((void*)page->pd) - 4;
+#elif defined(__i386__)
+ } else if (strcmp(protocol, XEN_IO_PROTO_ABI_X86_64) == 0) {
+ /* 32bit dom0, 64bit domU */
+ mode = 64;
+ pd = ((void*)page->pd) + 4;
+#endif
+ }
+
+ if (xenfb->pixels) {
+ munmap(xenfb->pixels, xenfb->fbpages * XC_PAGE_SIZE);
+ xenfb->pixels = NULL;
+ }
+
+ xenfb->fbpages = DIV_ROUND_UP(xenfb->fb_len, XC_PAGE_SIZE);
+ n_fbdirs = xenfb->fbpages * mode / 8;
+ n_fbdirs = DIV_ROUND_UP(n_fbdirs, XC_PAGE_SIZE);
+
+ pgmfns = g_malloc0(sizeof(xen_pfn_t) * n_fbdirs);
+ fbmfns = g_malloc0(sizeof(xen_pfn_t) * xenfb->fbpages);
+
+ xenfb_copy_mfns(mode, n_fbdirs, pgmfns, pd);
+ map = xenforeignmemory_map(xen_fmem, xenfb->c.xendev.dom,
+ PROT_READ, n_fbdirs, pgmfns, NULL);
+ if (map == NULL)
+ goto out;
+ xenfb_copy_mfns(mode, xenfb->fbpages, fbmfns, map);
+ xenforeignmemory_unmap(xen_fmem, map, n_fbdirs);
+
+ xenfb->pixels = xenforeignmemory_map(xen_fmem, xenfb->c.xendev.dom,
+ PROT_READ, xenfb->fbpages, fbmfns, NULL);
+ if (xenfb->pixels == NULL)
+ goto out;
+
+ ret = 0; /* all is fine */
+
+out:
+ g_free(pgmfns);
+ g_free(fbmfns);
+ return ret;
+}
+
+static int xenfb_configure_fb(struct XenFB *xenfb, size_t fb_len_lim,
+ int width, int height, int depth,
+ size_t fb_len, int offset, int row_stride)
+{
+ size_t mfn_sz = sizeof_field(struct xenfb_page, pd[0]);
+ size_t pd_len = sizeof_field(struct xenfb_page, pd) / mfn_sz;
+ size_t fb_pages = pd_len * XC_PAGE_SIZE / mfn_sz;
+ size_t fb_len_max = fb_pages * XC_PAGE_SIZE;
+ int max_width, max_height;
+
+ if (fb_len_lim > fb_len_max) {
+ xen_pv_printf(&xenfb->c.xendev, 0,
+ "fb size limit %zu exceeds %zu, corrected\n",
+ fb_len_lim, fb_len_max);
+ fb_len_lim = fb_len_max;
+ }
+ if (fb_len_lim && fb_len > fb_len_lim) {
+ xen_pv_printf(&xenfb->c.xendev, 0,
+ "frontend fb size %zu limited to %zu\n",
+ fb_len, fb_len_lim);
+ fb_len = fb_len_lim;
+ }
+ if (depth != 8 && depth != 16 && depth != 24 && depth != 32) {
+ xen_pv_printf(&xenfb->c.xendev, 0,
+ "can't handle frontend fb depth %d\n",
+ depth);
+ return -1;
+ }
+ if (row_stride <= 0 || row_stride > fb_len) {
+ xen_pv_printf(&xenfb->c.xendev, 0, "invalid frontend stride %d\n",
+ row_stride);
+ return -1;
+ }
+ max_width = row_stride / (depth / 8);
+ if (width < 0 || width > max_width) {
+ xen_pv_printf(&xenfb->c.xendev, 0,
+ "invalid frontend width %d limited to %d\n",
+ width, max_width);
+ width = max_width;
+ }
+ if (offset < 0 || offset >= fb_len) {
+ xen_pv_printf(&xenfb->c.xendev, 0,
+ "invalid frontend offset %d (max %zu)\n",
+ offset, fb_len - 1);
+ return -1;
+ }
+ max_height = (fb_len - offset) / row_stride;
+ if (height < 0 || height > max_height) {
+ xen_pv_printf(&xenfb->c.xendev, 0,
+ "invalid frontend height %d limited to %d\n",
+ height, max_height);
+ height = max_height;
+ }
+ xenfb->fb_len = fb_len;
+ xenfb->row_stride = row_stride;
+ xenfb->depth = depth;
+ xenfb->width = width;
+ xenfb->height = height;
+ xenfb->offset = offset;
+ xenfb->up_fullscreen = 1;
+ xenfb->do_resize = 1;
+ xen_pv_printf(&xenfb->c.xendev, 1,
+ "framebuffer %dx%dx%d offset %d stride %d\n",
+ width, height, depth, offset, row_stride);
+ return 0;
+}
+
+/* A convenient function for munging pixels between different depths */
+#define BLT(SRC_T,DST_T,RSB,GSB,BSB,RDB,GDB,BDB) \
+ for (line = y ; line < (y+h) ; line++) { \
+ SRC_T *src = (SRC_T *)(xenfb->pixels \
+ + xenfb->offset \
+ + (line * xenfb->row_stride) \
+ + (x * xenfb->depth / 8)); \
+ DST_T *dst = (DST_T *)(data \
+ + (line * linesize) \
+ + (x * bpp / 8)); \
+ int col; \
+ const int RSS = 32 - (RSB + GSB + BSB); \
+ const int GSS = 32 - (GSB + BSB); \
+ const int BSS = 32 - (BSB); \
+ const uint32_t RSM = (~0U) << (32 - RSB); \
+ const uint32_t GSM = (~0U) << (32 - GSB); \
+ const uint32_t BSM = (~0U) << (32 - BSB); \
+ const int RDS = 32 - (RDB + GDB + BDB); \
+ const int GDS = 32 - (GDB + BDB); \
+ const int BDS = 32 - (BDB); \
+ const uint32_t RDM = (~0U) << (32 - RDB); \
+ const uint32_t GDM = (~0U) << (32 - GDB); \
+ const uint32_t BDM = (~0U) << (32 - BDB); \
+ for (col = x ; col < (x+w) ; col++) { \
+ uint32_t spix = *src; \
+ *dst = (((spix << RSS) & RSM & RDM) >> RDS) | \
+ (((spix << GSS) & GSM & GDM) >> GDS) | \
+ (((spix << BSS) & BSM & BDM) >> BDS); \
+ src = (SRC_T *) ((unsigned long) src + xenfb->depth / 8); \
+ dst = (DST_T *) ((unsigned long) dst + bpp / 8); \
+ } \
+ }
+
+
+/*
+ * This copies data from the guest framebuffer region, into QEMU's
+ * displaysurface. qemu uses 16 or 32 bpp. In case the pv framebuffer
+ * uses something else we must convert and copy, otherwise we can
+ * supply the buffer directly and no thing here.
+ */
+static void xenfb_guest_copy(struct XenFB *xenfb, int x, int y, int w, int h)
+{
+ DisplaySurface *surface = qemu_console_surface(xenfb->con);
+ int line, oops = 0;
+ int bpp = surface_bits_per_pixel(surface);
+ int linesize = surface_stride(surface);
+ uint8_t *data = surface_data(surface);
+
+ if (!is_buffer_shared(surface)) {
+ switch (xenfb->depth) {
+ case 8:
+ if (bpp == 16) {
+ BLT(uint8_t, uint16_t, 3, 3, 2, 5, 6, 5);
+ } else if (bpp == 32) {
+ BLT(uint8_t, uint32_t, 3, 3, 2, 8, 8, 8);
+ } else {
+ oops = 1;
+ }
+ break;
+ case 24:
+ if (bpp == 16) {
+ BLT(uint32_t, uint16_t, 8, 8, 8, 5, 6, 5);
+ } else if (bpp == 32) {
+ BLT(uint32_t, uint32_t, 8, 8, 8, 8, 8, 8);
+ } else {
+ oops = 1;
+ }
+ break;
+ default:
+ oops = 1;
+ }
+ }
+ if (oops) /* should not happen */
+ xen_pv_printf(&xenfb->c.xendev, 0, "%s: oops: convert %d -> %d bpp?\n",
+ __func__, xenfb->depth, bpp);
+
+ dpy_gfx_update(xenfb->con, x, y, w, h);
+}
+
+#ifdef XENFB_TYPE_REFRESH_PERIOD
+static int xenfb_queue_full(struct XenFB *xenfb)
+{
+ struct xenfb_page *page = xenfb->c.page;
+ uint32_t cons, prod;
+
+ if (!page)
+ return 1;
+
+ prod = page->in_prod;
+ cons = page->in_cons;
+ return prod - cons == XENFB_IN_RING_LEN;
+}
+
+static void xenfb_send_event(struct XenFB *xenfb, union xenfb_in_event *event)
+{
+ uint32_t prod;
+ struct xenfb_page *page = xenfb->c.page;
+
+ prod = page->in_prod;
+ /* caller ensures !xenfb_queue_full() */
+ xen_mb(); /* ensure ring space available */
+ XENFB_IN_RING_REF(page, prod) = *event;
+ xen_wmb(); /* ensure ring contents visible */
+ page->in_prod = prod + 1;
+
+ xen_pv_send_notify(&xenfb->c.xendev);
+}
+
+static void xenfb_send_refresh_period(struct XenFB *xenfb, int period)
+{
+ union xenfb_in_event event;
+
+ memset(&event, 0, sizeof(event));
+ event.type = XENFB_TYPE_REFRESH_PERIOD;
+ event.refresh_period.period = period;
+ xenfb_send_event(xenfb, &event);
+}
+#endif
+
+/*
+ * Periodic update of display.
+ * Also transmit the refresh interval to the frontend.
+ *
+ * Never ever do any qemu display operations
+ * (resize, screen update) outside this function.
+ * Our screen might be inactive. When asked for
+ * an update we know it is active.
+ */
+static void xenfb_update(void *opaque)
+{
+ struct XenFB *xenfb = opaque;
+ DisplaySurface *surface;
+ int i;
+
+ if (xenfb->c.xendev.be_state != XenbusStateConnected)
+ return;
+
+ if (!xenfb->feature_update) {
+ /* we don't get update notifications, thus use the
+ * sledge hammer approach ... */
+ xenfb->up_fullscreen = 1;
+ }
+
+ /* resize if needed */
+ if (xenfb->do_resize) {
+ pixman_format_code_t format;
+
+ xenfb->do_resize = 0;
+ switch (xenfb->depth) {
+ case 16:
+ case 32:
+ /* console.c supported depth -> buffer can be used directly */
+ format = qemu_default_pixman_format(xenfb->depth, true);
+ surface = qemu_create_displaysurface_from
+ (xenfb->width, xenfb->height, format,
+ xenfb->row_stride, xenfb->pixels + xenfb->offset);
+ break;
+ default:
+ /* we must convert stuff */
+ surface = qemu_create_displaysurface(xenfb->width, xenfb->height);
+ break;
+ }
+ dpy_gfx_replace_surface(xenfb->con, surface);
+ xen_pv_printf(&xenfb->c.xendev, 1,
+ "update: resizing: %dx%d @ %d bpp%s\n",
+ xenfb->width, xenfb->height, xenfb->depth,
+ is_buffer_shared(surface) ? " (shared)" : "");
+ xenfb->up_fullscreen = 1;
+ }
+
+ /* run queued updates */
+ if (xenfb->up_fullscreen) {
+ xen_pv_printf(&xenfb->c.xendev, 3, "update: fullscreen\n");
+ xenfb_guest_copy(xenfb, 0, 0, xenfb->width, xenfb->height);
+ } else if (xenfb->up_count) {
+ xen_pv_printf(&xenfb->c.xendev, 3, "update: %d rects\n",
+ xenfb->up_count);
+ for (i = 0; i < xenfb->up_count; i++)
+ xenfb_guest_copy(xenfb,
+ xenfb->up_rects[i].x,
+ xenfb->up_rects[i].y,
+ xenfb->up_rects[i].w,
+ xenfb->up_rects[i].h);
+ } else {
+ xen_pv_printf(&xenfb->c.xendev, 3, "update: nothing\n");
+ }
+ xenfb->up_count = 0;
+ xenfb->up_fullscreen = 0;
+}
+
+static void xenfb_update_interval(void *opaque, uint64_t interval)
+{
+ struct XenFB *xenfb = opaque;
+
+ if (xenfb->feature_update) {
+#ifdef XENFB_TYPE_REFRESH_PERIOD
+ if (xenfb_queue_full(xenfb)) {
+ return;
+ }
+ xenfb_send_refresh_period(xenfb, interval);
+#endif
+ }
+}
+
+/* QEMU display state changed, so refresh the framebuffer copy */
+static void xenfb_invalidate(void *opaque)
+{
+ struct XenFB *xenfb = opaque;
+ xenfb->up_fullscreen = 1;
+}
+
+static void xenfb_handle_events(struct XenFB *xenfb)
+{
+ uint32_t prod, cons, out_cons;
+ struct xenfb_page *page = xenfb->c.page;
+
+ prod = page->out_prod;
+ out_cons = page->out_cons;
+ if (prod - out_cons > XENFB_OUT_RING_LEN) {
+ return;
+ }
+ xen_rmb(); /* ensure we see ring contents up to prod */
+ for (cons = out_cons; cons != prod; cons++) {
+ union xenfb_out_event *event = &XENFB_OUT_RING_REF(page, cons);
+ uint8_t type = event->type;
+ int x, y, w, h;
+
+ switch (type) {
+ case XENFB_TYPE_UPDATE:
+ if (xenfb->up_count == UP_QUEUE)
+ xenfb->up_fullscreen = 1;
+ if (xenfb->up_fullscreen)
+ break;
+ x = MAX(event->update.x, 0);
+ y = MAX(event->update.y, 0);
+ w = MIN(event->update.width, xenfb->width - x);
+ h = MIN(event->update.height, xenfb->height - y);
+ if (w < 0 || h < 0) {
+ xen_pv_printf(&xenfb->c.xendev, 1, "bogus update ignored\n");
+ break;
+ }
+ if (x != event->update.x ||
+ y != event->update.y ||
+ w != event->update.width ||
+ h != event->update.height) {
+ xen_pv_printf(&xenfb->c.xendev, 1, "bogus update clipped\n");
+ }
+ if (w == xenfb->width && h > xenfb->height / 2) {
+ /* scroll detector: updated more than 50% of the lines,
+ * don't bother keeping track of the rectangles then */
+ xenfb->up_fullscreen = 1;
+ } else {
+ xenfb->up_rects[xenfb->up_count].x = x;
+ xenfb->up_rects[xenfb->up_count].y = y;
+ xenfb->up_rects[xenfb->up_count].w = w;
+ xenfb->up_rects[xenfb->up_count].h = h;
+ xenfb->up_count++;
+ }
+ break;
+#ifdef XENFB_TYPE_RESIZE
+ case XENFB_TYPE_RESIZE:
+ if (xenfb_configure_fb(xenfb, xenfb->fb_len,
+ event->resize.width,
+ event->resize.height,
+ event->resize.depth,
+ xenfb->fb_len,
+ event->resize.offset,
+ event->resize.stride) < 0)
+ break;
+ xenfb_invalidate(xenfb);
+ break;
+#endif
+ }
+ }
+ xen_mb(); /* ensure we're done with ring contents */
+ page->out_cons = cons;
+}
+
+static int fb_init(struct XenLegacyDevice *xendev)
+{
+#ifdef XENFB_TYPE_RESIZE
+ xenstore_write_be_int(xendev, "feature-resize", 1);
+#endif
+ return 0;
+}
+
+static int fb_initialise(struct XenLegacyDevice *xendev)
+{
+ struct XenFB *fb = container_of(xendev, struct XenFB, c.xendev);
+ struct xenfb_page *fb_page;
+ int videoram;
+ int rc;
+
+ if (xenstore_read_fe_int(xendev, "videoram", &videoram) == -1)
+ videoram = 0;
+
+ rc = common_bind(&fb->c);
+ if (rc != 0)
+ return rc;
+
+ fb_page = fb->c.page;
+ rc = xenfb_configure_fb(fb, videoram * MiB,
+ fb_page->width, fb_page->height, fb_page->depth,
+ fb_page->mem_length, 0, fb_page->line_length);
+ if (rc != 0)
+ return rc;
+
+ rc = xenfb_map_fb(fb);
+ if (rc != 0)
+ return rc;
+
+ fb->con = graphic_console_init(NULL, 0, &xenfb_ops, fb);
+
+ if (xenstore_read_fe_int(xendev, "feature-update", &fb->feature_update) == -1)
+ fb->feature_update = 0;
+ if (fb->feature_update)
+ xenstore_write_be_int(xendev, "request-update", 1);
+
+ xen_pv_printf(xendev, 1, "feature-update=%d, videoram=%d\n",
+ fb->feature_update, videoram);
+ return 0;
+}
+
+static void fb_disconnect(struct XenLegacyDevice *xendev)
+{
+ struct XenFB *fb = container_of(xendev, struct XenFB, c.xendev);
+
+ /*
+ * FIXME: qemu can't un-init gfx display (yet?).
+ * Replacing the framebuffer with anonymous shared memory
+ * instead. This releases the guest pages and keeps qemu happy.
+ */
+ xenforeignmemory_unmap(xen_fmem, fb->pixels, fb->fbpages);
+ fb->pixels = mmap(fb->pixels, fb->fbpages * XC_PAGE_SIZE,
+ PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON,
+ -1, 0);
+ if (fb->pixels == MAP_FAILED) {
+ xen_pv_printf(xendev, 0,
+ "Couldn't replace the framebuffer with anonymous memory errno=%d\n",
+ errno);
+ }
+ common_unbind(&fb->c);
+ fb->feature_update = 0;
+ fb->bug_trigger = 0;
+}
+
+static void fb_frontend_changed(struct XenLegacyDevice *xendev,
+ const char *node)
+{
+ struct XenFB *fb = container_of(xendev, struct XenFB, c.xendev);
+
+ /*
+ * Set state to Connected *again* once the frontend switched
+ * to connected. We must trigger the watch a second time to
+ * workaround a frontend bug.
+ */
+ if (fb->bug_trigger == 0 && strcmp(node, "state") == 0 &&
+ xendev->fe_state == XenbusStateConnected &&
+ xendev->be_state == XenbusStateConnected) {
+ xen_pv_printf(xendev, 2, "re-trigger connected (frontend bug)\n");
+ xen_be_set_state(xendev, XenbusStateConnected);
+ fb->bug_trigger = 1; /* only once */
+ }
+}
+
+static void fb_event(struct XenLegacyDevice *xendev)
+{
+ struct XenFB *xenfb = container_of(xendev, struct XenFB, c.xendev);
+
+ xenfb_handle_events(xenfb);
+ xen_pv_send_notify(&xenfb->c.xendev);
+}
+
+/* -------------------------------------------------------------------- */
+
+struct XenDevOps xen_kbdmouse_ops = {
+ .size = sizeof(struct XenInput),
+ .init = input_init,
+ .initialise = input_initialise,
+ .connected = input_connected,
+ .disconnect = input_disconnect,
+ .event = input_event,
+};
+
+struct XenDevOps xen_framebuffer_ops = {
+ .size = sizeof(struct XenFB),
+ .init = fb_init,
+ .initialise = fb_initialise,
+ .disconnect = fb_disconnect,
+ .event = fb_event,
+ .frontend_changed = fb_frontend_changed,
+};
+
+static const GraphicHwOps xenfb_ops = {
+ .invalidate = xenfb_invalidate,
+ .gfx_update = xenfb_update,
+ .update_interval = xenfb_update_interval,
+};
diff --git a/hw/display/xlnx_dp.c b/hw/display/xlnx_dp.c
new file mode 100644
index 000000000..9bb781e31
--- /dev/null
+++ b/hw/display/xlnx_dp.c
@@ -0,0 +1,1383 @@
+/*
+ * Xilinx Display Port
+ *
+ * Copyright (C) 2015 : GreenSocs Ltd
+ * http://www.greensocs.com/ , email: info@greensocs.com
+ *
+ * Developed by :
+ * Frederic Konrad <fred.konrad@greensocs.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option)any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "qemu/error-report.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "hw/display/xlnx_dp.h"
+#include "hw/irq.h"
+#include "migration/vmstate.h"
+
+#ifndef DEBUG_DP
+#define DEBUG_DP 0
+#endif
+
+#define DPRINTF(fmt, ...) do { \
+ if (DEBUG_DP) { \
+ qemu_log("xlnx_dp: " fmt , ## __VA_ARGS__); \
+ } \
+} while (0)
+
+/*
+ * Register offset for DP.
+ */
+#define DP_LINK_BW_SET (0x0000 >> 2)
+#define DP_LANE_COUNT_SET (0x0004 >> 2)
+#define DP_ENHANCED_FRAME_EN (0x0008 >> 2)
+#define DP_TRAINING_PATTERN_SET (0x000C >> 2)
+#define DP_LINK_QUAL_PATTERN_SET (0x0010 >> 2)
+#define DP_SCRAMBLING_DISABLE (0x0014 >> 2)
+#define DP_DOWNSPREAD_CTRL (0x0018 >> 2)
+#define DP_SOFTWARE_RESET (0x001C >> 2)
+#define DP_TRANSMITTER_ENABLE (0x0080 >> 2)
+#define DP_MAIN_STREAM_ENABLE (0x0084 >> 2)
+#define DP_FORCE_SCRAMBLER_RESET (0x00C0 >> 2)
+#define DP_VERSION_REGISTER (0x00F8 >> 2)
+#define DP_CORE_ID (0x00FC >> 2)
+
+#define DP_AUX_COMMAND_REGISTER (0x0100 >> 2)
+#define AUX_ADDR_ONLY_MASK (0x1000)
+#define AUX_COMMAND_MASK (0x0F00)
+#define AUX_COMMAND_SHIFT (8)
+#define AUX_COMMAND_NBYTES (0x000F)
+
+#define DP_AUX_WRITE_FIFO (0x0104 >> 2)
+#define DP_AUX_ADDRESS (0x0108 >> 2)
+#define DP_AUX_CLOCK_DIVIDER (0x010C >> 2)
+#define DP_TX_USER_FIFO_OVERFLOW (0x0110 >> 2)
+#define DP_INTERRUPT_SIGNAL_STATE (0x0130 >> 2)
+#define DP_AUX_REPLY_DATA (0x0134 >> 2)
+#define DP_AUX_REPLY_CODE (0x0138 >> 2)
+#define DP_AUX_REPLY_COUNT (0x013C >> 2)
+#define DP_REPLY_DATA_COUNT (0x0148 >> 2)
+#define DP_REPLY_STATUS (0x014C >> 2)
+#define DP_HPD_DURATION (0x0150 >> 2)
+#define DP_MAIN_STREAM_HTOTAL (0x0180 >> 2)
+#define DP_MAIN_STREAM_VTOTAL (0x0184 >> 2)
+#define DP_MAIN_STREAM_POLARITY (0x0188 >> 2)
+#define DP_MAIN_STREAM_HSWIDTH (0x018C >> 2)
+#define DP_MAIN_STREAM_VSWIDTH (0x0190 >> 2)
+#define DP_MAIN_STREAM_HRES (0x0194 >> 2)
+#define DP_MAIN_STREAM_VRES (0x0198 >> 2)
+#define DP_MAIN_STREAM_HSTART (0x019C >> 2)
+#define DP_MAIN_STREAM_VSTART (0x01A0 >> 2)
+#define DP_MAIN_STREAM_MISC0 (0x01A4 >> 2)
+#define DP_MAIN_STREAM_MISC1 (0x01A8 >> 2)
+#define DP_MAIN_STREAM_M_VID (0x01AC >> 2)
+#define DP_MSA_TRANSFER_UNIT_SIZE (0x01B0 >> 2)
+#define DP_MAIN_STREAM_N_VID (0x01B4 >> 2)
+#define DP_USER_DATA_COUNT_PER_LANE (0x01BC >> 2)
+#define DP_MIN_BYTES_PER_TU (0x01C4 >> 2)
+#define DP_FRAC_BYTES_PER_TU (0x01C8 >> 2)
+#define DP_INIT_WAIT (0x01CC >> 2)
+#define DP_PHY_RESET (0x0200 >> 2)
+#define DP_PHY_VOLTAGE_DIFF_LANE_0 (0x0220 >> 2)
+#define DP_PHY_VOLTAGE_DIFF_LANE_1 (0x0224 >> 2)
+#define DP_TRANSMIT_PRBS7 (0x0230 >> 2)
+#define DP_PHY_CLOCK_SELECT (0x0234 >> 2)
+#define DP_TX_PHY_POWER_DOWN (0x0238 >> 2)
+#define DP_PHY_PRECURSOR_LANE_0 (0x023C >> 2)
+#define DP_PHY_PRECURSOR_LANE_1 (0x0240 >> 2)
+#define DP_PHY_POSTCURSOR_LANE_0 (0x024C >> 2)
+#define DP_PHY_POSTCURSOR_LANE_1 (0x0250 >> 2)
+#define DP_PHY_STATUS (0x0280 >> 2)
+
+#define DP_TX_AUDIO_CONTROL (0x0300 >> 2)
+#define DP_TX_AUD_CTRL (1)
+
+#define DP_TX_AUDIO_CHANNELS (0x0304 >> 2)
+#define DP_TX_AUDIO_INFO_DATA(n) ((0x0308 + 4 * n) >> 2)
+#define DP_TX_M_AUD (0x0328 >> 2)
+#define DP_TX_N_AUD (0x032C >> 2)
+#define DP_TX_AUDIO_EXT_DATA(n) ((0x0330 + 4 * n) >> 2)
+#define DP_INT_STATUS (0x03A0 >> 2)
+#define DP_INT_MASK (0x03A4 >> 2)
+#define DP_INT_EN (0x03A8 >> 2)
+#define DP_INT_DS (0x03AC >> 2)
+
+/*
+ * Registers offset for Audio Video Buffer configuration.
+ */
+#define V_BLEND_OFFSET (0xA000)
+#define V_BLEND_BG_CLR_0 (0x0000 >> 2)
+#define V_BLEND_BG_CLR_1 (0x0004 >> 2)
+#define V_BLEND_BG_CLR_2 (0x0008 >> 2)
+#define V_BLEND_SET_GLOBAL_ALPHA_REG (0x000C >> 2)
+#define V_BLEND_OUTPUT_VID_FORMAT (0x0014 >> 2)
+#define V_BLEND_LAYER0_CONTROL (0x0018 >> 2)
+#define V_BLEND_LAYER1_CONTROL (0x001C >> 2)
+
+#define V_BLEND_RGB2YCBCR_COEFF(n) ((0x0020 + 4 * n) >> 2)
+#define V_BLEND_IN1CSC_COEFF(n) ((0x0044 + 4 * n) >> 2)
+
+#define V_BLEND_LUMA_IN1CSC_OFFSET (0x0068 >> 2)
+#define V_BLEND_CR_IN1CSC_OFFSET (0x006C >> 2)
+#define V_BLEND_CB_IN1CSC_OFFSET (0x0070 >> 2)
+#define V_BLEND_LUMA_OUTCSC_OFFSET (0x0074 >> 2)
+#define V_BLEND_CR_OUTCSC_OFFSET (0x0078 >> 2)
+#define V_BLEND_CB_OUTCSC_OFFSET (0x007C >> 2)
+
+#define V_BLEND_IN2CSC_COEFF(n) ((0x0080 + 4 * n) >> 2)
+
+#define V_BLEND_LUMA_IN2CSC_OFFSET (0x00A4 >> 2)
+#define V_BLEND_CR_IN2CSC_OFFSET (0x00A8 >> 2)
+#define V_BLEND_CB_IN2CSC_OFFSET (0x00AC >> 2)
+#define V_BLEND_CHROMA_KEY_ENABLE (0x01D0 >> 2)
+#define V_BLEND_CHROMA_KEY_COMP1 (0x01D4 >> 2)
+#define V_BLEND_CHROMA_KEY_COMP2 (0x01D8 >> 2)
+#define V_BLEND_CHROMA_KEY_COMP3 (0x01DC >> 2)
+
+/*
+ * Registers offset for Audio Video Buffer configuration.
+ */
+#define AV_BUF_MANAGER_OFFSET (0xB000)
+#define AV_BUF_FORMAT (0x0000 >> 2)
+#define AV_BUF_NON_LIVE_LATENCY (0x0008 >> 2)
+#define AV_CHBUF0 (0x0010 >> 2)
+#define AV_CHBUF1 (0x0014 >> 2)
+#define AV_CHBUF2 (0x0018 >> 2)
+#define AV_CHBUF3 (0x001C >> 2)
+#define AV_CHBUF4 (0x0020 >> 2)
+#define AV_CHBUF5 (0x0024 >> 2)
+#define AV_BUF_STC_CONTROL (0x002C >> 2)
+#define AV_BUF_STC_INIT_VALUE0 (0x0030 >> 2)
+#define AV_BUF_STC_INIT_VALUE1 (0x0034 >> 2)
+#define AV_BUF_STC_ADJ (0x0038 >> 2)
+#define AV_BUF_STC_VIDEO_VSYNC_TS_REG0 (0x003C >> 2)
+#define AV_BUF_STC_VIDEO_VSYNC_TS_REG1 (0x0040 >> 2)
+#define AV_BUF_STC_EXT_VSYNC_TS_REG0 (0x0044 >> 2)
+#define AV_BUF_STC_EXT_VSYNC_TS_REG1 (0x0048 >> 2)
+#define AV_BUF_STC_CUSTOM_EVENT_TS_REG0 (0x004C >> 2)
+#define AV_BUF_STC_CUSTOM_EVENT_TS_REG1 (0x0050 >> 2)
+#define AV_BUF_STC_CUSTOM_EVENT2_TS_REG0 (0x0054 >> 2)
+#define AV_BUF_STC_CUSTOM_EVENT2_TS_REG1 (0x0058 >> 2)
+#define AV_BUF_STC_SNAPSHOT0 (0x0060 >> 2)
+#define AV_BUF_STC_SNAPSHOT1 (0x0064 >> 2)
+#define AV_BUF_OUTPUT_AUDIO_VIDEO_SELECT (0x0070 >> 2)
+#define AV_BUF_HCOUNT_VCOUNT_INT0 (0x0074 >> 2)
+#define AV_BUF_HCOUNT_VCOUNT_INT1 (0x0078 >> 2)
+#define AV_BUF_DITHER_CONFIG (0x007C >> 2)
+#define AV_BUF_DITHER_CONFIG_MAX (0x008C >> 2)
+#define AV_BUF_DITHER_CONFIG_MIN (0x0090 >> 2)
+#define AV_BUF_PATTERN_GEN_SELECT (0x0100 >> 2)
+#define AV_BUF_AUD_VID_CLK_SOURCE (0x0120 >> 2)
+#define AV_BUF_SRST_REG (0x0124 >> 2)
+#define AV_BUF_AUDIO_RDY_INTERVAL (0x0128 >> 2)
+#define AV_BUF_AUDIO_CH_CONFIG (0x012C >> 2)
+
+#define AV_BUF_GRAPHICS_COMP_SCALE_FACTOR(n)((0x0200 + 4 * n) >> 2)
+
+#define AV_BUF_VIDEO_COMP_SCALE_FACTOR(n) ((0x020C + 4 * n) >> 2)
+
+#define AV_BUF_LIVE_VIDEO_COMP_SF(n) ((0x0218 + 4 * n) >> 2)
+
+#define AV_BUF_LIVE_VID_CONFIG (0x0224 >> 2)
+
+#define AV_BUF_LIVE_GFX_COMP_SF(n) ((0x0228 + 4 * n) >> 2)
+
+#define AV_BUF_LIVE_GFX_CONFIG (0x0234 >> 2)
+
+#define AUDIO_MIXER_REGISTER_OFFSET (0xC000)
+#define AUDIO_MIXER_VOLUME_CONTROL (0x0000 >> 2)
+#define AUDIO_MIXER_META_DATA (0x0004 >> 2)
+#define AUD_CH_STATUS_REG(n) ((0x0008 + 4 * n) >> 2)
+#define AUD_CH_A_DATA_REG(n) ((0x0020 + 4 * n) >> 2)
+#define AUD_CH_B_DATA_REG(n) ((0x0038 + 4 * n) >> 2)
+
+#define DP_AUDIO_DMA_CHANNEL(n) (4 + n)
+#define DP_GRAPHIC_DMA_CHANNEL (3)
+#define DP_VIDEO_DMA_CHANNEL (0)
+
+enum DPGraphicFmt {
+ DP_GRAPHIC_RGBA8888 = 0 << 8,
+ DP_GRAPHIC_ABGR8888 = 1 << 8,
+ DP_GRAPHIC_RGB888 = 2 << 8,
+ DP_GRAPHIC_BGR888 = 3 << 8,
+ DP_GRAPHIC_RGBA5551 = 4 << 8,
+ DP_GRAPHIC_RGBA4444 = 5 << 8,
+ DP_GRAPHIC_RGB565 = 6 << 8,
+ DP_GRAPHIC_8BPP = 7 << 8,
+ DP_GRAPHIC_4BPP = 8 << 8,
+ DP_GRAPHIC_2BPP = 9 << 8,
+ DP_GRAPHIC_1BPP = 10 << 8,
+ DP_GRAPHIC_MASK = 0xF << 8
+};
+
+enum DPVideoFmt {
+ DP_NL_VID_CB_Y0_CR_Y1 = 0,
+ DP_NL_VID_CR_Y0_CB_Y1 = 1,
+ DP_NL_VID_Y0_CR_Y1_CB = 2,
+ DP_NL_VID_Y0_CB_Y1_CR = 3,
+ DP_NL_VID_YV16 = 4,
+ DP_NL_VID_YV24 = 5,
+ DP_NL_VID_YV16CL = 6,
+ DP_NL_VID_MONO = 7,
+ DP_NL_VID_YV16CL2 = 8,
+ DP_NL_VID_YUV444 = 9,
+ DP_NL_VID_RGB888 = 10,
+ DP_NL_VID_RGBA8880 = 11,
+ DP_NL_VID_RGB888_10BPC = 12,
+ DP_NL_VID_YUV444_10BPC = 13,
+ DP_NL_VID_YV16CL2_10BPC = 14,
+ DP_NL_VID_YV16CL_10BPC = 15,
+ DP_NL_VID_YV16_10BPC = 16,
+ DP_NL_VID_YV24_10BPC = 17,
+ DP_NL_VID_Y_ONLY_10BPC = 18,
+ DP_NL_VID_YV16_420 = 19,
+ DP_NL_VID_YV16CL_420 = 20,
+ DP_NL_VID_YV16CL2_420 = 21,
+ DP_NL_VID_YV16_420_10BPC = 22,
+ DP_NL_VID_YV16CL_420_10BPC = 23,
+ DP_NL_VID_YV16CL2_420_10BPC = 24,
+ DP_NL_VID_FMT_MASK = 0x1F
+};
+
+typedef enum DPGraphicFmt DPGraphicFmt;
+typedef enum DPVideoFmt DPVideoFmt;
+
+static const VMStateDescription vmstate_dp = {
+ .name = TYPE_XLNX_DP,
+ .version_id = 1,
+ .fields = (VMStateField[]){
+ VMSTATE_UINT32_ARRAY(core_registers, XlnxDPState,
+ DP_CORE_REG_ARRAY_SIZE),
+ VMSTATE_UINT32_ARRAY(avbufm_registers, XlnxDPState,
+ DP_AVBUF_REG_ARRAY_SIZE),
+ VMSTATE_UINT32_ARRAY(vblend_registers, XlnxDPState,
+ DP_VBLEND_REG_ARRAY_SIZE),
+ VMSTATE_UINT32_ARRAY(audio_registers, XlnxDPState,
+ DP_AUDIO_REG_ARRAY_SIZE),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void xlnx_dp_update_irq(XlnxDPState *s);
+
+static uint64_t xlnx_dp_audio_read(void *opaque, hwaddr offset, unsigned size)
+{
+ XlnxDPState *s = XLNX_DP(opaque);
+
+ offset = offset >> 2;
+ return s->audio_registers[offset];
+}
+
+static void xlnx_dp_audio_write(void *opaque, hwaddr offset, uint64_t value,
+ unsigned size)
+{
+ XlnxDPState *s = XLNX_DP(opaque);
+
+ offset = offset >> 2;
+
+ switch (offset) {
+ case AUDIO_MIXER_META_DATA:
+ s->audio_registers[offset] = value & 0x00000001;
+ break;
+ default:
+ s->audio_registers[offset] = value;
+ break;
+ }
+}
+
+static const MemoryRegionOps audio_ops = {
+ .read = xlnx_dp_audio_read,
+ .write = xlnx_dp_audio_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static inline uint32_t xlnx_dp_audio_get_volume(XlnxDPState *s,
+ uint8_t channel)
+{
+ switch (channel) {
+ case 0:
+ return extract32(s->audio_registers[AUDIO_MIXER_VOLUME_CONTROL], 0, 16);
+ case 1:
+ return extract32(s->audio_registers[AUDIO_MIXER_VOLUME_CONTROL], 16,
+ 16);
+ default:
+ return 0;
+ }
+}
+
+static inline void xlnx_dp_audio_activate(XlnxDPState *s)
+{
+ bool activated = ((s->core_registers[DP_TX_AUDIO_CONTROL]
+ & DP_TX_AUD_CTRL) != 0);
+ AUD_set_active_out(s->amixer_output_stream, activated);
+ xlnx_dpdma_set_host_data_location(s->dpdma, DP_AUDIO_DMA_CHANNEL(0),
+ &s->audio_buffer_0);
+ xlnx_dpdma_set_host_data_location(s->dpdma, DP_AUDIO_DMA_CHANNEL(1),
+ &s->audio_buffer_1);
+}
+
+static inline void xlnx_dp_audio_mix_buffer(XlnxDPState *s)
+{
+ /*
+ * Audio packets are signed and have this shape:
+ * | 16 | 16 | 16 | 16 | 16 | 16 | 16 | 16 |
+ * | R3 | L3 | R2 | L2 | R1 | L1 | R0 | L0 |
+ *
+ * Output audio is 16bits saturated.
+ */
+ int i;
+
+ if ((s->audio_data_available[0]) && (xlnx_dp_audio_get_volume(s, 0))) {
+ for (i = 0; i < s->audio_data_available[0] / 2; i++) {
+ s->temp_buffer[i] = (int64_t)(s->audio_buffer_0[i])
+ * xlnx_dp_audio_get_volume(s, 0) / 8192;
+ }
+ s->byte_left = s->audio_data_available[0];
+ } else {
+ memset(s->temp_buffer, 0, s->audio_data_available[1] / 2);
+ }
+
+ if ((s->audio_data_available[1]) && (xlnx_dp_audio_get_volume(s, 1))) {
+ if ((s->audio_data_available[0] == 0)
+ || (s->audio_data_available[1] == s->audio_data_available[0])) {
+ for (i = 0; i < s->audio_data_available[1] / 2; i++) {
+ s->temp_buffer[i] += (int64_t)(s->audio_buffer_1[i])
+ * xlnx_dp_audio_get_volume(s, 1) / 8192;
+ }
+ s->byte_left = s->audio_data_available[1];
+ }
+ }
+
+ for (i = 0; i < s->byte_left / 2; i++) {
+ s->out_buffer[i] = MAX(-32767, MIN(s->temp_buffer[i], 32767));
+ }
+
+ s->data_ptr = 0;
+}
+
+static void xlnx_dp_audio_callback(void *opaque, int avail)
+{
+ /*
+ * Get some data from the DPDMA and compute these datas.
+ * Then wait for QEMU's audio subsystem to call this callback.
+ */
+ XlnxDPState *s = XLNX_DP(opaque);
+ size_t written = 0;
+
+ /* If there are already some data don't get more data. */
+ if (s->byte_left == 0) {
+ s->audio_data_available[0] = xlnx_dpdma_start_operation(s->dpdma, 4,
+ true);
+ s->audio_data_available[1] = xlnx_dpdma_start_operation(s->dpdma, 5,
+ true);
+ xlnx_dp_audio_mix_buffer(s);
+ }
+
+ /* Send the buffer through the audio. */
+ if (s->byte_left <= MAX_QEMU_BUFFER_SIZE) {
+ if (s->byte_left != 0) {
+ written = AUD_write(s->amixer_output_stream,
+ &s->out_buffer[s->data_ptr], s->byte_left);
+ } else {
+ int len_to_copy;
+ /*
+ * There is nothing to play.. We don't have any data! Fill the
+ * buffer with zero's and send it.
+ */
+ written = 0;
+ while (avail) {
+ len_to_copy = MIN(AUD_CHBUF_MAX_DEPTH, avail);
+ memset(s->out_buffer, 0, len_to_copy);
+ avail -= AUD_write(s->amixer_output_stream, s->out_buffer,
+ len_to_copy);
+ }
+ }
+ } else {
+ written = AUD_write(s->amixer_output_stream,
+ &s->out_buffer[s->data_ptr], MAX_QEMU_BUFFER_SIZE);
+ }
+ s->byte_left -= written;
+ s->data_ptr += written;
+}
+
+/*
+ * AUX channel related function.
+ */
+static void xlnx_dp_aux_clear_rx_fifo(XlnxDPState *s)
+{
+ fifo8_reset(&s->rx_fifo);
+}
+
+static void xlnx_dp_aux_push_rx_fifo(XlnxDPState *s, uint8_t *buf, size_t len)
+{
+ DPRINTF("Push %u data in rx_fifo\n", (unsigned)len);
+ fifo8_push_all(&s->rx_fifo, buf, len);
+}
+
+static uint8_t xlnx_dp_aux_pop_rx_fifo(XlnxDPState *s)
+{
+ uint8_t ret;
+
+ if (fifo8_is_empty(&s->rx_fifo)) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Reading empty RX_FIFO\n",
+ __func__);
+ /*
+ * The datasheet is not clear about the reset value, it seems
+ * to be unspecified. We choose to return '0'.
+ */
+ ret = 0;
+ } else {
+ ret = fifo8_pop(&s->rx_fifo);
+ DPRINTF("pop 0x%" PRIX8 " from rx_fifo.\n", ret);
+ }
+ return ret;
+}
+
+static void xlnx_dp_aux_clear_tx_fifo(XlnxDPState *s)
+{
+ fifo8_reset(&s->tx_fifo);
+}
+
+static void xlnx_dp_aux_push_tx_fifo(XlnxDPState *s, uint8_t *buf, size_t len)
+{
+ DPRINTF("Push %u data in tx_fifo\n", (unsigned)len);
+ fifo8_push_all(&s->tx_fifo, buf, len);
+}
+
+static uint8_t xlnx_dp_aux_pop_tx_fifo(XlnxDPState *s)
+{
+ uint8_t ret;
+
+ if (fifo8_is_empty(&s->tx_fifo)) {
+ error_report("%s: TX_FIFO underflow", __func__);
+ abort();
+ }
+ ret = fifo8_pop(&s->tx_fifo);
+ DPRINTF("pop 0x%2.2X from tx_fifo.\n", ret);
+ return ret;
+}
+
+static uint32_t xlnx_dp_aux_get_address(XlnxDPState *s)
+{
+ return s->core_registers[DP_AUX_ADDRESS];
+}
+
+/*
+ * Get command from the register.
+ */
+static void xlnx_dp_aux_set_command(XlnxDPState *s, uint32_t value)
+{
+ bool address_only = (value & AUX_ADDR_ONLY_MASK) != 0;
+ AUXCommand cmd = (value & AUX_COMMAND_MASK) >> AUX_COMMAND_SHIFT;
+ uint8_t nbytes = (value & AUX_COMMAND_NBYTES) + 1;
+ uint8_t buf[16];
+ int i;
+
+ /*
+ * When an address_only command is executed nothing happen to the fifo, so
+ * just make nbytes = 0.
+ */
+ if (address_only) {
+ nbytes = 0;
+ }
+
+ switch (cmd) {
+ case READ_AUX:
+ case READ_I2C:
+ case READ_I2C_MOT:
+ s->core_registers[DP_AUX_REPLY_CODE] = aux_request(s->aux_bus, cmd,
+ xlnx_dp_aux_get_address(s),
+ nbytes, buf);
+ s->core_registers[DP_REPLY_DATA_COUNT] = nbytes;
+
+ if (s->core_registers[DP_AUX_REPLY_CODE] == AUX_I2C_ACK) {
+ xlnx_dp_aux_push_rx_fifo(s, buf, nbytes);
+ }
+ break;
+ case WRITE_AUX:
+ case WRITE_I2C:
+ case WRITE_I2C_MOT:
+ for (i = 0; i < nbytes; i++) {
+ buf[i] = xlnx_dp_aux_pop_tx_fifo(s);
+ }
+ s->core_registers[DP_AUX_REPLY_CODE] = aux_request(s->aux_bus, cmd,
+ xlnx_dp_aux_get_address(s),
+ nbytes, buf);
+ xlnx_dp_aux_clear_tx_fifo(s);
+ break;
+ case WRITE_I2C_STATUS:
+ qemu_log_mask(LOG_UNIMP, "xlnx_dp: Write i2c status not implemented\n");
+ break;
+ default:
+ error_report("%s: invalid command: %u", __func__, cmd);
+ abort();
+ }
+
+ s->core_registers[DP_INTERRUPT_SIGNAL_STATE] |= 0x04;
+}
+
+static void xlnx_dp_set_dpdma(const Object *obj, const char *name, Object *val,
+ Error **errp)
+{
+ XlnxDPState *s = XLNX_DP(obj);
+ if (s->console) {
+ DisplaySurface *surface = qemu_console_surface(s->console);
+ XlnxDPDMAState *dma = XLNX_DPDMA(val);
+ xlnx_dpdma_set_host_data_location(dma, DP_GRAPHIC_DMA_CHANNEL,
+ surface_data(surface));
+ }
+}
+
+static inline uint8_t xlnx_dp_global_alpha_value(XlnxDPState *s)
+{
+ return (s->vblend_registers[V_BLEND_SET_GLOBAL_ALPHA_REG] & 0x1FE) >> 1;
+}
+
+static inline bool xlnx_dp_global_alpha_enabled(XlnxDPState *s)
+{
+ /*
+ * If the alpha is totally opaque (255) we consider the alpha is disabled to
+ * reduce CPU consumption.
+ */
+ return ((xlnx_dp_global_alpha_value(s) != 0xFF) &&
+ ((s->vblend_registers[V_BLEND_SET_GLOBAL_ALPHA_REG] & 0x01) != 0));
+}
+
+static void xlnx_dp_recreate_surface(XlnxDPState *s)
+{
+ /*
+ * Two possibilities, if blending is enabled the console displays
+ * bout_plane, if not g_plane is displayed.
+ */
+ uint16_t width = s->core_registers[DP_MAIN_STREAM_HRES];
+ uint16_t height = s->core_registers[DP_MAIN_STREAM_VRES];
+ DisplaySurface *current_console_surface = qemu_console_surface(s->console);
+
+ if ((width != 0) && (height != 0)) {
+ /*
+ * As dpy_gfx_replace_surface calls qemu_free_displaysurface on the
+ * surface we need to be careful and don't free the surface associated
+ * to the console or double free will happen.
+ */
+ if (s->bout_plane.surface != current_console_surface) {
+ qemu_free_displaysurface(s->bout_plane.surface);
+ }
+ if (s->v_plane.surface != current_console_surface) {
+ qemu_free_displaysurface(s->v_plane.surface);
+ }
+ if (s->g_plane.surface != current_console_surface) {
+ qemu_free_displaysurface(s->g_plane.surface);
+ }
+
+ s->g_plane.surface
+ = qemu_create_displaysurface_from(width, height,
+ s->g_plane.format, 0, NULL);
+ s->v_plane.surface
+ = qemu_create_displaysurface_from(width, height,
+ s->v_plane.format, 0, NULL);
+ if (xlnx_dp_global_alpha_enabled(s)) {
+ s->bout_plane.surface =
+ qemu_create_displaysurface_from(width,
+ height,
+ s->g_plane.format,
+ 0, NULL);
+ dpy_gfx_replace_surface(s->console, s->bout_plane.surface);
+ } else {
+ s->bout_plane.surface = NULL;
+ dpy_gfx_replace_surface(s->console, s->g_plane.surface);
+ }
+
+ xlnx_dpdma_set_host_data_location(s->dpdma, DP_GRAPHIC_DMA_CHANNEL,
+ surface_data(s->g_plane.surface));
+ xlnx_dpdma_set_host_data_location(s->dpdma, DP_VIDEO_DMA_CHANNEL,
+ surface_data(s->v_plane.surface));
+ }
+}
+
+/*
+ * Change the graphic format of the surface.
+ */
+static void xlnx_dp_change_graphic_fmt(XlnxDPState *s)
+{
+ switch (s->avbufm_registers[AV_BUF_FORMAT] & DP_GRAPHIC_MASK) {
+ case DP_GRAPHIC_RGBA8888:
+ s->g_plane.format = PIXMAN_r8g8b8a8;
+ break;
+ case DP_GRAPHIC_ABGR8888:
+ s->g_plane.format = PIXMAN_a8b8g8r8;
+ break;
+ case DP_GRAPHIC_RGB565:
+ s->g_plane.format = PIXMAN_r5g6b5;
+ break;
+ case DP_GRAPHIC_RGB888:
+ s->g_plane.format = PIXMAN_r8g8b8;
+ break;
+ case DP_GRAPHIC_BGR888:
+ s->g_plane.format = PIXMAN_b8g8r8;
+ break;
+ default:
+ error_report("%s: unsupported graphic format %u", __func__,
+ s->avbufm_registers[AV_BUF_FORMAT] & DP_GRAPHIC_MASK);
+ abort();
+ }
+
+ switch (s->avbufm_registers[AV_BUF_FORMAT] & DP_NL_VID_FMT_MASK) {
+ case 0:
+ s->v_plane.format = PIXMAN_x8b8g8r8;
+ break;
+ case DP_NL_VID_Y0_CB_Y1_CR:
+ s->v_plane.format = PIXMAN_yuy2;
+ break;
+ case DP_NL_VID_RGBA8880:
+ s->v_plane.format = PIXMAN_x8b8g8r8;
+ break;
+ default:
+ error_report("%s: unsupported video format %u", __func__,
+ s->avbufm_registers[AV_BUF_FORMAT] & DP_NL_VID_FMT_MASK);
+ abort();
+ }
+
+ xlnx_dp_recreate_surface(s);
+}
+
+static void xlnx_dp_update_irq(XlnxDPState *s)
+{
+ uint32_t flags;
+
+ flags = s->core_registers[DP_INT_STATUS] & ~s->core_registers[DP_INT_MASK];
+ DPRINTF("update IRQ value = %" PRIx32 "\n", flags);
+ qemu_set_irq(s->irq, flags != 0);
+}
+
+static uint64_t xlnx_dp_read(void *opaque, hwaddr offset, unsigned size)
+{
+ XlnxDPState *s = XLNX_DP(opaque);
+ uint64_t ret = 0;
+
+ offset = offset >> 2;
+
+ switch (offset) {
+ case DP_TX_USER_FIFO_OVERFLOW:
+ /* This register is cleared after a read */
+ ret = s->core_registers[DP_TX_USER_FIFO_OVERFLOW];
+ s->core_registers[DP_TX_USER_FIFO_OVERFLOW] = 0;
+ break;
+ case DP_AUX_REPLY_DATA:
+ ret = xlnx_dp_aux_pop_rx_fifo(s);
+ break;
+ case DP_INTERRUPT_SIGNAL_STATE:
+ /*
+ * XXX: Not sure it is the right thing to do actually.
+ * The register is not written by the device driver so it's stuck
+ * to 0x04.
+ */
+ ret = s->core_registers[DP_INTERRUPT_SIGNAL_STATE];
+ s->core_registers[DP_INTERRUPT_SIGNAL_STATE] &= ~0x04;
+ break;
+ case DP_AUX_WRITE_FIFO:
+ case DP_TX_AUDIO_INFO_DATA(0):
+ case DP_TX_AUDIO_INFO_DATA(1):
+ case DP_TX_AUDIO_INFO_DATA(2):
+ case DP_TX_AUDIO_INFO_DATA(3):
+ case DP_TX_AUDIO_INFO_DATA(4):
+ case DP_TX_AUDIO_INFO_DATA(5):
+ case DP_TX_AUDIO_INFO_DATA(6):
+ case DP_TX_AUDIO_INFO_DATA(7):
+ case DP_TX_AUDIO_EXT_DATA(0):
+ case DP_TX_AUDIO_EXT_DATA(1):
+ case DP_TX_AUDIO_EXT_DATA(2):
+ case DP_TX_AUDIO_EXT_DATA(3):
+ case DP_TX_AUDIO_EXT_DATA(4):
+ case DP_TX_AUDIO_EXT_DATA(5):
+ case DP_TX_AUDIO_EXT_DATA(6):
+ case DP_TX_AUDIO_EXT_DATA(7):
+ case DP_TX_AUDIO_EXT_DATA(8):
+ /* write only registers */
+ ret = 0;
+ break;
+ default:
+ assert(offset <= (0x3AC >> 2));
+ if (offset == (0x3A8 >> 2) || offset == (0x3AC >> 2)) {
+ ret = s->core_registers[DP_INT_MASK];
+ } else {
+ ret = s->core_registers[offset];
+ }
+ break;
+ }
+
+ DPRINTF("core read @%" PRIx64 " = 0x%8.8" PRIX64 "\n", offset << 2, ret);
+ return ret;
+}
+
+static void xlnx_dp_write(void *opaque, hwaddr offset, uint64_t value,
+ unsigned size)
+{
+ XlnxDPState *s = XLNX_DP(opaque);
+
+ DPRINTF("core write @%" PRIx64 " = 0x%8.8" PRIX64 "\n", offset, value);
+
+ offset = offset >> 2;
+
+ switch (offset) {
+ /*
+ * Only special write case are handled.
+ */
+ case DP_LINK_BW_SET:
+ s->core_registers[offset] = value & 0x000000FF;
+ break;
+ case DP_LANE_COUNT_SET:
+ case DP_MAIN_STREAM_MISC0:
+ s->core_registers[offset] = value & 0x0000000F;
+ break;
+ case DP_TRAINING_PATTERN_SET:
+ case DP_LINK_QUAL_PATTERN_SET:
+ case DP_MAIN_STREAM_POLARITY:
+ case DP_PHY_VOLTAGE_DIFF_LANE_0:
+ case DP_PHY_VOLTAGE_DIFF_LANE_1:
+ s->core_registers[offset] = value & 0x00000003;
+ break;
+ case DP_ENHANCED_FRAME_EN:
+ case DP_SCRAMBLING_DISABLE:
+ case DP_DOWNSPREAD_CTRL:
+ case DP_MAIN_STREAM_ENABLE:
+ case DP_TRANSMIT_PRBS7:
+ s->core_registers[offset] = value & 0x00000001;
+ break;
+ case DP_PHY_CLOCK_SELECT:
+ s->core_registers[offset] = value & 0x00000007;
+ break;
+ case DP_SOFTWARE_RESET:
+ /*
+ * No need to update this bit as it's read '0'.
+ */
+ /*
+ * TODO: reset IP.
+ */
+ break;
+ case DP_TRANSMITTER_ENABLE:
+ s->core_registers[offset] = value & 0x01;
+ break;
+ case DP_FORCE_SCRAMBLER_RESET:
+ /*
+ * No need to update this bit as it's read '0'.
+ */
+ /*
+ * TODO: force a scrambler reset??
+ */
+ break;
+ case DP_AUX_COMMAND_REGISTER:
+ s->core_registers[offset] = value & 0x00001F0F;
+ xlnx_dp_aux_set_command(s, s->core_registers[offset]);
+ break;
+ case DP_MAIN_STREAM_HTOTAL:
+ case DP_MAIN_STREAM_VTOTAL:
+ case DP_MAIN_STREAM_HSTART:
+ case DP_MAIN_STREAM_VSTART:
+ s->core_registers[offset] = value & 0x0000FFFF;
+ break;
+ case DP_MAIN_STREAM_HRES:
+ case DP_MAIN_STREAM_VRES:
+ s->core_registers[offset] = value & 0x0000FFFF;
+ xlnx_dp_recreate_surface(s);
+ break;
+ case DP_MAIN_STREAM_HSWIDTH:
+ case DP_MAIN_STREAM_VSWIDTH:
+ s->core_registers[offset] = value & 0x00007FFF;
+ break;
+ case DP_MAIN_STREAM_MISC1:
+ s->core_registers[offset] = value & 0x00000086;
+ break;
+ case DP_MAIN_STREAM_M_VID:
+ case DP_MAIN_STREAM_N_VID:
+ s->core_registers[offset] = value & 0x00FFFFFF;
+ break;
+ case DP_MSA_TRANSFER_UNIT_SIZE:
+ case DP_MIN_BYTES_PER_TU:
+ case DP_INIT_WAIT:
+ s->core_registers[offset] = value & 0x00000007;
+ break;
+ case DP_USER_DATA_COUNT_PER_LANE:
+ s->core_registers[offset] = value & 0x0003FFFF;
+ break;
+ case DP_FRAC_BYTES_PER_TU:
+ s->core_registers[offset] = value & 0x000003FF;
+ break;
+ case DP_PHY_RESET:
+ s->core_registers[offset] = value & 0x00010003;
+ /*
+ * TODO: Reset something?
+ */
+ break;
+ case DP_TX_PHY_POWER_DOWN:
+ s->core_registers[offset] = value & 0x0000000F;
+ /*
+ * TODO: Power down things?
+ */
+ break;
+ case DP_AUX_WRITE_FIFO: {
+ uint8_t c = value;
+ xlnx_dp_aux_push_tx_fifo(s, &c, 1);
+ break;
+ }
+ case DP_AUX_CLOCK_DIVIDER:
+ break;
+ case DP_AUX_REPLY_COUNT:
+ /*
+ * Writing to this register clear the counter.
+ */
+ s->core_registers[offset] = 0x00000000;
+ break;
+ case DP_AUX_ADDRESS:
+ s->core_registers[offset] = value & 0x000FFFFF;
+ break;
+ case DP_VERSION_REGISTER:
+ case DP_CORE_ID:
+ case DP_TX_USER_FIFO_OVERFLOW:
+ case DP_AUX_REPLY_DATA:
+ case DP_AUX_REPLY_CODE:
+ case DP_REPLY_DATA_COUNT:
+ case DP_REPLY_STATUS:
+ case DP_HPD_DURATION:
+ /*
+ * Write to read only location..
+ */
+ break;
+ case DP_TX_AUDIO_CONTROL:
+ s->core_registers[offset] = value & 0x00000001;
+ xlnx_dp_audio_activate(s);
+ break;
+ case DP_TX_AUDIO_CHANNELS:
+ s->core_registers[offset] = value & 0x00000007;
+ xlnx_dp_audio_activate(s);
+ break;
+ case DP_INT_STATUS:
+ s->core_registers[DP_INT_STATUS] &= ~value;
+ xlnx_dp_update_irq(s);
+ break;
+ case DP_INT_EN:
+ s->core_registers[DP_INT_MASK] &= ~value;
+ xlnx_dp_update_irq(s);
+ break;
+ case DP_INT_DS:
+ s->core_registers[DP_INT_MASK] |= ~value;
+ xlnx_dp_update_irq(s);
+ break;
+ default:
+ assert(offset <= (0x504C >> 2));
+ s->core_registers[offset] = value;
+ break;
+ }
+}
+
+static const MemoryRegionOps dp_ops = {
+ .read = xlnx_dp_read,
+ .write = xlnx_dp_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+/*
+ * This is to handle Read/Write to the Video Blender.
+ */
+static void xlnx_dp_vblend_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ XlnxDPState *s = XLNX_DP(opaque);
+ bool alpha_was_enabled;
+
+ DPRINTF("vblend: write @0x%" HWADDR_PRIX " = 0x%" PRIX32 "\n", offset,
+ (uint32_t)value);
+ offset = offset >> 2;
+
+ switch (offset) {
+ case V_BLEND_BG_CLR_0:
+ case V_BLEND_BG_CLR_1:
+ case V_BLEND_BG_CLR_2:
+ s->vblend_registers[offset] = value & 0x00000FFF;
+ break;
+ case V_BLEND_SET_GLOBAL_ALPHA_REG:
+ /*
+ * A write to this register can enable or disable blending. Thus we need
+ * to recreate the surfaces.
+ */
+ alpha_was_enabled = xlnx_dp_global_alpha_enabled(s);
+ s->vblend_registers[offset] = value & 0x000001FF;
+ if (xlnx_dp_global_alpha_enabled(s) != alpha_was_enabled) {
+ xlnx_dp_recreate_surface(s);
+ }
+ break;
+ case V_BLEND_OUTPUT_VID_FORMAT:
+ s->vblend_registers[offset] = value & 0x00000017;
+ break;
+ case V_BLEND_LAYER0_CONTROL:
+ case V_BLEND_LAYER1_CONTROL:
+ s->vblend_registers[offset] = value & 0x00000103;
+ break;
+ case V_BLEND_RGB2YCBCR_COEFF(0):
+ case V_BLEND_RGB2YCBCR_COEFF(1):
+ case V_BLEND_RGB2YCBCR_COEFF(2):
+ case V_BLEND_RGB2YCBCR_COEFF(3):
+ case V_BLEND_RGB2YCBCR_COEFF(4):
+ case V_BLEND_RGB2YCBCR_COEFF(5):
+ case V_BLEND_RGB2YCBCR_COEFF(6):
+ case V_BLEND_RGB2YCBCR_COEFF(7):
+ case V_BLEND_RGB2YCBCR_COEFF(8):
+ case V_BLEND_IN1CSC_COEFF(0):
+ case V_BLEND_IN1CSC_COEFF(1):
+ case V_BLEND_IN1CSC_COEFF(2):
+ case V_BLEND_IN1CSC_COEFF(3):
+ case V_BLEND_IN1CSC_COEFF(4):
+ case V_BLEND_IN1CSC_COEFF(5):
+ case V_BLEND_IN1CSC_COEFF(6):
+ case V_BLEND_IN1CSC_COEFF(7):
+ case V_BLEND_IN1CSC_COEFF(8):
+ case V_BLEND_IN2CSC_COEFF(0):
+ case V_BLEND_IN2CSC_COEFF(1):
+ case V_BLEND_IN2CSC_COEFF(2):
+ case V_BLEND_IN2CSC_COEFF(3):
+ case V_BLEND_IN2CSC_COEFF(4):
+ case V_BLEND_IN2CSC_COEFF(5):
+ case V_BLEND_IN2CSC_COEFF(6):
+ case V_BLEND_IN2CSC_COEFF(7):
+ case V_BLEND_IN2CSC_COEFF(8):
+ s->vblend_registers[offset] = value & 0x0000FFFF;
+ break;
+ case V_BLEND_LUMA_IN1CSC_OFFSET:
+ case V_BLEND_CR_IN1CSC_OFFSET:
+ case V_BLEND_CB_IN1CSC_OFFSET:
+ case V_BLEND_LUMA_IN2CSC_OFFSET:
+ case V_BLEND_CR_IN2CSC_OFFSET:
+ case V_BLEND_CB_IN2CSC_OFFSET:
+ case V_BLEND_LUMA_OUTCSC_OFFSET:
+ case V_BLEND_CR_OUTCSC_OFFSET:
+ case V_BLEND_CB_OUTCSC_OFFSET:
+ s->vblend_registers[offset] = value & 0x3FFF7FFF;
+ break;
+ case V_BLEND_CHROMA_KEY_ENABLE:
+ s->vblend_registers[offset] = value & 0x00000003;
+ break;
+ case V_BLEND_CHROMA_KEY_COMP1:
+ case V_BLEND_CHROMA_KEY_COMP2:
+ case V_BLEND_CHROMA_KEY_COMP3:
+ s->vblend_registers[offset] = value & 0x0FFF0FFF;
+ break;
+ default:
+ s->vblend_registers[offset] = value;
+ break;
+ }
+}
+
+static uint64_t xlnx_dp_vblend_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ XlnxDPState *s = XLNX_DP(opaque);
+
+ DPRINTF("vblend: read @0x%" HWADDR_PRIX " = 0x%" PRIX32 "\n", offset,
+ s->vblend_registers[offset >> 2]);
+ return s->vblend_registers[offset >> 2];
+}
+
+static const MemoryRegionOps vblend_ops = {
+ .read = xlnx_dp_vblend_read,
+ .write = xlnx_dp_vblend_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+/*
+ * This is to handle Read/Write to the Audio Video buffer manager.
+ */
+static void xlnx_dp_avbufm_write(void *opaque, hwaddr offset, uint64_t value,
+ unsigned size)
+{
+ XlnxDPState *s = XLNX_DP(opaque);
+
+ DPRINTF("avbufm: write @0x%" HWADDR_PRIX " = 0x%" PRIX32 "\n", offset,
+ (uint32_t)value);
+ offset = offset >> 2;
+
+ switch (offset) {
+ case AV_BUF_FORMAT:
+ s->avbufm_registers[offset] = value & 0x00000FFF;
+ xlnx_dp_change_graphic_fmt(s);
+ break;
+ case AV_CHBUF0:
+ case AV_CHBUF1:
+ case AV_CHBUF2:
+ case AV_CHBUF3:
+ case AV_CHBUF4:
+ case AV_CHBUF5:
+ s->avbufm_registers[offset] = value & 0x0000007F;
+ break;
+ case AV_BUF_OUTPUT_AUDIO_VIDEO_SELECT:
+ s->avbufm_registers[offset] = value & 0x0000007F;
+ break;
+ case AV_BUF_DITHER_CONFIG:
+ s->avbufm_registers[offset] = value & 0x000007FF;
+ break;
+ case AV_BUF_DITHER_CONFIG_MAX:
+ case AV_BUF_DITHER_CONFIG_MIN:
+ s->avbufm_registers[offset] = value & 0x00000FFF;
+ break;
+ case AV_BUF_PATTERN_GEN_SELECT:
+ s->avbufm_registers[offset] = value & 0xFFFFFF03;
+ break;
+ case AV_BUF_AUD_VID_CLK_SOURCE:
+ s->avbufm_registers[offset] = value & 0x00000007;
+ break;
+ case AV_BUF_SRST_REG:
+ s->avbufm_registers[offset] = value & 0x00000002;
+ break;
+ case AV_BUF_AUDIO_CH_CONFIG:
+ s->avbufm_registers[offset] = value & 0x00000003;
+ break;
+ case AV_BUF_GRAPHICS_COMP_SCALE_FACTOR(0):
+ case AV_BUF_GRAPHICS_COMP_SCALE_FACTOR(1):
+ case AV_BUF_GRAPHICS_COMP_SCALE_FACTOR(2):
+ case AV_BUF_VIDEO_COMP_SCALE_FACTOR(0):
+ case AV_BUF_VIDEO_COMP_SCALE_FACTOR(1):
+ case AV_BUF_VIDEO_COMP_SCALE_FACTOR(2):
+ s->avbufm_registers[offset] = value & 0x0000FFFF;
+ break;
+ case AV_BUF_LIVE_VIDEO_COMP_SF(0):
+ case AV_BUF_LIVE_VIDEO_COMP_SF(1):
+ case AV_BUF_LIVE_VIDEO_COMP_SF(2):
+ case AV_BUF_LIVE_VID_CONFIG:
+ case AV_BUF_LIVE_GFX_COMP_SF(0):
+ case AV_BUF_LIVE_GFX_COMP_SF(1):
+ case AV_BUF_LIVE_GFX_COMP_SF(2):
+ case AV_BUF_LIVE_GFX_CONFIG:
+ case AV_BUF_NON_LIVE_LATENCY:
+ case AV_BUF_STC_CONTROL:
+ case AV_BUF_STC_INIT_VALUE0:
+ case AV_BUF_STC_INIT_VALUE1:
+ case AV_BUF_STC_ADJ:
+ case AV_BUF_STC_VIDEO_VSYNC_TS_REG0:
+ case AV_BUF_STC_VIDEO_VSYNC_TS_REG1:
+ case AV_BUF_STC_EXT_VSYNC_TS_REG0:
+ case AV_BUF_STC_EXT_VSYNC_TS_REG1:
+ case AV_BUF_STC_CUSTOM_EVENT_TS_REG0:
+ case AV_BUF_STC_CUSTOM_EVENT_TS_REG1:
+ case AV_BUF_STC_CUSTOM_EVENT2_TS_REG0:
+ case AV_BUF_STC_CUSTOM_EVENT2_TS_REG1:
+ case AV_BUF_STC_SNAPSHOT0:
+ case AV_BUF_STC_SNAPSHOT1:
+ case AV_BUF_HCOUNT_VCOUNT_INT0:
+ case AV_BUF_HCOUNT_VCOUNT_INT1:
+ qemu_log_mask(LOG_UNIMP, "avbufm: unimplemented register 0x%04"
+ PRIx64 "\n",
+ offset << 2);
+ break;
+ default:
+ s->avbufm_registers[offset] = value;
+ break;
+ }
+}
+
+static uint64_t xlnx_dp_avbufm_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ XlnxDPState *s = XLNX_DP(opaque);
+
+ offset = offset >> 2;
+ return s->avbufm_registers[offset];
+}
+
+static const MemoryRegionOps avbufm_ops = {
+ .read = xlnx_dp_avbufm_read,
+ .write = xlnx_dp_avbufm_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+/*
+ * This is a global alpha blending using pixman.
+ * Both graphic and video planes are multiplied with the global alpha
+ * coefficient and added.
+ */
+static inline void xlnx_dp_blend_surface(XlnxDPState *s)
+{
+ pixman_fixed_t alpha1[] = { pixman_double_to_fixed(1),
+ pixman_double_to_fixed(1),
+ pixman_double_to_fixed(1.0) };
+ pixman_fixed_t alpha2[] = { pixman_double_to_fixed(1),
+ pixman_double_to_fixed(1),
+ pixman_double_to_fixed(1.0) };
+
+ if ((surface_width(s->g_plane.surface)
+ != surface_width(s->v_plane.surface)) ||
+ (surface_height(s->g_plane.surface)
+ != surface_height(s->v_plane.surface))) {
+ return;
+ }
+
+ alpha1[2] = pixman_double_to_fixed((double)(xlnx_dp_global_alpha_value(s))
+ / 256.0);
+ alpha2[2] = pixman_double_to_fixed((255.0
+ - (double)xlnx_dp_global_alpha_value(s))
+ / 256.0);
+
+ pixman_image_set_filter(s->g_plane.surface->image,
+ PIXMAN_FILTER_CONVOLUTION, alpha1, 3);
+ pixman_image_composite(PIXMAN_OP_SRC, s->g_plane.surface->image, 0,
+ s->bout_plane.surface->image, 0, 0, 0, 0, 0, 0,
+ surface_width(s->g_plane.surface),
+ surface_height(s->g_plane.surface));
+ pixman_image_set_filter(s->v_plane.surface->image,
+ PIXMAN_FILTER_CONVOLUTION, alpha2, 3);
+ pixman_image_composite(PIXMAN_OP_ADD, s->v_plane.surface->image, 0,
+ s->bout_plane.surface->image, 0, 0, 0, 0, 0, 0,
+ surface_width(s->g_plane.surface),
+ surface_height(s->g_plane.surface));
+}
+
+static void xlnx_dp_update_display(void *opaque)
+{
+ XlnxDPState *s = XLNX_DP(opaque);
+
+ if ((s->core_registers[DP_TRANSMITTER_ENABLE] & 0x01) == 0) {
+ return;
+ }
+
+ s->core_registers[DP_INT_STATUS] |= (1 << 13);
+ xlnx_dp_update_irq(s);
+
+ xlnx_dpdma_trigger_vsync_irq(s->dpdma);
+
+ /*
+ * Trigger the DMA channel.
+ */
+ if (!xlnx_dpdma_start_operation(s->dpdma, 3, false)) {
+ /*
+ * An error occurred don't do anything with the data..
+ * Trigger an underflow interrupt.
+ */
+ s->core_registers[DP_INT_STATUS] |= (1 << 21);
+ xlnx_dp_update_irq(s);
+ return;
+ }
+
+ if (xlnx_dp_global_alpha_enabled(s)) {
+ if (!xlnx_dpdma_start_operation(s->dpdma, 0, false)) {
+ s->core_registers[DP_INT_STATUS] |= (1 << 21);
+ xlnx_dp_update_irq(s);
+ return;
+ }
+ xlnx_dp_blend_surface(s);
+ }
+
+ /*
+ * XXX: We might want to update only what changed.
+ */
+ dpy_gfx_update_full(s->console);
+}
+
+static const GraphicHwOps xlnx_dp_gfx_ops = {
+ .gfx_update = xlnx_dp_update_display,
+};
+
+static void xlnx_dp_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ XlnxDPState *s = XLNX_DP(obj);
+
+ memory_region_init(&s->container, obj, TYPE_XLNX_DP, 0xC050);
+
+ memory_region_init_io(&s->core_iomem, obj, &dp_ops, s, TYPE_XLNX_DP
+ ".core", 0x3AF);
+ memory_region_add_subregion(&s->container, 0x0000, &s->core_iomem);
+
+ memory_region_init_io(&s->vblend_iomem, obj, &vblend_ops, s, TYPE_XLNX_DP
+ ".v_blend", 0x1DF);
+ memory_region_add_subregion(&s->container, 0xA000, &s->vblend_iomem);
+
+ memory_region_init_io(&s->avbufm_iomem, obj, &avbufm_ops, s, TYPE_XLNX_DP
+ ".av_buffer_manager", 0x238);
+ memory_region_add_subregion(&s->container, 0xB000, &s->avbufm_iomem);
+
+ memory_region_init_io(&s->audio_iomem, obj, &audio_ops, s, TYPE_XLNX_DP
+ ".audio", sizeof(s->audio_registers));
+ memory_region_add_subregion(&s->container, 0xC000, &s->audio_iomem);
+
+ sysbus_init_mmio(sbd, &s->container);
+ sysbus_init_irq(sbd, &s->irq);
+
+ object_property_add_link(obj, "dpdma", TYPE_XLNX_DPDMA,
+ (Object **) &s->dpdma,
+ xlnx_dp_set_dpdma,
+ OBJ_PROP_LINK_STRONG);
+
+ /*
+ * Initialize AUX Bus.
+ */
+ s->aux_bus = aux_bus_init(DEVICE(obj), "aux");
+
+ /*
+ * Initialize DPCD and EDID..
+ */
+ s->dpcd = DPCD(qdev_new("dpcd"));
+ object_property_add_child(OBJECT(s), "dpcd", OBJECT(s->dpcd));
+
+ s->edid = I2CDDC(qdev_new("i2c-ddc"));
+ i2c_slave_set_address(I2C_SLAVE(s->edid), 0x50);
+ object_property_add_child(OBJECT(s), "edid", OBJECT(s->edid));
+
+ fifo8_create(&s->rx_fifo, 16);
+ fifo8_create(&s->tx_fifo, 16);
+}
+
+static void xlnx_dp_finalize(Object *obj)
+{
+ XlnxDPState *s = XLNX_DP(obj);
+
+ fifo8_destroy(&s->tx_fifo);
+ fifo8_destroy(&s->rx_fifo);
+}
+
+static void xlnx_dp_realize(DeviceState *dev, Error **errp)
+{
+ XlnxDPState *s = XLNX_DP(dev);
+ DisplaySurface *surface;
+ struct audsettings as;
+
+ aux_bus_realize(s->aux_bus);
+
+ qdev_realize(DEVICE(s->dpcd), BUS(s->aux_bus), &error_fatal);
+ aux_map_slave(AUX_SLAVE(s->dpcd), 0x0000);
+
+ qdev_realize_and_unref(DEVICE(s->edid), BUS(aux_get_i2c_bus(s->aux_bus)),
+ &error_fatal);
+
+ s->console = graphic_console_init(dev, 0, &xlnx_dp_gfx_ops, s);
+ surface = qemu_console_surface(s->console);
+ xlnx_dpdma_set_host_data_location(s->dpdma, DP_GRAPHIC_DMA_CHANNEL,
+ surface_data(surface));
+
+ as.freq = 44100;
+ as.nchannels = 2;
+ as.fmt = AUDIO_FORMAT_S16;
+ as.endianness = 0;
+
+ AUD_register_card("xlnx_dp.audio", &s->aud_card);
+
+ s->amixer_output_stream = AUD_open_out(&s->aud_card,
+ s->amixer_output_stream,
+ "xlnx_dp.audio.out",
+ s,
+ xlnx_dp_audio_callback,
+ &as);
+ AUD_set_volume_out(s->amixer_output_stream, 0, 255, 255);
+ xlnx_dp_audio_activate(s);
+}
+
+static void xlnx_dp_reset(DeviceState *dev)
+{
+ XlnxDPState *s = XLNX_DP(dev);
+
+ memset(s->core_registers, 0, sizeof(s->core_registers));
+ s->core_registers[DP_VERSION_REGISTER] = 0x04010000;
+ s->core_registers[DP_CORE_ID] = 0x01020000;
+ s->core_registers[DP_REPLY_STATUS] = 0x00000010;
+ s->core_registers[DP_MSA_TRANSFER_UNIT_SIZE] = 0x00000040;
+ s->core_registers[DP_INIT_WAIT] = 0x00000020;
+ s->core_registers[DP_PHY_RESET] = 0x00010003;
+ s->core_registers[DP_INT_MASK] = 0xFFFFF03F;
+ s->core_registers[DP_PHY_STATUS] = 0x00000043;
+ s->core_registers[DP_INTERRUPT_SIGNAL_STATE] = 0x00000001;
+
+ s->vblend_registers[V_BLEND_RGB2YCBCR_COEFF(0)] = 0x00001000;
+ s->vblend_registers[V_BLEND_RGB2YCBCR_COEFF(4)] = 0x00001000;
+ s->vblend_registers[V_BLEND_RGB2YCBCR_COEFF(8)] = 0x00001000;
+ s->vblend_registers[V_BLEND_IN1CSC_COEFF(0)] = 0x00001000;
+ s->vblend_registers[V_BLEND_IN1CSC_COEFF(4)] = 0x00001000;
+ s->vblend_registers[V_BLEND_IN1CSC_COEFF(8)] = 0x00001000;
+ s->vblend_registers[V_BLEND_IN2CSC_COEFF(0)] = 0x00001000;
+ s->vblend_registers[V_BLEND_IN2CSC_COEFF(4)] = 0x00001000;
+ s->vblend_registers[V_BLEND_IN2CSC_COEFF(8)] = 0x00001000;
+
+ s->avbufm_registers[AV_BUF_NON_LIVE_LATENCY] = 0x00000180;
+ s->avbufm_registers[AV_BUF_OUTPUT_AUDIO_VIDEO_SELECT] = 0x00000008;
+ s->avbufm_registers[AV_BUF_DITHER_CONFIG_MAX] = 0x00000FFF;
+ s->avbufm_registers[AV_BUF_GRAPHICS_COMP_SCALE_FACTOR(0)] = 0x00010101;
+ s->avbufm_registers[AV_BUF_GRAPHICS_COMP_SCALE_FACTOR(1)] = 0x00010101;
+ s->avbufm_registers[AV_BUF_GRAPHICS_COMP_SCALE_FACTOR(2)] = 0x00010101;
+ s->avbufm_registers[AV_BUF_VIDEO_COMP_SCALE_FACTOR(0)] = 0x00010101;
+ s->avbufm_registers[AV_BUF_VIDEO_COMP_SCALE_FACTOR(1)] = 0x00010101;
+ s->avbufm_registers[AV_BUF_VIDEO_COMP_SCALE_FACTOR(2)] = 0x00010101;
+ s->avbufm_registers[AV_BUF_LIVE_VIDEO_COMP_SF(0)] = 0x00010101;
+ s->avbufm_registers[AV_BUF_LIVE_VIDEO_COMP_SF(1)] = 0x00010101;
+ s->avbufm_registers[AV_BUF_LIVE_VIDEO_COMP_SF(2)] = 0x00010101;
+ s->avbufm_registers[AV_BUF_LIVE_GFX_COMP_SF(0)] = 0x00010101;
+ s->avbufm_registers[AV_BUF_LIVE_GFX_COMP_SF(1)] = 0x00010101;
+ s->avbufm_registers[AV_BUF_LIVE_GFX_COMP_SF(2)] = 0x00010101;
+
+ memset(s->audio_registers, 0, sizeof(s->audio_registers));
+ s->byte_left = 0;
+
+ xlnx_dp_aux_clear_rx_fifo(s);
+ xlnx_dp_change_graphic_fmt(s);
+ xlnx_dp_update_irq(s);
+}
+
+static void xlnx_dp_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ dc->realize = xlnx_dp_realize;
+ dc->vmsd = &vmstate_dp;
+ dc->reset = xlnx_dp_reset;
+}
+
+static const TypeInfo xlnx_dp_info = {
+ .name = TYPE_XLNX_DP,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(XlnxDPState),
+ .instance_init = xlnx_dp_init,
+ .instance_finalize = xlnx_dp_finalize,
+ .class_init = xlnx_dp_class_init,
+};
+
+static void xlnx_dp_register_types(void)
+{
+ type_register_static(&xlnx_dp_info);
+}
+
+type_init(xlnx_dp_register_types)