diff options
Diffstat (limited to 'hw/display')
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(©->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(®ion, scanout->x, scanout->y, + scanout->width, scanout->height); + + pixman_region_intersect(&finalregion, &flush_region, ®ion); + 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(®ion); + 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) |