From 18092598a50ab62aba2059a25a7fff8286875610 Mon Sep 17 00:00:00 2001 From: Helge Deller Date: Fri, 20 Dec 2019 22:15:07 +0100 Subject: hw/hppa/dino.c: Improve emulation of Dino PCI chip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Signed-off-by: Sven Schnelle Reviewed-by: Philippe Mathieu-Daudé Message-Id: <20191220211512.3289-2-svens@stackframe.org> Signed-off-by: Richard Henderson --- hw/hppa/dino.c | 97 ++++++++++++++++++++++++++++++++++++++++++++-------- hw/hppa/trace-events | 5 +++ 2 files changed, 88 insertions(+), 14 deletions(-) (limited to 'hw') diff --git a/hw/hppa/dino.c b/hw/hppa/dino.c index ab6969b..9797a7f 100644 --- a/hw/hppa/dino.c +++ b/hw/hppa/dino.c @@ -1,7 +1,7 @@ /* - * HP-PARISC Dino PCI chipset emulation. + * HP-PARISC Dino PCI chipset emulation, as in B160L and similiar machines * - * (C) 2017 by Helge Deller + * (C) 2017-2019 by Helge Deller * * 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 4e2acb6..f943b16 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" -- cgit v1.1 From 376b851909d42c6faf4f780f25b6be55f17f3a6e Mon Sep 17 00:00:00 2001 From: Helge Deller Date: Fri, 20 Dec 2019 22:15:08 +0100 Subject: hppa: Add support for LASI chip with i82596 NIC 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 Signed-off-by: Sven Schnelle Message-Id: <20191220211512.3289-3-svens@stackframe.org> Signed-off-by: Richard Henderson --- 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 + 12 files changed, 1375 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 (limited to 'hw') diff --git a/hw/hppa/Kconfig b/hw/hppa/Kconfig index 6e5d74a..2a7b38d 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 67838f5..eac3467 100644 --- a/hw/hppa/Makefile.objs +++ b/hw/hppa/Makefile.objs @@ -1 +1 @@ -obj-$(CONFIG_DINO) += pci.o machine.o dino.o +obj-$(CONFIG_DINO) += pci.o machine.o dino.o lasi.o diff --git a/hw/hppa/hppa_sys.h b/hw/hppa/hppa_sys.h index 4e50196..4d08501 100644 --- a/hw/hppa/hppa_sys.h +++ b/hw/hppa/hppa_sys.h @@ -12,6 +12,8 @@ #include "hppa_hardware.h" PCIBus *dino_init(MemoryRegion *, qemu_irq *, qemu_irq *); +DeviceState *lasi_init(MemoryRegion *); +#define enable_lasi_lan() 0 #define TYPE_DINO_PCI_HOST_BRIDGE "dino-pcihost" diff --git a/hw/hppa/lasi.c b/hw/hppa/lasi.c new file mode 100644 index 0000000..5175258 --- /dev/null +++ b/hw/hppa/lasi.c @@ -0,0 +1,360 @@ +/* + * HP-PARISC Lasi chipset emulation. + * + * (C) 2019 by Helge Deller + * + * 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 5d0de26..33e3769 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" @@ -101,6 +102,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); @@ -125,7 +129,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 f943b16..3ff6203 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 af6a11b..54411d3 100644 --- a/hw/net/Kconfig +++ b/hw/net/Kconfig @@ -31,6 +31,9 @@ config TULIP depends on PCI select NMC93XX_EEPROM +config I82596_COMMON + bool + config E1000_PCI bool default y if PCI_DEVICES @@ -89,6 +92,10 @@ config LANCE bool select PCNET_COMMON +config LASI_82596 + bool + select I82596_COMMON + config SUNHME bool diff --git a/hw/net/Makefile.objs b/hw/net/Makefile.objs index 7907d2c..19f13e9 100644 --- a/hw/net/Makefile.objs +++ b/hw/net/Makefile.objs @@ -28,6 +28,8 @@ common-obj-$(CONFIG_IMX_FEC) += imx_fec.o common-obj-$(CONFIG_CADENCE) += cadence_gem.o common-obj-$(CONFIG_STELLARIS_ENET) += stellaris_enet.o common-obj-$(CONFIG_LANCE) += lance.o +common-obj-$(CONFIG_LASI_82596) += lasi_i82596.o +common-obj-$(CONFIG_I82596_COMMON) += i82596.o common-obj-$(CONFIG_SUNHME) += sunhme.o common-obj-$(CONFIG_FTGMAC100) += ftgmac100.o common-obj-$(CONFIG_SUNGEM) += sungem.o diff --git a/hw/net/i82596.c b/hw/net/i82596.c new file mode 100644 index 0000000..3a0e1ec --- /dev/null +++ b/hw/net/i82596.c @@ -0,0 +1,734 @@ +/* + * QEMU Intel i82596 (Apricot) emulation + * + * Copyright (c) 2019 Helge Deller + * 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 /* For crc32 */ + +#if defined(ENABLE_DEBUG) +#define DBG(x) x +#else +#define DBG(x) do { } while (0) +#endif + +#define USE_TIMER 0 + +#define BITS(n, m) (((0xffffffffU << (31 - n)) >> (31 - n + m)) << m) + +#define PKT_BUF_SZ 1536 +#define MAX_MC_CNT 64 + +#define ISCP_BUSY 0x0001 + +#define I596_NULL ((uint32_t)0xffffffff) + +#define SCB_STATUS_CX 0x8000 /* CU finished command with I bit */ +#define SCB_STATUS_FR 0x4000 /* RU finished receiving a frame */ +#define SCB_STATUS_CNA 0x2000 /* CU left active state */ +#define SCB_STATUS_RNR 0x1000 /* RU left active state */ + +#define CU_IDLE 0 +#define CU_SUSPENDED 1 +#define CU_ACTIVE 2 + +#define RX_IDLE 0 +#define RX_SUSPENDED 1 +#define RX_READY 4 + +#define CMD_EOL 0x8000 /* The last command of the list, stop. */ +#define CMD_SUSP 0x4000 /* Suspend after doing cmd. */ +#define CMD_INTR 0x2000 /* Interrupt after doing cmd. */ + +#define CMD_FLEX 0x0008 /* Enable flexible memory model */ + +enum commands { + CmdNOp = 0, CmdSASetup = 1, CmdConfigure = 2, CmdMulticastList = 3, + CmdTx = 4, CmdTDR = 5, CmdDump = 6, CmdDiagnose = 7 +}; + +#define STAT_C 0x8000 /* Set to 0 after execution */ +#define STAT_B 0x4000 /* Command being executed */ +#define STAT_OK 0x2000 /* Command executed ok */ +#define STAT_A 0x1000 /* Command aborted */ + +#define I596_EOF 0x8000 +#define SIZE_MASK 0x3fff + +#define ETHER_TYPE_LEN 2 +#define VLAN_TCI_LEN 2 +#define VLAN_HLEN (ETHER_TYPE_LEN + VLAN_TCI_LEN) + +/* various flags in the chip config registers */ +#define I596_PREFETCH (s->config[0] & 0x80) +#define I596_PROMISC (s->config[8] & 0x01) +#define I596_BC_DISABLE (s->config[8] & 0x02) /* broadcast disable */ +#define I596_NOCRC_INS (s->config[8] & 0x08) +#define I596_CRCINM (s->config[11] & 0x04) /* CRC appended */ +#define I596_MC_ALL (s->config[11] & 0x20) +#define I596_MULTIIA (s->config[13] & 0x40) + + +static uint8_t get_byte(uint32_t addr) +{ + return ldub_phys(&address_space_memory, addr); +} + +static void set_byte(uint32_t addr, uint8_t c) +{ + return stb_phys(&address_space_memory, addr, c); +} + +static uint16_t get_uint16(uint32_t addr) +{ + return lduw_be_phys(&address_space_memory, addr); +} + +static void set_uint16(uint32_t addr, uint16_t w) +{ + return stw_be_phys(&address_space_memory, addr, w); +} + +static uint32_t get_uint32(uint32_t addr) +{ + uint32_t lo = lduw_be_phys(&address_space_memory, addr); + uint32_t hi = lduw_be_phys(&address_space_memory, addr + 2); + return (hi << 16) | lo; +} + +static void set_uint32(uint32_t addr, uint32_t val) +{ + set_uint16(addr, (uint16_t) val); + set_uint16(addr + 2, val >> 16); +} + + +struct qemu_ether_header { + uint8_t ether_dhost[6]; + uint8_t ether_shost[6]; + uint16_t ether_type; +}; + +#define PRINT_PKTHDR(txt, BUF) do { \ + struct qemu_ether_header *hdr = (void *)(BUF); \ + printf(txt ": packet dhost=" MAC_FMT ", shost=" MAC_FMT ", type=0x%04x\n",\ + MAC_ARG(hdr->ether_dhost), MAC_ARG(hdr->ether_shost), \ + be16_to_cpu(hdr->ether_type)); \ +} while (0) + +static void i82596_transmit(I82596State *s, uint32_t addr) +{ + uint32_t tdb_p; /* Transmit Buffer Descriptor */ + + /* TODO: Check flexible mode */ + tdb_p = get_uint32(addr + 8); + while (tdb_p != I596_NULL) { + uint16_t size, len; + uint32_t tba; + + size = get_uint16(tdb_p); + len = size & SIZE_MASK; + tba = get_uint32(tdb_p + 8); + trace_i82596_transmit(len, tba); + + if (s->nic && len) { + assert(len <= sizeof(s->tx_buffer)); + address_space_rw(&address_space_memory, tba, + MEMTXATTRS_UNSPECIFIED, s->tx_buffer, len, 0); + DBG(PRINT_PKTHDR("Send", &s->tx_buffer)); + DBG(printf("Sending %d bytes\n", len)); + qemu_send_packet(qemu_get_queue(s->nic), s->tx_buffer, len); + } + + /* was this the last package? */ + if (size & I596_EOF) { + break; + } + + /* get next buffer pointer */ + tdb_p = get_uint32(tdb_p + 4); + } +} + +static void set_individual_address(I82596State *s, uint32_t addr) +{ + NetClientState *nc; + uint8_t *m; + + nc = qemu_get_queue(s->nic); + m = s->conf.macaddr.a; + address_space_rw(&address_space_memory, addr + 8, + MEMTXATTRS_UNSPECIFIED, m, ETH_ALEN, 0); + qemu_format_nic_info_str(nc, m); + trace_i82596_new_mac(nc->info_str); +} + +static void set_multicast_list(I82596State *s, uint32_t addr) +{ + uint16_t mc_count, i; + + memset(&s->mult[0], 0, sizeof(s->mult)); + mc_count = get_uint16(addr + 8) / ETH_ALEN; + addr += 10; + if (mc_count > MAX_MC_CNT) { + mc_count = MAX_MC_CNT; + } + for (i = 0; i < mc_count; i++) { + uint8_t multicast_addr[ETH_ALEN]; + address_space_rw(&address_space_memory, + addr + i * ETH_ALEN, MEMTXATTRS_UNSPECIFIED, + multicast_addr, ETH_ALEN, 0); + DBG(printf("Add multicast entry " MAC_FMT "\n", + MAC_ARG(multicast_addr))); + unsigned mcast_idx = (net_crc32(multicast_addr, ETH_ALEN) & + BITS(7, 2)) >> 2; + assert(mcast_idx < 8 * sizeof(s->mult)); + s->mult[mcast_idx >> 3] |= (1 << (mcast_idx & 7)); + } + trace_i82596_set_multicast(mc_count); +} + +void i82596_set_link_status(NetClientState *nc) +{ + I82596State *d = qemu_get_nic_opaque(nc); + + d->lnkst = nc->link_down ? 0 : 0x8000; +} + +static void update_scb_status(I82596State *s) +{ + s->scb_status = (s->scb_status & 0xf000) + | (s->cu_status << 8) | (s->rx_status << 4); + set_uint16(s->scb, s->scb_status); +} + + +static void i82596_s_reset(I82596State *s) +{ + trace_i82596_s_reset(s); + s->scp = 0; + s->scb_status = 0; + s->cu_status = CU_IDLE; + s->rx_status = RX_SUSPENDED; + s->cmd_p = I596_NULL; + s->lnkst = 0x8000; /* initial link state: up */ + s->ca = s->ca_active = 0; + s->send_irq = 0; +} + + +static void command_loop(I82596State *s) +{ + uint16_t cmd; + uint16_t status; + uint8_t byte_cnt; + + DBG(printf("STARTING COMMAND LOOP cmd_p=%08x\n", s->cmd_p)); + + while (s->cmd_p != I596_NULL) { + /* set status */ + status = STAT_B; + set_uint16(s->cmd_p, status); + status = STAT_C | STAT_OK; /* update, but write later */ + + cmd = get_uint16(s->cmd_p + 2); + DBG(printf("Running command %04x at %08x\n", cmd, s->cmd_p)); + + switch (cmd & 0x07) { + case CmdNOp: + break; + case CmdSASetup: + set_individual_address(s, s->cmd_p); + break; + case CmdConfigure: + byte_cnt = get_byte(s->cmd_p + 8) & 0x0f; + byte_cnt = MAX(byte_cnt, 4); + byte_cnt = MIN(byte_cnt, sizeof(s->config)); + /* copy byte_cnt max. */ + address_space_rw(&address_space_memory, s->cmd_p + 8, + MEMTXATTRS_UNSPECIFIED, s->config, byte_cnt, 0); + /* config byte according to page 35ff */ + s->config[2] &= 0x82; /* mask valid bits */ + s->config[2] |= 0x40; + s->config[7] &= 0xf7; /* clear zero bit */ + assert(I596_NOCRC_INS == 0); /* do CRC insertion */ + s->config[10] = MAX(s->config[10], 5); /* min frame length */ + s->config[12] &= 0x40; /* only full duplex field valid */ + s->config[13] |= 0x3f; /* set ones in byte 13 */ + break; + case CmdTDR: + /* get signal LINK */ + set_uint32(s->cmd_p + 8, s->lnkst); + break; + case CmdTx: + i82596_transmit(s, s->cmd_p); + break; + case CmdMulticastList: + set_multicast_list(s, s->cmd_p); + break; + case CmdDump: + case CmdDiagnose: + printf("FIXME Command %d !!\n", cmd & 7); + assert(0); + } + + /* update status */ + set_uint16(s->cmd_p, status); + + s->cmd_p = get_uint32(s->cmd_p + 4); /* get link address */ + DBG(printf("NEXT addr would be %08x\n", s->cmd_p)); + if (s->cmd_p == 0) { + s->cmd_p = I596_NULL; + } + + /* Stop when last command of the list. */ + if (cmd & CMD_EOL) { + s->cmd_p = I596_NULL; + } + /* Suspend after doing cmd? */ + if (cmd & CMD_SUSP) { + s->cu_status = CU_SUSPENDED; + printf("FIXME SUSPEND !!\n"); + } + /* Interrupt after doing cmd? */ + if (cmd & CMD_INTR) { + s->scb_status |= SCB_STATUS_CX; + } else { + s->scb_status &= ~SCB_STATUS_CX; + } + update_scb_status(s); + + /* Interrupt after doing cmd? */ + if (cmd & CMD_INTR) { + s->send_irq = 1; + } + + if (s->cu_status != CU_ACTIVE) { + break; + } + } + DBG(printf("FINISHED COMMAND LOOP\n")); + qemu_flush_queued_packets(qemu_get_queue(s->nic)); +} + +static void i82596_flush_queue_timer(void *opaque) +{ + I82596State *s = opaque; + if (0) { + timer_del(s->flush_queue_timer); + qemu_flush_queued_packets(qemu_get_queue(s->nic)); + timer_mod(s->flush_queue_timer, + qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + 1000); + } +} + +static void examine_scb(I82596State *s) +{ + uint16_t command, cuc, ruc; + + /* get the scb command word */ + command = get_uint16(s->scb + 2); + cuc = (command >> 8) & 0x7; + ruc = (command >> 4) & 0x7; + DBG(printf("MAIN COMMAND %04x cuc %02x ruc %02x\n", command, cuc, ruc)); + /* and clear the scb command word */ + set_uint16(s->scb + 2, 0); + + if (command & BIT(31)) /* ACK-CX */ + s->scb_status &= ~SCB_STATUS_CX; + if (command & BIT(30)) /*ACK-FR */ + s->scb_status &= ~SCB_STATUS_FR; + if (command & BIT(29)) /*ACK-CNA */ + s->scb_status &= ~SCB_STATUS_CNA; + if (command & BIT(28)) /*ACK-RNR */ + s->scb_status &= ~SCB_STATUS_RNR; + + switch (cuc) { + case 0: /* no change */ + break; + case 1: /* CUC_START */ + s->cu_status = CU_ACTIVE; + break; + case 4: /* CUC_ABORT */ + s->cu_status = CU_SUSPENDED; + s->scb_status |= SCB_STATUS_CNA; /* CU left active state */ + break; + default: + printf("WARNING: Unknown CUC %d!\n", cuc); + } + + switch (ruc) { + case 0: /* no change */ + break; + case 1: /* RX_START */ + case 2: /* RX_RESUME */ + s->rx_status = RX_IDLE; + if (USE_TIMER) { + timer_mod(s->flush_queue_timer, qemu_clock_get_ms( + QEMU_CLOCK_VIRTUAL) + 1000); + } + break; + case 3: /* RX_SUSPEND */ + case 4: /* RX_ABORT */ + s->rx_status = RX_SUSPENDED; + s->scb_status |= SCB_STATUS_RNR; /* RU left active state */ + break; + default: + printf("WARNING: Unknown RUC %d!\n", ruc); + } + + if (command & 0x80) { /* reset bit set? */ + i82596_s_reset(s); + } + + /* execute commands from SCBL */ + if (s->cu_status != CU_SUSPENDED) { + if (s->cmd_p == I596_NULL) { + s->cmd_p = get_uint32(s->scb + 4); + } + } + + /* update scb status */ + update_scb_status(s); + + command_loop(s); +} + +static void signal_ca(I82596State *s) +{ + uint32_t iscp = 0; + + /* trace_i82596_channel_attention(s); */ + if (s->scp) { + /* CA after reset -> do init with new scp. */ + s->sysbus = get_byte(s->scp + 3); /* big endian */ + DBG(printf("SYSBUS = %08x\n", s->sysbus)); + if (((s->sysbus >> 1) & 0x03) != 2) { + printf("WARNING: NO LINEAR MODE !!\n"); + } + if ((s->sysbus >> 7)) { + printf("WARNING: 32BIT LINMODE IN B-STEPPING NOT SUPPORTED !!\n"); + } + iscp = get_uint32(s->scp + 8); + s->scb = get_uint32(iscp + 4); + set_byte(iscp + 1, 0); /* clear BUSY flag in iscp */ + s->scp = 0; + } + + s->ca++; /* count ca() */ + if (!s->ca_active) { + s->ca_active = 1; + while (s->ca) { + examine_scb(s); + s->ca--; + } + s->ca_active = 0; + } + + if (s->send_irq) { + s->send_irq = 0; + qemu_set_irq(s->irq, 1); + } +} + +void i82596_ioport_writew(void *opaque, uint32_t addr, uint32_t val) +{ + I82596State *s = opaque; + /* printf("i82596_ioport_writew addr=0x%08x val=0x%04x\n", addr, val); */ + switch (addr) { + case PORT_RESET: /* Reset */ + i82596_s_reset(s); + break; + case PORT_ALTSCP: + s->scp = val; + break; + case PORT_CA: + signal_ca(s); + break; + } +} + +uint32_t i82596_ioport_readw(void *opaque, uint32_t addr) +{ + return -1; +} + +void i82596_h_reset(void *opaque) +{ + I82596State *s = opaque; + + i82596_s_reset(s); +} + +int i82596_can_receive(NetClientState *nc) +{ + I82596State *s = qemu_get_nic_opaque(nc); + + if (s->rx_status == RX_SUSPENDED) { + return 0; + } + + if (!s->lnkst) { + return 0; + } + + if (USE_TIMER && !timer_pending(s->flush_queue_timer)) { + return 1; + } + + return 1; +} + +#define MIN_BUF_SIZE 60 + +ssize_t i82596_receive(NetClientState *nc, const uint8_t *buf, size_t sz) +{ + I82596State *s = qemu_get_nic_opaque(nc); + uint32_t rfd_p; + uint32_t rbd; + uint16_t is_broadcast = 0; + size_t len = sz; + uint32_t crc; + uint8_t *crc_ptr; + uint8_t buf1[MIN_BUF_SIZE + VLAN_HLEN]; + static const uint8_t broadcast_macaddr[6] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + + DBG(printf("i82596_receive() start\n")); + + if (USE_TIMER && timer_pending(s->flush_queue_timer)) { + return 0; + } + + /* first check if receiver is enabled */ + if (s->rx_status == RX_SUSPENDED) { + trace_i82596_receive_analysis(">>> Receiving suspended"); + return -1; + } + + if (!s->lnkst) { + trace_i82596_receive_analysis(">>> Link down"); + return -1; + } + + /* Received frame smaller than configured "min frame len"? */ + if (sz < s->config[10]) { + printf("Received frame too small, %zu vs. %u bytes\n", + sz, s->config[10]); + return -1; + } + + DBG(printf("Received %lu bytes\n", sz)); + + if (I596_PROMISC) { + + /* promiscuous: receive all */ + trace_i82596_receive_analysis( + ">>> packet received in promiscuous mode"); + + } else { + + if (!memcmp(buf, broadcast_macaddr, 6)) { + /* broadcast address */ + if (I596_BC_DISABLE) { + trace_i82596_receive_analysis(">>> broadcast packet rejected"); + + return len; + } + + trace_i82596_receive_analysis(">>> broadcast packet received"); + is_broadcast = 1; + + } else if (buf[0] & 0x01) { + /* multicast */ + if (!I596_MC_ALL) { + trace_i82596_receive_analysis(">>> multicast packet rejected"); + + return len; + } + + int mcast_idx = (net_crc32(buf, ETH_ALEN) & BITS(7, 2)) >> 2; + assert(mcast_idx < 8 * sizeof(s->mult)); + + if (!(s->mult[mcast_idx >> 3] & (1 << (mcast_idx & 7)))) { + trace_i82596_receive_analysis(">>> multicast address mismatch"); + + return len; + } + + trace_i82596_receive_analysis(">>> multicast packet received"); + is_broadcast = 1; + + } else if (!memcmp(s->conf.macaddr.a, buf, 6)) { + + /* match */ + trace_i82596_receive_analysis( + ">>> physical address matching packet received"); + + } else { + + trace_i82596_receive_analysis(">>> unknown packet"); + + return len; + } + } + + /* if too small buffer, then expand it */ + if (len < MIN_BUF_SIZE + VLAN_HLEN) { + memcpy(buf1, buf, len); + memset(buf1 + len, 0, MIN_BUF_SIZE + VLAN_HLEN - len); + buf = buf1; + if (len < MIN_BUF_SIZE) { + len = MIN_BUF_SIZE; + } + } + + /* Calculate the ethernet checksum (4 bytes) */ + len += 4; + crc = cpu_to_be32(crc32(~0, buf, sz)); + crc_ptr = (uint8_t *) &crc; + + rfd_p = get_uint32(s->scb + 8); /* get Receive Frame Descriptor */ + assert(rfd_p && rfd_p != I596_NULL); + + /* get first Receive Buffer Descriptor Address */ + rbd = get_uint32(rfd_p + 8); + assert(rbd && rbd != I596_NULL); + + trace_i82596_receive_packet(len); + /* PRINT_PKTHDR("Receive", buf); */ + + while (len) { + uint16_t command, status; + uint32_t next_rfd; + + command = get_uint16(rfd_p + 2); + assert(command & CMD_FLEX); /* assert Flex Mode */ + /* get first Receive Buffer Descriptor Address */ + rbd = get_uint32(rfd_p + 8); + assert(get_uint16(rfd_p + 14) == 0); + + /* printf("Receive: rfd is %08x\n", rfd_p); */ + + while (len) { + uint16_t buffer_size, num; + uint32_t rba; + + /* printf("Receive: rbd is %08x\n", rbd); */ + buffer_size = get_uint16(rbd + 12); + /* printf("buffer_size is 0x%x\n", buffer_size); */ + assert(buffer_size != 0); + + num = buffer_size & SIZE_MASK; + if (num > len) { + num = len; + } + rba = get_uint32(rbd + 8); + /* printf("rba is 0x%x\n", rba); */ + address_space_rw(&address_space_memory, rba, + MEMTXATTRS_UNSPECIFIED, (void *)buf, num, 1); + rba += num; + buf += num; + len -= num; + if (len == 0) { /* copy crc */ + address_space_rw(&address_space_memory, rba - 4, + MEMTXATTRS_UNSPECIFIED, crc_ptr, 4, 1); + } + + num |= 0x4000; /* set F BIT */ + if (len == 0) { + num |= I596_EOF; /* set EOF BIT */ + } + set_uint16(rbd + 0, num); /* write actual count with flags */ + + /* get next rbd */ + rbd = get_uint32(rbd + 4); + /* printf("Next Receive: rbd is %08x\n", rbd); */ + + if (buffer_size & I596_EOF) /* last entry */ + break; + } + + /* Housekeeping, see pg. 18 */ + next_rfd = get_uint32(rfd_p + 4); + set_uint32(next_rfd + 8, rbd); + + status = STAT_C | STAT_OK | is_broadcast; + set_uint16(rfd_p, status); + + if (command & CMD_SUSP) { /* suspend after command? */ + s->rx_status = RX_SUSPENDED; + s->scb_status |= SCB_STATUS_RNR; /* RU left active state */ + break; + } + if (command & CMD_EOL) /* was it last Frame Descriptor? */ + break; + + assert(len == 0); + } + + assert(len == 0); + + s->scb_status |= SCB_STATUS_FR; /* set "RU finished receiving frame" bit. */ + update_scb_status(s); + + /* send IRQ that we received data */ + qemu_set_irq(s->irq, 1); + /* s->send_irq = 1; */ + + if (0) { + DBG(printf("Checking:\n")); + rfd_p = get_uint32(s->scb + 8); /* get Receive Frame Descriptor */ + DBG(printf("Next Receive: rfd is %08x\n", rfd_p)); + rfd_p = get_uint32(rfd_p + 4); /* get Next Receive Frame Descriptor */ + DBG(printf("Next Receive: rfd is %08x\n", rfd_p)); + /* get first Receive Buffer Descriptor Address */ + rbd = get_uint32(rfd_p + 8); + DBG(printf("Next Receive: rbd is %08x\n", rbd)); + } + + return sz; +} + + +const VMStateDescription vmstate_i82596 = { + .name = "i82596", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT16(lnkst, I82596State), + VMSTATE_TIMER_PTR(flush_queue_timer, I82596State), + VMSTATE_END_OF_LIST() + } +}; + +void i82596_common_init(DeviceState *dev, I82596State *s, NetClientInfo *info) +{ + if (s->conf.macaddr.a[0] == 0) { + qemu_macaddr_default_if_unset(&s->conf.macaddr); + } + s->nic = qemu_new_nic(info, &s->conf, object_get_typename(OBJECT(dev)), + dev->id, s); + qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a); + + if (USE_TIMER) { + s->flush_queue_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, + i82596_flush_queue_timer, s); + } + s->lnkst = 0x8000; /* initial link state: up */ +} diff --git a/hw/net/i82596.h b/hw/net/i82596.h new file mode 100644 index 0000000..1238ac1 --- /dev/null +++ b/hw/net/i82596.h @@ -0,0 +1,55 @@ +#ifndef HW_I82596_H +#define HW_I82596_H + +#define I82596_IOPORT_SIZE 0x20 + +#include "exec/memory.h" +#include "exec/address-spaces.h" + +#define PORT_RESET 0x00 /* reset 82596 */ +#define PORT_SELFTEST 0x01 /* selftest */ +#define PORT_ALTSCP 0x02 /* alternate SCB address */ +#define PORT_ALTDUMP 0x03 /* Alternate DUMP address */ +#define PORT_CA 0x10 /* QEMU-internal CA signal */ + +typedef struct I82596State_st I82596State; + +struct I82596State_st { + MemoryRegion mmio; + MemoryRegion *as; + qemu_irq irq; + NICState *nic; + NICConf conf; + QEMUTimer *flush_queue_timer; + + hwaddr scp; /* pointer to SCP */ + uint8_t sysbus; + uint32_t scb; /* SCB */ + uint16_t scb_status; + uint8_t cu_status, rx_status; + uint16_t lnkst; + + uint32_t cmd_p; /* addr of current command */ + int ca; + int ca_active; + int send_irq; + + /* Hash register (multicast mask array, multiple individual addresses). */ + uint8_t mult[8]; + uint8_t config[14]; /* config bytes from CONFIGURE command */ + + uint8_t tx_buffer[0x4000]; +}; + +void i82596_h_reset(void *opaque); +void i82596_ioport_writew(void *opaque, uint32_t addr, uint32_t val); +uint32_t i82596_ioport_readw(void *opaque, uint32_t addr); +void i82596_ioport_writel(void *opaque, uint32_t addr, uint32_t val); +uint32_t i82596_ioport_readl(void *opaque, uint32_t addr); +uint32_t i82596_bcr_readw(I82596State *s, uint32_t rap); +ssize_t i82596_receive(NetClientState *nc, const uint8_t *buf, size_t size_); +int i82596_can_receive(NetClientState *nc); +void i82596_set_link_status(NetClientState *nc); +void i82596_common_init(DeviceState *dev, I82596State *s, NetClientInfo *info); +extern const VMStateDescription vmstate_i82596; +#endif diff --git a/hw/net/lasi_i82596.c b/hw/net/lasi_i82596.c new file mode 100644 index 0000000..427b3fb --- /dev/null +++ b/hw/net/lasi_i82596.c @@ -0,0 +1,188 @@ +/* + * QEMU LASI NIC i82596 emulation + * + * Copyright (c) 2019 Helge Deller + * This work is licensed under the GNU GPL license version 2 or later. + * + * + * On PA-RISC, this is the Network part of LASI chip. + * See: + * https://parisc.wiki.kernel.org/images-parisc/7/79/Lasi_ers.pdf + */ + +#include "qemu/osdep.h" +#include "qemu/timer.h" +#include "hw/sysbus.h" +#include "net/eth.h" +#include "hw/net/lasi_82596.h" +#include "hw/net/i82596.h" +#include "trace.h" +#include "sysemu/sysemu.h" +#include "hw/qdev-properties.h" +#include "migration/vmstate.h" + +#define PA_I82596_RESET 0 /* Offsets relative to LASI-LAN-Addr.*/ +#define PA_CPU_PORT_L_ACCESS 4 +#define PA_CHANNEL_ATTENTION 8 +#define PA_GET_MACADDR 12 + +#define SWAP32(x) (((uint32_t)(x) << 16) | ((((uint32_t)(x))) >> 16)) + +static void lasi_82596_mem_write(void *opaque, hwaddr addr, + uint64_t val, unsigned size) +{ + SysBusI82596State *d = opaque; + + trace_lasi_82596_mem_writew(addr, val); + switch (addr) { + case PA_I82596_RESET: + i82596_h_reset(&d->state); + break; + case PA_CPU_PORT_L_ACCESS: + d->val_index++; + if (d->val_index == 0) { + uint32_t v = d->last_val | (val << 16); + v = v & ~0xff; + i82596_ioport_writew(&d->state, d->last_val & 0xff, v); + } + d->last_val = val; + break; + case PA_CHANNEL_ATTENTION: + i82596_ioport_writew(&d->state, PORT_CA, val); + break; + case PA_GET_MACADDR: + /* + * Provided for SeaBIOS only. Write MAC of Network card to addr @val. + * Needed for the PDC_LAN_STATION_ID_READ PDC call. + */ + address_space_rw(&address_space_memory, val, + MEMTXATTRS_UNSPECIFIED, d->state.conf.macaddr.a, ETH_ALEN, 1); + break; + } +} + +static uint64_t lasi_82596_mem_read(void *opaque, hwaddr addr, + unsigned size) +{ + SysBusI82596State *d = opaque; + uint32_t val; + + if (addr == PA_GET_MACADDR) { + val = 0xBEEFBABE; + } else { + val = i82596_ioport_readw(&d->state, addr); + } + trace_lasi_82596_mem_readw(addr, val); + return val; +} + +static const MemoryRegionOps lasi_82596_mem_ops = { + .read = lasi_82596_mem_read, + .write = lasi_82596_mem_write, + .endianness = DEVICE_BIG_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static NetClientInfo net_lasi_82596_info = { + .type = NET_CLIENT_DRIVER_NIC, + .size = sizeof(NICState), + .can_receive = i82596_can_receive, + .receive = i82596_receive, + .link_status_changed = i82596_set_link_status, +}; + +static const VMStateDescription vmstate_lasi_82596 = { + .name = "i82596", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_STRUCT(state, SysBusI82596State, 0, vmstate_i82596, + I82596State), + VMSTATE_END_OF_LIST() + } +}; + +static void lasi_82596_realize(DeviceState *dev, Error **errp) +{ + SysBusI82596State *d = SYSBUS_I82596(dev); + I82596State *s = &d->state; + + memory_region_init_io(&s->mmio, OBJECT(d), &lasi_82596_mem_ops, d, + "lasi_82596-mmio", PA_GET_MACADDR + 4); + + i82596_common_init(dev, s, &net_lasi_82596_info); +} + +SysBusI82596State *lasi_82596_init(MemoryRegion *addr_space, + hwaddr hpa, qemu_irq lan_irq) +{ + DeviceState *dev; + SysBusI82596State *s; + static const MACAddr HP_MAC = { + .a = { 0x08, 0x00, 0x09, 0xef, 0x34, 0xf6 } }; + + qemu_check_nic_model(&nd_table[0], TYPE_LASI_82596); + dev = qdev_create(NULL, TYPE_LASI_82596); + s = SYSBUS_I82596(dev); + s->state.irq = lan_irq; + qdev_set_nic_properties(dev, &nd_table[0]); + qdev_init_nofail(dev); + s->state.conf.macaddr = HP_MAC; /* set HP MAC prefix */ + + /* LASI 82596 ports in main memory. */ + memory_region_add_subregion(addr_space, hpa, &s->state.mmio); + return s; +} + +static void lasi_82596_reset(DeviceState *dev) +{ + SysBusI82596State *d = SYSBUS_I82596(dev); + + i82596_h_reset(&d->state); +} + +static void lasi_82596_instance_init(Object *obj) +{ + SysBusI82596State *d = SYSBUS_I82596(obj); + I82596State *s = &d->state; + + device_add_bootindex_property(obj, &s->conf.bootindex, + "bootindex", "/ethernet-phy@0", + DEVICE(obj), NULL); +} + +static Property lasi_82596_properties[] = { + DEFINE_NIC_PROPERTIES(SysBusI82596State, state.conf), + DEFINE_PROP_END_OF_LIST(), +}; + +static void lasi_82596_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = lasi_82596_realize; + set_bit(DEVICE_CATEGORY_NETWORK, dc->categories); + dc->fw_name = "ethernet"; + dc->reset = lasi_82596_reset; + dc->vmsd = &vmstate_lasi_82596; + dc->user_creatable = false; + device_class_set_props(dc, lasi_82596_properties); +} + +static const TypeInfo lasi_82596_info = { + .name = TYPE_LASI_82596, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(SysBusI82596State), + .class_init = lasi_82596_class_init, + .instance_init = lasi_82596_instance_init, +}; + +static void lasi_82596_register_types(void) +{ + type_register_static(&lasi_82596_info); +} + +type_init(lasi_82596_register_types) diff --git a/hw/net/trace-events b/hw/net/trace-events index e70f12b..42066fc 100644 --- a/hw/net/trace-events +++ b/hw/net/trace-events @@ -381,3 +381,16 @@ tulip_mii_read(int phy, int reg, uint16_t data) "phy 0x%x, reg 0x%x data 0x%04x" tulip_reset(void) "" tulip_setup_frame(void) "" tulip_setup_filter(int n, uint8_t a, uint8_t b, uint8_t c, uint8_t d, uint8_t e, uint8_t f) "%d: %02x:%02x:%02x:%02x:%02x:%02x" + +# lasi_i82596.c +lasi_82596_mem_readw(uint64_t addr, uint32_t ret) "addr=0x%"PRIx64" val=0x%04x" +lasi_82596_mem_writew(uint64_t addr, uint32_t val) "addr=0x%"PRIx64" val=0x%04x" + +# i82596.c +i82596_s_reset(void *s) "%p Reset chip" +i82596_transmit(uint32_t size, uint32_t addr) "size %u from addr 0x%04x" +i82596_receive_analysis(const char *s) "%s" +i82596_receive_packet(size_t sz) "len=%zu" +i82596_new_mac(const char *id_with_mac) "New MAC for: %s" +i82596_set_multicast(uint16_t count) "Added %d multicast entries" +i82596_channel_attention(void *s) "%p: Received CHANNEL ATTENTION" -- cgit v1.1 From c56b6209189f6a4945562dc772cff440f03e4868 Mon Sep 17 00:00:00 2001 From: Sven Schnelle Date: Fri, 20 Dec 2019 22:15:09 +0100 Subject: ps2: accept 'Set Key Make and Break' commands 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 Message-Id: <20191220211512.3289-4-svens@stackframe.org> Signed-off-by: Richard Henderson --- hw/input/ps2.c | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'hw') diff --git a/hw/input/ps2.c b/hw/input/ps2.c index 67f92f6..0b671b6 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) { -- cgit v1.1 From 2a6505b0e79e553ed11570fb484071f3e5293cff Mon Sep 17 00:00:00 2001 From: Sven Schnelle Date: Fri, 20 Dec 2019 22:15:10 +0100 Subject: hppa: add emulation of LASI PS2 controllers Signed-off-by: Sven Schnelle Message-Id: <20191220211512.3289-5-svens@stackframe.org> Signed-off-by: Richard Henderson --- hw/hppa/Kconfig | 1 + hw/hppa/lasi.c | 10 +- hw/input/Kconfig | 3 + hw/input/Makefile.objs | 1 + hw/input/lasips2.c | 291 +++++++++++++++++++++++++++++++++++++++++++++++++ hw/input/ps2.c | 5 + hw/input/trace-events | 5 + 7 files changed, 315 insertions(+), 1 deletion(-) create mode 100644 hw/input/lasips2.c (limited to 'hw') diff --git a/hw/hppa/Kconfig b/hw/hppa/Kconfig index 2a7b38d..7f9be7f 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 5175258..d8d03f9 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 287f088..25c77a1 100644 --- a/hw/input/Kconfig +++ b/hw/input/Kconfig @@ -41,3 +41,6 @@ config VHOST_USER_INPUT config TSC210X bool + +config LASIPS2 + select PS2 diff --git a/hw/input/Makefile.objs b/hw/input/Makefile.objs index a1bc502..f98f635 100644 --- a/hw/input/Makefile.objs +++ b/hw/input/Makefile.objs @@ -15,3 +15,4 @@ common-obj-$(CONFIG_VHOST_USER_INPUT) += vhost-user-input.o obj-$(CONFIG_MILKYMIST) += milkymist-softusb.o obj-$(CONFIG_PXA2XX) += pxa2xx_keypad.o obj-$(CONFIG_TSC210X) += tsc210x.o +obj-$(CONFIG_LASIPS2) += lasips2.o diff --git a/hw/input/lasips2.c b/hw/input/lasips2.c new file mode 100644 index 0000000..0786e57 --- /dev/null +++ b/hw/input/lasips2.c @@ -0,0 +1,291 @@ +/* + * QEMU HP Lasi PS/2 interface emulation + * + * Copyright (c) 2019 Sven Schnelle + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "hw/qdev-properties.h" +#include "hw/hw.h" +#include "hw/input/ps2.h" +#include "hw/input/lasips2.h" +#include "hw/sysbus.h" +#include "exec/hwaddr.h" +#include "sysemu/sysemu.h" +#include "trace.h" +#include "exec/address-spaces.h" +#include "migration/vmstate.h" +#include "hw/irq.h" + + +struct LASIPS2State; +typedef struct LASIPS2Port { + struct LASIPS2State *parent; + MemoryRegion reg; + void *dev; + uint8_t id; + uint8_t control; + uint8_t buf; + bool loopback_rbne; + bool irq; +} LASIPS2Port; + +typedef struct LASIPS2State { + LASIPS2Port kbd; + LASIPS2Port mouse; + qemu_irq irq; +} LASIPS2State; + +static const VMStateDescription vmstate_lasips2 = { + .name = "lasips2", + .version_id = 0, + .minimum_version_id = 0, + .fields = (VMStateField[]) { + VMSTATE_UINT8(kbd.control, LASIPS2State), + VMSTATE_UINT8(kbd.id, LASIPS2State), + VMSTATE_BOOL(kbd.irq, LASIPS2State), + VMSTATE_UINT8(mouse.control, LASIPS2State), + VMSTATE_UINT8(mouse.id, LASIPS2State), + VMSTATE_BOOL(mouse.irq, LASIPS2State), + VMSTATE_END_OF_LIST() + } +}; + +typedef enum { + REG_PS2_ID = 0, + REG_PS2_RCVDATA = 4, + REG_PS2_CONTROL = 8, + REG_PS2_STATUS = 12, +} lasips2_read_reg_t; + +typedef enum { + REG_PS2_RESET = 0, + REG_PS2_XMTDATA = 4, +} lasips2_write_reg_t; + +typedef enum { + LASIPS2_CONTROL_ENABLE = 0x01, + LASIPS2_CONTROL_LOOPBACK = 0x02, + LASIPS2_CONTROL_DIAG = 0x20, + LASIPS2_CONTROL_DATDIR = 0x40, + LASIPS2_CONTROL_CLKDIR = 0x80, +} lasips2_control_reg_t; + +typedef enum { + LASIPS2_STATUS_RBNE = 0x01, + LASIPS2_STATUS_TBNE = 0x02, + LASIPS2_STATUS_TERR = 0x04, + LASIPS2_STATUS_PERR = 0x08, + LASIPS2_STATUS_CMPINTR = 0x10, + LASIPS2_STATUS_DATSHD = 0x40, + LASIPS2_STATUS_CLKSHD = 0x80, +} lasips2_status_reg_t; + +static const char *artist_read_reg_name(uint64_t addr) +{ + switch (addr & 0xc) { + case REG_PS2_ID: + return " PS2_ID"; + + case REG_PS2_RCVDATA: + return " PS2_RCVDATA"; + + case REG_PS2_CONTROL: + return " PS2_CONTROL"; + + case REG_PS2_STATUS: + return " PS2_STATUS"; + + default: + return ""; + } +} + +static const char *artist_write_reg_name(uint64_t addr) +{ + switch (addr & 0x0c) { + case REG_PS2_RESET: + return " PS2_RESET"; + + case REG_PS2_XMTDATA: + return " PS2_XMTDATA"; + + case REG_PS2_CONTROL: + return " PS2_CONTROL"; + + default: + return ""; + } +} + +static void lasips2_update_irq(LASIPS2State *s) +{ + trace_lasips2_intr(s->kbd.irq | s->mouse.irq); + qemu_set_irq(s->irq, s->kbd.irq | s->mouse.irq); +} + +static void lasips2_reg_write(void *opaque, hwaddr addr, uint64_t val, + unsigned size) +{ + LASIPS2Port *port = opaque; + + trace_lasips2_reg_write(size, port->id, addr, + artist_write_reg_name(addr), val); + + switch (addr & 0xc) { + case REG_PS2_CONTROL: + port->control = val; + break; + + case REG_PS2_XMTDATA: + if (port->control & LASIPS2_CONTROL_LOOPBACK) { + port->buf = val; + port->irq = true; + port->loopback_rbne = true; + lasips2_update_irq(port->parent); + break; + } + + if (port->id) { + ps2_write_mouse(port->dev, val); + } else { + ps2_write_keyboard(port->dev, val); + } + break; + + case REG_PS2_RESET: + break; + + default: + qemu_log_mask(LOG_UNIMP, "%s: unknown register 0x%02" HWADDR_PRIx "\n", + __func__, addr); + break; + } +} + +static uint64_t lasips2_reg_read(void *opaque, hwaddr addr, unsigned size) +{ + LASIPS2Port *port = opaque; + uint64_t ret = 0; + + switch (addr & 0xc) { + case REG_PS2_ID: + ret = port->id; + break; + + case REG_PS2_RCVDATA: + if (port->control & LASIPS2_CONTROL_LOOPBACK) { + port->irq = false; + port->loopback_rbne = false; + lasips2_update_irq(port->parent); + ret = port->buf; + break; + } + + ret = ps2_read_data(port->dev); + break; + + case REG_PS2_CONTROL: + ret = port->control; + break; + + case REG_PS2_STATUS: + + ret = LASIPS2_STATUS_DATSHD | LASIPS2_STATUS_CLKSHD; + + if (port->control & LASIPS2_CONTROL_DIAG) { + if (!(port->control & LASIPS2_CONTROL_DATDIR)) { + ret &= ~LASIPS2_STATUS_DATSHD; + } + + if (!(port->control & LASIPS2_CONTROL_CLKDIR)) { + ret &= ~LASIPS2_STATUS_CLKSHD; + } + } + + if (port->control & LASIPS2_CONTROL_LOOPBACK) { + if (port->loopback_rbne) { + ret |= LASIPS2_STATUS_RBNE; + } + } else { + if (!ps2_queue_empty(port->dev)) { + ret |= LASIPS2_STATUS_RBNE; + } + } + + if (port->parent->kbd.irq || port->parent->mouse.irq) { + ret |= LASIPS2_STATUS_CMPINTR; + } + break; + + default: + qemu_log_mask(LOG_UNIMP, "%s: unknown register 0x%02" HWADDR_PRIx "\n", + __func__, addr); + break; + } + trace_lasips2_reg_read(size, port->id, addr, + artist_read_reg_name(addr), ret); + + return ret; +} + +static const MemoryRegionOps lasips2_reg_ops = { + .read = lasips2_reg_read, + .write = lasips2_reg_write, + .impl = { + .min_access_size = 1, + .max_access_size = 4, + }, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void ps2dev_update_irq(void *opaque, int level) +{ + LASIPS2Port *port = opaque; + port->irq = level; + lasips2_update_irq(port->parent); +} + +void lasips2_init(MemoryRegion *address_space, + hwaddr base, qemu_irq irq) +{ + LASIPS2State *s; + + s = g_malloc0(sizeof(LASIPS2State)); + + s->irq = irq; + s->mouse.id = 1; + s->kbd.parent = s; + s->mouse.parent = s; + + vmstate_register(NULL, base, &vmstate_lasips2, s); + + s->kbd.dev = ps2_kbd_init(ps2dev_update_irq, &s->kbd); + s->mouse.dev = ps2_mouse_init(ps2dev_update_irq, &s->mouse); + + memory_region_init_io(&s->kbd.reg, NULL, &lasips2_reg_ops, &s->kbd, + "lasips2-kbd", 0x100); + memory_region_add_subregion(address_space, base, &s->kbd.reg); + + memory_region_init_io(&s->mouse.reg, NULL, &lasips2_reg_ops, &s->mouse, + "lasips2-mouse", 0x100); + memory_region_add_subregion(address_space, base + 0x100, &s->mouse.reg); +} diff --git a/hw/input/ps2.c b/hw/input/ps2.c index 0b671b6..f8746d2 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 cf072fa..a2888fd 100644 --- a/hw/input/trace-events +++ b/hw/input/trace-events @@ -53,3 +53,8 @@ tsc2005_sense(const char *state) "touchscreen sense %s" # virtio-input.c virtio_input_queue_full(void) "queue full" + +# lasips2.c +lasips2_reg_read(unsigned int size, int id, uint64_t addr, const char *name, uint64_t val) "%u %d addr 0x%"PRIx64 "%s -> 0x%"PRIx64 +lasips2_reg_write(unsigned int size, int id, uint64_t addr, const char *name, uint64_t val) "%u %d addr 0x%"PRIx64 "%s <- 0x%"PRIx64 +lasips2_intr(unsigned int val) "%d" -- cgit v1.1 From 0e6de5519491b9f2af45367358eaa79e269d7bf0 Mon Sep 17 00:00:00 2001 From: Helge Deller Date: Sat, 21 Dec 2019 23:25:30 +0100 Subject: hppa: Switch to tulip NIC by default Most HP PA-RISC machines have a Digital DS21142/43 Tulip network card, only some very latest generation machines have an e1000 NIC. Since qemu now provides an emulated tulip card, use that one instead. Signed-off-by: Helge Deller Message-Id: <20191221222530.GB27803@ls3530.fritz.box> Signed-off-by: Richard Henderson --- hw/hppa/machine.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'hw') diff --git a/hw/hppa/machine.c b/hw/hppa/machine.c index 33e3769..c8b1830 100644 --- a/hw/hppa/machine.c +++ b/hw/hppa/machine.c @@ -127,10 +127,10 @@ static void machine_hppa_init(MachineState *machine) dev = DEVICE(pci_create_simple(pci_bus, -1, "lsi53c895a")); lsi53c8xx_handle_legacy_cmdline(dev); - /* Network setup. e1000 is good enough, failing Tulip support. */ + /* Network setup. */ for (i = 0; i < nb_nics; i++) { if (!enable_lasi_lan()) { - pci_nic_init_nofail(&nd_table[i], pci_bus, "e1000", NULL); + pci_nic_init_nofail(&nd_table[i], pci_bus, "tulip", NULL); } } -- cgit v1.1 From 4765384ce33dd7dec6e4419616cddfee5358571a Mon Sep 17 00:00:00 2001 From: Sven Schnelle Date: Fri, 20 Dec 2019 22:15:11 +0100 Subject: hppa: Add emulation of Artist graphics 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. Adjust boot-serial-test to disable graphics, so that SeaBIOS outputs to the serial port, as expected by the test. Signed-off-by: Sven Schnelle Message-Id: <20191220211512.3289-6-svens@stackframe.org> [rth: Merge Helge's test for machine->enable_graphics] Signed-off-by: Richard Henderson --- hw/display/Kconfig | 4 + hw/display/Makefile.objs | 1 + hw/display/artist.c | 1454 ++++++++++++++++++++++++++++++++++++++++++++++ hw/display/trace-events | 9 + hw/hppa/Kconfig | 1 + hw/hppa/hppa_hardware.h | 1 + hw/hppa/machine.c | 10 + 7 files changed, 1480 insertions(+) create mode 100644 hw/display/artist.c (limited to 'hw') diff --git a/hw/display/Kconfig b/hw/display/Kconfig index c500d1f..15d59e1 100644 --- a/hw/display/Kconfig +++ b/hw/display/Kconfig @@ -91,6 +91,10 @@ config TCX config CG3 bool +config ARTIST + bool + select FRAMEBUFFER + config VGA bool diff --git a/hw/display/Makefile.objs b/hw/display/Makefile.objs index f2182e3..5f03dfd 100644 --- a/hw/display/Makefile.objs +++ b/hw/display/Makefile.objs @@ -40,6 +40,7 @@ common-obj-$(CONFIG_SM501) += sm501.o common-obj-$(CONFIG_TCX) += tcx.o common-obj-$(CONFIG_CG3) += cg3.o common-obj-$(CONFIG_NEXTCUBE) += next-fb.o +common-obj-$(CONFIG_ARTIST) += artist.o obj-$(CONFIG_VGA) += vga.o diff --git a/hw/display/artist.c b/hw/display/artist.c new file mode 100644 index 0000000..65be9e3 --- /dev/null +++ b/hw/display/artist.c @@ -0,0 +1,1454 @@ +/* + * QEMU HP Artist Emulation + * + * Copyright (c) 2019 Sven Schnelle + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + */ + +#include "qemu/osdep.h" +#include "qemu-common.h" +#include "qemu/error-report.h" +#include "qemu/typedefs.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "qemu/units.h" +#include "qapi/error.h" +#include "hw/sysbus.h" +#include "hw/loader.h" +#include "hw/qdev-core.h" +#include "hw/qdev-properties.h" +#include "migration/vmstate.h" +#include "ui/console.h" +#include "trace.h" +#include "hw/display/framebuffer.h" + +#define TYPE_ARTIST "artist" +#define ARTIST(obj) OBJECT_CHECK(ARTISTState, (obj), TYPE_ARTIST) + +#ifdef HOST_WORDS_BIGENDIAN +#define ROP8OFF(_i) (3 - (_i)) +#else +#define ROP8OFF +#endif + +struct vram_buffer { + MemoryRegion mr; + uint8_t *data; + int size; + int width; + int height; +}; + +typedef struct ARTISTState { + SysBusDevice parent_obj; + + QemuConsole *con; + MemoryRegion vram_mem; + MemoryRegion mem_as_root; + MemoryRegion reg; + MemoryRegionSection fbsection; + + void *vram_int_mr; + AddressSpace as; + + struct vram_buffer vram_buffer[16]; + + uint16_t width; + uint16_t height; + uint16_t depth; + + uint32_t fg_color; + uint32_t bg_color; + + uint32_t vram_char_y; + uint32_t vram_bitmask; + + uint32_t vram_start; + uint32_t vram_pos; + + uint32_t vram_size; + + uint32_t blockmove_source; + uint32_t blockmove_dest; + uint32_t blockmove_size; + + uint32_t line_size; + uint32_t line_end; + uint32_t line_xy; + uint32_t line_pattern_start; + uint32_t line_pattern_skip; + + uint32_t cursor_pos; + + uint32_t cursor_height; + uint32_t cursor_width; + + uint32_t plane_mask; + + uint32_t reg_100080; + uint32_t reg_300200; + uint32_t reg_300208; + uint32_t reg_300218; + + uint32_t cmap_bm_access; + uint32_t dst_bm_access; + uint32_t src_bm_access; + uint32_t control_plane; + uint32_t transfer_data; + uint32_t image_bitmap_op; + + uint32_t font_write1; + uint32_t font_write2; + uint32_t font_write_pos_y; + + int draw_line_pattern; +} ARTISTState; + +typedef enum { + ARTIST_BUFFER_AP = 1, + ARTIST_BUFFER_OVERLAY = 2, + ARTIST_BUFFER_CURSOR1 = 6, + ARTIST_BUFFER_CURSOR2 = 7, + ARTIST_BUFFER_ATTRIBUTE = 13, + ARTIST_BUFFER_CMAP = 15, +} artist_buffer_t; + +typedef enum { + VRAM_IDX = 0x1004a0, + VRAM_BITMASK = 0x1005a0, + VRAM_WRITE_INCR_X = 0x100600, + VRAM_WRITE_INCR_X2 = 0x100604, + VRAM_WRITE_INCR_Y = 0x100620, + VRAM_START = 0x100800, + BLOCK_MOVE_SIZE = 0x100804, + BLOCK_MOVE_SOURCE = 0x100808, + TRANSFER_DATA = 0x100820, + FONT_WRITE_INCR_Y = 0x1008a0, + VRAM_START_TRIGGER = 0x100a00, + VRAM_SIZE_TRIGGER = 0x100a04, + FONT_WRITE_START = 0x100aa0, + BLOCK_MOVE_DEST_TRIGGER = 0x100b00, + BLOCK_MOVE_SIZE_TRIGGER = 0x100b04, + LINE_XY = 0x100ccc, + PATTERN_LINE_START = 0x100ecc, + LINE_SIZE = 0x100e04, + LINE_END = 0x100e44, + CMAP_BM_ACCESS = 0x118000, + DST_BM_ACCESS = 0x118004, + SRC_BM_ACCESS = 0x118008, + CONTROL_PLANE = 0x11800c, + FG_COLOR = 0x118010, + BG_COLOR = 0x118014, + PLANE_MASK = 0x118018, + IMAGE_BITMAP_OP = 0x11801c, + CURSOR_POS = 0x300100, + CURSOR_CTRL = 0x300104, +} artist_reg_t; + +typedef enum { + ARTIST_ROP_CLEAR = 0, + ARTIST_ROP_COPY = 3, + ARTIST_ROP_XOR = 6, + ARTIST_ROP_NOT_DST = 10, + ARTIST_ROP_SET = 15, +} artist_rop_t; + +#define REG_NAME(_x) case _x: return " "#_x; +static const char *artist_reg_name(uint64_t addr) +{ + switch ((artist_reg_t)addr) { + REG_NAME(VRAM_IDX); + REG_NAME(VRAM_BITMASK); + REG_NAME(VRAM_WRITE_INCR_X); + REG_NAME(VRAM_WRITE_INCR_X2); + REG_NAME(VRAM_WRITE_INCR_Y); + REG_NAME(VRAM_START); + REG_NAME(BLOCK_MOVE_SIZE); + REG_NAME(BLOCK_MOVE_SOURCE); + REG_NAME(FG_COLOR); + REG_NAME(BG_COLOR); + REG_NAME(PLANE_MASK); + REG_NAME(VRAM_START_TRIGGER); + REG_NAME(VRAM_SIZE_TRIGGER); + REG_NAME(BLOCK_MOVE_DEST_TRIGGER); + REG_NAME(BLOCK_MOVE_SIZE_TRIGGER); + REG_NAME(TRANSFER_DATA); + REG_NAME(CONTROL_PLANE); + REG_NAME(IMAGE_BITMAP_OP); + REG_NAME(CMAP_BM_ACCESS); + REG_NAME(DST_BM_ACCESS); + REG_NAME(SRC_BM_ACCESS); + REG_NAME(CURSOR_POS); + REG_NAME(CURSOR_CTRL); + REG_NAME(LINE_XY); + REG_NAME(PATTERN_LINE_START); + REG_NAME(LINE_SIZE); + REG_NAME(LINE_END); + REG_NAME(FONT_WRITE_INCR_Y); + REG_NAME(FONT_WRITE_START); + } + return ""; +} +#undef REG_NAME + +static int16_t artist_get_x(uint32_t reg) +{ + return reg >> 16; +} + +static int16_t artist_get_y(uint32_t reg) +{ + return reg & 0xffff; +} + +static void artist_invalidate_lines(struct vram_buffer *buf, + int starty, int height) +{ + int start = starty * buf->width; + int size = height * buf->width; + + if (start + size <= buf->size) { + memory_region_set_dirty(&buf->mr, start, size); + } +} + +static int vram_write_pix_per_transfer(ARTISTState *s) +{ + if (s->cmap_bm_access) { + return 1 << ((s->cmap_bm_access >> 27) & 0x0f); + } else { + return 1 << ((s->dst_bm_access >> 27) & 0x0f); + } +} + +static int vram_pixel_length(ARTISTState *s) +{ + if (s->cmap_bm_access) { + return (s->cmap_bm_access >> 24) & 0x07; + } else { + return (s->dst_bm_access >> 24) & 0x07; + } +} + +static int vram_write_bufidx(ARTISTState *s) +{ + if (s->cmap_bm_access) { + return (s->cmap_bm_access >> 12) & 0x0f; + } else { + return (s->dst_bm_access >> 12) & 0x0f; + } +} + +static int vram_read_bufidx(ARTISTState *s) +{ + if (s->cmap_bm_access) { + return (s->cmap_bm_access >> 12) & 0x0f; + } else { + return (s->src_bm_access >> 12) & 0x0f; + } +} + +static struct vram_buffer *vram_read_buffer(ARTISTState *s) +{ + return &s->vram_buffer[vram_read_bufidx(s)]; +} + +static struct vram_buffer *vram_write_buffer(ARTISTState *s) +{ + return &s->vram_buffer[vram_write_bufidx(s)]; +} + +static uint8_t artist_get_color(ARTISTState *s) +{ + if (s->image_bitmap_op & 2) { + return s->fg_color; + } else { + return s->bg_color; + } +} + +static artist_rop_t artist_get_op(ARTISTState *s) +{ + return (s->image_bitmap_op >> 8) & 0xf; +} + +static void artist_rop8(ARTISTState *s, uint8_t *dst, uint8_t val) +{ + + const artist_rop_t op = artist_get_op(s); + uint8_t plane_mask = s->plane_mask & 0xff; + + switch (op) { + case ARTIST_ROP_CLEAR: + *dst &= ~plane_mask; + break; + + case ARTIST_ROP_COPY: + *dst &= ~plane_mask; + *dst |= val & plane_mask; + break; + + case ARTIST_ROP_XOR: + *dst ^= val & plane_mask; + break; + + case ARTIST_ROP_NOT_DST: + *dst ^= plane_mask; + break; + + case ARTIST_ROP_SET: + *dst |= plane_mask; + break; + + default: + qemu_log_mask(LOG_UNIMP, "%s: unsupported rop %d\n", __func__, op); + break; + } +} + +static void artist_get_cursor_pos(ARTISTState *s, int *x, int *y) +{ + /* + * Don't know whether these magic offset values are configurable via + * some register. They are the same for all resolutions, so don't + * bother about it. + */ + + *y = 0x47a - artist_get_y(s->cursor_pos); + *x = ((artist_get_x(s->cursor_pos) - 338) / 2); + + if (*x > s->width) { + *x = 0; + } + + if (*y > s->height) { + *y = 0; + } +} + +static void artist_invalidate_cursor(ARTISTState *s) +{ + int x, y; + artist_get_cursor_pos(s, &x, &y); + artist_invalidate_lines(&s->vram_buffer[ARTIST_BUFFER_AP], + y, s->cursor_height); +} + +static void vram_bit_write(ARTISTState *s, int posx, int posy, bool incr_x, + int size, uint32_t data) +{ + struct vram_buffer *buf; + uint32_t vram_bitmask = s->vram_bitmask; + int mask, i, pix_count, pix_length, offset, height, width; + uint8_t *data8, *p; + + pix_count = vram_write_pix_per_transfer(s); + pix_length = vram_pixel_length(s); + + buf = vram_write_buffer(s); + height = buf->height; + width = buf->width; + + if (s->cmap_bm_access) { + offset = s->vram_pos; + } else { + offset = posy * width + posx; + } + + if (!buf->size) { + qemu_log("write to non-existent buffer\n"); + return; + } + + p = buf->data; + + if (pix_count > size * 8) { + pix_count = size * 8; + } + + if (posy * width + posx + pix_count > buf->size) { + qemu_log("write outside bounds: wants %dx%d, max size %dx%d\n", + posx, posy, width, height); + return; + } + + + switch (pix_length) { + case 0: + if (s->image_bitmap_op & 0x20000000) { + data &= vram_bitmask; + } + + for (i = 0; i < pix_count; i++) { + artist_rop8(s, p + offset + pix_count - 1 - i, + (data & 1) ? (s->plane_mask >> 24) : 0); + data >>= 1; + } + memory_region_set_dirty(&buf->mr, offset, pix_count); + break; + + case 3: + if (s->cmap_bm_access) { + *(uint32_t *)(p + offset) = data; + break; + } + data8 = (uint8_t *)&data; + + for (i = 3; i >= 0; i--) { + if (!(s->image_bitmap_op & 0x20000000) || + s->vram_bitmask & (1 << (28 + i))) { + artist_rop8(s, p + offset + 3 - i, data8[ROP8OFF(i)]); + } + } + memory_region_set_dirty(&buf->mr, offset, 3); + break; + + case 6: + switch (size) { + default: + case 4: + vram_bitmask = s->vram_bitmask; + break; + + case 2: + vram_bitmask = s->vram_bitmask >> 16; + break; + + case 1: + vram_bitmask = s->vram_bitmask >> 24; + break; + } + + for (i = 0; i < pix_count; i++) { + mask = 1 << (pix_count - 1 - i); + + if (!(s->image_bitmap_op & 0x20000000) || + (vram_bitmask & mask)) { + if (data & mask) { + artist_rop8(s, p + offset + i, s->fg_color); + } else { + if (!(s->image_bitmap_op & 0x10000002)) { + artist_rop8(s, p + offset + i, s->bg_color); + } + } + } + } + memory_region_set_dirty(&buf->mr, offset, pix_count); + break; + + default: + qemu_log_mask(LOG_UNIMP, "%s: unknown pixel length %d\n", + __func__, pix_length); + break; + } + + if (incr_x) { + if (s->cmap_bm_access) { + s->vram_pos += 4; + } else { + s->vram_pos += pix_count << 2; + } + } + + if (vram_write_bufidx(s) == ARTIST_BUFFER_CURSOR1 || + vram_write_bufidx(s) == ARTIST_BUFFER_CURSOR2) { + artist_invalidate_cursor(s); + } +} + +static void block_move(ARTISTState *s, int source_x, int source_y, int dest_x, + int dest_y, int width, int height) +{ + struct vram_buffer *buf; + int line, endline, lineincr, startcolumn, endcolumn, columnincr, column; + uint32_t dst, src; + + trace_artist_block_move(source_x, source_y, dest_x, dest_y, width, height); + + if (s->control_plane != 0) { + /* We don't support CONTROL_PLANE accesses */ + qemu_log_mask(LOG_UNIMP, "%s: CONTROL_PLANE: %08x\n", __func__, + s->control_plane); + return; + } + + buf = &s->vram_buffer[ARTIST_BUFFER_AP]; + + if (dest_y > source_y) { + /* move down */ + line = height - 1; + endline = -1; + lineincr = -1; + } else { + /* move up */ + line = 0; + endline = height; + lineincr = 1; + } + + if (dest_x > source_x) { + /* move right */ + startcolumn = width - 1; + endcolumn = -1; + columnincr = -1; + } else { + /* move left */ + startcolumn = 0; + endcolumn = width; + columnincr = 1; + } + + for ( ; line != endline; line += lineincr) { + src = source_x + ((line + source_y) * buf->width); + dst = dest_x + ((line + dest_y) * buf->width); + + for (column = startcolumn; column != endcolumn; column += columnincr) { + if (dst + column > buf->size || src + column > buf->size) { + continue; + } + artist_rop8(s, buf->data + dst + column, buf->data[src + column]); + } + } + + artist_invalidate_lines(buf, dest_y, height); +} + +static void fill_window(ARTISTState *s, int startx, int starty, + int width, int height) +{ + uint32_t offset; + uint8_t color = artist_get_color(s); + struct vram_buffer *buf; + int x, y; + + trace_artist_fill_window(startx, starty, width, height, + s->image_bitmap_op, s->control_plane); + + if (s->control_plane != 0) { + /* We don't support CONTROL_PLANE accesses */ + qemu_log_mask(LOG_UNIMP, "%s: CONTROL_PLANE: %08x\n", __func__, + s->control_plane); + return; + } + + if (s->reg_100080 == 0x7d) { + /* + * Not sure what this register really does, but + * 0x7d seems to enable autoincremt of the Y axis + * by the current block move height. + */ + height = artist_get_y(s->blockmove_size); + s->vram_start += height; + } + + buf = &s->vram_buffer[ARTIST_BUFFER_AP]; + + for (y = starty; y < starty + height; y++) { + offset = y * s->width; + + for (x = startx; x < startx + width; x++) { + artist_rop8(s, buf->data + offset + x, color); + } + } + artist_invalidate_lines(buf, starty, height); +} + +static void draw_line(ARTISTState *s, int x1, int y1, int x2, int y2, + bool update_start, int skip_pix, int max_pix) +{ + struct vram_buffer *buf; + uint8_t color = artist_get_color(s); + int dx, dy, t, e, x, y, incy, diago, horiz; + bool c1; + uint8_t *p; + + + if (update_start) { + s->vram_start = (x2 << 16) | y2; + } + + buf = &s->vram_buffer[ARTIST_BUFFER_AP]; + + c1 = false; + incy = 1; + + if (x2 > x1) { + dx = x2 - x1; + } else { + dx = x1 - x2; + } + if (y2 > y1) { + dy = y2 - y1; + } else { + dy = y1 - y2; + } + if (dy > dx) { + t = y2; + y2 = x2; + x2 = t; + + t = y1; + y1 = x1; + x1 = t; + + t = dx; + dx = dy; + dy = t; + + c1 = true; + } + + if (x1 > x2) { + t = y2; + y2 = y1; + y1 = t; + + t = x1; + x1 = x2; + x2 = t; + } + + horiz = dy << 1; + diago = (dy - dx) << 1; + e = (dy << 1) - dx; + + if (y1 <= y2) { + incy = 1; + } else { + incy = -1; + } + x = x1; + y = y1; + + do { + if (c1) { + p = buf->data + x * s->width + y; + } else { + p = buf->data + y * s->width + x; + } + + if (skip_pix > 0) { + skip_pix--; + } else { + artist_rop8(s, p, color); + } + + if (e > 0) { + artist_invalidate_lines(buf, y, 1); + y += incy; + e += diago; + } else { + e += horiz; + } + x++; + } while (x <= x2 && (max_pix == -1 || --max_pix > 0)); +} + +static void draw_line_pattern_start(ARTISTState *s) +{ + + int startx = artist_get_x(s->vram_start); + int starty = artist_get_y(s->vram_start); + int endx = artist_get_x(s->blockmove_size); + int endy = artist_get_y(s->blockmove_size); + int pstart = s->line_pattern_start >> 16; + + trace_artist_draw_line(startx, starty, endx, endy); + draw_line(s, startx, starty, endx, endy, false, -1, pstart); + s->line_pattern_skip = pstart; +} + +static void draw_line_pattern_next(ARTISTState *s) +{ + + int startx = artist_get_x(s->vram_start); + int starty = artist_get_y(s->vram_start); + int endx = artist_get_x(s->blockmove_size); + int endy = artist_get_y(s->blockmove_size); + int line_xy = s->line_xy >> 16; + + trace_artist_draw_line(startx, starty, endx, endy); + draw_line(s, startx, starty, endx, endy, false, s->line_pattern_skip, + s->line_pattern_skip + line_xy); + s->line_pattern_skip += line_xy; + s->image_bitmap_op ^= 2; +} + +static void draw_line_size(ARTISTState *s, bool update_start) +{ + + int startx = artist_get_x(s->vram_start); + int starty = artist_get_y(s->vram_start); + int endx = artist_get_x(s->line_size); + int endy = artist_get_y(s->line_size); + + trace_artist_draw_line(startx, starty, endx, endy); + draw_line(s, startx, starty, endx, endy, update_start, -1, -1); +} + +static void draw_line_xy(ARTISTState *s, bool update_start) +{ + + int startx = artist_get_x(s->vram_start); + int starty = artist_get_y(s->vram_start); + int sizex = artist_get_x(s->blockmove_size); + int sizey = artist_get_y(s->blockmove_size); + int linexy = s->line_xy >> 16; + int endx, endy; + + endx = startx; + endy = starty; + + if (sizex > 0) { + endx = startx + linexy; + } + + if (sizex < 0) { + endx = startx; + startx -= linexy; + } + + if (sizey > 0) { + endy = starty + linexy; + } + + if (sizey < 0) { + endy = starty; + starty -= linexy; + } + + if (startx < 0) { + startx = 0; + } + + if (endx < 0) { + endx = 0; + } + + if (starty < 0) { + starty = 0; + } + + if (endy < 0) { + endy = 0; + } + + + if (endx < 0) { + return; + } + + if (endy < 0) { + return; + } + + trace_artist_draw_line(startx, starty, endx, endy); + draw_line(s, startx, starty, endx, endy, false, -1, -1); +} + +static void draw_line_end(ARTISTState *s, bool update_start) +{ + + int startx = artist_get_x(s->vram_start); + int starty = artist_get_y(s->vram_start); + int endx = artist_get_x(s->line_end); + int endy = artist_get_y(s->line_end); + + trace_artist_draw_line(startx, starty, endx, endy); + draw_line(s, startx, starty, endx, endy, update_start, -1, -1); +} + +static void font_write16(ARTISTState *s, uint16_t val) +{ + struct vram_buffer *buf; + uint32_t color = (s->image_bitmap_op & 2) ? s->fg_color : s->bg_color; + uint16_t mask; + int i; + + int startx = artist_get_x(s->vram_start); + int starty = artist_get_y(s->vram_start) + s->font_write_pos_y; + int offset = starty * s->width + startx; + + buf = &s->vram_buffer[ARTIST_BUFFER_AP]; + + if (offset + 16 > buf->size) { + return; + } + + for (i = 0; i < 16; i++) { + mask = 1 << (15 - i); + if (val & mask) { + artist_rop8(s, buf->data + offset + i, color); + } else { + if (!(s->image_bitmap_op & 0x20000000)) { + artist_rop8(s, buf->data + offset + i, s->bg_color); + } + } + } + artist_invalidate_lines(buf, starty, 1); +} + +static void font_write(ARTISTState *s, uint32_t val) +{ + font_write16(s, val >> 16); + if (++s->font_write_pos_y == artist_get_y(s->blockmove_size)) { + s->vram_start += (s->blockmove_size & 0xffff0000); + return; + } + + font_write16(s, val & 0xffff); + if (++s->font_write_pos_y == artist_get_y(s->blockmove_size)) { + s->vram_start += (s->blockmove_size & 0xffff0000); + return; + } +} + +static void combine_write_reg(hwaddr addr, uint64_t val, int size, void *out) +{ + /* + * FIXME: is there a qemu helper for this? + */ + +#ifndef HOST_WORDS_BIGENDIAN + addr ^= 3; +#endif + + switch (size) { + case 1: + *(uint8_t *)(out + (addr & 3)) = val; + break; + + case 2: + *(uint16_t *)(out + (addr & 2)) = val; + break; + + case 4: + *(uint32_t *)out = val; + break; + + default: + qemu_log_mask(LOG_UNIMP, "unsupported write size: %d\n", size); + } +} + +static void artist_reg_write(void *opaque, hwaddr addr, uint64_t val, + unsigned size) +{ + ARTISTState *s = opaque; + int posx, posy; + int width, height; + + trace_artist_reg_write(size, addr, artist_reg_name(addr & ~3ULL), val); + + switch (addr & ~3ULL) { + case 0x100080: + combine_write_reg(addr, val, size, &s->reg_100080); + break; + + case FG_COLOR: + combine_write_reg(addr, val, size, &s->fg_color); + break; + + case BG_COLOR: + combine_write_reg(addr, val, size, &s->bg_color); + break; + + case VRAM_BITMASK: + combine_write_reg(addr, val, size, &s->vram_bitmask); + break; + + case VRAM_WRITE_INCR_Y: + posx = (s->vram_pos >> 2) & 0x7ff; + posy = (s->vram_pos >> 13) & 0x3ff; + vram_bit_write(s, posx, posy + s->vram_char_y++, false, size, val); + break; + + case VRAM_WRITE_INCR_X: + case VRAM_WRITE_INCR_X2: + posx = (s->vram_pos >> 2) & 0x7ff; + posy = (s->vram_pos >> 13) & 0x3ff; + vram_bit_write(s, posx, posy + s->vram_char_y, true, size, val); + break; + + case VRAM_IDX: + combine_write_reg(addr, val, size, &s->vram_pos); + s->vram_char_y = 0; + s->draw_line_pattern = 0; + break; + + case VRAM_START: + combine_write_reg(addr, val, size, &s->vram_start); + s->draw_line_pattern = 0; + break; + + case VRAM_START_TRIGGER: + combine_write_reg(addr, val, size, &s->vram_start); + fill_window(s, artist_get_x(s->vram_start), + artist_get_y(s->vram_start), + artist_get_x(s->blockmove_size), + artist_get_y(s->blockmove_size)); + break; + + case VRAM_SIZE_TRIGGER: + combine_write_reg(addr, val, size, &s->vram_size); + + if (size == 2 && !(addr & 2)) { + height = artist_get_y(s->blockmove_size); + } else { + height = artist_get_y(s->vram_size); + } + + if (size == 2 && (addr & 2)) { + width = artist_get_x(s->blockmove_size); + } else { + width = artist_get_x(s->vram_size); + } + + fill_window(s, artist_get_x(s->vram_start), + artist_get_y(s->vram_start), + width, height); + break; + + case LINE_XY: + combine_write_reg(addr, val, size, &s->line_xy); + if (s->draw_line_pattern) { + draw_line_pattern_next(s); + } else { + draw_line_xy(s, true); + } + break; + + case PATTERN_LINE_START: + combine_write_reg(addr, val, size, &s->line_pattern_start); + s->draw_line_pattern = 1; + draw_line_pattern_start(s); + break; + + case LINE_SIZE: + combine_write_reg(addr, val, size, &s->line_size); + draw_line_size(s, true); + break; + + case LINE_END: + combine_write_reg(addr, val, size, &s->line_end); + draw_line_end(s, true); + break; + + case BLOCK_MOVE_SIZE: + combine_write_reg(addr, val, size, &s->blockmove_size); + break; + + case BLOCK_MOVE_SOURCE: + combine_write_reg(addr, val, size, &s->blockmove_source); + break; + + case BLOCK_MOVE_DEST_TRIGGER: + combine_write_reg(addr, val, size, &s->blockmove_dest); + + block_move(s, artist_get_x(s->blockmove_source), + artist_get_y(s->blockmove_source), + artist_get_x(s->blockmove_dest), + artist_get_y(s->blockmove_dest), + artist_get_x(s->blockmove_size), + artist_get_y(s->blockmove_size)); + break; + + case BLOCK_MOVE_SIZE_TRIGGER: + combine_write_reg(addr, val, size, &s->blockmove_size); + + block_move(s, + artist_get_x(s->blockmove_source), + artist_get_y(s->blockmove_source), + artist_get_x(s->vram_start), + artist_get_y(s->vram_start), + artist_get_x(s->blockmove_size), + artist_get_y(s->blockmove_size)); + break; + + case PLANE_MASK: + combine_write_reg(addr, val, size, &s->plane_mask); + break; + + case CMAP_BM_ACCESS: + combine_write_reg(addr, val, size, &s->cmap_bm_access); + break; + + case DST_BM_ACCESS: + combine_write_reg(addr, val, size, &s->dst_bm_access); + s->cmap_bm_access = 0; + break; + + case SRC_BM_ACCESS: + combine_write_reg(addr, val, size, &s->src_bm_access); + s->cmap_bm_access = 0; + break; + + case CONTROL_PLANE: + combine_write_reg(addr, val, size, &s->control_plane); + break; + + case TRANSFER_DATA: + combine_write_reg(addr, val, size, &s->transfer_data); + break; + + case 0x300200: + combine_write_reg(addr, val, size, &s->reg_300200); + break; + + case 0x300208: + combine_write_reg(addr, val, size, &s->reg_300208); + break; + + case 0x300218: + combine_write_reg(addr, val, size, &s->reg_300218); + break; + + case CURSOR_POS: + artist_invalidate_cursor(s); + combine_write_reg(addr, val, size, &s->cursor_pos); + artist_invalidate_cursor(s); + break; + + case CURSOR_CTRL: + break; + + case IMAGE_BITMAP_OP: + combine_write_reg(addr, val, size, &s->image_bitmap_op); + break; + + case FONT_WRITE_INCR_Y: + combine_write_reg(addr, val, size, &s->font_write1); + font_write(s, s->font_write1); + break; + + case FONT_WRITE_START: + combine_write_reg(addr, val, size, &s->font_write2); + s->font_write_pos_y = 0; + font_write(s, s->font_write2); + break; + + case 300104: + break; + + default: + qemu_log_mask(LOG_UNIMP, "%s: unknown register: reg=%08" HWADDR_PRIx + " val=%08" PRIx64 " size=%d\n", + __func__, addr, val, size); + break; + } +} + +static uint64_t combine_read_reg(hwaddr addr, int size, void *in) +{ + /* + * FIXME: is there a qemu helper for this? + */ + +#ifndef HOST_WORDS_BIGENDIAN + addr ^= 3; +#endif + + switch (size) { + case 1: + return *(uint8_t *)(in + (addr & 3)); + + case 2: + return *(uint16_t *)(in + (addr & 2)); + + case 4: + return *(uint32_t *)in; + + default: + qemu_log_mask(LOG_UNIMP, "unsupported read size: %d\n", size); + return 0; + } +} + +static uint64_t artist_reg_read(void *opaque, hwaddr addr, unsigned size) +{ + ARTISTState *s = opaque; + uint32_t val = 0; + + switch (addr & ~3ULL) { + /* Unknown status registers */ + case 0: + break; + + case 0x211110: + val = (s->width << 16) | s->height; + if (s->depth == 1) { + val |= 1 << 31; + } + break; + + case 0x100000: + case 0x300000: + case 0x300004: + case 0x300308: + case 0x380000: + break; + + case 0x300008: + case 0x380008: + /* + * FIFO ready flag. we're not emulating the FIFOs + * so we're always ready + */ + val = 0x10; + break; + + case 0x300200: + val = s->reg_300200; + break; + + case 0x300208: + val = s->reg_300208; + break; + + case 0x300218: + val = s->reg_300218; + break; + + case 0x30023c: + val = 0xac4ffdac; + break; + + case 0x380004: + /* 0x02000000 Buserror */ + val = 0x6dc20006; + break; + + default: + qemu_log_mask(LOG_UNIMP, "%s: unknown register: %08" HWADDR_PRIx + " size %d\n", __func__, addr, size); + break; + } + val = combine_read_reg(addr, size, &val); + trace_artist_reg_read(size, addr, artist_reg_name(addr & ~3ULL), val); + return val; +} + +static void artist_vram_write(void *opaque, hwaddr addr, uint64_t val, + unsigned size) +{ + ARTISTState *s = opaque; + struct vram_buffer *buf; + int posy = (addr >> 11) & 0x3ff; + int posx = addr & 0x7ff; + uint32_t offset; + trace_artist_vram_write(size, addr, val); + + if (s->cmap_bm_access) { + buf = &s->vram_buffer[ARTIST_BUFFER_CMAP]; + if (addr + 3 < buf->size) { + *(uint32_t *)(buf->data + addr) = val; + } + return; + } + + buf = vram_write_buffer(s); + if (!buf->size) { + return; + } + + if (posy > buf->height || posx > buf->width) { + return; + } + + offset = posy * buf->width + posx; + switch (size) { + case 4: + *(uint32_t *)(buf->data + offset) = be32_to_cpu(val); + memory_region_set_dirty(&buf->mr, offset, 4); + break; + case 2: + *(uint16_t *)(buf->data + offset) = be16_to_cpu(val); + memory_region_set_dirty(&buf->mr, offset, 2); + break; + case 1: + *(uint8_t *)(buf->data + offset) = val; + memory_region_set_dirty(&buf->mr, offset, 1); + break; + default: + break; + } +} + +static uint64_t artist_vram_read(void *opaque, hwaddr addr, unsigned size) +{ + ARTISTState *s = opaque; + struct vram_buffer *buf; + uint64_t val; + int posy, posx; + + if (s->cmap_bm_access) { + buf = &s->vram_buffer[ARTIST_BUFFER_CMAP]; + val = *(uint32_t *)(buf->data + addr); + trace_artist_vram_read(size, addr, 0, 0, val); + return 0; + } + + buf = vram_read_buffer(s); + if (!buf->size) { + return 0; + } + + posy = (addr >> 13) & 0x3ff; + posx = (addr >> 2) & 0x7ff; + + if (posy > buf->height || posx > buf->width) { + return 0; + } + + val = cpu_to_be32(*(uint32_t *)(buf->data + posy * buf->width + posx)); + trace_artist_vram_read(size, addr, posx, posy, val); + return val; +} + +static const MemoryRegionOps artist_reg_ops = { + .read = artist_reg_read, + .write = artist_reg_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + }, +}; + +static const MemoryRegionOps artist_vram_ops = { + .read = artist_vram_read, + .write = artist_vram_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + }, +}; + +static void artist_draw_cursor(ARTISTState *s) +{ + DisplaySurface *surface = qemu_console_surface(s->con); + uint32_t *data = (uint32_t *)surface_data(surface); + struct vram_buffer *cursor0, *cursor1 , *buf; + int cx, cy, cursor_pos_x, cursor_pos_y; + + cursor0 = &s->vram_buffer[ARTIST_BUFFER_CURSOR1]; + cursor1 = &s->vram_buffer[ARTIST_BUFFER_CURSOR2]; + buf = &s->vram_buffer[ARTIST_BUFFER_AP]; + + artist_get_cursor_pos(s, &cursor_pos_x, &cursor_pos_y); + + for (cy = 0; cy < s->cursor_height; cy++) { + + for (cx = 0; cx < s->cursor_width; cx++) { + + if (cursor_pos_y + cy < 0 || + cursor_pos_x + cx < 0 || + cursor_pos_y + cy > buf->height - 1 || + cursor_pos_x + cx > buf->width) { + continue; + } + + int dstoffset = (cursor_pos_y + cy) * s->width + + (cursor_pos_x + cx); + + if (cursor0->data[cy * cursor0->width + cx]) { + data[dstoffset] = 0; + } else { + if (cursor1->data[cy * cursor1->width + cx]) { + data[dstoffset] = 0xffffff; + } + } + } + } +} + +static void artist_draw_line(void *opaque, uint8_t *d, const uint8_t *src, + int width, int pitch) +{ + ARTISTState *s = ARTIST(opaque); + uint32_t *cmap, *data = (uint32_t *)d; + int x; + + cmap = (uint32_t *)(s->vram_buffer[ARTIST_BUFFER_CMAP].data + 0x400); + + for (x = 0; x < s->width; x++) { + *data++ = cmap[*src++]; + } +} + +static void artist_update_display(void *opaque) +{ + ARTISTState *s = opaque; + DisplaySurface *surface = qemu_console_surface(s->con); + int first = 0, last; + + + framebuffer_update_display(surface, &s->fbsection, s->width, s->height, + s->width, s->width * 4, 0, 0, artist_draw_line, + s, &first, &last); + + artist_draw_cursor(s); + + dpy_gfx_update(s->con, 0, 0, s->width, s->height); +} + +static void artist_invalidate(void *opaque) +{ + ARTISTState *s = ARTIST(opaque); + struct vram_buffer *buf = &s->vram_buffer[ARTIST_BUFFER_AP]; + memory_region_set_dirty(&buf->mr, 0, buf->size); +} + +static const GraphicHwOps artist_ops = { + .invalidate = artist_invalidate, + .gfx_update = artist_update_display, +}; + +static void artist_initfn(Object *obj) +{ + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + ARTISTState *s = ARTIST(obj); + + memory_region_init_io(&s->reg, obj, &artist_reg_ops, s, "artist.reg", + 4 * MiB); + memory_region_init_io(&s->vram_mem, obj, &artist_vram_ops, s, "artist.vram", + 8 * MiB); + sysbus_init_mmio(sbd, &s->reg); + sysbus_init_mmio(sbd, &s->vram_mem); +} + +static void artist_create_buffer(ARTISTState *s, const char *name, + hwaddr *offset, unsigned int idx, + int width, int height) +{ + struct vram_buffer *buf = s->vram_buffer + idx; + + memory_region_init_ram(&buf->mr, NULL, name, width * height, + &error_fatal); + memory_region_add_subregion_overlap(&s->mem_as_root, *offset, &buf->mr, 0); + + buf->data = memory_region_get_ram_ptr(&buf->mr); + buf->size = height * width; + buf->width = width; + buf->height = height; + + *offset += buf->size; +} + +static void artist_realizefn(DeviceState *dev, Error **errp) +{ + ARTISTState *s = ARTIST(dev); + struct vram_buffer *buf; + hwaddr offset = 0; + + memory_region_init(&s->mem_as_root, OBJECT(dev), "artist", ~0ull); + address_space_init(&s->as, &s->mem_as_root, "artist"); + + artist_create_buffer(s, "cmap", &offset, ARTIST_BUFFER_CMAP, 2048, 4); + artist_create_buffer(s, "ap", &offset, ARTIST_BUFFER_AP, + s->width, s->height); + artist_create_buffer(s, "cursor1", &offset, ARTIST_BUFFER_CURSOR1, 64, 64); + artist_create_buffer(s, "cursor2", &offset, ARTIST_BUFFER_CURSOR2, 64, 64); + artist_create_buffer(s, "attribute", &offset, ARTIST_BUFFER_ATTRIBUTE, + 64, 64); + + buf = &s->vram_buffer[ARTIST_BUFFER_AP]; + framebuffer_update_memory_section(&s->fbsection, &buf->mr, 0, + buf->width, buf->height); + /* + * no idea whether the cursor is fixed size or not, so assume 32x32 which + * seems sufficient for HP-UX X11. + */ + s->cursor_height = 32; + s->cursor_width = 32; + + s->con = graphic_console_init(DEVICE(dev), 0, &artist_ops, s); + qemu_console_resize(s->con, s->width, s->height); +} + +static int vmstate_artist_post_load(void *opaque, int version_id) +{ + artist_invalidate(opaque); + return 0; +} + +static const VMStateDescription vmstate_artist = { + .name = "artist", + .version_id = 1, + .minimum_version_id = 1, + .post_load = vmstate_artist_post_load, + .fields = (VMStateField[]) { + VMSTATE_UINT16(height, ARTISTState), + VMSTATE_UINT16(width, ARTISTState), + VMSTATE_UINT16(depth, ARTISTState), + VMSTATE_UINT32(fg_color, ARTISTState), + VMSTATE_UINT32(bg_color, ARTISTState), + VMSTATE_UINT32(vram_char_y, ARTISTState), + VMSTATE_UINT32(vram_bitmask, ARTISTState), + VMSTATE_UINT32(vram_start, ARTISTState), + VMSTATE_UINT32(vram_pos, ARTISTState), + VMSTATE_UINT32(vram_size, ARTISTState), + VMSTATE_UINT32(blockmove_source, ARTISTState), + VMSTATE_UINT32(blockmove_dest, ARTISTState), + VMSTATE_UINT32(blockmove_size, ARTISTState), + VMSTATE_UINT32(line_size, ARTISTState), + VMSTATE_UINT32(line_end, ARTISTState), + VMSTATE_UINT32(line_xy, ARTISTState), + VMSTATE_UINT32(cursor_pos, ARTISTState), + VMSTATE_UINT32(cursor_height, ARTISTState), + VMSTATE_UINT32(cursor_width, ARTISTState), + VMSTATE_UINT32(plane_mask, ARTISTState), + VMSTATE_UINT32(reg_100080, ARTISTState), + VMSTATE_UINT32(reg_300200, ARTISTState), + VMSTATE_UINT32(reg_300208, ARTISTState), + VMSTATE_UINT32(reg_300218, ARTISTState), + VMSTATE_UINT32(cmap_bm_access, ARTISTState), + VMSTATE_UINT32(dst_bm_access, ARTISTState), + VMSTATE_UINT32(src_bm_access, ARTISTState), + VMSTATE_UINT32(control_plane, ARTISTState), + VMSTATE_UINT32(transfer_data, ARTISTState), + VMSTATE_UINT32(image_bitmap_op, ARTISTState), + VMSTATE_UINT32(font_write1, ARTISTState), + VMSTATE_UINT32(font_write2, ARTISTState), + VMSTATE_UINT32(font_write_pos_y, ARTISTState), + VMSTATE_END_OF_LIST() + } +}; + +static Property artist_properties[] = { + DEFINE_PROP_UINT16("width", ARTISTState, width, 1280), + DEFINE_PROP_UINT16("height", ARTISTState, height, 1024), + DEFINE_PROP_UINT16("depth", ARTISTState, depth, 8), + DEFINE_PROP_END_OF_LIST(), +}; + +static void artist_reset(DeviceState *qdev) +{ +} + +static void artist_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = artist_realizefn; + dc->vmsd = &vmstate_artist; + dc->reset = artist_reset; + device_class_set_props(dc, artist_properties); +} + +static const TypeInfo artist_info = { + .name = TYPE_ARTIST, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(ARTISTState), + .instance_init = artist_initfn, + .class_init = artist_class_init, +}; + +static void artist_register_types(void) +{ + type_register_static(&artist_info); +} + +type_init(artist_register_types) diff --git a/hw/display/trace-events b/hw/display/trace-events index ba7787b..e6e22be 100644 --- a/hw/display/trace-events +++ b/hw/display/trace-events @@ -142,3 +142,12 @@ sii9022_switch_mode(const char *mode) "mode: %s" # ati.c ati_mm_read(unsigned int size, uint64_t addr, const char *name, uint64_t val) "%u 0x%"PRIx64 " %s -> 0x%"PRIx64 ati_mm_write(unsigned int size, uint64_t addr, const char *name, uint64_t val) "%u 0x%"PRIx64 " %s <- 0x%"PRIx64 + +# artist.c +artist_reg_read(unsigned int size, uint64_t addr, const char *name, uint64_t val) "%u 0x%"PRIx64 "%s -> 0x%"PRIx64 +artist_reg_write(unsigned int size, uint64_t addr, const char *name, uint64_t val) "%u 0x%"PRIx64 "%s <- 0x%"PRIx64 +artist_vram_read(unsigned int size, uint64_t addr, int posx, int posy, uint64_t val) "%u 0x%"PRIx64 " %ux%u-> 0x%"PRIx64 +artist_vram_write(unsigned int size, uint64_t addr, uint64_t val) "%u 0x%"PRIx64 " <- 0x%"PRIx64 +artist_fill_window(unsigned int start_x, unsigned int start_y, unsigned int width, unsigned int height, uint32_t op, uint32_t ctlpln) "start=%ux%u length=%ux%u op=0x%08x ctlpln=0x%08x" +artist_block_move(unsigned int start_x, unsigned int start_y, unsigned int dest_x, unsigned int dest_y, unsigned int width, unsigned int height) "source %ux%u -> dest %ux%u size %ux%u" +artist_draw_line(unsigned int start_x, unsigned int start_y, unsigned int end_x, unsigned int end_y) "%ux%u %ux%u" diff --git a/hw/hppa/Kconfig b/hw/hppa/Kconfig index 7f9be7f..82178c7 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 507f91e..4a2fe2d 100644 --- a/hw/hppa/hppa_hardware.h +++ b/hw/hppa/hppa_hardware.h @@ -22,6 +22,7 @@ #define LASI_PS2KBD_HPA 0xffd08000 #define LASI_PS2MOU_HPA 0xffd08100 #define LASI_GFX_HPA 0xf8000000 +#define ARTIST_FB_ADDR 0xf9000000 #define CPU_HPA 0xfffb0000 #define MEMORY_HPA 0xfffbf000 diff --git a/hw/hppa/machine.c b/hw/hppa/machine.c index c8b1830..a35527c 100644 --- a/hw/hppa/machine.c +++ b/hw/hppa/machine.c @@ -75,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; @@ -127,6 +128,15 @@ static void machine_hppa_init(MachineState *machine) dev = DEVICE(pci_create_simple(pci_bus, -1, "lsi53c895a")); lsi53c8xx_handle_legacy_cmdline(dev); + /* Graphics setup. */ + if (machine->enable_graphics && vga_interface_type != VGA_NONE) { + dev = qdev_create(NULL, "artist"); + qdev_init_nofail(dev); + s = SYS_BUS_DEVICE(dev); + sysbus_mmio_map(s, 0, LASI_GFX_HPA); + sysbus_mmio_map(s, 1, ARTIST_FB_ADDR); + } + /* Network setup. */ for (i = 0; i < nb_nics; i++) { if (!enable_lasi_lan()) { -- cgit v1.1 From 8262863d4b59aed7e22866234dcfd3d8fb016a6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Thu, 9 Jan 2020 01:05:23 +0100 Subject: hw/hppa/machine: Correctly check the firmware is in PDC range MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The firmware has to reside in the PDC range. If the Elf file expects to load it below FIRMWARE_START, it is incorrect, regardless the RAM size. Acked-by: Helge Deller Reviewed-by: Richard Henderson Signed-off-by: Philippe Mathieu-Daudé Message-Id: <20200109000525.24744-2-f4bug@amsat.org> Signed-off-by: Richard Henderson --- hw/hppa/machine.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'hw') diff --git a/hw/hppa/machine.c b/hw/hppa/machine.c index a35527c..e575309 100644 --- a/hw/hppa/machine.c +++ b/hw/hppa/machine.c @@ -171,7 +171,7 @@ static void machine_hppa_init(MachineState *machine) qemu_log_mask(CPU_LOG_PAGE, "Firmware loaded at 0x%08" PRIx64 "-0x%08" PRIx64 ", entry at 0x%08" PRIx64 ".\n", firmware_low, firmware_high, firmware_entry); - if (firmware_low < ram_size || firmware_high >= FIRMWARE_END) { + if (firmware_low < FIRMWARE_START || firmware_high >= FIRMWARE_END) { error_report("Firmware overlaps with memory or IO space"); exit(1); } -- cgit v1.1 From b7746b1194c8b15d8a31ba0cc308175bcb4b96f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Thu, 9 Jan 2020 01:05:24 +0100 Subject: hw/hppa/machine: Restrict the total memory size to 3GB MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The hardware expects DIMM slots of 1 or 2 GB, allowing up to 4 GB of memory. We want to accept the same amount of memory the hardware can deal with. DIMMs of 768MB are not available. However we have to deal with a firmware limitation: currently SeaBIOS only supports 32-bit, and expects the RAM size in a 32-bit register. When using a 4GB configuration, the 32-bit register get truncated and we report a size of 0MB to SeaBIOS, which ends halting the machine: $ qemu-system-hppa -m 4g -serial stdio SeaBIOS: Machine configured with too little memory (0 MB), minimum is 16 MB. SeaBIOS wants SYSTEM HALT. The easiest way is to restrict the machine to 3GB of memory. Acked-by: Helge Deller Reviewed-by: Richard Henderson Signed-off-by: Philippe Mathieu-Daudé Message-Id: <20200109000525.24744-3-f4bug@amsat.org> Signed-off-by: Richard Henderson --- hw/hppa/machine.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'hw') diff --git a/hw/hppa/machine.c b/hw/hppa/machine.c index e575309..57cb4e0 100644 --- a/hw/hppa/machine.c +++ b/hw/hppa/machine.c @@ -92,12 +92,11 @@ static void machine_hppa_init(MachineState *machine) g_free(name); } - /* Limit main memory. */ - if (ram_size > FIRMWARE_START) { - machine->ram_size = ram_size = FIRMWARE_START; - } - /* Main memory region. */ + if (machine->ram_size > 3 * GiB) { + error_report("RAM size is currently restricted to 3GB"); + exit(EXIT_FAILURE); + } ram_region = g_new(MemoryRegion, 1); memory_region_allocate_system_memory(ram_region, OBJECT(machine), "ram", ram_size); -- cgit v1.1 From 4debfdac03babcf858fb45204157a05236635a21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Date: Thu, 9 Jan 2020 01:05:25 +0100 Subject: hw/hppa/machine: Map the PDC memory region with higher priority MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The region in range [0xf0000000 - 0xf1000000] is the PDC area (Processor Dependent Code), where the firmware is loaded. This region has higher priority than the main memory. When the machine has more than 3840MB of RAM, there is an overlap. Since the PDC is closer to the CPU in the bus hierarchy, it gets accessed first, and the CPU does not have access to the RAM in this range. To model the same behavior and keep a simple memory layout, reduce the priority of the RAM region. The PDC region ends overlapping the RAM. Acked-by: Helge Deller Reviewed-by: Richard Henderson Signed-off-by: Philippe Mathieu-Daudé Message-Id: <20200109000525.24744-4-f4bug@amsat.org> Signed-off-by: Richard Henderson --- hw/hppa/machine.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'hw') diff --git a/hw/hppa/machine.c b/hw/hppa/machine.c index 57cb4e0..2d62a24 100644 --- a/hw/hppa/machine.c +++ b/hw/hppa/machine.c @@ -100,7 +100,7 @@ static void machine_hppa_init(MachineState *machine) ram_region = g_new(MemoryRegion, 1); memory_region_allocate_system_memory(ram_region, OBJECT(machine), "ram", ram_size); - memory_region_add_subregion(addr_space, 0, ram_region); + memory_region_add_subregion_overlap(addr_space, 0, ram_region, -1); /* Init Lasi chip */ lasi_init(addr_space); -- cgit v1.1