diff mbox

[v5,10/14] pci: Add support for Designware IP block

Message ID 20180207042438.15422-11-andrew.smirnov@gmail.com (mailing list archive)
State New, archived
Headers show

Commit Message

Andrey Smirnov Feb. 7, 2018, 4:24 a.m. UTC
Add code needed to get a functional PCI subsytem when using in
conjunction with upstream Linux guest (4.13+). Tested to work against
"e1000e" (network adapter, using MSI interrupts) as well as
"usb-ehci" (USB controller, using legacy PCI interrupts).

Based on "i.MX6 Applications Processor Reference Manual" (Document
Number: IMX6DQRM Rev. 4) as well as corresponding dirver in Linux
kernel (circa 4.13 - 4.16 found in drivers/pci/dwc/*)

Cc: Peter Maydell <peter.maydell@linaro.org>
Cc: Jason Wang <jasowang@redhat.com>
Cc: Philippe Mathieu-Daudé <f4bug@amsat.org>
Cc: Marcel Apfelbaum <marcel.apfelbaum@zoho.com>
Cc: Michael S. Tsirkin <mst@redhat.com>
Cc: qemu-devel@nongnu.org
Cc: qemu-arm@nongnu.org
Cc: yurovsky@gmail.com
Signed-off-by: Andrey Smirnov <andrew.smirnov@gmail.com>
---
 default-configs/arm-softmmu.mak  |   2 +
 hw/pci-host/Makefile.objs        |   2 +
 hw/pci-host/designware.c         | 759 +++++++++++++++++++++++++++++++++++++++
 include/hw/pci-host/designware.h |  97 +++++
 include/hw/pci/pci_ids.h         |   2 +
 5 files changed, 862 insertions(+)
 create mode 100644 hw/pci-host/designware.c
 create mode 100644 include/hw/pci-host/designware.h

Comments

Michael S. Tsirkin Feb. 8, 2018, 5:45 p.m. UTC | #1
On Tue, Feb 06, 2018 at 08:24:34PM -0800, Andrey Smirnov wrote:
> Add code needed to get a functional PCI subsytem when using in
> conjunction with upstream Linux guest (4.13+). Tested to work against
> "e1000e" (network adapter, using MSI interrupts) as well as
> "usb-ehci" (USB controller, using legacy PCI interrupts).
> 
> Based on "i.MX6 Applications Processor Reference Manual" (Document
> Number: IMX6DQRM Rev. 4) as well as corresponding dirver in Linux
> kernel (circa 4.13 - 4.16 found in drivers/pci/dwc/*)
> 
> Cc: Peter Maydell <peter.maydell@linaro.org>
> Cc: Jason Wang <jasowang@redhat.com>
> Cc: Philippe Mathieu-Daudé <f4bug@amsat.org>
> Cc: Marcel Apfelbaum <marcel.apfelbaum@zoho.com>
> Cc: Michael S. Tsirkin <mst@redhat.com>
> Cc: qemu-devel@nongnu.org
> Cc: qemu-arm@nongnu.org
> Cc: yurovsky@gmail.com
> Signed-off-by: Andrey Smirnov <andrew.smirnov@gmail.com>
> ---
>  default-configs/arm-softmmu.mak  |   2 +
>  hw/pci-host/Makefile.objs        |   2 +
>  hw/pci-host/designware.c         | 759 +++++++++++++++++++++++++++++++++++++++
>  include/hw/pci-host/designware.h |  97 +++++
>  include/hw/pci/pci_ids.h         |   2 +
>  5 files changed, 862 insertions(+)
>  create mode 100644 hw/pci-host/designware.c
>  create mode 100644 include/hw/pci-host/designware.h
> 
> diff --git a/default-configs/arm-softmmu.mak b/default-configs/arm-softmmu.mak
> index b0d6e65038..0c5ae914ed 100644
> --- a/default-configs/arm-softmmu.mak
> +++ b/default-configs/arm-softmmu.mak
> @@ -132,3 +132,5 @@ CONFIG_GPIO_KEY=y
>  CONFIG_MSF2=y
>  CONFIG_FW_CFG_DMA=y
>  CONFIG_XILINX_AXI=y
> +CONFIG_PCI_DESIGNWARE=y
> +
> diff --git a/hw/pci-host/Makefile.objs b/hw/pci-host/Makefile.objs
> index 4b69f737b5..6d6597c065 100644
> --- a/hw/pci-host/Makefile.objs
> +++ b/hw/pci-host/Makefile.objs
> @@ -17,3 +17,5 @@ common-obj-$(CONFIG_PCI_PIIX) += piix.o
>  common-obj-$(CONFIG_PCI_Q35) += q35.o
>  common-obj-$(CONFIG_PCI_GENERIC) += gpex.o
>  common-obj-$(CONFIG_PCI_XILINX) += xilinx-pcie.o
> +
> +common-obj-$(CONFIG_PCI_DESIGNWARE) += designware.o
> diff --git a/hw/pci-host/designware.c b/hw/pci-host/designware.c
> new file mode 100644
> index 0000000000..551a881af0
> --- /dev/null
> +++ b/hw/pci-host/designware.c
> @@ -0,0 +1,759 @@
> +/*
> + * Copyright (c) 2018, Impinj, Inc.
> + *
> + * Designware PCIe IP block emulation
> + *
> + * 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 "qemu/osdep.h"
> +#include "qapi/error.h"
> +#include "hw/pci/msi.h"
> +#include "hw/pci/pci_bridge.h"
> +#include "hw/pci/pci_host.h"
> +#include "hw/pci/pcie_port.h"
> +#include "hw/pci-host/designware.h"
> +
> +#define PCIE_PORT_LINK_CONTROL          0x710
> +
> +#define PCIE_PHY_DEBUG_R1               0x72C
> +#define PCIE_PHY_DEBUG_R1_XMLH_LINK_UP  BIT(4)
> +
> +#define PCIE_LINK_WIDTH_SPEED_CONTROL   0x80C
> +#define PORT_LOGIC_SPEED_CHANGE         (0x1 << 17)
> +
> +#define PCIE_MSI_ADDR_LO                0x820
> +#define PCIE_MSI_ADDR_HI                0x824
> +#define PCIE_MSI_INTR0_ENABLE           0x828
> +#define PCIE_MSI_INTR0_MASK             0x82C
> +#define PCIE_MSI_INTR0_STATUS           0x830
> +
> +#define PCIE_ATU_VIEWPORT               0x900
> +#define PCIE_ATU_REGION_INBOUND         (0x1 << 31)
> +#define PCIE_ATU_REGION_OUTBOUND        (0x0 << 31)
> +#define PCIE_ATU_REGION_INDEX2          (0x2 << 0)
> +#define PCIE_ATU_REGION_INDEX1          (0x1 << 0)
> +#define PCIE_ATU_REGION_INDEX0          (0x0 << 0)
> +#define PCIE_ATU_CR1                    0x904
> +#define PCIE_ATU_TYPE_MEM               (0x0 << 0)
> +#define PCIE_ATU_TYPE_IO                (0x2 << 0)
> +#define PCIE_ATU_TYPE_CFG0              (0x4 << 0)
> +#define PCIE_ATU_TYPE_CFG1              (0x5 << 0)
> +#define PCIE_ATU_CR2                    0x908
> +#define PCIE_ATU_ENABLE                 (0x1 << 31)
> +#define PCIE_ATU_BAR_MODE_ENABLE        (0x1 << 30)
> +#define PCIE_ATU_LOWER_BASE             0x90C
> +#define PCIE_ATU_UPPER_BASE             0x910
> +#define PCIE_ATU_LIMIT                  0x914
> +#define PCIE_ATU_LOWER_TARGET           0x918
> +#define PCIE_ATU_BUS(x)                 (((x) >> 24) & 0xff)
> +#define PCIE_ATU_DEVFN(x)               (((x) >> 16) & 0xff)
> +#define PCIE_ATU_UPPER_TARGET           0x91C
> +
> +static DesignwarePCIEHost *
> +designware_pcie_root_to_host(DesignwarePCIERoot *root)
> +{
> +    BusState *bus = qdev_get_parent_bus(DEVICE(root));
> +    return DESIGNWARE_PCIE_HOST(bus->parent);
> +}
> +
> +static void designware_pcie_root_msi_write(void *opaque, hwaddr addr,
> +                                           uint64_t val, unsigned len)
> +{
> +    DesignwarePCIERoot *root = DESIGNWARE_PCIE_ROOT(opaque);
> +    DesignwarePCIEHost *host = designware_pcie_root_to_host(root);
> +
> +    root->msi.intr[0].status |= BIT(val) & root->msi.intr[0].enable;
> +
> +    if (root->msi.intr[0].status & ~root->msi.intr[0].mask) {
> +        qemu_set_irq(host->pci.irqs[0], 1);
> +    }
> +}
> +
> +static const MemoryRegionOps designware_pci_host_msi_ops = {
> +    .write = designware_pcie_root_msi_write,
> +    .endianness = DEVICE_NATIVE_ENDIAN,


Native endian normally means you need to do byteswaps yourself.
I don't see any here which looks suspicious.

> +    .valid = {
> +        .min_access_size = 4,
> +        .max_access_size = 4,
> +    },
> +};
> +
> +static void designware_pcie_root_update_msi_mapping(DesignwarePCIERoot *root)
> +
> +{
> +    MemoryRegion *mem   = &root->msi.iomem;
> +    const uint64_t base = root->msi.base;
> +    const bool enable   = root->msi.intr[0].enable;
> +
> +    memory_region_set_address(mem, base);
> +    memory_region_set_enabled(mem, enable);
> +}
> +
> +static DesignwarePCIEViewport *
> +designware_pcie_root_get_current_viewport(DesignwarePCIERoot *root)
> +{
> +    const unsigned int idx = root->atu_viewport & 0xF;
> +    const unsigned int dir = !!(root->atu_viewport & PCIE_ATU_REGION_INBOUND);
> +    return &root->viewports[dir][idx];
> +}
> +
> +static uint32_t
> +designware_pcie_root_config_read(PCIDevice *d, uint32_t address, int len)
> +{
> +    DesignwarePCIERoot *root = DESIGNWARE_PCIE_ROOT(d);
> +    DesignwarePCIEViewport *viewport =
> +        designware_pcie_root_get_current_viewport(root);
> +
> +    uint32_t val;
> +
> +    switch (address) {
> +    case PCIE_PORT_LINK_CONTROL:
> +        /*
> +         * Linux guest uses this register only to configure number of
> +         * PCIE lane (which in our case is irrelevant) and doesn't
> +         * really care about the value it reads from this register
> +         */
> +        val = 0xDEADBEEF;
> +        break;
> +
> +    case PCIE_LINK_WIDTH_SPEED_CONTROL:
> +        /*
> +         * To make sure that any code in guest waiting for speed
> +         * change does not time out we always report
> +         * PORT_LOGIC_SPEED_CHANGE as set
> +         */
> +        val = PORT_LOGIC_SPEED_CHANGE;
> +        break;
> +
> +    case PCIE_MSI_ADDR_LO:
> +        val = root->msi.base;
> +        break;
> +
> +    case PCIE_MSI_ADDR_HI:
> +        val = root->msi.base >> 32;
> +        break;
> +
> +    case PCIE_MSI_INTR0_ENABLE:
> +        val = root->msi.intr[0].enable;
> +        break;
> +
> +    case PCIE_MSI_INTR0_MASK:
> +        val = root->msi.intr[0].mask;
> +        break;
> +
> +    case PCIE_MSI_INTR0_STATUS:
> +        val = root->msi.intr[0].status;
> +        break;
> +
> +    case PCIE_PHY_DEBUG_R1:
> +        val = PCIE_PHY_DEBUG_R1_XMLH_LINK_UP;
> +        break;
> +
> +    case PCIE_ATU_VIEWPORT:
> +        val = root->atu_viewport;
> +        break;
> +
> +    case PCIE_ATU_LOWER_BASE:
> +        val = viewport->base;
> +        break;
> +
> +    case PCIE_ATU_UPPER_BASE:
> +        val = viewport->base >> 32;
> +        break;
> +
> +    case PCIE_ATU_LOWER_TARGET:
> +        val = viewport->target;
> +        break;
> +
> +    case PCIE_ATU_UPPER_TARGET:
> +        val = viewport->target >> 32;
> +        break;
> +
> +    case PCIE_ATU_LIMIT:
> +        val = viewport->limit;
> +        break;
> +
> +    case PCIE_ATU_CR1:
> +    case PCIE_ATU_CR2:          /* FALLTHROUGH */
> +        val = viewport->cr[(address - PCIE_ATU_CR1) / sizeof(uint32_t)];
> +        break;
> +
> +    default:
> +        val = pci_default_read_config(d, address, len);
> +        break;
> +    }
> +
> +    return val;
> +}
> +
> +static uint64_t designware_pcie_root_data_access(void *opaque, hwaddr addr,
> +                                                 uint64_t *val, unsigned len)
> +{
> +    DesignwarePCIEViewport *viewport;
> +    DesignwarePCIERoot *root;
> +    PCIBus *pcibus;
> +
> +    root     = DESIGNWARE_PCIE_ROOT(opaque);
> +    viewport = designware_pcie_root_get_current_viewport(root);
> +    pcibus   = pci_get_bus(PCI_DEVICE(root));
> +
> +    addr &= PCIE_CONFIG_SPACE_SIZE - 1;
> +    addr |= PCIE_ATU_BUS(viewport->target) << 16;
> +    addr |= PCIE_ATU_DEVFN(viewport->target) << 8;
> +
> +    if (val) {
> +        pci_data_write(pcibus, addr, *val, len);
> +        return 0;
> +    }
> +
> +    return pci_data_read(pcibus, addr, len);
> +}
> +
> +static uint64_t designware_pcie_root_data_read(void *opaque, hwaddr addr,
> +                                               unsigned len)
> +{
> +    return designware_pcie_root_data_access(opaque, addr, NULL, len);
> +}
> +
> +static void designware_pcie_root_data_write(void *opaque, hwaddr addr,
> +                                            uint64_t val, unsigned len)
> +{
> +    designware_pcie_root_data_access(opaque, addr, &val, len);
> +}
> +
> +static const MemoryRegionOps designware_pci_host_conf_ops = {
> +    .read = designware_pcie_root_data_read,
> +    .write = designware_pcie_root_data_write,
> +    .endianness = DEVICE_NATIVE_ENDIAN,

Native endian is usually not what's needed for device emulation.


> +    .valid = {
> +        .min_access_size = 1,
> +        .max_access_size = 4,
> +    },
> +};
> +
> +static void designware_pcie_update_viewport(DesignwarePCIERoot *root,
> +                                            DesignwarePCIEViewport *viewport)
> +{
> +    const uint64_t target = viewport->target;
> +    const uint64_t base   = viewport->base;
> +    const uint64_t size   = (uint64_t)viewport->limit - base + 1;
> +    const bool enabled    = viewport->cr[1] & PCIE_ATU_ENABLE;
> +
> +    MemoryRegion *current, *other;
> +
> +    if (viewport->cr[0] == PCIE_ATU_TYPE_MEM) {
> +        current = &viewport->mem;
> +        other   = &viewport->cfg;
> +        memory_region_set_alias_offset(current, target);
> +    } else {
> +        current = &viewport->cfg;
> +        other   = &viewport->mem;
> +    }
> +
> +    /*
> +     * An outbound viewport can be reconfigure from being MEM to CFG,
> +     * to account for that we disable the "other" memory region that
> +     * becomes unused due to that fact.
> +     */
> +    memory_region_set_enabled(other, false);
> +    if (enabled) {
> +        memory_region_set_size(current, size);
> +        memory_region_set_address(current, base);
> +    }
> +    memory_region_set_enabled(current, enabled);
> +}
> +
> +static void designware_pcie_root_config_write(PCIDevice *d, uint32_t address,
> +                                              uint32_t val, int len)
> +{
> +    DesignwarePCIERoot *root = DESIGNWARE_PCIE_ROOT(d);
> +    DesignwarePCIEHost *host = designware_pcie_root_to_host(root);
> +    DesignwarePCIEViewport *viewport =
> +        designware_pcie_root_get_current_viewport(root);
> +
> +    switch (address) {
> +    case PCIE_PORT_LINK_CONTROL:
> +    case PCIE_LINK_WIDTH_SPEED_CONTROL:
> +    case PCIE_PHY_DEBUG_R1:
> +        /* No-op */
> +        break;
> +
> +    case PCIE_MSI_ADDR_LO:
> +        root->msi.base &= 0xFFFFFFFF00000000ULL;
> +        root->msi.base |= val;
> +        break;
> +
> +    case PCIE_MSI_ADDR_HI:
> +        root->msi.base &= 0x00000000FFFFFFFFULL;
> +        root->msi.base |= (uint64_t)val << 32;
> +        break;
> +
> +    case PCIE_MSI_INTR0_ENABLE: {
> +        const bool update_msi_mapping = !root->msi.intr[0].enable ^ !!val;
> +
> +        root->msi.intr[0].enable = val;
> +
> +        if (update_msi_mapping) {
> +            designware_pcie_root_update_msi_mapping(root);
> +        }
> +        break;
> +    }
> +
> +    case PCIE_MSI_INTR0_MASK:
> +        root->msi.intr[0].mask = val;
> +        break;
> +
> +    case PCIE_MSI_INTR0_STATUS:
> +        root->msi.intr[0].status ^= val;
> +        if (!root->msi.intr[0].status) {
> +            qemu_set_irq(host->pci.irqs[0], 0);
> +        }
> +        break;
> +
> +    case PCIE_ATU_VIEWPORT:
> +        root->atu_viewport = val;
> +        break;
> +
> +    case PCIE_ATU_LOWER_BASE:
> +        viewport->base &= 0xFFFFFFFF00000000ULL;
> +        viewport->base |= val;
> +        break;
> +
> +    case PCIE_ATU_UPPER_BASE:
> +        viewport->base &= 0x00000000FFFFFFFFULL;
> +        viewport->base |= (uint64_t)val << 32;
> +        break;
> +
> +    case PCIE_ATU_LOWER_TARGET:
> +        viewport->target &= 0xFFFFFFFF00000000ULL;
> +        viewport->target |= val;
> +        break;
> +
> +    case PCIE_ATU_UPPER_TARGET:
> +        viewport->target &= 0x00000000FFFFFFFFULL;
> +        viewport->target |= val;
> +        break;
> +
> +    case PCIE_ATU_LIMIT:
> +        viewport->limit = val;
> +        break;
> +
> +    case PCIE_ATU_CR1:
> +        viewport->cr[0] = val;
> +        break;
> +    case PCIE_ATU_CR2:
> +        viewport->cr[1] = val;
> +        designware_pcie_update_viewport(root, viewport);
> +        break;
> +
> +    default:
> +        pci_bridge_write_config(d, address, val, len);
> +        break;
> +    }
> +}
> +
> +static char *designware_pcie_viewport_name(const char *direction,
> +                                           unsigned int i,
> +                                           const char *type)
> +{
> +    return g_strdup_printf("PCI %s Viewport %u [%s]",
> +                           direction, i, type);
> +}
> +
> +static void designware_pcie_root_realize(PCIDevice *dev, Error **errp)
> +{
> +    DesignwarePCIERoot *root = DESIGNWARE_PCIE_ROOT(dev);
> +    DesignwarePCIEHost *host = designware_pcie_root_to_host(root);
> +    MemoryRegion *address_space = &host->pci.memory;
> +    PCIBridge *br = PCI_BRIDGE(dev);
> +    DesignwarePCIEViewport *viewport;
> +    /*
> +     * Dummy values used for initial configuration of MemoryRegions
> +     * that belong to a give viewport
> +     */
> +    const hwaddr dummy_offset = 0;
> +    const uint64_t dummy_size = 4;
> +    size_t i;
> +
> +    br->bus_name  = "dw-pcie";
> +
> +    pci_set_word(dev->config + PCI_COMMAND,
> +                 PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER);
> +
> +    pci_config_set_interrupt_pin(dev->config, 1);
> +    pci_bridge_initfn(dev, TYPE_PCIE_BUS);
> +
> +    pcie_port_init_reg(dev);
> +
> +    pcie_cap_init(dev, 0x70, PCI_EXP_TYPE_ROOT_PORT,
> +                  0, &error_fatal);
> +
> +    msi_nonbroken = true;
> +    msi_init(dev, 0x50, 32, true, true, &error_fatal);
> +
> +    for (i = 0; i < DESIGNWARE_PCIE_NUM_VIEWPORTS; i++) {
> +        MemoryRegion *source, *destination, *mem;
> +        const char *direction;
> +        char *name;
> +
> +        viewport = &root->viewports[DESIGNWARE_PCIE_VIEWPORT_INBOUND][i];
> +        viewport->inbound = true;
> +        viewport->base    = 0x0000000000000000ULL;
> +        viewport->target  = 0x0000000000000000ULL;
> +        viewport->limit   = UINT32_MAX;
> +        viewport->cr[0]   = PCIE_ATU_TYPE_MEM;
> +
> +        source      = &host->pci.address_space_root;
> +        destination = get_system_memory();
> +        direction   = "Inbound";
> +
> +        /*
> +         * Configure MemoryRegion implementing PCI -> CPU memory
> +         * access
> +         */
> +        mem  = &viewport->mem;
> +        name = designware_pcie_viewport_name(direction, i, "MEM");
> +        memory_region_init_alias(mem, OBJECT(root), name, destination,
> +                                 dummy_offset, dummy_size);
> +        memory_region_add_subregion_overlap(source, dummy_offset, mem, -1);
> +        memory_region_set_enabled(mem, false);
> +        g_free(name);
> +
> +        viewport = &root->viewports[DESIGNWARE_PCIE_VIEWPORT_OUTBOUND][i];
> +        viewport->inbound = false;
> +        viewport->base    = 0x0000000000000000ULL;
> +        viewport->target  = 0x0000000000000000ULL;
> +        viewport->limit   = UINT32_MAX;
> +        viewport->cr[0]   = PCIE_ATU_TYPE_MEM;
> +
> +        destination = &host->pci.memory;
> +        direction   = "Outbound";
> +        source      = get_system_memory();
> +
> +        /*
> +         * Configure MemoryRegion implementing CPU -> PCI memory
> +         * access
> +         */
> +        mem  = &viewport->mem;
> +        name = designware_pcie_viewport_name(direction, i, "MEM");
> +        memory_region_init_alias(mem, OBJECT(root), name, destination,
> +                                 dummy_offset, dummy_size);
> +        memory_region_add_subregion(source, dummy_offset, mem);
> +        memory_region_set_enabled(mem, false);
> +        g_free(name);
> +
> +        /*
> +         * Configure MemoryRegion implementing access to configuration
> +         * space
> +         */
> +        mem  = &viewport->cfg;
> +        name = designware_pcie_viewport_name(direction, i, "CFG");
> +        memory_region_init_io(&viewport->cfg, OBJECT(root),
> +                              &designware_pci_host_conf_ops,
> +                              root, name, dummy_size);
> +        memory_region_add_subregion(source, dummy_offset, mem);
> +        memory_region_set_enabled(mem, false);
> +        g_free(name);

This implements the MMCFG, doesn't it?
I suspect you need pcie_host_mmcfg_update instead of rolling
your own.

> +    }
> +
> +    /*
> +     * If no inbound iATU windows are configured, HW defaults to
> +     * letting inbound TLPs to pass in. We emulate that by exlicitly
> +     * configuring first inbound window to cover all of target's
> +     * address space.
> +     *
> +     * NOTE: This will not work correctly for the case when first
> +     * configured inbound window is window 0
> +     */
> +    viewport = &root->viewports[DESIGNWARE_PCIE_VIEWPORT_INBOUND][0];
> +    viewport->cr[1] = PCIE_ATU_ENABLE;
> +    designware_pcie_update_viewport(root, viewport);
> +
> +    memory_region_init_io(&root->msi.iomem, OBJECT(root),
> +                          &designware_pci_host_msi_ops,
> +                          root, "pcie-msi", 0x4);
> +    /*
> +     * We initially place MSI interrupt I/O region a adress 0 and
> +     * disable it. It'll be later moved to correct offset and enabled
> +     * in designware_pcie_root_update_msi_mapping() as a part of
> +     * initialization done by guest OS
> +     */
> +    memory_region_add_subregion(address_space, dummy_offset, &root->msi.iomem);
> +    memory_region_set_enabled(&root->msi.iomem, false);
> +}
> +
> +static void designware_pcie_set_irq(void *opaque, int irq_num, int level)
> +{
> +    DesignwarePCIEHost *host = DESIGNWARE_PCIE_HOST(opaque);
> +
> +    qemu_set_irq(host->pci.irqs[irq_num], level);
> +}
> +
> +static const char *
> +designware_pcie_host_root_bus_path(PCIHostState *host_bridge, PCIBus *rootbus)
> +{
> +    return "0000:00";
> +}
> +
> +static const VMStateDescription vmstate_designware_pcie_msi_bank = {
> +    .name = "designware-pcie-msi-bank",
> +    .version_id = 1,
> +    .minimum_version_id = 1,
> +    .fields = (VMStateField[]) {
> +        VMSTATE_UINT32(enable, DesignwarePCIEMSIBank),
> +        VMSTATE_UINT32(mask, DesignwarePCIEMSIBank),
> +        VMSTATE_UINT32(status, DesignwarePCIEMSIBank),
> +        VMSTATE_END_OF_LIST()
> +    }
> +};
> +
> +static const VMStateDescription vmstate_designware_pcie_msi = {
> +    .name = "designware-pcie-msi",
> +    .version_id = 1,
> +    .minimum_version_id = 1,
> +    .fields = (VMStateField[]) {
> +        VMSTATE_UINT64(base, DesignwarePCIEMSI),
> +        VMSTATE_STRUCT_ARRAY(intr,
> +                             DesignwarePCIEMSI,
> +                             DESIGNWARE_PCIE_NUM_MSI_BANKS,
> +                             1,
> +                             vmstate_designware_pcie_msi_bank,
> +                             DesignwarePCIEMSIBank),
> +        VMSTATE_END_OF_LIST()
> +    }
> +};
> +
> +static const VMStateDescription vmstate_designware_pcie_viewport = {
> +    .name = "designware-pcie-viewport",
> +    .version_id = 1,
> +    .minimum_version_id = 1,
> +    .fields = (VMStateField[]) {
> +        VMSTATE_UINT64(base, DesignwarePCIEViewport),
> +        VMSTATE_UINT64(target, DesignwarePCIEViewport),
> +        VMSTATE_UINT32(limit, DesignwarePCIEViewport),
> +        VMSTATE_UINT32_ARRAY(cr, DesignwarePCIEViewport, 2),
> +        VMSTATE_END_OF_LIST()
> +    }
> +};
> +
> +static const VMStateDescription vmstate_designware_pcie_root = {
> +    .name = "designware-pcie-root",
> +    .version_id = 1,
> +    .minimum_version_id = 1,
> +    .fields = (VMStateField[]) {
> +        VMSTATE_PCI_DEVICE(parent_obj, PCIBridge),
> +        VMSTATE_UINT32(atu_viewport, DesignwarePCIERoot),
> +        VMSTATE_STRUCT_2DARRAY(viewports,
> +                               DesignwarePCIERoot,
> +                               2,
> +                               DESIGNWARE_PCIE_NUM_VIEWPORTS,
> +                               1,
> +                               vmstate_designware_pcie_viewport,
> +                               DesignwarePCIEViewport),
> +        VMSTATE_STRUCT(msi,
> +                       DesignwarePCIERoot,
> +                       1,
> +                       vmstate_designware_pcie_msi,
> +                       DesignwarePCIEMSI),
> +        VMSTATE_END_OF_LIST()
> +    }
> +};
> +
> +static void designware_pcie_root_class_init(ObjectClass *klass, void *data)
> +{
> +    PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +
> +    set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories);
> +
> +    k->vendor_id = PCI_VENDOR_ID_SYNOPSYS;
> +    k->device_id = 0xABCD;
> +    k->revision = 0;
> +    k->class_id = PCI_CLASS_BRIDGE_PCI;
> +    k->is_express = true;
> +    k->is_bridge = true;
> +    k->exit = pci_bridge_exitfn;
> +    k->realize = designware_pcie_root_realize;
> +    k->config_read = designware_pcie_root_config_read;
> +    k->config_write = designware_pcie_root_config_write;
> +
> +    dc->reset = pci_bridge_reset;
> +    /*
> +     * PCI-facing part of the host bridge, not usable without the
> +     * host-facing part, which can't be device_add'ed, yet.
> +     */
> +    dc->user_creatable = false;
> +    dc->vmsd = &vmstate_designware_pcie_root;
> +}
> +
> +static uint64_t designware_pcie_host_mmio_read(void *opaque, hwaddr addr,
> +                                               unsigned int size)
> +{
> +    PCIHostState *pci = PCI_HOST_BRIDGE(opaque);
> +    PCIDevice *device = pci_find_device(pci->bus, 0, 0);
> +
> +    return pci_host_config_read_common(device,
> +                                       addr,
> +                                       pci_config_size(device),
> +                                       size);
> +}
> +
> +static void designware_pcie_host_mmio_write(void *opaque, hwaddr addr,
> +                                            uint64_t val, unsigned int size)
> +{
> +    PCIHostState *pci = PCI_HOST_BRIDGE(opaque);
> +    PCIDevice *device = pci_find_device(pci->bus, 0, 0);
> +
> +    return pci_host_config_write_common(device,
> +                                        addr,
> +                                        pci_config_size(device),
> +                                        val, size);
> +}
> +
> +static const MemoryRegionOps designware_pci_mmio_ops = {
> +    .read       = designware_pcie_host_mmio_read,
> +    .write      = designware_pcie_host_mmio_write,
> +    .endianness = DEVICE_NATIVE_ENDIAN,
> +    .impl = {
> +        /*
> +         * Our device would not work correctly if the guest was doing
> +         * unaligned access. This might not be a limitation on the real
> +         * device but in practice there is no reason for a guest to access
> +         * this device unaligned.
> +         */
> +        .min_access_size = 4,
> +        .max_access_size = 4,
> +        .unaligned = false,
> +    },
> +};
> +
> +static AddressSpace *designware_pcie_host_set_iommu(PCIBus *bus, void *opaque,
> +                                                    int devfn)
> +{
> +    DesignwarePCIEHost *s = DESIGNWARE_PCIE_HOST(opaque);
> +
> +    return &s->pci.address_space;
> +}
> +
> +static void designware_pcie_host_realize(DeviceState *dev, Error **errp)
> +{
> +    PCIHostState *pci = PCI_HOST_BRIDGE(dev);
> +    DesignwarePCIEHost *s = DESIGNWARE_PCIE_HOST(dev);
> +    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
> +    size_t i;
> +
> +    for (i = 0; i < ARRAY_SIZE(s->pci.irqs); i++) {
> +        sysbus_init_irq(sbd, &s->pci.irqs[i]);
> +    }
> +
> +    memory_region_init_io(&s->mmio,
> +                          OBJECT(s),
> +                          &designware_pci_mmio_ops,
> +                          s,
> +                          "pcie.reg", 4 * 1024);
> +    sysbus_init_mmio(sbd, &s->mmio);
> +
> +    memory_region_init(&s->pci.io, OBJECT(s), "pcie-pio", 16);
> +    memory_region_init(&s->pci.memory, OBJECT(s),
> +                       "pcie-bus-memory",
> +                       UINT64_MAX);
> +
> +    pci->bus = pci_register_root_bus(dev, "pcie",
> +                                     designware_pcie_set_irq,
> +                                     pci_swizzle_map_irq_fn,
> +                                     s,
> +                                     &s->pci.memory,
> +                                     &s->pci.io,
> +                                     0, 4,
> +                                     TYPE_PCIE_BUS);
> +
> +    memory_region_init(&s->pci.address_space_root,
> +                       OBJECT(s),
> +                       "pcie-bus-address-space-root",
> +                       UINT64_MAX);
> +    memory_region_add_subregion(&s->pci.address_space_root,
> +                                0x0, &s->pci.memory);
> +    address_space_init(&s->pci.address_space,
> +                       &s->pci.address_space_root,
> +                       "pcie-bus-address-space");
> +    pci_setup_iommu(pci->bus, designware_pcie_host_set_iommu, s);
> +
> +    qdev_set_parent_bus(DEVICE(&s->root), BUS(pci->bus));
> +    qdev_init_nofail(DEVICE(&s->root));
> +}
> +
> +static const VMStateDescription vmstate_designware_pcie_host = {
> +    .name = "designware-pcie-host",
> +    .version_id = 1,
> +    .minimum_version_id = 1,
> +    .fields = (VMStateField[]) {
> +        VMSTATE_STRUCT(root,
> +                       DesignwarePCIEHost,
> +                       1,
> +                       vmstate_designware_pcie_root,
> +                       DesignwarePCIERoot),
> +        VMSTATE_END_OF_LIST()
> +    }
> +};
> +
> +static void designware_pcie_host_class_init(ObjectClass *klass, void *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +    PCIHostBridgeClass *hc = PCI_HOST_BRIDGE_CLASS(klass);
> +
> +    hc->root_bus_path = designware_pcie_host_root_bus_path;
> +    dc->realize = designware_pcie_host_realize;
> +    set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories);
> +    dc->fw_name = "pci";
> +    dc->vmsd = &vmstate_designware_pcie_host;
> +}
> +
> +static void designware_pcie_host_init(Object *obj)
> +{
> +    DesignwarePCIEHost *s = DESIGNWARE_PCIE_HOST(obj);
> +    DesignwarePCIERoot *root = &s->root;
> +
> +    object_initialize(root, sizeof(*root), TYPE_DESIGNWARE_PCIE_ROOT);
> +    object_property_add_child(obj, "root", OBJECT(root), NULL);
> +    qdev_prop_set_int32(DEVICE(root), "addr", PCI_DEVFN(0, 0));
> +    qdev_prop_set_bit(DEVICE(root), "multifunction", false);
> +}
> +
> +static const TypeInfo designware_pcie_root_info = {
> +    .name = TYPE_DESIGNWARE_PCIE_ROOT,
> +    .parent = TYPE_PCI_BRIDGE,
> +    .instance_size = sizeof(DesignwarePCIERoot),
> +    .class_init = designware_pcie_root_class_init,
> +    .interfaces = (InterfaceInfo[]) {
> +        { INTERFACE_PCIE_DEVICE },
> +        { }
> +    },
> +};
> +
> +static const TypeInfo designware_pcie_host_info = {
> +    .name       = TYPE_DESIGNWARE_PCIE_HOST,
> +    .parent     = TYPE_PCI_HOST_BRIDGE,
> +    .instance_size = sizeof(DesignwarePCIEHost),
> +    .instance_init = designware_pcie_host_init,
> +    .class_init = designware_pcie_host_class_init,
> +};
> +
> +static void designware_pcie_register(void)
> +{
> +    type_register_static(&designware_pcie_root_info);
> +    type_register_static(&designware_pcie_host_info);
> +}
> +type_init(designware_pcie_register)
> diff --git a/include/hw/pci-host/designware.h b/include/hw/pci-host/designware.h
> new file mode 100644
> index 0000000000..63db9cdfbe
> --- /dev/null
> +++ b/include/hw/pci-host/designware.h
> @@ -0,0 +1,97 @@
> +/*
> + * Copyright (c) 2017, Impinj, Inc.
> + *
> + * Designware PCIe IP block emulation
> + *
> + * 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/>.
> + */
> +
> +#ifndef DESIGNWARE_H
> +#define DESIGNWARE_H
> +
> +#include "hw/hw.h"
> +#include "hw/sysbus.h"
> +#include "hw/pci/pci.h"
> +#include "hw/pci/pci_bus.h"
> +#include "hw/pci/pcie_host.h"
> +#include "hw/pci/pci_bridge.h"
> +
> +#define TYPE_DESIGNWARE_PCIE_HOST "designware-pcie-host"
> +#define DESIGNWARE_PCIE_HOST(obj) \
> +     OBJECT_CHECK(DesignwarePCIEHost, (obj), TYPE_DESIGNWARE_PCIE_HOST)
> +
> +#define TYPE_DESIGNWARE_PCIE_ROOT "designware-pcie-root"
> +#define DESIGNWARE_PCIE_ROOT(obj) \
> +     OBJECT_CHECK(DesignwarePCIERoot, (obj), TYPE_DESIGNWARE_PCIE_ROOT)
> +
> +typedef struct DesignwarePCIEViewport {
> +    MemoryRegion cfg;
> +    MemoryRegion mem;
> +
> +    uint64_t base;
> +    uint64_t target;
> +    uint32_t limit;
> +    uint32_t cr[2];
> +
> +    bool inbound;
> +} DesignwarePCIEViewport;
> +
> +typedef struct DesignwarePCIEMSIBank {
> +    uint32_t enable;
> +    uint32_t mask;
> +    uint32_t status;
> +} DesignwarePCIEMSIBank;
> +
> +typedef struct DesignwarePCIEMSI {
> +    uint64_t     base;
> +    MemoryRegion iomem;
> +
> +#define DESIGNWARE_PCIE_NUM_MSI_BANKS        1
> +
> +    DesignwarePCIEMSIBank intr[DESIGNWARE_PCIE_NUM_MSI_BANKS];
> +} DesignwarePCIEMSI;
> +
> +typedef struct DesignwarePCIERoot {
> +    PCIBridge parent_obj;
> +
> +    uint32_t atu_viewport;
> +
> +#define DESIGNWARE_PCIE_VIEWPORT_OUTBOUND    0
> +#define DESIGNWARE_PCIE_VIEWPORT_INBOUND     1
> +#define DESIGNWARE_PCIE_NUM_VIEWPORTS        4
> +
> +    DesignwarePCIEViewport viewports[2][DESIGNWARE_PCIE_NUM_VIEWPORTS];
> +    DesignwarePCIEMSI msi;
> +} DesignwarePCIERoot;
> +
> +typedef struct DesignwarePCIEHost {
> +    PCIHostState parent_obj;
> +
> +    DesignwarePCIERoot root;
> +
> +    struct {
> +        AddressSpace address_space;
> +        MemoryRegion address_space_root;
> +
> +        MemoryRegion memory;
> +        MemoryRegion io;
> +
> +        qemu_irq     irqs[4];
> +    } pci;
> +
> +    MemoryRegion mmio;
> +} DesignwarePCIEHost;
> +
> +#endif  /* DESIGNWARE_H */
> diff --git a/include/hw/pci/pci_ids.h b/include/hw/pci/pci_ids.h
> index 35df1874a9..23fefe1bc6 100644
> --- a/include/hw/pci/pci_ids.h
> +++ b/include/hw/pci/pci_ids.h
> @@ -266,4 +266,6 @@
>  #define PCI_VENDOR_ID_TEWS               0x1498
>  #define PCI_DEVICE_ID_TEWS_TPCI200       0x30C8
>  
> +#define PCI_VENDOR_ID_SYNOPSYS           0x16C3
> +
>  #endif
> -- 
> 2.14.3
Andrey Smirnov Feb. 8, 2018, 8:03 p.m. UTC | #2
On Thu, Feb 8, 2018 at 9:45 AM, Michael S. Tsirkin <mst@redhat.com> wrote:
> On Tue, Feb 06, 2018 at 08:24:34PM -0800, Andrey Smirnov wrote:
>> Add code needed to get a functional PCI subsytem when using in
>> conjunction with upstream Linux guest (4.13+). Tested to work against
>> "e1000e" (network adapter, using MSI interrupts) as well as
>> "usb-ehci" (USB controller, using legacy PCI interrupts).
>>
>> Based on "i.MX6 Applications Processor Reference Manual" (Document
>> Number: IMX6DQRM Rev. 4) as well as corresponding dirver in Linux
>> kernel (circa 4.13 - 4.16 found in drivers/pci/dwc/*)
>>
>> Cc: Peter Maydell <peter.maydell@linaro.org>
>> Cc: Jason Wang <jasowang@redhat.com>
>> Cc: Philippe Mathieu-Daudé <f4bug@amsat.org>
>> Cc: Marcel Apfelbaum <marcel.apfelbaum@zoho.com>
>> Cc: Michael S. Tsirkin <mst@redhat.com>
>> Cc: qemu-devel@nongnu.org
>> Cc: qemu-arm@nongnu.org
>> Cc: yurovsky@gmail.com
>> Signed-off-by: Andrey Smirnov <andrew.smirnov@gmail.com>
>> ---
>>  default-configs/arm-softmmu.mak  |   2 +
>>  hw/pci-host/Makefile.objs        |   2 +
>>  hw/pci-host/designware.c         | 759 +++++++++++++++++++++++++++++++++++++++
>>  include/hw/pci-host/designware.h |  97 +++++
>>  include/hw/pci/pci_ids.h         |   2 +
>>  5 files changed, 862 insertions(+)
>>  create mode 100644 hw/pci-host/designware.c
>>  create mode 100644 include/hw/pci-host/designware.h
>>
>> diff --git a/default-configs/arm-softmmu.mak b/default-configs/arm-softmmu.mak
>> index b0d6e65038..0c5ae914ed 100644
>> --- a/default-configs/arm-softmmu.mak
>> +++ b/default-configs/arm-softmmu.mak
>> @@ -132,3 +132,5 @@ CONFIG_GPIO_KEY=y
>>  CONFIG_MSF2=y
>>  CONFIG_FW_CFG_DMA=y
>>  CONFIG_XILINX_AXI=y
>> +CONFIG_PCI_DESIGNWARE=y
>> +
>> diff --git a/hw/pci-host/Makefile.objs b/hw/pci-host/Makefile.objs
>> index 4b69f737b5..6d6597c065 100644
>> --- a/hw/pci-host/Makefile.objs
>> +++ b/hw/pci-host/Makefile.objs
>> @@ -17,3 +17,5 @@ common-obj-$(CONFIG_PCI_PIIX) += piix.o
>>  common-obj-$(CONFIG_PCI_Q35) += q35.o
>>  common-obj-$(CONFIG_PCI_GENERIC) += gpex.o
>>  common-obj-$(CONFIG_PCI_XILINX) += xilinx-pcie.o
>> +
>> +common-obj-$(CONFIG_PCI_DESIGNWARE) += designware.o
>> diff --git a/hw/pci-host/designware.c b/hw/pci-host/designware.c
>> new file mode 100644
>> index 0000000000..551a881af0
>> --- /dev/null
>> +++ b/hw/pci-host/designware.c
>> @@ -0,0 +1,759 @@
>> +/*
>> + * Copyright (c) 2018, Impinj, Inc.
>> + *
>> + * Designware PCIe IP block emulation
>> + *
>> + * 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 "qemu/osdep.h"
>> +#include "qapi/error.h"
>> +#include "hw/pci/msi.h"
>> +#include "hw/pci/pci_bridge.h"
>> +#include "hw/pci/pci_host.h"
>> +#include "hw/pci/pcie_port.h"
>> +#include "hw/pci-host/designware.h"
>> +
>> +#define PCIE_PORT_LINK_CONTROL          0x710
>> +
>> +#define PCIE_PHY_DEBUG_R1               0x72C
>> +#define PCIE_PHY_DEBUG_R1_XMLH_LINK_UP  BIT(4)
>> +
>> +#define PCIE_LINK_WIDTH_SPEED_CONTROL   0x80C
>> +#define PORT_LOGIC_SPEED_CHANGE         (0x1 << 17)
>> +
>> +#define PCIE_MSI_ADDR_LO                0x820
>> +#define PCIE_MSI_ADDR_HI                0x824
>> +#define PCIE_MSI_INTR0_ENABLE           0x828
>> +#define PCIE_MSI_INTR0_MASK             0x82C
>> +#define PCIE_MSI_INTR0_STATUS           0x830
>> +
>> +#define PCIE_ATU_VIEWPORT               0x900
>> +#define PCIE_ATU_REGION_INBOUND         (0x1 << 31)
>> +#define PCIE_ATU_REGION_OUTBOUND        (0x0 << 31)
>> +#define PCIE_ATU_REGION_INDEX2          (0x2 << 0)
>> +#define PCIE_ATU_REGION_INDEX1          (0x1 << 0)
>> +#define PCIE_ATU_REGION_INDEX0          (0x0 << 0)
>> +#define PCIE_ATU_CR1                    0x904
>> +#define PCIE_ATU_TYPE_MEM               (0x0 << 0)
>> +#define PCIE_ATU_TYPE_IO                (0x2 << 0)
>> +#define PCIE_ATU_TYPE_CFG0              (0x4 << 0)
>> +#define PCIE_ATU_TYPE_CFG1              (0x5 << 0)
>> +#define PCIE_ATU_CR2                    0x908
>> +#define PCIE_ATU_ENABLE                 (0x1 << 31)
>> +#define PCIE_ATU_BAR_MODE_ENABLE        (0x1 << 30)
>> +#define PCIE_ATU_LOWER_BASE             0x90C
>> +#define PCIE_ATU_UPPER_BASE             0x910
>> +#define PCIE_ATU_LIMIT                  0x914
>> +#define PCIE_ATU_LOWER_TARGET           0x918
>> +#define PCIE_ATU_BUS(x)                 (((x) >> 24) & 0xff)
>> +#define PCIE_ATU_DEVFN(x)               (((x) >> 16) & 0xff)
>> +#define PCIE_ATU_UPPER_TARGET           0x91C
>> +
>> +static DesignwarePCIEHost *
>> +designware_pcie_root_to_host(DesignwarePCIERoot *root)
>> +{
>> +    BusState *bus = qdev_get_parent_bus(DEVICE(root));
>> +    return DESIGNWARE_PCIE_HOST(bus->parent);
>> +}
>> +
>> +static void designware_pcie_root_msi_write(void *opaque, hwaddr addr,
>> +                                           uint64_t val, unsigned len)
>> +{
>> +    DesignwarePCIERoot *root = DESIGNWARE_PCIE_ROOT(opaque);
>> +    DesignwarePCIEHost *host = designware_pcie_root_to_host(root);
>> +
>> +    root->msi.intr[0].status |= BIT(val) & root->msi.intr[0].enable;
>> +
>> +    if (root->msi.intr[0].status & ~root->msi.intr[0].mask) {
>> +        qemu_set_irq(host->pci.irqs[0], 1);
>> +    }
>> +}
>> +
>> +static const MemoryRegionOps designware_pci_host_msi_ops = {
>> +    .write = designware_pcie_root_msi_write,
>> +    .endianness = DEVICE_NATIVE_ENDIAN,
>
>
> Native endian normally means you need to do byteswaps yourself.
> I don't see any here which looks suspicious.
>

Yeah, I think I get away with it by running a little-endian machine. Will fix.

>> +    .valid = {
>> +        .min_access_size = 4,
>> +        .max_access_size = 4,
>> +    },
>> +};
>> +
>> +static void designware_pcie_root_update_msi_mapping(DesignwarePCIERoot *root)
>> +
>> +{
>> +    MemoryRegion *mem   = &root->msi.iomem;
>> +    const uint64_t base = root->msi.base;
>> +    const bool enable   = root->msi.intr[0].enable;
>> +
>> +    memory_region_set_address(mem, base);
>> +    memory_region_set_enabled(mem, enable);
>> +}
>> +
>> +static DesignwarePCIEViewport *
>> +designware_pcie_root_get_current_viewport(DesignwarePCIERoot *root)
>> +{
>> +    const unsigned int idx = root->atu_viewport & 0xF;
>> +    const unsigned int dir = !!(root->atu_viewport & PCIE_ATU_REGION_INBOUND);
>> +    return &root->viewports[dir][idx];
>> +}
>> +
>> +static uint32_t
>> +designware_pcie_root_config_read(PCIDevice *d, uint32_t address, int len)
>> +{
>> +    DesignwarePCIERoot *root = DESIGNWARE_PCIE_ROOT(d);
>> +    DesignwarePCIEViewport *viewport =
>> +        designware_pcie_root_get_current_viewport(root);
>> +
>> +    uint32_t val;
>> +
>> +    switch (address) {
>> +    case PCIE_PORT_LINK_CONTROL:
>> +        /*
>> +         * Linux guest uses this register only to configure number of
>> +         * PCIE lane (which in our case is irrelevant) and doesn't
>> +         * really care about the value it reads from this register
>> +         */
>> +        val = 0xDEADBEEF;
>> +        break;
>> +
>> +    case PCIE_LINK_WIDTH_SPEED_CONTROL:
>> +        /*
>> +         * To make sure that any code in guest waiting for speed
>> +         * change does not time out we always report
>> +         * PORT_LOGIC_SPEED_CHANGE as set
>> +         */
>> +        val = PORT_LOGIC_SPEED_CHANGE;
>> +        break;
>> +
>> +    case PCIE_MSI_ADDR_LO:
>> +        val = root->msi.base;
>> +        break;
>> +
>> +    case PCIE_MSI_ADDR_HI:
>> +        val = root->msi.base >> 32;
>> +        break;
>> +
>> +    case PCIE_MSI_INTR0_ENABLE:
>> +        val = root->msi.intr[0].enable;
>> +        break;
>> +
>> +    case PCIE_MSI_INTR0_MASK:
>> +        val = root->msi.intr[0].mask;
>> +        break;
>> +
>> +    case PCIE_MSI_INTR0_STATUS:
>> +        val = root->msi.intr[0].status;
>> +        break;
>> +
>> +    case PCIE_PHY_DEBUG_R1:
>> +        val = PCIE_PHY_DEBUG_R1_XMLH_LINK_UP;
>> +        break;
>> +
>> +    case PCIE_ATU_VIEWPORT:
>> +        val = root->atu_viewport;
>> +        break;
>> +
>> +    case PCIE_ATU_LOWER_BASE:
>> +        val = viewport->base;
>> +        break;
>> +
>> +    case PCIE_ATU_UPPER_BASE:
>> +        val = viewport->base >> 32;
>> +        break;
>> +
>> +    case PCIE_ATU_LOWER_TARGET:
>> +        val = viewport->target;
>> +        break;
>> +
>> +    case PCIE_ATU_UPPER_TARGET:
>> +        val = viewport->target >> 32;
>> +        break;
>> +
>> +    case PCIE_ATU_LIMIT:
>> +        val = viewport->limit;
>> +        break;
>> +
>> +    case PCIE_ATU_CR1:
>> +    case PCIE_ATU_CR2:          /* FALLTHROUGH */
>> +        val = viewport->cr[(address - PCIE_ATU_CR1) / sizeof(uint32_t)];
>> +        break;
>> +
>> +    default:
>> +        val = pci_default_read_config(d, address, len);
>> +        break;
>> +    }
>> +
>> +    return val;
>> +}
>> +
>> +static uint64_t designware_pcie_root_data_access(void *opaque, hwaddr addr,
>> +                                                 uint64_t *val, unsigned len)
>> +{
>> +    DesignwarePCIEViewport *viewport;
>> +    DesignwarePCIERoot *root;
>> +    PCIBus *pcibus;
>> +
>> +    root     = DESIGNWARE_PCIE_ROOT(opaque);
>> +    viewport = designware_pcie_root_get_current_viewport(root);
>> +    pcibus   = pci_get_bus(PCI_DEVICE(root));
>> +
>> +    addr &= PCIE_CONFIG_SPACE_SIZE - 1;
>> +    addr |= PCIE_ATU_BUS(viewport->target) << 16;
>> +    addr |= PCIE_ATU_DEVFN(viewport->target) << 8;
>> +
>> +    if (val) {
>> +        pci_data_write(pcibus, addr, *val, len);
>> +        return 0;
>> +    }
>> +
>> +    return pci_data_read(pcibus, addr, len);
>> +}
>> +
>> +static uint64_t designware_pcie_root_data_read(void *opaque, hwaddr addr,
>> +                                               unsigned len)
>> +{
>> +    return designware_pcie_root_data_access(opaque, addr, NULL, len);
>> +}
>> +
>> +static void designware_pcie_root_data_write(void *opaque, hwaddr addr,
>> +                                            uint64_t val, unsigned len)
>> +{
>> +    designware_pcie_root_data_access(opaque, addr, &val, len);
>> +}
>> +
>> +static const MemoryRegionOps designware_pci_host_conf_ops = {
>> +    .read = designware_pcie_root_data_read,
>> +    .write = designware_pcie_root_data_write,
>> +    .endianness = DEVICE_NATIVE_ENDIAN,
>
> Native endian is usually not what's needed for device emulation.
>

Same here. Will fix.

>
>> +    .valid = {
>> +        .min_access_size = 1,
>> +        .max_access_size = 4,
>> +    },
>> +};
>> +
>> +static void designware_pcie_update_viewport(DesignwarePCIERoot *root,
>> +                                            DesignwarePCIEViewport *viewport)
>> +{
>> +    const uint64_t target = viewport->target;
>> +    const uint64_t base   = viewport->base;
>> +    const uint64_t size   = (uint64_t)viewport->limit - base + 1;
>> +    const bool enabled    = viewport->cr[1] & PCIE_ATU_ENABLE;
>> +
>> +    MemoryRegion *current, *other;
>> +
>> +    if (viewport->cr[0] == PCIE_ATU_TYPE_MEM) {
>> +        current = &viewport->mem;
>> +        other   = &viewport->cfg;
>> +        memory_region_set_alias_offset(current, target);
>> +    } else {
>> +        current = &viewport->cfg;
>> +        other   = &viewport->mem;
>> +    }
>> +
>> +    /*
>> +     * An outbound viewport can be reconfigure from being MEM to CFG,
>> +     * to account for that we disable the "other" memory region that
>> +     * becomes unused due to that fact.
>> +     */
>> +    memory_region_set_enabled(other, false);
>> +    if (enabled) {
>> +        memory_region_set_size(current, size);
>> +        memory_region_set_address(current, base);
>> +    }
>> +    memory_region_set_enabled(current, enabled);
>> +}
>> +
>> +static void designware_pcie_root_config_write(PCIDevice *d, uint32_t address,
>> +                                              uint32_t val, int len)
>> +{
>> +    DesignwarePCIERoot *root = DESIGNWARE_PCIE_ROOT(d);
>> +    DesignwarePCIEHost *host = designware_pcie_root_to_host(root);
>> +    DesignwarePCIEViewport *viewport =
>> +        designware_pcie_root_get_current_viewport(root);
>> +
>> +    switch (address) {
>> +    case PCIE_PORT_LINK_CONTROL:
>> +    case PCIE_LINK_WIDTH_SPEED_CONTROL:
>> +    case PCIE_PHY_DEBUG_R1:
>> +        /* No-op */
>> +        break;
>> +
>> +    case PCIE_MSI_ADDR_LO:
>> +        root->msi.base &= 0xFFFFFFFF00000000ULL;
>> +        root->msi.base |= val;
>> +        break;
>> +
>> +    case PCIE_MSI_ADDR_HI:
>> +        root->msi.base &= 0x00000000FFFFFFFFULL;
>> +        root->msi.base |= (uint64_t)val << 32;
>> +        break;
>> +
>> +    case PCIE_MSI_INTR0_ENABLE: {
>> +        const bool update_msi_mapping = !root->msi.intr[0].enable ^ !!val;
>> +
>> +        root->msi.intr[0].enable = val;
>> +
>> +        if (update_msi_mapping) {
>> +            designware_pcie_root_update_msi_mapping(root);
>> +        }
>> +        break;
>> +    }
>> +
>> +    case PCIE_MSI_INTR0_MASK:
>> +        root->msi.intr[0].mask = val;
>> +        break;
>> +
>> +    case PCIE_MSI_INTR0_STATUS:
>> +        root->msi.intr[0].status ^= val;
>> +        if (!root->msi.intr[0].status) {
>> +            qemu_set_irq(host->pci.irqs[0], 0);
>> +        }
>> +        break;
>> +
>> +    case PCIE_ATU_VIEWPORT:
>> +        root->atu_viewport = val;
>> +        break;
>> +
>> +    case PCIE_ATU_LOWER_BASE:
>> +        viewport->base &= 0xFFFFFFFF00000000ULL;
>> +        viewport->base |= val;
>> +        break;
>> +
>> +    case PCIE_ATU_UPPER_BASE:
>> +        viewport->base &= 0x00000000FFFFFFFFULL;
>> +        viewport->base |= (uint64_t)val << 32;
>> +        break;
>> +
>> +    case PCIE_ATU_LOWER_TARGET:
>> +        viewport->target &= 0xFFFFFFFF00000000ULL;
>> +        viewport->target |= val;
>> +        break;
>> +
>> +    case PCIE_ATU_UPPER_TARGET:
>> +        viewport->target &= 0x00000000FFFFFFFFULL;
>> +        viewport->target |= val;
>> +        break;
>> +
>> +    case PCIE_ATU_LIMIT:
>> +        viewport->limit = val;
>> +        break;
>> +
>> +    case PCIE_ATU_CR1:
>> +        viewport->cr[0] = val;
>> +        break;
>> +    case PCIE_ATU_CR2:
>> +        viewport->cr[1] = val;
>> +        designware_pcie_update_viewport(root, viewport);
>> +        break;
>> +
>> +    default:
>> +        pci_bridge_write_config(d, address, val, len);
>> +        break;
>> +    }
>> +}
>> +
>> +static char *designware_pcie_viewport_name(const char *direction,
>> +                                           unsigned int i,
>> +                                           const char *type)
>> +{
>> +    return g_strdup_printf("PCI %s Viewport %u [%s]",
>> +                           direction, i, type);
>> +}
>> +
>> +static void designware_pcie_root_realize(PCIDevice *dev, Error **errp)
>> +{
>> +    DesignwarePCIERoot *root = DESIGNWARE_PCIE_ROOT(dev);
>> +    DesignwarePCIEHost *host = designware_pcie_root_to_host(root);
>> +    MemoryRegion *address_space = &host->pci.memory;
>> +    PCIBridge *br = PCI_BRIDGE(dev);
>> +    DesignwarePCIEViewport *viewport;
>> +    /*
>> +     * Dummy values used for initial configuration of MemoryRegions
>> +     * that belong to a give viewport
>> +     */
>> +    const hwaddr dummy_offset = 0;
>> +    const uint64_t dummy_size = 4;
>> +    size_t i;
>> +
>> +    br->bus_name  = "dw-pcie";
>> +
>> +    pci_set_word(dev->config + PCI_COMMAND,
>> +                 PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER);
>> +
>> +    pci_config_set_interrupt_pin(dev->config, 1);
>> +    pci_bridge_initfn(dev, TYPE_PCIE_BUS);
>> +
>> +    pcie_port_init_reg(dev);
>> +
>> +    pcie_cap_init(dev, 0x70, PCI_EXP_TYPE_ROOT_PORT,
>> +                  0, &error_fatal);
>> +
>> +    msi_nonbroken = true;
>> +    msi_init(dev, 0x50, 32, true, true, &error_fatal);
>> +
>> +    for (i = 0; i < DESIGNWARE_PCIE_NUM_VIEWPORTS; i++) {
>> +        MemoryRegion *source, *destination, *mem;
>> +        const char *direction;
>> +        char *name;
>> +
>> +        viewport = &root->viewports[DESIGNWARE_PCIE_VIEWPORT_INBOUND][i];
>> +        viewport->inbound = true;
>> +        viewport->base    = 0x0000000000000000ULL;
>> +        viewport->target  = 0x0000000000000000ULL;
>> +        viewport->limit   = UINT32_MAX;
>> +        viewport->cr[0]   = PCIE_ATU_TYPE_MEM;
>> +
>> +        source      = &host->pci.address_space_root;
>> +        destination = get_system_memory();
>> +        direction   = "Inbound";
>> +
>> +        /*
>> +         * Configure MemoryRegion implementing PCI -> CPU memory
>> +         * access
>> +         */
>> +        mem  = &viewport->mem;
>> +        name = designware_pcie_viewport_name(direction, i, "MEM");
>> +        memory_region_init_alias(mem, OBJECT(root), name, destination,
>> +                                 dummy_offset, dummy_size);
>> +        memory_region_add_subregion_overlap(source, dummy_offset, mem, -1);
>> +        memory_region_set_enabled(mem, false);
>> +        g_free(name);
>> +
>> +        viewport = &root->viewports[DESIGNWARE_PCIE_VIEWPORT_OUTBOUND][i];
>> +        viewport->inbound = false;
>> +        viewport->base    = 0x0000000000000000ULL;
>> +        viewport->target  = 0x0000000000000000ULL;
>> +        viewport->limit   = UINT32_MAX;
>> +        viewport->cr[0]   = PCIE_ATU_TYPE_MEM;
>> +
>> +        destination = &host->pci.memory;
>> +        direction   = "Outbound";
>> +        source      = get_system_memory();
>> +
>> +        /*
>> +         * Configure MemoryRegion implementing CPU -> PCI memory
>> +         * access
>> +         */
>> +        mem  = &viewport->mem;
>> +        name = designware_pcie_viewport_name(direction, i, "MEM");
>> +        memory_region_init_alias(mem, OBJECT(root), name, destination,
>> +                                 dummy_offset, dummy_size);
>> +        memory_region_add_subregion(source, dummy_offset, mem);
>> +        memory_region_set_enabled(mem, false);
>> +        g_free(name);
>> +
>> +        /*
>> +         * Configure MemoryRegion implementing access to configuration
>> +         * space
>> +         */
>> +        mem  = &viewport->cfg;
>> +        name = designware_pcie_viewport_name(direction, i, "CFG");
>> +        memory_region_init_io(&viewport->cfg, OBJECT(root),
>> +                              &designware_pci_host_conf_ops,
>> +                              root, name, dummy_size);
>> +        memory_region_add_subregion(source, dummy_offset, mem);
>> +        memory_region_set_enabled(mem, false);
>> +        g_free(name);
>
> This implements the MMCFG, doesn't it?
> I suspect you need pcie_host_mmcfg_update instead of rolling
> your own.

AFAIU, not quite. MMCFG embeds information about which bus/function to
access into address it is mapped into CPU address space (A/S), whereas
for this controller those concerns are separate and "viewport->base"
determines the placement within CPU A/S and "viewport->target" is used
to figure out which bus/function to access.

Now that've run into problems with my patch for pci_data_*, I am
inclined to revert back to my original implementation of configuration
space accessors, something along the lines of:

static uint64_t designware_pcie_root_data_read(void *opaque,
                                               hwaddr addr, unsigned len)
{
    DesignwarePCIERoot *root = DESIGNWARE_PCIE_ROOT(opaque);
    DesignwarePCIEViewport *viewport =
        designware_pcie_root_get_current_viewport(root);

    const uint8_t busnum = PCIE_ATU_BUS(viewport->target);
    const uint8_t devfn  = PCIE_ATU_DEVFN(viewport->target);
    PCIBus    *pcibus    = pci_get_bus(PCI_DEVICE(root));
    PCIDevice *pcidev    = pci_find_device(pcibus, busnum, devfn);

    if (pcidev) {
        addr &= pci_config_size(pcidev) - 1;

        return pci_host_config_read_common(pcidev, addr,
                                           pci_config_size(pcidev), len);
    }

    return UINT64_MAX;
}

does that sound reasonable?

Thanks,
Andrey Smirnov
Michael S. Tsirkin Feb. 8, 2018, 8:11 p.m. UTC | #3
On Thu, Feb 08, 2018 at 12:03:04PM -0800, Andrey Smirnov wrote:
> >> +#define PCIE_PORT_LINK_CONTROL          0x710
> >> +
> >> +#define PCIE_PHY_DEBUG_R1               0x72C
> >> +#define PCIE_PHY_DEBUG_R1_XMLH_LINK_UP  BIT(4)
> >> +
> >> +#define PCIE_LINK_WIDTH_SPEED_CONTROL   0x80C
> >> +#define PORT_LOGIC_SPEED_CHANGE         (0x1 << 17)
> >> +
> >> +#define PCIE_MSI_ADDR_LO                0x820
> >> +#define PCIE_MSI_ADDR_HI                0x824
> >> +#define PCIE_MSI_INTR0_ENABLE           0x828
> >> +#define PCIE_MSI_INTR0_MASK             0x82C
> >> +#define PCIE_MSI_INTR0_STATUS           0x830
> >> +
> >> +#define PCIE_ATU_VIEWPORT               0x900
> >> +#define PCIE_ATU_REGION_INBOUND         (0x1 << 31)
> >> +#define PCIE_ATU_REGION_OUTBOUND        (0x0 << 31)
> >> +#define PCIE_ATU_REGION_INDEX2          (0x2 << 0)
> >> +#define PCIE_ATU_REGION_INDEX1          (0x1 << 0)
> >> +#define PCIE_ATU_REGION_INDEX0          (0x0 << 0)
> >> +#define PCIE_ATU_CR1                    0x904
> >> +#define PCIE_ATU_TYPE_MEM               (0x0 << 0)
> >> +#define PCIE_ATU_TYPE_IO                (0x2 << 0)
> >> +#define PCIE_ATU_TYPE_CFG0              (0x4 << 0)
> >> +#define PCIE_ATU_TYPE_CFG1              (0x5 << 0)
> >> +#define PCIE_ATU_CR2                    0x908
> >> +#define PCIE_ATU_ENABLE                 (0x1 << 31)
> >> +#define PCIE_ATU_BAR_MODE_ENABLE        (0x1 << 30)
> >> +#define PCIE_ATU_LOWER_BASE             0x90C
> >> +#define PCIE_ATU_UPPER_BASE             0x910
> >> +#define PCIE_ATU_LIMIT                  0x914
> >> +#define PCIE_ATU_LOWER_TARGET           0x918
> >> +#define PCIE_ATU_BUS(x)                 (((x) >> 24) & 0xff)
> >> +#define PCIE_ATU_DEVFN(x)               (((x) >> 16) & 0xff)
> >> +#define PCIE_ATU_UPPER_TARGET           0x91C

Can you avoid a PCIE prefix for this btw?

Thaks!
Andrey Smirnov Feb. 8, 2018, 8:22 p.m. UTC | #4
On Thu, Feb 8, 2018 at 12:11 PM, Michael S. Tsirkin <mst@redhat.com> wrote:
> On Thu, Feb 08, 2018 at 12:03:04PM -0800, Andrey Smirnov wrote:
>> >> +#define PCIE_PORT_LINK_CONTROL          0x710
>> >> +
>> >> +#define PCIE_PHY_DEBUG_R1               0x72C
>> >> +#define PCIE_PHY_DEBUG_R1_XMLH_LINK_UP  BIT(4)
>> >> +
>> >> +#define PCIE_LINK_WIDTH_SPEED_CONTROL   0x80C
>> >> +#define PORT_LOGIC_SPEED_CHANGE         (0x1 << 17)
>> >> +
>> >> +#define PCIE_MSI_ADDR_LO                0x820
>> >> +#define PCIE_MSI_ADDR_HI                0x824
>> >> +#define PCIE_MSI_INTR0_ENABLE           0x828
>> >> +#define PCIE_MSI_INTR0_MASK             0x82C
>> >> +#define PCIE_MSI_INTR0_STATUS           0x830
>> >> +
>> >> +#define PCIE_ATU_VIEWPORT               0x900
>> >> +#define PCIE_ATU_REGION_INBOUND         (0x1 << 31)
>> >> +#define PCIE_ATU_REGION_OUTBOUND        (0x0 << 31)
>> >> +#define PCIE_ATU_REGION_INDEX2          (0x2 << 0)
>> >> +#define PCIE_ATU_REGION_INDEX1          (0x1 << 0)
>> >> +#define PCIE_ATU_REGION_INDEX0          (0x0 << 0)
>> >> +#define PCIE_ATU_CR1                    0x904
>> >> +#define PCIE_ATU_TYPE_MEM               (0x0 << 0)
>> >> +#define PCIE_ATU_TYPE_IO                (0x2 << 0)
>> >> +#define PCIE_ATU_TYPE_CFG0              (0x4 << 0)
>> >> +#define PCIE_ATU_TYPE_CFG1              (0x5 << 0)
>> >> +#define PCIE_ATU_CR2                    0x908
>> >> +#define PCIE_ATU_ENABLE                 (0x1 << 31)
>> >> +#define PCIE_ATU_BAR_MODE_ENABLE        (0x1 << 30)
>> >> +#define PCIE_ATU_LOWER_BASE             0x90C
>> >> +#define PCIE_ATU_UPPER_BASE             0x910
>> >> +#define PCIE_ATU_LIMIT                  0x914
>> >> +#define PCIE_ATU_LOWER_TARGET           0x918
>> >> +#define PCIE_ATU_BUS(x)                 (((x) >> 24) & 0xff)
>> >> +#define PCIE_ATU_DEVFN(x)               (((x) >> 16) & 0xff)
>> >> +#define PCIE_ATU_UPPER_TARGET           0x91C
>
> Can you avoid a PCIE prefix for this btw?
>

That's how those constants were named in Linux kernel, but yeah, I'll
add a prefix to them.

Thanks,
Andrey Smirnov
Michael S. Tsirkin Feb. 8, 2018, 8:33 p.m. UTC | #5
On Thu, Feb 08, 2018 at 12:22:53PM -0800, Andrey Smirnov wrote:
> On Thu, Feb 8, 2018 at 12:11 PM, Michael S. Tsirkin <mst@redhat.com> wrote:
> > On Thu, Feb 08, 2018 at 12:03:04PM -0800, Andrey Smirnov wrote:
> >> >> +#define PCIE_PORT_LINK_CONTROL          0x710
> >> >> +
> >> >> +#define PCIE_PHY_DEBUG_R1               0x72C
> >> >> +#define PCIE_PHY_DEBUG_R1_XMLH_LINK_UP  BIT(4)
> >> >> +
> >> >> +#define PCIE_LINK_WIDTH_SPEED_CONTROL   0x80C
> >> >> +#define PORT_LOGIC_SPEED_CHANGE         (0x1 << 17)
> >> >> +
> >> >> +#define PCIE_MSI_ADDR_LO                0x820
> >> >> +#define PCIE_MSI_ADDR_HI                0x824
> >> >> +#define PCIE_MSI_INTR0_ENABLE           0x828
> >> >> +#define PCIE_MSI_INTR0_MASK             0x82C
> >> >> +#define PCIE_MSI_INTR0_STATUS           0x830
> >> >> +
> >> >> +#define PCIE_ATU_VIEWPORT               0x900
> >> >> +#define PCIE_ATU_REGION_INBOUND         (0x1 << 31)
> >> >> +#define PCIE_ATU_REGION_OUTBOUND        (0x0 << 31)
> >> >> +#define PCIE_ATU_REGION_INDEX2          (0x2 << 0)
> >> >> +#define PCIE_ATU_REGION_INDEX1          (0x1 << 0)
> >> >> +#define PCIE_ATU_REGION_INDEX0          (0x0 << 0)
> >> >> +#define PCIE_ATU_CR1                    0x904
> >> >> +#define PCIE_ATU_TYPE_MEM               (0x0 << 0)
> >> >> +#define PCIE_ATU_TYPE_IO                (0x2 << 0)
> >> >> +#define PCIE_ATU_TYPE_CFG0              (0x4 << 0)
> >> >> +#define PCIE_ATU_TYPE_CFG1              (0x5 << 0)
> >> >> +#define PCIE_ATU_CR2                    0x908
> >> >> +#define PCIE_ATU_ENABLE                 (0x1 << 31)
> >> >> +#define PCIE_ATU_BAR_MODE_ENABLE        (0x1 << 30)
> >> >> +#define PCIE_ATU_LOWER_BASE             0x90C
> >> >> +#define PCIE_ATU_UPPER_BASE             0x910
> >> >> +#define PCIE_ATU_LIMIT                  0x914
> >> >> +#define PCIE_ATU_LOWER_TARGET           0x918
> >> >> +#define PCIE_ATU_BUS(x)                 (((x) >> 24) & 0xff)
> >> >> +#define PCIE_ATU_DEVFN(x)               (((x) >> 16) & 0xff)
> >> >> +#define PCIE_ATU_UPPER_TARGET           0x91C
> >
> > Can you avoid a PCIE prefix for this btw?
> >
> 
> That's how those constants were named in Linux kernel, but yeah, I'll
> add a prefix to them.
> 
> Thanks,
> Andrey Smirnov

In that case you should not copy it into your file.
Stuff from linux kernel should be imported into standard-headers.
Andrey Smirnov Feb. 8, 2018, 8:43 p.m. UTC | #6
On Thu, Feb 8, 2018 at 12:33 PM, Michael S. Tsirkin <mst@redhat.com> wrote:
> On Thu, Feb 08, 2018 at 12:22:53PM -0800, Andrey Smirnov wrote:
>> On Thu, Feb 8, 2018 at 12:11 PM, Michael S. Tsirkin <mst@redhat.com> wrote:
>> > On Thu, Feb 08, 2018 at 12:03:04PM -0800, Andrey Smirnov wrote:
>> >> >> +#define PCIE_PORT_LINK_CONTROL          0x710
>> >> >> +
>> >> >> +#define PCIE_PHY_DEBUG_R1               0x72C
>> >> >> +#define PCIE_PHY_DEBUG_R1_XMLH_LINK_UP  BIT(4)
>> >> >> +
>> >> >> +#define PCIE_LINK_WIDTH_SPEED_CONTROL   0x80C
>> >> >> +#define PORT_LOGIC_SPEED_CHANGE         (0x1 << 17)
>> >> >> +
>> >> >> +#define PCIE_MSI_ADDR_LO                0x820
>> >> >> +#define PCIE_MSI_ADDR_HI                0x824
>> >> >> +#define PCIE_MSI_INTR0_ENABLE           0x828
>> >> >> +#define PCIE_MSI_INTR0_MASK             0x82C
>> >> >> +#define PCIE_MSI_INTR0_STATUS           0x830
>> >> >> +
>> >> >> +#define PCIE_ATU_VIEWPORT               0x900
>> >> >> +#define PCIE_ATU_REGION_INBOUND         (0x1 << 31)
>> >> >> +#define PCIE_ATU_REGION_OUTBOUND        (0x0 << 31)
>> >> >> +#define PCIE_ATU_REGION_INDEX2          (0x2 << 0)
>> >> >> +#define PCIE_ATU_REGION_INDEX1          (0x1 << 0)
>> >> >> +#define PCIE_ATU_REGION_INDEX0          (0x0 << 0)
>> >> >> +#define PCIE_ATU_CR1                    0x904
>> >> >> +#define PCIE_ATU_TYPE_MEM               (0x0 << 0)
>> >> >> +#define PCIE_ATU_TYPE_IO                (0x2 << 0)
>> >> >> +#define PCIE_ATU_TYPE_CFG0              (0x4 << 0)
>> >> >> +#define PCIE_ATU_TYPE_CFG1              (0x5 << 0)
>> >> >> +#define PCIE_ATU_CR2                    0x908
>> >> >> +#define PCIE_ATU_ENABLE                 (0x1 << 31)
>> >> >> +#define PCIE_ATU_BAR_MODE_ENABLE        (0x1 << 30)
>> >> >> +#define PCIE_ATU_LOWER_BASE             0x90C
>> >> >> +#define PCIE_ATU_UPPER_BASE             0x910
>> >> >> +#define PCIE_ATU_LIMIT                  0x914
>> >> >> +#define PCIE_ATU_LOWER_TARGET           0x918
>> >> >> +#define PCIE_ATU_BUS(x)                 (((x) >> 24) & 0xff)
>> >> >> +#define PCIE_ATU_DEVFN(x)               (((x) >> 16) & 0xff)
>> >> >> +#define PCIE_ATU_UPPER_TARGET           0x91C
>> >
>> > Can you avoid a PCIE prefix for this btw?
>> >
>>
>> That's how those constants were named in Linux kernel, but yeah, I'll
>> add a prefix to them.
>>
>> Thanks,
>> Andrey Smirnov
>
> In that case you should not copy it into your file.
> Stuff from linux kernel should be imported into standard-headers.
>

Just to be sure we are on the same page, this is the file I am talking about:

https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/pci/dwc/pcie-designware.h?h=v4.15

you want me to put it in standard-headers/linux and get those
constants from there?

Thanks,
Andrey Smirnov
Michael S. Tsirkin Feb. 9, 2018, 12:08 a.m. UTC | #7
On Thu, Feb 08, 2018 at 12:43:03PM -0800, Andrey Smirnov wrote:
> On Thu, Feb 8, 2018 at 12:33 PM, Michael S. Tsirkin <mst@redhat.com> wrote:
> > On Thu, Feb 08, 2018 at 12:22:53PM -0800, Andrey Smirnov wrote:
> >> On Thu, Feb 8, 2018 at 12:11 PM, Michael S. Tsirkin <mst@redhat.com> wrote:
> >> > On Thu, Feb 08, 2018 at 12:03:04PM -0800, Andrey Smirnov wrote:
> >> >> >> +#define PCIE_PORT_LINK_CONTROL          0x710
> >> >> >> +
> >> >> >> +#define PCIE_PHY_DEBUG_R1               0x72C
> >> >> >> +#define PCIE_PHY_DEBUG_R1_XMLH_LINK_UP  BIT(4)
> >> >> >> +
> >> >> >> +#define PCIE_LINK_WIDTH_SPEED_CONTROL   0x80C
> >> >> >> +#define PORT_LOGIC_SPEED_CHANGE         (0x1 << 17)
> >> >> >> +
> >> >> >> +#define PCIE_MSI_ADDR_LO                0x820
> >> >> >> +#define PCIE_MSI_ADDR_HI                0x824
> >> >> >> +#define PCIE_MSI_INTR0_ENABLE           0x828
> >> >> >> +#define PCIE_MSI_INTR0_MASK             0x82C
> >> >> >> +#define PCIE_MSI_INTR0_STATUS           0x830
> >> >> >> +
> >> >> >> +#define PCIE_ATU_VIEWPORT               0x900
> >> >> >> +#define PCIE_ATU_REGION_INBOUND         (0x1 << 31)
> >> >> >> +#define PCIE_ATU_REGION_OUTBOUND        (0x0 << 31)
> >> >> >> +#define PCIE_ATU_REGION_INDEX2          (0x2 << 0)
> >> >> >> +#define PCIE_ATU_REGION_INDEX1          (0x1 << 0)
> >> >> >> +#define PCIE_ATU_REGION_INDEX0          (0x0 << 0)
> >> >> >> +#define PCIE_ATU_CR1                    0x904
> >> >> >> +#define PCIE_ATU_TYPE_MEM               (0x0 << 0)
> >> >> >> +#define PCIE_ATU_TYPE_IO                (0x2 << 0)
> >> >> >> +#define PCIE_ATU_TYPE_CFG0              (0x4 << 0)
> >> >> >> +#define PCIE_ATU_TYPE_CFG1              (0x5 << 0)
> >> >> >> +#define PCIE_ATU_CR2                    0x908
> >> >> >> +#define PCIE_ATU_ENABLE                 (0x1 << 31)
> >> >> >> +#define PCIE_ATU_BAR_MODE_ENABLE        (0x1 << 30)
> >> >> >> +#define PCIE_ATU_LOWER_BASE             0x90C
> >> >> >> +#define PCIE_ATU_UPPER_BASE             0x910
> >> >> >> +#define PCIE_ATU_LIMIT                  0x914
> >> >> >> +#define PCIE_ATU_LOWER_TARGET           0x918
> >> >> >> +#define PCIE_ATU_BUS(x)                 (((x) >> 24) & 0xff)
> >> >> >> +#define PCIE_ATU_DEVFN(x)               (((x) >> 16) & 0xff)
> >> >> >> +#define PCIE_ATU_UPPER_TARGET           0x91C
> >> >
> >> > Can you avoid a PCIE prefix for this btw?
> >> >
> >>
> >> That's how those constants were named in Linux kernel, but yeah, I'll
> >> add a prefix to them.
> >>
> >> Thanks,
> >> Andrey Smirnov
> >
> > In that case you should not copy it into your file.
> > Stuff from linux kernel should be imported into standard-headers.
> >
> 
> Just to be sure we are on the same page, this is the file I am talking about:
> 
> https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/pci/dwc/pcie-designware.h?h=v4.15
> 
> you want me to put it in standard-headers/linux and get those
> constants from there?
> 
> Thanks,
> Andrey Smirnov

Oh it's from a source file. I see. standard-headers/linux is for
files exported from include/uapi.

I take it back then but pls rename appropriately - and I guess
you should only copy what you actually use.
diff mbox

Patch

diff --git a/default-configs/arm-softmmu.mak b/default-configs/arm-softmmu.mak
index b0d6e65038..0c5ae914ed 100644
--- a/default-configs/arm-softmmu.mak
+++ b/default-configs/arm-softmmu.mak
@@ -132,3 +132,5 @@  CONFIG_GPIO_KEY=y
 CONFIG_MSF2=y
 CONFIG_FW_CFG_DMA=y
 CONFIG_XILINX_AXI=y
+CONFIG_PCI_DESIGNWARE=y
+
diff --git a/hw/pci-host/Makefile.objs b/hw/pci-host/Makefile.objs
index 4b69f737b5..6d6597c065 100644
--- a/hw/pci-host/Makefile.objs
+++ b/hw/pci-host/Makefile.objs
@@ -17,3 +17,5 @@  common-obj-$(CONFIG_PCI_PIIX) += piix.o
 common-obj-$(CONFIG_PCI_Q35) += q35.o
 common-obj-$(CONFIG_PCI_GENERIC) += gpex.o
 common-obj-$(CONFIG_PCI_XILINX) += xilinx-pcie.o
+
+common-obj-$(CONFIG_PCI_DESIGNWARE) += designware.o
diff --git a/hw/pci-host/designware.c b/hw/pci-host/designware.c
new file mode 100644
index 0000000000..551a881af0
--- /dev/null
+++ b/hw/pci-host/designware.c
@@ -0,0 +1,759 @@ 
+/*
+ * Copyright (c) 2018, Impinj, Inc.
+ *
+ * Designware PCIe IP block emulation
+ *
+ * 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 "qemu/osdep.h"
+#include "qapi/error.h"
+#include "hw/pci/msi.h"
+#include "hw/pci/pci_bridge.h"
+#include "hw/pci/pci_host.h"
+#include "hw/pci/pcie_port.h"
+#include "hw/pci-host/designware.h"
+
+#define PCIE_PORT_LINK_CONTROL          0x710
+
+#define PCIE_PHY_DEBUG_R1               0x72C
+#define PCIE_PHY_DEBUG_R1_XMLH_LINK_UP  BIT(4)
+
+#define PCIE_LINK_WIDTH_SPEED_CONTROL   0x80C
+#define PORT_LOGIC_SPEED_CHANGE         (0x1 << 17)
+
+#define PCIE_MSI_ADDR_LO                0x820
+#define PCIE_MSI_ADDR_HI                0x824
+#define PCIE_MSI_INTR0_ENABLE           0x828
+#define PCIE_MSI_INTR0_MASK             0x82C
+#define PCIE_MSI_INTR0_STATUS           0x830
+
+#define PCIE_ATU_VIEWPORT               0x900
+#define PCIE_ATU_REGION_INBOUND         (0x1 << 31)
+#define PCIE_ATU_REGION_OUTBOUND        (0x0 << 31)
+#define PCIE_ATU_REGION_INDEX2          (0x2 << 0)
+#define PCIE_ATU_REGION_INDEX1          (0x1 << 0)
+#define PCIE_ATU_REGION_INDEX0          (0x0 << 0)
+#define PCIE_ATU_CR1                    0x904
+#define PCIE_ATU_TYPE_MEM               (0x0 << 0)
+#define PCIE_ATU_TYPE_IO                (0x2 << 0)
+#define PCIE_ATU_TYPE_CFG0              (0x4 << 0)
+#define PCIE_ATU_TYPE_CFG1              (0x5 << 0)
+#define PCIE_ATU_CR2                    0x908
+#define PCIE_ATU_ENABLE                 (0x1 << 31)
+#define PCIE_ATU_BAR_MODE_ENABLE        (0x1 << 30)
+#define PCIE_ATU_LOWER_BASE             0x90C
+#define PCIE_ATU_UPPER_BASE             0x910
+#define PCIE_ATU_LIMIT                  0x914
+#define PCIE_ATU_LOWER_TARGET           0x918
+#define PCIE_ATU_BUS(x)                 (((x) >> 24) & 0xff)
+#define PCIE_ATU_DEVFN(x)               (((x) >> 16) & 0xff)
+#define PCIE_ATU_UPPER_TARGET           0x91C
+
+static DesignwarePCIEHost *
+designware_pcie_root_to_host(DesignwarePCIERoot *root)
+{
+    BusState *bus = qdev_get_parent_bus(DEVICE(root));
+    return DESIGNWARE_PCIE_HOST(bus->parent);
+}
+
+static void designware_pcie_root_msi_write(void *opaque, hwaddr addr,
+                                           uint64_t val, unsigned len)
+{
+    DesignwarePCIERoot *root = DESIGNWARE_PCIE_ROOT(opaque);
+    DesignwarePCIEHost *host = designware_pcie_root_to_host(root);
+
+    root->msi.intr[0].status |= BIT(val) & root->msi.intr[0].enable;
+
+    if (root->msi.intr[0].status & ~root->msi.intr[0].mask) {
+        qemu_set_irq(host->pci.irqs[0], 1);
+    }
+}
+
+static const MemoryRegionOps designware_pci_host_msi_ops = {
+    .write = designware_pcie_root_msi_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+    },
+};
+
+static void designware_pcie_root_update_msi_mapping(DesignwarePCIERoot *root)
+
+{
+    MemoryRegion *mem   = &root->msi.iomem;
+    const uint64_t base = root->msi.base;
+    const bool enable   = root->msi.intr[0].enable;
+
+    memory_region_set_address(mem, base);
+    memory_region_set_enabled(mem, enable);
+}
+
+static DesignwarePCIEViewport *
+designware_pcie_root_get_current_viewport(DesignwarePCIERoot *root)
+{
+    const unsigned int idx = root->atu_viewport & 0xF;
+    const unsigned int dir = !!(root->atu_viewport & PCIE_ATU_REGION_INBOUND);
+    return &root->viewports[dir][idx];
+}
+
+static uint32_t
+designware_pcie_root_config_read(PCIDevice *d, uint32_t address, int len)
+{
+    DesignwarePCIERoot *root = DESIGNWARE_PCIE_ROOT(d);
+    DesignwarePCIEViewport *viewport =
+        designware_pcie_root_get_current_viewport(root);
+
+    uint32_t val;
+
+    switch (address) {
+    case PCIE_PORT_LINK_CONTROL:
+        /*
+         * Linux guest uses this register only to configure number of
+         * PCIE lane (which in our case is irrelevant) and doesn't
+         * really care about the value it reads from this register
+         */
+        val = 0xDEADBEEF;
+        break;
+
+    case PCIE_LINK_WIDTH_SPEED_CONTROL:
+        /*
+         * To make sure that any code in guest waiting for speed
+         * change does not time out we always report
+         * PORT_LOGIC_SPEED_CHANGE as set
+         */
+        val = PORT_LOGIC_SPEED_CHANGE;
+        break;
+
+    case PCIE_MSI_ADDR_LO:
+        val = root->msi.base;
+        break;
+
+    case PCIE_MSI_ADDR_HI:
+        val = root->msi.base >> 32;
+        break;
+
+    case PCIE_MSI_INTR0_ENABLE:
+        val = root->msi.intr[0].enable;
+        break;
+
+    case PCIE_MSI_INTR0_MASK:
+        val = root->msi.intr[0].mask;
+        break;
+
+    case PCIE_MSI_INTR0_STATUS:
+        val = root->msi.intr[0].status;
+        break;
+
+    case PCIE_PHY_DEBUG_R1:
+        val = PCIE_PHY_DEBUG_R1_XMLH_LINK_UP;
+        break;
+
+    case PCIE_ATU_VIEWPORT:
+        val = root->atu_viewport;
+        break;
+
+    case PCIE_ATU_LOWER_BASE:
+        val = viewport->base;
+        break;
+
+    case PCIE_ATU_UPPER_BASE:
+        val = viewport->base >> 32;
+        break;
+
+    case PCIE_ATU_LOWER_TARGET:
+        val = viewport->target;
+        break;
+
+    case PCIE_ATU_UPPER_TARGET:
+        val = viewport->target >> 32;
+        break;
+
+    case PCIE_ATU_LIMIT:
+        val = viewport->limit;
+        break;
+
+    case PCIE_ATU_CR1:
+    case PCIE_ATU_CR2:          /* FALLTHROUGH */
+        val = viewport->cr[(address - PCIE_ATU_CR1) / sizeof(uint32_t)];
+        break;
+
+    default:
+        val = pci_default_read_config(d, address, len);
+        break;
+    }
+
+    return val;
+}
+
+static uint64_t designware_pcie_root_data_access(void *opaque, hwaddr addr,
+                                                 uint64_t *val, unsigned len)
+{
+    DesignwarePCIEViewport *viewport;
+    DesignwarePCIERoot *root;
+    PCIBus *pcibus;
+
+    root     = DESIGNWARE_PCIE_ROOT(opaque);
+    viewport = designware_pcie_root_get_current_viewport(root);
+    pcibus   = pci_get_bus(PCI_DEVICE(root));
+
+    addr &= PCIE_CONFIG_SPACE_SIZE - 1;
+    addr |= PCIE_ATU_BUS(viewport->target) << 16;
+    addr |= PCIE_ATU_DEVFN(viewport->target) << 8;
+
+    if (val) {
+        pci_data_write(pcibus, addr, *val, len);
+        return 0;
+    }
+
+    return pci_data_read(pcibus, addr, len);
+}
+
+static uint64_t designware_pcie_root_data_read(void *opaque, hwaddr addr,
+                                               unsigned len)
+{
+    return designware_pcie_root_data_access(opaque, addr, NULL, len);
+}
+
+static void designware_pcie_root_data_write(void *opaque, hwaddr addr,
+                                            uint64_t val, unsigned len)
+{
+    designware_pcie_root_data_access(opaque, addr, &val, len);
+}
+
+static const MemoryRegionOps designware_pci_host_conf_ops = {
+    .read = designware_pcie_root_data_read,
+    .write = designware_pcie_root_data_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+    .valid = {
+        .min_access_size = 1,
+        .max_access_size = 4,
+    },
+};
+
+static void designware_pcie_update_viewport(DesignwarePCIERoot *root,
+                                            DesignwarePCIEViewport *viewport)
+{
+    const uint64_t target = viewport->target;
+    const uint64_t base   = viewport->base;
+    const uint64_t size   = (uint64_t)viewport->limit - base + 1;
+    const bool enabled    = viewport->cr[1] & PCIE_ATU_ENABLE;
+
+    MemoryRegion *current, *other;
+
+    if (viewport->cr[0] == PCIE_ATU_TYPE_MEM) {
+        current = &viewport->mem;
+        other   = &viewport->cfg;
+        memory_region_set_alias_offset(current, target);
+    } else {
+        current = &viewport->cfg;
+        other   = &viewport->mem;
+    }
+
+    /*
+     * An outbound viewport can be reconfigure from being MEM to CFG,
+     * to account for that we disable the "other" memory region that
+     * becomes unused due to that fact.
+     */
+    memory_region_set_enabled(other, false);
+    if (enabled) {
+        memory_region_set_size(current, size);
+        memory_region_set_address(current, base);
+    }
+    memory_region_set_enabled(current, enabled);
+}
+
+static void designware_pcie_root_config_write(PCIDevice *d, uint32_t address,
+                                              uint32_t val, int len)
+{
+    DesignwarePCIERoot *root = DESIGNWARE_PCIE_ROOT(d);
+    DesignwarePCIEHost *host = designware_pcie_root_to_host(root);
+    DesignwarePCIEViewport *viewport =
+        designware_pcie_root_get_current_viewport(root);
+
+    switch (address) {
+    case PCIE_PORT_LINK_CONTROL:
+    case PCIE_LINK_WIDTH_SPEED_CONTROL:
+    case PCIE_PHY_DEBUG_R1:
+        /* No-op */
+        break;
+
+    case PCIE_MSI_ADDR_LO:
+        root->msi.base &= 0xFFFFFFFF00000000ULL;
+        root->msi.base |= val;
+        break;
+
+    case PCIE_MSI_ADDR_HI:
+        root->msi.base &= 0x00000000FFFFFFFFULL;
+        root->msi.base |= (uint64_t)val << 32;
+        break;
+
+    case PCIE_MSI_INTR0_ENABLE: {
+        const bool update_msi_mapping = !root->msi.intr[0].enable ^ !!val;
+
+        root->msi.intr[0].enable = val;
+
+        if (update_msi_mapping) {
+            designware_pcie_root_update_msi_mapping(root);
+        }
+        break;
+    }
+
+    case PCIE_MSI_INTR0_MASK:
+        root->msi.intr[0].mask = val;
+        break;
+
+    case PCIE_MSI_INTR0_STATUS:
+        root->msi.intr[0].status ^= val;
+        if (!root->msi.intr[0].status) {
+            qemu_set_irq(host->pci.irqs[0], 0);
+        }
+        break;
+
+    case PCIE_ATU_VIEWPORT:
+        root->atu_viewport = val;
+        break;
+
+    case PCIE_ATU_LOWER_BASE:
+        viewport->base &= 0xFFFFFFFF00000000ULL;
+        viewport->base |= val;
+        break;
+
+    case PCIE_ATU_UPPER_BASE:
+        viewport->base &= 0x00000000FFFFFFFFULL;
+        viewport->base |= (uint64_t)val << 32;
+        break;
+
+    case PCIE_ATU_LOWER_TARGET:
+        viewport->target &= 0xFFFFFFFF00000000ULL;
+        viewport->target |= val;
+        break;
+
+    case PCIE_ATU_UPPER_TARGET:
+        viewport->target &= 0x00000000FFFFFFFFULL;
+        viewport->target |= val;
+        break;
+
+    case PCIE_ATU_LIMIT:
+        viewport->limit = val;
+        break;
+
+    case PCIE_ATU_CR1:
+        viewport->cr[0] = val;
+        break;
+    case PCIE_ATU_CR2:
+        viewport->cr[1] = val;
+        designware_pcie_update_viewport(root, viewport);
+        break;
+
+    default:
+        pci_bridge_write_config(d, address, val, len);
+        break;
+    }
+}
+
+static char *designware_pcie_viewport_name(const char *direction,
+                                           unsigned int i,
+                                           const char *type)
+{
+    return g_strdup_printf("PCI %s Viewport %u [%s]",
+                           direction, i, type);
+}
+
+static void designware_pcie_root_realize(PCIDevice *dev, Error **errp)
+{
+    DesignwarePCIERoot *root = DESIGNWARE_PCIE_ROOT(dev);
+    DesignwarePCIEHost *host = designware_pcie_root_to_host(root);
+    MemoryRegion *address_space = &host->pci.memory;
+    PCIBridge *br = PCI_BRIDGE(dev);
+    DesignwarePCIEViewport *viewport;
+    /*
+     * Dummy values used for initial configuration of MemoryRegions
+     * that belong to a give viewport
+     */
+    const hwaddr dummy_offset = 0;
+    const uint64_t dummy_size = 4;
+    size_t i;
+
+    br->bus_name  = "dw-pcie";
+
+    pci_set_word(dev->config + PCI_COMMAND,
+                 PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER);
+
+    pci_config_set_interrupt_pin(dev->config, 1);
+    pci_bridge_initfn(dev, TYPE_PCIE_BUS);
+
+    pcie_port_init_reg(dev);
+
+    pcie_cap_init(dev, 0x70, PCI_EXP_TYPE_ROOT_PORT,
+                  0, &error_fatal);
+
+    msi_nonbroken = true;
+    msi_init(dev, 0x50, 32, true, true, &error_fatal);
+
+    for (i = 0; i < DESIGNWARE_PCIE_NUM_VIEWPORTS; i++) {
+        MemoryRegion *source, *destination, *mem;
+        const char *direction;
+        char *name;
+
+        viewport = &root->viewports[DESIGNWARE_PCIE_VIEWPORT_INBOUND][i];
+        viewport->inbound = true;
+        viewport->base    = 0x0000000000000000ULL;
+        viewport->target  = 0x0000000000000000ULL;
+        viewport->limit   = UINT32_MAX;
+        viewport->cr[0]   = PCIE_ATU_TYPE_MEM;
+
+        source      = &host->pci.address_space_root;
+        destination = get_system_memory();
+        direction   = "Inbound";
+
+        /*
+         * Configure MemoryRegion implementing PCI -> CPU memory
+         * access
+         */
+        mem  = &viewport->mem;
+        name = designware_pcie_viewport_name(direction, i, "MEM");
+        memory_region_init_alias(mem, OBJECT(root), name, destination,
+                                 dummy_offset, dummy_size);
+        memory_region_add_subregion_overlap(source, dummy_offset, mem, -1);
+        memory_region_set_enabled(mem, false);
+        g_free(name);
+
+        viewport = &root->viewports[DESIGNWARE_PCIE_VIEWPORT_OUTBOUND][i];
+        viewport->inbound = false;
+        viewport->base    = 0x0000000000000000ULL;
+        viewport->target  = 0x0000000000000000ULL;
+        viewport->limit   = UINT32_MAX;
+        viewport->cr[0]   = PCIE_ATU_TYPE_MEM;
+
+        destination = &host->pci.memory;
+        direction   = "Outbound";
+        source      = get_system_memory();
+
+        /*
+         * Configure MemoryRegion implementing CPU -> PCI memory
+         * access
+         */
+        mem  = &viewport->mem;
+        name = designware_pcie_viewport_name(direction, i, "MEM");
+        memory_region_init_alias(mem, OBJECT(root), name, destination,
+                                 dummy_offset, dummy_size);
+        memory_region_add_subregion(source, dummy_offset, mem);
+        memory_region_set_enabled(mem, false);
+        g_free(name);
+
+        /*
+         * Configure MemoryRegion implementing access to configuration
+         * space
+         */
+        mem  = &viewport->cfg;
+        name = designware_pcie_viewport_name(direction, i, "CFG");
+        memory_region_init_io(&viewport->cfg, OBJECT(root),
+                              &designware_pci_host_conf_ops,
+                              root, name, dummy_size);
+        memory_region_add_subregion(source, dummy_offset, mem);
+        memory_region_set_enabled(mem, false);
+        g_free(name);
+    }
+
+    /*
+     * If no inbound iATU windows are configured, HW defaults to
+     * letting inbound TLPs to pass in. We emulate that by exlicitly
+     * configuring first inbound window to cover all of target's
+     * address space.
+     *
+     * NOTE: This will not work correctly for the case when first
+     * configured inbound window is window 0
+     */
+    viewport = &root->viewports[DESIGNWARE_PCIE_VIEWPORT_INBOUND][0];
+    viewport->cr[1] = PCIE_ATU_ENABLE;
+    designware_pcie_update_viewport(root, viewport);
+
+    memory_region_init_io(&root->msi.iomem, OBJECT(root),
+                          &designware_pci_host_msi_ops,
+                          root, "pcie-msi", 0x4);
+    /*
+     * We initially place MSI interrupt I/O region a adress 0 and
+     * disable it. It'll be later moved to correct offset and enabled
+     * in designware_pcie_root_update_msi_mapping() as a part of
+     * initialization done by guest OS
+     */
+    memory_region_add_subregion(address_space, dummy_offset, &root->msi.iomem);
+    memory_region_set_enabled(&root->msi.iomem, false);
+}
+
+static void designware_pcie_set_irq(void *opaque, int irq_num, int level)
+{
+    DesignwarePCIEHost *host = DESIGNWARE_PCIE_HOST(opaque);
+
+    qemu_set_irq(host->pci.irqs[irq_num], level);
+}
+
+static const char *
+designware_pcie_host_root_bus_path(PCIHostState *host_bridge, PCIBus *rootbus)
+{
+    return "0000:00";
+}
+
+static const VMStateDescription vmstate_designware_pcie_msi_bank = {
+    .name = "designware-pcie-msi-bank",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT32(enable, DesignwarePCIEMSIBank),
+        VMSTATE_UINT32(mask, DesignwarePCIEMSIBank),
+        VMSTATE_UINT32(status, DesignwarePCIEMSIBank),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static const VMStateDescription vmstate_designware_pcie_msi = {
+    .name = "designware-pcie-msi",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT64(base, DesignwarePCIEMSI),
+        VMSTATE_STRUCT_ARRAY(intr,
+                             DesignwarePCIEMSI,
+                             DESIGNWARE_PCIE_NUM_MSI_BANKS,
+                             1,
+                             vmstate_designware_pcie_msi_bank,
+                             DesignwarePCIEMSIBank),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static const VMStateDescription vmstate_designware_pcie_viewport = {
+    .name = "designware-pcie-viewport",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT64(base, DesignwarePCIEViewport),
+        VMSTATE_UINT64(target, DesignwarePCIEViewport),
+        VMSTATE_UINT32(limit, DesignwarePCIEViewport),
+        VMSTATE_UINT32_ARRAY(cr, DesignwarePCIEViewport, 2),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static const VMStateDescription vmstate_designware_pcie_root = {
+    .name = "designware-pcie-root",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_PCI_DEVICE(parent_obj, PCIBridge),
+        VMSTATE_UINT32(atu_viewport, DesignwarePCIERoot),
+        VMSTATE_STRUCT_2DARRAY(viewports,
+                               DesignwarePCIERoot,
+                               2,
+                               DESIGNWARE_PCIE_NUM_VIEWPORTS,
+                               1,
+                               vmstate_designware_pcie_viewport,
+                               DesignwarePCIEViewport),
+        VMSTATE_STRUCT(msi,
+                       DesignwarePCIERoot,
+                       1,
+                       vmstate_designware_pcie_msi,
+                       DesignwarePCIEMSI),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static void designware_pcie_root_class_init(ObjectClass *klass, void *data)
+{
+    PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories);
+
+    k->vendor_id = PCI_VENDOR_ID_SYNOPSYS;
+    k->device_id = 0xABCD;
+    k->revision = 0;
+    k->class_id = PCI_CLASS_BRIDGE_PCI;
+    k->is_express = true;
+    k->is_bridge = true;
+    k->exit = pci_bridge_exitfn;
+    k->realize = designware_pcie_root_realize;
+    k->config_read = designware_pcie_root_config_read;
+    k->config_write = designware_pcie_root_config_write;
+
+    dc->reset = pci_bridge_reset;
+    /*
+     * PCI-facing part of the host bridge, not usable without the
+     * host-facing part, which can't be device_add'ed, yet.
+     */
+    dc->user_creatable = false;
+    dc->vmsd = &vmstate_designware_pcie_root;
+}
+
+static uint64_t designware_pcie_host_mmio_read(void *opaque, hwaddr addr,
+                                               unsigned int size)
+{
+    PCIHostState *pci = PCI_HOST_BRIDGE(opaque);
+    PCIDevice *device = pci_find_device(pci->bus, 0, 0);
+
+    return pci_host_config_read_common(device,
+                                       addr,
+                                       pci_config_size(device),
+                                       size);
+}
+
+static void designware_pcie_host_mmio_write(void *opaque, hwaddr addr,
+                                            uint64_t val, unsigned int size)
+{
+    PCIHostState *pci = PCI_HOST_BRIDGE(opaque);
+    PCIDevice *device = pci_find_device(pci->bus, 0, 0);
+
+    return pci_host_config_write_common(device,
+                                        addr,
+                                        pci_config_size(device),
+                                        val, size);
+}
+
+static const MemoryRegionOps designware_pci_mmio_ops = {
+    .read       = designware_pcie_host_mmio_read,
+    .write      = designware_pcie_host_mmio_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+    .impl = {
+        /*
+         * Our device would not work correctly if the guest was doing
+         * unaligned access. This might not be a limitation on the real
+         * device but in practice there is no reason for a guest to access
+         * this device unaligned.
+         */
+        .min_access_size = 4,
+        .max_access_size = 4,
+        .unaligned = false,
+    },
+};
+
+static AddressSpace *designware_pcie_host_set_iommu(PCIBus *bus, void *opaque,
+                                                    int devfn)
+{
+    DesignwarePCIEHost *s = DESIGNWARE_PCIE_HOST(opaque);
+
+    return &s->pci.address_space;
+}
+
+static void designware_pcie_host_realize(DeviceState *dev, Error **errp)
+{
+    PCIHostState *pci = PCI_HOST_BRIDGE(dev);
+    DesignwarePCIEHost *s = DESIGNWARE_PCIE_HOST(dev);
+    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+    size_t i;
+
+    for (i = 0; i < ARRAY_SIZE(s->pci.irqs); i++) {
+        sysbus_init_irq(sbd, &s->pci.irqs[i]);
+    }
+
+    memory_region_init_io(&s->mmio,
+                          OBJECT(s),
+                          &designware_pci_mmio_ops,
+                          s,
+                          "pcie.reg", 4 * 1024);
+    sysbus_init_mmio(sbd, &s->mmio);
+
+    memory_region_init(&s->pci.io, OBJECT(s), "pcie-pio", 16);
+    memory_region_init(&s->pci.memory, OBJECT(s),
+                       "pcie-bus-memory",
+                       UINT64_MAX);
+
+    pci->bus = pci_register_root_bus(dev, "pcie",
+                                     designware_pcie_set_irq,
+                                     pci_swizzle_map_irq_fn,
+                                     s,
+                                     &s->pci.memory,
+                                     &s->pci.io,
+                                     0, 4,
+                                     TYPE_PCIE_BUS);
+
+    memory_region_init(&s->pci.address_space_root,
+                       OBJECT(s),
+                       "pcie-bus-address-space-root",
+                       UINT64_MAX);
+    memory_region_add_subregion(&s->pci.address_space_root,
+                                0x0, &s->pci.memory);
+    address_space_init(&s->pci.address_space,
+                       &s->pci.address_space_root,
+                       "pcie-bus-address-space");
+    pci_setup_iommu(pci->bus, designware_pcie_host_set_iommu, s);
+
+    qdev_set_parent_bus(DEVICE(&s->root), BUS(pci->bus));
+    qdev_init_nofail(DEVICE(&s->root));
+}
+
+static const VMStateDescription vmstate_designware_pcie_host = {
+    .name = "designware-pcie-host",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_STRUCT(root,
+                       DesignwarePCIEHost,
+                       1,
+                       vmstate_designware_pcie_root,
+                       DesignwarePCIERoot),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static void designware_pcie_host_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    PCIHostBridgeClass *hc = PCI_HOST_BRIDGE_CLASS(klass);
+
+    hc->root_bus_path = designware_pcie_host_root_bus_path;
+    dc->realize = designware_pcie_host_realize;
+    set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories);
+    dc->fw_name = "pci";
+    dc->vmsd = &vmstate_designware_pcie_host;
+}
+
+static void designware_pcie_host_init(Object *obj)
+{
+    DesignwarePCIEHost *s = DESIGNWARE_PCIE_HOST(obj);
+    DesignwarePCIERoot *root = &s->root;
+
+    object_initialize(root, sizeof(*root), TYPE_DESIGNWARE_PCIE_ROOT);
+    object_property_add_child(obj, "root", OBJECT(root), NULL);
+    qdev_prop_set_int32(DEVICE(root), "addr", PCI_DEVFN(0, 0));
+    qdev_prop_set_bit(DEVICE(root), "multifunction", false);
+}
+
+static const TypeInfo designware_pcie_root_info = {
+    .name = TYPE_DESIGNWARE_PCIE_ROOT,
+    .parent = TYPE_PCI_BRIDGE,
+    .instance_size = sizeof(DesignwarePCIERoot),
+    .class_init = designware_pcie_root_class_init,
+    .interfaces = (InterfaceInfo[]) {
+        { INTERFACE_PCIE_DEVICE },
+        { }
+    },
+};
+
+static const TypeInfo designware_pcie_host_info = {
+    .name       = TYPE_DESIGNWARE_PCIE_HOST,
+    .parent     = TYPE_PCI_HOST_BRIDGE,
+    .instance_size = sizeof(DesignwarePCIEHost),
+    .instance_init = designware_pcie_host_init,
+    .class_init = designware_pcie_host_class_init,
+};
+
+static void designware_pcie_register(void)
+{
+    type_register_static(&designware_pcie_root_info);
+    type_register_static(&designware_pcie_host_info);
+}
+type_init(designware_pcie_register)
diff --git a/include/hw/pci-host/designware.h b/include/hw/pci-host/designware.h
new file mode 100644
index 0000000000..63db9cdfbe
--- /dev/null
+++ b/include/hw/pci-host/designware.h
@@ -0,0 +1,97 @@ 
+/*
+ * Copyright (c) 2017, Impinj, Inc.
+ *
+ * Designware PCIe IP block emulation
+ *
+ * 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/>.
+ */
+
+#ifndef DESIGNWARE_H
+#define DESIGNWARE_H
+
+#include "hw/hw.h"
+#include "hw/sysbus.h"
+#include "hw/pci/pci.h"
+#include "hw/pci/pci_bus.h"
+#include "hw/pci/pcie_host.h"
+#include "hw/pci/pci_bridge.h"
+
+#define TYPE_DESIGNWARE_PCIE_HOST "designware-pcie-host"
+#define DESIGNWARE_PCIE_HOST(obj) \
+     OBJECT_CHECK(DesignwarePCIEHost, (obj), TYPE_DESIGNWARE_PCIE_HOST)
+
+#define TYPE_DESIGNWARE_PCIE_ROOT "designware-pcie-root"
+#define DESIGNWARE_PCIE_ROOT(obj) \
+     OBJECT_CHECK(DesignwarePCIERoot, (obj), TYPE_DESIGNWARE_PCIE_ROOT)
+
+typedef struct DesignwarePCIEViewport {
+    MemoryRegion cfg;
+    MemoryRegion mem;
+
+    uint64_t base;
+    uint64_t target;
+    uint32_t limit;
+    uint32_t cr[2];
+
+    bool inbound;
+} DesignwarePCIEViewport;
+
+typedef struct DesignwarePCIEMSIBank {
+    uint32_t enable;
+    uint32_t mask;
+    uint32_t status;
+} DesignwarePCIEMSIBank;
+
+typedef struct DesignwarePCIEMSI {
+    uint64_t     base;
+    MemoryRegion iomem;
+
+#define DESIGNWARE_PCIE_NUM_MSI_BANKS        1
+
+    DesignwarePCIEMSIBank intr[DESIGNWARE_PCIE_NUM_MSI_BANKS];
+} DesignwarePCIEMSI;
+
+typedef struct DesignwarePCIERoot {
+    PCIBridge parent_obj;
+
+    uint32_t atu_viewport;
+
+#define DESIGNWARE_PCIE_VIEWPORT_OUTBOUND    0
+#define DESIGNWARE_PCIE_VIEWPORT_INBOUND     1
+#define DESIGNWARE_PCIE_NUM_VIEWPORTS        4
+
+    DesignwarePCIEViewport viewports[2][DESIGNWARE_PCIE_NUM_VIEWPORTS];
+    DesignwarePCIEMSI msi;
+} DesignwarePCIERoot;
+
+typedef struct DesignwarePCIEHost {
+    PCIHostState parent_obj;
+
+    DesignwarePCIERoot root;
+
+    struct {
+        AddressSpace address_space;
+        MemoryRegion address_space_root;
+
+        MemoryRegion memory;
+        MemoryRegion io;
+
+        qemu_irq     irqs[4];
+    } pci;
+
+    MemoryRegion mmio;
+} DesignwarePCIEHost;
+
+#endif  /* DESIGNWARE_H */
diff --git a/include/hw/pci/pci_ids.h b/include/hw/pci/pci_ids.h
index 35df1874a9..23fefe1bc6 100644
--- a/include/hw/pci/pci_ids.h
+++ b/include/hw/pci/pci_ids.h
@@ -266,4 +266,6 @@ 
 #define PCI_VENDOR_ID_TEWS               0x1498
 #define PCI_DEVICE_ID_TEWS_TPCI200       0x30C8
 
+#define PCI_VENDOR_ID_SYNOPSYS           0x16C3
+
 #endif