qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v4 0/6] HPPA: i82596, PS/2 and graphics emulation
@ 2019-11-03 20:56 Sven Schnelle
  2019-11-03 20:56 ` [PATCH v4 1/6] hw/hppa/dino.c: Improve emulation of Dino PCI chip Sven Schnelle
                   ` (5 more replies)
  0 siblings, 6 replies; 14+ messages in thread
From: Sven Schnelle @ 2019-11-03 20:56 UTC (permalink / raw)
  To: Richard Henderson; +Cc: Helge Deller, Sven Schnelle, qemu-devel

Hi,

these series adds quite a lot to the HPPA emulation in QEMU:
i82596 emulation from Helge, PS/2 and Artist graphics emulation.

See https://parisc.wiki.kernel.org/index.php/Qemu for a few screenshots
of QEMU running a X11/CDE session in HP-UX.

changes in v4:
 - introduce Artist-internal address space
 - rewrite screen update functions to use the generic framebuffer routines
 - use dirty bitmap code to not always redraw the whole screen

Changes in v3:
 - use BIT() macro in gsc_to_pci_forwarding()
 - fix version id in vm state
 - fix an error in the PS/2 KBD_CMD_SET_MAKE_BREAK implementation

Changes in v2:
 - dropped 'hppa: remove ISA region' as that patch requires some more work
 - added shortlog to seabios update
 - use const and MAKE_64BIT_MASK in dino.c

Regards,
Sven

Helge Deller (2):
  hw/hppa/dino.c: Improve emulation of Dino PCI chip
  hppa: Add support for LASI chip with i82596 NIC

Sven Schnelle (4):
  ps2: accept 'Set Key Make and Break' commands
  hppa: add emulation of LASI PS2 controllers
  hppa: Add emulation of Artist graphics
  seabios-hppa: update to latest version

 MAINTAINERS                 |    4 +-
 hw/display/Kconfig          |    4 +
 hw/display/Makefile.objs    |    1 +
 hw/display/artist.c         | 1449 +++++++++++++++++++++++++++++++++++
 hw/display/trace-events     |    9 +
 hw/hppa/Kconfig             |    3 +
 hw/hppa/Makefile.objs       |    2 +-
 hw/hppa/dino.c              |   97 ++-
 hw/hppa/hppa_hardware.h     |    1 +
 hw/hppa/hppa_sys.h          |    2 +
 hw/hppa/lasi.c              |  368 +++++++++
 hw/hppa/machine.c           |   17 +-
 hw/hppa/trace-events        |   10 +
 hw/input/Kconfig            |    3 +
 hw/input/Makefile.objs      |    1 +
 hw/input/lasips2.c          |  289 +++++++
 hw/input/ps2.c              |   15 +
 hw/input/trace-events       |    5 +
 hw/net/Kconfig              |    7 +
 hw/net/Makefile.objs        |    2 +
 hw/net/i82596.c             |  734 ++++++++++++++++++
 hw/net/i82596.h             |   55 ++
 hw/net/lasi_i82596.c        |  188 +++++
 hw/net/trace-events         |   14 +
 include/hw/input/lasips2.h  |   16 +
 include/hw/input/ps2.h      |    1 +
 include/hw/net/lasi_82596.h |   29 +
 pc-bios/hppa-firmware.img   |  Bin 783724 -> 772876 bytes
 roms/seabios-hppa           |    2 +-
 29 files changed, 3310 insertions(+), 18 deletions(-)
 create mode 100644 hw/display/artist.c
 create mode 100644 hw/hppa/lasi.c
 create mode 100644 hw/input/lasips2.c
 create mode 100644 hw/net/i82596.c
 create mode 100644 hw/net/i82596.h
 create mode 100644 hw/net/lasi_i82596.c
 create mode 100644 include/hw/input/lasips2.h
 create mode 100644 include/hw/net/lasi_82596.h

-- 
2.24.0.rc2



^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH v4 1/6] hw/hppa/dino.c: Improve emulation of Dino PCI chip
  2019-11-03 20:56 [PATCH v4 0/6] HPPA: i82596, PS/2 and graphics emulation Sven Schnelle
@ 2019-11-03 20:56 ` Sven Schnelle
  2019-11-03 20:56 ` [PATCH v4 2/6] hppa: Add support for LASI chip with i82596 NIC Sven Schnelle
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 14+ messages in thread
From: Sven Schnelle @ 2019-11-03 20:56 UTC (permalink / raw)
  To: Richard Henderson
  Cc: Philippe Mathieu-Daudé, Helge Deller, Sven Schnelle, qemu-devel

From: Helge Deller <deller@gmx.de>

The tests of the dino chip with the Online-diagnostics CD
("ODE DINOTEST") now succeeds.
Additionally add some qemu trace events.

Signed-off-by: Helge Deller <deller@gmx.de>
Signed-off-by: Sven Schnelle <svens@stackframe.org>
Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
---
 MAINTAINERS          |  2 +-
 hw/hppa/dino.c       | 97 +++++++++++++++++++++++++++++++++++++-------
 hw/hppa/trace-events |  5 +++
 3 files changed, 89 insertions(+), 15 deletions(-)

