qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v3 0/6] HPPA: i82596, PS/2 and graphics emulation
@ 2019-10-22 20:59 Sven Schnelle
  2019-10-22 20:59 ` [PATCH v3 1/6] hw/hppa/dino.c: Improve emulation of Dino PCI chip Sven Schnelle
                   ` (4 more replies)
  0 siblings, 5 replies; 17+ messages in thread
From: Sven Schnelle @ 2019-10-22 20:59 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 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          |    3 +
 hw/display/Makefile.objs    |    1 +
 hw/display/artist.c         | 1336 +++++++++++++++++++++++++++++++++++
 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           |   18 +-
 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         |   13 +
 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, 3196 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.23.0



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

* [PATCH v3 1/6] hw/hppa/dino.c: Improve emulation of Dino PCI chip
  2019-10-22 20:59 [PATCH v3 0/6] HPPA: i82596, PS/2 and graphics emulation Sven Schnelle
@ 2019-10-22 20:59 ` Sven Schnelle
  2019-10-22 20:59 ` [PATCH v3 2/6] hppa: Add support for LASI chip with i82596 NIC Sven Schnelle
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 17+ messages in thread
From: Sven Schnelle @ 2019-10-22 20:59 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 250ce8e7a1..f9541c3305 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.23.0



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

* [PATCH v3 2/6] hppa: Add support for LASI chip with i82596 NIC
  2019-10-22 20:59 [PATCH v3 0/6] HPPA: i82596, PS/2 and graphics emulation Sven Schnelle
  2019-10-22 20:59 ` [PATCH v3 1/6] hw/hppa/dino.c: Improve emulation of Dino PCI chip Sven Schnelle
@ 2019-10-22 20:59 ` Sven Schnelle
  2019-10-22 20:59 ` [PATCH v3 3/6] ps2: accept 'Set Key Make and Break' commands Sven Schnelle
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 17+ messages in thread
From: Sven Schnelle @ 2019-10-22 20:59 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         |  13 +
 include/hw/net/lasi_82596.h |  29 ++
 14 files changed, 1406 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 f9541c3305..91e9e8ceac 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 7e23675429..65fc20ebed 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 4ef86dc3a5..a38f9adaeb 100644
--- a/hw/net/Kconfig
+++ b/hw/net/Kconfig
@@ -24,6 +24,9 @@ config PCNET_PCI
 config PCNET_COMMON
     bool
 
+config I82596_COMMON
+    bool
+
 config E1000_PCI
     bool
     default y if PCI_DEVICES
@@ -82,6 +85,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 9904273b06..f3ef9ffe9b 100644
--- a/hw/net/Makefile.objs
+++ b/hw/net/Makefile.objs
@@ -27,6 +27,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 58665655cc..abb71a6a9a 100644
--- a/hw/net/trace-events
+++ b/hw/net/trace-events
@@ -367,3 +367,16 @@ virtio_net_announce_notify(void) ""
 virtio_net_announce_timer(int round) "%d"
 virtio_net_handle_announce(int round) "%d"
 virtio_net_post_load_device(void)
+
+# 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.23.0



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

* [PATCH v3 3/6] ps2: accept 'Set Key Make and Break' commands
  2019-10-22 20:59 [PATCH v3 0/6] HPPA: i82596, PS/2 and graphics emulation Sven Schnelle
  2019-10-22 20:59 ` [PATCH v3 1/6] hw/hppa/dino.c: Improve emulation of Dino PCI chip Sven Schnelle
  2019-10-22 20:59 ` [PATCH v3 2/6] hppa: Add support for LASI chip with i82596 NIC Sven Schnelle
@ 2019-10-22 20:59 ` Sven Schnelle
  2019-10-23 11:08   ` Philippe Mathieu-Daudé
  2019-10-22 20:59 ` [PATCH v3 4/6] hppa: add emulation of LASI PS2 controllers Sven Schnelle
  2019-10-22 20:59 ` [PATCH v3 5/6] hppa: Add emulation of Artist graphics Sven Schnelle
  4 siblings, 1 reply; 17+ messages in thread
From: Sven Schnelle @ 2019-10-22 20:59 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.23.0



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

* [PATCH v3 4/6] hppa: add emulation of LASI PS2 controllers
  2019-10-22 20:59 [PATCH v3 0/6] HPPA: i82596, PS/2 and graphics emulation Sven Schnelle
                   ` (2 preceding siblings ...)
  2019-10-22 20:59 ` [PATCH v3 3/6] ps2: accept 'Set Key Make and Break' commands Sven Schnelle
@ 2019-10-22 20:59 ` Sven Schnelle
  2019-10-22 20:59 ` [PATCH v3 5/6] hppa: Add emulation of Artist graphics Sven Schnelle
  4 siblings, 0 replies; 17+ messages in thread
From: Sven Schnelle @ 2019-10-22 20:59 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.23.0



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

* [PATCH v3 5/6] hppa: Add emulation of Artist graphics
  2019-10-22 20:59 [PATCH v3 0/6] HPPA: i82596, PS/2 and graphics emulation Sven Schnelle
                   ` (3 preceding siblings ...)
  2019-10-22 20:59 ` [PATCH v3 4/6] hppa: add emulation of LASI PS2 controllers Sven Schnelle
@ 2019-10-22 20:59 ` Sven Schnelle
  2019-10-24 20:51   ` Mark Cave-Ayland
  2019-10-26 11:43   ` Philippe Mathieu-Daudé
  4 siblings, 2 replies; 17+ messages in thread
From: Sven Schnelle @ 2019-10-22 20:59 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       |    3 +
 hw/display/Makefile.objs |    1 +
 hw/display/artist.c      | 1336 ++++++++++++++++++++++++++++++++++++++
 hw/display/trace-events  |    9 +
 hw/hppa/Kconfig          |    1 +
 hw/hppa/hppa_hardware.h  |    1 +
 hw/hppa/machine.c        |   10 +
 7 files changed, 1361 insertions(+)
 create mode 100644 hw/display/artist.c

diff --git a/hw/display/Kconfig b/hw/display/Kconfig
index cbdf7b1a67..953631afb6 100644
--- a/hw/display/Kconfig
+++ b/hw/display/Kconfig
@@ -91,6 +91,9 @@ config TCX
 config CG3
     bool
 
+config ARTIST
+    bool
+
 config VGA
     bool
 
