diff mbox

[27/77] ppc/pnv: Add XSCOM infrastructure

Message ID 1447201710-10229-28-git-send-email-benh@kernel.crashing.org
State New
Headers show

Commit Message

Benjamin Herrenschmidt Nov. 11, 2015, 12:27 a.m. UTC
XSCOM is an interface to a sideband bus provided by the POWER8 chip
pervasive unit, which gives access to a number of facilities in the
chip that are needed by the OPAL firmware and to a lesser extent,
Linux. This is among others how the PCI Host bridges get configured
at boot or how the LPC bus is accessed.

This provides a simple bus and device type for devices sitting on
XSCOM along with some facilities to optionally generate corresponding
device-tree nodes

Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
---
 hw/ppc/Makefile.objs       |   2 +-
 hw/ppc/pnv.c               |  11 ++
 hw/ppc/pnv_xscom.c         | 415 +++++++++++++++++++++++++++++++++++++++++++++
 include/hw/ppc/pnv.h       |   2 +
 include/hw/ppc/pnv_xscom.h |  73 ++++++++
 5 files changed, 502 insertions(+), 1 deletion(-)
 create mode 100644 hw/ppc/pnv_xscom.c
 create mode 100644 include/hw/ppc/pnv_xscom.h

Comments

David Gibson Nov. 24, 2015, 3:20 a.m. UTC | #1
On Wed, Nov 11, 2015 at 11:27:40AM +1100, Benjamin Herrenschmidt wrote:
> XSCOM is an interface to a sideband bus provided by the POWER8 chip
> pervasive unit, which gives access to a number of facilities in the
> chip that are needed by the OPAL firmware and to a lesser extent,
> Linux. This is among others how the PCI Host bridges get configured
> at boot or how the LPC bus is accessed.
> 
> This provides a simple bus and device type for devices sitting on
> XSCOM along with some facilities to optionally generate corresponding
> device-tree nodes
> 
> Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
> ---
>  hw/ppc/Makefile.objs       |   2 +-
>  hw/ppc/pnv.c               |  11 ++
>  hw/ppc/pnv_xscom.c         | 415 +++++++++++++++++++++++++++++++++++++++++++++
>  include/hw/ppc/pnv.h       |   2 +
>  include/hw/ppc/pnv_xscom.h |  73 ++++++++
>  5 files changed, 502 insertions(+), 1 deletion(-)
>  create mode 100644 hw/ppc/pnv_xscom.c
>  create mode 100644 include/hw/ppc/pnv_xscom.h
> 
> diff --git a/hw/ppc/Makefile.objs b/hw/ppc/Makefile.objs
> index cd74c96..2a7dd42 100644
> --- a/hw/ppc/Makefile.objs
> +++ b/hw/ppc/Makefile.objs
> @@ -5,7 +5,7 @@ obj-$(CONFIG_PSERIES) += spapr.o spapr_vio.o spapr_events.o
>  obj-$(CONFIG_PSERIES) += spapr_hcall.o spapr_iommu.o spapr_rtas.o
>  obj-$(CONFIG_PSERIES) += spapr_pci.o spapr_rtc.o spapr_drc.o spapr_rng.o
>  # IBM PowerNV
> -obj-$(CONFIG_POWERNV) += pnv.o
> +obj-$(CONFIG_POWERNV) += pnv.o pnv_xscom.o
>  ifeq ($(CONFIG_PCI)$(CONFIG_PSERIES)$(CONFIG_LINUX), yyy)
>  obj-y += spapr_pci_vfio.o
>  endif
> diff --git a/hw/ppc/pnv.c b/hw/ppc/pnv.c
> index e68c9b1..2eac877 100644
> --- a/hw/ppc/pnv.c
> +++ b/hw/ppc/pnv.c
> @@ -41,6 +41,7 @@
>  #include "hw/ppc/ppc.h"
>  #include "hw/ppc/pnv.h"
>  #include "hw/loader.h"
> +#include "hw/ppc/pnv_xscom.h"
>  
>  #include "exec/address-spaces.h"
>  #include "qemu/config-file.h"
> @@ -310,6 +311,7 @@ static void *powernv_create_fdt(PnvSystem *sys, uint32_t initrd_base, uint32_t i
>      uint32_t end_prop = cpu_to_be32(initrd_base + initrd_size);
>      char *buf;
>      const char plat_compat[] = "qemu,powernv\0ibm,powernv";
> +    unsigned int i;
>  
>      fdt = g_malloc0(FDT_MAX_SIZE);
>      _FDT((fdt_create(fdt, FDT_MAX_SIZE)));
> @@ -367,6 +369,12 @@ static void *powernv_create_fdt(PnvSystem *sys, uint32_t initrd_base, uint32_t i
>      /* Memory */
>      _FDT((powernv_populate_memory(fdt)));
>  
> +    /* For each chip */
> +    for (i = 0; i < sys->num_chips; i++) {
> +        /* Populate XSCOM */
> +        _FDT((xscom_populate_fdt(sys->chips[i].xscom, fdt)));
> +    }
> +
>      /* /hypervisor node */
>      if (kvm_enabled()) {
>          uint8_t hypercall[16];
> @@ -424,6 +432,9 @@ static void pnv_create_chip(PnvSystem *sys, unsigned int chip_no)
>  
>      /* XXX Improve chip numbering to better match HW */
>      chip->chip_id = chip_no;
> +
> +    /* Set up XSCOM bus */
> +    xscom_create(chip);

Hmm.. I'm thinking it probably makes sense to unify the representation
of "chip" and "xscom" since there's a 1:1 correspondance.  To be
QOMishly correct, I think the right way would be a SysBusDevice for
each chip, implementing the xscom MMIOs, then an array of link properties
under the Machine object to find the right scoms by chip id.

>  }
>  
>  static void ppc_powernv_init(MachineState *machine)
> diff --git a/hw/ppc/pnv_xscom.c b/hw/ppc/pnv_xscom.c
> new file mode 100644
> index 0000000..bb35422
> --- /dev/null
> +++ b/hw/ppc/pnv_xscom.c
> @@ -0,0 +1,415 @@
> +
> +/*
> + * QEMU PowerNV XSCOM bus definitions
> + *
> + * Copyright (c) 2010 David Gibson, IBM Corporation <dwg@au1.ibm.com>
> + * Based on the s390 virtio bus code:
> + * Copyright (c) 2009 Alexander Graf <agraf@suse.de>
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2 of the License, or (at your option) any later version.
> + *
> + * This library 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
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +/* TODO: Add some infrastructure for "random stuff" and FIRs that
> + * various units might want to deal with without creating actual
> + * XSCOM devices.
> + *
> + * For example, HB LPC XSCOM in the PIBAM
> + */
> +#include "hw/hw.h"
> +#include "sysemu/sysemu.h"
> +#include "hw/boards.h"
> +#include "monitor/monitor.h"
> +#include "hw/loader.h"
> +#include "elf.h"
> +#include "hw/sysbus.h"
> +#include "sysemu/kvm.h"
> +#include "sysemu/device_tree.h"
> +#include "kvm_ppc.h"
> +
> +#include "hw/ppc/pnv_xscom.h"
> +
> +#include <libfdt.h>
> +
> +#define TYPE_XSCOM "xscom"
> +#define XSCOM(obj) OBJECT_CHECK(XScomState, (obj), TYPE_XSCOM)
> +
> +#define XSCOM_SIZE        0x800000000ull
> +#define XSCOM_BASE(chip)  (0x3fc0000000000ull + ((uint64_t)(chip)) * XSCOM_SIZE)
> +
> +//#define TRACE_SCOMS
> +
> +typedef struct XScomState {
> +    /*< private >*/
> +    SysBusDevice parent_obj;
> +    /*< public >*/
> +
> +    MemoryRegion mem;
> +    int32_t chip_id;
> +    XScomBus *bus;
> +} XScomState;
> +
> +static uint32_t xscom_to_pcb_addr(uint64_t addr)
> +{
> +        addr &= (XSCOM_SIZE - 1);
> +        return ((addr >> 4) & ~0xfull) | ((addr >> 3) & 0xf);

Wow, that's a pretty weird address transform.

> +}
> +
> +static void xscom_complete(uint64_t hmer_bits)
> +{
> +    CPUState *cs = current_cpu;
> +    PowerPCCPU *cpu = POWERPC_CPU(cs);
> +    CPUPPCState *env = &cpu->env;
> +
> +    cpu_synchronize_state(cs);
> +    env->spr[SPR_HMER] |= hmer_bits;
> +
> +    /* XXX Need a CPU helper to set HMER, also handle gneeration
> +     * of HMIs

Not sure what you're referring to here.  Nothing more should be needed
to set the HMER - because you've called cpu_synchronize_state() it
will be marked dirty and flushed back to KVM before re-entry.

> +     */
> +}
> +
> +static XScomDevice *xscom_find_target(XScomState *s, uint32_t pcb_addr, uint32_t *range)
> +{
> +    BusChild *bc;
> +
> +    QTAILQ_FOREACH(bc, &s->bus->bus.children, sibling) {
> +        DeviceState *qd = bc->child;
> +        XScomDevice *xd = XSCOM_DEVICE(qd);
> +        unsigned int i;
> +
> +        for (i = 0; i < MAX_XSCOM_RANGES; i++) {
> +            if (xd->ranges[i].addr <= pcb_addr &&
> +                (xd->ranges[i].addr + xd->ranges[i].size) > pcb_addr) {
> +                *range = i;
> +                return xd;
> +            }
> +        }
> +    }

I'm wondering if it makes sense to construct a custom AddressSpace and
use the existing address space lookup logic from exec.c and memory.c
rather than implementing your own.

> +    return NULL;
> +}
> +
> +static bool xscom_dispatch_read(XScomState *s, uint32_t pcb_addr, uint64_t *out_val)
> +{
> +    uint32_t range, offset;
> +    struct XScomDevice *xd = xscom_find_target(s, pcb_addr, &range);
> +    XScomDeviceClass *xc;
> +
> +    if (!xd) {
> +        return false;
> +    }
> +    xc = XSCOM_DEVICE_GET_CLASS(xd);
> +    if (!xc->read) {
> +        return false;
> +    }
> +    offset = pcb_addr - xd->ranges[range].addr;
> +    return xc->read(xd, range, offset, out_val);
> +}
> +
> +static bool xscom_dispatch_write(XScomState *s, uint32_t pcb_addr, uint64_t val)
> +{
> +    uint32_t range, offset;
> +    struct XScomDevice *xd = xscom_find_target(s, pcb_addr, &range);
> +    XScomDeviceClass *xc;
> +
> +    if (!xd) {
> +        return false;
> +    }
> +    xc = XSCOM_DEVICE_GET_CLASS(xd);
> +    if (!xc->write) {
> +        return false;
> +    }
> +    offset = pcb_addr - xd->ranges[range].addr;
> +    return xc->write(xd, range, offset, val);
> +}
> +
> +static uint64_t xscom_read(void *opaque, hwaddr addr, unsigned width)
> +{
> +    XScomState *s = opaque;
> +    uint32_t pcba = xscom_to_pcb_addr(addr);
> +    uint64_t val;
> +
> +    assert(width == 8);
> +
> +#ifdef TRACE_SCOMS
> +    printf("XSCOM_READ(0x%x:0x%x)\n", s->chip_id, pcba);
> +#endif

You should be using the built in trace infrastructure here - it's
really not that much of a pain.  Put
	trace_xscom_read(s->chip_id, pcba)
here, put a suitable format in trace-events, and ./configure
--enable-trace-backends=stderr

> +
> +    /* Handle some SCOMs here before dispatch */
> +    switch(pcba) {
> +    case 0xf000f:
> +        val = 0x221EF04980000000;
> +        break;
> +    case 0x1010c00:     /* PIBAM FIR */
> +    case 0x1010c03:     /* PIBAM FIR MASK */
> +    case 0x2020007:     /* ADU stuff */
> +    case 0x2020009:     /* ADU stuff */
> +    case 0x202000f:     /* ADU stuff */
> +        val = 0;
> +        break;
> +    case 0x2013f00:     /* PBA stuff */
> +    case 0x2013f01:     /* PBA stuff */
> +    case 0x2013f02:     /* PBA stuff */
> +    case 0x2013f03:     /* PBA stuff */
> +    case 0x2013f04:     /* PBA stuff */
> +    case 0x2013f05:     /* PBA stuff */
> +    case 0x2013f06:     /* PBA stuff */
> +    case 0x2013f07:     /* PBA stuff */
> +        val = 0;
> +        break;
> +    default:
> +        if (!xscom_dispatch_read(s, pcba, &val)) {
> +            xscom_complete(HMER_XSCOM_FAIL | HMER_XSCOM_DONE);
> +            return 0;
> +        }
> +    }
> +
> +    xscom_complete(HMER_XSCOM_DONE);
> +    return val;
> +}
> +
> +static void xscom_write(void *opaque, hwaddr addr, uint64_t val,
> +                        unsigned width)
> +{
> +    XScomState *s = opaque;
> +    uint32_t pcba = xscom_to_pcb_addr(addr);
> +
> +    assert(width == 8);
> +
> +#ifdef TRACE_SCOMS
> +    printf("XSCOM_WRITE(0x%x:0x%x, 0x%016llx)\n",
> +           s->chip_id, pcba, (unsigned long long)val);
> +#endif
> +    /* Handle some SCOMs here before dispatch */
> +    switch(pcba) {
> +        /* We ignore writes to these */
> +    case 0xf000f:       /* chip id is RO */
> +    case 0x1010c00:     /* PIBAM FIR */
> +    case 0x1010c01:     /* PIBAM FIR */
> +    case 0x1010c02:     /* PIBAM FIR */
> +    case 0x1010c03:     /* PIBAM FIR MASK */
> +    case 0x1010c04:     /* PIBAM FIR MASK */
> +    case 0x1010c05:     /* PIBAM FIR MASK */
> +    case 0x2020007:     /* ADU stuff */
> +    case 0x2020009:     /* ADU stuff */
> +    case 0x202000f:     /* ADU stuff */
> +        break;
> +    default:
> +        if (!xscom_dispatch_write(s, pcba, val)) {
> +            xscom_complete(HMER_XSCOM_FAIL | HMER_XSCOM_DONE);
> +            return;
> +        }
> +    }
> +
> +    xscom_complete(HMER_XSCOM_DONE);
> +}
> +
> +static const MemoryRegionOps xscom_ops = {
> +    .read = xscom_read,
> +    .write = 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 int xscom_init(SysBusDevice *dev)
> +{
> +    XScomState *s = XSCOM(dev);
> +
> +    s->chip_id = -1;
> +    return 0;
> +}
> +
> +static void xscom_realize(DeviceState *dev, Error **errp)
> +{
> +    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
> +    XScomState *s = XSCOM(dev);
> +    char *name;
> +
> +    assert(s->chip_id >= 0);

So, this assert could be tripped if the user explicitly instantiated
an xscom device which they probably shouldn't do, but could.  So, it
probably makes sense to use error_setg() here instead of assert().

> +    name = g_strdup_printf("xscom-%x", s->chip_id);
> +    memory_region_init_io(&s->mem, OBJECT(s), &xscom_ops, s, name, XSCOM_SIZE);
> +    sysbus_init_mmio(sbd, &s->mem);
> +    sysbus_mmio_map(sbd, 0, XSCOM_BASE(s->chip_id));
> +}
> +
> +static Property xscom_properties[] = {
> +        DEFINE_PROP_INT32("chip_id", XScomState, chip_id, 0),
> +        DEFINE_PROP_END_OF_LIST(),
> +};
> +
> +static void xscom_class_init(ObjectClass *klass, void *data)
> +{
> +    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +
> +    dc->props = xscom_properties;
> +    dc->realize = xscom_realize;
> +    k->init = xscom_init;
> +}
> +
> +static const TypeInfo xscom_info = {
> +    .name          = TYPE_XSCOM,
> +    .parent        = TYPE_SYS_BUS_DEVICE,
> +    .instance_size = sizeof(XScomState),
> +    .class_init    = xscom_class_init,
> +};
> +
> +static void xscom_bus_class_init(ObjectClass *klass, void *data)
> +{
> +}
> +
> +static const TypeInfo xscom_bus_info = {
> +    .name = TYPE_XSCOM_BUS,
> +    .parent = TYPE_BUS,
> +    .class_init = xscom_bus_class_init,
> +    .instance_size = sizeof(XScomBus),
> +};
> +
> +void xscom_create(PnvChip *chip)
> +{
> +    DeviceState *dev;
> +    XScomState *xdev;
> +    BusState *qbus;
> +    XScomBus *xb;
> +
> +    dev = qdev_create(NULL, TYPE_XSCOM);
> +    qdev_prop_set_uint32(dev, "chip_id", chip->chip_id);
> +    qdev_init_nofail(dev);
> +
> +    /* Create bus on bridge device */
> +    qbus = qbus_create(TYPE_XSCOM_BUS, dev, "xscom");
> +    xb = DO_UPCAST(XScomBus, bus, qbus);
> +    xb->chip_id = chip->chip_id;
> +    xdev = XSCOM(dev);
> +    xdev->bus = xb;
> +    chip->xscom = xb;

I believe the qbus_create() is usually invoked by the bridge's init
function, rather than externally.

> +}
> +
> +#define _FDT(exp) \
> +    do { \
> +        int ret = (exp);                                           \
> +        if (ret < 0) {                                             \
> +            fprintf(stderr, "qemu: error creating device tree: %s: %s\n", \
> +                    #exp, fdt_strerror(ret));                      \
> +            exit(1);                                               \
> +        }                                                          \
> +    } while (0)
> +
> +
> +int xscom_populate_fdt(XScomBus *xb, void *fdt)
> +{
> +    BusChild *bc;
> +    char *name;
> +    const char compat[] = "ibm,power8-xscom\0ibm,xscom";
> +    uint64_t reg[] = { cpu_to_be64(XSCOM_BASE(xb->chip_id)),
> +                       cpu_to_be64(XSCOM_SIZE) };
> +
> +    name = g_strdup_printf("xscom@%llx", (unsigned long long)be64_to_cpu(reg[0]));
> +    _FDT((fdt_begin_node(fdt, name)));
> +    g_free(name);
> +    _FDT((fdt_property_cell(fdt, "ibm,chip-id", xb->chip_id)));
> +    _FDT((fdt_property_cell(fdt, "#address-cells", 1)));
> +    _FDT((fdt_property_cell(fdt, "#size-cells", 1)));
> +    _FDT((fdt_property(fdt, "reg", reg, sizeof(reg))));
> +    _FDT((fdt_property(fdt, "compatible", compat, sizeof(compat)))); 
> +    _FDT((fdt_property(fdt, "scom-controller", NULL, 0))); 
> +
> +    QTAILQ_FOREACH(bc, &xb->bus.children, sibling) {
> +        DeviceState *qd = bc->child;
> +        XScomDevice *xd = XSCOM_DEVICE(qd);
> +        XScomDeviceClass *xc = XSCOM_DEVICE_GET_CLASS(xd);
> +        uint32_t reg[MAX_XSCOM_RANGES * 2];
> +        unsigned int i, sz = 0;
> +        void *cp, *p;
> +
> +        /* Some XSCOM slaves may not be represented in the DT */
> +        if (!xc->dt_name) {
> +            continue;
> +        }
> +        name = g_strdup_printf("%s@%x", xc->dt_name, xd->ranges[0].addr);
> +        _FDT((fdt_begin_node(fdt, name)));
> +        g_free(name);
> +        for (i = 0; i < MAX_XSCOM_RANGES; i++) {
> +            if (xd->ranges[i].size == 0) {
> +                break;
> +            }
> +            reg[sz++] = cpu_to_be32(xd->ranges[i].addr);
> +            reg[sz++] = cpu_to_be32(xd->ranges[i].size);
> +        }
> +        _FDT((fdt_property(fdt, "reg", reg, sz * 4)));
> +        if (xc->devnode) {
> +            _FDT((xc->devnode(xd, fdt)));
> +        }
> +#define MAX_COMPATIBLE_PROP     1024
> +        cp = p = g_malloc0(MAX_COMPATIBLE_PROP);
> +        i = 0;
> +        while((p - cp) < MAX_COMPATIBLE_PROP) {
> +            int l;
> +            if (xc->dt_compatible[i] == NULL) {
> +                break;
> +            }
> +            l = strlen(xc->dt_compatible[i]);
> +            if (l >= (MAX_COMPATIBLE_PROP - i)) {
> +                break;
> +            }
> +            strcpy(p, xc->dt_compatible[i++]);
> +            p += l + 1;
> +        }
> +        _FDT((fdt_property(fdt, "compatible", cp, p - cp)));
> +        _FDT((fdt_end_node(fdt)));
> +    }
> +
> +    _FDT((fdt_end_node(fdt)));
> +
> +    return 0;
> +}
> +
> +static int xscom_qdev_init(DeviceState *qdev)
> +{
> +    XScomDevice *xdev = (XScomDevice *)qdev;
> +    XScomDeviceClass *xc = XSCOM_DEVICE_GET_CLASS(xdev);
> +
> +    if (xc->init) {
> +        return xc->init(xdev);
> +    }
> +    return 0;
> +}
> +
> +static void xscom_device_class_init(ObjectClass *klass, void *data)
> +{
> +    DeviceClass *k = DEVICE_CLASS(klass);
> +    k->init = xscom_qdev_init;
> +    k->bus_type = TYPE_XSCOM_BUS;
> +}
> +
> +static const TypeInfo xscom_dev_info = {
> +    .name = TYPE_XSCOM_DEVICE,
> +    .parent = TYPE_DEVICE,
> +    .instance_size = sizeof(XScomDevice),
> +    .abstract = true,
> +    .class_size = sizeof(XScomDeviceClass),
> +    .class_init = xscom_device_class_init,
> +};
> +
> +static void xscom_register_types(void)
> +{
> +    type_register_static(&xscom_info);
> +    type_register_static(&xscom_bus_info);
> +    type_register_static(&xscom_dev_info);
> +}
> +
> +type_init(xscom_register_types)
> +
> diff --git a/include/hw/ppc/pnv.h b/include/hw/ppc/pnv.h
> index 9a48c16..cb157eb 100644
> --- a/include/hw/ppc/pnv.h
> +++ b/include/hw/ppc/pnv.h
> @@ -20,10 +20,12 @@
>   */
>  
>  #include "hw/hw.h"
> +typedef struct XScomBus XScomBus;
>  
>  /* Should we turn that into a QOjb of some sort ? */
>  typedef struct PnvChip {
>      uint32_t         chip_id;
> +    XScomBus         *xscom;
>  } PnvChip;
>  
>  typedef struct PnvSystem {
> diff --git a/include/hw/ppc/pnv_xscom.h b/include/hw/ppc/pnv_xscom.h
> new file mode 100644
> index 0000000..99de078
> --- /dev/null
> +++ b/include/hw/ppc/pnv_xscom.h
> @@ -0,0 +1,73 @@
> +#ifndef _HW_XSCOM_H
> +#define _HW_XSCOM_H
> +/*
> + * QEMU PowerNV XSCOM bus definitions
> + *
> + * Copyright (c) 2010 David Gibson, IBM Corporation <david@gibson.dropbear.id.au>
> + * Based on the s390 virtio bus definitions:
> + * Copyright (c) 2009 Alexander Graf <agraf@suse.de>
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2 of the License, or (at your option) any later version.
> + *
> + * This library 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
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#include <hw/ppc/pnv.h>
> +
> +#define TYPE_XSCOM_DEVICE "xscom-device"
> +#define XSCOM_DEVICE(obj) \
> +     OBJECT_CHECK(XScomDevice, (obj), TYPE_XSCOM_DEVICE)
> +#define XSCOM_DEVICE_CLASS(klass) \
> +     OBJECT_CLASS_CHECK(XScomDeviceClass, (klass), TYPE_XSCOM_DEVICE)
> +#define XSCOM_DEVICE_GET_CLASS(obj) \
> +     OBJECT_GET_CLASS(XScomDeviceClass, (obj), TYPE_XSCOM_DEVICE)
> +
> +#define TYPE_XSCOM_BUS "xscom-bus"
> +#define XSCOM_BUS(obj) OBJECT_CHECK(XScomBus, (obj), TYPE_XSCOM_BUS)
> +
> +typedef struct XScomDevice XScomDevice;
> +typedef struct XScomBus XScomBus;
> +
> +typedef struct XScomDeviceClass {
> +    DeviceClass parent_class;
> +
> +    const char *dt_name;
> +    const char **dt_compatible;
> +    int (*init)(XScomDevice *dev);
> +    int (*devnode)(XScomDevice *dev, void *fdt);
> +
> +    /* Actual XScom accesses */
> +    bool (*read)(XScomDevice *dev, uint32_t range, uint32_t offset, uint64_t *out_val);
> +    bool (*write)(XScomDevice *dev, uint32_t range, uint32_t offset, uint64_t val);
> +} XScomDeviceClass;
> +
> +typedef struct XScomRange {
> +    uint32_t addr;
> +    uint32_t size;
> +} XScomRange;
> +
> +struct XScomDevice {
> +    DeviceState qdev;
> +#define MAX_XSCOM_RANGES	4
> +    struct XScomRange ranges[MAX_XSCOM_RANGES];
> +};
> +
> +struct XScomBus {
> +    BusState bus;
> +    uint32_t chip_id;
> +};
> +
> +extern void xscom_create(PnvChip *chip);
> +extern int xscom_populate_fdt(XScomBus *xscom, void *fdt);
> +
> +
> +#endif /* _HW_XSCOM_H */
Benjamin Herrenschmidt Nov. 24, 2015, 8:49 a.m. UTC | #2
On Tue, 2015-11-24 at 14:20 +1100, David Gibson wrote:
> Hmm.. I'm thinking it probably makes sense to unify the representation
> of "chip" and "xscom" since there's a 1:1 correspondance.  To be
> QOMishly correct, I think the right way would be a SysBusDevice for
> each chip, implementing the xscom MMIOs, then an array of link properties
> under the Machine object to find the right scoms by chip id.

But there is more than xscom's under each chip ... there's PHBs, LPC
controllers (though those are currently under XSCOM) and possibly other
completely unrelated things...

To be honest I've never been a big fan of that whole QOM business and
am a bit confused as to how that would all work together.

Cheers,
Ben.
Benjamin Herrenschmidt Nov. 24, 2015, 8:55 a.m. UTC | #3
On Tue, 2015-11-24 at 14:20 +1100, David Gibson wrote:
> 

> > +static uint32_t xscom_to_pcb_addr(uint64_t addr)

> > +{

> > +        addr &= (XSCOM_SIZE - 1);

> > +        return ((addr >> 4) & ~0xfull) | ((addr >> 3) & 0xf);

> 

> Wow, that's a pretty weird address transform.


Indeed :-) That's how it is in HW. It's also a bit different between
chip generations.

> > +}

> > +

> > +static void xscom_complete(uint64_t hmer_bits)

> > +{

> > +    CPUState *cs = current_cpu;

> > +    PowerPCCPU *cpu = POWERPC_CPU(cs);

> > +    CPUPPCState *env = &cpu->env;

> > +

> > +    cpu_synchronize_state(cs);

> > +    env->spr[SPR_HMER] |= hmer_bits;

> > +

> > +    /* XXX Need a CPU helper to set HMER, also handle gneeration

> > +     * of HMIs

> 

> Not sure what you're referring to here.  Nothing more should be

> needed to set the HMER - because you've called

> cpu_synchronize_state() it will be marked dirty and flushed back to

> KVM before re-entry.


No it's not that. Setting HMER can potentially generate an HMI
interrupt (if enabled in HMEER). We never use the interrupt
corresponding to XSCOMs in FW though, so that's why I haven't bothered
yet.

> > +     */

> > +}

> > +

> > +static XScomDevice *xscom_find_target(XScomState *s, uint32_t pcb_addr, uint32_t *range)

> > +{

> > +    BusChild *bc;

> > +

> > +    QTAILQ_FOREACH(bc, &s->bus->bus.children, sibling) {

> > +        DeviceState *qd = bc->child;

> > +        XScomDevice *xd = XSCOM_DEVICE(qd);

> > +        unsigned int i;

> > +

> > +        for (i = 0; i < MAX_XSCOM_RANGES; i++) {

> > +            if (xd->ranges[i].addr <= pcb_addr &&

> > +                (xd->ranges[i].addr + xd->ranges[i].size) > pcb_addr) {

> > +                *range = i;

> > +                return xd;

> > +            }

> > +        }

> > +    }

> 

> I'm wondering if it makes sense to construct a custom AddressSpace

> and

> use the existing address space lookup logic from exec.c and memory.c

> rather than implementing your own.


Maybe but we'd then have to shift everything by 3 bits, which means the
"XSCOM addresses" would no longer match the doc unless we use some kind
of macro to do the shifting.

> > +    return NULL;

> > +}

> > +

> > +static bool xscom_dispatch_read(XScomState *s, uint32_t pcb_addr, uint64_t *out_val)

> > +{

> > +    uint32_t range, offset;

> > +    struct XScomDevice *xd = xscom_find_target(s, pcb_addr, &range);

> > +    XScomDeviceClass *xc;

> > +

> > +    if (!xd) {

> > +        return false;

> > +    }

> > +    xc = XSCOM_DEVICE_GET_CLASS(xd);

> > +    if (!xc->read) {

> > +        return false;

> > +    }

> > +    offset = pcb_addr - xd->ranges[range].addr;

> > +    return xc->read(xd, range, offset, out_val);

> > +}

> > +

> > +static bool xscom_dispatch_write(XScomState *s, uint32_t pcb_addr, uint64_t val)

> > +{

> > +    uint32_t range, offset;

> > +    struct XScomDevice *xd = xscom_find_target(s, pcb_addr, &range);

> > +    XScomDeviceClass *xc;

> > +

> > +    if (!xd) {

> > +        return false;

> > +    }

> > +    xc = XSCOM_DEVICE_GET_CLASS(xd);

> > +    if (!xc->write) {

> > +        return false;

> > +    }

> > +    offset = pcb_addr - xd->ranges[range].addr;

> > +    return xc->write(xd, range, offset, val);

> > +}

> > +

> > +static uint64_t xscom_read(void *opaque, hwaddr addr, unsigned width)

> > +{

> > +    XScomState *s = opaque;

> > +    uint32_t pcba = xscom_to_pcb_addr(addr);

> > +    uint64_t val;

> > +

> > +    assert(width == 8);

> > +

> > +#ifdef TRACE_SCOMS

> > +    printf("XSCOM_READ(0x%x:0x%x)\n", s->chip_id, pcba);

> > +#endif

> 

> You should be using the built in trace infrastructure here - it's

> really not that much of a pain.  Put

>         trace_xscom_read(s->chip_id, pcba)

> here, put a suitable format in trace-events, and ./configure

> --enable-trace-backends=stderr


I'll investigate this.

> > +

> > +    /* Handle some SCOMs here before dispatch */

> > +    switch(pcba) {

> > +    case 0xf000f:

> > +        val = 0x221EF04980000000;

> > +        break;

> > +    case 0x1010c00:     /* PIBAM FIR */

> > +    case 0x1010c03:     /* PIBAM FIR MASK */

> > +    case 0x2020007:     /* ADU stuff */

> > +    case 0x2020009:     /* ADU stuff */

> > +    case 0x202000f:     /* ADU stuff */

> > +        val = 0;

> > +        break;

> > +    case 0x2013f00:     /* PBA stuff */

> > +    case 0x2013f01:     /* PBA stuff */

> > +    case 0x2013f02:     /* PBA stuff */

> > +    case 0x2013f03:     /* PBA stuff */

> > +    case 0x2013f04:     /* PBA stuff */

> > +    case 0x2013f05:     /* PBA stuff */

> > +    case 0x2013f06:     /* PBA stuff */

> > +    case 0x2013f07:     /* PBA stuff */

> > +        val = 0;

> > +        break;

> > +    default:

> > +        if (!xscom_dispatch_read(s, pcba, &val)) {

> > +            xscom_complete(HMER_XSCOM_FAIL | HMER_XSCOM_DONE);

> > +            return 0;

> > +        }

> > +    }

> > +

> > +    xscom_complete(HMER_XSCOM_DONE);

> > +    return val;

> > +}

> > +

> > +static void xscom_write(void *opaque, hwaddr addr, uint64_t val,

> > +                        unsigned width)

> > +{

> > +    XScomState *s = opaque;

> > +    uint32_t pcba = xscom_to_pcb_addr(addr);

> > +

> > +    assert(width == 8);

> > +

> > +#ifdef TRACE_SCOMS

> > +    printf("XSCOM_WRITE(0x%x:0x%x, 0x%016llx)\n",

> > +           s->chip_id, pcba, (unsigned long long)val);

> > +#endif

> > +    /* Handle some SCOMs here before dispatch */

> > +    switch(pcba) {

> > +        /* We ignore writes to these */

> > +    case 0xf000f:       /* chip id is RO */

> > +    case 0x1010c00:     /* PIBAM FIR */

> > +    case 0x1010c01:     /* PIBAM FIR */

> > +    case 0x1010c02:     /* PIBAM FIR */

> > +    case 0x1010c03:     /* PIBAM FIR MASK */

> > +    case 0x1010c04:     /* PIBAM FIR MASK */

> > +    case 0x1010c05:     /* PIBAM FIR MASK */

> > +    case 0x2020007:     /* ADU stuff */

> > +    case 0x2020009:     /* ADU stuff */

> > +    case 0x202000f:     /* ADU stuff */

> > +        break;

> > +    default:

> > +        if (!xscom_dispatch_write(s, pcba, val)) {

> > +            xscom_complete(HMER_XSCOM_FAIL | HMER_XSCOM_DONE);

> > +            return;

> > +        }

> > +    }

> > +

> > +    xscom_complete(HMER_XSCOM_DONE);

> > +}

> > +

> > +static const MemoryRegionOps xscom_ops = {

> > +    .read = xscom_read,

> > +    .write = 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 int xscom_init(SysBusDevice *dev)

> > +{

> > +    XScomState *s = XSCOM(dev);

> > +

> > +    s->chip_id = -1;

> > +    return 0;

> > +}

> > +

> > +static void xscom_realize(DeviceState *dev, Error **errp)

> > +{

> > +    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);

> > +    XScomState *s = XSCOM(dev);

> > +    char *name;

> > +

> > +    assert(s->chip_id >= 0);

> 

> So, this assert could be tripped if the user explicitly instantiated

> an xscom device which they probably shouldn't do, but could.  So, it

> probably makes sense to use error_setg() here instead of assert().


No idea what error_setg() is, I'll look into it :)

> > +    name = g_strdup_printf("xscom-%x", s->chip_id);

> > +    memory_region_init_io(&s->mem, OBJECT(s), &xscom_ops, s, name,

> XSCOM_SIZE);

> > +    sysbus_init_mmio(sbd, &s->mem);

> > +    sysbus_mmio_map(sbd, 0, XSCOM_BASE(s->chip_id));

> > +}

> > +

> > +static Property xscom_properties[] = {

> > +        DEFINE_PROP_INT32("chip_id", XScomState, chip_id, 0),

> > +        DEFINE_PROP_END_OF_LIST(),

> > +};

> > +

> > +static void xscom_class_init(ObjectClass *klass, void *data)

> > +{

> > +    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);

> > +    DeviceClass *dc = DEVICE_CLASS(klass);

> > +

> > +    dc->props = xscom_properties;

> > +    dc->realize = xscom_realize;

> > +    k->init = xscom_init;

> > +}

> > +

> > +static const TypeInfo xscom_info = {

> > +    .name          = TYPE_XSCOM,

> > +    .parent        = TYPE_SYS_BUS_DEVICE,

> > +    .instance_size = sizeof(XScomState),

> > +    .class_init    = xscom_class_init,

> > +};

> > +

> > +static void xscom_bus_class_init(ObjectClass *klass, void *data)

> > +{

> > +}

> > +

> > +static const TypeInfo xscom_bus_info = {

> > +    .name = TYPE_XSCOM_BUS,

> > +    .parent = TYPE_BUS,

> > +    .class_init = xscom_bus_class_init,

> > +    .instance_size = sizeof(XScomBus),

> > +};

> > +

> > +void xscom_create(PnvChip *chip)

> > +{

> > +    DeviceState *dev;

> > +    XScomState *xdev;

> > +    BusState *qbus;

> > +    XScomBus *xb;

> > +

> > +    dev = qdev_create(NULL, TYPE_XSCOM);

> > +    qdev_prop_set_uint32(dev, "chip_id", chip->chip_id);

> > +    qdev_init_nofail(dev);

> > +

> > +    /* Create bus on bridge device */

> > +    qbus = qbus_create(TYPE_XSCOM_BUS, dev, "xscom");

> > +    xb = DO_UPCAST(XScomBus, bus, qbus);

> > +    xb->chip_id = chip->chip_id;

> > +    xdev = XSCOM(dev);

> > +    xdev->bus = xb;

> > +    chip->xscom = xb;

> 

> I believe the qbus_create() is usually invoked by the bridge's init

> function, rather than externally.


Init or realize ?

Cheers,
Ben.
diff mbox

Patch

diff --git a/hw/ppc/Makefile.objs b/hw/ppc/Makefile.objs
index cd74c96..2a7dd42 100644
--- a/hw/ppc/Makefile.objs
+++ b/hw/ppc/Makefile.objs
@@ -5,7 +5,7 @@  obj-$(CONFIG_PSERIES) += spapr.o spapr_vio.o spapr_events.o
 obj-$(CONFIG_PSERIES) += spapr_hcall.o spapr_iommu.o spapr_rtas.o
 obj-$(CONFIG_PSERIES) += spapr_pci.o spapr_rtc.o spapr_drc.o spapr_rng.o
 # IBM PowerNV
-obj-$(CONFIG_POWERNV) += pnv.o
+obj-$(CONFIG_POWERNV) += pnv.o pnv_xscom.o
 ifeq ($(CONFIG_PCI)$(CONFIG_PSERIES)$(CONFIG_LINUX), yyy)
 obj-y += spapr_pci_vfio.o
 endif
diff --git a/hw/ppc/pnv.c b/hw/ppc/pnv.c
index e68c9b1..2eac877 100644
--- a/hw/ppc/pnv.c
+++ b/hw/ppc/pnv.c
@@ -41,6 +41,7 @@ 
 #include "hw/ppc/ppc.h"
 #include "hw/ppc/pnv.h"
 #include "hw/loader.h"
+#include "hw/ppc/pnv_xscom.h"
 
 #include "exec/address-spaces.h"
 #include "qemu/config-file.h"
@@ -310,6 +311,7 @@  static void *powernv_create_fdt(PnvSystem *sys, uint32_t initrd_base, uint32_t i
     uint32_t end_prop = cpu_to_be32(initrd_base + initrd_size);
     char *buf;
     const char plat_compat[] = "qemu,powernv\0ibm,powernv";
+    unsigned int i;
 
     fdt = g_malloc0(FDT_MAX_SIZE);
     _FDT((fdt_create(fdt, FDT_MAX_SIZE)));
@@ -367,6 +369,12 @@  static void *powernv_create_fdt(PnvSystem *sys, uint32_t initrd_base, uint32_t i
     /* Memory */
     _FDT((powernv_populate_memory(fdt)));
 
+    /* For each chip */
+    for (i = 0; i < sys->num_chips; i++) {
+        /* Populate XSCOM */
+        _FDT((xscom_populate_fdt(sys->chips[i].xscom, fdt)));
+    }
+
     /* /hypervisor node */
     if (kvm_enabled()) {
         uint8_t hypercall[16];
@@ -424,6 +432,9 @@  static void pnv_create_chip(PnvSystem *sys, unsigned int chip_no)
 
     /* XXX Improve chip numbering to better match HW */
     chip->chip_id = chip_no;
+
+    /* Set up XSCOM bus */
+    xscom_create(chip);
 }
 
 static void ppc_powernv_init(MachineState *machine)
diff --git a/hw/ppc/pnv_xscom.c b/hw/ppc/pnv_xscom.c
new file mode 100644
index 0000000..bb35422
--- /dev/null
+++ b/hw/ppc/pnv_xscom.c
@@ -0,0 +1,415 @@ 
+
+/*
+ * QEMU PowerNV XSCOM bus definitions
+ *
+ * Copyright (c) 2010 David Gibson, IBM Corporation <dwg@au1.ibm.com>
+ * Based on the s390 virtio bus code:
+ * Copyright (c) 2009 Alexander Graf <agraf@suse.de>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* TODO: Add some infrastructure for "random stuff" and FIRs that
+ * various units might want to deal with without creating actual
+ * XSCOM devices.
+ *
+ * For example, HB LPC XSCOM in the PIBAM
+ */
+#include "hw/hw.h"
+#include "sysemu/sysemu.h"
+#include "hw/boards.h"
+#include "monitor/monitor.h"
+#include "hw/loader.h"
+#include "elf.h"
+#include "hw/sysbus.h"
+#include "sysemu/kvm.h"
+#include "sysemu/device_tree.h"
+#include "kvm_ppc.h"
+
+#include "hw/ppc/pnv_xscom.h"
+
+#include <libfdt.h>
+
+#define TYPE_XSCOM "xscom"
+#define XSCOM(obj) OBJECT_CHECK(XScomState, (obj), TYPE_XSCOM)
+
+#define XSCOM_SIZE        0x800000000ull
+#define XSCOM_BASE(chip)  (0x3fc0000000000ull + ((uint64_t)(chip)) * XSCOM_SIZE)
+
+//#define TRACE_SCOMS
+
+typedef struct XScomState {
+    /*< private >*/
+    SysBusDevice parent_obj;
+    /*< public >*/
+
+    MemoryRegion mem;
+    int32_t chip_id;
+    XScomBus *bus;
+} XScomState;
+
+static uint32_t xscom_to_pcb_addr(uint64_t addr)
+{
+        addr &= (XSCOM_SIZE - 1);
+        return ((addr >> 4) & ~0xfull) | ((addr >> 3) & 0xf);
+}
+
+static void xscom_complete(uint64_t hmer_bits)
+{
+    CPUState *cs = current_cpu;
+    PowerPCCPU *cpu = POWERPC_CPU(cs);
+    CPUPPCState *env = &cpu->env;
+
+    cpu_synchronize_state(cs);
+    env->spr[SPR_HMER] |= hmer_bits;
+
+    /* XXX Need a CPU helper to set HMER, also handle gneeration
+     * of HMIs
+     */
+}
+
+static XScomDevice *xscom_find_target(XScomState *s, uint32_t pcb_addr, uint32_t *range)
+{
+    BusChild *bc;
+
+    QTAILQ_FOREACH(bc, &s->bus->bus.children, sibling) {
+        DeviceState *qd = bc->child;
+        XScomDevice *xd = XSCOM_DEVICE(qd);
+        unsigned int i;
+
+        for (i = 0; i < MAX_XSCOM_RANGES; i++) {
+            if (xd->ranges[i].addr <= pcb_addr &&
+                (xd->ranges[i].addr + xd->ranges[i].size) > pcb_addr) {
+                *range = i;
+                return xd;
+            }
+        }
+    }
+    return NULL;
+}
+
+static bool xscom_dispatch_read(XScomState *s, uint32_t pcb_addr, uint64_t *out_val)
+{
+    uint32_t range, offset;
+    struct XScomDevice *xd = xscom_find_target(s, pcb_addr, &range);
+    XScomDeviceClass *xc;
+
+    if (!xd) {
+        return false;
+    }
+    xc = XSCOM_DEVICE_GET_CLASS(xd);
+    if (!xc->read) {
+        return false;
+    }
+    offset = pcb_addr - xd->ranges[range].addr;
+    return xc->read(xd, range, offset, out_val);
+}
+
+static bool xscom_dispatch_write(XScomState *s, uint32_t pcb_addr, uint64_t val)
+{
+    uint32_t range, offset;
+    struct XScomDevice *xd = xscom_find_target(s, pcb_addr, &range);
+    XScomDeviceClass *xc;
+
+    if (!xd) {
+        return false;
+    }
+    xc = XSCOM_DEVICE_GET_CLASS(xd);
+    if (!xc->write) {
+        return false;
+    }
+    offset = pcb_addr - xd->ranges[range].addr;
+    return xc->write(xd, range, offset, val);
+}
+
+static uint64_t xscom_read(void *opaque, hwaddr addr, unsigned width)
+{
+    XScomState *s = opaque;
+    uint32_t pcba = xscom_to_pcb_addr(addr);
+    uint64_t val;
+
+    assert(width == 8);
+
+#ifdef TRACE_SCOMS
+    printf("XSCOM_READ(0x%x:0x%x)\n", s->chip_id, pcba);
+#endif
+
+    /* Handle some SCOMs here before dispatch */
+    switch(pcba) {
+    case 0xf000f:
+        val = 0x221EF04980000000;
+        break;
+    case 0x1010c00:     /* PIBAM FIR */
+    case 0x1010c03:     /* PIBAM FIR MASK */
+    case 0x2020007:     /* ADU stuff */
+    case 0x2020009:     /* ADU stuff */
+    case 0x202000f:     /* ADU stuff */
+        val = 0;
+        break;
+    case 0x2013f00:     /* PBA stuff */
+    case 0x2013f01:     /* PBA stuff */
+    case 0x2013f02:     /* PBA stuff */
+    case 0x2013f03:     /* PBA stuff */
+    case 0x2013f04:     /* PBA stuff */
+    case 0x2013f05:     /* PBA stuff */
+    case 0x2013f06:     /* PBA stuff */
+    case 0x2013f07:     /* PBA stuff */
+        val = 0;
+        break;
+    default:
+        if (!xscom_dispatch_read(s, pcba, &val)) {
+            xscom_complete(HMER_XSCOM_FAIL | HMER_XSCOM_DONE);
+            return 0;
+        }
+    }
+
+    xscom_complete(HMER_XSCOM_DONE);
+    return val;
+}
+
+static void xscom_write(void *opaque, hwaddr addr, uint64_t val,
+                        unsigned width)
+{
+    XScomState *s = opaque;
+    uint32_t pcba = xscom_to_pcb_addr(addr);
+
+    assert(width == 8);
+
+#ifdef TRACE_SCOMS
+    printf("XSCOM_WRITE(0x%x:0x%x, 0x%016llx)\n",
+           s->chip_id, pcba, (unsigned long long)val);
+#endif
+    /* Handle some SCOMs here before dispatch */
+    switch(pcba) {
+        /* We ignore writes to these */
+    case 0xf000f:       /* chip id is RO */
+    case 0x1010c00:     /* PIBAM FIR */
+    case 0x1010c01:     /* PIBAM FIR */
+    case 0x1010c02:     /* PIBAM FIR */
+    case 0x1010c03:     /* PIBAM FIR MASK */
+    case 0x1010c04:     /* PIBAM FIR MASK */
+    case 0x1010c05:     /* PIBAM FIR MASK */
+    case 0x2020007:     /* ADU stuff */
+    case 0x2020009:     /* ADU stuff */
+    case 0x202000f:     /* ADU stuff */
+        break;
+    default:
+        if (!xscom_dispatch_write(s, pcba, val)) {
+            xscom_complete(HMER_XSCOM_FAIL | HMER_XSCOM_DONE);
+            return;
+        }
+    }
+
+    xscom_complete(HMER_XSCOM_DONE);
+}
+
+static const MemoryRegionOps xscom_ops = {
+    .read = xscom_read,
+    .write = 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 int xscom_init(SysBusDevice *dev)
+{
+    XScomState *s = XSCOM(dev);
+
+    s->chip_id = -1;
+    return 0;
+}
+
+static void xscom_realize(DeviceState *dev, Error **errp)
+{
+    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+    XScomState *s = XSCOM(dev);
+    char *name;
+
+    assert(s->chip_id >= 0);
+    name = g_strdup_printf("xscom-%x", s->chip_id);
+    memory_region_init_io(&s->mem, OBJECT(s), &xscom_ops, s, name, XSCOM_SIZE);
+    sysbus_init_mmio(sbd, &s->mem);
+    sysbus_mmio_map(sbd, 0, XSCOM_BASE(s->chip_id));
+}
+
+static Property xscom_properties[] = {
+        DEFINE_PROP_INT32("chip_id", XScomState, chip_id, 0),
+        DEFINE_PROP_END_OF_LIST(),
+};
+
+static void xscom_class_init(ObjectClass *klass, void *data)
+{
+    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->props = xscom_properties;
+    dc->realize = xscom_realize;
+    k->init = xscom_init;
+}
+
+static const TypeInfo xscom_info = {
+    .name          = TYPE_XSCOM,
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(XScomState),
+    .class_init    = xscom_class_init,
+};
+
+static void xscom_bus_class_init(ObjectClass *klass, void *data)
+{
+}
+
+static const TypeInfo xscom_bus_info = {
+    .name = TYPE_XSCOM_BUS,
+    .parent = TYPE_BUS,
+    .class_init = xscom_bus_class_init,
+    .instance_size = sizeof(XScomBus),
+};
+
+void xscom_create(PnvChip *chip)
+{
+    DeviceState *dev;
+    XScomState *xdev;
+    BusState *qbus;
+    XScomBus *xb;
+
+    dev = qdev_create(NULL, TYPE_XSCOM);
+    qdev_prop_set_uint32(dev, "chip_id", chip->chip_id);
+    qdev_init_nofail(dev);
+
+    /* Create bus on bridge device */
+    qbus = qbus_create(TYPE_XSCOM_BUS, dev, "xscom");
+    xb = DO_UPCAST(XScomBus, bus, qbus);
+    xb->chip_id = chip->chip_id;
+    xdev = XSCOM(dev);
+    xdev->bus = xb;
+    chip->xscom = xb;
+}
+
+#define _FDT(exp) \
+    do { \
+        int ret = (exp);                                           \
+        if (ret < 0) {                                             \
+            fprintf(stderr, "qemu: error creating device tree: %s: %s\n", \
+                    #exp, fdt_strerror(ret));                      \
+            exit(1);                                               \
+        }                                                          \
+    } while (0)
+
+
+int xscom_populate_fdt(XScomBus *xb, void *fdt)
+{
+    BusChild *bc;
+    char *name;
+    const char compat[] = "ibm,power8-xscom\0ibm,xscom";
+    uint64_t reg[] = { cpu_to_be64(XSCOM_BASE(xb->chip_id)),
+                       cpu_to_be64(XSCOM_SIZE) };
+
+    name = g_strdup_printf("xscom@%llx", (unsigned long long)be64_to_cpu(reg[0]));
+    _FDT((fdt_begin_node(fdt, name)));
+    g_free(name);
+    _FDT((fdt_property_cell(fdt, "ibm,chip-id", xb->chip_id)));
+    _FDT((fdt_property_cell(fdt, "#address-cells", 1)));
+    _FDT((fdt_property_cell(fdt, "#size-cells", 1)));
+    _FDT((fdt_property(fdt, "reg", reg, sizeof(reg))));
+    _FDT((fdt_property(fdt, "compatible", compat, sizeof(compat)))); 
+    _FDT((fdt_property(fdt, "scom-controller", NULL, 0))); 
+
+    QTAILQ_FOREACH(bc, &xb->bus.children, sibling) {
+        DeviceState *qd = bc->child;
+        XScomDevice *xd = XSCOM_DEVICE(qd);
+        XScomDeviceClass *xc = XSCOM_DEVICE_GET_CLASS(xd);
+        uint32_t reg[MAX_XSCOM_RANGES * 2];
+        unsigned int i, sz = 0;
+        void *cp, *p;
+
+        /* Some XSCOM slaves may not be represented in the DT */
+        if (!xc->dt_name) {
+            continue;
+        }
+        name = g_strdup_printf("%s@%x", xc->dt_name, xd->ranges[0].addr);
+        _FDT((fdt_begin_node(fdt, name)));
+        g_free(name);
+        for (i = 0; i < MAX_XSCOM_RANGES; i++) {
+            if (xd->ranges[i].size == 0) {
+                break;
+            }
+            reg[sz++] = cpu_to_be32(xd->ranges[i].addr);
+            reg[sz++] = cpu_to_be32(xd->ranges[i].size);
+        }
+        _FDT((fdt_property(fdt, "reg", reg, sz * 4)));
+        if (xc->devnode) {
+            _FDT((xc->devnode(xd, fdt)));
+        }
+#define MAX_COMPATIBLE_PROP     1024
+        cp = p = g_malloc0(MAX_COMPATIBLE_PROP);
+        i = 0;
+        while((p - cp) < MAX_COMPATIBLE_PROP) {
+            int l;
+            if (xc->dt_compatible[i] == NULL) {
+                break;
+            }
+            l = strlen(xc->dt_compatible[i]);
+            if (l >= (MAX_COMPATIBLE_PROP - i)) {
+                break;
+            }
+            strcpy(p, xc->dt_compatible[i++]);
+            p += l + 1;
+        }
+        _FDT((fdt_property(fdt, "compatible", cp, p - cp)));
+        _FDT((fdt_end_node(fdt)));
+    }
+
+    _FDT((fdt_end_node(fdt)));
+
+    return 0;
+}
+
+static int xscom_qdev_init(DeviceState *qdev)
+{
+    XScomDevice *xdev = (XScomDevice *)qdev;
+    XScomDeviceClass *xc = XSCOM_DEVICE_GET_CLASS(xdev);
+
+    if (xc->init) {
+        return xc->init(xdev);
+    }
+    return 0;
+}
+
+static void xscom_device_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *k = DEVICE_CLASS(klass);
+    k->init = xscom_qdev_init;
+    k->bus_type = TYPE_XSCOM_BUS;
+}
+
+static const TypeInfo xscom_dev_info = {
+    .name = TYPE_XSCOM_DEVICE,
+    .parent = TYPE_DEVICE,
+    .instance_size = sizeof(XScomDevice),
+    .abstract = true,
+    .class_size = sizeof(XScomDeviceClass),
+    .class_init = xscom_device_class_init,
+};
+
+static void xscom_register_types(void)
+{
+    type_register_static(&xscom_info);
+    type_register_static(&xscom_bus_info);
+    type_register_static(&xscom_dev_info);
+}
+
+type_init(xscom_register_types)
+
diff --git a/include/hw/ppc/pnv.h b/include/hw/ppc/pnv.h
index 9a48c16..cb157eb 100644
--- a/include/hw/ppc/pnv.h
+++ b/include/hw/ppc/pnv.h
@@ -20,10 +20,12 @@ 
  */
 
 #include "hw/hw.h"
+typedef struct XScomBus XScomBus;
 
 /* Should we turn that into a QOjb of some sort ? */
 typedef struct PnvChip {
     uint32_t         chip_id;
+    XScomBus         *xscom;
 } PnvChip;
 
 typedef struct PnvSystem {
diff --git a/include/hw/ppc/pnv_xscom.h b/include/hw/ppc/pnv_xscom.h
new file mode 100644
index 0000000..99de078
--- /dev/null
+++ b/include/hw/ppc/pnv_xscom.h
@@ -0,0 +1,73 @@ 
+#ifndef _HW_XSCOM_H
+#define _HW_XSCOM_H
+/*
+ * QEMU PowerNV XSCOM bus definitions
+ *
+ * Copyright (c) 2010 David Gibson, IBM Corporation <david@gibson.dropbear.id.au>
+ * Based on the s390 virtio bus definitions:
+ * Copyright (c) 2009 Alexander Graf <agraf@suse.de>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <hw/ppc/pnv.h>
+
+#define TYPE_XSCOM_DEVICE "xscom-device"
+#define XSCOM_DEVICE(obj) \
+     OBJECT_CHECK(XScomDevice, (obj), TYPE_XSCOM_DEVICE)
+#define XSCOM_DEVICE_CLASS(klass) \
+     OBJECT_CLASS_CHECK(XScomDeviceClass, (klass), TYPE_XSCOM_DEVICE)
+#define XSCOM_DEVICE_GET_CLASS(obj) \
+     OBJECT_GET_CLASS(XScomDeviceClass, (obj), TYPE_XSCOM_DEVICE)
+
+#define TYPE_XSCOM_BUS "xscom-bus"
+#define XSCOM_BUS(obj) OBJECT_CHECK(XScomBus, (obj), TYPE_XSCOM_BUS)
+
+typedef struct XScomDevice XScomDevice;
+typedef struct XScomBus XScomBus;
+
+typedef struct XScomDeviceClass {
+    DeviceClass parent_class;
+
+    const char *dt_name;
+    const char **dt_compatible;
+    int (*init)(XScomDevice *dev);
+    int (*devnode)(XScomDevice *dev, void *fdt);
+
+    /* Actual XScom accesses */
+    bool (*read)(XScomDevice *dev, uint32_t range, uint32_t offset, uint64_t *out_val);
+    bool (*write)(XScomDevice *dev, uint32_t range, uint32_t offset, uint64_t val);
+} XScomDeviceClass;
+
+typedef struct XScomRange {
+    uint32_t addr;
+    uint32_t size;
+} XScomRange;
+
+struct XScomDevice {
+    DeviceState qdev;
+#define MAX_XSCOM_RANGES	4
+    struct XScomRange ranges[MAX_XSCOM_RANGES];
+};
+
+struct XScomBus {
+    BusState bus;
+    uint32_t chip_id;
+};
+
+extern void xscom_create(PnvChip *chip);
+extern int xscom_populate_fdt(XScomBus *xscom, void *fdt);
+
+
+#endif /* _HW_XSCOM_H */