diff mbox

[RFC,09/12] ppc440: Add emulation of plb-pcix controller found in some 440 SoCs

Message ID 0362ffdbfb08b46edef0e2180387078299a9b721.1502643878.git.balaton@eik.bme.hu
State New
Headers show

Commit Message

BALATON Zoltan Aug. 13, 2017, 5:04 p.m. UTC
Signed-off-by: BALATON Zoltan <balaton@eik.bme.hu>
---
 hw/ppc/Makefile.objs |   2 +-
 hw/ppc/ppc440_pcix.c | 506 +++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 507 insertions(+), 1 deletion(-)
 create mode 100644 hw/ppc/ppc440_pcix.c

Comments

David Gibson Aug. 18, 2017, 1:53 a.m. UTC | #1
On Sun, Aug 13, 2017 at 07:04:38PM +0200, BALATON Zoltan wrote:

You know I'm going to say it, right: needs a commit message.

What's a "plb-pcix", and what's an example of a 440 SoCs which has it.

This is basically a new device, so I'm pretty willing to merge for
2.11 with minimal review once rebased with the rest of the series.

Couple of comments below

[snip]
> +static void ppc440_pcix_reset(DeviceState *dev)
> +{
> +    struct PPC440PCIXState *s = PPC440_PCIX_HOST_BRIDGE(dev);
> +    int i;
> +
> +    memset(s->pom, 0, sizeof(s->pom));
> +    memset(s->pim, 0, sizeof(s->pim));

Is it safe to just memset() the memory region objects within the
pim/pom arrays without cleaning them up?  I'm guessing not..

> +    for (i = 0; i < PPC440_PCIX_NR_PIMS; i++) {
> +        s->pim[i].sa = 0xffffffff00000000ULL;
> +    }
> +    s->sts = 0;
> +}
> +
> +/* All pins from each slot are tied to a single board IRQ.
> + * This may need further refactoring for other boards. */
> +static int ppc440_pcix_map_irq(PCIDevice *pci_dev, int irq_num)
> +{
> +    int slot = pci_dev->devfn >> 3;
> +
> +    DPRINTF("%s: devfn %x irq %d -> %d\n", __func__,
> +            pci_dev->devfn, irq_num, slot);
> +
> +    return slot - 1;
> +}
> +
> +static void ppc440_pcix_set_irq(void *opaque, int irq_num, int level)
> +{
> +    qemu_irq *pci_irqs = opaque;
> +
> +    DPRINTF("%s: PCI irq %d\n", __func__, irq_num);
> +    if (irq_num < 0) {
> +        fprintf(stderr, "%s: PCI irq %d\n", __func__, irq_num);

Use report_error() please.

> +        return;
> +    }
> +    qemu_set_irq(pci_irqs[irq_num], level);
> +}
> +
> +static AddressSpace *ppc440_pcix_set_iommu(PCIBus *b, void *opaque, int devfn)
> +{
> +    PPC440PCIXState *s = opaque;
> +
> +    return &s->bm_as;
> +}
> +
> +static int ppc440_pcix_initfn(SysBusDevice *dev)
> +{
> +    PPC440PCIXState *s;
> +    PCIHostState *h;
> +    int i;
> +
> +    h = PCI_HOST_BRIDGE(dev);
> +    s = PPC440_PCIX_HOST_BRIDGE(dev);
> +
> +    for (i = 0; i < ARRAY_SIZE(s->irq); i++) {
> +        sysbus_init_irq(dev, &s->irq[i]);
> +    }
> +
> +    memory_region_init(&s->busmem, OBJECT(dev), "pci bus memory", UINT64_MAX);
> +    h->bus = pci_register_bus(DEVICE(dev), NULL, ppc440_pcix_set_irq,
> +                         ppc440_pcix_map_irq, s->irq, &s->busmem,
> +                         get_system_io(), PCI_DEVFN(0, 0), 4, TYPE_PCI_BUS);
> +
> +    s->dev = pci_create_simple(h->bus, PCI_DEVFN(0, 0), "ppc4xx-host-bridge");
> +
> +    memory_region_init(&s->bm, OBJECT(s), "bm-ppc440-pcix", UINT64_MAX);
> +    memory_region_add_subregion(&s->bm, 0x0, &s->busmem);
> +    address_space_init(&s->bm_as, &s->bm, "pci-bm");
> +    pci_setup_iommu(h->bus, ppc440_pcix_set_iommu, s);
> +
> +    memory_region_init(&s->container, OBJECT(s), "pci-container", PCI_ALL_SIZE);
> +    memory_region_init_io(&h->conf_mem, OBJECT(s), &pci_host_conf_le_ops, h,
> +                          "pci-conf-idx", 4);
> +    memory_region_init_io(&h->data_mem, OBJECT(s), &pci_host_data_le_ops, h,
> +                          "pci-conf-data", 4);
> +    memory_region_init_io(&s->iomem, OBJECT(s), &pci_reg_ops, s,
> +                          "pci.reg", PPC440_REG_SIZE);
> +    memory_region_add_subregion(&s->container, PCIC0_CFGADDR, &h->conf_mem);
> +    memory_region_add_subregion(&s->container, PCIC0_CFGDATA, &h->data_mem);
> +    memory_region_add_subregion(&s->container, PPC440_REG_BASE, &s->iomem);
> +    sysbus_init_mmio(dev, &s->container);
> +
> +    return 0;
> +}
> +
> +static void ppc440_pcix_class_init(ObjectClass *klass, void *data)
> +{
> +    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +
> +    k->init = ppc440_pcix_initfn;
> +    dc->reset = ppc440_pcix_reset;
> +}
> +
> +static const TypeInfo ppc440_pcix_info = {
> +    .name          = TYPE_PPC440_PCIX_HOST_BRIDGE,
> +    .parent        = TYPE_PCI_HOST_BRIDGE,
> +    .instance_size = sizeof(PPC440PCIXState),
> +    .class_init    = ppc440_pcix_class_init,
> +};
> +
> +static void ppc440_pcix_register_types(void)
> +{
> +    type_register_static(&ppc440_pcix_info);
> +}
> +
> +type_init(ppc440_pcix_register_types)
luigi burdo Aug. 18, 2017, 9:30 a.m. UTC | #2
hi Balaton,
i can help with amigaos4 for sam .
i can test it on P5040 book3e and on G5 quad book3s machine.
note sam for boot amigaos need a special modified version of uboot that is available on acube website it is a firmware update. without modified uboot will be not posdible auto boot amigaos.
idont know if is possible use standard uboot incuded in qemu for boot amigaos from uboot command line. i can ask help to Enrico Vidale as my FB friend but i dont know if he will be available for this kind of helping.

bye
luigi

Inviato da iPad