diff --git a/hw/display/Makefile.objs b/hw/display/Makefile.objs
index 5a4066383b..5f63294149 100644
--- a/hw/display/Makefile.objs
+++ b/hw/display/Makefile.objs
@@ -39,6 +39,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..9b285b3993
--- /dev/null
+++ b/hw/display/artist.c
@@ -0,0 +1,1336 @@
+/*
+ * 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 "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"
+
+#define TYPE_ARTIST "artist"
+#define ARTIST(obj) OBJECT_CHECK(ARTISTState, (obj), TYPE_ARTIST)
+
+struct vram_buffer {
+    uint8_t *data;
+    int size;
+    int width;
+    int height;
+};
+
+typedef struct ARTISTState {
+    SysBusDevice parent_obj;
+
+    QemuConsole *con;
+    MemoryRegion vram_mem;
+    MemoryRegion reg;
+    uint8_t *vram;
+
+    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_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];
+
+    /*
+     * 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.
+     */
+    cursor_pos_y = 0x47a - artist_get_y(s->cursor_pos);
+    cursor_pos_x = ((artist_get_x(s->cursor_pos) - 338) / 2);
+
+    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_update_display(void *opaque)
+{
+    ARTISTState *s = opaque;
+    DisplaySurface *surface = qemu_console_surface(s->con);
+    const uint8_t *pix;
+    uint32_t *data, *cmap;
+    int x, y;
+
+    if (surface_bits_per_pixel(surface) != 32) {
+        return;
+    }
+
+    pix = s->vram_buffer[ARTIST_BUFFER_AP].data;
+    cmap = (uint32_t *)(s->vram_buffer[ARTIST_BUFFER_CMAP].data + 0x400);
+    data = (uint32_t *)surface_data(surface);
+
+    for (y = 0; y < s->height; y++) {
+        for (x = 0; x < s->width; x++) {
+            *data++ = cmap[*pix++];
+        }
+    }
+    artist_draw_cursor(s);
+    dpy_gfx_update(s->con, 0, 0, s->width, s->height);
+
+}
+
+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 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;
+        }
+        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))) {
+#ifdef HOST_WORDS_BIGENDIAN
+                artist_rop8(s, p + offset + 3 - i, data8[3 - i]);
+#else
+                artist_rop8(s, p + offset + 3 - i, data8[i]);
+#endif
+            }
+        }
+        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);
+                    }
+                }
+            }
+        }
+        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;
+        }
+    }
+}
+
+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) {
+        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) {
+            artist_rop8(s, buf->data + dst + column, buf->data[src + column]);
+        }
+    }
+}
+
+static void fill_window(ARTISTState *s, int startx, int starty,
+                        int width, int height)
+{
+    uint32_t offset;
+    uint8_t color = artist_get_color(s);
+    uint8_t *buf;
+    int x, y;
+
+    trace_artist_fill_window(startx, starty, width, height,
+        s->image_bitmap_op, s->control_plane);
+
+    if (s->control_plane != 0) {
+        qemu_log_mask(LOG_UNIMP, "%s: CONTROL_PLANE: %08x\n", __func__,
+            s->control_plane);
+        return;
+    }
+
+    if (s->reg_100080 == 0x7d) {
+        height = artist_get_y(s->blockmove_size);
+        s->vram_start += height;
+    }
+
+    buf = s->vram_buffer[ARTIST_BUFFER_AP].data;
+
+    for (y = starty; y < starty + height; y++) {
+        offset = y * s->width;
+
+        for (x = startx; x < startx + width; x++) {
+            artist_rop8(s, buf + offset + x, color);
+        }
+    }
+}
+
+static void draw_line(ARTISTState *s, int x1, int y1, int x2, int y2,
+        bool update_start, int skip_pix, int max_pix)
+{
+    uint8_t color = artist_get_color(s);
+    int dx, dy, t, e, x, y, incy, diago, horiz;
+    bool c1;
+    uint8_t *buf, *p;
+
+
+    if (update_start) {
+        s->vram_start = (x2 << 16) | y2;
+    }
+
+    buf = s->vram_buffer[ARTIST_BUFFER_AP].data;
+
+    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 + x * s->width + y;
+       } else {
+            p = buf + y * s->width + x;
+       }
+
+        if (skip_pix > 0) {
+            skip_pix--;
+        } else {
+            artist_rop8(s, p, color);
+        }
+
+        if (e > 0) {
+            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)
+{
+    uint32_t color = (s->image_bitmap_op & 2) ? s->fg_color : s->bg_color;
+    uint8_t *buf;
+    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].data;
+
+    for (i = 0; i < 16; i++) {
+        mask = 1 << (15 - i);
+        if (val & mask) {
+            artist_rop8(s, buf + offset + i, color);
+        } else {
+            if (!(s->image_bitmap_op & 0x20000000)) {
+                artist_rop8(s, buf + offset + i, s->bg_color);
+            }
+        }
+    }
+}
+
+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:
+        combine_write_reg(addr, val, size, &s->cursor_pos);
+        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;
+
+    trace_artist_vram_write(size, addr, val);
+
+    if (s->cmap_bm_access) {
+        buf = &s->vram_buffer[ARTIST_BUFFER_CMAP];
+        *(uint32_t *)(buf->data + addr) = val;
+        return;
+    }
+
+    buf = vram_write_buffer(s);
+    if (!buf->size) {
+        return;
+    }
+
+    switch (size) {
+    case 4:
+        *(uint32_t *)(buf->data + posy * buf->width + posx) = be32_to_cpu(val);
+        break;
+    case 2:
+        *(uint16_t *)(buf->data + posy * buf->width + posx) = be16_to_cpu(val);
+        break;
+    case 1:
+        *(uint8_t *)(buf->data + posy * buf->width + posx) = val;
+        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;
+    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 const GraphicHwOps artist_ops = {
+    .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",
+            0x400000);
+    memory_region_init_io(&s->vram_mem, obj, &artist_vram_ops, s, "artist.vram",
+            0x800000);
+    sysbus_init_mmio(sbd, &s->reg);
+    sysbus_init_mmio(sbd, &s->vram_mem);
+}
+
+static void artist_set_buffer(ARTISTState *s, uint8_t **vram, unsigned int idx,
+                              int width, int height)
+{
+    struct vram_buffer *buf = s->vram_buffer + idx;
+
+    buf->data = *vram;
+    buf->size = height * width;
+    buf->width = width;
+    buf->height = height;
+    *vram = *vram + buf->size;
+}
+
+static void artist_realizefn(DeviceState *dev, Error **errp)
+{
+    uint8_t *vram;
+
+    ARTISTState *s = ARTIST(dev);
+
+    vram = g_malloc0(4 * 1048576);
+    s->vram = vram;
+    artist_set_buffer(s, &vram, ARTIST_BUFFER_CMAP, 2048, 4);
+    artist_set_buffer(s, &vram, ARTIST_BUFFER_AP, s->width, s->height);
+    artist_set_buffer(s, &vram, ARTIST_BUFFER_CURSOR1, 64, 64);
+    artist_set_buffer(s, &vram, ARTIST_BUFFER_CURSOR2, 64, 64);
+    artist_set_buffer(s, &vram, ARTIST_BUFFER_ATTRIBUTE, 64, 64);
+
+    /*
+     * 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)
+{
+    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_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->realize = artist_realizefn;
+    dc->vmsd = &vmstate_artist;
+    dc->props = artist_properties;
+}
+
+static const TypeInfo artist_info = {
+    .name          = TYPE_ARTIST,
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(ARTISTState),
+    .instance_init = artist_initfn,
+    .class_init    = artist_class_init,
+};
+
+static void artist_register_types(void)
+{
+    type_register_static(&artist_info);
+}
+
+type_init(artist_register_types)
diff --git a/hw/display/trace-events b/hw/display/trace-events
index ba7787b180..e6e22bef88 100644
--- a/hw/display/trace-events
+++ b/hw/display/trace-events
@@ -142,3 +142,12 @@ sii9022_switch_mode(const char *mode) "mode: %s"
 # ati.c
 ati_mm_read(unsigned int size, uint64_t addr, const char *name, uint64_t val) "%u 0x%"PRIx64 " %s -> 0x%"PRIx64
 ati_mm_write(unsigned int size, uint64_t addr, const char *name, uint64_t val) "%u 0x%"PRIx64 " %s <- 0x%"PRIx64
+
+# artist.c
+artist_reg_read(unsigned int size, uint64_t addr, const char *name, uint64_t val) "%u 0x%"PRIx64 "%s -> 0x%"PRIx64
+artist_reg_write(unsigned int size, uint64_t addr, const char *name, uint64_t val) "%u 0x%"PRIx64 "%s <- 0x%"PRIx64
+artist_vram_read(unsigned int size, uint64_t addr, int posx, int posy, uint64_t val) "%u 0x%"PRIx64 " %ux%u-> 0x%"PRIx64
+artist_vram_write(unsigned int size, uint64_t addr, uint64_t val) "%u 0x%"PRIx64 " <- 0x%"PRIx64
+artist_fill_window(unsigned int start_x, unsigned int start_y, unsigned int width, unsigned int height, uint32_t op, uint32_t ctlpln) "start=%ux%u length=%ux%u op=0x%08x ctlpln=0x%08x"
+artist_block_move(unsigned int start_x, unsigned int start_y, unsigned int dest_x, unsigned int dest_y, unsigned int width, unsigned int height) "source %ux%u -> dest %ux%u size %ux%u"
+artist_draw_line(unsigned int start_x, unsigned int start_y, unsigned int end_x, unsigned int end_y) "%ux%u %ux%u"
diff --git a/hw/hppa/Kconfig b/hw/hppa/Kconfig
index 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 65fc20ebed..39bd5f1834 100644
--- a/hw/hppa/machine.c
+++ b/hw/hppa/machine.c
@@ -21,6 +21,7 @@
 #include "qemu/units.h"
 #include "qapi/error.h"
 #include "qemu/log.h"
+#include "hw/usb.h"
 
 #define MAX_IDE_BUS 2
 
@@ -74,6 +75,7 @@ static void machine_hppa_init(MachineState *machine)
     MemoryRegion *cpu_region;
     long i;
     unsigned int smp_cpus = machine->smp.cpus;
+    SysBusDevice *s;
 
     ram_size = machine->ram_size;
 
@@ -126,6 +128,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.23.0



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

* Re: [PATCH v3 3/6] ps2: accept 'Set Key Make and Break' commands
  2019-10-22 20:59 ` [PATCH v3 3/6] ps2: accept 'Set Key Make and Break' commands Sven Schnelle
@ 2019-10-23 11:08   ` Philippe Mathieu-Daudé
  2019-10-23 12:08     ` Sven Schnelle
  0 siblings, 1 reply; 17+ messages in thread
From: Philippe Mathieu-Daudé @ 2019-10-23 11:08 UTC (permalink / raw)
  To: Sven Schnelle, Richard Henderson; +Cc: Helge Deller, qemu-devel

Hi Sven,

(Please Cc reviewers who previously commented your patch)

On 10/22/19 10:59 PM, Sven Schnelle wrote:
> 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:

OK

>               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);

I'm not sure, can't this loop?

Can you fold it with the '0x00' case?

> +            break;
>           default:
>               ps2_queue(&s->common, KBD_REPLY_RESEND);
>               break;
>           }
>           break;
> +    case KBD_CMD_SET_MAKE_BREAK:

We can reorder this one in the same case with:

     case KBD_CMD_SET_LEDS:
     case KBD_CMD_SET_RATE:

> +        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) {
> 



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

* Re: [PATCH v3 3/6] ps2: accept 'Set Key Make and Break' commands
  2019-10-23 11:08   ` Philippe Mathieu-Daudé
@ 2019-10-23 12:08     ` Sven Schnelle
  2019-10-23 12:32       ` Philippe Mathieu-Daudé
  0 siblings, 1 reply; 17+ messages in thread
From: Sven Schnelle @ 2019-10-23 12:08 UTC (permalink / raw)
  To: Philippe Mathieu-Daudé; +Cc: Helge Deller, qemu-devel, Richard Henderson

Hi Philippe,

On Wed, Oct 23, 2019 at 01:08:35PM +0200, Philippe Mathieu-Daudé wrote:
> Hi Sven,
> 
> (Please Cc reviewers who previously commented your patch)
>
> On 10/22/19 10:59 PM, Sven Schnelle wrote:
> > 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:
> 
> OK
> 
> >               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);
> 
> I'm not sure, can't this loop?

I don't see how?

> Can you fold it with the '0x00' case?

Ok.

> > +            break;
> >           default:
> >               ps2_queue(&s->common, KBD_REPLY_RESEND);
> >               break;
> >           }
> >           break;
> > +    case KBD_CMD_SET_MAKE_BREAK:
> 
> We can reorder this one in the same case with:
> 
>     case KBD_CMD_SET_LEDS:
>     case KBD_CMD_SET_RATE:

Ok.

> > +        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) {
> > 
> 

Regards
Sven


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

* Re: [PATCH v3 3/6] ps2: accept 'Set Key Make and Break' commands
  2019-10-23 12:08     ` Sven Schnelle
@ 2019-10-23 12:32       ` Philippe Mathieu-Daudé
  0 siblings, 0 replies; 17+ messages in thread
From: Philippe Mathieu-Daudé @ 2019-10-23 12:32 UTC (permalink / raw)
  To: Sven Schnelle; +Cc: Helge Deller, qemu-devel, Richard Henderson

On 10/23/19 2:08 PM, Sven Schnelle wrote:
> Hi Philippe,
> 
> On Wed, Oct 23, 2019 at 01:08:35PM +0200, Philippe Mathieu-Daudé wrote:
>> Hi Sven,
>>
>> (Please Cc reviewers who previously commented your patch)
>>
>> On 10/22/19 10:59 PM, Sven Schnelle wrote:
>>> 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:
>>
>> OK
>>
>>>                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);
>>
>> I'm not sure, can't this loop?
> 
> I don't see how?

I misunderstood the two switch statements.

>> Can you fold it with the '0x00' case?
> 
> Ok.
> 
>>> +            break;
>>>            default:
>>>                ps2_queue(&s->common, KBD_REPLY_RESEND);
>>>                break;
>>>            }
>>>            break;
>>> +    case KBD_CMD_SET_MAKE_BREAK:
>>
>> We can reorder this one in the same case with:
>>
>>      case KBD_CMD_SET_LEDS:
>>      case KBD_CMD_SET_RATE:
> 
> Ok.

With these changes:
Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>

> 
>>> +        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) {
>>>
>>
> 
> Regards
> Sven
> 



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

* Re: [PATCH v3 5/6] hppa: Add emulation of Artist graphics
  2019-10-22 20:59 ` [PATCH v3 5/6] hppa: Add emulation of Artist graphics Sven Schnelle
@ 2019-10-24 20:51   ` Mark Cave-Ayland
  2019-10-25  9:31     ` Sven Schnelle
  2019-10-26 11:43   ` Philippe Mathieu-Daudé
  1 sibling, 1 reply; 17+ messages in thread
From: Mark Cave-Ayland @ 2019-10-24 20:51 UTC (permalink / raw)
  To: Sven Schnelle, Richard Henderson; +Cc: Helge Deller, qemu-devel

On 22/10/2019 21:59, 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       |    3 +
>  hw/display/Makefile.objs |    1 +
>  hw/display/artist.c      | 1336 ++++++++++++++++++++++++++++++++++++++
>  hw/display/trace-events  |    9 +
>  hw/hppa/Kconfig          |    1 +
>  hw/hppa/hppa_hardware.h  |    1 +
>  hw/hppa/machine.c        |   10 +
>  7 files changed, 1361 insertions(+)
>  create mode 100644 hw/display/artist.c
> 
> diff --git a/hw/display/Kconfig b/hw/display/Kconfig
> index cbdf7b1a67..953631afb6 100644
> --- a/hw/display/Kconfig
> +++ b/hw/display/Kconfig
> @@ -91,6 +91,9 @@ config TCX
>  config CG3
>      bool
>  
> +config ARTIST
> +    bool
> +
>  config VGA
>      bool
>  
> diff --git a/hw/display/Makefile.objs b/hw/display/Makefile.objs
> index 5a4066383b..5f63294149 100644
> --- a/hw/display/Makefile.objs
> +++ b/hw/display/Makefile.objs
> @@ -39,6 +39,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..9b285b3993
> --- /dev/null
> +++ b/hw/display/artist.c
> @@ -0,0 +1,1336 @@
> +/*
> + * 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 "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"
> +
> +#define TYPE_ARTIST "artist"
> +#define ARTIST(obj) OBJECT_CHECK(ARTISTState, (obj), TYPE_ARTIST)
> +
> +struct vram_buffer {
> +    uint8_t *data;
> +    int size;
> +    int width;
> +    int height;
> +};
> +
> +typedef struct ARTISTState {
> +    SysBusDevice parent_obj;
> +
> +    QemuConsole *con;
> +    MemoryRegion vram_mem;
> +    MemoryRegion reg;
> +    uint8_t *vram;
> +
> +    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_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];
> +
> +    /*
> +     * 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.
> +     */
> +    cursor_pos_y = 0x47a - artist_get_y(s->cursor_pos);
> +    cursor_pos_x = ((artist_get_x(s->cursor_pos) - 338) / 2);
> +
> +    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_update_display(void *opaque)
> +{
> +    ARTISTState *s = opaque;
> +    DisplaySurface *surface = qemu_console_surface(s->con);
> +    const uint8_t *pix;
> +    uint32_t *data, *cmap;
> +    int x, y;
> +
> +    if (surface_bits_per_pixel(surface) != 32) {
> +        return;
> +    }

DisplaySurfaces are always 32-bit in QEMU these days, and so you shouldn't need this
check.

> +    pix = s->vram_buffer[ARTIST_BUFFER_AP].data;
> +    cmap = (uint32_t *)(s->vram_buffer[ARTIST_BUFFER_CMAP].data + 0x400);
> +    data = (uint32_t *)surface_data(surface);
> +
> +    for (y = 0; y < s->height; y++) {
> +        for (x = 0; x < s->width; x++) {
> +            *data++ = cmap[*pix++];
> +        }
> +    }
> +    artist_draw_cursor(s);
> +    dpy_gfx_update(s->con, 0, 0, s->width, s->height);
> +
> +}

It looks like artist_update_display() is based upon older code which doesn't use
memory_region_snapshot_and_clear_dirty() to ensure that you don't get display
tearing. See for example commit fec5e8c92b "vga: make display updates thread safe."
although if you look at the latest code you'll see that vga_sync_dirty_bitmap() is no
longer required.

> +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 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;
> +        }
> +        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))) {
> +#ifdef HOST_WORDS_BIGENDIAN
> +                artist_rop8(s, p + offset + 3 - i, data8[3 - i]);
> +#else
> +                artist_rop8(s, p + offset + 3 - i, data8[i]);
> +#endif

I tend to find it more readable to create a macro outside of the function to handle
the endian swap e.g.

#ifdef HOST_WORDS_BIGENDIAN
#define ROP8OFF (3 - i)
#else
#define ROP8OFF (i)
#endif

and then replace this with just:

    artist_rop8(s, p + offset + 3 - i, data8[ROP8OFF(i)]);

> +            }
> +        }
> +        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);
> +                    }
> +                }
> +            }
> +        }
> +        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;
> +        }
> +    }
> +}
> +
> +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) {
> +        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) {
> +            artist_rop8(s, buf->data + dst + column, buf->data[src + column]);
> +        }
> +    }
> +}
> +
> +static void fill_window(ARTISTState *s, int startx, int starty,
> +                        int width, int height)
> +{
> +    uint32_t offset;
> +    uint8_t color = artist_get_color(s);
> +    uint8_t *buf;
> +    int x, y;
> +
> +    trace_artist_fill_window(startx, starty, width, height,
> +        s->image_bitmap_op, s->control_plane);
> +
> +    if (s->control_plane != 0) {
> +        qemu_log_mask(LOG_UNIMP, "%s: CONTROL_PLANE: %08x\n", __func__,
> +            s->control_plane);
> +        return;
> +    }
> +
> +    if (s->reg_100080 == 0x7d) {
> +        height = artist_get_y(s->blockmove_size);
> +        s->vram_start += height;
> +    }
> +
> +    buf = s->vram_buffer[ARTIST_BUFFER_AP].data;
> +
> +    for (y = starty; y < starty + height; y++) {
> +        offset = y * s->width;
> +
> +        for (x = startx; x < startx + width; x++) {
> +            artist_rop8(s, buf + offset + x, color);
> +        }
> +    }
> +}
> +
> +static void draw_line(ARTISTState *s, int x1, int y1, int x2, int y2,
> +        bool update_start, int skip_pix, int max_pix)
> +{
> +    uint8_t color = artist_get_color(s);
> +    int dx, dy, t, e, x, y, incy, diago, horiz;
> +    bool c1;
> +    uint8_t *buf, *p;
> +
> +
> +    if (update_start) {
> +        s->vram_start = (x2 << 16) | y2;
> +    }
> +
> +    buf = s->vram_buffer[ARTIST_BUFFER_AP].data;
> +
> +    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 + x * s->width + y;
> +       } else {
> +            p = buf + y * s->width + x;
> +       }
> +
> +        if (skip_pix > 0) {
> +            skip_pix--;
> +        } else {
> +            artist_rop8(s, p, color);
> +        }
> +
> +        if (e > 0) {
> +            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)
> +{
> +    uint32_t color = (s->image_bitmap_op & 2) ? s->fg_color : s->bg_color;
> +    uint8_t *buf;
> +    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].data;
> +
> +    for (i = 0; i < 16; i++) {
> +        mask = 1 << (15 - i);
> +        if (val & mask) {
> +            artist_rop8(s, buf + offset + i, color);
> +        } else {
> +            if (!(s->image_bitmap_op & 0x20000000)) {
> +                artist_rop8(s, buf + offset + i, s->bg_color);
> +            }
> +        }
> +    }
> +}
> +
> +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

