aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--MAINTAINERS4
-rw-r--r--hw/display/Kconfig4
-rw-r--r--hw/display/Makefile.objs1
-rw-r--r--hw/display/artist.c1454
-rw-r--r--hw/display/trace-events9
-rw-r--r--hw/hppa/Kconfig3
-rw-r--r--hw/hppa/Makefile.objs2
-rw-r--r--hw/hppa/dino.c97
-rw-r--r--hw/hppa/hppa_hardware.h1
-rw-r--r--hw/hppa/hppa_sys.h2
-rw-r--r--hw/hppa/lasi.c368
-rw-r--r--hw/hppa/machine.c33
-rw-r--r--hw/hppa/trace-events10
-rw-r--r--hw/input/Kconfig3
-rw-r--r--hw/input/Makefile.objs1
-rw-r--r--hw/input/lasips2.c291
-rw-r--r--hw/input/ps2.c15
-rw-r--r--hw/input/trace-events5
-rw-r--r--hw/net/Kconfig7
-rw-r--r--hw/net/Makefile.objs2
-rw-r--r--hw/net/i82596.c734
-rw-r--r--hw/net/i82596.h55
-rw-r--r--hw/net/lasi_i82596.c188
-rw-r--r--hw/net/trace-events13
-rw-r--r--include/hw/input/lasips2.h16
-rw-r--r--include/hw/input/ps2.h1
-rw-r--r--include/hw/net/lasi_82596.h29
-rw-r--r--pc-bios/hppa-firmware.imgbin783724 -> 766136 bytes
m---------roms/seabios-hppa0
-rw-r--r--target/hppa/helper.h2
-rw-r--r--target/hppa/op_helper.c9
-rw-r--r--target/hppa/translate.c15
-rw-r--r--tests/qtest/boot-serial-test.c3
33 files changed, 3350 insertions, 27 deletions
diff --git a/MAINTAINERS b/MAINTAINERS
index f6511d5..efd3f38 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -178,6 +178,8 @@ S: Maintained
F: target/hppa/
F: hw/hppa/
F: disas/hppa.c
+F: hw/net/*i82596*
+F: include/hw/net/lasi_82596.h
LM32 TCG CPUs
M: Michael Walle <michael@walle.cc>
@@ -890,7 +892,7 @@ F: hw/*/etraxfs_*.c
HP-PARISC Machines
------------------
-Dino
+HP B160L
M: Richard Henderson <rth@twiddle.net>
R: Helge Deller <deller@gmx.de>
S: Odd Fixes
diff --git a/hw/display/Kconfig b/hw/display/Kconfig
index c500d1f..15d59e1 100644
--- a/hw/display/Kconfig
+++ b/hw/display/Kconfig
@@ -91,6 +91,10 @@ config TCX
config CG3
bool
+config ARTIST
+ bool
+ select FRAMEBUFFER
+
config VGA
bool
diff --git a/hw/display/Makefile.objs b/hw/display/Makefile.objs
index f2182e3..5f03dfd 100644
--- a/hw/display/Makefile.objs
+++ b/hw/display/Makefile.objs
@@ -40,6 +40,7 @@ common-obj-$(CONFIG_SM501) += sm501.o
common-obj-$(CONFIG_TCX) += tcx.o
common-obj-$(CONFIG_CG3) += cg3.o
common-obj-$(CONFIG_NEXTCUBE) += next-fb.o
+common-obj-$(CONFIG_ARTIST) += artist.o
obj-$(CONFIG_VGA) += vga.o
diff --git a/hw/display/artist.c b/hw/display/artist.c
new file mode 100644
index 0000000..65be9e3
--- /dev/null
+++ b/hw/display/artist.c
@@ -0,0 +1,1454 @@
+/*
+ * 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/typedefs.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 "hw/display/framebuffer.h"
+
+#define TYPE_ARTIST "artist"
+#define ARTIST(obj) OBJECT_CHECK(ARTISTState, (obj), TYPE_ARTIST)
+
+#ifdef HOST_WORDS_BIGENDIAN
+#define ROP8OFF(_i) (3 - (_i))
+#else
+#define ROP8OFF
+#endif
+
+struct vram_buffer {
+ MemoryRegion mr;
+ uint8_t *data;
+ int size;
+ int width;
+ int height;
+};
+
+typedef 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;
+} ARTISTState;
+
+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
+
+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 = 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, uint8_t *dst, uint8_t val)
+{
+
+ const artist_rop_t op = artist_get_op(s);
+ uint8_t plane_mask = s->plane_mask & 0xff;
+
+ switch (op) {
+ case ARTIST_ROP_CLEAR:
+ *dst &= ~plane_mask;
+ break;
+
+ case ARTIST_ROP_COPY:
+ *dst &= ~plane_mask;
+ *dst |= 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 posx, 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, offset, height, width;
+ uint8_t *data8, *p;
+
+ pix_count = vram_write_pix_per_transfer(s);
+ pix_length = vram_pixel_length(s);
+
+ buf = vram_write_buffer(s);
+ height = buf->height;
+ width = buf->width;
+
+ if (s->cmap_bm_access) {
+ offset = s->vram_pos;
+ } else {
+ offset = posy * width + posx;
+ }
+
+ if (!buf->size) {
+ qemu_log("write to non-existent buffer\n");
+ return;
+ }
+
+ p = buf->data;
+
+ if (pix_count > size * 8) {
+ pix_count = size * 8;
+ }
+
+ if (posy * width + posx + pix_count > buf->size) {
+ qemu_log("write outside bounds: wants %dx%d, max size %dx%d\n",
+ posx, posy, width, height);
+ return;
+ }
+
+
+ switch (pix_length) {
+ case 0:
+ if (s->image_bitmap_op & 0x20000000) {
+ data &= vram_bitmask;
+ }
+
+ for (i = 0; i < pix_count; i++) {
+ artist_rop8(s, p + offset + pix_count - 1 - i,
+ (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) {
+ *(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))) {
+ artist_rop8(s, p + offset + 3 - i, 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; i++) {
+ mask = 1 << (pix_count - 1 - i);
+
+ if (!(s->image_bitmap_op & 0x20000000) ||
+ (vram_bitmask & mask)) {
+ if (data & mask) {
+ artist_rop8(s, p + offset + i, s->fg_color);
+ } else {
+ if (!(s->image_bitmap_op & 0x10000002)) {
+ artist_rop8(s, p + 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, int source_x, int source_y, int dest_x,
+ int dest_y, int width, int height)
+{
+ struct vram_buffer *buf;
+ int line, endline, lineincr, startcolumn, endcolumn, columnincr, column;
+ uint32_t 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 (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);
+ dst = dest_x + ((line + dest_y) * buf->width);
+
+ for (column = startcolumn; column != endcolumn; column += columnincr) {
+ if (dst + column > buf->size || src + column > buf->size) {
+ continue;
+ }
+ artist_rop8(s, buf->data + dst + column, buf->data[src + column]);
+ }
+ }
+
+ artist_invalidate_lines(buf, dest_y, height);
+}
+
+static void fill_window(ARTISTState *s, int startx, int starty,
+ int width, int height)
+{
+ uint32_t 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->data + offset + x, color);
+ }
+ }
+ artist_invalidate_lines(buf, starty, height);
+}
+
+static void draw_line(ARTISTState *s, int x1, int y1, int x2, int y2,
+ bool update_start, int skip_pix, int max_pix)
+{
+ struct vram_buffer *buf;
+ uint8_t color = artist_get_color(s);
+ int dx, dy, t, e, x, y, incy, diago, horiz;
+ bool c1;
+ uint8_t *p;
+
+
+ if (update_start) {
+ s->vram_start = (x2 << 16) | y2;
+ }
+
+ buf = &s->vram_buffer[ARTIST_BUFFER_AP];
+
+ c1 = false;
+ incy = 1;
+
+ if (x2 > x1) {
+ dx = x2 - x1;
+ } else {
+ dx = x1 - x2;
+ }
+ if (y2 > y1) {
+ dy = y2 - y1;
+ } else {
+ dy = y1 - y2;
+ }
+ 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;
+
+ do {
+ if (c1) {
+ p = buf->data + x * s->width + y;
+ } else {
+ p = buf->data + y * s->width + x;
+ }
+
+ if (skip_pix > 0) {
+ skip_pix--;
+ } else {
+ artist_rop8(s, p, color);
+ }
+
+ if (e > 0) {
+ artist_invalidate_lines(buf, y, 1);
+ y += incy;
+ e += diago;
+ } else {
+ e += horiz;
+ }
+ x++;
+ } while (x <= x2 && (max_pix == -1 || --max_pix > 0));
+}
+
+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;
+
+ trace_artist_draw_line(startx, starty, endx, endy);
+ 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;
+
+ trace_artist_draw_line(startx, starty, endx, endy);
+ 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);
+
+ trace_artist_draw_line(startx, starty, endx, endy);
+ 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;
+ }
+
+
+ if (endx < 0) {
+ return;
+ }
+
+ if (endy < 0) {
+ return;
+ }
+
+ trace_artist_draw_line(startx, starty, endx, endy);
+ 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);
+
+ trace_artist_draw_line(startx, starty, endx, endy);
+ 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;
+
+ int startx = artist_get_x(s->vram_start);
+ int starty = artist_get_y(s->vram_start) + s->font_write_pos_y;
+ int offset = starty * s->width + startx;
+
+ buf = &s->vram_buffer[ARTIST_BUFFER_AP];
+
+ if (offset + 16 > buf->size) {
+ return;
+ }
+
+ for (i = 0; i < 16; i++) {
+ mask = 1 << (15 - i);
+ if (val & mask) {
+ artist_rop8(s, buf->data + offset + i, color);
+ } else {
+ if (!(s->image_bitmap_op & 0x20000000)) {
+ artist_rop8(s, buf->data + 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 posx, posy;
+ 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:
+ posx = (s->vram_pos >> 2) & 0x7ff;
+ posy = (s->vram_pos >> 13) & 0x3ff;
+ vram_bit_write(s, posx, posy + s->vram_char_y++, false, size, val);
+ break;
+
+ case VRAM_WRITE_INCR_X:
+ case VRAM_WRITE_INCR_X2:
+ posx = (s->vram_pos >> 2) & 0x7ff;
+ posy = (s->vram_pos >> 13) & 0x3ff;
+ vram_bit_write(s, posx, posy + 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;
+ int posy = (addr >> 11) & 0x3ff;
+ int posx = addr & 0x7ff;
+ uint32_t 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);
+ if (!buf->size) {
+ return;
+ }
+
+ if (posy > buf->height || posx > buf->width) {
+ return;
+ }
+
+ offset = posy * buf->width + posx;
+ switch (size) {
+ case 4:
+ *(uint32_t *)(buf->data + offset) = be32_to_cpu(val);
+ memory_region_set_dirty(&buf->mr, offset, 4);
+ break;
+ case 2:
+ *(uint16_t *)(buf->data + offset) = be16_to_cpu(val);
+ memory_region_set_dirty(&buf->mr, offset, 2);
+ break;
+ case 1:
+ *(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;
+ int posy, posx;
+
+ if (s->cmap_bm_access) {
+ buf = &s->vram_buffer[ARTIST_BUFFER_CMAP];
+ val = *(uint32_t *)(buf->data + addr);
+ trace_artist_vram_read(size, addr, 0, 0, val);
+ return 0;
+ }
+
+ buf = vram_read_buffer(s);
+ if (!buf->size) {
+ return 0;
+ }
+
+ posy = (addr >> 13) & 0x3ff;
+ posx = (addr >> 2) & 0x7ff;
+
+ 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,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ },
+};
+
+static const MemoryRegionOps artist_vram_ops = {
+ .read = artist_vram_read,
+ .write = artist_vram_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .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;
+
+ 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(DEVICE(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/trace-events b/hw/display/trace-events
index ba7787b..e6e22be 100644
--- a/hw/display/trace-events
+++ b/hw/display/trace-events
@@ -142,3 +142,12 @@ 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"
diff --git a/hw/hppa/Kconfig b/hw/hppa/Kconfig
index 6e5d74a..82178c7 100644
--- a/hw/hppa/Kconfig
+++ b/hw/hppa/Kconfig
@@ -10,3 +10,6 @@ config DINO
select IDE_CMD646
select MC146818RTC
select LSI_SCSI_PCI
+ select LASI_82596
+ select LASIPS2
+ select ARTIST
diff --git a/hw/hppa/Makefile.objs b/hw/hppa/Makefile.objs
index 67838f5..eac3467 100644
--- a/hw/hppa/Makefile.objs
+++ b/hw/hppa/Makefile.objs
@@ -1 +1 @@
-obj-$(CONFIG_DINO) += pci.o machine.o dino.o
+obj-$(CONFIG_DINO) += pci.o machine.o dino.o lasi.o
diff --git a/hw/hppa/dino.c b/hw/hppa/dino.c
index ab6969b..9797a7f 100644
--- a/hw/hppa/dino.c
+++ b/hw/hppa/dino.c
@@ -1,7 +1,7 @@
/*
- * HP-PARISC Dino PCI chipset emulation.
+ * HP-PARISC Dino PCI chipset emulation, as in B160L and similiar machines
*
- * (C) 2017 by Helge Deller <deller@gmx.de>
+ * (C) 2017-2019 by Helge Deller <deller@gmx.de>
*
* This work is licensed under the GNU GPL license version 2 or later.
*
@@ -21,6 +21,7 @@
#include "migration/vmstate.h"
#include "hppa_sys.h"
#include "exec/address-spaces.h"
+#include "trace.h"
#define TYPE_DINO_PCI_HOST_BRIDGE "dino-pcihost"
@@ -82,11 +83,28 @@
#define DINO_PCI_HOST_BRIDGE(obj) \
OBJECT_CHECK(DinoState, (obj), TYPE_DINO_PCI_HOST_BRIDGE)
+#define DINO800_REGS ((DINO_TLTIM - DINO_GMASK) / 4)
+static const uint32_t reg800_keep_bits[DINO800_REGS] = {
+ MAKE_64BIT_MASK(0, 1),
+ MAKE_64BIT_MASK(0, 7),
+ MAKE_64BIT_MASK(0, 7),
+ MAKE_64BIT_MASK(0, 8),
+ MAKE_64BIT_MASK(0, 7),
+ MAKE_64BIT_MASK(0, 9),
+ MAKE_64BIT_MASK(0, 32),
+ MAKE_64BIT_MASK(0, 8),
+ MAKE_64BIT_MASK(0, 30),
+ MAKE_64BIT_MASK(0, 25),
+ MAKE_64BIT_MASK(0, 22),
+ MAKE_64BIT_MASK(0, 9),
+};
+
typedef struct DinoState {
PCIHostState parent_obj;
/* PCI_CONFIG_ADDR is parent_obj.config_reg, via pci_host_conf_be_ops,
so that we can map PCI_CONFIG_DATA to pci_host_data_be_ops. */
+ uint32_t config_reg_dino; /* keep original copy, including 2 lowest bits */
uint32_t iar0;
uint32_t iar1;
@@ -94,8 +112,12 @@ typedef struct DinoState {
uint32_t ipr;
uint32_t icr;
uint32_t ilr;
+ uint32_t io_fbb_en;
uint32_t io_addr_en;
uint32_t io_control;
+ uint32_t toc_addr;
+
+ uint32_t reg800[DINO800_REGS];
MemoryRegion this_mem;
MemoryRegion pci_mem;
@@ -106,8 +128,6 @@ typedef struct DinoState {
MemoryRegion bm_ram_alias;
MemoryRegion bm_pci_alias;
MemoryRegion bm_cpu_alias;
-
- MemoryRegion cpu0_eir_mem;
} DinoState;
/*
@@ -122,6 +142,8 @@ static void gsc_to_pci_forwarding(DinoState *s)
tmp = extract32(s->io_control, 7, 2);
enabled = (tmp == 0x01);
io_addr_en = s->io_addr_en;
+ /* Mask out first (=firmware) and last (=Dino) areas. */
+ io_addr_en &= ~(BIT(31) | BIT(0));
memory_region_transaction_begin();
for (i = 1; i < 31; i++) {
@@ -142,6 +164,8 @@ static bool dino_chip_mem_valid(void *opaque, hwaddr addr,
unsigned size, bool is_write,
MemTxAttrs attrs)
{
+ bool ret = false;
+
switch (addr) {
case DINO_IAR0:
case DINO_IAR1:
@@ -152,16 +176,22 @@ static bool dino_chip_mem_valid(void *opaque, hwaddr addr,
case DINO_ICR:
case DINO_ILR:
case DINO_IO_CONTROL:
+ case DINO_IO_FBB_EN:
case DINO_IO_ADDR_EN:
case DINO_PCI_IO_DATA:
- return true;
+ case DINO_TOC_ADDR:
+ case DINO_GMASK ... DINO_TLTIM:
+ ret = true;
+ break;
case DINO_PCI_IO_DATA + 2:
- return size <= 2;
+ ret = (size <= 2);
+ break;
case DINO_PCI_IO_DATA + 1:
case DINO_PCI_IO_DATA + 3:
- return size == 1;
+ ret = (size == 1);
}
- return false;
+ trace_dino_chip_mem_valid(addr, ret);
+ return ret;
}
static MemTxResult dino_chip_read_with_attrs(void *opaque, hwaddr addr,
@@ -194,6 +224,9 @@ static MemTxResult dino_chip_read_with_attrs(void *opaque, hwaddr addr,
}
break;
+ case DINO_IO_FBB_EN:
+ val = s->io_fbb_en;
+ break;
case DINO_IO_ADDR_EN:
val = s->io_addr_en;
break;
@@ -227,12 +260,28 @@ static MemTxResult dino_chip_read_with_attrs(void *opaque, hwaddr addr,
case DINO_IRR1:
val = s->ilr & s->imr & s->icr;
break;
+ case DINO_TOC_ADDR:
+ val = s->toc_addr;
+ break;
+ case DINO_GMASK ... DINO_TLTIM:
+ val = s->reg800[(addr - DINO_GMASK) / 4];
+ if (addr == DINO_PAMR) {
+ val &= ~0x01; /* LSB is hardwired to 0 */
+ }
+ if (addr == DINO_MLTIM) {
+ val &= ~0x07; /* 3 LSB are hardwired to 0 */
+ }
+ if (addr == DINO_BRDG_FEAT) {
+ val &= ~(0x10710E0ul | 8); /* bits 5-7, 24 & 15 reserved */
+ }
+ break;
default:
/* Controlled by dino_chip_mem_valid above. */
g_assert_not_reached();
}
+ trace_dino_chip_read(addr, val);
*data = val;
return ret;
}
@@ -245,6 +294,9 @@ static MemTxResult dino_chip_write_with_attrs(void *opaque, hwaddr addr,
AddressSpace *io;
MemTxResult ret;
uint16_t ioaddr;
+ int i;
+
+ trace_dino_chip_write(addr, val);
switch (addr) {
case DINO_IO_DATA ... DINO_PCI_IO_DATA + 3:
@@ -266,9 +318,11 @@ static MemTxResult dino_chip_write_with_attrs(void *opaque, hwaddr addr,
}
return ret;
+ case DINO_IO_FBB_EN:
+ s->io_fbb_en = val & 0x03;
+ break;
case DINO_IO_ADDR_EN:
- /* Never allow first (=firmware) and last (=Dino) areas. */
- s->io_addr_en = val & 0x7ffffffe;
+ s->io_addr_en = val;
gsc_to_pci_forwarding(s);
break;
case DINO_IO_CONTROL:
@@ -292,6 +346,10 @@ static MemTxResult dino_chip_write_with_attrs(void *opaque, hwaddr addr,
/* Any write to IPR clears the register. */
s->ipr = 0;
break;
+ case DINO_TOC_ADDR:
+ /* IO_COMMAND of CPU with client_id bits */
+ s->toc_addr = 0xFFFA0030 | (val & 0x1e000);
+ break;
case DINO_ILR:
case DINO_IRR0:
@@ -299,6 +357,12 @@ static MemTxResult dino_chip_write_with_attrs(void *opaque, hwaddr addr,
/* These registers are read-only. */
break;
+ case DINO_GMASK ... DINO_TLTIM:
+ i = (addr - DINO_GMASK) / 4;
+ val &= reg800_keep_bits[i];
+ s->reg800[i] = val;
+ break;
+
default:
/* Controlled by dino_chip_mem_valid above. */
g_assert_not_reached();
@@ -323,7 +387,7 @@ static const MemoryRegionOps dino_chip_ops = {
static const VMStateDescription vmstate_dino = {
.name = "Dino",
- .version_id = 1,
+ .version_id = 2,
.minimum_version_id = 1,
.fields = (VMStateField[]) {
VMSTATE_UINT32(iar0, DinoState),
@@ -332,13 +396,14 @@ static const VMStateDescription vmstate_dino = {
VMSTATE_UINT32(ipr, DinoState),
VMSTATE_UINT32(icr, DinoState),
VMSTATE_UINT32(ilr, DinoState),
+ VMSTATE_UINT32(io_fbb_en, DinoState),
VMSTATE_UINT32(io_addr_en, DinoState),
VMSTATE_UINT32(io_control, DinoState),
+ VMSTATE_UINT32(toc_addr, DinoState),
VMSTATE_END_OF_LIST()
}
};
-
/* Unlike pci_config_data_le_ops, no check of high bit set in config_reg. */
static uint64_t dino_config_data_read(void *opaque, hwaddr addr, unsigned len)
@@ -362,14 +427,16 @@ static const MemoryRegionOps dino_config_data_ops = {
static uint64_t dino_config_addr_read(void *opaque, hwaddr addr, unsigned len)
{
- PCIHostState *s = opaque;
- return s->config_reg;
+ DinoState *s = opaque;
+ return s->config_reg_dino;
}
static void dino_config_addr_write(void *opaque, hwaddr addr,
uint64_t val, unsigned len)
{
PCIHostState *s = opaque;
+ DinoState *ds = opaque;
+ ds->config_reg_dino = val; /* keep a copy of original value */
s->config_reg = val & ~3U;
}
@@ -453,6 +520,8 @@ PCIBus *dino_init(MemoryRegion *addr_space,
dev = qdev_create(NULL, TYPE_DINO_PCI_HOST_BRIDGE);
s = DINO_PCI_HOST_BRIDGE(dev);
+ s->iar0 = s->iar1 = CPU_HPA + 3;
+ s->toc_addr = 0xFFFA0030; /* IO_COMMAND of CPU */
/* Dino PCI access from main memory. */
memory_region_init_io(&s->this_mem, OBJECT(s), &dino_chip_ops,
diff --git a/hw/hppa/hppa_hardware.h b/hw/hppa/hppa_hardware.h
index 507f91e..4a2fe2d 100644
--- a/hw/hppa/hppa_hardware.h
+++ b/hw/hppa/hppa_hardware.h
@@ -22,6 +22,7 @@
#define LASI_PS2KBD_HPA 0xffd08000
#define LASI_PS2MOU_HPA 0xffd08100
#define LASI_GFX_HPA 0xf8000000
+#define ARTIST_FB_ADDR 0xf9000000
#define CPU_HPA 0xfffb0000
#define MEMORY_HPA 0xfffbf000
diff --git a/hw/hppa/hppa_sys.h b/hw/hppa/hppa_sys.h
index 4e50196..4d08501 100644
--- a/hw/hppa/hppa_sys.h
+++ b/hw/hppa/hppa_sys.h
@@ -12,6 +12,8 @@
#include "hppa_hardware.h"
PCIBus *dino_init(MemoryRegion *, qemu_irq *, qemu_irq *);
+DeviceState *lasi_init(MemoryRegion *);
+#define enable_lasi_lan() 0
#define TYPE_DINO_PCI_HOST_BRIDGE "dino-pcihost"
diff --git a/hw/hppa/lasi.c b/hw/hppa/lasi.c
new file mode 100644
index 0000000..d8d03f9
--- /dev/null
+++ b/hw/hppa/lasi.c
@@ -0,0 +1,368 @@
+/*
+ * HP-PARISC Lasi chipset emulation.
+ *
+ * (C) 2019 by Helge Deller <deller@gmx.de>
+ *
+ * This work is licensed under the GNU GPL license version 2 or later.
+ *
+ * Documentation available at:
+ * https://parisc.wiki.kernel.org/images-parisc/7/79/Lasi_ers.pdf
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/units.h"
+#include "qapi/error.h"
+#include "cpu.h"
+#include "trace.h"
+#include "hw/hw.h"
+#include "hw/irq.h"
+#include "sysemu/sysemu.h"
+#include "sysemu/runstate.h"
+#include "hppa_sys.h"
+#include "hw/net/lasi_82596.h"
+#include "hw/char/parallel.h"
+#include "hw/char/serial.h"
+#include "hw/input/lasips2.h"
+#include "exec/address-spaces.h"
+#include "migration/vmstate.h"
+
+#define TYPE_LASI_CHIP "lasi-chip"
+
+#define LASI_IRR 0x00 /* RO */
+#define LASI_IMR 0x04
+#define LASI_IPR 0x08
+#define LASI_ICR 0x0c
+#define LASI_IAR 0x10
+
+#define LASI_PCR 0x0C000 /* LASI Power Control register */
+#define LASI_ERRLOG 0x0C004 /* LASI Error Logging register */
+#define LASI_VER 0x0C008 /* LASI Version Control register */
+#define LASI_IORESET 0x0C00C /* LASI I/O Reset register */
+#define LASI_AMR 0x0C010 /* LASI Arbitration Mask register */
+#define LASI_IO_CONF 0x7FFFE /* LASI primary configuration register */
+#define LASI_IO_CONF2 0x7FFFF /* LASI secondary configuration register */
+
+#define LASI_BIT(x) (1ul << (x))
+#define LASI_IRQ_BITS (LASI_BIT(5) | LASI_BIT(7) | LASI_BIT(8) | LASI_BIT(9) \
+ | LASI_BIT(13) | LASI_BIT(14) | LASI_BIT(16) | LASI_BIT(17) \
+ | LASI_BIT(18) | LASI_BIT(19) | LASI_BIT(20) | LASI_BIT(21) \
+ | LASI_BIT(26))
+
+#define ICR_BUS_ERROR_BIT LASI_BIT(8) /* bit 8 in ICR */
+#define ICR_TOC_BIT LASI_BIT(1) /* bit 1 in ICR */
+
+#define LASI_CHIP(obj) \
+ OBJECT_CHECK(LasiState, (obj), TYPE_LASI_CHIP)
+
+#define LASI_RTC_HPA (LASI_HPA + 0x9000)
+
+typedef struct LasiState {
+ PCIHostState parent_obj;
+
+ uint32_t irr;
+ uint32_t imr;
+ uint32_t ipr;
+ uint32_t icr;
+ uint32_t iar;
+
+ uint32_t errlog;
+ uint32_t amr;
+ uint32_t rtc;
+ time_t rtc_ref;
+
+ MemoryRegion this_mem;
+} LasiState;
+
+static bool lasi_chip_mem_valid(void *opaque, hwaddr addr,
+ unsigned size, bool is_write,
+ MemTxAttrs attrs)
+{
+ bool ret = false;
+
+ switch (addr) {
+ case LASI_IRR:
+ case LASI_IMR:
+ case LASI_IPR:
+ case LASI_ICR:
+ case LASI_IAR:
+
+ case (LASI_LAN_HPA - LASI_HPA):
+ case (LASI_LPT_HPA - LASI_HPA):
+ case (LASI_UART_HPA - LASI_HPA):
+ case (LASI_RTC_HPA - LASI_HPA):
+
+ case LASI_PCR ... LASI_AMR:
+ ret = true;
+ }
+
+ trace_lasi_chip_mem_valid(addr, ret);
+ return ret;
+}
+
+static MemTxResult lasi_chip_read_with_attrs(void *opaque, hwaddr addr,
+ uint64_t *data, unsigned size,
+ MemTxAttrs attrs)
+{
+ LasiState *s = opaque;
+ MemTxResult ret = MEMTX_OK;
+ uint32_t val;
+
+ switch (addr) {
+ case LASI_IRR:
+ val = s->irr;
+ break;
+ case LASI_IMR:
+ val = s->imr;
+ break;
+ case LASI_IPR:
+ val = s->ipr;
+ /* Any read to IPR clears the register. */
+ s->ipr = 0;
+ break;
+ case LASI_ICR:
+ val = s->icr & ICR_BUS_ERROR_BIT; /* bus_error */
+ break;
+ case LASI_IAR:
+ val = s->iar;
+ break;
+
+ case (LASI_LAN_HPA - LASI_HPA):
+ case (LASI_LPT_HPA - LASI_HPA):
+ case (LASI_UART_HPA - LASI_HPA):
+ val = 0;
+ break;
+ case (LASI_RTC_HPA - LASI_HPA):
+ val = time(NULL);
+ val += s->rtc_ref;
+ break;
+
+ case LASI_PCR:
+ case LASI_VER: /* only version 0 existed. */
+ case LASI_IORESET:
+ val = 0;
+ break;
+ case LASI_ERRLOG:
+ val = s->errlog;
+ break;
+ case LASI_AMR:
+ val = s->amr;
+ break;
+
+ default:
+ /* Controlled by lasi_chip_mem_valid above. */
+ g_assert_not_reached();
+ }
+
+ trace_lasi_chip_read(addr, val);
+
+ *data = val;
+ return ret;
+}
+
+static MemTxResult lasi_chip_write_with_attrs(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size,
+ MemTxAttrs attrs)
+{
+ LasiState *s = opaque;
+
+ trace_lasi_chip_write(addr, val);
+
+ switch (addr) {
+ case LASI_IRR:
+ /* read-only. */
+ break;
+ case LASI_IMR:
+ s->imr = val; /* 0x20 ?? */
+ assert((val & LASI_IRQ_BITS) == val);
+ break;
+ case LASI_IPR:
+ /* Any write to IPR clears the register. */
+ s->ipr = 0;
+ break;
+ case LASI_ICR:
+ s->icr = val;
+ /* if (val & ICR_TOC_BIT) issue_toc(); */
+ break;
+ case LASI_IAR:
+ s->iar = val;
+ break;
+
+ case (LASI_LAN_HPA - LASI_HPA):
+ /* XXX: reset LAN card */
+ break;
+ case (LASI_LPT_HPA - LASI_HPA):
+ /* XXX: reset parallel port */
+ break;
+ case (LASI_UART_HPA - LASI_HPA):
+ /* XXX: reset serial port */
+ break;
+ case (LASI_RTC_HPA - LASI_HPA):
+ s->rtc_ref = val - time(NULL);
+ break;
+
+ case LASI_PCR:
+ if (val == 0x02) /* immediately power off */
+ qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN);
+ break;
+ case LASI_ERRLOG:
+ s->errlog = val;
+ break;
+ case LASI_VER:
+ /* read-only. */
+ break;
+ case LASI_IORESET:
+ break; /* XXX: TODO: Reset various devices. */
+ case LASI_AMR:
+ s->amr = val;
+ break;
+
+ default:
+ /* Controlled by lasi_chip_mem_valid above. */
+ g_assert_not_reached();
+ }
+ return MEMTX_OK;
+}
+
+static const MemoryRegionOps lasi_chip_ops = {
+ .read_with_attrs = lasi_chip_read_with_attrs,
+ .write_with_attrs = lasi_chip_write_with_attrs,
+ .endianness = DEVICE_BIG_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ .accepts = lasi_chip_mem_valid,
+ },
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ },
+};
+
+static const VMStateDescription vmstate_lasi = {
+ .name = "Lasi",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(irr, LasiState),
+ VMSTATE_UINT32(imr, LasiState),
+ VMSTATE_UINT32(ipr, LasiState),
+ VMSTATE_UINT32(icr, LasiState),
+ VMSTATE_UINT32(iar, LasiState),
+ VMSTATE_UINT32(errlog, LasiState),
+ VMSTATE_UINT32(amr, LasiState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+
+static void lasi_set_irq(void *opaque, int irq, int level)
+{
+ LasiState *s = opaque;
+ uint32_t bit = 1u << irq;
+
+ if (level) {
+ s->ipr |= bit;
+ if (bit & s->imr) {
+ uint32_t iar = s->iar;
+ s->irr |= bit;
+ if ((s->icr & ICR_BUS_ERROR_BIT) == 0) {
+ stl_be_phys(&address_space_memory, iar & -32, iar & 31);
+ }
+ }
+ }
+}
+
+static int lasi_get_irq(unsigned long hpa)
+{
+ switch (hpa) {
+ case LASI_HPA:
+ return 14;
+ case LASI_UART_HPA:
+ return 5;
+ case LASI_LPT_HPA:
+ return 7;
+ case LASI_LAN_HPA:
+ return 8;
+ case LASI_SCSI_HPA:
+ return 9;
+ case LASI_AUDIO_HPA:
+ return 13;
+ case LASI_PS2KBD_HPA:
+ case LASI_PS2MOU_HPA:
+ return 26;
+ default:
+ g_assert_not_reached();
+ }
+}
+
+DeviceState *lasi_init(MemoryRegion *address_space)
+{
+ DeviceState *dev;
+ LasiState *s;
+
+ dev = qdev_create(NULL, TYPE_LASI_CHIP);
+ s = LASI_CHIP(dev);
+ s->iar = CPU_HPA + 3;
+
+ /* Lasi access from main memory. */
+ memory_region_init_io(&s->this_mem, OBJECT(s), &lasi_chip_ops,
+ s, "lasi", 0x100000);
+ memory_region_add_subregion(address_space, LASI_HPA, &s->this_mem);
+
+ qdev_init_nofail(dev);
+
+ /* LAN */
+ if (enable_lasi_lan()) {
+ qemu_irq lan_irq = qemu_allocate_irq(lasi_set_irq, s,
+ lasi_get_irq(LASI_LAN_HPA));
+ lasi_82596_init(address_space, LASI_LAN_HPA, lan_irq);
+ }
+
+ /* Parallel port */
+ qemu_irq lpt_irq = qemu_allocate_irq(lasi_set_irq, s,
+ lasi_get_irq(LASI_LPT_HPA));
+ parallel_mm_init(address_space, LASI_LPT_HPA + 0x800, 0,
+ lpt_irq, parallel_hds[0]);
+
+ /* Real time clock (RTC), it's only one 32-bit counter @9000 */
+
+ s->rtc = time(NULL);
+ s->rtc_ref = 0;
+
+ if (serial_hd(1)) {
+ /* Serial port */
+ qemu_irq serial_irq = qemu_allocate_irq(lasi_set_irq, s,
+ lasi_get_irq(LASI_UART_HPA));
+ serial_mm_init(address_space, LASI_UART_HPA + 0x800, 0,
+ serial_irq, 8000000 / 16,
+ serial_hd(0), DEVICE_NATIVE_ENDIAN);
+ }
+
+ /* PS/2 Keyboard/Mouse */
+ qemu_irq ps2kbd_irq = qemu_allocate_irq(lasi_set_irq, s,
+ lasi_get_irq(LASI_PS2KBD_HPA));
+ lasips2_init(address_space, LASI_PS2KBD_HPA, ps2kbd_irq);
+
+ return dev;
+}
+
+static void lasi_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->vmsd = &vmstate_lasi;
+}
+
+static const TypeInfo lasi_pcihost_info = {
+ .name = TYPE_LASI_CHIP,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(LasiState),
+ .class_init = lasi_class_init,
+};
+
+static void lasi_register_types(void)
+{
+ type_register_static(&lasi_pcihost_info);
+}
+
+type_init(lasi_register_types)
diff --git a/hw/hppa/machine.c b/hw/hppa/machine.c
index 5d0de26..2d62a24 100644
--- a/hw/hppa/machine.c
+++ b/hw/hppa/machine.c
@@ -16,6 +16,7 @@
#include "hw/ide.h"
#include "hw/timer/i8254.h"
#include "hw/char/serial.h"
+#include "hw/net/lasi_82596.h"
#include "hppa_sys.h"
#include "qemu/units.h"
#include "qapi/error.h"
@@ -74,6 +75,7 @@ static void machine_hppa_init(MachineState *machine)
MemoryRegion *cpu_region;
long i;
unsigned int smp_cpus = machine->smp.cpus;
+ SysBusDevice *s;
ram_size = machine->ram_size;
@@ -90,16 +92,18 @@ static void machine_hppa_init(MachineState *machine)
g_free(name);
}
- /* Limit main memory. */
- if (ram_size > FIRMWARE_START) {
- machine->ram_size = ram_size = FIRMWARE_START;
- }
-
/* Main memory region. */
+ if (machine->ram_size > 3 * GiB) {
+ error_report("RAM size is currently restricted to 3GB");
+ exit(EXIT_FAILURE);
+ }
ram_region = g_new(MemoryRegion, 1);
memory_region_allocate_system_memory(ram_region, OBJECT(machine),
"ram", ram_size);
- memory_region_add_subregion(addr_space, 0, ram_region);
+ memory_region_add_subregion_overlap(addr_space, 0, ram_region, -1);
+
+ /* Init Lasi chip */
+ lasi_init(addr_space);
/* Init Dino (PCI host bus chip). */
pci_bus = dino_init(addr_space, &rtc_irq, &serial_irq);
@@ -123,9 +127,20 @@ static void machine_hppa_init(MachineState *machine)
dev = DEVICE(pci_create_simple(pci_bus, -1, "lsi53c895a"));
lsi53c8xx_handle_legacy_cmdline(dev);
- /* Network setup. e1000 is good enough, failing Tulip support. */
+ /* Graphics setup. */
+ if (machine->enable_graphics && vga_interface_type != VGA_NONE) {
+ dev = qdev_create(NULL, "artist");
+ qdev_init_nofail(dev);
+ s = SYS_BUS_DEVICE(dev);
+ sysbus_mmio_map(s, 0, LASI_GFX_HPA);
+ sysbus_mmio_map(s, 1, ARTIST_FB_ADDR);
+ }
+
+ /* Network setup. */
for (i = 0; i < nb_nics; i++) {
- pci_nic_init_nofail(&nd_table[i], pci_bus, "e1000", NULL);
+ if (!enable_lasi_lan()) {
+ pci_nic_init_nofail(&nd_table[i], pci_bus, "tulip", NULL);
+ }
}
/* Load firmware. Given that this is not "real" firmware,
@@ -155,7 +170,7 @@ static void machine_hppa_init(MachineState *machine)
qemu_log_mask(CPU_LOG_PAGE, "Firmware loaded at 0x%08" PRIx64
"-0x%08" PRIx64 ", entry at 0x%08" PRIx64 ".\n",
firmware_low, firmware_high, firmware_entry);
- if (firmware_low < ram_size || firmware_high >= FIRMWARE_END) {
+ if (firmware_low < FIRMWARE_START || firmware_high >= FIRMWARE_END) {
error_report("Firmware overlaps with memory or IO space");
exit(1);
}
diff --git a/hw/hppa/trace-events b/hw/hppa/trace-events
index 4e2acb6..3ff6203 100644
--- a/hw/hppa/trace-events
+++ b/hw/hppa/trace-events
@@ -2,3 +2,13 @@
# pci.c
hppa_pci_iack_write(void) ""
+
+# dino.c
+dino_chip_mem_valid(uint64_t addr, uint32_t val) "access to addr 0x%"PRIx64" is %d"
+dino_chip_read(uint64_t addr, uint32_t val) "addr 0x%"PRIx64" val 0x%08x"
+dino_chip_write(uint64_t addr, uint32_t val) "addr 0x%"PRIx64" val 0x%08x"
+
+# lasi.c
+lasi_chip_mem_valid(uint64_t addr, uint32_t val) "access to addr 0x%"PRIx64" is %d"
+lasi_chip_read(uint64_t addr, uint32_t val) "addr 0x%"PRIx64" val 0x%08x"
+lasi_chip_write(uint64_t addr, uint32_t val) "addr 0x%"PRIx64" val 0x%08x"
diff --git a/hw/input/Kconfig b/hw/input/Kconfig
index 287f088..25c77a1 100644
--- a/hw/input/Kconfig
+++ b/hw/input/Kconfig
@@ -41,3 +41,6 @@ config VHOST_USER_INPUT
config TSC210X
bool
+
+config LASIPS2
+ select PS2
diff --git a/hw/input/Makefile.objs b/hw/input/Makefile.objs
index a1bc502..f98f635 100644
--- a/hw/input/Makefile.objs
+++ b/hw/input/Makefile.objs
@@ -15,3 +15,4 @@ common-obj-$(CONFIG_VHOST_USER_INPUT) += vhost-user-input.o
obj-$(CONFIG_MILKYMIST) += milkymist-softusb.o
obj-$(CONFIG_PXA2XX) += pxa2xx_keypad.o
obj-$(CONFIG_TSC210X) += tsc210x.o
+obj-$(CONFIG_LASIPS2) += lasips2.o
diff --git a/hw/input/lasips2.c b/hw/input/lasips2.c
new file mode 100644
index 0000000..0786e57
--- /dev/null
+++ b/hw/input/lasips2.c
@@ -0,0 +1,291 @@
+/*
+ * QEMU HP Lasi PS/2 interface emulation
+ *
+ * Copyright (c) 2019 Sven Schnelle
+ *
+ * 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/log.h"
+#include "hw/qdev-properties.h"
+#include "hw/hw.h"
+#include "hw/input/ps2.h"
+#include "hw/input/lasips2.h"
+#include "hw/sysbus.h"
+#include "exec/hwaddr.h"
+#include "sysemu/sysemu.h"
+#include "trace.h"
+#include "exec/address-spaces.h"
+#include "migration/vmstate.h"
+#include "hw/irq.h"
+
+
+struct LASIPS2State;
+typedef struct LASIPS2Port {
+ struct LASIPS2State *parent;
+ MemoryRegion reg;
+ void *dev;
+ uint8_t id;
+ uint8_t control;
+ uint8_t buf;
+ bool loopback_rbne;
+ bool irq;
+} LASIPS2Port;
+
+typedef struct LASIPS2State {
+ LASIPS2Port kbd;
+ LASIPS2Port mouse;
+ qemu_irq irq;
+} LASIPS2State;
+
+static const VMStateDescription vmstate_lasips2 = {
+ .name = "lasips2",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(kbd.control, LASIPS2State),
+ VMSTATE_UINT8(kbd.id, LASIPS2State),
+ VMSTATE_BOOL(kbd.irq, LASIPS2State),
+ VMSTATE_UINT8(mouse.control, LASIPS2State),
+ VMSTATE_UINT8(mouse.id, LASIPS2State),
+ VMSTATE_BOOL(mouse.irq, LASIPS2State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+typedef enum {
+ REG_PS2_ID = 0,
+ REG_PS2_RCVDATA = 4,
+ REG_PS2_CONTROL = 8,
+ REG_PS2_STATUS = 12,
+} lasips2_read_reg_t;
+
+typedef enum {
+ REG_PS2_RESET = 0,
+ REG_PS2_XMTDATA = 4,
+} lasips2_write_reg_t;
+
+typedef enum {
+ LASIPS2_CONTROL_ENABLE = 0x01,
+ LASIPS2_CONTROL_LOOPBACK = 0x02,
+ LASIPS2_CONTROL_DIAG = 0x20,
+ LASIPS2_CONTROL_DATDIR = 0x40,
+ LASIPS2_CONTROL_CLKDIR = 0x80,
+} lasips2_control_reg_t;
+
+typedef enum {
+ LASIPS2_STATUS_RBNE = 0x01,
+ LASIPS2_STATUS_TBNE = 0x02,
+ LASIPS2_STATUS_TERR = 0x04,
+ LASIPS2_STATUS_PERR = 0x08,
+ LASIPS2_STATUS_CMPINTR = 0x10,
+ LASIPS2_STATUS_DATSHD = 0x40,
+ LASIPS2_STATUS_CLKSHD = 0x80,
+} lasips2_status_reg_t;
+
+static const char *artist_read_reg_name(uint64_t addr)
+{
+ switch (addr & 0xc) {
+ case REG_PS2_ID:
+ return " PS2_ID";
+
+ case REG_PS2_RCVDATA:
+ return " PS2_RCVDATA";
+
+ case REG_PS2_CONTROL:
+ return " PS2_CONTROL";
+
+ case REG_PS2_STATUS:
+ return " PS2_STATUS";
+
+ default:
+ return "";
+ }
+}
+
+static const char *artist_write_reg_name(uint64_t addr)
+{
+ switch (addr & 0x0c) {
+ case REG_PS2_RESET:
+ return " PS2_RESET";
+
+ case REG_PS2_XMTDATA:
+ return " PS2_XMTDATA";
+
+ case REG_PS2_CONTROL:
+ return " PS2_CONTROL";
+
+ default:
+ return "";
+ }
+}
+
+static void lasips2_update_irq(LASIPS2State *s)
+{
+ trace_lasips2_intr(s->kbd.irq | s->mouse.irq);
+ qemu_set_irq(s->irq, s->kbd.irq | s->mouse.irq);
+}
+
+static void lasips2_reg_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ LASIPS2Port *port = opaque;
+
+ trace_lasips2_reg_write(size, port->id, addr,
+ artist_write_reg_name(addr), val);
+
+ switch (addr & 0xc) {
+ case REG_PS2_CONTROL:
+ port->control = val;
+ break;
+
+ case REG_PS2_XMTDATA:
+ if (port->control & LASIPS2_CONTROL_LOOPBACK) {
+ port->buf = val;
+ port->irq = true;
+ port->loopback_rbne = true;
+ lasips2_update_irq(port->parent);
+ break;
+ }
+
+ if (port->id) {
+ ps2_write_mouse(port->dev, val);
+ } else {
+ ps2_write_keyboard(port->dev, val);
+ }
+ break;
+
+ case REG_PS2_RESET:
+ break;
+
+ default:
+ qemu_log_mask(LOG_UNIMP, "%s: unknown register 0x%02" HWADDR_PRIx "\n",
+ __func__, addr);
+ break;
+ }
+}
+
+static uint64_t lasips2_reg_read(void *opaque, hwaddr addr, unsigned size)
+{
+ LASIPS2Port *port = opaque;
+ uint64_t ret = 0;
+
+ switch (addr & 0xc) {
+ case REG_PS2_ID:
+ ret = port->id;
+ break;
+
+ case REG_PS2_RCVDATA:
+ if (port->control & LASIPS2_CONTROL_LOOPBACK) {
+ port->irq = false;
+ port->loopback_rbne = false;
+ lasips2_update_irq(port->parent);
+ ret = port->buf;
+ break;
+ }
+
+ ret = ps2_read_data(port->dev);
+ break;
+
+ case REG_PS2_CONTROL:
+ ret = port->control;
+ break;
+
+ case REG_PS2_STATUS:
+
+ ret = LASIPS2_STATUS_DATSHD | LASIPS2_STATUS_CLKSHD;
+
+ if (port->control & LASIPS2_CONTROL_DIAG) {
+ if (!(port->control & LASIPS2_CONTROL_DATDIR)) {
+ ret &= ~LASIPS2_STATUS_DATSHD;
+ }
+
+ if (!(port->control & LASIPS2_CONTROL_CLKDIR)) {
+ ret &= ~LASIPS2_STATUS_CLKSHD;
+ }
+ }
+
+ if (port->control & LASIPS2_CONTROL_LOOPBACK) {
+ if (port->loopback_rbne) {
+ ret |= LASIPS2_STATUS_RBNE;
+ }
+ } else {
+ if (!ps2_queue_empty(port->dev)) {
+ ret |= LASIPS2_STATUS_RBNE;
+ }
+ }
+
+ if (port->parent->kbd.irq || port->parent->mouse.irq) {
+ ret |= LASIPS2_STATUS_CMPINTR;
+ }
+ break;
+
+ default:
+ qemu_log_mask(LOG_UNIMP, "%s: unknown register 0x%02" HWADDR_PRIx "\n",
+ __func__, addr);
+ break;
+ }
+ trace_lasips2_reg_read(size, port->id, addr,
+ artist_read_reg_name(addr), ret);
+
+ return ret;
+}
+
+static const MemoryRegionOps lasips2_reg_ops = {
+ .read = lasips2_reg_read,
+ .write = lasips2_reg_write,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void ps2dev_update_irq(void *opaque, int level)
+{
+ LASIPS2Port *port = opaque;
+ port->irq = level;
+ lasips2_update_irq(port->parent);
+}
+
+void lasips2_init(MemoryRegion *address_space,
+ hwaddr base, qemu_irq irq)
+{
+ LASIPS2State *s;
+
+ s = g_malloc0(sizeof(LASIPS2State));
+
+ s->irq = irq;
+ s->mouse.id = 1;
+ s->kbd.parent = s;
+ s->mouse.parent = s;
+
+ vmstate_register(NULL, base, &vmstate_lasips2, s);
+
+ s->kbd.dev = ps2_kbd_init(ps2dev_update_irq, &s->kbd);
+ s->mouse.dev = ps2_mouse_init(ps2dev_update_irq, &s->mouse);
+
+ memory_region_init_io(&s->kbd.reg, NULL, &lasips2_reg_ops, &s->kbd,
+ "lasips2-kbd", 0x100);
+ memory_region_add_subregion(address_space, base, &s->kbd.reg);
+
+ memory_region_init_io(&s->mouse.reg, NULL, &lasips2_reg_ops, &s->mouse,
+ "lasips2-mouse", 0x100);
+ memory_region_add_subregion(address_space, base + 0x100, &s->mouse.reg);
+}
diff --git a/hw/input/ps2.c b/hw/input/ps2.c
index 67f92f6..f8746d2 100644
--- a/hw/input/ps2.c
+++ b/hw/input/ps2.c
@@ -49,6 +49,8 @@
#define KBD_CMD_RESET_DISABLE 0xF5 /* reset and disable scanning */
#define KBD_CMD_RESET_ENABLE 0xF6 /* reset and enable scanning */
#define KBD_CMD_RESET 0xFF /* Reset */
+#define KBD_CMD_SET_MAKE_BREAK 0xFC /* Set Make and Break mode */
+#define KBD_CMD_SET_TYPEMATIC 0xFA /* Set Typematic Make and Break mode */
/* Keyboard Replies */
#define KBD_REPLY_POR 0xAA /* Power on reset */
@@ -190,6 +192,11 @@ static void ps2_reset_queue(PS2State *s)
q->count = 0;
}
+int ps2_queue_empty(PS2State *s)
+{
+ return s->queue.count == 0;
+}
+
void ps2_queue_noirq(PS2State *s, int b)
{
PS2Queue *q = &s->queue;
@@ -573,6 +580,7 @@ void ps2_write_keyboard(void *opaque, int val)
case KBD_CMD_SCANCODE:
case KBD_CMD_SET_LEDS:
case KBD_CMD_SET_RATE:
+ case KBD_CMD_SET_MAKE_BREAK:
s->common.write_cmd = val;
ps2_queue(&s->common, KBD_REPLY_ACK);
break;
@@ -592,11 +600,18 @@ void ps2_write_keyboard(void *opaque, int val)
KBD_REPLY_ACK,
KBD_REPLY_POR);
break;
+ case KBD_CMD_SET_TYPEMATIC:
+ ps2_queue(&s->common, KBD_REPLY_ACK);
+ break;
default:
ps2_queue(&s->common, KBD_REPLY_RESEND);
break;
}
break;
+ case KBD_CMD_SET_MAKE_BREAK:
+ ps2_queue(&s->common, KBD_REPLY_ACK);
+ s->common.write_cmd = -1;
+ break;
case KBD_CMD_SCANCODE:
if (val == 0) {
if (s->common.queue.count <= PS2_QUEUE_SIZE - 2) {
diff --git a/hw/input/trace-events b/hw/input/trace-events
index cf072fa..a2888fd 100644
--- a/hw/input/trace-events
+++ b/hw/input/trace-events
@@ -53,3 +53,8 @@ tsc2005_sense(const char *state) "touchscreen sense %s"
# virtio-input.c
virtio_input_queue_full(void) "queue full"
+
+# lasips2.c
+lasips2_reg_read(unsigned int size, int id, uint64_t addr, const char *name, uint64_t val) "%u %d addr 0x%"PRIx64 "%s -> 0x%"PRIx64
+lasips2_reg_write(unsigned int size, int id, uint64_t addr, const char *name, uint64_t val) "%u %d addr 0x%"PRIx64 "%s <- 0x%"PRIx64
+lasips2_intr(unsigned int val) "%d"
diff --git a/hw/net/Kconfig b/hw/net/Kconfig
index af6a11b..54411d3 100644
--- a/hw/net/Kconfig
+++ b/hw/net/Kconfig
@@ -31,6 +31,9 @@ config TULIP
depends on PCI
select NMC93XX_EEPROM
+config I82596_COMMON
+ bool
+
config E1000_PCI
bool
default y if PCI_DEVICES
@@ -89,6 +92,10 @@ config LANCE
bool
select PCNET_COMMON
+config LASI_82596
+ bool
+ select I82596_COMMON
+
config SUNHME
bool
diff --git a/hw/net/Makefile.objs b/hw/net/Makefile.objs
index 7907d2c..19f13e9 100644
--- a/hw/net/Makefile.objs
+++ b/hw/net/Makefile.objs
@@ -28,6 +28,8 @@ common-obj-$(CONFIG_IMX_FEC) += imx_fec.o
common-obj-$(CONFIG_CADENCE) += cadence_gem.o
common-obj-$(CONFIG_STELLARIS_ENET) += stellaris_enet.o
common-obj-$(CONFIG_LANCE) += lance.o
+common-obj-$(CONFIG_LASI_82596) += lasi_i82596.o
+common-obj-$(CONFIG_I82596_COMMON) += i82596.o
common-obj-$(CONFIG_SUNHME) += sunhme.o
common-obj-$(CONFIG_FTGMAC100) += ftgmac100.o
common-obj-$(CONFIG_SUNGEM) += sungem.o
diff --git a/hw/net/i82596.c b/hw/net/i82596.c
new file mode 100644
index 0000000..3a0e1ec
--- /dev/null
+++ b/hw/net/i82596.c
@@ -0,0 +1,734 @@
+/*
+ * QEMU Intel i82596 (Apricot) emulation
+ *
+ * Copyright (c) 2019 Helge Deller <deller@gmx.de>
+ * This work is licensed under the GNU GPL license version 2 or later.
+ *
+ * This software was written to be compatible with the specification:
+ * https://www.intel.com/assets/pdf/general/82596ca.pdf
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/timer.h"
+#include "net/net.h"
+#include "net/eth.h"
+#include "sysemu/sysemu.h"
+#include "hw/irq.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "qemu/module.h"
+#include "trace.h"
+#include "i82596.h"
+#include <zlib.h> /* For crc32 */
+
+#if defined(ENABLE_DEBUG)
+#define DBG(x) x
+#else
+#define DBG(x) do { } while (0)
+#endif
+
+#define USE_TIMER 0
+
+#define BITS(n, m) (((0xffffffffU << (31 - n)) >> (31 - n + m)) << m)
+
+#define PKT_BUF_SZ 1536
+#define MAX_MC_CNT 64
+
+#define ISCP_BUSY 0x0001
+
+#define I596_NULL ((uint32_t)0xffffffff)
+
+#define SCB_STATUS_CX 0x8000 /* CU finished command with I bit */
+#define SCB_STATUS_FR 0x4000 /* RU finished receiving a frame */
+#define SCB_STATUS_CNA 0x2000 /* CU left active state */
+#define SCB_STATUS_RNR 0x1000 /* RU left active state */
+
+#define CU_IDLE 0
+#define CU_SUSPENDED 1
+#define CU_ACTIVE 2
+
+#define RX_IDLE 0
+#define RX_SUSPENDED 1
+#define RX_READY 4
+
+#define CMD_EOL 0x8000 /* The last command of the list, stop. */
+#define CMD_SUSP 0x4000 /* Suspend after doing cmd. */
+#define CMD_INTR 0x2000 /* Interrupt after doing cmd. */
+
+#define CMD_FLEX 0x0008 /* Enable flexible memory model */
+
+enum commands {
+ CmdNOp = 0, CmdSASetup = 1, CmdConfigure = 2, CmdMulticastList = 3,
+ CmdTx = 4, CmdTDR = 5, CmdDump = 6, CmdDiagnose = 7
+};
+
+#define STAT_C 0x8000 /* Set to 0 after execution */
+#define STAT_B 0x4000 /* Command being executed */
+#define STAT_OK 0x2000 /* Command executed ok */
+#define STAT_A 0x1000 /* Command aborted */
+
+#define I596_EOF 0x8000
+#define SIZE_MASK 0x3fff
+
+#define ETHER_TYPE_LEN 2
+#define VLAN_TCI_LEN 2
+#define VLAN_HLEN (ETHER_TYPE_LEN + VLAN_TCI_LEN)
+
+/* various flags in the chip config registers */
+#define I596_PREFETCH (s->config[0] & 0x80)
+#define I596_PROMISC (s->config[8] & 0x01)
+#define I596_BC_DISABLE (s->config[8] & 0x02) /* broadcast disable */
+#define I596_NOCRC_INS (s->config[8] & 0x08)
+#define I596_CRCINM (s->config[11] & 0x04) /* CRC appended */
+#define I596_MC_ALL (s->config[11] & 0x20)
+#define I596_MULTIIA (s->config[13] & 0x40)
+
+
+static uint8_t get_byte(uint32_t addr)
+{
+ return ldub_phys(&address_space_memory, addr);
+}
+
+static void set_byte(uint32_t addr, uint8_t c)
+{
+ return stb_phys(&address_space_memory, addr, c);
+}
+
+static uint16_t get_uint16(uint32_t addr)
+{
+ return lduw_be_phys(&address_space_memory, addr);
+}
+
+static void set_uint16(uint32_t addr, uint16_t w)
+{
+ return stw_be_phys(&address_space_memory, addr, w);
+}
+
+static uint32_t get_uint32(uint32_t addr)
+{
+ uint32_t lo = lduw_be_phys(&address_space_memory, addr);
+ uint32_t hi = lduw_be_phys(&address_space_memory, addr + 2);
+ return (hi << 16) | lo;
+}
+
+static void set_uint32(uint32_t addr, uint32_t val)
+{
+ set_uint16(addr, (uint16_t) val);
+ set_uint16(addr + 2, val >> 16);
+}
+
+
+struct qemu_ether_header {
+ uint8_t ether_dhost[6];
+ uint8_t ether_shost[6];
+ uint16_t ether_type;
+};
+
+#define PRINT_PKTHDR(txt, BUF) do { \
+ struct qemu_ether_header *hdr = (void *)(BUF); \
+ printf(txt ": packet dhost=" MAC_FMT ", shost=" MAC_FMT ", type=0x%04x\n",\
+ MAC_ARG(hdr->ether_dhost), MAC_ARG(hdr->ether_shost), \
+ be16_to_cpu(hdr->ether_type)); \
+} while (0)
+
+static void i82596_transmit(I82596State *s, uint32_t addr)
+{
+ uint32_t tdb_p; /* Transmit Buffer Descriptor */
+
+ /* TODO: Check flexible mode */
+ tdb_p = get_uint32(addr + 8);
+ while (tdb_p != I596_NULL) {
+ uint16_t size, len;
+ uint32_t tba;
+
+ size = get_uint16(tdb_p);
+ len = size & SIZE_MASK;
+ tba = get_uint32(tdb_p + 8);
+ trace_i82596_transmit(len, tba);
+
+ if (s->nic && len) {
+ assert(len <= sizeof(s->tx_buffer));
+ address_space_rw(&address_space_memory, tba,
+ MEMTXATTRS_UNSPECIFIED, s->tx_buffer, len, 0);
+ DBG(PRINT_PKTHDR("Send", &s->tx_buffer));
+ DBG(printf("Sending %d bytes\n", len));
+ qemu_send_packet(qemu_get_queue(s->nic), s->tx_buffer, len);
+ }
+
+ /* was this the last package? */
+ if (size & I596_EOF) {
+ break;
+ }
+
+ /* get next buffer pointer */
+ tdb_p = get_uint32(tdb_p + 4);
+ }
+}
+
+static void set_individual_address(I82596State *s, uint32_t addr)
+{
+ NetClientState *nc;
+ uint8_t *m;
+
+ nc = qemu_get_queue(s->nic);
+ m = s->conf.macaddr.a;
+ address_space_rw(&address_space_memory, addr + 8,
+ MEMTXATTRS_UNSPECIFIED, m, ETH_ALEN, 0);
+ qemu_format_nic_info_str(nc, m);
+ trace_i82596_new_mac(nc->info_str);
+}
+
+static void set_multicast_list(I82596State *s, uint32_t addr)
+{
+ uint16_t mc_count, i;
+
+ memset(&s->mult[0], 0, sizeof(s->mult));
+ mc_count = get_uint16(addr + 8) / ETH_ALEN;
+ addr += 10;
+ if (mc_count > MAX_MC_CNT) {
+ mc_count = MAX_MC_CNT;
+ }
+ for (i = 0; i < mc_count; i++) {
+ uint8_t multicast_addr[ETH_ALEN];
+ address_space_rw(&address_space_memory,
+ addr + i * ETH_ALEN, MEMTXATTRS_UNSPECIFIED,
+ multicast_addr, ETH_ALEN, 0);
+ DBG(printf("Add multicast entry " MAC_FMT "\n",
+ MAC_ARG(multicast_addr)));
+ unsigned mcast_idx = (net_crc32(multicast_addr, ETH_ALEN) &
+ BITS(7, 2)) >> 2;
+ assert(mcast_idx < 8 * sizeof(s->mult));
+ s->mult[mcast_idx >> 3] |= (1 << (mcast_idx & 7));
+ }
+ trace_i82596_set_multicast(mc_count);
+}
+
+void i82596_set_link_status(NetClientState *nc)
+{
+ I82596State *d = qemu_get_nic_opaque(nc);
+
+ d->lnkst = nc->link_down ? 0 : 0x8000;
+}
+
+static void update_scb_status(I82596State *s)
+{
+ s->scb_status = (s->scb_status & 0xf000)
+ | (s->cu_status << 8) | (s->rx_status << 4);
+ set_uint16(s->scb, s->scb_status);
+}
+
+
+static void i82596_s_reset(I82596State *s)
+{
+ trace_i82596_s_reset(s);
+ s->scp = 0;
+ s->scb_status = 0;
+ s->cu_status = CU_IDLE;
+ s->rx_status = RX_SUSPENDED;
+ s->cmd_p = I596_NULL;
+ s->lnkst = 0x8000; /* initial link state: up */
+ s->ca = s->ca_active = 0;
+ s->send_irq = 0;
+}
+
+
+static void command_loop(I82596State *s)
+{
+ uint16_t cmd;
+ uint16_t status;
+ uint8_t byte_cnt;
+
+ DBG(printf("STARTING COMMAND LOOP cmd_p=%08x\n", s->cmd_p));
+
+ while (s->cmd_p != I596_NULL) {
+ /* set status */
+ status = STAT_B;
+ set_uint16(s->cmd_p, status);
+ status = STAT_C | STAT_OK; /* update, but write later */
+
+ cmd = get_uint16(s->cmd_p + 2);
+ DBG(printf("Running command %04x at %08x\n", cmd, s->cmd_p));
+
+ switch (cmd & 0x07) {
+ case CmdNOp:
+ break;
+ case CmdSASetup:
+ set_individual_address(s, s->cmd_p);
+ break;
+ case CmdConfigure:
+ byte_cnt = get_byte(s->cmd_p + 8) & 0x0f;
+ byte_cnt = MAX(byte_cnt, 4);
+ byte_cnt = MIN(byte_cnt, sizeof(s->config));
+ /* copy byte_cnt max. */
+ address_space_rw(&address_space_memory, s->cmd_p + 8,
+ MEMTXATTRS_UNSPECIFIED, s->config, byte_cnt, 0);
+ /* config byte according to page 35ff */
+ s->config[2] &= 0x82; /* mask valid bits */
+ s->config[2] |= 0x40;
+ s->config[7] &= 0xf7; /* clear zero bit */
+ assert(I596_NOCRC_INS == 0); /* do CRC insertion */
+ s->config[10] = MAX(s->config[10], 5); /* min frame length */
+ s->config[12] &= 0x40; /* only full duplex field valid */
+ s->config[13] |= 0x3f; /* set ones in byte 13 */
+ break;
+ case CmdTDR:
+ /* get signal LINK */
+ set_uint32(s->cmd_p + 8, s->lnkst);
+ break;
+ case CmdTx:
+ i82596_transmit(s, s->cmd_p);
+ break;
+ case CmdMulticastList:
+ set_multicast_list(s, s->cmd_p);
+ break;
+ case CmdDump:
+ case CmdDiagnose:
+ printf("FIXME Command %d !!\n", cmd & 7);
+ assert(0);
+ }
+
+ /* update status */
+ set_uint16(s->cmd_p, status);
+
+ s->cmd_p = get_uint32(s->cmd_p + 4); /* get link address */
+ DBG(printf("NEXT addr would be %08x\n", s->cmd_p));
+ if (s->cmd_p == 0) {
+ s->cmd_p = I596_NULL;
+ }
+
+ /* Stop when last command of the list. */
+ if (cmd & CMD_EOL) {
+ s->cmd_p = I596_NULL;
+ }
+ /* Suspend after doing cmd? */
+ if (cmd & CMD_SUSP) {
+ s->cu_status = CU_SUSPENDED;
+ printf("FIXME SUSPEND !!\n");
+ }
+ /* Interrupt after doing cmd? */
+ if (cmd & CMD_INTR) {
+ s->scb_status |= SCB_STATUS_CX;
+ } else {
+ s->scb_status &= ~SCB_STATUS_CX;
+ }
+ update_scb_status(s);
+
+ /* Interrupt after doing cmd? */
+ if (cmd & CMD_INTR) {
+ s->send_irq = 1;
+ }
+
+ if (s->cu_status != CU_ACTIVE) {
+ break;
+ }
+ }
+ DBG(printf("FINISHED COMMAND LOOP\n"));
+ qemu_flush_queued_packets(qemu_get_queue(s->nic));
+}
+
+static void i82596_flush_queue_timer(void *opaque)
+{
+ I82596State *s = opaque;
+ if (0) {
+ timer_del(s->flush_queue_timer);
+ qemu_flush_queued_packets(qemu_get_queue(s->nic));
+ timer_mod(s->flush_queue_timer,
+ qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + 1000);
+ }
+}
+
+static void examine_scb(I82596State *s)
+{
+ uint16_t command, cuc, ruc;
+
+ /* get the scb command word */
+ command = get_uint16(s->scb + 2);
+ cuc = (command >> 8) & 0x7;
+ ruc = (command >> 4) & 0x7;
+ DBG(printf("MAIN COMMAND %04x cuc %02x ruc %02x\n", command, cuc, ruc));
+ /* and clear the scb command word */
+ set_uint16(s->scb + 2, 0);
+
+ if (command & BIT(31)) /* ACK-CX */
+ s->scb_status &= ~SCB_STATUS_CX;
+ if (command & BIT(30)) /*ACK-FR */
+ s->scb_status &= ~SCB_STATUS_FR;
+ if (command & BIT(29)) /*ACK-CNA */
+ s->scb_status &= ~SCB_STATUS_CNA;
+ if (command & BIT(28)) /*ACK-RNR */
+ s->scb_status &= ~SCB_STATUS_RNR;
+
+ switch (cuc) {
+ case 0: /* no change */
+ break;
+ case 1: /* CUC_START */
+ s->cu_status = CU_ACTIVE;
+ break;
+ case 4: /* CUC_ABORT */
+ s->cu_status = CU_SUSPENDED;
+ s->scb_status |= SCB_STATUS_CNA; /* CU left active state */
+ break;
+ default:
+ printf("WARNING: Unknown CUC %d!\n", cuc);
+ }
+
+ switch (ruc) {
+ case 0: /* no change */
+ break;
+ case 1: /* RX_START */
+ case 2: /* RX_RESUME */
+ s->rx_status = RX_IDLE;
+ if (USE_TIMER) {
+ timer_mod(s->flush_queue_timer, qemu_clock_get_ms(
+ QEMU_CLOCK_VIRTUAL) + 1000);
+ }
+ break;
+ case 3: /* RX_SUSPEND */
+ case 4: /* RX_ABORT */
+ s->rx_status = RX_SUSPENDED;
+ s->scb_status |= SCB_STATUS_RNR; /* RU left active state */
+ break;
+ default:
+ printf("WARNING: Unknown RUC %d!\n", ruc);
+ }
+
+ if (command & 0x80) { /* reset bit set? */
+ i82596_s_reset(s);
+ }
+
+ /* execute commands from SCBL */
+ if (s->cu_status != CU_SUSPENDED) {
+ if (s->cmd_p == I596_NULL) {
+ s->cmd_p = get_uint32(s->scb + 4);
+ }
+ }
+
+ /* update scb status */
+ update_scb_status(s);
+
+ command_loop(s);
+}
+
+static void signal_ca(I82596State *s)
+{
+ uint32_t iscp = 0;
+
+ /* trace_i82596_channel_attention(s); */
+ if (s->scp) {
+ /* CA after reset -> do init with new scp. */
+ s->sysbus = get_byte(s->scp + 3); /* big endian */
+ DBG(printf("SYSBUS = %08x\n", s->sysbus));
+ if (((s->sysbus >> 1) & 0x03) != 2) {
+ printf("WARNING: NO LINEAR MODE !!\n");
+ }
+ if ((s->sysbus >> 7)) {
+ printf("WARNING: 32BIT LINMODE IN B-STEPPING NOT SUPPORTED !!\n");
+ }
+ iscp = get_uint32(s->scp + 8);
+ s->scb = get_uint32(iscp + 4);
+ set_byte(iscp + 1, 0); /* clear BUSY flag in iscp */
+ s->scp = 0;
+ }
+
+ s->ca++; /* count ca() */
+ if (!s->ca_active) {
+ s->ca_active = 1;
+ while (s->ca) {
+ examine_scb(s);
+ s->ca--;
+ }
+ s->ca_active = 0;
+ }
+
+ if (s->send_irq) {
+ s->send_irq = 0;
+ qemu_set_irq(s->irq, 1);
+ }
+}
+
+void i82596_ioport_writew(void *opaque, uint32_t addr, uint32_t val)
+{
+ I82596State *s = opaque;
+ /* printf("i82596_ioport_writew addr=0x%08x val=0x%04x\n", addr, val); */
+ switch (addr) {
+ case PORT_RESET: /* Reset */
+ i82596_s_reset(s);
+ break;
+ case PORT_ALTSCP:
+ s->scp = val;
+ break;
+ case PORT_CA:
+ signal_ca(s);
+ break;
+ }
+}
+
+uint32_t i82596_ioport_readw(void *opaque, uint32_t addr)
+{
+ return -1;
+}
+
+void i82596_h_reset(void *opaque)
+{
+ I82596State *s = opaque;
+
+ i82596_s_reset(s);
+}
+
+int i82596_can_receive(NetClientState *nc)
+{
+ I82596State *s = qemu_get_nic_opaque(nc);
+
+ if (s->rx_status == RX_SUSPENDED) {
+ return 0;
+ }
+
+ if (!s->lnkst) {
+ return 0;
+ }
+
+ if (USE_TIMER && !timer_pending(s->flush_queue_timer)) {
+ return 1;
+ }
+
+ return 1;
+}
+
+#define MIN_BUF_SIZE 60
+
+ssize_t i82596_receive(NetClientState *nc, const uint8_t *buf, size_t sz)
+{
+ I82596State *s = qemu_get_nic_opaque(nc);
+ uint32_t rfd_p;
+ uint32_t rbd;
+ uint16_t is_broadcast = 0;
+ size_t len = sz;
+ uint32_t crc;
+ uint8_t *crc_ptr;
+ uint8_t buf1[MIN_BUF_SIZE + VLAN_HLEN];
+ static const uint8_t broadcast_macaddr[6] = {
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
+
+ DBG(printf("i82596_receive() start\n"));
+
+ if (USE_TIMER && timer_pending(s->flush_queue_timer)) {
+ return 0;
+ }
+
+ /* first check if receiver is enabled */
+ if (s->rx_status == RX_SUSPENDED) {
+ trace_i82596_receive_analysis(">>> Receiving suspended");
+ return -1;
+ }
+
+ if (!s->lnkst) {
+ trace_i82596_receive_analysis(">>> Link down");
+ return -1;
+ }
+
+ /* Received frame smaller than configured "min frame len"? */
+ if (sz < s->config[10]) {
+ printf("Received frame too small, %zu vs. %u bytes\n",
+ sz, s->config[10]);
+ return -1;
+ }
+
+ DBG(printf("Received %lu bytes\n", sz));
+
+ if (I596_PROMISC) {
+
+ /* promiscuous: receive all */
+ trace_i82596_receive_analysis(
+ ">>> packet received in promiscuous mode");
+
+ } else {
+
+ if (!memcmp(buf, broadcast_macaddr, 6)) {
+ /* broadcast address */
+ if (I596_BC_DISABLE) {
+ trace_i82596_receive_analysis(">>> broadcast packet rejected");
+
+ return len;
+ }
+
+ trace_i82596_receive_analysis(">>> broadcast packet received");
+ is_broadcast = 1;
+
+ } else if (buf[0] & 0x01) {
+ /* multicast */
+ if (!I596_MC_ALL) {
+ trace_i82596_receive_analysis(">>> multicast packet rejected");
+
+ return len;
+ }
+
+ int mcast_idx = (net_crc32(buf, ETH_ALEN) & BITS(7, 2)) >> 2;
+ assert(mcast_idx < 8 * sizeof(s->mult));
+
+ if (!(s->mult[mcast_idx >> 3] & (1 << (mcast_idx & 7)))) {
+ trace_i82596_receive_analysis(">>> multicast address mismatch");
+
+ return len;
+ }
+
+ trace_i82596_receive_analysis(">>> multicast packet received");
+ is_broadcast = 1;
+
+ } else if (!memcmp(s->conf.macaddr.a, buf, 6)) {
+
+ /* match */
+ trace_i82596_receive_analysis(
+ ">>> physical address matching packet received");
+
+ } else {
+
+ trace_i82596_receive_analysis(">>> unknown packet");
+
+ return len;
+ }
+ }
+
+ /* if too small buffer, then expand it */
+ if (len < MIN_BUF_SIZE + VLAN_HLEN) {
+ memcpy(buf1, buf, len);
+ memset(buf1 + len, 0, MIN_BUF_SIZE + VLAN_HLEN - len);
+ buf = buf1;
+ if (len < MIN_BUF_SIZE) {
+ len = MIN_BUF_SIZE;
+ }
+ }
+
+ /* Calculate the ethernet checksum (4 bytes) */
+ len += 4;
+ crc = cpu_to_be32(crc32(~0, buf, sz));
+ crc_ptr = (uint8_t *) &crc;
+
+ rfd_p = get_uint32(s->scb + 8); /* get Receive Frame Descriptor */
+ assert(rfd_p && rfd_p != I596_NULL);
+
+ /* get first Receive Buffer Descriptor Address */
+ rbd = get_uint32(rfd_p + 8);
+ assert(rbd && rbd != I596_NULL);
+
+ trace_i82596_receive_packet(len);
+ /* PRINT_PKTHDR("Receive", buf); */
+
+ while (len) {
+ uint16_t command, status;
+ uint32_t next_rfd;
+
+ command = get_uint16(rfd_p + 2);
+ assert(command & CMD_FLEX); /* assert Flex Mode */
+ /* get first Receive Buffer Descriptor Address */
+ rbd = get_uint32(rfd_p + 8);
+ assert(get_uint16(rfd_p + 14) == 0);
+
+ /* printf("Receive: rfd is %08x\n", rfd_p); */
+
+ while (len) {
+ uint16_t buffer_size, num;
+ uint32_t rba;
+
+ /* printf("Receive: rbd is %08x\n", rbd); */
+ buffer_size = get_uint16(rbd + 12);
+ /* printf("buffer_size is 0x%x\n", buffer_size); */
+ assert(buffer_size != 0);
+
+ num = buffer_size & SIZE_MASK;
+ if (num > len) {
+ num = len;
+ }
+ rba = get_uint32(rbd + 8);
+ /* printf("rba is 0x%x\n", rba); */
+ address_space_rw(&address_space_memory, rba,
+ MEMTXATTRS_UNSPECIFIED, (void *)buf, num, 1);
+ rba += num;
+ buf += num;
+ len -= num;
+ if (len == 0) { /* copy crc */
+ address_space_rw(&address_space_memory, rba - 4,
+ MEMTXATTRS_UNSPECIFIED, crc_ptr, 4, 1);
+ }
+
+ num |= 0x4000; /* set F BIT */
+ if (len == 0) {
+ num |= I596_EOF; /* set EOF BIT */
+ }
+ set_uint16(rbd + 0, num); /* write actual count with flags */
+
+ /* get next rbd */
+ rbd = get_uint32(rbd + 4);
+ /* printf("Next Receive: rbd is %08x\n", rbd); */
+
+ if (buffer_size & I596_EOF) /* last entry */
+ break;
+ }
+
+ /* Housekeeping, see pg. 18 */
+ next_rfd = get_uint32(rfd_p + 4);
+ set_uint32(next_rfd + 8, rbd);
+
+ status = STAT_C | STAT_OK | is_broadcast;
+ set_uint16(rfd_p, status);
+
+ if (command & CMD_SUSP) { /* suspend after command? */
+ s->rx_status = RX_SUSPENDED;
+ s->scb_status |= SCB_STATUS_RNR; /* RU left active state */
+ break;
+ }
+ if (command & CMD_EOL) /* was it last Frame Descriptor? */
+ break;
+
+ assert(len == 0);
+ }
+
+ assert(len == 0);
+
+ s->scb_status |= SCB_STATUS_FR; /* set "RU finished receiving frame" bit. */
+ update_scb_status(s);
+
+ /* send IRQ that we received data */
+ qemu_set_irq(s->irq, 1);
+ /* s->send_irq = 1; */
+
+ if (0) {
+ DBG(printf("Checking:\n"));
+ rfd_p = get_uint32(s->scb + 8); /* get Receive Frame Descriptor */
+ DBG(printf("Next Receive: rfd is %08x\n", rfd_p));
+ rfd_p = get_uint32(rfd_p + 4); /* get Next Receive Frame Descriptor */
+ DBG(printf("Next Receive: rfd is %08x\n", rfd_p));
+ /* get first Receive Buffer Descriptor Address */
+ rbd = get_uint32(rfd_p + 8);
+ DBG(printf("Next Receive: rbd is %08x\n", rbd));
+ }
+
+ return sz;
+}
+
+
+const VMStateDescription vmstate_i82596 = {
+ .name = "i82596",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT16(lnkst, I82596State),
+ VMSTATE_TIMER_PTR(flush_queue_timer, I82596State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+void i82596_common_init(DeviceState *dev, I82596State *s, NetClientInfo *info)
+{
+ if (s->conf.macaddr.a[0] == 0) {
+ qemu_macaddr_default_if_unset(&s->conf.macaddr);
+ }
+ s->nic = qemu_new_nic(info, &s->conf, object_get_typename(OBJECT(dev)),
+ dev->id, s);
+ qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a);
+
+ if (USE_TIMER) {
+ s->flush_queue_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
+ i82596_flush_queue_timer, s);
+ }
+ s->lnkst = 0x8000; /* initial link state: up */
+}
diff --git a/hw/net/i82596.h b/hw/net/i82596.h
new file mode 100644
index 0000000..1238ac1
--- /dev/null
+++ b/hw/net/i82596.h
@@ -0,0 +1,55 @@
+#ifndef HW_I82596_H
+#define HW_I82596_H
+
+#define I82596_IOPORT_SIZE 0x20
+
+#include "exec/memory.h"
+#include "exec/address-spaces.h"
+
+#define PORT_RESET 0x00 /* reset 82596 */
+#define PORT_SELFTEST 0x01 /* selftest */
+#define PORT_ALTSCP 0x02 /* alternate SCB address */
+#define PORT_ALTDUMP 0x03 /* Alternate DUMP address */
+#define PORT_CA 0x10 /* QEMU-internal CA signal */
+
+typedef struct I82596State_st I82596State;
+
+struct I82596State_st {
+ MemoryRegion mmio;
+ MemoryRegion *as;
+ qemu_irq irq;
+ NICState *nic;
+ NICConf conf;
+ QEMUTimer *flush_queue_timer;
+
+ hwaddr scp; /* pointer to SCP */
+ uint8_t sysbus;
+ uint32_t scb; /* SCB */
+ uint16_t scb_status;
+ uint8_t cu_status, rx_status;
+ uint16_t lnkst;
+
+ uint32_t cmd_p; /* addr of current command */
+ int ca;
+ int ca_active;
+ int send_irq;
+
+ /* Hash register (multicast mask array, multiple individual addresses). */
+ uint8_t mult[8];
+ uint8_t config[14]; /* config bytes from CONFIGURE command */
+
+ uint8_t tx_buffer[0x4000];
+};
+
+void i82596_h_reset(void *opaque);
+void i82596_ioport_writew(void *opaque, uint32_t addr, uint32_t val);
+uint32_t i82596_ioport_readw(void *opaque, uint32_t addr);
+void i82596_ioport_writel(void *opaque, uint32_t addr, uint32_t val);
+uint32_t i82596_ioport_readl(void *opaque, uint32_t addr);
+uint32_t i82596_bcr_readw(I82596State *s, uint32_t rap);
+ssize_t i82596_receive(NetClientState *nc, const uint8_t *buf, size_t size_);
+int i82596_can_receive(NetClientState *nc);
+void i82596_set_link_status(NetClientState *nc);
+void i82596_common_init(DeviceState *dev, I82596State *s, NetClientInfo *info);
+extern const VMStateDescription vmstate_i82596;
+#endif
diff --git a/hw/net/lasi_i82596.c b/hw/net/lasi_i82596.c
new file mode 100644
index 0000000..427b3fb
--- /dev/null
+++ b/hw/net/lasi_i82596.c
@@ -0,0 +1,188 @@
+/*
+ * QEMU LASI NIC i82596 emulation
+ *
+ * Copyright (c) 2019 Helge Deller <deller@gmx.de>
+ * This work is licensed under the GNU GPL license version 2 or later.
+ *
+ *
+ * On PA-RISC, this is the Network part of LASI chip.
+ * See:
+ * https://parisc.wiki.kernel.org/images-parisc/7/79/Lasi_ers.pdf
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/timer.h"
+#include "hw/sysbus.h"
+#include "net/eth.h"
+#include "hw/net/lasi_82596.h"
+#include "hw/net/i82596.h"
+#include "trace.h"
+#include "sysemu/sysemu.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+
+#define PA_I82596_RESET 0 /* Offsets relative to LASI-LAN-Addr.*/
+#define PA_CPU_PORT_L_ACCESS 4
+#define PA_CHANNEL_ATTENTION 8
+#define PA_GET_MACADDR 12
+
+#define SWAP32(x) (((uint32_t)(x) << 16) | ((((uint32_t)(x))) >> 16))
+
+static void lasi_82596_mem_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ SysBusI82596State *d = opaque;
+
+ trace_lasi_82596_mem_writew(addr, val);
+ switch (addr) {
+ case PA_I82596_RESET:
+ i82596_h_reset(&d->state);
+ break;
+ case PA_CPU_PORT_L_ACCESS:
+ d->val_index++;
+ if (d->val_index == 0) {
+ uint32_t v = d->last_val | (val << 16);
+ v = v & ~0xff;
+ i82596_ioport_writew(&d->state, d->last_val & 0xff, v);
+ }
+ d->last_val = val;
+ break;
+ case PA_CHANNEL_ATTENTION:
+ i82596_ioport_writew(&d->state, PORT_CA, val);
+ break;
+ case PA_GET_MACADDR:
+ /*
+ * Provided for SeaBIOS only. Write MAC of Network card to addr @val.
+ * Needed for the PDC_LAN_STATION_ID_READ PDC call.
+ */
+ address_space_rw(&address_space_memory, val,
+ MEMTXATTRS_UNSPECIFIED, d->state.conf.macaddr.a, ETH_ALEN, 1);
+ break;
+ }
+}
+
+static uint64_t lasi_82596_mem_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ SysBusI82596State *d = opaque;
+ uint32_t val;
+
+ if (addr == PA_GET_MACADDR) {
+ val = 0xBEEFBABE;
+ } else {
+ val = i82596_ioport_readw(&d->state, addr);
+ }
+ trace_lasi_82596_mem_readw(addr, val);
+ return val;
+}
+
+static const MemoryRegionOps lasi_82596_mem_ops = {
+ .read = lasi_82596_mem_read,
+ .write = lasi_82596_mem_write,
+ .endianness = DEVICE_BIG_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static NetClientInfo net_lasi_82596_info = {
+ .type = NET_CLIENT_DRIVER_NIC,
+ .size = sizeof(NICState),
+ .can_receive = i82596_can_receive,
+ .receive = i82596_receive,
+ .link_status_changed = i82596_set_link_status,
+};
+
+static const VMStateDescription vmstate_lasi_82596 = {
+ .name = "i82596",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(state, SysBusI82596State, 0, vmstate_i82596,
+ I82596State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void lasi_82596_realize(DeviceState *dev, Error **errp)
+{
+ SysBusI82596State *d = SYSBUS_I82596(dev);
+ I82596State *s = &d->state;
+
+ memory_region_init_io(&s->mmio, OBJECT(d), &lasi_82596_mem_ops, d,
+ "lasi_82596-mmio", PA_GET_MACADDR + 4);
+
+ i82596_common_init(dev, s, &net_lasi_82596_info);
+}
+
+SysBusI82596State *lasi_82596_init(MemoryRegion *addr_space,
+ hwaddr hpa, qemu_irq lan_irq)
+{
+ DeviceState *dev;
+ SysBusI82596State *s;
+ static const MACAddr HP_MAC = {
+ .a = { 0x08, 0x00, 0x09, 0xef, 0x34, 0xf6 } };
+
+ qemu_check_nic_model(&nd_table[0], TYPE_LASI_82596);
+ dev = qdev_create(NULL, TYPE_LASI_82596);
+ s = SYSBUS_I82596(dev);
+ s->state.irq = lan_irq;
+ qdev_set_nic_properties(dev, &nd_table[0]);
+ qdev_init_nofail(dev);
+ s->state.conf.macaddr = HP_MAC; /* set HP MAC prefix */
+
+ /* LASI 82596 ports in main memory. */
+ memory_region_add_subregion(addr_space, hpa, &s->state.mmio);
+ return s;
+}
+
+static void lasi_82596_reset(DeviceState *dev)
+{
+ SysBusI82596State *d = SYSBUS_I82596(dev);
+
+ i82596_h_reset(&d->state);
+}
+
+static void lasi_82596_instance_init(Object *obj)
+{
+ SysBusI82596State *d = SYSBUS_I82596(obj);
+ I82596State *s = &d->state;
+
+ device_add_bootindex_property(obj, &s->conf.bootindex,
+ "bootindex", "/ethernet-phy@0",
+ DEVICE(obj), NULL);
+}
+
+static Property lasi_82596_properties[] = {
+ DEFINE_NIC_PROPERTIES(SysBusI82596State, state.conf),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void lasi_82596_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = lasi_82596_realize;
+ set_bit(DEVICE_CATEGORY_NETWORK, dc->categories);
+ dc->fw_name = "ethernet";
+ dc->reset = lasi_82596_reset;
+ dc->vmsd = &vmstate_lasi_82596;
+ dc->user_creatable = false;
+ device_class_set_props(dc, lasi_82596_properties);
+}
+
+static const TypeInfo lasi_82596_info = {
+ .name = TYPE_LASI_82596,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(SysBusI82596State),
+ .class_init = lasi_82596_class_init,
+ .instance_init = lasi_82596_instance_init,
+};
+
+static void lasi_82596_register_types(void)
+{
+ type_register_static(&lasi_82596_info);
+}
+
+type_init(lasi_82596_register_types)
diff --git a/hw/net/trace-events b/hw/net/trace-events
index e70f12b..42066fc 100644
--- a/hw/net/trace-events
+++ b/hw/net/trace-events
@@ -381,3 +381,16 @@ tulip_mii_read(int phy, int reg, uint16_t data) "phy 0x%x, reg 0x%x data 0x%04x"
tulip_reset(void) ""
tulip_setup_frame(void) ""
tulip_setup_filter(int n, uint8_t a, uint8_t b, uint8_t c, uint8_t d, uint8_t e, uint8_t f) "%d: %02x:%02x:%02x:%02x:%02x:%02x"
+
+# lasi_i82596.c
+lasi_82596_mem_readw(uint64_t addr, uint32_t ret) "addr=0x%"PRIx64" val=0x%04x"
+lasi_82596_mem_writew(uint64_t addr, uint32_t val) "addr=0x%"PRIx64" val=0x%04x"
+
+# i82596.c
+i82596_s_reset(void *s) "%p Reset chip"
+i82596_transmit(uint32_t size, uint32_t addr) "size %u from addr 0x%04x"
+i82596_receive_analysis(const char *s) "%s"
+i82596_receive_packet(size_t sz) "len=%zu"
+i82596_new_mac(const char *id_with_mac) "New MAC for: %s"
+i82596_set_multicast(uint16_t count) "Added %d multicast entries"
+i82596_channel_attention(void *s) "%p: Received CHANNEL ATTENTION"
diff --git a/include/hw/input/lasips2.h b/include/hw/input/lasips2.h
new file mode 100644
index 0000000..0cd7b59
--- /dev/null
+++ b/include/hw/input/lasips2.h
@@ -0,0 +1,16 @@
+/*
+ * QEMU LASI PS/2 emulation
+ *
+ * Copyright (c) 2019 Sven Schnelle
+ *
+ */
+#ifndef HW_INPUT_LASIPS2_H
+#define HW_INPUT_LASIPS2_H
+
+#include "exec/hwaddr.h"
+
+#define TYPE_LASIPS2 "lasips2"
+
+void lasips2_init(MemoryRegion *address_space, hwaddr base, qemu_irq irq);
+
+#endif /* HW_INPUT_LASIPS2_H */
diff --git a/include/hw/input/ps2.h b/include/hw/input/ps2.h
index b60455d..35d9838 100644
--- a/include/hw/input/ps2.h
+++ b/include/hw/input/ps2.h
@@ -47,5 +47,6 @@ void ps2_queue_3(PS2State *s, int b1, int b2, int b3);
void ps2_queue_4(PS2State *s, int b1, int b2, int b3, int b4);
void ps2_keyboard_set_translation(void *opaque, int mode);
void ps2_mouse_fake_event(void *opaque);
+int ps2_queue_empty(PS2State *s);
#endif /* HW_PS2_H */
diff --git a/include/hw/net/lasi_82596.h b/include/hw/net/lasi_82596.h
new file mode 100644
index 0000000..e76ef83
--- /dev/null
+++ b/include/hw/net/lasi_82596.h
@@ -0,0 +1,29 @@
+/*
+ * QEMU LASI i82596 device emulation
+ *
+ * Copyright (c) 201 Helge Deller <deller@gmx.de>
+ *
+ */
+
+#ifndef LASI_82596_H
+#define LASI_82596_H
+
+#include "net/net.h"
+#include "hw/net/i82596.h"
+
+#define TYPE_LASI_82596 "lasi_82596"
+#define SYSBUS_I82596(obj) \
+ OBJECT_CHECK(SysBusI82596State, (obj), TYPE_LASI_82596)
+
+typedef struct {
+ SysBusDevice parent_obj;
+
+ I82596State state;
+ uint16_t last_val;
+ int val_index:1;
+} SysBusI82596State;
+
+SysBusI82596State *lasi_82596_init(MemoryRegion *addr_space,
+ hwaddr hpa, qemu_irq irq);
+
+#endif
diff --git a/pc-bios/hppa-firmware.img b/pc-bios/hppa-firmware.img
index c79e1e9..82d98b1 100644
--- a/pc-bios/hppa-firmware.img
+++ b/pc-bios/hppa-firmware.img
Binary files differ
diff --git a/roms/seabios-hppa b/roms/seabios-hppa
-Subproject 0f4fe84658165e96ce35870fd19fc634e182e77
+Subproject 1630ac7d65c4a09218cc677f1fa56cd5b314044
diff --git a/target/hppa/helper.h b/target/hppa/helper.h
index 38d834e..2d483aa 100644
--- a/target/hppa/helper.h
+++ b/target/hppa/helper.h
@@ -17,6 +17,8 @@ DEF_HELPER_FLAGS_3(stby_b_parallel, TCG_CALL_NO_WG, void, env, tl, tr)
DEF_HELPER_FLAGS_3(stby_e, TCG_CALL_NO_WG, void, env, tl, tr)
DEF_HELPER_FLAGS_3(stby_e_parallel, TCG_CALL_NO_WG, void, env, tl, tr)
+DEF_HELPER_FLAGS_1(ldc_check, TCG_CALL_NO_RWG, void, tl)
+
DEF_HELPER_FLAGS_4(probe, TCG_CALL_NO_WG, tr, env, tl, i32, i32)
DEF_HELPER_FLAGS_1(loaded_fr0, TCG_CALL_NO_RWG, void, env)
diff --git a/target/hppa/op_helper.c b/target/hppa/op_helper.c
index f0516e8..7823706 100644
--- a/target/hppa/op_helper.c
+++ b/target/hppa/op_helper.c
@@ -153,6 +153,15 @@ void HELPER(stby_e_parallel)(CPUHPPAState *env, target_ulong addr,
do_stby_e(env, addr, val, true, GETPC());
}
+void HELPER(ldc_check)(target_ulong addr)
+{
+ if (unlikely(addr & 0xf)) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "Undefined ldc to unaligned address mod 16: "
+ TARGET_FMT_lx "\n", addr);
+ }
+}
+
target_ureg HELPER(probe)(CPUHPPAState *env, target_ulong addr,
uint32_t level, uint32_t want)
{
diff --git a/target/hppa/translate.c b/target/hppa/translate.c
index f25927a..52d7bea 100644
--- a/target/hppa/translate.c
+++ b/target/hppa/translate.c
@@ -2942,7 +2942,7 @@ static bool trans_st(DisasContext *ctx, arg_ldst *a)
static bool trans_ldc(DisasContext *ctx, arg_ldst *a)
{
- MemOp mop = MO_TEUL | MO_ALIGN_16 | a->size;
+ MemOp mop = MO_TE | MO_ALIGN | a->size;
TCGv_reg zero, dest, ofs;
TCGv_tl addr;
@@ -2958,8 +2958,21 @@ static bool trans_ldc(DisasContext *ctx, arg_ldst *a)
form_gva(ctx, &addr, &ofs, a->b, a->x, a->scale ? a->size : 0,
a->disp, a->sp, a->m, ctx->mmu_idx == MMU_PHYS_IDX);
+
+ /*
+ * For hppa1.1, LDCW is undefined unless aligned mod 16.
+ * However actual hardware succeeds with aligned mod 4.
+ * Detect this case and log a GUEST_ERROR.
+ *
+ * TODO: HPPA64 relaxes the over-alignment requirement
+ * with the ,co completer.
+ */
+ gen_helper_ldc_check(addr);
+
zero = tcg_const_reg(0);
tcg_gen_atomic_xchg_reg(dest, addr, zero, ctx->mmu_idx, mop);
+ tcg_temp_free(zero);
+
if (a->m) {
save_gpr(ctx, a->b, ofs);
}
diff --git a/tests/qtest/boot-serial-test.c b/tests/qtest/boot-serial-test.c
index 05c7f44..8e8c5b0 100644
--- a/tests/qtest/boot-serial-test.c
+++ b/tests/qtest/boot-serial-test.c
@@ -135,7 +135,8 @@ static testdef_t tests[] = {
sizeof(kernel_plml605), kernel_plml605 },
{ "moxie", "moxiesim", "", "TT", sizeof(bios_moxiesim), 0, bios_moxiesim },
{ "arm", "raspi2", "", "TT", sizeof(bios_raspi2), 0, bios_raspi2 },
- { "hppa", "hppa", "", "SeaBIOS wants SYSTEM HALT" },
+ /* For hppa, force bios to output to serial by disabling graphics. */
+ { "hppa", "hppa", "-vga none", "SeaBIOS wants SYSTEM HALT" },
{ "aarch64", "virt", "-cpu cortex-a57", "TT", sizeof(kernel_aarch64),
kernel_aarch64 },
{ "arm", "microbit", "", "T", sizeof(kernel_nrf51), kernel_nrf51 },