/* * 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 * . */ #include "qemu/osdep.h" #include "qapi/error.h" #include "qemu/module.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 "migration/vmstate.h" #include "hw/irq.h" #include "hw/pci-host/designware.h" #define DESIGNWARE_PCIE_PORT_LINK_CONTROL 0x710 #define DESIGNWARE_PCIE_PHY_DEBUG_R1 0x72C #define DESIGNWARE_PCIE_PHY_DEBUG_R1_XMLH_LINK_UP BIT(4) #define DESIGNWARE_PCIE_LINK_WIDTH_SPEED_CONTROL 0x80C #define DESIGNWARE_PCIE_PORT_LOGIC_SPEED_CHANGE BIT(17) #define DESIGNWARE_PCIE_MSI_ADDR_LO 0x820 #define DESIGNWARE_PCIE_MSI_ADDR_HI 0x824 #define DESIGNWARE_PCIE_MSI_INTR0_ENABLE 0x828 #define DESIGNWARE_PCIE_MSI_INTR0_MASK 0x82C #define DESIGNWARE_PCIE_MSI_INTR0_STATUS 0x830 #define DESIGNWARE_PCIE_ATU_VIEWPORT 0x900 #define DESIGNWARE_PCIE_ATU_REGION_INBOUND BIT(31) #define DESIGNWARE_PCIE_ATU_CR1 0x904 #define DESIGNWARE_PCIE_ATU_TYPE_MEM (0x0 << 0) #define DESIGNWARE_PCIE_ATU_CR2 0x908 #define DESIGNWARE_PCIE_ATU_ENABLE BIT(31) #define DESIGNWARE_PCIE_ATU_LOWER_BASE 0x90C #define DESIGNWARE_PCIE_ATU_UPPER_BASE 0x910 #define DESIGNWARE_PCIE_ATU_LIMIT 0x914 #define DESIGNWARE_PCIE_ATU_LOWER_TARGET 0x918 #define DESIGNWARE_PCIE_ATU_BUS(x) (((x) >> 24) & 0xff) #define DESIGNWARE_PCIE_ATU_DEVFN(x) (((x) >> 16) & 0xff) #define DESIGNWARE_PCIE_ATU_UPPER_TARGET 0x91C #define DESIGNWARE_PCIE_IRQ_MSI 3 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[DESIGNWARE_PCIE_IRQ_MSI], 1); } } static const MemoryRegionOps designware_pci_host_msi_ops = { .write = designware_pcie_root_msi_write, .endianness = DEVICE_LITTLE_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 & DESIGNWARE_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 DESIGNWARE_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 DESIGNWARE_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 = DESIGNWARE_PCIE_PORT_LOGIC_SPEED_CHANGE; break; case DESIGNWARE_PCIE_MSI_ADDR_LO: val = root->msi.base; break; case DESIGNWARE_PCIE_MSI_ADDR_HI: val = root->msi.base >> 32; break; case DESIGNWARE_PCIE_MSI_INTR0_ENABLE: val = root->msi.intr[0].enable; break; case DESIGNWARE_PCIE_MSI_INTR0_MASK: val = root->msi.intr[0].mask; break; case DESIGNWARE_PCIE_MSI_INTR0_STATUS: val = root->msi.intr[0].status; break; case DESIGNWARE_PCIE_PHY_DEBUG_R1: val = DESIGNWARE_PCIE_PHY_DEBUG_R1_XMLH_LINK_UP; break; case DESIGNWARE_PCIE_ATU_VIEWPORT: val = root->atu_viewport; break; case DESIGNWARE_PCIE_ATU_LOWER_BASE: val = viewport->base; break; case DESIGNWARE_PCIE_ATU_UPPER_BASE: val = viewport->base >> 32; break; case DESIGNWARE_PCIE_ATU_LOWER_TARGET: val = viewport->target; break; case DESIGNWARE_PCIE_ATU_UPPER_TARGET: val = viewport->target >> 32; break; case DESIGNWARE_PCIE_ATU_LIMIT: val = viewport->limit; break; case DESIGNWARE_PCIE_ATU_CR1: case DESIGNWARE_PCIE_ATU_CR2: /* FALLTHROUGH */ val = viewport->cr[(address - DESIGNWARE_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 = opaque; DesignwarePCIERoot *root = viewport->root; const uint8_t busnum = DESIGNWARE_PCIE_ATU_BUS(viewport->target); const uint8_t devfn = DESIGNWARE_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; if (val) { pci_host_config_write_common(pcidev, addr, pci_config_size(pcidev), *val, len); } else { return pci_host_config_read_common(pcidev, addr, pci_config_size(pcidev), len); } } return UINT64_MAX; } 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_LITTLE_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] & DESIGNWARE_PCIE_ATU_ENABLE; MemoryRegion *current, *other; if (viewport->cr[0] == DESIGNWARE_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 DESIGNWARE_PCIE_PORT_LINK_CONTROL: case DESIGNWARE_PCIE_LINK_WIDTH_SPEED_CONTROL: case DESIGNWARE_PCIE_PHY_DEBUG_R1: /* No-op */ break; case DESIGNWARE_PCIE_MSI_ADDR_LO: root->msi.base &= 0xFFFFFFFF00000000ULL; root->msi.base |= val; designware_pcie_root_update_msi_mapping(root); break; case DESIGNWARE_PCIE_MSI_ADDR_HI: root->msi.base &= 0x00000000FFFFFFFFULL; root->msi.base |= (uint64_t)val << 32; designware_pcie_root_update_msi_mapping(root); break; case DESIGNWARE_PCIE_MSI_INTR0_ENABLE: root->msi.intr[0].enable = val; designware_pcie_root_update_msi_mapping(root); break; case DESIGNWARE_PCIE_MSI_INTR0_MASK: root->msi.intr[0].mask = val; break; case DESIGNWARE_PCIE_MSI_INTR0_STATUS: root->msi.intr[0].status ^= val; if (!root->msi.intr[0].status) { qemu_set_irq(host->pci.irqs[DESIGNWARE_PCIE_IRQ_MSI], 0); } break; case DESIGNWARE_PCIE_ATU_VIEWPORT: root->atu_viewport = val; break; case DESIGNWARE_PCIE_ATU_LOWER_BASE: viewport->base &= 0xFFFFFFFF00000000ULL; viewport->base |= val; break; case DESIGNWARE_PCIE_ATU_UPPER_BASE: viewport->base &= 0x00000000FFFFFFFFULL; viewport->base |= (uint64_t)val << 32; break; case DESIGNWARE_PCIE_ATU_LOWER_TARGET: viewport->target &= 0xFFFFFFFF00000000ULL; viewport->target |= val; break; case DESIGNWARE_PCIE_ATU_UPPER_TARGET: viewport->target &= 0x00000000FFFFFFFFULL; viewport->target |= val; break; case DESIGNWARE_PCIE_ATU_LIMIT: viewport->limit = val; break; case DESIGNWARE_PCIE_ATU_CR1: viewport->cr[0] = val; break; case DESIGNWARE_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 given 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] = DESIGNWARE_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->root = root; viewport->inbound = false; viewport->base = 0x0000000000000000ULL; viewport->target = 0x0000000000000000ULL; viewport->limit = UINT32_MAX; viewport->cr[0] = DESIGNWARE_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, viewport, 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] = DESIGNWARE_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_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_LITTLE_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_child(obj, "root", root, sizeof(*root), TYPE_DESIGNWARE_PCIE_ROOT, &error_abort, 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)