Are the values being written as little-endian or big-endian? You should be able to
replace them with either st{w,l}_le_p() or st{w,l}_be_p().

> +    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:
> +        combine_write_reg(addr, val, size, &s->cursor_pos);
> +        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

Same comment as for combine_write_reg() above.

> +    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;
> +
> +    trace_artist_vram_write(size, addr, val);
> +
> +    if (s->cmap_bm_access) {
> +        buf = &s->vram_buffer[ARTIST_BUFFER_CMAP];
> +        *(uint32_t *)(buf->data + addr) = val;
> +        return;
> +    }
> +
> +    buf = vram_write_buffer(s);
> +    if (!buf->size) {
> +        return;
> +    }
> +
> +    switch (size) {
> +    case 4:
> +        *(uint32_t *)(buf->data + posy * buf->width + posx) = be32_to_cpu(val);
> +        break;
> +    case 2:
> +        *(uint16_t *)(buf->data + posy * buf->width + posx) = be16_to_cpu(val);
> +        break;
> +    case 1:
> +        *(uint8_t *)(buf->data + posy * buf->width + posx) = val;
> +        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;
> +    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,
> +    },
> +};

...however you are using combine_read_reg() and combine_write_reg() to handle the
host-endian swaps within artist_reg_read() and artist_reg_write(). Could you not just
set the .endianness above to DEVICE_LITTLE_ENDIAN/DEVICE_BIG_ENDIAN as appropriate to
do the conversion for you?