diff --git a/MAINTAINERS b/MAINTAINERS
index 92961faa0e..7f8abbddab 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -874,7 +874,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/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/trace-events b/hw/hppa/trace-events
index 4e2acb6176..f943b16c4e 100644
--- a/hw/hppa/trace-events
+++ b/hw/hppa/trace-events
@@ -2,3 +2,8 @@
 
 # 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"
-- 
2.24.0.rc2



^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v4 2/6] hppa: Add support for LASI chip with i82596 NIC
  2019-11-03 20:56 [PATCH v4 0/6] HPPA: i82596, PS/2 and graphics emulation Sven Schnelle
  2019-11-03 20:56 ` [PATCH v4 1/6] hw/hppa/dino.c: Improve emulation of Dino PCI chip Sven Schnelle
@ 2019-11-03 20:56 ` Sven Schnelle
  2019-11-03 20:56 ` [PATCH v4 3/6] ps2: accept 'Set Key Make and Break' commands Sven Schnelle
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 14+ messages in thread
From: Sven Schnelle @ 2019-11-03 20:56 UTC (permalink / raw)
  To: Richard Henderson; +Cc: Jason Wang, Helge Deller, Sven Schnelle, qemu-devel

From: Helge Deller <deller@gmx.de>

LASI is a built-in multi-I/O chip which supports serial, parallel,
network (Intel i82596 Apricot), sound and other functionalities.
LASI has been used in many HP PARISC machines.
This patch adds the necessary parts to allow Linux and HP-UX to detect
LASI and the network card.

Signed-off-by: Helge Deller <deller@gmx.de>
Signed-off-by: Sven Schnelle <svens@stackframe.org>
---
 MAINTAINERS                 |   2 +
 hw/hppa/Kconfig             |   1 +
 hw/hppa/Makefile.objs       |   2 +-
 hw/hppa/hppa_sys.h          |   2 +
 hw/hppa/lasi.c              | 360 ++++++++++++++++++
 hw/hppa/machine.c           |   8 +-
 hw/hppa/trace-events        |   5 +
 hw/net/Kconfig              |   7 +
 hw/net/Makefile.objs        |   2 +
 hw/net/i82596.c             | 734 ++++++++++++++++++++++++++++++++++++
 hw/net/i82596.h             |  55 +++
 hw/net/lasi_i82596.c        | 188 +++++++++
 hw/net/trace-events         |  14 +
 include/hw/net/lasi_82596.h |  29 ++
 14 files changed, 1407 insertions(+), 2 deletions(-)
 create mode 100644 hw/hppa/lasi.c
 create mode 100644 hw/net/i82596.c
 create mode 100644 hw/net/i82596.h
 create mode 100644 hw/net/lasi_i82596.c
 create mode 100644 include/hw/net/lasi_82596.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 7f8abbddab..c4cdd761ba 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>
diff --git a/hw/hppa/Kconfig b/hw/hppa/Kconfig
index 6e5d74a825..2a7b38d6d6 100644
--- a/hw/hppa/Kconfig
+++ b/hw/hppa/Kconfig
@@ -10,3 +10,4 @@ config DINO
     select IDE_CMD646
     select MC146818RTC
     select LSI_SCSI_PCI
+    select LASI_82596
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/hppa_sys.h b/hw/hppa/hppa_sys.h
index 43d25d21fc..d99b5dd87b 100644
--- a/hw/hppa/hppa_sys.h
+++ b/hw/hppa/hppa_sys.h
@@ -11,6 +11,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..51752589f3
--- /dev/null
+++ b/hw/hppa/lasi.c
@@ -0,0 +1,360 @@
+/*
+ * 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 "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(1), DEVICE_NATIVE_ENDIAN);
+    }
+    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 b30aba6d54..967738477a 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"
@@ -100,6 +101,9 @@ static void machine_hppa_init(MachineState *machine)
                                          "ram", ram_size);
     memory_region_add_subregion(addr_space, 0, ram_region);
 
+    /* Init Lasi chip */
+    lasi_init(addr_space);
+
     /* Init Dino (PCI host bus chip).  */
     pci_bus = dino_init(addr_space, &rtc_irq, &serial_irq);
     assert(pci_bus);
@@ -124,7 +128,9 @@ static void machine_hppa_init(MachineState *machine)
 
     /* Network setup.  e1000 is good enough, failing Tulip support.  */
     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, "e1000", NULL);
+        }
     }
 
     /* Load firmware.  Given that this is not "real" firmware,
diff --git a/hw/hppa/trace-events b/hw/hppa/trace-events
index f943b16c4e..3ff620319a 100644
--- a/hw/hppa/trace-events
+++ b/hw/hppa/trace-events
@@ -7,3 +7,8 @@ hppa_pci_iack_write(void) ""
 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/net/Kconfig b/hw/net/Kconfig
index 3856417d42..c03cdfaa9d 100644
--- a/hw/net/Kconfig
+++ b/hw/net/Kconfig
@@ -29,6 +29,9 @@ config TULIP
     default y if PCI_DEVICES
     depends on PCI
 
+config I82596_COMMON
+    bool
+
 config E1000_PCI
     bool
     default y if PCI_DEVICES
@@ -87,6 +90,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..ce1cc18b93
--- /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, %lu 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..9629ba189d
--- /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->props = lasi_82596_properties;
+    dc->user_creatable = false;
+}
+
+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..922be47f71 100644
--- a/hw/net/trace-events
+++ b/hw/net/trace-events
@@ -381,3 +381,17 @@ 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/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
-- 
2.24.0.rc2



^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v4 3/6] ps2: accept 'Set Key Make and Break' commands
  2019-11-03 20:56 [PATCH v4 0/6] HPPA: i82596, PS/2 and graphics emulation Sven Schnelle
  2019-11-03 20:56 ` [PATCH v4 1/6] hw/hppa/dino.c: Improve emulation of Dino PCI chip Sven Schnelle
  2019-11-03 20:56 ` [PATCH v4 2/6] hppa: Add support for LASI chip with i82596 NIC Sven Schnelle
@ 2019-11-03 20:56 ` Sven Schnelle
  2019-11-03 20:56 ` [PATCH v4 4/6] hppa: add emulation of LASI PS2 controllers Sven Schnelle
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 14+ messages in thread
From: Sven Schnelle @ 2019-11-03 20:56 UTC (permalink / raw)
  To: Richard Henderson; +Cc: Helge Deller, Sven Schnelle, qemu-devel

HP-UX sends both the 'Set key make and break (0xfc) and
'Set all key typematic make and break' (0xfa). QEMU response
with 'Resend' as it doesn't handle these commands. HP-UX than
reports an PS/2 max retransmission exceeded error. Add these
commands and just reply with ACK.

Signed-off-by: Sven Schnelle <svens@stackframe.org>
---
 hw/input/ps2.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/hw/input/ps2.c b/hw/input/ps2.c
index 67f92f6112..0b671b6339 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 */
@@ -573,6 +575,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 +595,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) {
-- 
2.24.0.rc2



^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v4 4/6] hppa: add emulation of LASI PS2 controllers
  2019-11-03 20:56 [PATCH v4 0/6] HPPA: i82596, PS/2 and graphics emulation Sven Schnelle
                   ` (2 preceding siblings ...)
  2019-11-03 20:56 ` [PATCH v4 3/6] ps2: accept 'Set Key Make and Break' commands Sven Schnelle
@ 2019-11-03 20:56 ` Sven Schnelle
  2019-11-03 20:56 ` [PATCH v4 5/6] hppa: Add emulation of Artist graphics Sven Schnelle
  2019-11-23 13:59 ` [PATCH v4 0/6] HPPA: i82596, PS/2 and graphics emulation Sven Schnelle
  5 siblings, 0 replies; 14+ messages in thread
