summary refs log tree commit diff stats
path: root/hw
diff options
context:
space:
mode:
Diffstat (limited to 'hw')
-rw-r--r--hw/hppa/Kconfig1
-rw-r--r--hw/hppa/hppa_hardware.h1
-rw-r--r--hw/hppa/machine.c367
-rw-r--r--hw/input/lasips2.c10
-rw-r--r--hw/net/tulip.c2
-rw-r--r--hw/pci-host/Kconfig4
-rw-r--r--hw/pci-host/astro.c885
-rw-r--r--hw/pci-host/meson.build1
-rw-r--r--hw/pci-host/trace-events11
9 files changed, 1202 insertions, 80 deletions
diff --git a/hw/hppa/Kconfig b/hw/hppa/Kconfig
index 5dd8b5b21e..ff8528aaa8 100644
--- a/hw/hppa/Kconfig
+++ b/hw/hppa/Kconfig
@@ -3,6 +3,7 @@ config HPPA_B160L
     imply PCI_DEVICES
     imply E1000_PCI
     imply VIRTIO_VGA
+    select ASTRO
     select DINO
     select LASI
     select SERIAL
diff --git a/hw/hppa/hppa_hardware.h b/hw/hppa/hppa_hardware.h
index a5ac3dd0fd..a9be7bb851 100644
--- a/hw/hppa/hppa_hardware.h
+++ b/hw/hppa/hppa_hardware.h
@@ -18,7 +18,6 @@
 #define LASI_UART_HPA   0xffd05000
 #define LASI_SCSI_HPA   0xffd06000
 #define LASI_LAN_HPA    0xffd07000
-#define LASI_RTC_HPA    0xffd09000
 #define LASI_LPT_HPA    0xffd02000
 #define LASI_AUDIO_HPA  0xffd04000
 #define LASI_PS2KBD_HPA 0xffd08000
diff --git a/hw/hppa/machine.c b/hw/hppa/machine.c
index cf28cb9586..67d4d1b5e0 100644
--- a/hw/hppa/machine.c
+++ b/hw/hppa/machine.c
@@ -1,6 +1,8 @@
 /*
  * QEMU HPPA hardware system emulator.
- * Copyright 2018 Helge Deller <deller@gmx.de>
+ * (C) Copyright 2018-2023 Helge Deller <deller@gmx.de>
+ *
+ * This work is licensed under the GNU GPL license version 2 or later.
  */
 
 #include "qemu/osdep.h"
@@ -20,7 +22,10 @@
 #include "hw/input/lasips2.h"
 #include "hw/net/lasi_82596.h"
 #include "hw/nmi.h"
+#include "hw/usb.h"
 #include "hw/pci/pci.h"
+#include "hw/pci/pci_device.h"
+#include "hw/pci-host/astro.h"
 #include "hw/pci-host/dino.h"
 #include "hw/misc/lasi.h"
 #include "hppa_hardware.h"
@@ -29,12 +34,13 @@
 #include "net/net.h"
 #include "qemu/log.h"
 
-#define MIN_SEABIOS_HPPA_VERSION 6 /* require at least this fw version */
+#define MIN_SEABIOS_HPPA_VERSION 10 /* require at least this fw version */
 
 #define HPA_POWER_BUTTON (FIRMWARE_END - 0x10)
 
 #define enable_lasi_lan()       0
 
+static DeviceState *lasi_dev;
 
 static void hppa_powerdown_req(Notifier *n, void *opaque)
 {
@@ -95,14 +101,69 @@ static ISABus *hppa_isa_bus(void)
 
     isa_bus = isa_bus_new(NULL, get_system_memory(), isa_region,
                           &error_abort);
-    isa_irqs = i8259_init(isa_bus,
-                          /* qemu_allocate_irq(dino_set_isa_irq, s, 0)); */
-                          NULL);
+    isa_irqs = i8259_init(isa_bus, NULL);
     isa_bus_register_input_irqs(isa_bus, isa_irqs);
 
     return isa_bus;
 }
 
+/*
+ * Helper functions to emulate RTC clock and DebugOutputPort
+ */
+static time_t rtc_ref;
+
+static uint64_t io_cpu_read(void *opaque, hwaddr addr, unsigned size)
+{
+    uint64_t val = 0;
+
+    switch (addr) {
+    case 0:             /* RTC clock */
+        val = time(NULL);
+        val += rtc_ref;
+        break;
+    case 8:             /* DebugOutputPort */
+        return 0xe9;    /* readback */
+    }
+    return val;
+}
+
+static void io_cpu_write(void *opaque, hwaddr addr,
+                         uint64_t val, unsigned size)
+{
+    unsigned char ch;
+    Chardev *debugout;
+
+    switch (addr) {
+    case 0:             /* RTC clock */
+        rtc_ref = val - time(NULL);
+        break;
+    case 8:             /* DebugOutputPort */
+        ch = val;
+        debugout = serial_hd(0);
+        if (debugout) {
+            qemu_chr_fe_write_all(debugout->be, &ch, 1);
+        } else {
+            fprintf(stderr, "%c", ch);
+        }
+        break;
+    }
+}
+
+static const MemoryRegionOps hppa_io_helper_ops = {
+    .read = io_cpu_read,
+    .write = io_cpu_write,
+    .endianness = DEVICE_BIG_ENDIAN,
+    .valid = {
+        .min_access_size = 1,
+        .max_access_size = 8,
+    },
+    .impl = {
+        .min_access_size = 1,
+        .max_access_size = 8,
+    },
+};
+
+
 static uint64_t cpu_hppa_to_phys(void *opaque, uint64_t addr)
 {
     addr &= (0x10000000 - 1);
@@ -118,11 +179,13 @@ static void fw_cfg_boot_set(void *opaque, const char *boot_device,
     fw_cfg_modify_i16(opaque, FW_CFG_BOOT_DEVICE, boot_device[0]);
 }
 
-static FWCfgState *create_fw_cfg(MachineState *ms)
+static FWCfgState *create_fw_cfg(MachineState *ms, PCIBus *pci_bus)
 {
     FWCfgState *fw_cfg;
     uint64_t val;
     const char qemu_version[] = QEMU_VERSION;
+    MachineClass *mc = MACHINE_GET_CLASS(ms);
+    int len;
 
     fw_cfg = fw_cfg_init_mem(FW_CFG_IO_BASE, FW_CFG_IO_BASE + 4);
     fw_cfg_add_i16(fw_cfg, FW_CFG_NB_CPUS, ms->smp.cpus);
@@ -137,8 +200,24 @@ static FWCfgState *create_fw_cfg(MachineState *ms)
     fw_cfg_add_file(fw_cfg, "/etc/cpu/tlb_entries",
                     g_memdup(&val, sizeof(val)), sizeof(val));
 
+    val = cpu_to_le64(HPPA_BTLB_ENTRIES);
+    fw_cfg_add_file(fw_cfg, "/etc/cpu/btlb_entries",
+                    g_memdup(&val, sizeof(val)), sizeof(val));
+
+    len = strlen(mc->name) + 1;
+    fw_cfg_add_file(fw_cfg, "/etc/hppa/machine",
+                    g_memdup(mc->name, len), len);
+
     val = cpu_to_le64(HPA_POWER_BUTTON);
-    fw_cfg_add_file(fw_cfg, "/etc/power-button-addr",
+    fw_cfg_add_file(fw_cfg, "/etc/hppa/power-button-addr",
+                    g_memdup(&val, sizeof(val)), sizeof(val));
+
+    val = cpu_to_le64(CPU_HPA + 16);
+    fw_cfg_add_file(fw_cfg, "/etc/hppa/rtc-addr",
+                    g_memdup(&val, sizeof(val)), sizeof(val));
+
+    val = cpu_to_le64(CPU_HPA + 24);
+    fw_cfg_add_file(fw_cfg, "/etc/hppa/DebugOutputPort",
                     g_memdup(&val, sizeof(val)), sizeof(val));
 
     fw_cfg_add_i16(fw_cfg, FW_CFG_BOOT_DEVICE, ms->boot_config.order[0]);
@@ -148,6 +227,8 @@ static FWCfgState *create_fw_cfg(MachineState *ms)
                     g_memdup(qemu_version, sizeof(qemu_version)),
                     sizeof(qemu_version));
 
+    fw_cfg_add_extra_pci_roots(pci_bus, fw_cfg);
+
     return fw_cfg;
 }
 
@@ -173,29 +254,20 @@ static DinoState *dino_init(MemoryRegion *addr_space)
     return DINO_PCI_HOST_BRIDGE(dev);
 }
 