> +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 const GraphicHwOps artist_ops = {
> +    .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",
> +            0x400000);
> +    memory_region_init_io(&s->vram_mem, obj, &artist_vram_ops, s, "artist.vram",
> +            0x800000);
> +    sysbus_init_mmio(sbd, &s->reg);
> +    sysbus_init_mmio(sbd, &s->vram_mem);
> +}
> +
> +static void artist_set_buffer(ARTISTState *s, uint8_t **vram, unsigned int idx,
> +                              int width, int height)
> +{
> +    struct vram_buffer *buf = s->vram_buffer + idx;
> +
> +    buf->data = *vram;
> +    buf->size = height * width;
> +    buf->width = width;
> +    buf->height = height;
> +    *vram = *vram + buf->size;
> +}
> +
> +static void artist_realizefn(DeviceState *dev, Error **errp)
> +{
> +    uint8_t *vram;
> +
> +    ARTISTState *s = ARTIST(dev);
> +
> +    vram = g_malloc0(4 * 1048576);

You may find it more readable to use (4 * MiB) here.

> +    s->vram = vram;
> +    artist_set_buffer(s, &vram, ARTIST_BUFFER_CMAP, 2048, 4);
> +    artist_set_buffer(s, &vram, ARTIST_BUFFER_AP, s->width, s->height);
> +    artist_set_buffer(s, &vram, ARTIST_BUFFER_CURSOR1, 64, 64);
> +    artist_set_buffer(s, &vram, ARTIST_BUFFER_CURSOR2, 64, 64);
> +    artist_set_buffer(s, &vram, ARTIST_BUFFER_ATTRIBUTE, 64, 64);
> +
> +    /*
> +     * 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)
> +{
> +    return 0;
> +}

Is there some code missing here? Looking at a few of the other framebuffers they tend
to force a full display redraw during post_load.

> +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_class_init(ObjectClass *klass, void *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +
> +    dc->realize = artist_realizefn;
> +    dc->vmsd = &vmstate_artist;
> +    dc->props = artist_properties;
> +}
> +
> +static const TypeInfo artist_info = {
> +    .name          = TYPE_ARTIST,
> +    .parent        = TYPE_SYS_BUS_DEVICE,
> +    .instance_size = sizeof(ARTISTState),
> +    .instance_init = artist_initfn,
> +    .class_init    = artist_class_init,
> +};
> +
> +static void artist_register_types(void)
> +{
> +    type_register_static(&artist_info);
> +}
> +
> +type_init(artist_register_types)
> diff --git a/hw/display/trace-events b/hw/display/trace-events
> index ba7787b180..e6e22bef88 100644
> --- a/hw/display/trace-events
> +++ b/hw/display/trace-events
> @@ -142,3 +142,12 @@ sii9022_switch_mode(const char *mode) "mode: %s"
>  # ati.c
>  ati_mm_read(unsigned int size, uint64_t addr, const char *name, uint64_t val) "%u 0x%"PRIx64 " %s -> 0x%"PRIx64
>  ati_mm_write(unsigned int size, uint64_t addr, const char *name, uint64_t val) "%u 0x%"PRIx64 " %s <- 0x%"PRIx64
> +
> +# artist.c
> +artist_reg_read(unsigned int size, uint64_t addr, const char *name, uint64_t val) "%u 0x%"PRIx64 "%s -> 0x%"PRIx64
> +artist_reg_write(unsigned int size, uint64_t addr, const char *name, uint64_t val) "%u 0x%"PRIx64 "%s <- 0x%"PRIx64
> +artist_vram_read(unsigned int size, uint64_t addr, int posx, int posy, uint64_t val) "%u 0x%"PRIx64 " %ux%u-> 0x%"PRIx64
> +artist_vram_write(unsigned int size, uint64_t addr, uint64_t val) "%u 0x%"PRIx64 " <- 0x%"PRIx64
> +artist_fill_window(unsigned int start_x, unsigned int start_y, unsigned int width, unsigned int height, uint32_t op, uint32_t ctlpln) "start=%ux%u length=%ux%u op=0x%08x ctlpln=0x%08x"
> +artist_block_move(unsigned int start_x, unsigned int start_y, unsigned int dest_x, unsigned int dest_y, unsigned int width, unsigned int height) "source %ux%u -> dest %ux%u size %ux%u"
> +artist_draw_line(unsigned int start_x, unsigned int start_y, unsigned int end_x, unsigned int end_y) "%ux%u %ux%u"
> diff --git a/hw/hppa/Kconfig b/hw/hppa/Kconfig
> index 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 65fc20ebed..39bd5f1834 100644
> --- a/hw/hppa/machine.c
> +++ b/hw/hppa/machine.c
> @@ -21,6 +21,7 @@
>  #include "qemu/units.h"
>  #include "qapi/error.h"
>  #include "qemu/log.h"
> +#include "hw/usb.h"

This line looks like it belongs to a different patch?

>  #define MAX_IDE_BUS 2
>  
> @@ -74,6 +75,7 @@ static void machine_hppa_init(MachineState *machine)
>      MemoryRegion *cpu_region;
>      long i;
>      unsigned int smp_cpus = machine->smp.cpus;
> +    SysBusDevice *s;
>  
>      ram_size = machine->ram_size;
>  
> @@ -126,6 +128,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()) {
> 


ATB,

Mark.


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

* Re: [PATCH v3 5/6] hppa: Add emulation of Artist graphics
  2019-10-24 20:51   ` Mark Cave-Ayland
@ 2019-10-25  9:31     ` Sven Schnelle
  2019-10-26  9:35       ` Mark Cave-Ayland
  0 siblings, 1 reply; 17+ messages in thread
From: Sven Schnelle @ 2019-10-25  9:31 UTC (permalink / raw)
  To: Mark Cave-Ayland; +Cc: Helge Deller, qemu-devel, Richard Henderson

Hi Mark,

On Thu, Oct 24, 2019 at 09:51:31PM +0100, Mark Cave-Ayland wrote:

> On 22/10/2019 21:59, 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       |    3 +
> >  hw/display/Makefile.objs |    1 +
> >  hw/display/artist.c      | 1336 ++++++++++++++++++++++++++++++++++++++
> >  hw/display/trace-events  |    9 +
> >  hw/hppa/Kconfig          |    1 +
> >  hw/hppa/hppa_hardware.h  |    1 +
> >  hw/hppa/machine.c        |   10 +
> >  7 files changed, 1361 insertions(+)
> >  create mode 100644 hw/display/artist.c
> > 
> > diff --git a/hw/display/Kconfig b/hw/display/Kconfig
> > index cbdf7b1a67..953631afb6 100644
> > --- a/hw/display/Kconfig
> > +++ b/hw/display/Kconfig
> > @@ -91,6 +91,9 @@ config TCX
> >  config CG3
> >      bool
> >  
> > +config ARTIST
> > +    bool
> > +
> >  config VGA
> >      bool
> >  
> > diff --git a/hw/display/Makefile.objs b/hw/display/Makefile.objs
> > index 5a4066383b..5f63294149 100644
> > --- a/hw/display/Makefile.objs
> > +++ b/hw/display/Makefile.objs
> > @@ -39,6 +39,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..9b285b3993
> > --- /dev/null
> > +++ b/hw/display/artist.c
> > @@ -0,0 +1,1336 @@
> > +/*
> > + * 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 "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"
> > +
> > +#define TYPE_ARTIST "artist"
> > +#define ARTIST(obj) OBJECT_CHECK(ARTISTState, (obj), TYPE_ARTIST)
> > +
> > +struct vram_buffer {
> > +    uint8_t *data;
> > +    int size;
> > +    int width;
> > +    int height;
> > +};
> > +
> > +typedef struct ARTISTState {
> > +    SysBusDevice parent_obj;
> > +
> > +    QemuConsole *con;
> > +    MemoryRegion vram_mem;
> > +    MemoryRegion reg;
> > +    uint8_t *vram;
> > +
> > +    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_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];
> > +
> > +    /*
> > +     * 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.
> > +     */
> > +    cursor_pos_y = 0x47a - artist_get_y(s->cursor_pos);
> > +    cursor_pos_x = ((artist_get_x(s->cursor_pos) - 338) / 2);
> > +
> > +    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_update_display(void *opaque)
> > +{
> > +    ARTISTState *s = opaque;
> > +    DisplaySurface *surface = qemu_console_surface(s->con);
> > +    const uint8_t *pix;
> > +    uint32_t *data, *cmap;
> > +    int x, y;
> > +
> > +    if (surface_bits_per_pixel(surface) != 32) {
> > +        return;
> > +    }
> 
> DisplaySurfaces are always 32-bit in QEMU these days, and so you shouldn't need this
> check.
> 

Ok.

> > +    pix = s->vram_buffer[ARTIST_BUFFER_AP].data;
> > +    cmap = (uint32_t *)(s->vram_buffer[ARTIST_BUFFER_CMAP].data + 0x400);
> > +    data = (uint32_t *)surface_data(surface);
> > +
> > +    for (y = 0; y < s->height; y++) {
> > +        for (x = 0; x < s->width; x++) {
> > +            *data++ = cmap[*pix++];
> > +        }
> > +    }
> > +    artist_draw_cursor(s);
> > +    dpy_gfx_update(s->con, 0, 0, s->width, s->height);
> > +
> > +}
> 
> It looks like artist_update_display() is based upon older code which doesn't use
> memory_region_snapshot_and_clear_dirty() to ensure that you don't get display
> tearing. See for example commit fec5e8c92b "vga: make display updates thread safe."
> although if you look at the latest code you'll see that vga_sync_dirty_bitmap() is no
> longer required.
>

That was already proposed on IRC, and i tried to go via Memory Region Aliases.
However, the VRAM in Artist is not really exposed to the Host. Instead,
there's the Chipset inbetween that can do byte swapping (Colormap is LE,
VRAM is BE) and Bit-to-Byte/Word/Dword conversion. For example you could
write 0x55 into that VRAM region, and the chipset would expand that to
VRAM Bytes: 00 01 00 01 00 01 00 01. And to make it even worse emulation
wise it can also do different encodings for Read or Write accesses, and
mask out certain bits of the data. So after trying to convert it to the
"dirty bitmap" API i decided to just leave it as it is. The CPU load
used by the display update code is usually < 1%, so it's ok for me.

> > +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 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;
> > +        }
> > +        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))) {
> > +#ifdef HOST_WORDS_BIGENDIAN
> > +                artist_rop8(s, p + offset + 3 - i, data8[3 - i]);
> > +#else
> > +                artist_rop8(s, p + offset + 3 - i, data8[i]);
> > +#endif
> 
> I tend to find it more readable to create a macro outside of the function to handle
> the endian swap e.g.
> 
> #ifdef HOST_WORDS_BIGENDIAN
> #define ROP8OFF (3 - i)
> #else
> #define ROP8OFF (i)
> #endif
> 
> and then replace this with just:
> 
>     artist_rop8(s, p + offset + 3 - i, data8[ROP8OFF(i)]);
> 

Ok.

> > +            }
> > +        }
> > +        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);
> > +                    }
> > +                }
> > +            }
> > +        }
> > +        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;
> > +        }
> > +    }
> > +}
> > +
> > +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) {
> > +        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) {
> > +            artist_rop8(s, buf->data + dst + column, buf->data[src + column]);
> > +        }
> > +    }
> > +}
> > +
> > +static void fill_window(ARTISTState *s, int startx, int starty,
> > +                        int width, int height)
> > +{
> > +    uint32_t offset;
> > +    uint8_t color = artist_get_color(s);
> > +    uint8_t *buf;
> > +    int x, y;
> > +
> > +    trace_artist_fill_window(startx, starty, width, height,
> > +        s->image_bitmap_op, s->control_plane);
> > +
> > +    if (s->control_plane != 0) {
> > +        qemu_log_mask(LOG_UNIMP, "%s: CONTROL_PLANE: %08x\n", __func__,
> > +            s->control_plane);
> > +        return;
> > +    }
> > +
> > +    if (s->reg_100080 == 0x7d) {
> > +        height = artist_get_y(s->blockmove_size);
> > +        s->vram_start += height;
> > +    }
> > +
> > +    buf = s->vram_buffer[ARTIST_BUFFER_AP].data;
> > +
> > +    for (y = starty; y < starty + height; y++) {
> > +        offset = y * s->width;
> > +
> > +        for (x = startx; x < startx + width; x++) {
> > +            artist_rop8(s, buf + offset + x, color);
> > +        }
> > +    }
> > +}
> > +
> > +static void draw_line(ARTISTState *s, int x1, int y1, int x2, int y2,
> > +        bool update_start, int skip_pix, int max_pix)
> > +{
> > +    uint8_t color = artist_get_color(s);
> > +    int dx, dy, t, e, x, y, incy, diago, horiz;
> > +    bool c1;
> > +    uint8_t *buf, *p;
> > +
> > +
> > +    if (update_start) {
> > +        s->vram_start = (x2 << 16) | y2;
> > +    }
> > +
> > +    buf = s->vram_buffer[ARTIST_BUFFER_AP].data;
> > +
> > +    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 + x * s->width + y;
> > +       } else {
> > +            p = buf + y * s->width + x;
> > +       }
> > +
> > +        if (skip_pix > 0) {
> > +            skip_pix--;
> > +        } else {
> > +            artist_rop8(s, p, color);
> > +        }
> > +
> > +        if (e > 0) {
> > +            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)
> > +{
> > +    uint32_t color = (s->image_bitmap_op & 2) ? s->fg_color : s->bg_color;
> > +    uint8_t *buf;
> > +    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].data;
> > +
> > +    for (i = 0; i < 16; i++) {
> > +        mask = 1 << (15 - i);
> > +        if (val & mask) {
> > +            artist_rop8(s, buf + offset + i, color);
> > +        } else {
> > +            if (!(s->image_bitmap_op & 0x20000000)) {
> > +                artist_rop8(s, buf + offset + i, s->bg_color);
> > +            }
> > +        }
> > +    }
> > +}
> > +
> > +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
> 
> Are the values being written as little-endian or big-endian? You should be able to
> replace them with either st{w,l}_le_p() or st{w,l}_be_p().
>