From: Sven Schnelle @ 2019-11-03 20:56 UTC (permalink / raw)
  To: Richard Henderson; +Cc: Helge Deller, Sven Schnelle, qemu-devel

Signed-off-by: Sven Schnelle <svens@stackframe.org>
---
 hw/hppa/Kconfig            |   1 +
 hw/hppa/lasi.c             |  10 +-
 hw/input/Kconfig           |   3 +
 hw/input/Makefile.objs     |   1 +
 hw/input/lasips2.c         | 289 +++++++++++++++++++++++++++++++++++++
 hw/input/ps2.c             |   5 +
 hw/input/trace-events      |   5 +
 include/hw/input/lasips2.h |  16 ++
 include/hw/input/ps2.h     |   1 +
 9 files changed, 330 insertions(+), 1 deletion(-)
 create mode 100644 hw/input/lasips2.c
 create mode 100644 include/hw/input/lasips2.h

diff --git a/hw/hppa/Kconfig b/hw/hppa/Kconfig
index 2a7b38d6d6..7f9be7f25c 100644
--- a/hw/hppa/Kconfig
+++ b/hw/hppa/Kconfig
@@ -11,3 +11,4 @@ config DINO
     select MC146818RTC
     select LSI_SCSI_PCI
     select LASI_82596
+    select LASIPS2
diff --git a/hw/hppa/lasi.c b/hw/hppa/lasi.c
index 51752589f3..d8d03f95c0 100644
--- a/hw/hppa/lasi.c
+++ b/hw/hppa/lasi.c
@@ -22,6 +22,7 @@
 #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"
 
@@ -324,6 +325,7 @@ DeviceState *lasi_init(MemoryRegion *address_space)
                      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;
 
@@ -333,8 +335,14 @@ DeviceState *lasi_init(MemoryRegion *address_space)
                 lasi_get_irq(LASI_UART_HPA));
         serial_mm_init(address_space, LASI_UART_HPA + 0x800, 0,
                 serial_irq, 8000000 / 16,
-                serial_hd(1), DEVICE_NATIVE_ENDIAN);
+                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;
 }
 
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..1943671d1e
--- /dev/null
+++ b/hw/input/lasips2.c
@@ -0,0 +1,289 @@
+/*
+ * 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 "";
+    }
+    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 "";
+    }
+    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("%s: unknown register 0x%02lx\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("%s: unknown register 0x%02lx\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 0b671b6339..f8746d2f52 100644
--- a/hw/input/ps2.c
+++ b/hw/input/ps2.c
@@ -192,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;
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/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 */
-- 
2.24.0.rc2