-static void machine_hppa_init(MachineState *machine)
+/*
+ * Step 1: Create CPUs and Memory
+ */
+static void machine_HP_common_init_cpus(MachineState *machine)
 {
-    const char *kernel_filename = machine->kernel_filename;
-    const char *kernel_cmdline = machine->kernel_cmdline;
-    const char *initrd_filename = machine->initrd_filename;
-    MachineClass *mc = MACHINE_GET_CLASS(machine);
-    DeviceState *dev, *dino_dev, *lasi_dev;
-    PCIBus *pci_bus;
-    ISABus *isa_bus;
-    char *firmware_filename;
-    uint64_t firmware_low, firmware_high;
-    long size;
-    uint64_t kernel_entry = 0, kernel_low, kernel_high;
     MemoryRegion *addr_space = get_system_memory();
-    MemoryRegion *rom_region;
     MemoryRegion *cpu_region;
     long i;
     unsigned int smp_cpus = machine->smp.cpus;
-    SysBusDevice *s;
+    char *name;
 
     /* Create CPUs.  */
     for (i = 0; i < smp_cpus; i++) {
-        char *name = g_strdup_printf("cpu%ld-io-eir", i);
+        name = g_strdup_printf("cpu%ld-io-eir", i);
         cpu[i] = HPPA_CPU(cpu_create(machine->cpu_type));
 
         cpu_region = g_new(MemoryRegion, 1);
@@ -206,51 +278,40 @@ static void machine_hppa_init(MachineState *machine)
         g_free(name);
     }
 
+    /* RTC and DebugOutputPort on CPU #0 */
+    cpu_region = g_new(MemoryRegion, 1);
+    memory_region_init_io(cpu_region, OBJECT(cpu[0]), &hppa_io_helper_ops,
+                          cpu[0], "cpu0-io-rtc", 2 * sizeof(uint64_t));
+    memory_region_add_subregion(addr_space, CPU_HPA + 16, cpu_region);
+
     /* Main memory region. */
     if (machine->ram_size > 3 * GiB) {
         error_report("RAM size is currently restricted to 3GB");
         exit(EXIT_FAILURE);
     }
     memory_region_add_subregion_overlap(addr_space, 0, machine->ram, -1);
+}
 
-
-    /* Init Lasi chip */
-    lasi_dev = DEVICE(lasi_init());
-    memory_region_add_subregion(addr_space, LASI_HPA,
-                                sysbus_mmio_get_region(
-                                    SYS_BUS_DEVICE(lasi_dev), 0));
-
-    /* Init Dino (PCI host bus chip).  */
-    dino_dev = DEVICE(dino_init(addr_space));
-    memory_region_add_subregion(addr_space, DINO_HPA,
-                                sysbus_mmio_get_region(
-                                    SYS_BUS_DEVICE(dino_dev), 0));
-    pci_bus = PCI_BUS(qdev_get_child_bus(dino_dev, "pci"));
-    assert(pci_bus);
-
-    /* Create ISA bus. */
-    isa_bus = hppa_isa_bus();
-    assert(isa_bus);
-
-    /* Realtime clock, used by firmware for PDC_TOD call. */
-    mc146818_rtc_init(isa_bus, 2000, NULL);
-
-    /* Serial ports: Lasi and Dino use a 7.272727 MHz clock. */
-    serial_mm_init(addr_space, LASI_UART_HPA + 0x800, 0,
-        qdev_get_gpio_in(lasi_dev, LASI_IRQ_UART_HPA), 7272727 / 16,
-        serial_hd(0), DEVICE_BIG_ENDIAN);
-
-    serial_mm_init(addr_space, DINO_UART_HPA + 0x800, 0,
-        qdev_get_gpio_in(dino_dev, DINO_IRQ_RS232INT), 7272727 / 16,
-        serial_hd(1), DEVICE_BIG_ENDIAN);
-
-    /* Parallel port */
-    parallel_mm_init(addr_space, LASI_LPT_HPA + 0x800, 0,
-                     qdev_get_gpio_in(lasi_dev, LASI_IRQ_LAN_HPA),
-                     parallel_hds[0]);
-
-    /* fw_cfg configuration interface */
-    create_fw_cfg(machine);
+/*
+ * Last creation step: Add SCSI discs, NICs, graphics & load firmware
+ */
+static void machine_HP_common_init_tail(MachineState *machine, PCIBus *pci_bus)
+{
+    const char *kernel_filename = machine->kernel_filename;
+    const char *kernel_cmdline = machine->kernel_cmdline;
+    const char *initrd_filename = machine->initrd_filename;
+    MachineClass *mc = MACHINE_GET_CLASS(machine);
+    DeviceState *dev;
+    PCIDevice *pci_dev;
+    char *firmware_filename;
+    uint64_t firmware_low, firmware_high;
+    long size;
+    uint64_t kernel_entry = 0, kernel_low, kernel_high;
+    MemoryRegion *addr_space = get_system_memory();
+    MemoryRegion *rom_region;
+    long i;
+    unsigned int smp_cpus = machine->smp.cpus;
+    SysBusDevice *s;
 
     /* SCSI disk setup. */
     dev = DEVICE(pci_create_simple(pci_bus, -1, "lsi53c895a"));
@@ -278,21 +339,42 @@ static void machine_hppa_init(MachineState *machine)
         }
     }
 
-    /* PS/2 Keyboard/Mouse */
-    dev = qdev_new(TYPE_LASIPS2);
-    sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
-    sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0,
-                       qdev_get_gpio_in(lasi_dev, LASI_IRQ_PS2KBD_HPA));
-    memory_region_add_subregion(addr_space, LASI_PS2KBD_HPA,
-                                sysbus_mmio_get_region(SYS_BUS_DEVICE(dev),
-                                                       0));
-    memory_region_add_subregion(addr_space, LASI_PS2KBD_HPA + 0x100,
-                                sysbus_mmio_get_region(SYS_BUS_DEVICE(dev),
-                                                       1));
+    /* BMC board: HP Powerbar SP2 Diva (with console only) */
+    pci_dev = pci_new(-1, "pci-serial");
+    if (!lasi_dev) {
+        /* bind default keyboard/serial to Diva card */
+        qdev_prop_set_chr(DEVICE(pci_dev), "chardev", serial_hd(0));
+    }
+    qdev_prop_set_uint8(DEVICE(pci_dev), "prog_if", 0);
+    pci_realize_and_unref(pci_dev, pci_bus, &error_fatal);
+    pci_config_set_vendor_id(pci_dev->config, PCI_VENDOR_ID_HP);
+    pci_config_set_device_id(pci_dev->config, 0x1048);
+    pci_set_word(&pci_dev->config[PCI_SUBSYSTEM_VENDOR_ID], PCI_VENDOR_ID_HP);
+    pci_set_word(&pci_dev->config[PCI_SUBSYSTEM_ID], 0x1227); /* Powerbar */
+
+    /* create a second serial PCI card when running Astro */
+    if (!lasi_dev) {
+        pci_dev = pci_new(-1, "pci-serial-4x");
+        qdev_prop_set_chr(DEVICE(pci_dev), "chardev1", serial_hd(1));
+        qdev_prop_set_chr(DEVICE(pci_dev), "chardev2", serial_hd(2));
+        qdev_prop_set_chr(DEVICE(pci_dev), "chardev3", serial_hd(3));
+        qdev_prop_set_chr(DEVICE(pci_dev), "chardev4", serial_hd(4));
+        pci_realize_and_unref(pci_dev, pci_bus, &error_fatal);
+    }
+
+    /* create USB OHCI controller for USB keyboard & mouse on Astro machines */
+    if (!lasi_dev && machine->enable_graphics) {
+        pci_create_simple(pci_bus, -1, "pci-ohci");
+        usb_create_simple(usb_bus_find(-1), "usb-kbd");
+        usb_create_simple(usb_bus_find(-1), "usb-mouse");
+    }
 
     /* register power switch emulation */
     qemu_register_powerdown_notifier(&hppa_system_powerdown_notifier);
 