Can't say. For 32 bit accesses it's certainly Big Endian (which would
make sense as HPPA is big endian). However, for 8 or 16 bit accesses i
need to invert the address lines.

> > +    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:
> > +        combine_write_reg(addr, val, size, &s->cursor_pos);
> > +        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
> 
> Same comment as for combine_write_reg() above.
> 
> > +    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;
> > +
> > +    trace_artist_vram_write(size, addr, val);
> > +
> > +    if (s->cmap_bm_access) {
> > +        buf = &s->vram_buffer[ARTIST_BUFFER_CMAP];
> > +        *(uint32_t *)(buf->data + addr) = val;
> > +        return;
> > +    }
> > +
> > +    buf = vram_write_buffer(s);
> > +    if (!buf->size) {
> > +        return;
> > +    }
> > +
> > +    switch (size) {
> > +    case 4:
> > +        *(uint32_t *)(buf->data + posy * buf->width + posx) = be32_to_cpu(val);
> > +        break;
> > +    case 2:
> > +        *(uint16_t *)(buf->data + posy * buf->width + posx) = be16_to_cpu(val);
> > +        break;
> > +    case 1:
> > +        *(uint8_t *)(buf->data + posy * buf->width + posx) = val;
> > +        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;
> > +    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,
> > +    },
> > +};
> 
> ...however you are using combine_read_reg() and combine_write_reg() to handle the
> host-endian swaps within artist_reg_read() and artist_reg_write(). Could you not just
> set the .endianness above to DEVICE_LITTLE_ENDIAN/DEVICE_BIG_ENDIAN as appropriate to
> do the conversion for you?
> 

See my comment above. Additionally, Colormap is LE, while Register access seems
to be Big Endian.

> > +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 const GraphicHwOps artist_ops = {
> > +    .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",
> > +            0x400000);
> > +    memory_region_init_io(&s->vram_mem, obj, &artist_vram_ops, s, "artist.vram",
> > +            0x800000);
> > +    sysbus_init_mmio(sbd, &s->reg);
> > +    sysbus_init_mmio(sbd, &s->vram_mem);
> > +}
> > +
> > +static void artist_set_buffer(ARTISTState *s, uint8_t **vram, unsigned int idx,
> > +                              int width, int height)
> > +{
> > +    struct vram_buffer *buf = s->vram_buffer + idx;
> > +
> > +    buf->data = *vram;
> > +    buf->size = height * width;
> > +    buf->width = width;
> > +    buf->height = height;
> > +    *vram = *vram + buf->size;
> > +}
> > +
> > +static void artist_realizefn(DeviceState *dev, Error **errp)
> > +{
> > +    uint8_t *vram;
> > +
> > +    ARTISTState *s = ARTIST(dev);
> > +
> > +    vram = g_malloc0(4 * 1048576);
> 
> You may find it more readable to use (4 * MiB) here.
> 

Ok.

> > +    s->vram = vram;
> > +    artist_set_buffer(s, &vram, ARTIST_BUFFER_CMAP, 2048, 4);
> > +    artist_set_buffer(s, &vram, ARTIST_BUFFER_AP, s->width, s->height);
> > +    artist_set_buffer(s, &vram, ARTIST_BUFFER_CURSOR1, 64, 64);
> > +    artist_set_buffer(s, &vram, ARTIST_BUFFER_CURSOR2, 64, 64);
> > +    artist_set_buffer(s, &vram, ARTIST_BUFFER_ATTRIBUTE, 64, 64);
> > +
> > +    /*
> > +     * 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)
> > +{
> > +    return 0;
> > +}
> 
> Is there some code missing here? Looking at a few of the other framebuffers they tend
> to force a full display redraw during post_load.
> 

If there's no dirty bitmap i would expect that the display get always
updated, so i don't think we need to do anything here. Likely the whole
function can be dropped?

> > +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_class_init(ObjectClass *klass, void *data)
> > +{
> > +    DeviceClass *dc = DEVICE_CLASS(klass);
> > +
> > +    dc->realize = artist_realizefn;
> > +    dc->vmsd = &vmstate_artist;
> > +    dc->props = artist_properties;
> > +}
> > +
> > +static const TypeInfo artist_info = {
> > +    .name          = TYPE_ARTIST,
> > +    .parent        = TYPE_SYS_BUS_DEVICE,
> > +    .instance_size = sizeof(ARTISTState),
> > +    .instance_init = artist_initfn,
> > +    .class_init    = artist_class_init,
> > +};
> > +
> > +static void artist_register_types(void)
> > +{
> > +    type_register_static(&artist_info);
> > +}
> > +
> > +type_init(artist_register_types)
> > diff --git a/hw/display/trace-events b/hw/display/trace-events
> > index ba7787b180..e6e22bef88 100644
> > --- a/hw/display/trace-events
> > +++ b/hw/display/trace-events
> > @@ -142,3 +142,12 @@ sii9022_switch_mode(const char *mode) "mode: %s"
> >  # ati.c
> >  ati_mm_read(unsigned int size, uint64_t addr, const char *name, uint64_t val) "%u 0x%"PRIx64 " %s -> 0x%"PRIx64
> >  ati_mm_write(unsigned int size, uint64_t addr, const char *name, uint64_t val) "%u 0x%"PRIx64 " %s <- 0x%"PRIx64
> > +
> > +# artist.c
> > +artist_reg_read(unsigned int size, uint64_t addr, const char *name, uint64_t val) "%u 0x%"PRIx64 "%s -> 0x%"PRIx64
> > +artist_reg_write(unsigned int size, uint64_t addr, const char *name, uint64_t val) "%u 0x%"PRIx64 "%s <- 0x%"PRIx64
> > +artist_vram_read(unsigned int size, uint64_t addr, int posx, int posy, uint64_t val) "%u 0x%"PRIx64 " %ux%u-> 0x%"PRIx64
> > +artist_vram_write(unsigned int size, uint64_t addr, uint64_t val) "%u 0x%"PRIx64 " <- 0x%"PRIx64
> > +artist_fill_window(unsigned int start_x, unsigned int start_y, unsigned int width, unsigned int height, uint32_t op, uint32_t ctlpln) "start=%ux%u length=%ux%u op=0x%08x ctlpln=0x%08x"
> > +artist_block_move(unsigned int start_x, unsigned int start_y, unsigned int dest_x, unsigned int dest_y, unsigned int width, unsigned int height) "source %ux%u -> dest %ux%u size %ux%u"
> > +artist_draw_line(unsigned int start_x, unsigned int start_y, unsigned int end_x, unsigned int end_y) "%ux%u %ux%u"
> > diff --git a/hw/hppa/Kconfig b/hw/hppa/Kconfig
> > index 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 65fc20ebed..39bd5f1834 100644
> > --- a/hw/hppa/machine.c
> > +++ b/hw/hppa/machine.c
> > @@ -21,6 +21,7 @@
> >  #include "qemu/units.h"
> >  #include "qapi/error.h"
> >  #include "qemu/log.h"
> > +#include "hw/usb.h"
> 
> This line looks like it belongs to a different patch?

Umm, yes. This was from when i started to add Artist emulation and had
no PS/2 emulation for HPPA. So i just plugged in a USB Keyboard :-)

> >  #define MAX_IDE_BUS 2
> >  
> > @@ -74,6 +75,7 @@ static void machine_hppa_init(MachineState *machine)
> >      MemoryRegion *cpu_region;
> >      long i;
> >      unsigned int smp_cpus = machine->smp.cpus;
> > +    SysBusDevice *s;
> >  
> >      ram_size = machine->ram_size;
> >  
> > @@ -126,6 +128,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()) {
> > 
> 
> 
> ATB,
> 
> Mark.
> 


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

* Re: [PATCH v3 5/6] hppa: Add emulation of Artist graphics
  2019-10-25  9:31     ` Sven Schnelle
@ 2019-10-26  9:35       ` Mark Cave-Ayland
  2019-10-26 17:54         ` Sven Schnelle
  0 siblings, 1 reply; 17+ messages in thread
From: Mark Cave-Ayland @ 2019-10-26  9:35 UTC (permalink / raw)
  To: Sven Schnelle; +Cc: Helge Deller, qemu-devel, Richard Henderson

On 25/10/2019 10:31, Sven Schnelle wrote:

> Hi Mark,
> 
> On Thu, Oct 24, 2019 at 09:51:31PM +0100, Mark Cave-Ayland wrote:
> 
>> On 22/10/2019 21:59, 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       |    3 +
>>>  hw/display/Makefile.objs |    1 +
>>>  hw/display/artist.c      | 1336 ++++++++++++++++++++++++++++++++++++++
>>>  hw/display/trace-events  |    9 +
>>>  hw/hppa/Kconfig          |    1 +
>>>  hw/hppa/hppa_hardware.h  |    1 +
>>>  hw/hppa/machine.c        |   10 +
>>>  7 files changed, 1361 insertions(+)
>>>  create mode 100644 hw/display/artist.c
>>>
>>> diff --git a/hw/display/Kconfig b/hw/display/Kconfig
>>> index cbdf7b1a67..953631afb6 100644
>>> --- a/hw/display/Kconfig
>>> +++ b/hw/display/Kconfig
>>> @@ -91,6 +91,9 @@ config TCX
>>>  config CG3
>>>      bool
>>>  
>>> +config ARTIST
>>> +    bool
>>> +
>>>  config VGA
>>>      bool
>>>  
>>> diff --git a/hw/display/Makefile.objs b/hw/display/Makefile.objs
>>> index 5a4066383b..5f63294149 100644
>>> --- a/hw/display/Makefile.objs
>>> +++ b/hw/display/Makefile.objs
>>> @@ -39,6 +39,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..9b285b3993
>>> --- /dev/null
>>> +++ b/hw/display/artist.c
>>> @@ -0,0 +1,1336 @@
>>> +/*
>>> + * 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 "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"
>>> +
>>> +#define TYPE_ARTIST "artist"
>>> +#define ARTIST(obj) OBJECT_CHECK(ARTISTState, (obj), TYPE_ARTIST)
>>> +
>>> +struct vram_buffer {
>>> +    uint8_t *data;
>>> +    int size;
>>> +    int width;
>>> +    int height;
>>> +};
>>> +
>>> +typedef struct ARTISTState {
>>> +    SysBusDevice parent_obj;
>>> +
>>> +    QemuConsole *con;
>>> +    MemoryRegion vram_mem;
>>> +    MemoryRegion reg;
>>> +    uint8_t *vram;
>>> +
>>> +    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_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];
>>> +
>>> +    /*
>>> +     * 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.
>>> +     */
>>> +    cursor_pos_y = 0x47a - artist_get_y(s->cursor_pos);
>>> +    cursor_pos_x = ((artist_get_x(s->cursor_pos) - 338) / 2);
>>> +
>>> +    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_update_display(void *opaque)
>>> +{
>>> +    ARTISTState *s = opaque;
>>> +    DisplaySurface *surface = qemu_console_surface(s->con);
>>> +    const uint8_t *pix;
>>> +    uint32_t *data, *cmap;
>>> +    int x, y;
>>> +
>>> +    if (surface_bits_per_pixel(surface) != 32) {
>>> +        return;
>>> +    }
>>
>> DisplaySurfaces are always 32-bit in QEMU these days, and so you shouldn't need this
>> check.
>>
> 
> Ok.
> 
>>> +    pix = s->vram_buffer[ARTIST_BUFFER_AP].data;
>>> +    cmap = (uint32_t *)(s->vram_buffer[ARTIST_BUFFER_CMAP].data + 0x400);
>>> +    data = (uint32_t *)surface_data(surface);
>>> +
>>> +    for (y = 0; y < s->height; y++) {
>>> +        for (x = 0; x < s->width; x++) {
>>> +            *data++ = cmap[*pix++];
>>> +        }
>>> +    }
>>> +    artist_draw_cursor(s);
>>> +    dpy_gfx_update(s->con, 0, 0, s->width, s->height);
>>> +
>>> +}
>>
>> It looks like artist_update_display() is based upon older code which doesn't use
>> memory_region_snapshot_and_clear_dirty() to ensure that you don't get display
>> tearing. See for example commit fec5e8c92b "vga: make display updates thread safe."
>> although if you look at the latest code you'll see that vga_sync_dirty_bitmap() is no
>> longer required.
>>
> 
> That was already proposed on IRC, and i tried to go via Memory Region Aliases.
> However, the VRAM in Artist is not really exposed to the Host. Instead,
> there's the Chipset inbetween that can do byte swapping (Colormap is LE,
> VRAM is BE) and Bit-to-Byte/Word/Dword conversion. For example you could
> write 0x55 into that VRAM region, and the chipset would expand that to
> VRAM Bytes: 00 01 00 01 00 01 00 01. And to make it even worse emulation
> wise it can also do different encodings for Read or Write accesses, and
> mask out certain bits of the data. So after trying to convert it to the
> "dirty bitmap" API i decided to just leave it as it is. The CPU load
> used by the display update code is usually < 1%, so it's ok for me.