^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH v4 5/6] hppa: Add emulation of Artist graphics
  2019-11-03 20:56 [PATCH v4 0/6] HPPA: i82596, PS/2 and graphics emulation Sven Schnelle
                   ` (3 preceding siblings ...)
  2019-11-03 20:56 ` [PATCH v4 4/6] hppa: add emulation of LASI PS2 controllers Sven Schnelle
@ 2019-11-03 20:56 ` Sven Schnelle
  2019-12-19  0:28   ` Richard Henderson
  2019-11-23 13:59 ` [PATCH v4 0/6] HPPA: i82596, PS/2 and graphics emulation Sven Schnelle
  5 siblings, 1 reply; 14+ messages in thread
From: Sven Schnelle @ 2019-11-03 20:56 UTC (permalink / raw)
  To: Richard Henderson; +Cc: Helge Deller, Sven Schnelle, qemu-devel

This adds emulation of Artist graphics good enough
to get a Text console on both Linux and HP-UX. The
X11 server from HP-UX also works.

Signed-off-by: Sven Schnelle <svens@stackframe.org>
---
 hw/display/Kconfig       |    4 +
 hw/display/Makefile.objs |    1 +
 hw/display/artist.c      | 1449 ++++++++++++++++++++++++++++++++++++++
 hw/display/trace-events  |    9 +
 hw/hppa/Kconfig          |    1 +
 hw/hppa/hppa_hardware.h  |    1 +
 hw/hppa/machine.c        |    9 +
 7 files changed, 1474 insertions(+)
 create mode 100644 hw/display/artist.c

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..1d6c7d5d76
--- /dev/null
+++ b/hw/display/artist.c
@@ -0,0 +1,1449 @@
+/*
+ * 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 "";
+}
+
+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;
+    }
+
+    if (posy * width + posx > buf->size) {
+        qemu_log("write outside bounds: wants %dx%d, max size %dx%d\n",
+                posx, posy, width, height);
+        return;
+    }
+
+    p = buf->data;
+
+    if (pix_count > size * 8) {
+        pix_count = size * 8;
+    }
+
+    switch (pix_length) {
+    case 0:
+        if (s->image_bitmap_op & 0x20000000) {
+                data &= vram_bitmask;
+        }
+
+        for (i = 0; i < pix_count; i++) {
+            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=%08lx val=%08lx"
+                " 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("%s: unknown register: %08lx size %d\n", __func__, addr, size);
+    }
+    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->props = artist_properties;
+    dc->reset = artist_reset;
+}
+
+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 7f9be7f25c..82178c7dcb 100644
--- a/hw/hppa/Kconfig
+++ b/hw/hppa/Kconfig
@@ -12,3 +12,4 @@ config DINO
     select LSI_SCSI_PCI
     select LASI_82596
     select LASIPS2
+    select ARTIST
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/machine.c b/hw/hppa/machine.c
index 967738477a..9d0192c4fa 100644
--- a/hw/hppa/machine.c
+++ b/hw/hppa/machine.c
@@ -74,6 +74,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;
 
@@ -126,6 +127,14 @@ static void machine_hppa_init(MachineState *machine)
     dev = DEVICE(pci_create_simple(pci_bus, -1, "lsi53c895a"));
     lsi53c8xx_handle_legacy_cmdline(dev);
 
+    if (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.  e1000 is good enough, failing Tulip support.  */
     for (i = 0; i < nb_nics; i++) {
         if (!enable_lasi_lan()) {
-- 
2.24.0.rc2



^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: [PATCH v4 0/6] HPPA: i82596, PS/2 and graphics emulation
  2019-11-03 20:56 [PATCH v4 0/6] HPPA: i82596, PS/2 and graphics emulation Sven Schnelle
                   ` (4 preceding siblings ...)
  2019-11-03 20:56 ` [PATCH v4 5/6] hppa: Add emulation of Artist graphics Sven Schnelle
@ 2019-11-23 13:59 ` Sven Schnelle
  2019-11-25 10:01   ` Richard Henderson
  5 siblings, 1 reply; 14+ messages in thread
From: Sven Schnelle @ 2019-11-23 13:59 UTC (permalink / raw)
  To: Richard Henderson; +Cc: Helge Deller, qemu-devel

On Sun, Nov 03, 2019 at 09:56:01PM +0100, Sven Schnelle wrote:
> Hi,
> 
> these series adds quite a lot to the HPPA emulation in QEMU:
> i82596 emulation from Helge, PS/2 and Artist graphics emulation.
> [..]

Ping? :-)

Regards
Sven


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v4 0/6] HPPA: i82596, PS/2 and graphics emulation
  2019-11-23 13:59 ` [PATCH v4 0/6] HPPA: i82596, PS/2 and graphics emulation Sven Schnelle
@ 2019-11-25 10:01   ` Richard Henderson
  2019-12-13 20:40     ` Helge Deller
  0 siblings, 1 reply; 14+ messages in thread
From: Richard Henderson @ 2019-11-25 10:01 UTC (permalink / raw)
  To: Sven Schnelle, Richard Henderson; +Cc: Helge Deller, qemu-devel