+    /* fw_cfg configuration interface */
+    create_fw_cfg(machine, pci_bus);
+
     /* Load firmware.  Given that this is not "real" firmware,
        but one explicitly written for the emulation, we might as
        well load it directly from an ELF image.  */
@@ -410,6 +492,103 @@ static void machine_hppa_init(MachineState *machine)
     cpu[0]->env.gr[19] = FW_CFG_IO_BASE;
 }
 
+/*
+ * Create HP B160L workstation
+ */
+static void machine_HP_B160L_init(MachineState *machine)
+{
+    DeviceState *dev, *dino_dev;
+    MemoryRegion *addr_space = get_system_memory();
+    ISABus *isa_bus;
+    PCIBus *pci_bus;
+
+    /* Create CPUs and RAM.  */
+    machine_HP_common_init_cpus(machine);
+
+    /* Init Lasi chip */
+    lasi_dev = DEVICE(lasi_init());
+    memory_region_add_subregion(addr_space, LASI_HPA,
+                                sysbus_mmio_get_region(
+                                    SYS_BUS_DEVICE(lasi_dev), 0));
+
+    /* Init Dino (PCI host bus chip).  */
+    dino_dev = DEVICE(dino_init(addr_space));
+    memory_region_add_subregion(addr_space, DINO_HPA,
+                                sysbus_mmio_get_region(
+                                    SYS_BUS_DEVICE(dino_dev), 0));
+    pci_bus = PCI_BUS(qdev_get_child_bus(dino_dev, "pci"));
+    assert(pci_bus);
+
+    /* Create ISA bus, needed for PS/2 kbd/mouse port emulation */
+    isa_bus = hppa_isa_bus();
+    assert(isa_bus);
+
+    /* Serial ports: Lasi and Dino use a 7.272727 MHz clock. */
+    serial_mm_init(addr_space, LASI_UART_HPA + 0x800, 0,
+        qdev_get_gpio_in(lasi_dev, LASI_IRQ_UART_HPA), 7272727 / 16,
+        serial_hd(0), DEVICE_BIG_ENDIAN);
+
+    serial_mm_init(addr_space, DINO_UART_HPA + 0x800, 0,
+        qdev_get_gpio_in(dino_dev, DINO_IRQ_RS232INT), 7272727 / 16,
+        serial_hd(1), DEVICE_BIG_ENDIAN);
+
+    /* Parallel port */
+    parallel_mm_init(addr_space, LASI_LPT_HPA + 0x800, 0,
+                     qdev_get_gpio_in(lasi_dev, LASI_IRQ_LAN_HPA),
+                     parallel_hds[0]);
+
+    /* PS/2 Keyboard/Mouse */
+    dev = qdev_new(TYPE_LASIPS2);
+    sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
+    sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0,
+                       qdev_get_gpio_in(lasi_dev, LASI_IRQ_PS2KBD_HPA));
+    memory_region_add_subregion(addr_space, LASI_PS2KBD_HPA,
+                                sysbus_mmio_get_region(SYS_BUS_DEVICE(dev),
+                                                       0));
+    memory_region_add_subregion(addr_space, LASI_PS2KBD_HPA + 0x100,
+                                sysbus_mmio_get_region(SYS_BUS_DEVICE(dev),
+                                                       1));
+
+    /* Add SCSI discs, NICs, graphics & load firmware */
+    machine_HP_common_init_tail(machine, pci_bus);
+}
+
+static AstroState *astro_init(void)
+{
+    DeviceState *dev;
+
+    dev = qdev_new(TYPE_ASTRO_CHIP);
+    sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
+
+    return ASTRO_CHIP(dev);
+}
+
+/*
+ * Create HP C3700 workstation
+ */
+static void machine_HP_C3700_init(MachineState *machine)
+{
+    PCIBus *pci_bus;
+    AstroState *astro;
+    DeviceState *astro_dev;
+    MemoryRegion *addr_space = get_system_memory();
+
+    /* Create CPUs and RAM.  */
+    machine_HP_common_init_cpus(machine);
+
+    /* Init Astro and the Elroys (PCI host bus chips).  */
+    astro = astro_init();
+    astro_dev = DEVICE(astro);
+    memory_region_add_subregion(addr_space, ASTRO_HPA,
+                                sysbus_mmio_get_region(
+                                    SYS_BUS_DEVICE(astro_dev), 0));
+    pci_bus = PCI_BUS(qdev_get_child_bus(DEVICE(astro->elroy[0]), "pci"));
+    assert(pci_bus);
+
+    /* Add SCSI discs, NICs, graphics & load firmware */
+    machine_HP_common_init_tail(machine, pci_bus);
+}
+
 static void hppa_machine_reset(MachineState *ms, ShutdownCause reason)
 {
     unsigned int smp_cpus = ms->smp.cpus;
@@ -458,14 +637,14 @@ static void hppa_nmi(NMIState *n, int cpu_index, Error **errp)
     }
 }
 
-static void hppa_machine_init_class_init(ObjectClass *oc, void *data)
+static void HP_B160L_machine_init_class_init(ObjectClass *oc, void *data)
 {
     MachineClass *mc = MACHINE_CLASS(oc);
     NMIClass *nc = NMI_CLASS(oc);
 
-    mc->desc = "HPPA B160L machine";
+    mc->desc = "HP B160L workstation";
     mc->default_cpu_type = TYPE_HPPA_CPU;
-    mc->init = machine_hppa_init;
+    mc->init = machine_HP_B160L_init;
     mc->reset = hppa_machine_reset;
     mc->block_default_type = IF_SCSI;
     mc->max_cpus = HPPA_MAX_CPUS;
@@ -479,10 +658,41 @@ static void hppa_machine_init_class_init(ObjectClass *oc, void *data)
     nc->nmi_monitor_handler = hppa_nmi;
 }
 