Wow that sounds that some interesting hardware(!). Does it make sense to model the
behaviour of the chipset separately using a proxy MemoryRegion similar to virtio i.e.
introduce an intermediate IO MemoryRegion that does the swapping and then forward it
onto the VRAM MemoryRegion?

>>> +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 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;
>>> +        }
>>> +        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))) {
>>> +#ifdef HOST_WORDS_BIGENDIAN
>>> +                artist_rop8(s, p + offset + 3 - i, data8[3 - i]);
>>> +#else
>>> +                artist_rop8(s, p + offset + 3 - i, data8[i]);
>>> +#endif
>>
>> I tend to find it more readable to create a macro outside of the function to handle
>> the endian swap e.g.
>>
>> #ifdef HOST_WORDS_BIGENDIAN
>> #define ROP8OFF (3 - i)
>> #else
>> #define ROP8OFF (i)
>> #endif
>>
>> and then replace this with just:
>>
>>     artist_rop8(s, p + offset + 3 - i, data8[ROP8OFF(i)]);
>>
> 
> Ok.
> 
>>> +            }
>>> +        }
>>> +        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);
>>> +                    }
>>> +                }
>>> +            }
>>> +        }
>>> +        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;
>>> +        }
>>> +    }
>>> +}
>>> +
>>> +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) {
>>> +        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) {
>>> +            artist_rop8(s, buf->data + dst + column, buf->data[src + column]);
>>> +        }
>>> +    }
>>> +}
>>> +
>>> +static void fill_window(ARTISTState *s, int startx, int starty,
>>> +                        int width, int height)
>>> +{
>>> +    uint32_t offset;
>>> +    uint8_t color = artist_get_color(s);
>>> +    uint8_t *buf;
>>> +    int x, y;
>>> +
>>> +    trace_artist_fill_window(startx, starty, width, height,
>>> +        s->image_bitmap_op, s->control_plane);
>>> +
>>> +    if (s->control_plane != 0) {
>>> +        qemu_log_mask(LOG_UNIMP, "%s: CONTROL_PLANE: %08x\n", __func__,
>>> +            s->control_plane);
>>> +        return;
>>> +    }
>>> +
>>> +    if (s->reg_100080 == 0x7d) {
>>> +        height = artist_get_y(s->blockmove_size);
>>> +        s->vram_start += height;
>>> +    }
>>> +
>>> +    buf = s->vram_buffer[ARTIST_BUFFER_AP].data;
>>> +
>>> +    for (y = starty; y < starty + height; y++) {
>>> +        offset = y * s->width;
>>> +
>>> +        for (x = startx; x < startx + width; x++) {
>>> +            artist_rop8(s, buf + offset + x, color);
>>> +        }
>>> +    }
>>> +}
>>> +
>>> +static void draw_line(ARTISTState *s, int x1, int y1, int x2, int y2,
>>> +        bool update_start, int skip_pix, int max_pix)
>>> +{
>>> +    uint8_t color = artist_get_color(s);
>>> +    int dx, dy, t, e, x, y, incy, diago, horiz;
>>> +    bool c1;
>>> +    uint8_t *buf, *p;
>>> +
>>> +
>>> +    if (update_start) {
>>> +        s->vram_start = (x2 << 16) | y2;
>>> +    }
>>> +
>>> +    buf = s->vram_buffer[ARTIST_BUFFER_AP].data;
>>> +
>>> +    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 + x * s->width + y;
>>> +       } else {
>>> +            p = buf + y * s->width + x;
>>> +       }
>>> +
>>> +        if (skip_pix > 0) {
>>> +            skip_pix--;
>>> +        } else {
>>> +            artist_rop8(s, p, color);
>>> +        }
>>> +
>>> +        if (e > 0) {
>>> +            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)
>>> +{
>>> +    uint32_t color = (s->image_bitmap_op & 2) ? s->fg_color : s->bg_color;
>>> +    uint8_t *buf;
>>> +    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].data;
>>> +
>>> +    for (i = 0; i < 16; i++) {
>>> +        mask = 1 << (15 - i);
>>> +        if (val & mask) {
>>> +            artist_rop8(s, buf + offset + i, color);
>>> +        } else {
>>> +            if (!(s->image_bitmap_op & 0x20000000)) {
>>> +                artist_rop8(s, buf + offset + i, s->bg_color);
>>> +            }
>>> +        }
>>> +    }
>>> +}
>>> +
>>> +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
>>
>> Are the values being written as little-endian or big-endian? You should be able to
>> replace them with either st{w,l}_le_p() or st{w,l}_be_p().
>>
> 
> Can't say. For 32 bit accesses it's certainly Big Endian (which would
> make sense as HPPA is big endian). However, for 8 or 16 bit accesses i
> need to invert the address lines.
> 
>>> +    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:
>>> +        combine_write_reg(addr, val, size, &s->cursor_pos);
>>> +        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
>>
>> Same comment as for combine_write_reg() above.
>>
>>> +    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;
>>> +
>>> +    trace_artist_vram_write(size, addr, val);
>>> +
>>> +    if (s->cmap_bm_access) {
>>> +        buf = &s->vram_buffer[ARTIST_BUFFER_CMAP];
>>> +        *(uint32_t *)(buf->data + addr) = val;
>>> +        return;
>>> +    }
>>> +
>>> +    buf = vram_write_buffer(s);
>>> +    if (!buf->size) {
>>> +        return;
>>> +    }
>>> +
>>> +    switch (size) {
>>> +    case 4:
>>> +        *(uint32_t *)(buf->data + posy * buf->width + posx) = be32_to_cpu(val);
>>> +        break;
>>> +    case 2:
>>> +        *(uint16_t *)(buf->data + posy * buf->width + posx) = be16_to_cpu(val);
>>> +        break;
>>> +    case 1:
>>> +        *(uint8_t *)(buf->data + posy * buf->width + posx) = val;
>>> +        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;
>>> +    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,
>>> +    },
>>> +};
>>
>> ...however you are using combine_read_reg() and combine_write_reg() to handle the
>> host-endian swaps within artist_reg_read() and artist_reg_write(). Could you not just
>> set the .endianness above to DEVICE_LITTLE_ENDIAN/DEVICE_BIG_ENDIAN as appropriate to
>> do the conversion for you?

This is for the artist_reg_ops() rather than the VRAM though? Or is there really
mixed endian access to the card registers?

> See my comment above. Additionally, Colormap is LE, while Register access seems
> to be Big Endian.

How does the real hardware determine the source of the access, and therefore its
endian? Again it feels like this complexity related to endianness is coming from
missing something in how the chipset is managing the two different types of access.