On 11/23/19 1:59 PM, Sven Schnelle wrote:
> On Sun, Nov 03, 2019 at 09:56:01PM +0100, Sven Schnelle wrote:
>> Hi,
>>
>> these series adds quite a lot to the HPPA emulation in QEMU:
>> i82596 emulation from Helge, PS/2 and Artist graphics emulation.
>> [..]
> 
> Ping? :-)

Version 1 missed the 4.2 cutoff by few days.
We'll get this merged for 5.0 once development opens.


r~


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v4 0/6] HPPA: i82596, PS/2 and graphics emulation
  2019-11-25 10:01   ` Richard Henderson
@ 2019-12-13 20:40     ` Helge Deller
  2019-12-19  0:44       ` Richard Henderson
  0 siblings, 1 reply; 14+ messages in thread
From: Helge Deller @ 2019-12-13 20:40 UTC (permalink / raw)
  To: Richard Henderson, Sven Schnelle, Richard Henderson; +Cc: qemu-devel

On 25.11.19 11:01, Richard Henderson wrote:
> On 11/23/19 1:59 PM, Sven Schnelle wrote:
>> On Sun, Nov 03, 2019 at 09:56:01PM +0100, Sven Schnelle wrote:
>>> Hi,
>>>
>>> these series adds quite a lot to the HPPA emulation in QEMU:
>>> i82596 emulation from Helge, PS/2 and Artist graphics emulation.
>>> [..]
>>
>> Ping? :-)
>
> Version 1 missed the 4.2 cutoff by few days.
> We'll get this merged for 5.0 once development opens.

To avoid confusion because we missed qemu 4.2, I've deleted the
parisc-qemu-4.2 branch at the parisc-seabios git repo [1].
Instead I've created a parisc-qemu-5.0 branch.
Please use that instead if you push the prebuilt seabios rom and
git submodule references.

Helge

[1] https://github.com/hdeller/seabios-hppa/tree/parisc-qemu-5.0


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v4 5/6] hppa: Add emulation of Artist graphics
  2019-11-03 20:56 ` [PATCH v4 5/6] hppa: Add emulation of Artist graphics Sven Schnelle
@ 2019-12-19  0:28   ` Richard Henderson
  2019-12-20  7:26     ` Helge Deller
  0 siblings, 1 reply; 14+ messages in thread
From: Richard Henderson @ 2019-12-19  0:28 UTC (permalink / raw)
  To: Sven Schnelle, Richard Henderson; +Cc: Helge Deller, qemu-devel

On 11/3/19 10:56 AM, Sven Schnelle wrote:
> This adds emulation of Artist graphics good enough
> to get a Text console on both Linux and HP-UX. The
> X11 server from HP-UX also works.
> 
> Signed-off-by: Sven Schnelle <svens@stackframe.org>
> ---
>  hw/display/Kconfig       |    4 +
>  hw/display/Makefile.objs |    1 +
>  hw/display/artist.c      | 1449 ++++++++++++++++++++++++++++++++++++++
>  hw/display/trace-events  |    9 +
>  hw/hppa/Kconfig          |    1 +
>  hw/hppa/hppa_hardware.h  |    1 +
>  hw/hppa/machine.c        |    9 +
>  7 files changed, 1474 insertions(+)
>  create mode 100644 hw/display/artist.c

Seems to have some problems rebased upon master:


> Thread 6 "qemu-system-hpp" received signal SIGSEGV, Segmentation fault.
> [Switching to Thread 0x7fffee3b6700 (LWP 11752)]
> 0x00005555558bba54 in artist_rop8 (s=s@entry=0x555556105400, 
>     dst=dst@entry=0x7fffed740000 "", val=0 '\000')
>     at /home/rth/qemu/qemu/hw/display/artist.c:288
> 288	        *dst |= val & plane_mask;
> (gdb) where
> #0  0x00005555558bba54 in artist_rop8
>     (s=s@entry=0x555556105400, dst=dst@entry=0x7fffed740000 "", val=0 '\000')
>     at /home/rth/qemu/qemu/hw/display/artist.c:288
> #1  0x00005555558bc145 in vram_bit_write
>     (s=s@entry=0x555556105400, posx=<optimized out>, posy=<optimized out>, incr_x=incr_x@entry=false, size=size@entry=4, data=<optimized out>, data@entry=0)
>     at /home/rth/qemu/qemu/hw/display/artist.c:430
> #2  0x00005555558bd33b in artist_reg_write
>     (opaque=0x555556105400, addr=1050144, val=0, size=4)
>     at /home/rth/qemu/qemu/hw/display/artist.c:862
> #3  0x00005555557b271b in memory_region_write_accessor
>     (mr=mr@entry=0x5555561058f0, addr=addr@entry=1050144, value=value@entry=0x7fffee3b4f08, size=size@entry=4, shift=shift@entry=0, mask=mask@entry=4294967295, attrs=...) at /home/rth/qemu/qemu/memory.c:483
> #4  0x00005555557b03d3 in access_with_adjusted_size
>     (addr=addr@entry=1050144, value=value@entry=0x7fffee3b4f08, size=size@entry=4, access_size_min=<optimized out>, access_size_max=<optimized out>, access_fn=access_fn@entry=
>     0x5555557b25f0 <memory_region_write_accessor>, mr=0x5555561058f0, attrs=...) at /home/rth/qemu/qemu/memory.c:539
> #5  0x00005555557b4b34 in memory_region_dispatch_write
>     (mr=mr@entry=0x5555561058f0, addr=addr@entry=1050144, data=<optimized out>, data@entry=0, op=op@entry=MO_BEUL, attrs=...)
>     at /home/rth/qemu/qemu/memory.c:1475
> #6  0x00005555557c18ed in io_writex
>     (env=env@entry=0x5555563a6b60, mmu_idx=mmu_idx@entry=4, val=val@entry=0, addr=addr@entry=4161799712, retaddr=140736415114886, op=MO_BEUL, iotlbentry=<optimized out>, iotlbentry=<optimized out>)
>     at /home/rth/qemu/qemu/accel/tcg/cputlb.c:977
> #7  0x00005555557c77bc in store_helper
>     (op=MO_BEUL, retaddr=140736415114886, oi=<optimized out>, val=0, addr=4161799712, env=0x5555563a6b60) at /home/rth/qemu/qemu/accel/tcg/cputlb.c:1716
> #8  0x00005555557c77bc in helper_be_stl_mmu
>     (env=0x5555563a6b60, addr=4161799712, val=0, oi=<optimized out>, retaddr=140736415114886) at /home/rth/qemu/qemu/accel/tcg/cputlb.c:1842
> #9  0x00007fffc007a686 in code_gen_buffer ()


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v4 0/6] HPPA: i82596, PS/2 and graphics emulation
  2019-12-13 20:40     ` Helge Deller
@ 2019-12-19  0:44       ` Richard Henderson
  0 siblings, 0 replies; 14+ messages in thread