-static const TypeInfo hppa_machine_init_typeinfo = {
-    .name = MACHINE_TYPE_NAME("hppa"),
+static const TypeInfo HP_B160L_machine_init_typeinfo = {
+    .name = MACHINE_TYPE_NAME("B160L"),
+    .parent = TYPE_MACHINE,
+    .class_init = HP_B160L_machine_init_class_init,
+    .interfaces = (InterfaceInfo[]) {
+        { TYPE_NMI },
+        { }
+    },
+};
+
+static void HP_C3700_machine_init_class_init(ObjectClass *oc, void *data)
+{
+    MachineClass *mc = MACHINE_CLASS(oc);
+    NMIClass *nc = NMI_CLASS(oc);
+
+    mc->desc = "HP C3700 workstation";
+    mc->default_cpu_type = TYPE_HPPA_CPU;
+    mc->init = machine_HP_C3700_init;
+    mc->reset = hppa_machine_reset;
+    mc->block_default_type = IF_SCSI;
+    mc->max_cpus = HPPA_MAX_CPUS;
+    mc->default_cpus = 1;
+    mc->is_default = false;
+    mc->default_ram_size = 1024 * MiB;
+    mc->default_boot_order = "cd";
+    mc->default_ram_id = "ram";
+    mc->default_nic = "tulip";
+
+    nc->nmi_monitor_handler = hppa_nmi;
+}
+
+static const TypeInfo HP_C3700_machine_init_typeinfo = {
+    .name = MACHINE_TYPE_NAME("C3700"),
     .parent = TYPE_MACHINE,
-    .class_init = hppa_machine_init_class_init,
+    .class_init = HP_C3700_machine_init_class_init,
     .interfaces = (InterfaceInfo[]) {
         { TYPE_NMI },
         { }
@@ -491,7 +701,8 @@ static const TypeInfo hppa_machine_init_typeinfo = {
 
 static void hppa_machine_init_register_types(void)
 {
-    type_register_static(&hppa_machine_init_typeinfo);
+    type_register_static(&HP_B160L_machine_init_typeinfo);
+    type_register_static(&HP_C3700_machine_init_typeinfo);
 }
 
 type_init(hppa_machine_init_register_types)
diff --git a/hw/input/lasips2.c b/hw/input/lasips2.c
index ea7c07a2ba..6075121b72 100644
--- a/hw/input/lasips2.c
+++ b/hw/input/lasips2.c
@@ -351,6 +351,11 @@ static void lasips2_port_class_init(ObjectClass *klass, void *data)
 {
     DeviceClass *dc = DEVICE_CLASS(klass);
 
+    /*
+     * The PS/2 mouse port is integreal part of LASI and can not be
+     * created by users without LASI.
+     */
+    dc->user_creatable = false;
     dc->realize = lasips2_port_realize;
 }
 
@@ -397,6 +402,11 @@ static void lasips2_kbd_port_class_init(ObjectClass *klass, void *data)
     DeviceClass *dc = DEVICE_CLASS(klass);
     LASIPS2PortDeviceClass *lpdc = LASIPS2_PORT_CLASS(klass);
 
+    /*
+     * The PS/2 keyboard port is integreal part of LASI and can not be
+     * created by users without LASI.
+     */
+    dc->user_creatable = false;
     device_class_set_parent_realize(dc, lasips2_kbd_port_realize,
                                     &lpdc->parent_realize);
 }
diff --git a/hw/net/tulip.c b/hw/net/tulip.c
index 915e5fb595..11d866e431 100644
--- a/hw/net/tulip.c
+++ b/hw/net/tulip.c
@@ -1020,7 +1020,7 @@ static void tulip_class_init(ObjectClass *klass, void *data)
     k->exit = pci_tulip_exit;
     k->vendor_id = PCI_VENDOR_ID_DEC;
     k->device_id = PCI_DEVICE_ID_DEC_21143;
-    k->subsystem_vendor_id = 0x103c;
+    k->subsystem_vendor_id = PCI_VENDOR_ID_HP;
     k->subsystem_id = 0x104f;
     k->class_id = PCI_CLASS_NETWORK_ETHERNET;
     dc->vmsd = &vmstate_pci_tulip;
diff --git a/hw/pci-host/Kconfig b/hw/pci-host/Kconfig
index a07070eddf..54a609d2ca 100644
--- a/hw/pci-host/Kconfig
+++ b/hw/pci-host/Kconfig
@@ -82,6 +82,10 @@ config DINO
     bool
     select PCI
 
+config ASTRO
+    bool
+    select PCI
+
 config GT64120
     bool
     select PCI
diff --git a/hw/pci-host/astro.c b/hw/pci-host/astro.c
new file mode 100644
index 0000000000..4b2d7caf2d
--- /dev/null
+++ b/hw/pci-host/astro.c
@@ -0,0 +1,885 @@
+/*
+ * HP-PARISC Astro/Pluto/Ike/REO system bus adapter (SBA)
+ * with Elroy PCI bus (LBA) adapter emulation
+ * Found in C3000 and similar machines
+ *
+ * (C) 2023 by Helge Deller <deller@gmx.de>
+ *
+ * This work is licensed under the GNU GPL license version 2 or later.
+ *
+ * Chip documentation is available at:
+ * https://parisc.wiki.kernel.org/index.php/Technical_Documentation
+ *
+ * TODO:
+ * - All user-added devices are currently attached to the first
+ *   Elroy (PCI bus) only for now. To fix this additional work in
+ *   SeaBIOS and this driver is needed. See "user_creatable" flag below.
+ * - GMMIO (Greater than 4 GB MMIO) register
+ */
+
+#define TYPE_ASTRO_IOMMU_MEMORY_REGION "astro-iommu-memory-region"
+
+#include "qemu/osdep.h"
+#include "qemu/module.h"
+#include "qemu/units.h"
+#include "qapi/error.h"
+#include "hw/irq.h"
+#include "hw/pci/pci_device.h"
+#include "hw/pci/pci_bus.h"
+#include "hw/qdev-properties.h"
+#include "hw/pci-host/astro.h"
+#include "hw/hppa/hppa_hardware.h"
+#include "migration/vmstate.h"
+#include "trace.h"
+#include "qom/object.h"
+
+/*
+ * Helper functions
+ */
+
+static uint64_t mask_32bit_val(hwaddr addr, unsigned size, uint64_t val)
+{
+    if (size == 8) {
+        return val;
+    }
+    if (addr & 4) {
+        val >>= 32;
+    } else {
+        val = (uint32_t) val;
+    }
+    return val;
+}
+
+static void put_val_in_int64(uint64_t *p, hwaddr addr, unsigned size,
+                             uint64_t val)
+{
+    if (size == 8) {
+        *p = val;
+    } else if (size == 4) {
+        if (addr & 4) {
+            *p = ((*p << 32) >> 32) | (val << 32);
+        } else {
+            *p = ((*p >> 32) << 32) | (uint32_t) val;
+        }
+    }
+}
+
+static void put_val_in_arrary(uint64_t *array, hwaddr start_addr,
+                              hwaddr addr, unsigned size, uint64_t val)
+{
+    int index;
+
+    index = (addr - start_addr) / 8;
+    put_val_in_int64(&array[index], addr, size, val);
+}
+
+
+/*
+ * The Elroy PCI host bridge. We have at least 4 of those under Astro.
+ */
+
+static MemTxResult elroy_chip_read_with_attrs(void *opaque, hwaddr addr,
+                                             uint64_t *data, unsigned size,
+                                             MemTxAttrs attrs)
+{
+    MemTxResult ret = MEMTX_OK;
+    ElroyState *s = opaque;
+    uint64_t val = -1;
+    int index;
+
+    switch ((addr >> 3) << 3) {
+    case 0x0008:
+        val = 0x6000005; /* func_class */
+        break;
+    case 0x0058:
+        /*
+         * Scratch register, but firmware initializes it with the
+         * PCI BUS number and Linux/HP-UX uses it then.
+         */
+        val = s->pci_bus_num;
+        /* Upper byte holds the end of this bus number */
+        val |= s->pci_bus_num << 8;
+        break;
+    case 0x0080:
+        val = s->arb_mask; /* set ARB mask */
+        break;
+    case 0x0108:
+        val = s->status_control;
+        break;
+    case 0x200 ... 0x250 - 1: /* LMMIO, GMMIO, WLMMIO, WGMMIO, ... */
+        index = (addr - 0x200) / 8;
+        val = s->mmio_base[index];
+        break;
+    case 0x0680:
+        val = s->error_config;
+        break;
+    case 0x0688:
+        val = 0;                /* ERROR_STATUS */
+        break;
+    case 0x0800:                /* IOSAPIC_REG_SELECT */
+        val = s->iosapic_reg_select;
+        break;
+    case 0x0808:
+        val = UINT64_MAX;            /* XXX: tbc. */
+        g_assert_not_reached();
+        break;
+    case 0x0810:                /* IOSAPIC_REG_WINDOW */
+        switch (s->iosapic_reg_select) {
+        case 0x01:              /* IOSAPIC_REG_VERSION */
+            val = (32 << 16) | 1; /* upper 16bit holds max entries */
+            break;
+        default:
+            if (s->iosapic_reg_select < ARRAY_SIZE(s->iosapic_reg)) {
+                val = s->iosapic_reg[s->iosapic_reg_select];
+            } else {
+                trace_iosapic_reg_read(s->iosapic_reg_select, size, val);
+                g_assert_not_reached();
+            }
+        }
+        trace_iosapic_reg_read(s->iosapic_reg_select, size, val);
+        break;
+    default:
+        trace_elroy_read(addr, size, val);
+        g_assert_not_reached();
+    }
+    trace_elroy_read(addr, size, val);
+
+    /* for 32-bit accesses mask return value */
+    val = mask_32bit_val(addr, size, val);
+
+    trace_astro_chip_read(addr, size, val);
+    *data = val;
+    return ret;
+}
+
+
+static MemTxResult elroy_chip_write_with_attrs(void *opaque, hwaddr addr,
+                                              uint64_t val, unsigned size,
+                                              MemTxAttrs attrs)
+{
+    ElroyState *s = opaque;
+    int i;
+
+    trace_elroy_write(addr, size, val);
+
+    switch ((addr >> 3) << 3) {
+    case 0x080:
+        put_val_in_int64(&s->arb_mask, addr, size, val);
+        break;
+    case 0x0108:
+        put_val_in_int64(&s->status_control, addr, size, val);
+        break;
+    case 0x200 ... 0x250 - 1:   /* LMMIO, GMMIO, WLMMIO, WGMMIO, ... */
+        put_val_in_arrary(s->mmio_base, 0x200, addr, size, val);
+        break;
+    case 0x0680:
+        put_val_in_int64(&s->error_config, addr, size, val);
+        break;
+    case 0x0800:                /* IOSAPIC_REG_SELECT */
+        s->iosapic_reg_select = val;
+        break;
+    case 0x0810:                /* IOSAPIC_REG_WINDOW */
+        trace_iosapic_reg_write(s->iosapic_reg_select, size, val);
+        if (s->iosapic_reg_select < ARRAY_SIZE(s->iosapic_reg)) {
+            s->iosapic_reg[s->iosapic_reg_select] = val;
+        } else {
+            g_assert_not_reached();
+        }
+        break;
+    case 0x0840:                /* IOSAPIC_REG_EOI */
+        val = le64_to_cpu(val);
+        val &= 63;
+        for (i = 0; i < ELROY_IRQS; i++) {
+            if ((s->iosapic_reg[0x10 + 2 * i] & 63) == val) {
+                s->ilr &= ~(1ull << i);
+            }
+        }
+        break;
+    default:
+        g_assert_not_reached();
+    }
+    return MEMTX_OK;
+}
+
+static const MemoryRegionOps elroy_chip_ops = {
+    .read_with_attrs = elroy_chip_read_with_attrs,
+    .write_with_attrs = elroy_chip_write_with_attrs,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 8,
+    },
+    .impl = {
+        .min_access_size = 4,
+        .max_access_size = 8,
+    },
+};
+
+
+/* Unlike pci_config_data_le_ops, no check of high bit set in config_reg.  */
+
+static uint64_t elroy_config_data_read(void *opaque, hwaddr addr, unsigned len)
+{
+    uint64_t val;
+
+    PCIHostState *s = opaque;
+    val = pci_data_read(s->bus, s->config_reg | (addr & 3), len);
+    trace_elroy_pci_config_data_read(s->config_reg | (addr & 3), len, val);
+    return val;
+}
+
+static void elroy_config_data_write(void *opaque, hwaddr addr,
+                                   uint64_t val, unsigned len)
+{
+    PCIHostState *s = opaque;
+    pci_data_write(s->bus, s->config_reg | (addr & 3), val, len);
+    trace_elroy_pci_config_data_write(s->config_reg | (addr & 3), len, val);
+}
+
+static const MemoryRegionOps elroy_config_data_ops = {
+    .read = elroy_config_data_read,
+    .write = elroy_config_data_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static uint64_t elroy_config_addr_read(void *opaque, hwaddr addr, unsigned len)
+{
+    ElroyState *s = opaque;
+    return s->config_reg_elroy;
+}
+
+static void elroy_config_addr_write(void *opaque, hwaddr addr,
+                                   uint64_t val, unsigned len)
+{
+    PCIHostState *s = opaque;
+    ElroyState *es = opaque;
+    es->config_reg_elroy = val; /* keep a copy of original value */
+    s->config_reg = val;
+}
+
+static const MemoryRegionOps elroy_config_addr_ops = {
+    .read = elroy_config_addr_read,
+    .write = elroy_config_addr_write,
+    .valid.min_access_size = 4,
+    .valid.max_access_size = 8,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+
+/*
+ * A subroutine of astro_translate_iommu that builds an IOMMUTLBEntry using the
+ * given translated address and mask.
+ */
+static bool make_iommu_tlbe(hwaddr addr, hwaddr taddr, hwaddr mask,
+                            IOMMUTLBEntry *ret)
+{
+    hwaddr tce_mask = ~((1ull << 12) - 1);
+    ret->target_as = &address_space_memory;
+    ret->iova = addr & tce_mask;
+    ret->translated_addr = taddr & tce_mask;
+    ret->addr_mask = ~tce_mask;
+    ret->perm = IOMMU_RW;
+    return true;
+}
+
+/* Handle PCI-to-system address translation.  */
+static IOMMUTLBEntry astro_translate_iommu(IOMMUMemoryRegion *iommu,
+                                             hwaddr addr,
+                                             IOMMUAccessFlags flag,
+                                             int iommu_idx)
+{
+    AstroState *s = container_of(iommu, AstroState, iommu);
+    IOMMUTLBEntry ret = {
+        .target_as = &address_space_memory,
+        .iova = addr,
+        .translated_addr = 0,
+        .addr_mask = ~(hwaddr)0,
+        .perm = IOMMU_NONE,
+    };
+    hwaddr pdir_ptr, index, a, ibase;
+    hwaddr addr_mask = 0xfff; /* 4k translation */
+    uint64_t entry;
+
+#define IOVP_SHIFT              12   /* equals PAGE_SHIFT */
+#define PDIR_INDEX(iovp)        ((iovp) >> IOVP_SHIFT)
+#define IOVP_MASK               PAGE_MASK
+#define SBA_PDIR_VALID_BIT      0x8000000000000000ULL
+
+    /* "range enable" flag cleared? */
+    if ((s->tlb_ibase & 1) == 0) {
+        make_iommu_tlbe(addr, addr, addr_mask, &ret);
+        return ret;
+    }
+
+    a = addr;
+    ibase = s->tlb_ibase & ~1ULL;
+    if ((a & s->tlb_imask) != ibase) {
+        /* do not translate this one! */
+        make_iommu_tlbe(addr, addr, addr_mask, &ret);
+        return ret;
+    }
+    index = PDIR_INDEX(a);
+    pdir_ptr = s->tlb_pdir_base + index * sizeof(entry);
+    entry = ldq_le_phys(&address_space_memory, pdir_ptr);
+    if (!(entry & SBA_PDIR_VALID_BIT)) { /* I/O PDIR entry valid ? */
+        g_assert_not_reached();
+        goto failure;
+    }
+    entry &= ~SBA_PDIR_VALID_BIT;
+    entry >>= IOVP_SHIFT;
+    entry <<= 12;
+    entry |= addr & 0xfff;
+    make_iommu_tlbe(addr, entry, addr_mask, &ret);
+    goto success;
+
+ failure:
+    ret = (IOMMUTLBEntry) { .perm = IOMMU_NONE };
+ success:
+    return ret;
+}
+
+static AddressSpace *elroy_pcihost_set_iommu(PCIBus *bus, void *opaque,
+                                            int devfn)
+{
+    ElroyState *s = opaque;
+    return &s->astro->iommu_as;
+}
+
+/*
+ * Encoding in IOSAPIC:
+ * base_addr == 0xfffa0000, we want to get 0xa0ff0000.
+ * eid  0x0ff00000 -> 0x00ff0000
+ * id   0x000ff000 -> 0xff000000
+ */
+#define SWIZZLE_HPA(a) \
+        ((((a) & 0x0ff00000) >> 4) | (((a) & 0x000ff000) << 12))
+#define UNSWIZZLE_HPA(a) \
+        (((((a) << 4) & 0x0ff00000) | (((a) >> 12) & 0x000ff000) | 0xf0000000))
+
+/* bits in the "low" I/O Sapic IRdT entry */
+#define IOSAPIC_IRDT_DISABLE      0x10000 /* if bit is set, mask this irq */
+#define IOSAPIC_IRDT_PO_LOW       0x02000
+#define IOSAPIC_IRDT_LEVEL_TRIG   0x08000
+#define IOSAPIC_IRDT_MODE_LPRI    0x00100
+
+#define CPU_IRQ_OFFSET            2
+
+static void elroy_set_irq(void *opaque, int irq, int level)
+{
+    ElroyState *s = opaque;
+    uint32_t bit;
+    uint32_t old_ilr = s->ilr;
+    hwaddr cpu_hpa;
+    uint32_t val;
+
+    val     = s->iosapic_reg[0x10 + 2 * irq];
+    cpu_hpa = s->iosapic_reg[0x11 + 2 * irq];
+    /* low nibble of val has value to write into CPU irq reg */
+    bit     = 1u << (val & (ELROY_IRQS - 1));
+    cpu_hpa = UNSWIZZLE_HPA(cpu_hpa);
+
+    if (level && (!(val & IOSAPIC_IRDT_DISABLE)) && cpu_hpa) {
+        uint32_t ena = bit & ~old_ilr;
+        s->ilr = old_ilr | bit;
+        if (ena != 0) {
+            stl_be_phys(&address_space_memory, cpu_hpa, val & 63);
+        }
+    } else {
+        s->ilr = old_ilr & ~bit;
+    }
+}
+
+static int elroy_pci_map_irq(PCIDevice *d, int irq_num)
+{
+    int slot = PCI_SLOT(d->devfn);
+
+    assert(irq_num >= 0 && irq_num < ELROY_IRQS);
+    return slot & (ELROY_IRQS - 1);
+}
+
+static void elroy_reset(DeviceState *dev)
+{
+    ElroyState *s = ELROY_PCI_HOST_BRIDGE(dev);
+    int irq;
+
+    /*
+     * Make sure to disable interrupts at reboot, otherwise the Linux kernel
+     * serial8250_config_port() in drivers/tty/serial/8250/8250_port.c
+     * will hang during autoconfig().
+     */
+    s->ilr = 0;
+    for (irq = 0; irq < ELROY_IRQS; irq++) {
+        s->iosapic_reg[0x10 + 2 * irq] = IOSAPIC_IRDT_PO_LOW |
+                IOSAPIC_IRDT_LEVEL_TRIG | (irq + CPU_IRQ_OFFSET) |
+                IOSAPIC_IRDT_DISABLE;
+        s->iosapic_reg[0x11 + 2 * irq] = SWIZZLE_HPA(CPU_HPA);
+    }
+}
+
+static void elroy_pcihost_init(Object *obj)
+{
+    ElroyState *s = ELROY_PCI_HOST_BRIDGE(obj);
+    PCIHostState *phb = PCI_HOST_BRIDGE(obj);
+    SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+
+    /* Elroy config access from CPU.  */
+    memory_region_init_io(&s->this_mem, OBJECT(s), &elroy_chip_ops,
+                          s, "elroy", 0x2000);
+
+    /* Elroy PCI config. */
+    memory_region_init_io(&phb->conf_mem, OBJECT(phb),
+                          &elroy_config_addr_ops, DEVICE(s),
+                          "pci-conf-idx", 8);
+    memory_region_init_io(&phb->data_mem, OBJECT(phb),
+                          &elroy_config_data_ops, DEVICE(s),
+                          "pci-conf-data", 8);
+    memory_region_add_subregion(&s->this_mem, 0x40,
+                                &phb->conf_mem);
+    memory_region_add_subregion(&s->this_mem, 0x48,
+                                &phb->data_mem);
+
+    /* Elroy PCI bus memory.  */
+    memory_region_init(&s->pci_mmio, OBJECT(s), "pci-mmio", UINT64_MAX);
+    memory_region_init_io(&s->pci_io, OBJECT(s), &unassigned_io_ops, obj,
+                            "pci-isa-mmio",
+                            ((uint32_t) IOS_DIST_BASE_SIZE) / ROPES_PER_IOC);
+
+    phb->bus = pci_register_root_bus(DEVICE(s), "pci",
+                                     elroy_set_irq, elroy_pci_map_irq, s,
+                                     &s->pci_mmio, &s->pci_io,
+                                     PCI_DEVFN(0, 0), ELROY_IRQS, TYPE_PCI_BUS);
+
+    sysbus_init_mmio(sbd, &s->this_mem);
+
+    qdev_init_gpio_in(DEVICE(obj), elroy_set_irq, ELROY_IRQS);
+}
+
+static Property elroy_pcihost_properties[] = {
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static const VMStateDescription vmstate_elroy = {
+    .name = "Elroy",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT64(hpa, ElroyState),
+        VMSTATE_UINT32(pci_bus_num, ElroyState),
+        VMSTATE_UINT64(config_address, ElroyState),
+        VMSTATE_UINT64(config_reg_elroy, ElroyState),
+        VMSTATE_UINT64(status_control, ElroyState),
+        VMSTATE_UINT64(arb_mask, ElroyState),
+        VMSTATE_UINT64_ARRAY(mmio_base, ElroyState, (0x0250 - 0x200) / 8),
+        VMSTATE_UINT64(error_config, ElroyState),
+        VMSTATE_UINT32(iosapic_reg_select, ElroyState),
+        VMSTATE_UINT64_ARRAY(iosapic_reg, ElroyState, 0x20),
+        VMSTATE_UINT32(ilr, ElroyState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static void elroy_pcihost_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->reset = elroy_reset;
+    device_class_set_props(dc, elroy_pcihost_properties);
+    dc->vmsd = &vmstate_elroy;
+    dc->user_creatable = false;
+}
+
+static const TypeInfo elroy_pcihost_info = {
+    .name          = TYPE_ELROY_PCI_HOST_BRIDGE,
+    .parent        = TYPE_PCI_HOST_BRIDGE,
+    .instance_init = elroy_pcihost_init,
+    .instance_size = sizeof(ElroyState),
+    .class_init    = elroy_pcihost_class_init,
+};
+
+static void elroy_register_types(void)
+{
+    type_register_static(&elroy_pcihost_info);
+}
+
+type_init(elroy_register_types)
+
+
+static ElroyState *elroy_init(int num)
+{
+    DeviceState *dev;
+
+    dev = qdev_new(TYPE_ELROY_PCI_HOST_BRIDGE);
+    dev->id = g_strdup_printf("elroy%d", num);
+    sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
+
+    return ELROY_PCI_HOST_BRIDGE(dev);
+}
+
+/*
+ * Astro Runway chip.
+ */
+
+static MemTxResult astro_chip_read_with_attrs(void *opaque, hwaddr addr,
+                                             uint64_t *data, unsigned size,
+                                             MemTxAttrs attrs)
+{
+    AstroState *s = opaque;
+    MemTxResult ret = MEMTX_OK;
+    uint64_t val = -1;
+    int index;
+
+    switch ((addr >> 3) << 3) {
+    /* R2I registers */
+    case 0x0000:        /* ID */
+        val = (0x01 << 3) | 0x01ULL;
+        break;
+    case 0x0008:        /* IOC_CTRL */
+        val = s->ioc_ctrl;
+        break;
+    case 0x0010:        /* TOC_CLIENT_ID */
+        break;
+    case 0x0030:        /* HP-UX 10.20 and 11.11 reads it. No idea. */
+        val = -1;
+        break;
+    case 0x0300 ... 0x03d8:     /* LMMIO_DIRECT0_BASE... */
+        index = (addr - 0x300) / 8;
+        val = s->ioc_ranges[index];
+        break;
+    case 0x10200:
+        val = 0;
+        break;
+    case 0x10220:
+    case 0x10230:        /* HP-UX 11.11 reads it. No idea. */
+        val = -1;
+        break;
+    case 0x22108:        /* IOC STATUS_CONTROL */
+        val = s->ioc_status_ctrl;
+        break;
+    case 0x20200 ... 0x20240 - 1: /* IOC Rope0_Control ... */
+        index = (addr - 0x20200) / 8;
+        val = s->ioc_rope_control[index];
+        break;
+    case 0x20040:        /* IOC Rope config */
+        val = s->ioc_rope_config;
+        break;
+    case 0x20050:        /* IOC Rope debug */
+        val = 0;
+        break;
+    case 0x20108:        /* IOC STATUS_CONTROL */
+        val = s->ioc_status_control;
+        break;
+    case 0x20310:        /* IOC_PCOM */
+        val = s->tlb_pcom;
+        /* TODO: flush iommu */
+        break;
+    case 0x20400:
+        val = s->ioc_flush_control;
+        break;
+    /* empty placeholders for non-existent elroys */
+#define EMPTY_PORT(x) case x:    case x+8:   val = 0;          break; \
+                      case x+40: case x+48:  val = UINT64_MAX; break;
+        EMPTY_PORT(0x30000)
+        EMPTY_PORT(0x32000)
+        EMPTY_PORT(0x34000)
+        EMPTY_PORT(0x36000)
+        EMPTY_PORT(0x38000)
+        EMPTY_PORT(0x3a000)
+        EMPTY_PORT(0x3c000)
+        EMPTY_PORT(0x3e000)
+#undef EMPTY_PORT
+
+    default:
+        trace_astro_chip_read(addr, size, val);
+        g_assert_not_reached();
+    }
+
+    /* for 32-bit accesses mask return value */
+    val = mask_32bit_val(addr, size, val);
+
+    trace_astro_chip_read(addr, size, val);
+    *data = val;
+    return ret;
+}
+
+static MemTxResult astro_chip_write_with_attrs(void *opaque, hwaddr addr,
+                                              uint64_t val, unsigned size,
+                                              MemTxAttrs attrs)
+{
+    AstroState *s = opaque;
+
+    trace_astro_chip_write(addr, size, val);
+
+    switch ((addr >> 3) << 3) {
+    case 0x0000:        /* ID */
+        break;
+    case 0x0008:        /* IOC_CTRL */
+        val &= 0x0ffffff;
+        put_val_in_int64(&s->ioc_ctrl, addr, size, val);
+        break;
+    case 0x0010:        /* TOC_CLIENT_ID */
+        break;
+    case 0x0030:        /* HP-UX 10.20 and 11.11 reads it. No idea. */
+        break;
+    case 0x0300 ... 0x03d8 - 1: /* LMMIO_DIRECT0_BASE... */
+        put_val_in_arrary(s->ioc_ranges, 0x300, addr, size, val);
+        break;
+    case 0x10200:
+    case 0x10220:
+    case 0x10230:        /* HP-UX 11.11 reads it. No idea. */
+        break;
+    case 0x22108:        /* IOC STATUS_CONTROL */
+        put_val_in_int64(&s->ioc_status_ctrl, addr, size, val);
+        break;
+    case 0x20200 ... 0x20240 - 1: /* IOC Rope0_Control ... */
+        put_val_in_arrary(s->ioc_rope_control, 0x20200, addr, size, val);
+        break;
+    case 0x20040:        /* IOC Rope config */
+        put_val_in_int64(&s->ioc_rope_config, addr, size, val);
+        break;
+    case 0x20300:
+        put_val_in_int64(&s->tlb_ibase, addr, size, val);
+        break;
+    case 0x20308:
+        put_val_in_int64(&s->tlb_imask, addr, size, val);
+        break;
+    case 0x20310:
+        put_val_in_int64(&s->tlb_pcom, addr, size, val);
+        /* TODO: flush iommu */
+        break;
+    case 0x20318:
+        put_val_in_int64(&s->tlb_tcnfg, addr, size, val);
+        break;
+    case 0x20320:
+        put_val_in_int64(&s->tlb_pdir_base, addr, size, val);
+        break;
+    /*
+     * empty placeholders for non-existent elroys, e.g.
+     * func_class, pci config & data
+     */
+#define EMPTY_PORT(x) case x: case x+8: case x+0x40: case x+0x48:
+        EMPTY_PORT(0x30000)
+        EMPTY_PORT(0x32000)
+        EMPTY_PORT(0x34000)
+        EMPTY_PORT(0x36000)
+        EMPTY_PORT(0x38000)
+        EMPTY_PORT(0x3a000)
+        EMPTY_PORT(0x3c000)
+        EMPTY_PORT(0x3e000)
+        break;
+#undef EMPTY_PORT
+
+    default:
+        /* Controlled by astro_chip_mem_valid above.  */
+        trace_astro_chip_write(addr, size, val);
+        g_assert_not_reached();
+    }
+    return MEMTX_OK;
+}
+
+static const MemoryRegionOps astro_chip_ops = {
+    .read_with_attrs = astro_chip_read_with_attrs,
+    .write_with_attrs = astro_chip_write_with_attrs,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 8,
+    },
+    .impl = {
+        .min_access_size = 4,
+        .max_access_size = 8,
+    },
+};
+
+static const VMStateDescription vmstate_astro = {
+    .name = "Astro",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT64(ioc_ctrl, AstroState),
+        VMSTATE_UINT64(ioc_status_ctrl, AstroState),
+        VMSTATE_UINT64_ARRAY(ioc_ranges, AstroState, (0x03d8 - 0x300) / 8),
+        VMSTATE_UINT64(ioc_rope_config, AstroState),
+        VMSTATE_UINT64(ioc_status_control, AstroState),
+        VMSTATE_UINT64(ioc_flush_control, AstroState),
+        VMSTATE_UINT64_ARRAY(ioc_rope_control, AstroState, 8),
+        VMSTATE_UINT64(tlb_ibase, AstroState),
+        VMSTATE_UINT64(tlb_imask, AstroState),
+        VMSTATE_UINT64(tlb_pcom, AstroState),
+        VMSTATE_UINT64(tlb_tcnfg, AstroState),
+        VMSTATE_UINT64(tlb_pdir_base, AstroState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static void astro_reset(DeviceState *dev)
+{
+    AstroState *s = ASTRO_CHIP(dev);
+    int i;
+
+    s->ioc_ctrl = 0x29cf;
+    s->ioc_rope_config = 0xc5f;
+    s->ioc_flush_control = 0xb03;
+    s->ioc_status_control = 0;
+    memset(&s->ioc_rope_control, 0, sizeof(s->ioc_rope_control));
+
+    /*
+     * The SBA BASE/MASK registers control CPU -> IO routing.
+     * The LBA BASE/MASK registers control IO -> System routing (in Elroy)
+     */
+    memset(&s->ioc_ranges, 0, sizeof(s->ioc_ranges));
+    s->ioc_ranges[(0x360 - 0x300) / 8] = LMMIO_DIST_BASE_ADDR | 0x01; /* LMMIO_DIST_BASE (SBA) */
+    s->ioc_ranges[(0x368 - 0x300) / 8] = 0xfc000000;          /* LMMIO_DIST_MASK */
+    s->ioc_ranges[(0x370 - 0x300) / 8] = 0;                   /* LMMIO_DIST_ROUTE */
+    s->ioc_ranges[(0x390 - 0x300) / 8] = IOS_DIST_BASE_ADDR | 0x01; /* IOS_DIST_BASE */
+    s->ioc_ranges[(0x398 - 0x300) / 8] = 0xffffff0000;        /* IOS_DIST_MASK    */
+    s->ioc_ranges[(0x3a0 - 0x300) / 8] = 0x3400000000000000ULL; /* IOS_DIST_ROUTE */
+    s->ioc_ranges[(0x3c0 - 0x300) / 8] = 0xfffee00000;        /* IOS_DIRECT_BASE  */
+    s->ioc_ranges[(0x3c8 - 0x300) / 8] = 0xffffff0000;        /* IOS_DIRECT_MASK  */
+    s->ioc_ranges[(0x3d0 - 0x300) / 8] = 0x0;                 /* IOS_DIRECT_ROUTE */
+
+    s->tlb_ibase = 0;
+    s->tlb_imask = 0;
+    s->tlb_pcom = 0;
+    s->tlb_tcnfg = 0;
+    s->tlb_pdir_base = 0;
+
+    for (i = 0; i < ELROY_NUM; i++) {
+        elroy_reset(DEVICE(s->elroy[i]));
+    }
+}
+
+static void astro_init(Object *obj)
+{
+}
+
+static void astro_realize(DeviceState *obj, Error **errp)
+{
+    AstroState *s = ASTRO_CHIP(obj);
+    SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+    int i;
+
+    memory_region_init_io(&s->this_mem, OBJECT(s), &astro_chip_ops,
+                          s, "astro", 0x40000);
+    sysbus_init_mmio(sbd, &s->this_mem);
+
+    /* Host memory as seen from Elroys PCI side, via the IOMMU.  */
+    memory_region_init_iommu(&s->iommu, sizeof(s->iommu),
+                             TYPE_ASTRO_IOMMU_MEMORY_REGION, OBJECT(s),
+                             "iommu-astro", UINT64_MAX);
+    address_space_init(&s->iommu_as, MEMORY_REGION(&s->iommu),
+                       "bm-pci");
+
+    /* Create Elroys (PCI host bus chips).  */
+    for (i = 0; i < ELROY_NUM; i++) {
+        static const int elroy_hpa_offsets[ELROY_NUM] = {
+                    0x30000, 0x32000, 0x38000, 0x3c000 };
+        static const char elroy_rope_nr[ELROY_NUM] = {
+                    0, 1, 4, 6 }; /* busnum path, e.g. [10:6] */
+        int addr_offset;
+        ElroyState *elroy;
+        hwaddr map_addr;
+        uint64_t map_size;
+        int rope;
+
+        addr_offset = elroy_hpa_offsets[i];
+        rope = elroy_rope_nr[i];
+
+        elroy = elroy_init(i);
+        s->elroy[i] = elroy;
+        elroy->hpa = ASTRO_HPA + addr_offset;
+        elroy->pci_bus_num = i;
+        elroy->astro = s;
+
+        /*
+         * NOTE: we only allow PCI devices on first Elroy for now.
+         * SeaBIOS will not find devices on the other busses.
+         */
+        if (i > 0) {
+            qbus_mark_full(&PCI_HOST_BRIDGE(elroy)->bus->qbus);
+        }
+
+        /* map elroy config addresses into Astro space */
+        memory_region_add_subregion(&s->this_mem, addr_offset,
+                                    &elroy->this_mem);
+
+        /* LMMIO */
+        elroy->mmio_base[(0x0200 - 0x200) / 8] = 0xf0000001;
+        elroy->mmio_base[(0x0208 - 0x200) / 8] = 0xf8000000;
+        /* GMMIO */
+        elroy->mmio_base[(0x0210 - 0x200) / 8] = 0x000000f800000001;
+        elroy->mmio_base[(0x0218 - 0x200) / 8] = 0x000000ff80000000;
+        /* WLMMIO */
+        elroy->mmio_base[(0x0220 - 0x200) / 8] = 0xf0000001;
+        elroy->mmio_base[(0x0228 - 0x200) / 8] = 0xf0000000;
+        /* WGMMIO */
+        elroy->mmio_base[(0x0230 - 0x200) / 8] = 0x000000f800000001;
+        elroy->mmio_base[(0x0238 - 0x200) / 8] = 0x000000fc00000000;
+        /* IOS_BASE */
+        map_size = IOS_DIST_BASE_SIZE / ROPES_PER_IOC;
+        elroy->mmio_base[(0x0240 - 0x200) / 8] = rope * map_size | 0x01;
+        elroy->mmio_base[(0x0248 - 0x200) / 8] = 0x0000e000;
+
+        /* map elroys mmio */
+        map_size = LMMIO_DIST_BASE_SIZE / ROPES_PER_IOC;
+        map_addr = (uint32_t) (LMMIO_DIST_BASE_ADDR + rope * map_size);
+        memory_region_init_alias(&elroy->pci_mmio_alias, OBJECT(elroy),
+                                 "pci-mmio-alias",
+                                 &elroy->pci_mmio, map_addr, map_size);
+        memory_region_add_subregion(get_system_memory(), map_addr,
+                                 &elroy->pci_mmio_alias);
+
+        map_size = IOS_DIST_BASE_SIZE / ROPES_PER_IOC;
+        map_addr = (uint32_t) (IOS_DIST_BASE_ADDR + rope * map_size);
+        memory_region_add_subregion(get_system_memory(), map_addr,
+                                 &elroy->pci_io);
+
+        /* Host memory as seen from the PCI side, via the IOMMU.  */
+        pci_setup_iommu(PCI_HOST_BRIDGE(elroy)->bus, elroy_pcihost_set_iommu,
+                                 elroy);
+    }
+}
+
+static void astro_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->reset = astro_reset;
+    dc->vmsd = &vmstate_astro;
+    dc->realize = astro_realize;
+    /*
+     * astro with elroys are hard part of the newer PA2.0 machines and can not
+     * be created without that hardware
+     */
+    dc->user_creatable = false;
+}
+
+static const TypeInfo astro_chip_info = {
+    .name          = TYPE_ASTRO_CHIP,
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_init = astro_init,
+    .instance_size = sizeof(AstroState),
+    .class_init    = astro_class_init,
+};
+
+static void astro_iommu_memory_region_class_init(ObjectClass *klass,
+                                                   void *data)
+{
+    IOMMUMemoryRegionClass *imrc = IOMMU_MEMORY_REGION_CLASS(klass);
+
+    imrc->translate = astro_translate_iommu;
+}
+
+static const TypeInfo astro_iommu_memory_region_info = {
+    .parent = TYPE_IOMMU_MEMORY_REGION,
+    .name = TYPE_ASTRO_IOMMU_MEMORY_REGION,
+    .class_init = astro_iommu_memory_region_class_init,
+};
+
+
+static void astro_register_types(void)
+{
+    type_register_static(&astro_chip_info);
+    type_register_static(&astro_iommu_memory_region_info);
+}
+
+type_init(astro_register_types)
diff --git a/hw/pci-host/meson.build b/hw/pci-host/meson.build
index 64eada76fe..f891f026cb 100644
--- a/hw/pci-host/meson.build
+++ b/hw/pci-host/meson.build
@@ -27,6 +27,7 @@ pci_ss.add(when: 'CONFIG_MV64361', if_true: files('mv64361.c'))
 pci_ss.add(when: 'CONFIG_VERSATILE_PCI', if_true: files('versatile.c'))
 
 # HPPA devices
+pci_ss.add(when: 'CONFIG_ASTRO', if_true: files('astro.c'))
 pci_ss.add(when: 'CONFIG_DINO', if_true: files('dino.c'))
 
 system_ss.add_all(when: 'CONFIG_PCI', if_true: pci_ss)
diff --git a/hw/pci-host/trace-events b/hw/pci-host/trace-events
index 9d216bb89f..b2f47e6335 100644
--- a/hw/pci-host/trace-events
+++ b/hw/pci-host/trace-events
@@ -46,3 +46,14 @@ pnv_phb4_xive_notify_abt(uint64_t notif_port, uint64_t data) "notif=@0x%"PRIx64"
 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"
+
+# astro.c
+astro_chip_mem_valid(uint64_t addr, uint32_t val) "access to addr 0x%"PRIx64" is %d"
+astro_chip_read(uint64_t addr, int size, uint64_t val) "addr 0x%"PRIx64" size %d val 0x%"PRIx64
+astro_chip_write(uint64_t addr, int size, uint64_t val) "addr 0x%"PRIx64" size %d val 0x%"PRIx64
+elroy_read(uint64_t addr, int size, uint64_t val) "addr 0x%"PRIx64" size %d val 0x%"PRIx64
+elroy_write(uint64_t addr, int size, uint64_t val) "addr 0x%"PRIx64" size %d val 0x%"PRIx64
+elroy_pci_config_data_read(uint64_t addr, int size, uint64_t val) "addr 0x%"PRIx64" size %d val 0x%"PRIx64
+elroy_pci_config_data_write(uint64_t addr, int size, uint64_t val) "addr 0x%"PRIx64" size %d val 0x%"PRIx64
+iosapic_reg_write(uint64_t reg_select, int size, uint64_t val) "reg_select 0x%"PRIx64" size %d val 0x%"PRIx64
+iosapic_reg_read(uint64_t reg_select, int size, uint64_t val) "reg_select 0x%"PRIx64" size %d val 0x%"PRIx64