>>> +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 const GraphicHwOps artist_ops = {
>>> +    .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",
>>> +            0x400000);
>>> +    memory_region_init_io(&s->vram_mem, obj, &artist_vram_ops, s, "artist.vram",
>>> +            0x800000);
>>> +    sysbus_init_mmio(sbd, &s->reg);
>>> +    sysbus_init_mmio(sbd, &s->vram_mem);
>>> +}
>>> +
>>> +static void artist_set_buffer(ARTISTState *s, uint8_t **vram, unsigned int idx,
>>> +                              int width, int height)
>>> +{
>>> +    struct vram_buffer *buf = s->vram_buffer + idx;
>>> +
>>> +    buf->data = *vram;
>>> +    buf->size = height * width;
>>> +    buf->width = width;
>>> +    buf->height = height;
>>> +    *vram = *vram + buf->size;
>>> +}
>>> +
>>> +static void artist_realizefn(DeviceState *dev, Error **errp)
>>> +{
>>> +    uint8_t *vram;
>>> +
>>> +    ARTISTState *s = ARTIST(dev);
>>> +
>>> +    vram = g_malloc0(4 * 1048576);
>>
>> You may find it more readable to use (4 * MiB) here.
>>
> 
> Ok.
> 
>>> +    s->vram = vram;
>>> +    artist_set_buffer(s, &vram, ARTIST_BUFFER_CMAP, 2048, 4);
>>> +    artist_set_buffer(s, &vram, ARTIST_BUFFER_AP, s->width, s->height);
>>> +    artist_set_buffer(s, &vram, ARTIST_BUFFER_CURSOR1, 64, 64);
>>> +    artist_set_buffer(s, &vram, ARTIST_BUFFER_CURSOR2, 64, 64);
>>> +    artist_set_buffer(s, &vram, ARTIST_BUFFER_ATTRIBUTE, 64, 64);
>>> +
>>> +    /*
>>> +     * 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)
>>> +{
>>> +    return 0;
>>> +}
>>
>> Is there some code missing here? Looking at a few of the other framebuffers they tend
>> to force a full display redraw during post_load.
>>
> 
> If there's no dirty bitmap i would expect that the display get always
> updated, so i don't think we need to do anything here. Likely the whole
> function can be dropped?

Possibly - I'm afraid I don't know exactly the details, although some framebuffers do
have a separate .invalidate function in GraphicHwOps which appears to do something
similar.

>>> +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_class_init(ObjectClass *klass, void *data)
>>> +{
>>> +    DeviceClass *dc = DEVICE_CLASS(klass);
>>> +
>>> +    dc->realize = artist_realizefn;
>>> +    dc->vmsd = &vmstate_artist;
>>> +    dc->props = artist_properties;
>>> +}
>>> +
>>> +static const TypeInfo artist_info = {
>>> +    .name          = TYPE_ARTIST,
>>> +    .parent        = TYPE_SYS_BUS_DEVICE,
>>> +    .instance_size = sizeof(ARTISTState),
>>> +    .instance_init = artist_initfn,
>>> +    .class_init    = artist_class_init,
>>> +};
>>> +
>>> +static void artist_register_types(void)
>>> +{
>>> +    type_register_static(&artist_info);
>>> +}
>>> +
>>> +type_init(artist_register_types)
>>> diff --git a/hw/display/trace-events b/hw/display/trace-events
>>> index ba7787b180..e6e22bef88 100644
>>> --- a/hw/display/trace-events
>>> +++ b/hw/display/trace-events
>>> @@ -142,3 +142,12 @@ sii9022_switch_mode(const char *mode) "mode: %s"
>>>  # ati.c
>>>  ati_mm_read(unsigned int size, uint64_t addr, const char *name, uint64_t val) "%u 0x%"PRIx64 " %s -> 0x%"PRIx64
>>>  ati_mm_write(unsigned int size, uint64_t addr, const char *name, uint64_t val) "%u 0x%"PRIx64 " %s <- 0x%"PRIx64
>>> +
>>> +# artist.c
>>> +artist_reg_read(unsigned int size, uint64_t addr, const char *name, uint64_t val) "%u 0x%"PRIx64 "%s -> 0x%"PRIx64
>>> +artist_reg_write(unsigned int size, uint64_t addr, const char *name, uint64_t val) "%u 0x%"PRIx64 "%s <- 0x%"PRIx64
>>> +artist_vram_read(unsigned int size, uint64_t addr, int posx, int posy, uint64_t val) "%u 0x%"PRIx64 " %ux%u-> 0x%"PRIx64
>>> +artist_vram_write(unsigned int size, uint64_t addr, uint64_t val) "%u 0x%"PRIx64 " <- 0x%"PRIx64
>>> +artist_fill_window(unsigned int start_x, unsigned int start_y, unsigned int width, unsigned int height, uint32_t op, uint32_t ctlpln) "start=%ux%u length=%ux%u op=0x%08x ctlpln=0x%08x"
>>> +artist_block_move(unsigned int start_x, unsigned int start_y, unsigned int dest_x, unsigned int dest_y, unsigned int width, unsigned int height) "source %ux%u -> dest %ux%u size %ux%u"
>>> +artist_draw_line(unsigned int start_x, unsigned int start_y, unsigned int end_x, unsigned int end_y) "%ux%u %ux%u"
>>> diff --git a/hw/hppa/Kconfig b/hw/hppa/Kconfig
>>> index 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 65fc20ebed..39bd5f1834 100644
>>> --- a/hw/hppa/machine.c
>>> +++ b/hw/hppa/machine.c
>>> @@ -21,6 +21,7 @@
>>>  #include "qemu/units.h"
>>>  #include "qapi/error.h"
>>>  #include "qemu/log.h"
>>> +#include "hw/usb.h"
>>
>> This line looks like it belongs to a different patch?
> 
> Umm, yes. This was from when i started to add Artist emulation and had
> no PS/2 emulation for HPPA. So i just plugged in a USB Keyboard :-)
> 
>>>  #define MAX_IDE_BUS 2
>>>  
>>> @@ -74,6 +75,7 @@ static void machine_hppa_init(MachineState *machine)
>>>      MemoryRegion *cpu_region;
>>>      long i;
>>>      unsigned int smp_cpus = machine->smp.cpus;
>>> +    SysBusDevice *s;
>>>  
>>>      ram_size = machine->ram_size;
>>>  
>>> @@ -126,6 +128,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()) {


ATB,

Mark.


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

* Re: [PATCH v3 5/6] hppa: Add emulation of Artist graphics
  2019-10-22 20:59 ` [PATCH v3 5/6] hppa: Add emulation of Artist graphics Sven Schnelle
  2019-10-24 20:51   ` Mark Cave-Ayland
@ 2019-10-26 11:43   ` Philippe Mathieu-Daudé
  1 sibling, 0 replies; 17+ messages in thread
From: Philippe Mathieu-Daudé @ 2019-10-26 11:43 UTC (permalink / raw)
  To: Sven Schnelle, Richard Henderson; +Cc: Helge Deller, qemu-devel

Hi Sven,

On 10/22/19 10:59 PM, 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       |    3 +
>   hw/display/Makefile.objs |    1 +
>   hw/display/artist.c      | 1336 ++++++++++++++++++++++++++++++++++++++
>   hw/display/trace-events  |    9 +
>   hw/hppa/Kconfig          |    1 +
>   hw/hppa/hppa_hardware.h  |    1 +
>   hw/hppa/machine.c        |   10 +
>   7 files changed, 1361 insertions(+)
>   create mode 100644 hw/display/artist.c
> 
[...]
> +static void fill_window(ARTISTState *s, int startx, int starty,
> +                        int width, int height)
> +{
> +    uint32_t offset;
> +    uint8_t color = artist_get_color(s);
> +    uint8_t *buf;
> +    int x, y;
> +
> +    trace_artist_fill_window(startx, starty, width, height,
> +        s->image_bitmap_op, s->control_plane);
> +
> +    if (s->control_plane != 0) {
> +        qemu_log_mask(LOG_UNIMP, "%s: CONTROL_PLANE: %08x\n", __func__,
> +            s->control_plane);
> +        return;
> +    }
> +
> +    if (s->reg_100080 == 0x7d) {

What is checked here? Can you add a comment about it?

> +        height = artist_get_y(s->blockmove_size);
> +        s->vram_start += height;
> +    }
> +
> +    buf = s->vram_buffer[ARTIST_BUFFER_AP].data;
> +
> +    for (y = starty; y < starty + height; y++) {
> +        offset = y * s->width;
> +
> +        for (x = startx; x < startx + width; x++) {
> +            artist_rop8(s, buf + offset + x, color);
> +        }
> +    }
> +}
> +
[...]
> +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",
> +            0x400000);

Easier to read as: 4 * MiB

> +    memory_region_init_io(&s->vram_mem, obj, &artist_vram_ops, s, "artist.vram",
> +            0x800000);

And 8 * MiB.

> +    sysbus_init_mmio(sbd, &s->reg);
> +    sysbus_init_mmio(sbd, &s->vram_mem);
> +}
> +
> +static void artist_set_buffer(ARTISTState *s, uint8_t **vram, unsigned int idx,
> +                              int width, int height)
> +{
> +    struct vram_buffer *buf = s->vram_buffer + idx;
> +
> +    buf->data = *vram;
> +    buf->size = height * width;
> +    buf->width = width;
> +    buf->height = height;
> +    *vram = *vram + buf->size;
> +}
> +
> +static void artist_realizefn(DeviceState *dev, Error **errp)
> +{
> +    uint8_t *vram;
> +
> +    ARTISTState *s = ARTIST(dev);
> +
> +    vram = g_malloc0(4 * 1048576);

Here you can simply use g_malloc(4 * MiB). If you really need
to bzero the VRAM, that should be done in the reset() handler.

> +    s->vram = vram;
> +    artist_set_buffer(s, &vram, ARTIST_BUFFER_CMAP, 2048, 4);
> +    artist_set_buffer(s, &vram, ARTIST_BUFFER_AP, s->width, s->height);
> +    artist_set_buffer(s, &vram, ARTIST_BUFFER_CURSOR1, 64, 64);
> +    artist_set_buffer(s, &vram, ARTIST_BUFFER_CURSOR2, 64, 64);
> +    artist_set_buffer(s, &vram, ARTIST_BUFFER_ATTRIBUTE, 64, 64);

Shouldn't this be done by firmware code? If no firmware, this seems to
belong to reset() too, isn't it?

> +
> +    /*
> +     * 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);
> +}
[...]



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

* Re: [PATCH v3 5/6] hppa: Add emulation of Artist graphics
  2019-10-26  9:35       ` Mark Cave-Ayland
@ 2019-10-26 17:54         ` Sven Schnelle
  2019-11-01 21:59           ` Sven Schnelle
  0 siblings, 1 reply; 17+ messages in thread
From: Sven Schnelle @ 2019-10-26 17:54 UTC (permalink / raw)
  To: Mark Cave-Ayland; +Cc: Helge Deller, qemu-devel, Richard Henderson

Hi Mark,

On Sat, Oct 26, 2019 at 10:35:20AM +0100, Mark Cave-Ayland wrote:

> > However, the VRAM in Artist is not really exposed to the Host. Instead,
> > there's the Chipset inbetween that can do byte swapping (Colormap is LE,
> > VRAM is BE) and Bit-to-Byte/Word/Dword conversion. For example you could
> > write 0x55 into that VRAM region, and the chipset would expand that to
> > VRAM Bytes: 00 01 00 01 00 01 00 01. And to make it even worse emulation
> > wise it can also do different encodings for Read or Write accesses, and
> > mask out certain bits of the data. So after trying to convert it to the
> > "dirty bitmap" API i decided to just leave it as it is. The CPU load
> > used by the display update code is usually < 1%, so it's ok for me.
> 
> Wow that sounds that some interesting hardware(!). Does it make sense to model the
> behaviour of the chipset separately using a proxy MemoryRegion similar to virtio i.e.
> introduce an intermediate IO MemoryRegion that does the swapping and then forward it
> onto the VRAM MemoryRegion?

Thanks for the pointer, i'll check whether that would work. For now i
think i'll remove the Artist patch from the series, so we can apply the
other patches, and i'll re-submit Artist when it's done. I guess the
rewrite to use a MemRegion is a bit bigger. But i would to get the other
patches in especially the LASI Stuff as both Helge and i have a lot of
stuff depending on that.

Regards
Sven


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

* Re: [PATCH v3 5/6] hppa: Add emulation of Artist graphics
  2019-10-26 17:54         ` Sven Schnelle
@ 2019-11-01 21:59           ` Sven Schnelle
  2019-11-03 20:56             ` Mark Cave-Ayland
  0 siblings, 1 reply; 17+ messages in thread
From: Sven Schnelle @ 2019-11-01 21:59 UTC (permalink / raw)
  To: Mark Cave-Ayland; +Cc: Helge Deller, qemu-devel, Richard Henderson

Hi Mark,

On Sat, Oct 26, 2019 at 07:54:40PM +0200, Sven Schnelle wrote:
> Hi Mark,
> 
> On Sat, Oct 26, 2019 at 10:35:20AM +0100, Mark Cave-Ayland wrote:
> 
> > > However, the VRAM in Artist is not really exposed to the Host. Instead,
> > > there's the Chipset inbetween that can do byte swapping (Colormap is LE,
> > > VRAM is BE) and Bit-to-Byte/Word/Dword conversion. For example you could
> > > write 0x55 into that VRAM region, and the chipset would expand that to
> > > VRAM Bytes: 00 01 00 01 00 01 00 01. And to make it even worse emulation
> > > wise it can also do different encodings for Read or Write accesses, and
> > > mask out certain bits of the data. So after trying to convert it to the
> > > "dirty bitmap" API i decided to just leave it as it is. The CPU load
> > > used by the display update code is usually < 1%, so it's ok for me.
> > 
> > Wow that sounds that some interesting hardware(!). Does it make sense to model the
> > behaviour of the chipset separately using a proxy MemoryRegion similar to virtio i.e.
> > introduce an intermediate IO MemoryRegion that does the swapping and then forward it
> > onto the VRAM MemoryRegion?
> 
> Thanks for the pointer, i'll check whether that would work. For now i
> think i'll remove the Artist patch from the series, so we can apply the
> other patches, and i'll re-submit Artist when it's done. I guess the
> rewrite to use a MemRegion is a bit bigger. But i would to get the other
> patches in especially the LASI Stuff as both Helge and i have a lot of
> stuff depending on that.

I've looked into it again and changed my mind. There are at least the following
functions that the Artist chip does before a Read/Write is passed to/from VRAM:

- endianess conversion (actually configurable via some register, but i don't
  know how and hardwired it depending on CMAP / FB access)

- The Address passed on the System bus are the X/Y coordinates added to the FB
  base address in the selected buffer instead of the VRAM offset for pixel data.
  I think that's configurable via the some registers, but i don't know how.
  Unfortunately there's absolutely no documentation about Artist available and
  everything was developed by reverse engineering.

- bitmap to Byte/Word conversion (not implemented yet for the VRAM window, only
  for the I/O register window)

So in my opinion it's way to much effort to squeeze all of that into the memory
space, and it is not really a Memory range that's just behind a bus bridge.

Regards
Sven


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

* Re: [PATCH v3 5/6] hppa: Add emulation of Artist graphics
  2019-11-01 21:59           ` Sven Schnelle
@ 2019-11-03 20:56             ` Mark Cave-Ayland
  2019-11-03 21:03               ` Sven Schnelle
  0 siblings, 1 reply; 17+ messages in thread
From: Mark Cave-Ayland @ 2019-11-03 20:56 UTC (permalink / raw)
  To: Sven Schnelle; +Cc: Helge Deller, qemu-devel, Richard Henderson

On 01/11/2019 21:59, Sven Schnelle wrote:

> On Sat, Oct 26, 2019 at 07:54:40PM +0200, Sven Schnelle wrote:
>> Hi Mark,
>>
>> On Sat, Oct 26, 2019 at 10:35:20AM +0100, Mark Cave-Ayland wrote:
>>
>>>> However, the VRAM in Artist is not really exposed to the Host. Instead,
>>>> there's the Chipset inbetween that can do byte swapping (Colormap is LE,
>>>> VRAM is BE) and Bit-to-Byte/Word/Dword conversion. For example you could
>>>> write 0x55 into that VRAM region, and the chipset would expand that to
>>>> VRAM Bytes: 00 01 00 01 00 01 00 01. And to make it even worse emulation
>>>> wise it can also do different encodings for Read or Write accesses, and
>>>> mask out certain bits of the data. So after trying to convert it to the
>>>> "dirty bitmap" API i decided to just leave it as it is. The CPU load
>>>> used by the display update code is usually < 1%, so it's ok for me.
>>>
>>> Wow that sounds that some interesting hardware(!). Does it make sense to model the
>>> behaviour of the chipset separately using a proxy MemoryRegion similar to virtio i.e.
>>> introduce an intermediate IO MemoryRegion that does the swapping and then forward it
>>> onto the VRAM MemoryRegion?
>>
>> Thanks for the pointer, i'll check whether that would work. For now i
>> think i'll remove the Artist patch from the series, so we can apply the
>> other patches, and i'll re-submit Artist when it's done. I guess the
>> rewrite to use a MemRegion is a bit bigger. But i would to get the other
>> patches in especially the LASI Stuff as both Helge and i have a lot of
>> stuff depending on that.
> 
> I've looked into it again and changed my mind. There are at least the following
> functions that the Artist chip does before a Read/Write is passed to/from VRAM:
> 
> - endianess conversion (actually configurable via some register, but i don't
>   know how and hardwired it depending on CMAP / FB access)
> 
> - The Address passed on the System bus are the X/Y coordinates added to the FB
>   base address in the selected buffer instead of the VRAM offset for pixel data.
>   I think that's configurable via the some registers, but i don't know how.
>   Unfortunately there's absolutely no documentation about Artist available and
>   everything was developed by reverse engineering.
> 
> - bitmap to Byte/Word conversion (not implemented yet for the VRAM window, only
>   for the I/O register window)
> 
> So in my opinion it's way to much effort to squeeze all of that into the memory
> space, and it is not really a Memory range that's just behind a bus bridge.

Hi Sven,

Certainly in some cases it isn't possible to model devices in QEMU exactly as real
hardware, although I think that some of the ideas above could be used to improve the
implementation without too much extra effort.

Then again from my work on QEMU I completely understand that sometimes this can be
difficult with older, more esoteric devices. Ultimately after review that decision
has to come from the maintainer(s) for the relevant devices/machines, so I guess that
would be Richard and/or Gerd in this case?


ATB,

Mark.


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

* Re: [PATCH v3 5/6] hppa: Add emulation of Artist graphics
  2019-11-03 20:56             ` Mark Cave-Ayland
@ 2019-11-03 21:03               ` Sven Schnelle
  0 siblings, 0 replies; 17+ messages in thread
From: Sven Schnelle @ 2019-11-03 21:03 UTC (permalink / raw)
  To: Mark Cave-Ayland; +Cc: Helge Deller, qemu-devel, Richard Henderson

Hi Mark,

On Sun, Nov 03, 2019 at 08:56:43PM +0000, Mark Cave-Ayland wrote:
> On 01/11/2019 21:59, Sven Schnelle wrote:
> 
> > On Sat, Oct 26, 2019 at 07:54:40PM +0200, Sven Schnelle wrote:
> >> Hi Mark,
> >>
> >> On Sat, Oct 26, 2019 at 10:35:20AM +0100, Mark Cave-Ayland wrote:
> >>
> >>>> However, the VRAM in Artist is not really exposed to the Host. Instead,
> >>>> there's the Chipset inbetween that can do byte swapping (Colormap is LE,
> >>>> VRAM is BE) and Bit-to-Byte/Word/Dword conversion. For example you could
> >>>> write 0x55 into that VRAM region, and the chipset would expand that to
> >>>> VRAM Bytes: 00 01 00 01 00 01 00 01. And to make it even worse emulation
> >>>> wise it can also do different encodings for Read or Write accesses, and
> >>>> mask out certain bits of the data. So after trying to convert it to the
> >>>> "dirty bitmap" API i decided to just leave it as it is. The CPU load
> >>>> used by the display update code is usually < 1%, so it's ok for me.
> >>>
> >>> Wow that sounds that some interesting hardware(!). Does it make sense to model the
> >>> behaviour of the chipset separately using a proxy MemoryRegion similar to virtio i.e.
> >>> introduce an intermediate IO MemoryRegion that does the swapping and then forward it
> >>> onto the VRAM MemoryRegion?
> >>
> >> Thanks for the pointer, i'll check whether that would work. For now i
> >> think i'll remove the Artist patch from the series, so we can apply the
> >> other patches, and i'll re-submit Artist when it's done. I guess the
> >> rewrite to use a MemRegion is a bit bigger. But i would to get the other
> >> patches in especially the LASI Stuff as both Helge and i have a lot of
> >> stuff depending on that.
> > 
> > I've looked into it again and changed my mind. There are at least the following
> > functions that the Artist chip does before a Read/Write is passed to/from VRAM:
> > 
> > - endianess conversion (actually configurable via some register, but i don't
> >   know how and hardwired it depending on CMAP / FB access)
> > 
> > - The Address passed on the System bus are the X/Y coordinates added to the FB
> >   base address in the selected buffer instead of the VRAM offset for pixel data.
> >   I think that's configurable via the some registers, but i don't know how.
> >   Unfortunately there's absolutely no documentation about Artist available and
> >   everything was developed by reverse engineering.
> > 
> > - bitmap to Byte/Word conversion (not implemented yet for the VRAM window, only
> >   for the I/O register window)
> > 
> > So in my opinion it's way to much effort to squeeze all of that into the memory
> > space, and it is not really a Memory range that's just behind a bus bridge.
> 
> Hi Sven,
> 
> Certainly in some cases it isn't possible to model devices in QEMU exactly as real
> hardware, although I think that some of the ideas above could be used to improve the
> implementation without too much extra effort.
> 
> Then again from my work on QEMU I completely understand that sometimes this can be
> difficult with older, more esoteric devices. Ultimately after review that decision
> has to come from the maintainer(s) for the relevant devices/machines, so I guess that
> would be Richard and/or Gerd in this case?

I think that would be Richard. I rewrote the code to at least use the generic
framebuffer functions now, and added dirty memory tracking. I'm still not happy
with all the endianess conversion that are going on, but without any
Documentation about chip it's impossible to say how the chip really works.

Regards
Sven


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

end of thread, other threads:[~2019-11-03 21:10 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-10-22 20:59 [PATCH v3 0/6] HPPA: i82596, PS/2 and graphics emulation Sven Schnelle
2019-10-22 20:59 ` [PATCH v3 1/6] hw/hppa/dino.c: Improve emulation of Dino PCI chip Sven Schnelle
2019-10-22 20:59 ` [PATCH v3 2/6] hppa: Add support for LASI chip with i82596 NIC Sven Schnelle
2019-10-22 20:59 ` [PATCH v3 3/6] ps2: accept 'Set Key Make and Break' commands Sven Schnelle
2019-10-23 11:08   ` Philippe Mathieu-Daudé
2019-10-23 12:08     ` Sven Schnelle
2019-10-23 12:32       ` Philippe Mathieu-Daudé
2019-10-22 20:59 ` [PATCH v3 4/6] hppa: add emulation of LASI PS2 controllers Sven Schnelle
2019-10-22 20:59 ` [PATCH v3 5/6] hppa: Add emulation of Artist graphics Sven Schnelle
2019-10-24 20:51   ` Mark Cave-Ayland
2019-10-25  9:31     ` Sven Schnelle
2019-10-26  9:35       ` Mark Cave-Ayland
2019-10-26 17:54         ` Sven Schnelle
2019-11-01 21:59           ` Sven Schnelle
2019-11-03 20:56             ` Mark Cave-Ayland
2019-11-03 21:03               ` Sven Schnelle
2019-10-26 11:43   ` Philippe Mathieu-Daudé

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).