From: Richard Henderson @ 2019-12-19  0:44 UTC (permalink / raw)
  To: Helge Deller, Richard Henderson, Sven Schnelle; +Cc: qemu-devel

On 12/13/19 10:40 AM, Helge Deller wrote:
> To avoid confusion because we missed qemu 4.2, I've deleted the
> parisc-qemu-4.2 branch at the parisc-seabios git repo [1].
> Instead I've created a parisc-qemu-5.0 branch.
> Please use that instead if you push the prebuilt seabios rom and
> git submodule references.
> 
> Helge
> 
> [1] https://github.com/hdeller/seabios-hppa/tree/parisc-qemu-5.0

I've re-generated patch 6 using the head of that branch.


r~


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v4 5/6] hppa: Add emulation of Artist graphics
  2019-12-19  0:28   ` Richard Henderson
@ 2019-12-20  7:26     ` Helge Deller
  2019-12-20 16:36       ` Helge Deller
  0 siblings, 1 reply; 14+ messages in thread
From: Helge Deller @ 2019-12-20  7:26 UTC (permalink / raw)
  To: Richard Henderson, Sven Schnelle, Richard Henderson; +Cc: qemu-devel

[-- Attachment #1: Type: text/plain, Size: 3517 bytes --]

On 19.12.19 01:28, Richard Henderson wrote:
> On 11/3/19 10:56 AM, Sven Schnelle wrote:
>> This adds emulation of Artist graphics good enough
>> to get a Text console on both Linux and HP-UX. The
>> X11 server from HP-UX also works.
>>
>> Signed-off-by: Sven Schnelle <svens@stackframe.org>
>> ---
>>  hw/display/Kconfig       |    4 +
>>  hw/display/Makefile.objs |    1 +
>>  hw/display/artist.c      | 1449 ++++++++++++++++++++++++++++++++++++++
>>  hw/display/trace-events  |    9 +
>>  hw/hppa/Kconfig          |    1 +
>>  hw/hppa/hppa_hardware.h  |    1 +
>>  hw/hppa/machine.c        |    9 +
>>  7 files changed, 1474 insertions(+)
>>  create mode 100644 hw/display/artist.c
>
> Seems to have some problems rebased upon master:
>
>
>> Thread 6 "qemu-system-hpp" received signal SIGSEGV, Segmentation fault.
>> [Switching to Thread 0x7fffee3b6700 (LWP 11752)]
>> 0x00005555558bba54 in artist_rop8 (s=s@entry=0x555556105400,
>>     dst=dst@entry=0x7fffed740000 "", val=0 '\000')
>>     at /home/rth/qemu/qemu/hw/display/artist.c:288
>> 288	        *dst |= val & plane_mask;
>> (gdb) where
>> #0  0x00005555558bba54 in artist_rop8
>>     (s=s@entry=0x555556105400, dst=dst@entry=0x7fffed740000 "", val=0 '\000')
>>     at /home/rth/qemu/qemu/hw/display/artist.c:288
>> #1  0x00005555558bc145 in vram_bit_write
>>     (s=s@entry=0x555556105400, posx=<optimized out>, posy=<optimized out>, incr_x=incr_x@entry=false, size=size@entry=4, data=<optimized out>, data@entry=0)
>>     at /home/rth/qemu/qemu/hw/display/artist.c:430
>> #2  0x00005555558bd33b in artist_reg_write
>>     (opaque=0x555556105400, addr=1050144, val=0, size=4)
>>     at /home/rth/qemu/qemu/hw/display/artist.c:862
>> #3  0x00005555557b271b in memory_region_write_accessor
>>     (mr=mr@entry=0x5555561058f0, addr=addr@entry=1050144, value=value@entry=0x7fffee3b4f08, size=size@entry=4, shift=shift@entry=0, mask=mask@entry=4294967295, attrs=...) at /home/rth/qemu/qemu/memory.c:483
>> #4  0x00005555557b03d3 in access_with_adjusted_size
>>     (addr=addr@entry=1050144, value=value@entry=0x7fffee3b4f08, size=size@entry=4, access_size_min=<optimized out>, access_size_max=<optimized out>, access_fn=access_fn@entry=
>>     0x5555557b25f0 <memory_region_write_accessor>, mr=0x5555561058f0, attrs=...) at /home/rth/qemu/qemu/memory.c:539
>> #5  0x00005555557b4b34 in memory_region_dispatch_write
>>     (mr=mr@entry=0x5555561058f0, addr=addr@entry=1050144, data=<optimized out>, data@entry=0, op=op@entry=MO_BEUL, attrs=...)
>>     at /home/rth/qemu/qemu/memory.c:1475
>> #6  0x00005555557c18ed in io_writex
>>     (env=env@entry=0x5555563a6b60, mmu_idx=mmu_idx@entry=4, val=val@entry=0, addr=addr@entry=4161799712, retaddr=140736415114886, op=MO_BEUL, iotlbentry=<optimized out>, iotlbentry=<optimized out>)
>>     at /home/rth/qemu/qemu/accel/tcg/cputlb.c:977
>> #7  0x00005555557c77bc in store_helper
>>     (op=MO_BEUL, retaddr=140736415114886, oi=<optimized out>, val=0, addr=4161799712, env=0x5555563a6b60) at /home/rth/qemu/qemu/accel/tcg/cputlb.c:1716
>> #8  0x00005555557c77bc in helper_be_stl_mmu
>>     (env=0x5555563a6b60, addr=4161799712, val=0, oi=<optimized out>, retaddr=140736415114886) at /home/rth/qemu/qemu/accel/tcg/cputlb.c:1842
>> #9  0x00007fffc007a686 in code_gen_buffer ()


Richard, the attached patch (for seabios-hppa) fixes it for me.
Can you test as well?
It fixes the sti text column to go out-of-range and thus outside the framebuffer memory.

Helge

[-- Attachment #2: sti.patch --]
[-- Type: text/x-patch, Size: 358 bytes --]

diff --git a/src/parisc/sti.c b/src/parisc/sti.c
index 7935770..eca79da 100644
--- a/src/parisc/sti.c
+++ b/src/parisc/sti.c
@@ -168,5 +168,8 @@ void sti_putc(const char c)
         }
         return;
     }
-    sti_putchar(rom, row, col++, c);
+
+    sti_putchar(rom, row, col, c);
+    if (col < ((sti_glob_cfg.onscreen_x / font->width) - 1))
+	col++;
 }

^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: [PATCH v4 5/6] hppa: Add emulation of Artist graphics
  2019-12-20  7:26     ` Helge Deller
