| /* |
| * 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) |