> Il giorno 14 ago 2017, alle ore 01:30, BALATON Zoltan <balaton@eik.bme.hu> ha scritto:
> 
> Signed-off-by: BALATON Zoltan <balaton@eik.bme.hu>
> ---
> hw/ppc/Makefile.objs |   2 +-
> hw/ppc/ppc440_pcix.c | 506 +++++++++++++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 507 insertions(+), 1 deletion(-)
> create mode 100644 hw/ppc/ppc440_pcix.c
> 
> diff --git a/hw/ppc/Makefile.objs b/hw/ppc/Makefile.objs
> index 2077216..9c5d58a 100644
> --- a/hw/ppc/Makefile.objs
> +++ b/hw/ppc/Makefile.objs
> @@ -13,7 +13,7 @@ endif
> obj-$(CONFIG_PSERIES) += spapr_rtas_ddw.o
> # PowerPC 4xx boards
> obj-y += ppc405_boards.o ppc4xx_devs.o ppc405_uc.o ppc440_bamboo.o
> -obj-y += ppc4xx_pci.o ppc4xx_i2c.o
> +obj-y += ppc4xx_pci.o ppc440_pcix.o ppc4xx_i2c.o
> # PReP
> obj-$(CONFIG_PREP) += prep.o
> obj-$(CONFIG_PREP) += prep_systemio.o
> diff --git a/hw/ppc/ppc440_pcix.c b/hw/ppc/ppc440_pcix.c
> new file mode 100644
> index 0000000..3abd0d0
> --- /dev/null
> +++ b/hw/ppc/ppc440_pcix.c
> @@ -0,0 +1,506 @@
> +/*
> + * Emulation of the ibm,plb-pcix PCI controller
> + * This is found in some 440 SoCs e.g. the 460EX.
> + *
> + * Copyright (c) 2016 BALATON Zoltan
> + *
> + * Derived from ppc4xx_pci.c and pci-host/ppce500.c
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License, version 2, as
> + * published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#include "qemu/osdep.h"
> +#include "hw/hw.h"
> +#include "hw/ppc/ppc.h"
> +#include "hw/ppc/ppc4xx.h"
> +#include "hw/pci/pci.h"
> +#include "hw/pci/pci_host.h"
> +#include "exec/address-spaces.h"
> +
> +/*#define DEBUG_PCI*/
> +
> +#ifdef DEBUG_PCI
> +#define DPRINTF(fmt, ...) fprintf(stderr, fmt, ## __VA_ARGS__);
> +#else
> +#define DPRINTF(fmt, ...)
> +#endif /* DEBUG */
> +
> +struct PLBOutMap {
> +    uint64_t la;
> +    uint64_t pcia;
> +    uint32_t sa;
> +    MemoryRegion mr;
> +};
> +
> +struct PLBInMap {
> +    uint64_t sa;
> +    uint64_t la;
> +    MemoryRegion mr;
> +};
> +
> +#define TYPE_PPC440_PCIX_HOST_BRIDGE "ppc440-pcix-host"
> +#define PPC440_PCIX_HOST_BRIDGE(obj) \
> +    OBJECT_CHECK(PPC440PCIXState, (obj), TYPE_PPC440_PCIX_HOST_BRIDGE)
> +
> +#define PPC440_PCIX_NR_POMS 3
> +#define PPC440_PCIX_NR_PIMS 3
> +
> +typedef struct PPC440PCIXState {
> +    PCIHostState parent_obj;
> +
> +    PCIDevice *dev;
> +    struct PLBOutMap pom[PPC440_PCIX_NR_POMS];
> +    struct PLBInMap pim[PPC440_PCIX_NR_PIMS];
> +    uint32_t sts;
> +    qemu_irq irq[PCI_NUM_PINS];
> +    AddressSpace bm_as;
> +    MemoryRegion bm;
> +
> +    MemoryRegion container;
> +    MemoryRegion iomem;
> +    MemoryRegion busmem;
> +} PPC440PCIXState;
> +
> +#define PPC440_REG_BASE     0x80000
> +#define PPC440_REG_SIZE     0xff
> +
> +#define PCIC0_CFGADDR       0x0
> +#define PCIC0_CFGDATA       0x4
> +
> +#define PCIX0_POM0LAL       0x68
> +#define PCIX0_POM0LAH       0x6c
> +#define PCIX0_POM0SA        0x70
> +#define PCIX0_POM0PCIAL     0x74
> +#define PCIX0_POM0PCIAH     0x78
> +#define PCIX0_POM1LAL       0x7c
> +#define PCIX0_POM1LAH       0x80
> +#define PCIX0_POM1SA        0x84
> +#define PCIX0_POM1PCIAL     0x88
> +#define PCIX0_POM1PCIAH     0x8c
> +#define PCIX0_POM2SA        0x90
> +
> +#define PCIX0_PIM0SAL       0x98
> +#define PCIX0_PIM0LAL       0x9c
> +#define PCIX0_PIM0LAH       0xa0
> +#define PCIX0_PIM1SA        0xa4
> +#define PCIX0_PIM1LAL       0xa8
> +#define PCIX0_PIM1LAH       0xac
> +#define PCIX0_PIM2SAL       0xb0
> +#define PCIX0_PIM2LAL       0xb4
> +#define PCIX0_PIM2LAH       0xb8
> +#define PCIX0_PIM0SAH       0xf8
> +#define PCIX0_PIM2SAH       0xfc
> +
> +#define PCIX0_STS           0xe0
> +
> +#define PCI_ALL_SIZE        (PPC440_REG_BASE + PPC440_REG_SIZE)
> +
> +/* DMA mapping */
> +static void ppc440_pcix_update_pim(PPC440PCIXState *s, int idx)
> +{
> +    MemoryRegion *mem = &s->pim[idx].mr;
> +    char *name;
> +    uint64_t size;
> +
> +    if (memory_region_is_mapped(mem)) {
> +        /* Before we modify anything, unmap and destroy the region */
> +        memory_region_del_subregion(&s->bm, mem);
> +        object_unparent(OBJECT(mem));
> +    }
> +
> +    if (!(s->pim[idx].sa & 1)) {
> +        /* Not enabled, nothing to do */
> +        return;
> +    }
> +
> +    name = g_strdup_printf("PCI Inbound Window %d", idx);
> +    size = ~(s->pim[idx].sa & ~7ULL) + 1;
> +    memory_region_init_alias(mem, OBJECT(s), name, get_system_memory(),
> +                             s->pim[idx].la, size);
> +    memory_region_add_subregion_overlap(&s->bm, 0, mem, -1);
> +    g_free(name);
> +
> +    DPRINTF("%s: Added window %d of size=%#"PRIx64" to CPU=%#"PRIx64"\n",
> +            __func__, idx, size, s->pim[idx].la);
> +}
> +
> +/* BAR mapping */
> +static void ppc440_pcix_update_pom(PPC440PCIXState *s, int idx)
> +{
> +    MemoryRegion *mem = &s->pom[idx].mr;
> +    MemoryRegion *address_space_mem = get_system_memory();
> +    char *name;
> +    uint32_t size;
> +
> +    if (memory_region_is_mapped(mem)) {
> +        /* Before we modify anything, unmap and destroy the region */
> +        memory_region_del_subregion(address_space_mem, mem);
> +        object_unparent(OBJECT(mem));
> +    }
> +
> +    if (!(s->pom[idx].sa & 1)) {
> +        /* Not enabled, nothing to do */
> +        return;
> +    }
> +
> +    name = g_strdup_printf("PCI Outbound Window %d", idx);
> +    size = ~(s->pom[idx].sa & 0xfffffffe) + 1;
> +    if (!size) {
> +        size = 0xffffffff;
> +    }
> +    memory_region_init_alias(mem, OBJECT(s), name, &s->busmem,
> +                             s->pom[idx].pcia, size);
> +    memory_region_add_subregion(address_space_mem, s->pom[idx].la, mem);
> +    g_free(name);
> +
> +    DPRINTF("%s: Added window %d of size=%#x from CPU=%#"PRIx64
> +            " to PCI=%#"PRIx64"\n", __func__, idx, size, s->pom[idx].la,
> +            s->pom[idx].pcia);
> +}
> +
> +static void ppc440_pcix_reg_write4(void *opaque, hwaddr addr,
> +                                   uint64_t val, unsigned size)
> +{
> +    struct PPC440PCIXState *s = opaque;
> +
> +    DPRINTF("%s: addr 0x%"PRIx64 " = %"PRIx64 "\n", __func__, addr, val);
> +    switch (addr) {
> +    case PCI_VENDOR_ID ... PCI_MAX_LAT:
> +        stl_le_p(s->dev->config + addr, val);
> +        break;
> +
> +    case PCIX0_POM0LAL:
> +        s->pom[0].la &= 0xffffffff00000000ULL;
> +        s->pom[0].la |= val;
> +        ppc440_pcix_update_pom(s, 0);
> +        break;
> +    case PCIX0_POM0LAH:
> +        s->pom[0].la &= 0xffffffffULL;
> +        s->pom[0].la |= val << 32;
> +        ppc440_pcix_update_pom(s, 0);
> +        break;
> +    case PCIX0_POM0SA:
> +        s->pom[0].sa = val;
> +        ppc440_pcix_update_pom(s, 0);
> +        break;
> +    case PCIX0_POM0PCIAL:
> +        s->pom[0].pcia &= 0xffffffff00000000ULL;
> +        s->pom[0].pcia |= val;
> +        ppc440_pcix_update_pom(s, 0);
> +        break;
> +    case PCIX0_POM0PCIAH:
> +        s->pom[0].pcia &= 0xffffffffULL;
> +        s->pom[0].pcia |= val << 32;
> +        ppc440_pcix_update_pom(s, 0);
> +        break;
> +    case PCIX0_POM1LAL:
> +        s->pom[1].la &= 0xffffffff00000000ULL;
> +        s->pom[1].la |= val;
> +        ppc440_pcix_update_pom(s, 1);
> +        break;
> +    case PCIX0_POM1LAH:
> +        s->pom[1].la &= 0xffffffffULL;
> +        s->pom[1].la |= val << 32;
> +        ppc440_pcix_update_pom(s, 1);
> +        break;
> +    case PCIX0_POM1SA:
> +        s->pom[1].sa = val;
> +        ppc440_pcix_update_pom(s, 1);
> +        break;
> +    case PCIX0_POM1PCIAL:
> +        s->pom[1].pcia &= 0xffffffff00000000ULL;
> +        s->pom[1].pcia |= val;
> +        ppc440_pcix_update_pom(s, 1);
> +        break;
> +    case PCIX0_POM1PCIAH:
> +        s->pom[1].pcia &= 0xffffffffULL;
> +        s->pom[1].pcia |= val << 32;
> +        ppc440_pcix_update_pom(s, 1);
> +        break;
> +    case PCIX0_POM2SA:
> +        s->pom[2].sa = val;
> +        break;
> +
> +    case PCIX0_PIM0SAL:
> +        s->pim[0].sa &= 0xffffffff00000000ULL;
> +        s->pim[0].sa |= val;
> +        ppc440_pcix_update_pim(s, 0);
> +        break;
> +    case PCIX0_PIM0LAL:
> +        s->pim[0].la &= 0xffffffff00000000ULL;
> +        s->pim[0].la |= val;
> +        ppc440_pcix_update_pim(s, 0);
> +        break;
> +    case PCIX0_PIM0LAH:
> +        s->pim[0].la &= 0xffffffffULL;
> +        s->pim[0].la |= val << 32;
> +        ppc440_pcix_update_pim(s, 0);
> +        break;
> +    case PCIX0_PIM1SA:
> +        s->pim[1].sa = val;
> +        ppc440_pcix_update_pim(s, 1);
> +        break;
> +    case PCIX0_PIM1LAL:
> +        s->pim[1].la &= 0xffffffff00000000ULL;
> +        s->pim[1].la |= val;
> +        ppc440_pcix_update_pim(s, 1);
> +        break;
> +    case PCIX0_PIM1LAH:
> +        s->pim[1].la &= 0xffffffffULL;
> +        s->pim[1].la |= val << 32;
> +        ppc440_pcix_update_pim(s, 1);
> +        break;
> +    case PCIX0_PIM2SAL:
> +        s->pim[2].sa &= 0xffffffff00000000ULL;
> +        s->pim[2].sa = val;
> +        ppc440_pcix_update_pim(s, 2);
> +        break;
> +    case PCIX0_PIM2LAL:
> +        s->pim[2].la &= 0xffffffff00000000ULL;
> +        s->pim[2].la |= val;
> +        ppc440_pcix_update_pim(s, 2);
> +        break;
> +    case PCIX0_PIM2LAH:
> +        s->pim[2].la &= 0xffffffffULL;
> +        s->pim[2].la |= val << 32;
> +        ppc440_pcix_update_pim(s, 2);
> +        break;
> +
> +    case PCIX0_STS:
> +        s->sts = val;
> +        break;
> +
> +    case PCIX0_PIM0SAH:
> +        s->pim[0].sa &= 0xffffffffULL;
> +        s->pim[0].sa |= val << 32;
> +        ppc440_pcix_update_pim(s, 0);
> +        break;
> +    case PCIX0_PIM2SAH:
> +        s->pim[2].sa &= 0xffffffffULL;
> +        s->pim[2].sa |= val << 32;
> +        ppc440_pcix_update_pim(s, 2);
> +        break;
> +
> +    default:
> +        printf("%s: unhandled PCI internal register 0x%lx\n", __func__,
> +               (unsigned long)addr);
> +        break;
> +    }
> +}
> +
> +static uint64_t ppc440_pcix_reg_read4(void *opaque, hwaddr addr,
> +                                     unsigned size)
> +{
> +    struct PPC440PCIXState *s = opaque;
> +    uint32_t val;
> +
> +    switch (addr) {
> +    case PCI_VENDOR_ID ... PCI_MAX_LAT:
> +        val = ldl_le_p(s->dev->config + addr);
> +        break;
> +
> +    case PCIX0_POM0LAL:
> +        val = s->pom[0].la;
> +        break;
> +    case PCIX0_POM0LAH:
> +        val = s->pom[0].la >> 32;
> +        break;
> +    case PCIX0_POM0SA:
> +        val = s->pom[0].sa;
> +        break;
> +    case PCIX0_POM0PCIAL:
> +        val = s->pom[0].pcia;
> +        break;
> +    case PCIX0_POM0PCIAH:
> +        val = s->pom[0].pcia >> 32;
> +        break;
> +    case PCIX0_POM1LAL:
> +        val = s->pom[1].la;
> +        break;
> +    case PCIX0_POM1LAH:
> +        val = s->pom[1].la >> 32;
> +        break;
> +    case PCIX0_POM1SA:
> +        val = s->pom[1].sa;
> +        break;
> +    case PCIX0_POM1PCIAL:
> +        val = s->pom[1].pcia;
> +        break;
> +    case PCIX0_POM1PCIAH:
> +        val = s->pom[1].pcia >> 32;
> +        break;
> +    case PCIX0_POM2SA:
> +        val = s->pom[2].sa;
> +        break;
> +
> +    case PCIX0_PIM0SAL:
> +        val = s->pim[0].sa;
> +        break;
> +    case PCIX0_PIM0LAL:
> +        val = s->pim[0].la;
> +        break;
> +    case PCIX0_PIM0LAH:
> +        val = s->pim[0].la >> 32;
> +        break;
> +    case PCIX0_PIM1SA:
> +        val = s->pim[1].sa;
> +        break;
> +    case PCIX0_PIM1LAL:
> +        val = s->pim[1].la;
> +        break;
> +    case PCIX0_PIM1LAH:
> +        val = s->pim[1].la >> 32;
> +        break;
> +    case PCIX0_PIM2SAL:
> +        val = s->pim[2].sa;
> +        break;
> +    case PCIX0_PIM2LAL:
> +        val = s->pim[2].la;
> +        break;
> +    case PCIX0_PIM2LAH:
> +        val = s->pim[2].la >> 32;
> +        break;
> +
> +    case PCIX0_STS:
> +        val = s->sts;
> +        break;
> +
> +    case PCIX0_PIM0SAH:
> +        val = s->pim[0].sa  >> 32;
> +        break;
> +    case PCIX0_PIM2SAH:
> +        val = s->pim[2].sa  >> 32;
> +        break;
> +
> +    default:
> +        printf("%s: invalid PCI internal register 0x%lx\n", __func__,
> +               (unsigned long)addr);
> +        val = 0;
> +    }
> +
> +    DPRINTF("%s: addr 0x%"PRIx64 " = %"PRIx32 "\n", __func__, addr, val);
> +    return val;
> +}
> +
> +static const MemoryRegionOps pci_reg_ops = {
> +    .read = ppc440_pcix_reg_read4,
> +    .write = ppc440_pcix_reg_write4,
> +    .endianness = DEVICE_LITTLE_ENDIAN,
> +};
> +
> +static void ppc440_pcix_reset(DeviceState *dev)
> +{
> +    struct PPC440PCIXState *s = PPC440_PCIX_HOST_BRIDGE(dev);
> +    int i;
> +
> +    memset(s->pom, 0, sizeof(s->pom));
> +    memset(s->pim, 0, sizeof(s->pim));
> +    for (i = 0; i < PPC440_PCIX_NR_PIMS; i++) {
> +        s->pim[i].sa = 0xffffffff00000000ULL;
> +    }
> +    s->sts = 0;
> +}
> +
> +/* All pins from each slot are tied to a single board IRQ.
> + * This may need further refactoring for other boards. */
> +static int ppc440_pcix_map_irq(PCIDevice *pci_dev, int irq_num)
> +{
> +    int slot = pci_dev->devfn >> 3;
> +
> +    DPRINTF("%s: devfn %x irq %d -> %d\n", __func__,
> +            pci_dev->devfn, irq_num, slot);
> +
> +    return slot - 1;
> +}
> +
> +static void ppc440_pcix_set_irq(void *opaque, int irq_num, int level)
> +{
> +    qemu_irq *pci_irqs = opaque;
> +
> +    DPRINTF("%s: PCI irq %d\n", __func__, irq_num);
> +    if (irq_num < 0) {
> +        fprintf(stderr, "%s: PCI irq %d\n", __func__, irq_num);
> +        return;
> +    }
> +    qemu_set_irq(pci_irqs[irq_num], level);
> +}
> +
> +static AddressSpace *ppc440_pcix_set_iommu(PCIBus *b, void *opaque, int devfn)
> +{
> +    PPC440PCIXState *s = opaque;
> +
> +    return &s->bm_as;
> +}
> +
> +static int ppc440_pcix_initfn(SysBusDevice *dev)
> +{
> +    PPC440PCIXState *s;
> +    PCIHostState *h;
> +    int i;
> +
> +    h = PCI_HOST_BRIDGE(dev);
> +    s = PPC440_PCIX_HOST_BRIDGE(dev);
> +
> +    for (i = 0; i < ARRAY_SIZE(s->irq); i++) {
> +        sysbus_init_irq(dev, &s->irq[i]);
> +    }
> +
> +    memory_region_init(&s->busmem, OBJECT(dev), "pci bus memory", UINT64_MAX);
> +    h->bus = pci_register_bus(DEVICE(dev), NULL, ppc440_pcix_set_irq,
> +                         ppc440_pcix_map_irq, s->irq, &s->busmem,
> +                         get_system_io(), PCI_DEVFN(0, 0), 4, TYPE_PCI_BUS);
> +
> +    s->dev = pci_create_simple(h->bus, PCI_DEVFN(0, 0), "ppc4xx-host-bridge");
> +
> +    memory_region_init(&s->bm, OBJECT(s), "bm-ppc440-pcix", UINT64_MAX);
> +    memory_region_add_subregion(&s->bm, 0x0, &s->busmem);
> +    address_space_init(&s->bm_as, &s->bm, "pci-bm");
> +    pci_setup_iommu(h->bus, ppc440_pcix_set_iommu, s);
> +
> +    memory_region_init(&s->container, OBJECT(s), "pci-container", PCI_ALL_SIZE);
> +    memory_region_init_io(&h->conf_mem, OBJECT(s), &pci_host_conf_le_ops, h,
> +                          "pci-conf-idx", 4);
> +    memory_region_init_io(&h->data_mem, OBJECT(s), &pci_host_data_le_ops, h,
> +                          "pci-conf-data", 4);
> +    memory_region_init_io(&s->iomem, OBJECT(s), &pci_reg_ops, s,
> +                          "pci.reg", PPC440_REG_SIZE);
> +    memory_region_add_subregion(&s->container, PCIC0_CFGADDR, &h->conf_mem);
> +    memory_region_add_subregion(&s->container, PCIC0_CFGDATA, &h->data_mem);
> +    memory_region_add_subregion(&s->container, PPC440_REG_BASE, &s->iomem);
> +    sysbus_init_mmio(dev, &s->container);
> +
> +    return 0;
> +}
> +
> +static void ppc440_pcix_class_init(ObjectClass *klass, void *data)
> +{
> +    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +
> +    k->init = ppc440_pcix_initfn;
> +    dc->reset = ppc440_pcix_reset;
> +}
> +
> +static const TypeInfo ppc440_pcix_info = {
> +    .name          = TYPE_PPC440_PCIX_HOST_BRIDGE,
> +    .parent        = TYPE_PCI_HOST_BRIDGE,
> +    .instance_size = sizeof(PPC440PCIXState),
> +    .class_init    = ppc440_pcix_class_init,
> +};
> +
> +static void ppc440_pcix_register_types(void)
> +{
> +    type_register_static(&ppc440_pcix_info);
> +}
> +
> +type_init(ppc440_pcix_register_types)
> -- 
> 2.7.4
> 
>
François Revol Aug. 18, 2017, 11:07 a.m. UTC | #3
Hi,

