| /* |
| * QEMU PowerPC PowerNV (POWER8) PHB3 model |
| * |
| * Copyright (c) 2014-2020, IBM Corporation. |
| * |
| * This code is licensed under the GPL version 2 or later. See the |
| * COPYING file in the top-level directory. |
| */ |
| #include "qemu/osdep.h" |
| #include "qapi/error.h" |
| #include "qemu/log.h" |
| #include "target/ppc/cpu.h" |
| #include "hw/ppc/fdt.h" |
| #include "hw/pci-host/pnv_phb3_regs.h" |
| #include "hw/pci-host/pnv_phb3.h" |
| #include "hw/ppc/pnv.h" |
| #include "hw/ppc/pnv_xscom.h" |
| #include "hw/pci/pci_bridge.h" |
| #include "hw/pci/pci_bus.h" |
| |
| #include <libfdt.h> |
| |
| #define phb3_pbcq_error(pbcq, fmt, ...) \ |
| qemu_log_mask(LOG_GUEST_ERROR, "phb3_pbcq[%d:%d]: " fmt "\n", \ |
| (pbcq)->phb->chip_id, (pbcq)->phb->phb_id, ## __VA_ARGS__) |
| |
| static uint64_t pnv_pbcq_nest_xscom_read(void *opaque, hwaddr addr, |
| unsigned size) |
| { |
| PnvPBCQState *pbcq = PNV_PBCQ(opaque); |
| uint32_t offset = addr >> 3; |
| |
| return pbcq->nest_regs[offset]; |
| } |
| |
| static uint64_t pnv_pbcq_pci_xscom_read(void *opaque, hwaddr addr, |
| unsigned size) |
| { |
| PnvPBCQState *pbcq = PNV_PBCQ(opaque); |
| uint32_t offset = addr >> 3; |
| |
| return pbcq->pci_regs[offset]; |
| } |
| |
| static uint64_t pnv_pbcq_spci_xscom_read(void *opaque, hwaddr addr, |
| unsigned size) |
| { |
| PnvPBCQState *pbcq = PNV_PBCQ(opaque); |
| uint32_t offset = addr >> 3; |
| |
| if (offset == PBCQ_SPCI_ASB_DATA) { |
| return pnv_phb3_reg_read(pbcq->phb, |
| pbcq->spci_regs[PBCQ_SPCI_ASB_ADDR], 8); |
| } |
| return pbcq->spci_regs[offset]; |
| } |
| |
| static void pnv_pbcq_update_map(PnvPBCQState *pbcq) |
| { |
| uint64_t bar_en = pbcq->nest_regs[PBCQ_NEST_BAR_EN]; |
| uint64_t bar, mask, size; |
| |
| /* |
| * NOTE: This will really not work well if those are remapped |
| * after the PHB has created its sub regions. We could do better |
| * if we had a way to resize regions but we don't really care |
| * that much in practice as the stuff below really only happens |
| * once early during boot |
| */ |
| |
| /* Handle unmaps */ |
| if (memory_region_is_mapped(&pbcq->mmbar0) && |
| !(bar_en & PBCQ_NEST_BAR_EN_MMIO0)) { |
| memory_region_del_subregion(get_system_memory(), &pbcq->mmbar0); |
| } |
| if (memory_region_is_mapped(&pbcq->mmbar1) && |
| !(bar_en & PBCQ_NEST_BAR_EN_MMIO1)) { |
| memory_region_del_subregion(get_system_memory(), &pbcq->mmbar1); |
| } |
| if (memory_region_is_mapped(&pbcq->phbbar) && |
| !(bar_en & PBCQ_NEST_BAR_EN_PHB)) { |
| memory_region_del_subregion(get_system_memory(), &pbcq->phbbar); |
| } |
| |
| /* Update PHB */ |
| pnv_phb3_update_regions(pbcq->phb); |
| |
| /* Handle maps */ |
| if (!memory_region_is_mapped(&pbcq->mmbar0) && |
| (bar_en & PBCQ_NEST_BAR_EN_MMIO0)) { |
| bar = pbcq->nest_regs[PBCQ_NEST_MMIO_BAR0] >> 14; |
| mask = pbcq->nest_regs[PBCQ_NEST_MMIO_MASK0]; |
| size = ((~mask) >> 14) + 1; |
| memory_region_init(&pbcq->mmbar0, OBJECT(pbcq), "pbcq-mmio0", size); |
| memory_region_add_subregion(get_system_memory(), bar, &pbcq->mmbar0); |
| pbcq->mmio0_base = bar; |
| pbcq->mmio0_size = size; |
| } |
| if (!memory_region_is_mapped(&pbcq->mmbar1) && |
| (bar_en & PBCQ_NEST_BAR_EN_MMIO1)) { |
| bar = pbcq->nest_regs[PBCQ_NEST_MMIO_BAR1] >> 14; |
| mask = pbcq->nest_regs[PBCQ_NEST_MMIO_MASK1]; |
| size = ((~mask) >> 14) + 1; |
| memory_region_init(&pbcq->mmbar1, OBJECT(pbcq), "pbcq-mmio1", size); |
| memory_region_add_subregion(get_system_memory(), bar, &pbcq->mmbar1); |
| pbcq->mmio1_base = bar; |
| pbcq->mmio1_size = size; |
| } |
| if (!memory_region_is_mapped(&pbcq->phbbar) |
| && (bar_en & PBCQ_NEST_BAR_EN_PHB)) { |
| bar = pbcq->nest_regs[PBCQ_NEST_PHB_BAR] >> 14; |
| size = 0x1000; |
| memory_region_init(&pbcq->phbbar, OBJECT(pbcq), "pbcq-phb", size); |
| memory_region_add_subregion(get_system_memory(), bar, &pbcq->phbbar); |
| } |
| |
| /* Update PHB */ |
| pnv_phb3_update_regions(pbcq->phb); |
| } |
| |
| static void pnv_pbcq_nest_xscom_write(void *opaque, hwaddr addr, |
| uint64_t val, unsigned size) |
| { |
| PnvPBCQState *pbcq = PNV_PBCQ(opaque); |
| uint32_t reg = addr >> 3; |
| |
| switch (reg) { |
| case PBCQ_NEST_MMIO_BAR0: |
| case PBCQ_NEST_MMIO_BAR1: |
| case PBCQ_NEST_MMIO_MASK0: |
| case PBCQ_NEST_MMIO_MASK1: |
| if (pbcq->nest_regs[PBCQ_NEST_BAR_EN] & |
| (PBCQ_NEST_BAR_EN_MMIO0 | |
| PBCQ_NEST_BAR_EN_MMIO1)) { |
| phb3_pbcq_error(pbcq, "Changing enabled BAR unsupported"); |
| } |
| pbcq->nest_regs[reg] = val & 0xffffffffc0000000ull; |
| break; |
| case PBCQ_NEST_PHB_BAR: |
| if (pbcq->nest_regs[PBCQ_NEST_BAR_EN] & PBCQ_NEST_BAR_EN_PHB) { |
| phb3_pbcq_error(pbcq, "Changing enabled BAR unsupported"); |
| } |
| pbcq->nest_regs[reg] = val & 0xfffffffffc000000ull; |
| break; |
| case PBCQ_NEST_BAR_EN: |
| pbcq->nest_regs[reg] = val & 0xf800000000000000ull; |
| pnv_pbcq_update_map(pbcq); |
| pnv_phb3_remap_irqs(pbcq->phb); |
| break; |
| case PBCQ_NEST_IRSN_COMPARE: |
| case PBCQ_NEST_IRSN_MASK: |
| pbcq->nest_regs[reg] = val & PBCQ_NEST_IRSN_COMP; |
| pnv_phb3_remap_irqs(pbcq->phb); |
| break; |
| case PBCQ_NEST_LSI_SRC_ID: |
| pbcq->nest_regs[reg] = val & PBCQ_NEST_LSI_SRC; |
| pnv_phb3_remap_irqs(pbcq->phb); |
| break; |
| default: |
| phb3_pbcq_error(pbcq, "%s @0x%"HWADDR_PRIx"=%"PRIx64, __func__, |
| addr, val); |
| } |
| } |
| |
| static void pnv_pbcq_pci_xscom_write(void *opaque, hwaddr addr, |
| uint64_t val, unsigned size) |
| { |
| PnvPBCQState *pbcq = PNV_PBCQ(opaque); |
| uint32_t reg = addr >> 3; |
| |
| switch (reg) { |
| case PBCQ_PCI_BAR2: |
| pbcq->pci_regs[reg] = val & 0xfffffffffc000000ull; |
| pnv_pbcq_update_map(pbcq); |
| break; |
| default: |
| phb3_pbcq_error(pbcq, "%s @0x%"HWADDR_PRIx"=%"PRIx64, __func__, |
| addr, val); |
| } |
| } |
| |
| static void pnv_pbcq_spci_xscom_write(void *opaque, hwaddr addr, |
| uint64_t val, unsigned size) |
| { |
| PnvPBCQState *pbcq = PNV_PBCQ(opaque); |
| uint32_t reg = addr >> 3; |
| |
| switch (reg) { |
| case PBCQ_SPCI_ASB_ADDR: |
| pbcq->spci_regs[reg] = val & 0xfff; |
| break; |
| case PBCQ_SPCI_ASB_STATUS: |
| pbcq->spci_regs[reg] &= ~val; |
| break; |
| case PBCQ_SPCI_ASB_DATA: |
| pnv_phb3_reg_write(pbcq->phb, pbcq->spci_regs[PBCQ_SPCI_ASB_ADDR], |
| val, 8); |
| break; |
| case PBCQ_SPCI_AIB_CAPP_EN: |
| case PBCQ_SPCI_CAPP_SEC_TMR: |
| break; |
| default: |
| phb3_pbcq_error(pbcq, "%s @0x%"HWADDR_PRIx"=%"PRIx64, __func__, |
| addr, val); |
| } |
| } |
| |
| static const MemoryRegionOps pnv_pbcq_nest_xscom_ops = { |
| .read = pnv_pbcq_nest_xscom_read, |
| .write = pnv_pbcq_nest_xscom_write, |
| .valid.min_access_size = 8, |
| .valid.max_access_size = 8, |
| .impl.min_access_size = 8, |
| .impl.max_access_size = 8, |
| .endianness = DEVICE_BIG_ENDIAN, |
| }; |
| |
| static const MemoryRegionOps pnv_pbcq_pci_xscom_ops = { |
| .read = pnv_pbcq_pci_xscom_read, |
| .write = pnv_pbcq_pci_xscom_write, |
| .valid.min_access_size = 8, |
| .valid.max_access_size = 8, |
| .impl.min_access_size = 8, |
| .impl.max_access_size = 8, |
| .endianness = DEVICE_BIG_ENDIAN, |
| }; |
| |
| static const MemoryRegionOps pnv_pbcq_spci_xscom_ops = { |
| .read = pnv_pbcq_spci_xscom_read, |
| .write = pnv_pbcq_spci_xscom_write, |
| .valid.min_access_size = 8, |
| .valid.max_access_size = 8, |
| .impl.min_access_size = 8, |
| .impl.max_access_size = 8, |
| .endianness = DEVICE_BIG_ENDIAN, |
| }; |
| |
| static void pnv_pbcq_default_bars(PnvPBCQState *pbcq) |
| { |
| uint64_t mm0, mm1, reg; |
| PnvPHB3 *phb = pbcq->phb; |
| |
| mm0 = 0x3d00000000000ull + 0x4000000000ull * phb->chip_id + |
| 0x1000000000ull * phb->phb_id; |
| mm1 = 0x3ff8000000000ull + 0x0200000000ull * phb->chip_id + |
| 0x0080000000ull * phb->phb_id; |
| reg = 0x3fffe40000000ull + 0x0000400000ull * phb->chip_id + |
| 0x0000100000ull * phb->phb_id; |
| |
| pbcq->nest_regs[PBCQ_NEST_MMIO_BAR0] = mm0 << 14; |
| pbcq->nest_regs[PBCQ_NEST_MMIO_BAR1] = mm1 << 14; |
| pbcq->nest_regs[PBCQ_NEST_PHB_BAR] = reg << 14; |
| pbcq->nest_regs[PBCQ_NEST_MMIO_MASK0] = 0x3fff000000000ull << 14; |
| pbcq->nest_regs[PBCQ_NEST_MMIO_MASK1] = 0x3ffff80000000ull << 14; |
| pbcq->pci_regs[PBCQ_PCI_BAR2] = reg << 14; |
| } |
| |
| static void pnv_pbcq_realize(DeviceState *dev, Error **errp) |
| { |
| PnvPBCQState *pbcq = PNV_PBCQ(dev); |
| PnvPHB3 *phb; |
| char name[32]; |
| |
| assert(pbcq->phb); |
| phb = pbcq->phb; |
| |
| /* TODO: Fix OPAL to do that: establish default BAR values */ |
| pnv_pbcq_default_bars(pbcq); |
| |
| /* Initialize the XSCOM region for the PBCQ registers */ |
| snprintf(name, sizeof(name), "xscom-pbcq-nest-%d.%d", |
| phb->chip_id, phb->phb_id); |
| pnv_xscom_region_init(&pbcq->xscom_nest_regs, OBJECT(dev), |
| &pnv_pbcq_nest_xscom_ops, pbcq, name, |
| PNV_XSCOM_PBCQ_NEST_SIZE); |
| snprintf(name, sizeof(name), "xscom-pbcq-pci-%d.%d", |
| phb->chip_id, phb->phb_id); |
| pnv_xscom_region_init(&pbcq->xscom_pci_regs, OBJECT(dev), |
| &pnv_pbcq_pci_xscom_ops, pbcq, name, |
| PNV_XSCOM_PBCQ_PCI_SIZE); |
| snprintf(name, sizeof(name), "xscom-pbcq-spci-%d.%d", |
| phb->chip_id, phb->phb_id); |
| pnv_xscom_region_init(&pbcq->xscom_spci_regs, OBJECT(dev), |
| &pnv_pbcq_spci_xscom_ops, pbcq, name, |
| PNV_XSCOM_PBCQ_SPCI_SIZE); |
| |
| /* Populate the XSCOM address space. */ |
| pnv_xscom_add_subregion(phb->chip, |
| PNV_XSCOM_PBCQ_NEST_BASE + 0x400 * phb->phb_id, |
| &pbcq->xscom_nest_regs); |
| pnv_xscom_add_subregion(phb->chip, |
| PNV_XSCOM_PBCQ_PCI_BASE + 0x400 * phb->phb_id, |
| &pbcq->xscom_pci_regs); |
| pnv_xscom_add_subregion(phb->chip, |
| PNV_XSCOM_PBCQ_SPCI_BASE + 0x040 * phb->phb_id, |
| &pbcq->xscom_spci_regs); |
| } |
| |
| static int pnv_pbcq_dt_xscom(PnvXScomInterface *dev, void *fdt, |
| int xscom_offset) |
| { |
| const char compat[] = "ibm,power8-pbcq"; |
| PnvPHB3 *phb = PNV_PBCQ(dev)->phb; |
| char *name; |
| int offset; |
| uint32_t lpc_pcba = PNV_XSCOM_PBCQ_NEST_BASE + 0x400 * phb->phb_id; |
| uint32_t reg[] = { |
| cpu_to_be32(lpc_pcba), |
| cpu_to_be32(PNV_XSCOM_PBCQ_NEST_SIZE), |
| cpu_to_be32(PNV_XSCOM_PBCQ_PCI_BASE + 0x400 * phb->phb_id), |
| cpu_to_be32(PNV_XSCOM_PBCQ_PCI_SIZE), |
| cpu_to_be32(PNV_XSCOM_PBCQ_SPCI_BASE + 0x040 * phb->phb_id), |
| cpu_to_be32(PNV_XSCOM_PBCQ_SPCI_SIZE) |
| }; |
| |
| name = g_strdup_printf("pbcq@%x", lpc_pcba); |
| offset = fdt_add_subnode(fdt, xscom_offset, name); |
| _FDT(offset); |
| g_free(name); |
| |
| _FDT((fdt_setprop(fdt, offset, "reg", reg, sizeof(reg)))); |
| |
| _FDT((fdt_setprop_cell(fdt, offset, "ibm,phb-index", phb->phb_id))); |
| _FDT((fdt_setprop_cell(fdt, offset, "ibm,chip-id", phb->chip_id))); |
| _FDT((fdt_setprop(fdt, offset, "compatible", compat, |
| sizeof(compat)))); |
| return 0; |
| } |
| |
| static void phb3_pbcq_instance_init(Object *obj) |
| { |
| PnvPBCQState *pbcq = PNV_PBCQ(obj); |
| |
| object_property_add_link(obj, "phb", TYPE_PNV_PHB3, |
| (Object **)&pbcq->phb, |
| object_property_allow_set_link, |
| OBJ_PROP_LINK_STRONG); |
| } |
| |
| static void pnv_pbcq_class_init(ObjectClass *klass, void *data) |
| { |
| DeviceClass *dc = DEVICE_CLASS(klass); |
| PnvXScomInterfaceClass *xdc = PNV_XSCOM_INTERFACE_CLASS(klass); |
| |
| xdc->dt_xscom = pnv_pbcq_dt_xscom; |
| |
| dc->realize = pnv_pbcq_realize; |
| dc->user_creatable = false; |
| } |
| |
| static const TypeInfo pnv_pbcq_type_info = { |
| .name = TYPE_PNV_PBCQ, |
| .parent = TYPE_DEVICE, |
| .instance_size = sizeof(PnvPBCQState), |
| .instance_init = phb3_pbcq_instance_init, |
| .class_init = pnv_pbcq_class_init, |
| .interfaces = (InterfaceInfo[]) { |
| { TYPE_PNV_XSCOM_INTERFACE }, |
| { } |
| } |
| }; |
| |
| static void pnv_pbcq_register_types(void) |
| { |
| type_register_static(&pnv_pbcq_type_info); |
| } |
| |
| type_init(pnv_pbcq_register_types) |