2015-06-02 14:23:06 +03:00
|
|
|
/*
|
|
|
|
* 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"
|
2015-06-19 05:40:10 +03:00
|
|
|
#include "hw/pci/pci_bridge.h"
|
2015-06-02 14:23:06 +03:00
|
|
|
#include "hw/i386/pc.h"
|
|
|
|
#include "qemu/range.h"
|
|
|
|
#include "qemu/error-report.h"
|
2015-06-02 14:23:10 +03:00
|
|
|
#include "sysemu/numa.h"
|
2015-06-02 14:23:06 +03:00
|
|
|
|
|
|
|
#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;
|
2015-06-02 14:23:10 +03:00
|
|
|
uint16_t numa_node;
|
2015-06-02 14:23:06 +03:00
|
|
|
} PXBDev;
|
|
|
|
|
2015-06-19 05:40:17 +03:00
|
|
|
static GList *pxb_dev_list;
|
|
|
|
|
2015-06-02 14:23:06 +03:00
|
|
|
#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 */
|
|
|
|
}
|
|
|
|
|
2015-06-02 14:23:10 +03:00
|
|
|
static uint16_t pxb_bus_numa_node(PCIBus *bus)
|
|
|
|
{
|
|
|
|
PXBDev *pxb = PXB_DEV(bus->parent_dev);
|
|
|
|
|
|
|
|
return pxb->numa_node;
|
|
|
|
}
|
|
|
|
|
2015-06-02 14:23:06 +03:00
|
|
|
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;
|
2015-06-02 14:23:10 +03:00
|
|
|
pbc->numa_node = pxb_bus_numa_node;
|
2015-06-02 14:23:06 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2015-06-19 05:40:17 +03:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2015-06-02 14:23:06 +03:00
|
|
|
static void pxb_host_class_init(ObjectClass *class, void *data)
|
|
|
|
{
|
|
|
|
DeviceClass *dc = DEVICE_CLASS(class);
|
2015-06-19 05:40:17 +03:00
|
|
|
SysBusDeviceClass *sbc = SYS_BUS_DEVICE_CLASS(class);
|
2015-06-02 14:23:06 +03:00
|
|
|
PCIHostBridgeClass *hc = PCI_HOST_BRIDGE_CLASS(class);
|
|
|
|
|
|
|
|
dc->fw_name = "pci";
|
2015-06-19 05:40:17 +03:00
|
|
|
sbc->explicit_ofw_unit_address = pxb_host_ofw_unit_address;
|
2015-06-02 14:23:06 +03:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2015-06-02 14:23:08 +03:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2015-06-19 05:40:17 +03:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2015-06-02 14:23:06 +03:00
|
|
|
static int pxb_dev_initfn(PCIDevice *dev)
|
|
|
|
{
|
|
|
|
PXBDev *pxb = PXB_DEV(dev);
|
|
|
|
DeviceState *ds, *bds;
|
|
|
|
PCIBus *bus;
|
|
|
|
const char *dev_name = NULL;
|
|
|
|
|
2015-06-02 14:23:10 +03:00
|
|
|
if (pxb->numa_node != NUMA_NODE_UNASSIGNED &&
|
|
|
|
pxb->numa_node >= nb_numa_nodes) {
|
|
|
|
error_report("Illegal numa node %d.", pxb->numa_node);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2015-06-02 14:23:06 +03:00
|
|
|
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;
|
2015-06-02 14:23:08 +03:00
|
|
|
bus->map_irq = pxb_map_irq_fn;
|
2015-06-02 14:23:06 +03:00
|
|
|
|
|
|
|
bds = qdev_create(BUS(bus), "pci-bridge");
|
|
|
|
bds->id = dev_name;
|
2015-06-19 05:40:10 +03:00
|
|
|
qdev_prop_set_uint8(bds, PCI_BRIDGE_DEV_PROP_CHASSIS_NR, pxb->bus_nr);
|
hw/pci-bridge: disable SHPC in PXB
OVMF downloads the ACPI linker/loader script from QEMU when the edk2 PCI
Bus driver globally signals the firmware that PCI enumeration and resource
allocation have completed. At this point QEMU regenerates the ACPI payload
in an fw_cfg read callback, and this is when the PXB's _CRS gets
populated.
Unfortunately, when this happens, the PCI_COMMAND_MEMORY bit is clear in
the root bus's command register, *unlike* under SeaBIOS. The consequences
unfold as follows:
- When build_crs() fetches dev->io_regions[i].addr, it is all-bits-one,
because pci_update_mappings() --> pci_bar_address() calculated it as
PCI_BAR_UNMAPPED, due to the PCI_COMMAND_MEMORY bit being clear.
- Consequently, the SHPC MMIO BAR (bar 0) of the bridge is not added to
the _CRS, *despite* having been programmed in PCI config space.
- Similarly, the SHPC MMIO BAR of the PXB is not removed from the main
root bus's DWordMemory descriptor.
- Guest OSes (Linux and Windows alike) notice the pre-programmed SHPC BAR
within the PXB's config space, and notice that it conflicts with the
main root bus's memory resource descriptors. Linux reports
pci 0000:04:00.0: BAR 0: can't assign mem (size 0x100)
pci 0000:04:00.0: BAR 0: trying firmware assignment [mem
0x88200000-0x882000ff 64bit]
pci 0000:04:00.0: BAR 0: [mem 0x88200000-0x882000ff 64bit] conflicts
with PCI Bus 0000:00 [mem
0x88200000-0xfebfffff]
While Windows Server 2012 R2 reports
https://technet.microsoft.com/en-us/library/cc732199%28v=ws.10%29.aspx
This device cannot find enough free resources that it can use. If you
want to use this device, you will need to disable one of the other
devices on this system. (Code 12)
This issue was apparently encountered earlier, see the "hack" in:
https://lists.nongnu.org/archive/html/qemu-devel/2015-01/msg02983.html
and the current hole-punching logic in build_crs() and build_ssdt() is
probably supposed to remedy exactly that problem -- however, for OVMF they
don't work, because at the end of the PCI enumeration and resource
allocation, which cues the ACPI linker/loader client, the command register
is clear.
The "shpc" property of "pci-bridge", introduced in the previous patches,
allows us to disable the standard hotplug controller cleanly, eliminating
the SHPC bar and the conflict.
Cc: Michael S. Tsirkin <mst@redhat.com>
Cc: Marcel Apfelbaum <marcel@redhat.com>
Signed-off-by: Laszlo Ersek <lersek@redhat.com>
Reviewed-by: Michael S. Tsirkin <mst@redhat.com>
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
2015-06-19 05:40:14 +03:00
|
|
|
qdev_prop_set_bit(bds, PCI_BRIDGE_DEV_PROP_SHPC, false);
|
2015-06-02 14:23:06 +03:00
|
|
|
|
|
|
|
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);
|
|
|
|
|
2015-06-19 05:40:17 +03:00
|
|
|
pxb_dev_list = g_list_insert_sorted(pxb_dev_list, pxb, pxb_compare);
|
2015-06-02 14:23:06 +03:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-06-19 05:40:17 +03:00
|
|
|
static void pxb_dev_exitfn(PCIDevice *pci_dev)
|
|
|
|
{
|
|
|
|
PXBDev *pxb = PXB_DEV(pci_dev);
|
|
|
|
|
|
|
|
pxb_dev_list = g_list_remove(pxb_dev_list, pxb);
|
|
|
|
}
|
|
|
|
|
2015-06-02 14:23:06 +03:00
|
|
|
static Property pxb_dev_properties[] = {
|
|
|
|
/* Note: 0 is not a legal a PXB bus number. */
|
|
|
|
DEFINE_PROP_UINT8("bus_nr", PXBDev, bus_nr, 0),
|
2015-06-02 14:23:10 +03:00
|
|
|
DEFINE_PROP_UINT16("numa_node", PXBDev, numa_node, NUMA_NODE_UNASSIGNED),
|
2015-06-02 14:23:06 +03:00
|
|
|
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;
|
2015-06-19 05:40:17 +03:00
|
|
|
k->exit = pxb_dev_exitfn;
|
2015-06-02 14:23:06 +03:00
|
|
|
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)
|