Le 18/08/2017 à 03:53, David Gibson a écrit :
> On Sun, Aug 13, 2017 at 07:04:38PM +0200, BALATON Zoltan wrote:
> 
> You know I'm going to say it, right: needs a commit message.
> 
> What's a "plb-pcix", and what's an example of a 440 SoCs which has it.

IIRC that's the PCI(express) controller, which is not memory-mapped but
inside the DCR address space, so accessed from the PLB.

See:

http://www.embeddeddeveloper.com/assets/processors/amcc/datasheets/PP460EX_DS2063.pdf

page 9, 13.



François.
François Revol Aug. 18, 2017, 11:20 a.m. UTC | #4
Hi,

Le 18/08/2017 à 11:30, luigi burdo a écrit :
> hi Balaton, i can help with amigaos4 for sam . i can test it on P5040
> book3e and on G5 quad book3s machine. note sam for boot amigaos need
> a special modified version of uboot that is available on acube
> website it is a firmware update. without modified uboot will be not
> posdible auto boot amigaos. idont know if is possible use standard
> uboot incuded in qemu for boot amigaos from uboot command line. i can
> ask help to Enrico Vidale as my FB friend but i dont know if he will
> be available for this kind of helping.
> 

Yes they use their own custom U-Boot, with a lot of changes and code
duplication, and added x86 emulation to run a VGA BIOS, and never
upstreamed the changes... much like any other vendor, sadly.

