/* * QEMU HP Artist Emulation * * Copyright (c) 2019 Sven Schnelle * * 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); posx = ADDR_TO_X(addr); 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); posx = ADDR_TO_X(addr); 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)