@ 2019-12-20 16:36       ` Helge Deller
  2019-12-20 17:03         ` Sven Schnelle
  0 siblings, 1 reply; 14+ messages in thread
From: Helge Deller @ 2019-12-20 16:36 UTC (permalink / raw)
  To: Richard Henderson, Sven Schnelle, Richard Henderson; +Cc: qemu-devel

[-- Attachment #1: Type: text/plain, Size: 1175 bytes --]

On 20.12.19 08:26, Helge Deller wrote:
> On 19.12.19 01:28, Richard Henderson wrote:
>> On 11/3/19 10:56 AM, Sven Schnelle wrote:
>>> This adds emulation of Artist graphics good enough
>>> to get a Text console on both Linux and HP-UX. The
>>> X11 server from HP-UX also works.
>>>
>>> Signed-off-by: Sven Schnelle <svens@stackframe.org>
>>> ---
>>>  hw/display/Kconfig       |    4 +
>>>  hw/display/Makefile.objs |    1 +
>>>  hw/display/artist.c      | 1449 ++++++++++++++++++++++++++++++++++++++
>>>  hw/display/trace-events  |    9 +
>>>  hw/hppa/Kconfig          |    1 +
>>>  hw/hppa/hppa_hardware.h  |    1 +
>>>  hw/hppa/machine.c        |    9 +
>>>  7 files changed, 1474 insertions(+)
>>>  create mode 100644 hw/display/artist.c
>>
>> Seems to have some problems rebased upon master:
>>
>> ...
>
> Richard, the attached patch (for seabios-hppa) fixes it for me.
> Can you test as well?
> It fixes the sti text column to go out-of-range and thus outside the framebuffer memory.

The attached patch is even better.
It always wraps to the next line (or scrolls the screen if necessary) if
the end of the line has been reached.

Helge

[-- Attachment #2: p1 --]
[-- Type: text/plain, Size: 396 bytes --]

diff --git a/src/parisc/sti.c b/src/parisc/sti.c
index 7935770..61e7002 100644
--- a/src/parisc/sti.c
+++ b/src/parisc/sti.c
@@ -168,5 +168,10 @@ void sti_putc(const char c)
         }
         return;
     }