I once managed to build a binary from their published source code with
an old compiler, although it's not exactly the same of the binary used,
which I think is actually run with the on-chip RAM or something weird alike.

François.
BALATON Zoltan Aug. 18, 2017, 12:15 p.m. UTC | #5
On Fri, 18 Aug 2017, François Revol wrote:
> Le 18/08/2017 à 03:53, David Gibson a écrit :
>> On Sun, Aug 13, 2017 at 07:04:38PM +0200, BALATON Zoltan wrote:
>>
>> You know I'm going to say it, right: needs a commit message.
>>
>> What's a "plb-pcix", and what's an example of a 440 SoCs which has it.
>
> IIRC that's the PCI(express) controller, which is not memory-mapped but
> inside the DCR address space, so accessed from the PLB.

This is the PCI(X) controller only, the PCIe(xpress) controller is 
(partially) implemented in sam460ex.c 
TYPE_PPC460EX_PCIE_HOST/"ppc460ex-pcie-host". The plb-pcix is the 
compatible name the fdt refers to. I only know of the 460EX that has it 
but probably other similar SoCs (like some 440 variants) also have this 
kind of PCI controller (according to #defines in U-Boot source but don't 
remember the details).

Regards,
BALATON Zoltan
David Gibson Aug. 18, 2017, 12:18 p.m. UTC | #6
On Fri, Aug 18, 2017 at 01:07:28PM +0200, François Revol wrote:
> Hi,
> 
> Le 18/08/2017 à 03:53, David Gibson a écrit :
> > On Sun, Aug 13, 2017 at 07:04:38PM +0200, BALATON Zoltan wrote:
> > 
> > You know I'm going to say it, right: needs a commit message.
> > 
> > What's a "plb-pcix", and what's an example of a 440 SoCs which has it.
> 
> IIRC that's the PCI(express) controller, which is not memory-mapped but
> inside the DCR address space, so accessed from the PLB.
> 
> See:
> 
> http://www.embeddeddeveloper.com/assets/processors/amcc/datasheets/PP460EX_DS2063.pdf
> 
> page 9, 13.

