|  | /* | 
|  | * 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" | 
|  |  | 
|  | #define F_EXTEND(addr) ((addr) | MAKE_64BIT_MASK(32, 32)) | 
|  |  | 
|  | #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 "target/hppa/cpu.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 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 { | 
|  | goto check_hf; | 
|  | } | 
|  | } | 
|  | trace_iosapic_reg_read(s->iosapic_reg_select, size, val); | 
|  | break; | 
|  | default: | 
|  | check_hf: | 
|  | if (s->status_control & HF_ENABLE) { | 
|  | val = 0; | 
|  | ret = MEMTX_DECODE_ERROR; | 
|  | } else { | 
|  | /* return -1ULL if HardFail is disabled */ | 
|  | val = ~0; | 
|  | ret = MEMTX_OK; | 
|  | } | 
|  | } | 
|  | 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 0x000: /* PCI_ID & PCI_COMMAND_STATUS_REG */ | 
|  | break; | 
|  | 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 0x300: /* ibase */ | 
|  | case 0x308: /* imask */ | 
|  | 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 { | 
|  | goto check_hf; | 
|  | } | 
|  | 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: | 
|  | check_hf: | 
|  | if (s->status_control & HF_ENABLE) { | 
|  | return MEMTX_DECODE_ERROR; | 
|  | } | 
|  | } | 
|  | 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, | 
|  | }; | 
|  |  | 
|  |  | 
|  | /* 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); | 
|  | hwaddr pdir_ptr, index, 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 SBA_PDIR_VALID_BIT      0x8000000000000000ULL | 
|  |  | 
|  | addr &= ~addr_mask; | 
|  |  | 
|  | /* | 
|  | * Default translation: "32-bit PCI Addressing on 40-bit Runway". | 
|  | * For addresses in the 32-bit memory address range ... and then | 
|  | * language which not-coincidentally matches the PSW.W=0 mapping. | 
|  | */ | 
|  | if (addr <= UINT32_MAX) { | 
|  | entry = hppa_abs_to_phys_pa2_w0(addr); | 
|  | } else { | 
|  | entry = addr; | 
|  | } | 
|  |  | 
|  | /* "range enable" flag cleared? */ | 
|  | if ((s->tlb_ibase & 1) == 0) { | 
|  | goto skip; | 
|  | } | 
|  |  | 
|  | ibase = s->tlb_ibase & ~1ULL; | 
|  | if ((addr & s->tlb_imask) != ibase) { | 
|  | /* do not translate this one! */ | 
|  | goto skip; | 
|  | } | 
|  |  | 
|  | index = PDIR_INDEX(addr); | 
|  | 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 ? */ | 
|  | /* failure */ | 
|  | return (IOMMUTLBEntry) { .perm = IOMMU_NONE }; | 
|  | } | 
|  |  | 
|  | entry &= ~SBA_PDIR_VALID_BIT; | 
|  | entry >>= IOVP_SHIFT; | 
|  | entry <<= 12; | 
|  |  | 
|  | skip: | 
|  | return (IOMMUTLBEntry) { | 
|  | .target_as = &address_space_memory, | 
|  | .iova = addr, | 
|  | .translated_addr = entry, | 
|  | .addr_mask = addr_mask, | 
|  | .perm = IOMMU_RW, | 
|  | }; | 
|  | } | 
|  |  | 
|  | static AddressSpace *elroy_pcihost_set_iommu(PCIBus *bus, void *opaque, | 
|  | int devfn) | 
|  | { | 
|  | ElroyState *s = opaque; | 
|  | return &s->astro->iommu_as; | 
|  | } | 
|  |  | 
|  | static const PCIIOMMUOps elroy_pcihost_iommu_ops = { | 
|  | .get_address_space = elroy_pcihost_set_iommu, | 
|  | }; | 
|  |  | 
|  | /* | 
|  | * 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, F_EXTEND(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 = (const 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 0x0078:        /* NetBSD reads 0x78 ? */ | 
|  | 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: | 
|  | val = 0; | 
|  | ret = MEMTX_DECODE_ERROR; | 
|  | } | 
|  |  | 
|  | /* 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) | 
|  | { | 
|  | MemTxResult ret = MEMTX_OK; | 
|  | 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 0x20200 ... 0x20240 - 1: /* IOC Rope0_Control ... */ | 
|  | put_val_in_arrary(s->ioc_rope_control, 0x20200, addr, size, val); | 
|  | break; | 
|  | case 0x20040:        /* IOC Rope config */ | 
|  | case 0x22040: | 
|  | put_val_in_int64(&s->ioc_rope_config, addr, size, val); | 
|  | break; | 
|  | case 0x20300: | 
|  | case 0x22300: | 
|  | put_val_in_int64(&s->tlb_ibase, addr, size, val); | 
|  | break; | 
|  | case 0x20308: | 
|  | case 0x22308: | 
|  | put_val_in_int64(&s->tlb_imask, addr, size, val); | 
|  | break; | 
|  | case 0x20310: | 
|  | case 0x22310: | 
|  | put_val_in_int64(&s->tlb_pcom, addr, size, val); | 
|  | /* TODO: flush iommu */ | 
|  | break; | 
|  | case 0x20318: | 
|  | case 0x22318: | 
|  | put_val_in_int64(&s->tlb_tcnfg, addr, size, val); | 
|  | break; | 
|  | case 0x20320: | 
|  | case 0x22320: | 
|  | put_val_in_int64(&s->tlb_pdir_base, addr, size, val); | 
|  | break; | 
|  | case 0x22000:       /* func_id */ | 
|  | break; | 
|  | case 0x22008:       /* func_class */ | 
|  | break; | 
|  | case 0x22050:       /* rope_debug */ | 
|  | break; | 
|  | case 0x22108:        /* IOC STATUS_CONTROL */ | 
|  | put_val_in_int64(&s->ioc_status_ctrl, 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: | 
|  | ret = MEMTX_DECODE_ERROR; | 
|  | } | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | 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 = (const 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 = F_EXTEND(LMMIO_DIST_BASE_ADDR + rope * map_size); | 
|  | memory_region_init_alias(&elroy->pci_mmio_alias, OBJECT(elroy), | 
|  | "pci-mmio-alias", | 
|  | &elroy->pci_mmio, (uint32_t) map_addr, map_size); | 
|  | memory_region_add_subregion(get_system_memory(), map_addr, | 
|  | &elroy->pci_mmio_alias); | 
|  |  | 
|  | /* map elroys io */ | 
|  | map_size = IOS_DIST_BASE_SIZE / ROPES_PER_IOC; | 
|  | map_addr = F_EXTEND(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_iommu_ops, | 
|  | 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) |