+
+    /* wrap to next line or scroll screen if EOL reached */
+    if (col >= ((sti_glob_cfg.onscreen_x / font->width) - 1))
+	sti_putc('\n');
+
     sti_putchar(rom, row, col++, c);
 }

^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: [PATCH v4 5/6] hppa: Add emulation of Artist graphics
  2019-12-20 16:36       ` Helge Deller
@ 2019-12-20 17:03         ` Sven Schnelle
  0 siblings, 0 replies; 14+ messages in thread
From: Sven Schnelle @ 2019-12-20 17:03 UTC (permalink / raw)
  To: Helge Deller; +Cc: Richard Henderson, qemu-devel, Richard Henderson

Hi,

On Fri, Dec 20, 2019 at 05:36:36PM +0100, Helge Deller wrote:
> On 20.12.19 08:26, Helge Deller wrote:
> > On 19.12.19 01:28, Richard Henderson wrote:
> >> On 11/3/19 10:56 AM, Sven Schnelle wrote:
> >>> This adds emulation of Artist graphics good enough
> >>> to get a Text console on both Linux and HP-UX. The
> >>> X11 server from HP-UX also works.
> >>>
> >>> Signed-off-by: Sven Schnelle <svens@stackframe.org>
> >>> ---
> >>>  hw/display/Kconfig       |    4 +
> >>>  hw/display/Makefile.objs |    1 +
> >>>  hw/display/artist.c      | 1449 ++++++++++++++++++++++++++++++++++++++
> >>>  hw/display/trace-events  |    9 +
> >>>  hw/hppa/Kconfig          |    1 +
> >>>  hw/hppa/hppa_hardware.h  |    1 +
> >>>  hw/hppa/machine.c        |    9 +
> >>>  7 files changed, 1474 insertions(+)
> >>>  create mode 100644 hw/display/artist.c
> >>
> >> Seems to have some problems rebased upon master:
> >>
> >> ...
> >
> > Richard, the attached patch (for seabios-hppa) fixes it for me.
> > Can you test as well?
> > It fixes the sti text column to go out-of-range and thus outside the framebuffer memory.
> 
> The attached patch is even better.
> It always wraps to the next line (or scrolls the screen if necessary) if
> the end of the line has been reached.
> 
> Helge

> diff --git a/src/parisc/sti.c b/src/parisc/sti.c
> index 7935770..61e7002 100644
> --- a/src/parisc/sti.c
> +++ b/src/parisc/sti.c
> @@ -168,5 +168,10 @@ void sti_putc(const char c)
>          }
>          return;
>      }
> +
> +    /* wrap to next line or scroll screen if EOL reached */
> +    if (col >= ((sti_glob_cfg.onscreen_x / font->width) - 1))
> +	sti_putc('\n');
> +
>      sti_putchar(rom, row, col++, c);
>  }

Besides this, the root cause is the out-of-bounds check in vram_bit_write():
This fixes the crash for me. I'll resend an updated version later. Thanks for
helping debugging this issue!

diff --git a/hw/display/artist.c b/hw/display/artist.c
index 1d6c7d5d76..13c770e795 100644
--- a/hw/display/artist.c
+++ b/hw/display/artist.c
@@ -360,7 +360,7 @@ static void vram_bit_write(ARTISTState *s, int posx, int posy, bool incr_x,
         return;
     }

-    if (posy * width + posx > buf->size) {
+    if (posy * width + posx >= buf->size) {
         qemu_log("write outside bounds: wants %dx%d, max size %dx%d\n",
                 posx, posy, width, height);
         return;

Regards
Sven


^ permalink raw reply related	[flat|nested] 14+ messages in thread

end of thread, other threads:[~2019-12-20 17:09 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-11-03 20:56 [PATCH v4 0/6] HPPA: i82596, PS/2 and graphics emulation Sven Schnelle
2019-11-03 20:56 ` [PATCH v4 1/6] hw/hppa/dino.c: Improve emulation of Dino PCI chip Sven Schnelle
2019-11-03 20:56 ` [PATCH v4 2/6] hppa: Add support for LASI chip with i82596 NIC Sven Schnelle
2019-11-03 20:56 ` [PATCH v4 3/6] ps2: accept 'Set Key Make and Break' commands Sven Schnelle
2019-11-03 20:56 ` [PATCH v4 4/6] hppa: add emulation of LASI PS2 controllers Sven Schnelle
2019-11-03 20:56 ` [PATCH v4 5/6] hppa: Add emulation of Artist graphics Sven Schnelle
2019-12-19  0:28   ` Richard Henderson
2019-12-20  7:26     ` Helge Deller
2019-12-20 16:36       ` Helge Deller
2019-12-20 17:03         ` Sven Schnelle
2019-11-23 13:59 ` [PATCH v4 0/6] HPPA: i82596, PS/2 and graphics emulation Sven Schnelle
2019-11-25 10:01   ` Richard Henderson
2019-12-13 20:40     ` Helge Deller
2019-12-19  0:44       ` Richard Henderson

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).