Sure, I can guess that.  But my point is that this is the sort of
information (including the link) that ought to be in the commit
message.
BALATON Zoltan Aug. 18, 2017, 12:34 p.m. UTC | #7
On Fri, 18 Aug 2017, luigi burdo wrote:
> hi Balaton,

Ciao Luigi,

> i can help with amigaos4 for sam .
> i can test it on P5040 book3e and on G5 quad book3s machine.

Thanks for the offer but unfortunately that would not help because these 
are different CPUs than the 460EX used in the Sam board. I'd definitely 
need logs from Sam460ex or Sam460cr, other logs are not the same and not 
help to find differences between real hardware and emulation.

> note sam for boot amigaos need a special modified version of uboot that 
> is available on acube website it is a firmware update. without modified 
> uboot will be not posdible auto boot amigaos.

I know about this and if you read the cover message (RFC PATCH 00/12) I 
link to a fixed version of that U-Boot firmware (rebuilt from source with 
the mentioned patches) which works with this emulation. (The original one 
from the updater also starts but cannot boot due to some problems which 
are fixed by these patches in my image. This is described in the cover 
message.) With that fixed firmware image it should be possible to at least 
try booting AmigaOS on the emulation and get some logs. I'd be surprised 
if it also worked at this point but I could not try it as it needs an 
appropriate AmigaOS version that runs on the Sam460. It should produce 
some logs though with -serial stdio which may help finding what is 
missing.

> idont know if is possible use standard uboot incuded in qemu for boot 
> amigaos from uboot command line. i can ask help to Enrico Vidale as my 
> FB friend but i dont know if he will be available for this kind of 
> helping.

I think it would not be possible to use the U-Boot in QEMU now as that is 
for e500 CPU but this is not needed either. See above, we are aiming to 
emulate enough of the board that it can run the original firmware which 
should be able to boot these Amiga like OSes and normal Linux images used 
on the Sam460ex. This already works but things fail after or during boot 
currently. This is what needs to be debugged. So we'd need someone who has 
a Sam460 (ex or cr) board and can test on that to get logs from OSes on 
real hardware for comparison.

Regards,
BALATON Zoltan
BALATON Zoltan Aug. 18, 2017, 1:03 p.m. UTC | #8
On Fri, 18 Aug 2017, François Revol wrote:
> I once managed to build a binary from their published source code with
> an old compiler,

It builds here with the patch I've posted next to the bin image.

> although it's not exactly the same of the binary used,
> which I think is actually run with the on-chip RAM or something weird alike.

