summary refs log tree commit diff stats
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 f6511d5120..efd3f3875f 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 c500d1fc6d..15d59e10dc 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 f2182e3bef..5f03dfdcc4 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 0000000000..65be9e3554
--- /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 ba7787b180..e6e22bef88 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 6e5d74a825..82178c7dcb 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 67838f50a3..eac3467d8a 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 ab6969b45f..9797a7f0d9 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 507f91e05d..4a2fe2df60 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 4e5019695e..4d08501464 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 0000000000..d8d03f95c0
--- /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 5d0de26140..2d62a248b8 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 4e2acb6176..3ff620319a 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 287f08887b..25c77a1b87 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 a1bc502ed0..f98f635685 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 0000000000..0786e57338
--- /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 67f92f6112..f8746d2f52 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 cf072fa2f8..a2888fd10c 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 af6a11baf3..54411d3dcc 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 7907d2c199..19f13e9fa5 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 0000000000..3a0e1ec4c0
--- /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 0000000000..1238ac11f8
--- /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 0000000000..427b3fbf70
--- /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 e70f12bee1..42066fcf66 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 0000000000..0cd7b59064
--- /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 b60455d4f6..35d983897a 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 0000000000..e76ef8308e
--- /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 c79e1e923c..82d98b1353 100644
--- a/pc-bios/hppa-firmware.img
+++ b/pc-bios/hppa-firmware.img
Binary files differdiff --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 38d834ef6b..2d483aab58 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 f0516e81f1..7823706e9c 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 f25927aeca..52d7bea1ea 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 05c7f44457..8e8c5b0a0f 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 },