diff options
Diffstat (limited to 'hw/pci-bridge')
-rw-r--r-- | hw/pci-bridge/Makefile.objs | 1 | ||||
-rw-r--r-- | hw/pci-bridge/i82801b11.c | 21 | ||||
-rw-r--r-- | hw/pci-bridge/pci_bridge_dev.c | 117 | ||||
-rw-r--r-- | hw/pci-bridge/pci_expander_bridge.c | 286 |
4 files changed, 385 insertions, 40 deletions
diff --git a/hw/pci-bridge/Makefile.objs b/hw/pci-bridge/Makefile.objs index 96c596eb3..f2adfe348 100644 --- a/hw/pci-bridge/Makefile.objs +++ b/hw/pci-bridge/Makefile.objs @@ -1,4 +1,5 @@ common-obj-y += pci_bridge_dev.o +common-obj-y += pci_expander_bridge.o common-obj-$(CONFIG_XIO3130) += xio3130_upstream.o xio3130_downstream.o common-obj-$(CONFIG_IOH3420) += ioh3420.o common-obj-$(CONFIG_I82801B11) += i82801b11.o diff --git a/hw/pci-bridge/i82801b11.c b/hw/pci-bridge/i82801b11.c index 14cd7fd40..7e79bc01e 100644 --- a/hw/pci-bridge/i82801b11.c +++ b/hw/pci-bridge/i82801b11.c @@ -101,27 +101,6 @@ static const TypeInfo i82801b11_bridge_info = { .class_init = i82801b11_bridge_class_init, }; -PCIBus *ich9_d2pbr_init(PCIBus *bus, int devfn, int sec_bus) -{ - PCIDevice *d; - PCIBridge *br; - char buf[16]; - DeviceState *qdev; - - d = pci_create_multifunction(bus, devfn, true, "i82801b11-bridge"); - if (!d) { - return NULL; - } - br = PCI_BRIDGE(d); - qdev = DEVICE(d); - - snprintf(buf, sizeof(buf), "pci.%d", sec_bus); - pci_bridge_map_irq(br, buf, pci_swizzle_map_irq_fn); - qdev_init_nofail(qdev); - - return pci_bridge_get_sec_bus(br); -} - static void d2pbr_register(void) { type_register_static(&i82801b11_bridge_info); diff --git a/hw/pci-bridge/pci_bridge_dev.c b/hw/pci-bridge/pci_bridge_dev.c index 36f73e1f8..26aded9f0 100644 --- a/hw/pci-bridge/pci_bridge_dev.c +++ b/hw/pci-bridge/pci_bridge_dev.c @@ -28,7 +28,8 @@ #include "hw/pci/pci_bus.h" #include "hw/hotplug.h" -#define TYPE_PCI_BRIDGE_DEV "pci-bridge" +#define TYPE_PCI_BRIDGE_DEV "pci-bridge" +#define TYPE_PCI_BRIDGE_SEAT_DEV "pci-bridge-seat" #define PCI_BRIDGE_DEV(obj) \ OBJECT_CHECK(PCIBridgeDev, (obj), TYPE_PCI_BRIDGE_DEV) @@ -40,6 +41,7 @@ struct PCIBridgeDev { MemoryRegion bar; uint8_t chassis_nr; #define PCI_BRIDGE_DEV_F_MSI_REQ 0 +#define PCI_BRIDGE_DEV_F_SHPC_REQ 1 uint32_t flags; }; typedef struct PCIBridgeDev PCIBridgeDev; @@ -54,11 +56,17 @@ static int pci_bridge_dev_initfn(PCIDevice *dev) if (err) { goto bridge_error; } - dev->config[PCI_INTERRUPT_PIN] = 0x1; - memory_region_init(&bridge_dev->bar, OBJECT(dev), "shpc-bar", shpc_bar_size(dev)); - err = shpc_init(dev, &br->sec_bus, &bridge_dev->bar, 0); - if (err) { - goto shpc_error; + if (bridge_dev->flags & (1 << PCI_BRIDGE_DEV_F_SHPC_REQ)) { + dev->config[PCI_INTERRUPT_PIN] = 0x1; + memory_region_init(&bridge_dev->bar, OBJECT(dev), "shpc-bar", + shpc_bar_size(dev)); + err = shpc_init(dev, &br->sec_bus, &bridge_dev->bar, 0); + if (err) { + goto shpc_error; + } + } else { + /* MSI is not applicable without SHPC */ + bridge_dev->flags &= ~(1 << PCI_BRIDGE_DEV_F_MSI_REQ); } err = slotid_cap_init(dev, 0, bridge_dev->chassis_nr, 0); if (err) { @@ -71,15 +79,19 @@ static int pci_bridge_dev_initfn(PCIDevice *dev) goto msi_error; } } - /* TODO: spec recommends using 64 bit prefetcheable BAR. - * Check whether that works well. */ - pci_register_bar(dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY | - PCI_BASE_ADDRESS_MEM_TYPE_64, &bridge_dev->bar); + if (shpc_present(dev)) { + /* TODO: spec recommends using 64 bit prefetcheable BAR. + * Check whether that works well. */ + pci_register_bar(dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY | + PCI_BASE_ADDRESS_MEM_TYPE_64, &bridge_dev->bar); + } return 0; msi_error: slotid_cap_cleanup(dev); slotid_error: - shpc_cleanup(dev, &bridge_dev->bar); + if (shpc_present(dev)) { + shpc_cleanup(dev, &bridge_dev->bar); + } shpc_error: pci_bridge_exitfn(dev); bridge_error: @@ -93,12 +105,15 @@ static void pci_bridge_dev_exitfn(PCIDevice *dev) msi_uninit(dev); } slotid_cap_cleanup(dev); - shpc_cleanup(dev, &bridge_dev->bar); + if (shpc_present(dev)) { + shpc_cleanup(dev, &bridge_dev->bar); + } pci_bridge_exitfn(dev); } static void pci_bridge_dev_instance_finalize(Object *obj) { + /* this function is idempotent and handles (PCIDevice.shpc == NULL) */ shpc_free(PCI_DEVICE(obj)); } @@ -109,7 +124,9 @@ static void pci_bridge_dev_write_config(PCIDevice *d, if (msi_present(d)) { msi_write_config(d, address, val, len); } - shpc_cap_write_config(d, address, val, len); + if (shpc_present(d)) { + shpc_cap_write_config(d, address, val, len); + } } static void qdev_pci_bridge_dev_reset(DeviceState *qdev) @@ -117,25 +134,65 @@ static void qdev_pci_bridge_dev_reset(DeviceState *qdev) PCIDevice *dev = PCI_DEVICE(qdev); pci_bridge_reset(qdev); - shpc_reset(dev); + if (shpc_present(dev)) { + shpc_reset(dev); + } } static Property pci_bridge_dev_properties[] = { /* Note: 0 is not a legal chassis number. */ - DEFINE_PROP_UINT8("chassis_nr", PCIBridgeDev, chassis_nr, 0), - DEFINE_PROP_BIT("msi", PCIBridgeDev, flags, PCI_BRIDGE_DEV_F_MSI_REQ, true), + DEFINE_PROP_UINT8(PCI_BRIDGE_DEV_PROP_CHASSIS_NR, PCIBridgeDev, chassis_nr, + 0), + DEFINE_PROP_BIT(PCI_BRIDGE_DEV_PROP_MSI, PCIBridgeDev, flags, + PCI_BRIDGE_DEV_F_MSI_REQ, true), + DEFINE_PROP_BIT(PCI_BRIDGE_DEV_PROP_SHPC, PCIBridgeDev, flags, + PCI_BRIDGE_DEV_F_SHPC_REQ, true), DEFINE_PROP_END_OF_LIST(), }; +static bool pci_device_shpc_present(void *opaque, int version_id) +{ + PCIDevice *dev = opaque; + + return shpc_present(dev); +} + static const VMStateDescription pci_bridge_dev_vmstate = { .name = "pci_bridge", .fields = (VMStateField[]) { VMSTATE_PCI_DEVICE(parent_obj, PCIBridge), - SHPC_VMSTATE(shpc, PCIDevice), + SHPC_VMSTATE(shpc, PCIDevice, pci_device_shpc_present), VMSTATE_END_OF_LIST() } }; +static void pci_bridge_dev_hotplug_cb(HotplugHandler *hotplug_dev, + DeviceState *dev, Error **errp) +{ + PCIDevice *pci_hotplug_dev = PCI_DEVICE(hotplug_dev); + + if (!shpc_present(pci_hotplug_dev)) { + error_setg(errp, "standard hotplug controller has been disabled for " + "this %s", TYPE_PCI_BRIDGE_DEV); + return; + } + shpc_device_hotplug_cb(hotplug_dev, dev, errp); +} + +static void pci_bridge_dev_hot_unplug_request_cb(HotplugHandler *hotplug_dev, + DeviceState *dev, + Error **errp) +{ + PCIDevice *pci_hotplug_dev = PCI_DEVICE(hotplug_dev); + + if (!shpc_present(pci_hotplug_dev)) { + error_setg(errp, "standard hotplug controller has been disabled for " + "this %s", TYPE_PCI_BRIDGE_DEV); + return; + } + shpc_device_hot_unplug_request_cb(hotplug_dev, dev, errp); +} + static void pci_bridge_dev_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); @@ -154,8 +211,8 @@ static void pci_bridge_dev_class_init(ObjectClass *klass, void *data) dc->props = pci_bridge_dev_properties; dc->vmsd = &pci_bridge_dev_vmstate; set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories); - hc->plug = shpc_device_hotplug_cb; - hc->unplug_request = shpc_device_hot_unplug_request_cb; + hc->plug = pci_bridge_dev_hotplug_cb; + hc->unplug_request = pci_bridge_dev_hot_unplug_request_cb; } static const TypeInfo pci_bridge_dev_info = { @@ -170,9 +227,31 @@ static const TypeInfo pci_bridge_dev_info = { } }; +/* + * Multiseat bridge. Same as the standard pci bridge, only with a + * different pci id, so we can match it easily in the guest for + * automagic multiseat configuration. See docs/multiseat.txt for more. + */ +static void pci_bridge_dev_seat_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + + k->device_id = PCI_DEVICE_ID_REDHAT_BRIDGE_SEAT; + dc->desc = "Standard PCI Bridge (multiseat)"; +} + +static const TypeInfo pci_bridge_dev_seat_info = { + .name = TYPE_PCI_BRIDGE_SEAT_DEV, + .parent = TYPE_PCI_BRIDGE_DEV, + .instance_size = sizeof(PCIBridgeDev), + .class_init = pci_bridge_dev_seat_class_init, +}; + static void pci_bridge_dev_register(void) { type_register_static(&pci_bridge_dev_info); + type_register_static(&pci_bridge_dev_seat_info); } type_init(pci_bridge_dev_register); diff --git a/hw/pci-bridge/pci_expander_bridge.c b/hw/pci-bridge/pci_expander_bridge.c new file mode 100644 index 000000000..57f8a3762 --- /dev/null +++ b/hw/pci-bridge/pci_expander_bridge.c @@ -0,0 +1,286 @@ +/* + * PCI Expander Bridge Device Emulation + * + * Copyright (C) 2015 Red Hat Inc + * + * Authors: + * Marcel Apfelbaum <marcel@redhat.com> + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#include "hw/pci/pci.h" +#include "hw/pci/pci_bus.h" +#include "hw/pci/pci_host.h" +#include "hw/pci/pci_bus.h" +#include "hw/pci/pci_bridge.h" +#include "hw/i386/pc.h" +#include "qemu/range.h" +#include "qemu/error-report.h" +#include "sysemu/numa.h" + +#define TYPE_PXB_BUS "pxb-bus" +#define PXB_BUS(obj) OBJECT_CHECK(PXBBus, (obj), TYPE_PXB_BUS) + +typedef struct PXBBus { + /*< private >*/ + PCIBus parent_obj; + /*< public >*/ + + char bus_path[8]; +} PXBBus; + +#define TYPE_PXB_DEVICE "pxb" +#define PXB_DEV(obj) OBJECT_CHECK(PXBDev, (obj), TYPE_PXB_DEVICE) + +typedef struct PXBDev { + /*< private >*/ + PCIDevice parent_obj; + /*< public >*/ + + uint8_t bus_nr; + uint16_t numa_node; +} PXBDev; + +static GList *pxb_dev_list; + +#define TYPE_PXB_HOST "pxb-host" + +static int pxb_bus_num(PCIBus *bus) +{ + PXBDev *pxb = PXB_DEV(bus->parent_dev); + + return pxb->bus_nr; +} + +static bool pxb_is_root(PCIBus *bus) +{ + return true; /* by definition */ +} + +static uint16_t pxb_bus_numa_node(PCIBus *bus) +{ + PXBDev *pxb = PXB_DEV(bus->parent_dev); + + return pxb->numa_node; +} + +static void pxb_bus_class_init(ObjectClass *class, void *data) +{ + PCIBusClass *pbc = PCI_BUS_CLASS(class); + + pbc->bus_num = pxb_bus_num; + pbc->is_root = pxb_is_root; + pbc->numa_node = pxb_bus_numa_node; +} + +static const TypeInfo pxb_bus_info = { + .name = TYPE_PXB_BUS, + .parent = TYPE_PCI_BUS, + .instance_size = sizeof(PXBBus), + .class_init = pxb_bus_class_init, +}; + +static const char *pxb_host_root_bus_path(PCIHostState *host_bridge, + PCIBus *rootbus) +{ + PXBBus *bus = PXB_BUS(rootbus); + + snprintf(bus->bus_path, 8, "0000:%02x", pxb_bus_num(rootbus)); + return bus->bus_path; +} + +static char *pxb_host_ofw_unit_address(const SysBusDevice *dev) +{ + const PCIHostState *pxb_host; + const PCIBus *pxb_bus; + const PXBDev *pxb_dev; + int position; + const DeviceState *pxb_dev_base; + const PCIHostState *main_host; + const SysBusDevice *main_host_sbd; + + pxb_host = PCI_HOST_BRIDGE(dev); + pxb_bus = pxb_host->bus; + pxb_dev = PXB_DEV(pxb_bus->parent_dev); + position = g_list_index(pxb_dev_list, pxb_dev); + assert(position >= 0); + + pxb_dev_base = DEVICE(pxb_dev); + main_host = PCI_HOST_BRIDGE(pxb_dev_base->parent_bus->parent); + main_host_sbd = SYS_BUS_DEVICE(main_host); + + if (main_host_sbd->num_mmio > 0) { + return g_strdup_printf(TARGET_FMT_plx ",%x", + main_host_sbd->mmio[0].addr, position + 1); + } + if (main_host_sbd->num_pio > 0) { + return g_strdup_printf("i%04x,%x", + main_host_sbd->pio[0], position + 1); + } + return NULL; +} + +static void pxb_host_class_init(ObjectClass *class, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(class); + SysBusDeviceClass *sbc = SYS_BUS_DEVICE_CLASS(class); + PCIHostBridgeClass *hc = PCI_HOST_BRIDGE_CLASS(class); + + dc->fw_name = "pci"; + sbc->explicit_ofw_unit_address = pxb_host_ofw_unit_address; + hc->root_bus_path = pxb_host_root_bus_path; +} + +static const TypeInfo pxb_host_info = { + .name = TYPE_PXB_HOST, + .parent = TYPE_PCI_HOST_BRIDGE, + .class_init = pxb_host_class_init, +}; + +/* + * Registers the PXB bus as a child of the i440fx root bus. + * + * Returns 0 on successs, -1 if i440fx host was not + * found or the bus number is already in use. + */ +static int pxb_register_bus(PCIDevice *dev, PCIBus *pxb_bus) +{ + PCIBus *bus = dev->bus; + int pxb_bus_num = pci_bus_num(pxb_bus); + + if (bus->parent_dev) { + error_report("PXB devices can be attached only to root bus."); + return -1; + } + + QLIST_FOREACH(bus, &bus->child, sibling) { + if (pci_bus_num(bus) == pxb_bus_num) { + error_report("Bus %d is already in use.", pxb_bus_num); + return -1; + } + } + QLIST_INSERT_HEAD(&dev->bus->child, pxb_bus, sibling); + + return 0; +} + +static int pxb_map_irq_fn(PCIDevice *pci_dev, int pin) +{ + PCIDevice *pxb = pci_dev->bus->parent_dev; + + /* + * The bios does not index the pxb slot number when + * it computes the IRQ because it resides on bus 0 + * and not on the current bus. + * However QEMU routes the irq through bus 0 and adds + * the pxb slot to the IRQ computation of the PXB + * device. + * + * Synchronize between bios and QEMU by canceling + * pxb's effect. + */ + return pin - PCI_SLOT(pxb->devfn); +} + +static gint pxb_compare(gconstpointer a, gconstpointer b) +{ + const PXBDev *pxb_a = a, *pxb_b = b; + + return pxb_a->bus_nr < pxb_b->bus_nr ? -1 : + pxb_a->bus_nr > pxb_b->bus_nr ? 1 : + 0; +} + +static int pxb_dev_initfn(PCIDevice *dev) +{ + PXBDev *pxb = PXB_DEV(dev); + DeviceState *ds, *bds; + PCIBus *bus; + const char *dev_name = NULL; + + if (pxb->numa_node != NUMA_NODE_UNASSIGNED && + pxb->numa_node >= nb_numa_nodes) { + error_report("Illegal numa node %d.", pxb->numa_node); + return -EINVAL; + } + + if (dev->qdev.id && *dev->qdev.id) { + dev_name = dev->qdev.id; + } + + ds = qdev_create(NULL, TYPE_PXB_HOST); + bus = pci_bus_new(ds, "pxb-internal", NULL, NULL, 0, TYPE_PXB_BUS); + + bus->parent_dev = dev; + bus->address_space_mem = dev->bus->address_space_mem; + bus->address_space_io = dev->bus->address_space_io; + bus->map_irq = pxb_map_irq_fn; + + bds = qdev_create(BUS(bus), "pci-bridge"); + bds->id = dev_name; + qdev_prop_set_uint8(bds, PCI_BRIDGE_DEV_PROP_CHASSIS_NR, pxb->bus_nr); + qdev_prop_set_bit(bds, PCI_BRIDGE_DEV_PROP_SHPC, false); + + PCI_HOST_BRIDGE(ds)->bus = bus; + + if (pxb_register_bus(dev, bus)) { + return -EINVAL; + } + + qdev_init_nofail(ds); + qdev_init_nofail(bds); + + pci_word_test_and_set_mask(dev->config + PCI_STATUS, + PCI_STATUS_66MHZ | PCI_STATUS_FAST_BACK); + pci_config_set_class(dev->config, PCI_CLASS_BRIDGE_HOST); + + pxb_dev_list = g_list_insert_sorted(pxb_dev_list, pxb, pxb_compare); + return 0; +} + +static void pxb_dev_exitfn(PCIDevice *pci_dev) +{ + PXBDev *pxb = PXB_DEV(pci_dev); + + pxb_dev_list = g_list_remove(pxb_dev_list, pxb); +} + +static Property pxb_dev_properties[] = { + /* Note: 0 is not a legal a PXB bus number. */ + DEFINE_PROP_UINT8("bus_nr", PXBDev, bus_nr, 0), + DEFINE_PROP_UINT16("numa_node", PXBDev, numa_node, NUMA_NODE_UNASSIGNED), + DEFINE_PROP_END_OF_LIST(), +}; + +static void pxb_dev_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + + k->init = pxb_dev_initfn; + k->exit = pxb_dev_exitfn; + k->vendor_id = PCI_VENDOR_ID_REDHAT; + k->device_id = PCI_DEVICE_ID_REDHAT_PXB; + k->class_id = PCI_CLASS_BRIDGE_HOST; + + dc->desc = "PCI Expander Bridge"; + dc->props = pxb_dev_properties; +} + +static const TypeInfo pxb_dev_info = { + .name = TYPE_PXB_DEVICE, + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(PXBDev), + .class_init = pxb_dev_class_init, +}; + +static void pxb_register_types(void) +{ + type_register_static(&pxb_bus_info); + type_register_static(&pxb_host_info); + type_register_static(&pxb_dev_info); +} + +type_init(pxb_register_types) |