diff options
Diffstat (limited to 'hw/pci/bridge/pci_bridge_dev.c')
-rw-r--r-- | hw/pci/bridge/pci_bridge_dev.c | 158 |
1 files changed, 158 insertions, 0 deletions
diff --git a/hw/pci/bridge/pci_bridge_dev.c b/hw/pci/bridge/pci_bridge_dev.c new file mode 100644 index 0000000000..971b432474 --- /dev/null +++ b/hw/pci/bridge/pci_bridge_dev.c @@ -0,0 +1,158 @@ +/* + * Standard PCI Bridge Device + * + * Copyright (c) 2011 Red Hat Inc. Author: Michael S. Tsirkin <mst@redhat.com> + * + * http://www.pcisig.com/specifications/conventional/pci_to_pci_bridge_architecture/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "hw/pci/pci_bridge.h" +#include "hw/pci/pci_ids.h" +#include "hw/pci/msi.h" +#include "hw/pci/shpc.h" +#include "hw/pci/slotid_cap.h" +#include "exec/memory.h" +#include "hw/pci/pci_bus.h" + +struct PCIBridgeDev { + PCIBridge bridge; + MemoryRegion bar; + uint8_t chassis_nr; +#define PCI_BRIDGE_DEV_F_MSI_REQ 0 + uint32_t flags; +}; +typedef struct PCIBridgeDev PCIBridgeDev; + +static int pci_bridge_dev_initfn(PCIDevice *dev) +{ + PCIBridge *br = DO_UPCAST(PCIBridge, dev, dev); + PCIBridgeDev *bridge_dev = DO_UPCAST(PCIBridgeDev, bridge, br); + int err; + + err = pci_bridge_initfn(dev, TYPE_PCI_BUS); + if (err) { + goto bridge_error; + } + memory_region_init(&bridge_dev->bar, "shpc-bar", shpc_bar_size(dev)); + err = shpc_init(dev, &br->sec_bus, &bridge_dev->bar, 0); + if (err) { + goto shpc_error; + } + err = slotid_cap_init(dev, 0, bridge_dev->chassis_nr, 0); + if (err) { + goto slotid_error; + } + if ((bridge_dev->flags & (1 << PCI_BRIDGE_DEV_F_MSI_REQ)) && + msi_supported) { + err = msi_init(dev, 0, 1, true, true); + if (err < 0) { + 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); + dev->config[PCI_INTERRUPT_PIN] = 0x1; + return 0; +msi_error: + slotid_cap_cleanup(dev); +slotid_error: + shpc_cleanup(dev, &bridge_dev->bar); +shpc_error: + memory_region_destroy(&bridge_dev->bar); + pci_bridge_exitfn(dev); +bridge_error: + return err; +} + +static void pci_bridge_dev_exitfn(PCIDevice *dev) +{ + PCIBridge *br = DO_UPCAST(PCIBridge, dev, dev); + PCIBridgeDev *bridge_dev = DO_UPCAST(PCIBridgeDev, bridge, br); + if (msi_present(dev)) { + msi_uninit(dev); + } + slotid_cap_cleanup(dev); + shpc_cleanup(dev, &bridge_dev->bar); + memory_region_destroy(&bridge_dev->bar); + pci_bridge_exitfn(dev); +} + +static void pci_bridge_dev_write_config(PCIDevice *d, + uint32_t address, uint32_t val, int len) +{ + pci_bridge_write_config(d, address, val, len); + if (msi_present(d)) { + msi_write_config(d, address, val, len); + } + shpc_cap_write_config(d, address, val, len); +} + +static void qdev_pci_bridge_dev_reset(DeviceState *qdev) +{ + PCIDevice *dev = DO_UPCAST(PCIDevice, qdev, qdev); + + pci_bridge_reset(qdev); + 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_END_OF_LIST(), +}; + +static const VMStateDescription pci_bridge_dev_vmstate = { + .name = "pci_bridge", + .fields = (VMStateField[]) { + VMSTATE_PCI_DEVICE(bridge.dev, PCIBridgeDev), + SHPC_VMSTATE(bridge.dev.shpc, PCIBridgeDev), + VMSTATE_END_OF_LIST() + } +}; + +static void pci_bridge_dev_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + k->init = pci_bridge_dev_initfn; + k->exit = pci_bridge_dev_exitfn; + k->config_write = pci_bridge_dev_write_config; + k->vendor_id = PCI_VENDOR_ID_REDHAT; + k->device_id = PCI_DEVICE_ID_REDHAT_BRIDGE; + k->class_id = PCI_CLASS_BRIDGE_PCI; + k->is_bridge = 1, + dc->desc = "Standard PCI Bridge"; + dc->reset = qdev_pci_bridge_dev_reset; + dc->props = pci_bridge_dev_properties; + dc->vmsd = &pci_bridge_dev_vmstate; +} + +static const TypeInfo pci_bridge_dev_info = { + .name = "pci-bridge", + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(PCIBridgeDev), + .class_init = pci_bridge_dev_class_init, +}; + +static void pci_bridge_dev_register(void) +{ + type_register_static(&pci_bridge_dev_info); +} + +type_init(pci_bridge_dev_register); |