Maybe it's the same but we may have a problem with MMU emulation so it 
behaves differently (or they've compiled with different compiler/version 
which generates different code, I haven't checked that deep).

But AROS has a problem with MMU so it may be a bug. I'll send a separate 
message about that.

Regards,
BALATON Zoltan
luigi burdo Aug. 18, 2017, 7:43 p.m. UTC | #9
hi Balaton,
> I know about this and if you read the cover message (RFC PATCH 00/12) I link to a fixed version of that U-Boot firmware (rebuilt from source with the mentioned patches) which works with this emulation. (The original one from the updater also starts but cannot boot due to some problems which are fixed by these patches in my image. This is described in the cover message.) With that fixed firmware image it should be possible to at least try booting AmigaOS on the emulation and get some logs. I'd be surprised if it also worked at this point but I could not try it as it needs an appropriate AmigaOS version that runs on the Sam460. It should produce some logs though with -serial stdio which may help finding what is missing.

i have sam460 amigaos cdrom iso as my backup and can test it inside qemu. sharing the logs if you needed.

> I think it would not be possible to use the U-Boot in QEMU now as that is for e500 CPU but this is not needed either. See above, we are aiming to emulate enough of the board that it can run the original firmware which should be able to boot these Amiga like OSes and normal Linux images used on the Sam460ex. This already works but things fail after or during boot currently. This is what needs to be debugged. So we'd need someone who has a Sam460 (ex or cr) board and can test on that to get logs from OSes on real hardware for comparison.

 about real hardware no problem i will ask inside amigan community if someone have the opportunity to share the serial debug of sam 460 runinng amigaos.

can i ask you something ?
why you dont try to integrate in qemu the pegasos 2 or the efika machine. i have the feeling that probably it can be more simple because more old machine and components.

bye 
luigi
François Revol Aug. 18, 2017, 8:52 p.m. UTC | #10
Le 18/08/2017 à 21:43, luigi burdo a écrit :
> can i ask you something ? why you dont try to integrate in qemu the
> pegasos 2 or the efika machine. i have the feeling that probably it
> can be more simple because more old machine and components.
> 

Except they are antique, and the Pegasos at least has an horribly buggy
OpenFirmware implementation.

I'd rather work on BeBox support ;-)

François.
BALATON Zoltan Aug. 19, 2017, 10:03 a.m. UTC | #11
On Fri, 18 Aug 2017, luigi burdo wrote:
> i have sam460 amigaos cdrom iso as my backup and can test it inside 
> qemu. sharing the logs if you needed.

It may help if you can do that. You need to compile QEMU from git with 
this patch series applied and copy the u-boot-sam460-20100605-fixed.bin 
linked from the cover message as u-boot-sam460-20100605.bin to the current 
dir. Then try booting the CD and see if it shows something. It should 
print some messages to console where it's started with -serial stdio when 
you start AmigaOS with debug (you may need to set os4_commandline in 
U-Boot for this but I don't know AmigaOS).

> about real hardware no problem i will ask inside amigan community if 
> someone have the opportunity to share the serial debug of sam 460 
> runinng amigaos.

Do you know about a good place for such requests? Is there some forum or 
mailing list I should be aware of?

> can i ask you something ?
> why you dont try to integrate in qemu the pegasos 2 or the efika 
> machine. i have the feeling that probably it can be more simple because 
> more old machine and components.

The age of a machine and components has nothing to do with how easy it is 
to emulate. It depends on what is already emulated in QEMU. If none of 
those old components are implemented by QEMU already then it's a lot of 
work to write emulation of them. (Such as the Marvell system controller 
chip in Pegasos or the SoC the Efika uses. Besides, does AmigaOS run on 
Efika?) If there are some previous work we can build on then it's easier. 
QEMU already had ppc440 emulation including a similar board and Francois 
has started sam460ex emulation so it was easier to finish this than making 
a new one. (Also firmware and graphics emulation may be more difficult for 
those other boards.)

At the end it does not matter which machine we emulate if the goal is to 
run these OSes. If we have any of the supported boards they will run so we 
don't need more of these just one and it was the sam460 which was nearest 
to start working. (The MorphOS team or one of the developers may have some 
Pegasos 2 emulation but they don't intend to publish that and I don't know 
about previous work on Efika so the Sam460ex seems like a good choice with 
support for a lot of OSes, including porting Haiku which was why Francois 
has started emulating it so this may help that activity as well 
increasing the available OSes.)

Regards,
BALATON Zoltan
diff mbox

Patch

diff --git a/hw/ppc/Makefile.objs b/hw/ppc/Makefile.objs
index 2077216..9c5d58a 100644
--- a/hw/ppc/Makefile.objs
+++ b/hw/ppc/Makefile.objs
@@ -13,7 +13,7 @@  endif
 obj-$(CONFIG_PSERIES) += spapr_rtas_ddw.o
 # PowerPC 4xx boards
 obj-y += ppc405_boards.o ppc4xx_devs.o ppc405_uc.o ppc440_bamboo.o
-obj-y += ppc4xx_pci.o ppc4xx_i2c.o
+obj-y += ppc4xx_pci.o ppc440_pcix.o ppc4xx_i2c.o
 # PReP
 obj-$(CONFIG_PREP) += prep.o
 obj-$(CONFIG_PREP) += prep_systemio.o
diff --git a/hw/ppc/ppc440_pcix.c b/hw/ppc/ppc440_pcix.c
new file mode 100644
index 0000000..3abd0d0
--- /dev/null
+++ b/hw/ppc/ppc440_pcix.c
@@ -0,0 +1,506 @@ 
+/*
+ * Emulation of the ibm,plb-pcix PCI controller
+ * This is found in some 440 SoCs e.g. the 460EX.
+ *
+ * Copyright (c) 2016 BALATON Zoltan
+ *
+ * Derived from ppc4xx_pci.c and pci-host/ppce500.c
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 2, as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/hw.h"
+#include "hw/ppc/ppc.h"
+#include "hw/ppc/ppc4xx.h"
+#include "hw/pci/pci.h"
+#include "hw/pci/pci_host.h"
+#include "exec/address-spaces.h"
+
+/*#define DEBUG_PCI*/
+
+#ifdef DEBUG_PCI
+#define DPRINTF(fmt, ...) fprintf(stderr, fmt, ## __VA_ARGS__);
+#else
+#define DPRINTF(fmt, ...)
+#endif /* DEBUG */
+
+struct PLBOutMap {
+    uint64_t la;
+    uint64_t pcia;
+    uint32_t sa;
+    MemoryRegion mr;
+};
+
+struct PLBInMap {
+    uint64_t sa;
+    uint64_t la;
+    MemoryRegion mr;
+};
+
+#define TYPE_PPC440_PCIX_HOST_BRIDGE "ppc440-pcix-host"
+#define PPC440_PCIX_HOST_BRIDGE(obj) \
+    OBJECT_CHECK(PPC440PCIXState, (obj), TYPE_PPC440_PCIX_HOST_BRIDGE)
+
+#define PPC440_PCIX_NR_POMS 3
+#define PPC440_PCIX_NR_PIMS 3
+
+typedef struct PPC440PCIXState {
+    PCIHostState parent_obj;
+
+    PCIDevice *dev;
+    struct PLBOutMap pom[PPC440_PCIX_NR_POMS];
+    struct PLBInMap pim[PPC440_PCIX_NR_PIMS];
+    uint32_t sts;
+    qemu_irq irq[PCI_NUM_PINS];
+    AddressSpace bm_as;
+    MemoryRegion bm;
+
+    MemoryRegion container;
+    MemoryRegion iomem;
+    MemoryRegion busmem;
+} PPC440PCIXState;
+
+#define PPC440_REG_BASE     0x80000
+#define PPC440_REG_SIZE     0xff
+
+#define PCIC0_CFGADDR       0x0
+#define PCIC0_CFGDATA       0x4
+
+#define PCIX0_POM0LAL       0x68
+#define PCIX0_POM0LAH       0x6c
+#define PCIX0_POM0SA        0x70
+#define PCIX0_POM0PCIAL     0x74
+#define PCIX0_POM0PCIAH     0x78
+#define PCIX0_POM1LAL       0x7c
+#define PCIX0_POM1LAH       0x80
+#define PCIX0_POM1SA        0x84
+#define PCIX0_POM1PCIAL     0x88
+#define PCIX0_POM1PCIAH     0x8c
+#define PCIX0_POM2SA        0x90
+
+#define PCIX0_PIM0SAL       0x98
+#define PCIX0_PIM0LAL       0x9c
+#define PCIX0_PIM0LAH       0xa0
+#define PCIX0_PIM1SA        0xa4
+#define PCIX0_PIM1LAL       0xa8
+#define PCIX0_PIM1LAH       0xac
+#define PCIX0_PIM2SAL       0xb0
+#define PCIX0_PIM2LAL       0xb4
+#define PCIX0_PIM2LAH       0xb8
+#define PCIX0_PIM0SAH       0xf8
+#define PCIX0_PIM2SAH       0xfc
+
+#define PCIX0_STS           0xe0
+
+#define PCI_ALL_SIZE        (PPC440_REG_BASE + PPC440_REG_SIZE)
+
+/* DMA mapping */
+static void ppc440_pcix_update_pim(PPC440PCIXState *s, int idx)
+{
+    MemoryRegion *mem = &s->pim[idx].mr;
+    char *name;
+    uint64_t size;
+
+    if (memory_region_is_mapped(mem)) {
+        /* Before we modify anything, unmap and destroy the region */
+        memory_region_del_subregion(&s->bm, mem);
+        object_unparent(OBJECT(mem));
+    }
+
+    if (!(s->pim[idx].sa & 1)) {
+        /* Not enabled, nothing to do */
+        return;
+    }
+
+    name = g_strdup_printf("PCI Inbound Window %d", idx);
+    size = ~(s->pim[idx].sa & ~7ULL) + 1;
+    memory_region_init_alias(mem, OBJECT(s), name, get_system_memory(),
+                             s->pim[idx].la, size);
+    memory_region_add_subregion_overlap(&s->bm, 0, mem, -1);
+    g_free(name);
+
+    DPRINTF("%s: Added window %d of size=%#"PRIx64" to CPU=%#"PRIx64"\n",
+            __func__, idx, size, s->pim[idx].la);
+}
+
+/* BAR mapping */
+static void ppc440_pcix_update_pom(PPC440PCIXState *s, int idx)
+{
+    MemoryRegion *mem = &s->pom[idx].mr;
+    MemoryRegion *address_space_mem = get_system_memory();
+    char *name;
+    uint32_t size;
+
+    if (memory_region_is_mapped(mem)) {
+        /* Before we modify anything, unmap and destroy the region */
+        memory_region_del_subregion(address_space_mem, mem);
+        object_unparent(OBJECT(mem));
+    }
+
+    if (!(s->pom[idx].sa & 1)) {
+        /* Not enabled, nothing to do */
+        return;
+    }
+
+    name = g_strdup_printf("PCI Outbound Window %d", idx);
+    size = ~(s->pom[idx].sa & 0xfffffffe) + 1;
+    if (!size) {
+        size = 0xffffffff;
+    }
+    memory_region_init_alias(mem, OBJECT(s), name, &s->busmem,
+                             s->pom[idx].pcia, size);
+    memory_region_add_subregion(address_space_mem, s->pom[idx].la, mem);
+    g_free(name);
+
+    DPRINTF("%s: Added window %d of size=%#x from CPU=%#"PRIx64
+            " to PCI=%#"PRIx64"\n", __func__, idx, size, s->pom[idx].la,
+            s->pom[idx].pcia);
+}
+
+static void ppc440_pcix_reg_write4(void *opaque, hwaddr addr,
+                                   uint64_t val, unsigned size)
+{
+    struct PPC440PCIXState *s = opaque;
+
+    DPRINTF("%s: addr 0x%"PRIx64 " = %"PRIx64 "\n", __func__, addr, val);
+    switch (addr) {
+    case PCI_VENDOR_ID ... PCI_MAX_LAT:
+        stl_le_p(s->dev->config + addr, val);
+        break;
+
+    case PCIX0_POM0LAL:
+        s->pom[0].la &= 0xffffffff00000000ULL;
+        s->pom[0].la |= val;
+        ppc440_pcix_update_pom(s, 0);
+        break;
+    case PCIX0_POM0LAH:
+        s->pom[0].la &= 0xffffffffULL;
+        s->pom[0].la |= val << 32;
+        ppc440_pcix_update_pom(s, 0);
+        break;
+    case PCIX0_POM0SA:
+        s->pom[0].sa = val;
+        ppc440_pcix_update_pom(s, 0);
+        break;
+    case PCIX0_POM0PCIAL:
+        s->pom[0].pcia &= 0xffffffff00000000ULL;
+        s->pom[0].pcia |= val;
+        ppc440_pcix_update_pom(s, 0);
+        break;
+    case PCIX0_POM0PCIAH:
+        s->pom[0].pcia &= 0xffffffffULL;
+        s->pom[0].pcia |= val << 32;
+        ppc440_pcix_update_pom(s, 0);
+        break;
+    case PCIX0_POM1LAL:
+        s->pom[1].la &= 0xffffffff00000000ULL;
+        s->pom[1].la |= val;
+        ppc440_pcix_update_pom(s, 1);
+        break;
+    case PCIX0_POM1LAH:
+        s->pom[1].la &= 0xffffffffULL;
+        s->pom[1].la |= val << 32;
+        ppc440_pcix_update_pom(s, 1);
+        break;
+    case PCIX0_POM1SA:
+        s->pom[1].sa = val;
+        ppc440_pcix_update_pom(s, 1);
+        break;
+    case PCIX0_POM1PCIAL:
+        s->pom[1].pcia &= 0xffffffff00000000ULL;
+        s->pom[1].pcia |= val;
+        ppc440_pcix_update_pom(s, 1);
+        break;
+    case PCIX0_POM1PCIAH:
+        s->pom[1].pcia &= 0xffffffffULL;
+        s->pom[1].pcia |= val << 32;
+        ppc440_pcix_update_pom(s, 1);
+        break;
+    case PCIX0_POM2SA:
+        s->pom[2].sa = val;
+        break;
+
+    case PCIX0_PIM0SAL:
+        s->pim[0].sa &= 0xffffffff00000000ULL;
+        s->pim[0].sa |= val;
+        ppc440_pcix_update_pim(s, 0);
+        break;
+    case PCIX0_PIM0LAL:
+        s->pim[0].la &= 0xffffffff00000000ULL;
+        s->pim[0].la |= val;
+        ppc440_pcix_update_pim(s, 0);
+        break;
+    case PCIX0_PIM0LAH:
+        s->pim[0].la &= 0xffffffffULL;
+        s->pim[0].la |= val << 32;
+        ppc440_pcix_update_pim(s, 0);
+        break;
+    case PCIX0_PIM1SA:
+        s->pim[1].sa = val;
+        ppc440_pcix_update_pim(s, 1);
+        break;
+    case PCIX0_PIM1LAL:
+        s->pim[1].la &= 0xffffffff00000000ULL;
+        s->pim[1].la |= val;
+        ppc440_pcix_update_pim(s, 1);
+        break;
+    case PCIX0_PIM1LAH:
+        s->pim[1].la &= 0xffffffffULL;
+        s->pim[1].la |= val << 32;
+        ppc440_pcix_update_pim(s, 1);
+        break;
+    case PCIX0_PIM2SAL:
+        s->pim[2].sa &= 0xffffffff00000000ULL;
+        s->pim[2].sa = val;
+        ppc440_pcix_update_pim(s, 2);
+        break;
+    case PCIX0_PIM2LAL:
+        s->pim[2].la &= 0xffffffff00000000ULL;
+        s->pim[2].la |= val;
+        ppc440_pcix_update_pim(s, 2);
+        break;
+    case PCIX0_PIM2LAH:
+        s->pim[2].la &= 0xffffffffULL;
+        s->pim[2].la |= val << 32;
+        ppc440_pcix_update_pim(s, 2);
+        break;
+
+    case PCIX0_STS:
+        s->sts = val;
+        break;
+
+    case PCIX0_PIM0SAH:
+        s->pim[0].sa &= 0xffffffffULL;
+        s->pim[0].sa |= val << 32;
+        ppc440_pcix_update_pim(s, 0);
+        break;
+    case PCIX0_PIM2SAH:
+        s->pim[2].sa &= 0xffffffffULL;
+        s->pim[2].sa |= val << 32;
+        ppc440_pcix_update_pim(s, 2);
+        break;
+
+    default:
+        printf("%s: unhandled PCI internal register 0x%lx\n", __func__,
+               (unsigned long)addr);
+        break;
+    }
+}
+
+static uint64_t ppc440_pcix_reg_read4(void *opaque, hwaddr addr,
+                                     unsigned size)
+{
+    struct PPC440PCIXState *s = opaque;
+    uint32_t val;
+
+    switch (addr) {
+    case PCI_VENDOR_ID ... PCI_MAX_LAT:
+        val = ldl_le_p(s->dev->config + addr);
+        break;
+
+    case PCIX0_POM0LAL:
+        val = s->pom[0].la;
+        break;
+    case PCIX0_POM0LAH:
+        val = s->pom[0].la >> 32;
+        break;
+    case PCIX0_POM0SA:
+        val = s->pom[0].sa;
+        break;
+    case PCIX0_POM0PCIAL:
+        val = s->pom[0].pcia;
+        break;
+    case PCIX0_POM0PCIAH:
+        val = s->pom[0].pcia >> 32;
+        break;
+    case PCIX0_POM1LAL:
+        val = s->pom[1].la;
+        break;
+    case PCIX0_POM1LAH:
+        val = s->pom[1].la >> 32;
+        break;
+    case PCIX0_POM1SA:
+        val = s->pom[1].sa;
+        break;
+    case PCIX0_POM1PCIAL:
+        val = s->pom[1].pcia;
+        break;
+    case PCIX0_POM1PCIAH:
+        val = s->pom[1].pcia >> 32;
+        break;
+    case PCIX0_POM2SA:
+        val = s->pom[2].sa;
+        break;
+
+    case PCIX0_PIM0SAL:
+        val = s->pim[0].sa;
+        break;
+    case PCIX0_PIM0LAL:
+        val = s->pim[0].la;
+        break;
+    case PCIX0_PIM0LAH:
+        val = s->pim[0].la >> 32;
+        break;
+    case PCIX0_PIM1SA:
+        val = s->pim[1].sa;
+        break;
+    case PCIX0_PIM1LAL:
+        val = s->pim[1].la;
+        break;
+    case PCIX0_PIM1LAH:
+        val = s->pim[1].la >> 32;
+        break;
+    case PCIX0_PIM2SAL:
+        val = s->pim[2].sa;
+        break;
+    case PCIX0_PIM2LAL:
+        val = s->pim[2].la;
+        break;
+    case PCIX0_PIM2LAH:
+        val = s->pim[2].la >> 32;
+        break;
+
+    case PCIX0_STS:
+        val = s->sts;
+        break;
+
+    case PCIX0_PIM0SAH:
+        val = s->pim[0].sa  >> 32;
+        break;
+    case PCIX0_PIM2SAH:
+        val = s->pim[2].sa  >> 32;
+        break;
+
+    default:
+        printf("%s: invalid PCI internal register 0x%lx\n", __func__,
+               (unsigned long)addr);
+        val = 0;
+    }
+
+    DPRINTF("%s: addr 0x%"PRIx64 " = %"PRIx32 "\n", __func__, addr, val);
+    return val;
+}
+
+static const MemoryRegionOps pci_reg_ops = {
+    .read = ppc440_pcix_reg_read4,
+    .write = ppc440_pcix_reg_write4,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void ppc440_pcix_reset(DeviceState *dev)
+{
+    struct PPC440PCIXState *s = PPC440_PCIX_HOST_BRIDGE(dev);
+    int i;
+
+    memset(s->pom, 0, sizeof(s->pom));
+    memset(s->pim, 0, sizeof(s->pim));
+    for (i = 0; i < PPC440_PCIX_NR_PIMS; i++) {
+        s->pim[i].sa = 0xffffffff00000000ULL;
+    }
+    s->sts = 0;
+}
+
+/* All pins from each slot are tied to a single board IRQ.
+ * This may need further refactoring for other boards. */
+static int ppc440_pcix_map_irq(PCIDevice *pci_dev, int irq_num)
+{
+    int slot = pci_dev->devfn >> 3;
+
+    DPRINTF("%s: devfn %x irq %d -> %d\n", __func__,
+            pci_dev->devfn, irq_num, slot);
+
+    return slot - 1;
+}
+
+static void ppc440_pcix_set_irq(void *opaque, int irq_num, int level)
+{
+    qemu_irq *pci_irqs = opaque;
+
+    DPRINTF("%s: PCI irq %d\n", __func__, irq_num);
+    if (irq_num < 0) {
+        fprintf(stderr, "%s: PCI irq %d\n", __func__, irq_num);
+        return;
+    }
+    qemu_set_irq(pci_irqs[irq_num], level);
+}
+
+static AddressSpace *ppc440_pcix_set_iommu(PCIBus *b, void *opaque, int devfn)
+{
+    PPC440PCIXState *s = opaque;
+
+    return &s->bm_as;
+}
+
+static int ppc440_pcix_initfn(SysBusDevice *dev)
+{
+    PPC440PCIXState *s;
+    PCIHostState *h;
+    int i;
+
+    h = PCI_HOST_BRIDGE(dev);
+    s = PPC440_PCIX_HOST_BRIDGE(dev);
+
+    for (i = 0; i < ARRAY_SIZE(s->irq); i++) {
+        sysbus_init_irq(dev, &s->irq[i]);
+    }
+
+    memory_region_init(&s->busmem, OBJECT(dev), "pci bus memory", UINT64_MAX);
+    h->bus = pci_register_bus(DEVICE(dev), NULL, ppc440_pcix_set_irq,
+                         ppc440_pcix_map_irq, s->irq, &s->busmem,
+                         get_system_io(), PCI_DEVFN(0, 0), 4, TYPE_PCI_BUS);
+
+    s->dev = pci_create_simple(h->bus, PCI_DEVFN(0, 0), "ppc4xx-host-bridge");
+
+    memory_region_init(&s->bm, OBJECT(s), "bm-ppc440-pcix", UINT64_MAX);
+    memory_region_add_subregion(&s->bm, 0x0, &s->busmem);
+    address_space_init(&s->bm_as, &s->bm, "pci-bm");
+    pci_setup_iommu(h->bus, ppc440_pcix_set_iommu, s);
+
+    memory_region_init(&s->container, OBJECT(s), "pci-container", PCI_ALL_SIZE);
+    memory_region_init_io(&h->conf_mem, OBJECT(s), &pci_host_conf_le_ops, h,
+                          "pci-conf-idx", 4);
+    memory_region_init_io(&h->data_mem, OBJECT(s), &pci_host_data_le_ops, h,
+                          "pci-conf-data", 4);
+    memory_region_init_io(&s->iomem, OBJECT(s), &pci_reg_ops, s,
+                          "pci.reg", PPC440_REG_SIZE);
+    memory_region_add_subregion(&s->container, PCIC0_CFGADDR, &h->conf_mem);
+    memory_region_add_subregion(&s->container, PCIC0_CFGDATA, &h->data_mem);
+    memory_region_add_subregion(&s->container, PPC440_REG_BASE, &s->iomem);
+    sysbus_init_mmio(dev, &s->container);
+
+    return 0;
+}
+
+static void ppc440_pcix_class_init(ObjectClass *klass, void *data)
+{
+    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    k->init = ppc440_pcix_initfn;
+    dc->reset = ppc440_pcix_reset;
+}
+
+static const TypeInfo ppc440_pcix_info = {
+    .name          = TYPE_PPC440_PCIX_HOST_BRIDGE,
+    .parent        = TYPE_PCI_HOST_BRIDGE,
+    .instance_size = sizeof(PPC440PCIXState),
+    .class_init    = ppc440_pcix_class_init,
+};
+
+static void ppc440_pcix_register_types(void)
+{
+    type_register_static(&ppc440_pcix_info);
+}
+
+type_init(ppc440_pcix_register_types)