From cfb0a50a06825a0bee349cae3fa94f96242e83a5 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Mon, 12 Jul 2010 19:36:40 +0900 Subject: [PATCH 01/39] pci: move out pci internal structures, PCIBus, PCIBridge, and pci_bus_info. move out pci internal structures, PCIBus, PCIBridge and pci_bus_info into private header file, pci_internals.h. This is a preparation. Later pci bridge implementation will be split out form pci.c into pci_bridge.c. Signed-off-by: Isaku Yamahata Signed-off-by: Michael S. Tsirkin --- hw/pci.c | 32 ++------------------------------ hw/pci_internals.h | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 30 deletions(-) create mode 100644 hw/pci_internals.h diff --git a/hw/pci.c b/hw/pci.c index a98d6f3ad1..9c83d747ea 100644 --- a/hw/pci.c +++ b/hw/pci.c @@ -23,6 +23,7 @@ */ #include "hw.h" #include "pci.h" +#include "pci_internals.h" #include "monitor.h" #include "net.h" #include "sysemu.h" @@ -36,31 +37,10 @@ # define PCI_DPRINTF(format, ...) do { } while (0) #endif -struct PCIBus { - BusState qbus; - int devfn_min; - pci_set_irq_fn set_irq; - pci_map_irq_fn map_irq; - pci_hotplug_fn hotplug; - DeviceState *hotplug_qdev; - void *irq_opaque; - PCIDevice *devices[256]; - PCIDevice *parent_dev; - target_phys_addr_t mem_base; - - QLIST_HEAD(, PCIBus) child; /* this will be replaced by qdev later */ - QLIST_ENTRY(PCIBus) sibling;/* this will be replaced by qdev later */ - - /* The bus IRQ state is the logical OR of the connected devices. - Keep a count of the number of devices with raised IRQs. */ - int nirq; - int *irq_count; -}; - static void pcibus_dev_print(Monitor *mon, DeviceState *dev, int indent); static char *pcibus_get_dev_path(DeviceState *dev); -static struct BusInfo pci_bus_info = { +struct BusInfo pci_bus_info = { .name = "PCI", .size = sizeof(PCIBus), .print_dev = pcibus_dev_print, @@ -1533,14 +1513,6 @@ PCIDevice *pci_nic_init_nofail(NICInfo *nd, const char *default_model, return res; } -typedef struct { - PCIDevice dev; - PCIBus bus; - uint32_t vid; - uint32_t did; -} PCIBridge; - - static void pci_bridge_update_mappings_fn(PCIBus *b, PCIDevice *d) { pci_update_mappings(d); diff --git a/hw/pci_internals.h b/hw/pci_internals.h new file mode 100644 index 0000000000..8a3026bdaf --- /dev/null +++ b/hw/pci_internals.h @@ -0,0 +1,40 @@ +#ifndef QEMU_PCI_INTERNALS_H +#define QEMU_PCI_INTERNALS_H + +/* + * This header files is private to pci.c and pci_bridge.c + * So following structures are opaque to others and shouldn't be + * accessed. + */ + +extern struct BusInfo pci_bus_info; + +struct PCIBus { + BusState qbus; + int devfn_min; + pci_set_irq_fn set_irq; + pci_map_irq_fn map_irq; + pci_hotplug_fn hotplug; + DeviceState *hotplug_qdev; + void *irq_opaque; + PCIDevice *devices[256]; + PCIDevice *parent_dev; + target_phys_addr_t mem_base; + + QLIST_HEAD(, PCIBus) child; /* this will be replaced by qdev later */ + QLIST_ENTRY(PCIBus) sibling;/* this will be replaced by qdev later */ + + /* The bus IRQ state is the logical OR of the connected devices. + Keep a count of the number of devices with raised IRQs. */ + int nirq; + int *irq_count; +}; + +typedef struct { + PCIDevice dev; + PCIBus bus; + uint32_t vid; + uint32_t did; +} PCIBridge; + +#endif /* QEMU_PCI_INTERNALS_H */ From 783753fd53fe513d37fbbfe6694c0c1ab9701fd1 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Tue, 13 Jul 2010 13:01:39 +0900 Subject: [PATCH 02/39] pci/bridge: split out pci bridge code into pci_bridge.c from pci.c Move pci bridge related code into pci_bridge.c from pci.c for further enhancement. pci.c is big enough now, so split it out. No code change but exporting some accesser functions. In fact, few pci bridge functions stays in pci.c. Signed-off-by: Isaku Yamahata Signed-off-by: Michael S. Tsirkin --- Makefile.objs | 2 +- hw/apb_pci.c | 1 + hw/dec_pci.c | 1 + hw/pci.c | 177 +--------------------------------------- hw/pci.h | 5 +- hw/pci_bridge.c | 208 ++++++++++++++++++++++++++++++++++++++++++++++++ hw/pci_bridge.h | 48 +++++++++++ 7 files changed, 262 insertions(+), 180 deletions(-) create mode 100644 hw/pci_bridge.c create mode 100644 hw/pci_bridge.h diff --git a/Makefile.objs b/Makefile.objs index 67f1b215b1..594894bbed 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -139,7 +139,7 @@ user-obj-y += cutils.o cache-utils.o hw-obj-y = hw-obj-y += vl.o loader.o hw-obj-y += virtio.o virtio-console.o -hw-obj-y += fw_cfg.o pci.o pci_host.o pcie_host.o +hw-obj-y += fw_cfg.o pci.o pci_host.o pcie_host.o pci_bridge.o hw-obj-y += watchdog.o hw-obj-$(CONFIG_ISA_MMIO) += isa_mmio.o hw-obj-$(CONFIG_ECC) += ecc.o diff --git a/hw/apb_pci.c b/hw/apb_pci.c index 0ecac55792..88ee4a9d92 100644 --- a/hw/apb_pci.c +++ b/hw/apb_pci.c @@ -29,6 +29,7 @@ #include "sysbus.h" #include "pci.h" #include "pci_host.h" +#include "pci_bridge.h" #include "rwhandler.h" #include "apb_pci.h" #include "sysemu.h" diff --git a/hw/dec_pci.c b/hw/dec_pci.c index ee49d5adf0..f7a9cdcfc3 100644 --- a/hw/dec_pci.c +++ b/hw/dec_pci.c @@ -27,6 +27,7 @@ #include "sysbus.h" #include "pci.h" #include "pci_host.h" +#include "pci_bridge.h" /* debug DEC */ //#define DEBUG_DEC diff --git a/hw/pci.c b/hw/pci.c index 9c83d747ea..2dc157724a 100644 --- a/hw/pci.c +++ b/hw/pci.c @@ -23,6 +23,7 @@ */ #include "hw.h" #include "pci.h" +#include "pci_bridge.h" #include "pci_internals.h" #include "monitor.h" #include "net.h" @@ -272,26 +273,6 @@ PCIBus *pci_register_bus(DeviceState *parent, const char *name, return bus; } -static void pci_register_secondary_bus(PCIBus *parent, - PCIBus *bus, - PCIDevice *dev, - pci_map_irq_fn map_irq, - const char *name) -{ - qbus_create_inplace(&bus->qbus, &pci_bus_info, &dev->qdev, name); - bus->map_irq = map_irq; - bus->parent_dev = dev; - - QLIST_INIT(&bus->child); - QLIST_INSERT_HEAD(&parent->child, bus, sibling); -} - -static void pci_unregister_secondary_bus(PCIBus *bus) -{ - assert(QLIST_EMPTY(&bus->child)); - QLIST_REMOVE(bus, sibling); -} - int pci_bus_num(PCIBus *s) { if (!s->parent_dev) @@ -799,75 +780,6 @@ void pci_register_bar(PCIDevice *pci_dev, int region_num, } } -static uint32_t pci_config_get_io_base(PCIDevice *d, - uint32_t base, uint32_t base_upper16) -{ - uint32_t val; - - val = ((uint32_t)d->config[base] & PCI_IO_RANGE_MASK) << 8; - if (d->config[base] & PCI_IO_RANGE_TYPE_32) { - val |= (uint32_t)pci_get_word(d->config + base_upper16) << 16; - } - return val; -} - -static pcibus_t pci_config_get_memory_base(PCIDevice *d, uint32_t base) -{ - return ((pcibus_t)pci_get_word(d->config + base) & PCI_MEMORY_RANGE_MASK) - << 16; -} - -static pcibus_t pci_config_get_pref_base(PCIDevice *d, - uint32_t base, uint32_t upper) -{ - pcibus_t tmp; - pcibus_t val; - - tmp = (pcibus_t)pci_get_word(d->config + base); - val = (tmp & PCI_PREF_RANGE_MASK) << 16; - if (tmp & PCI_PREF_RANGE_TYPE_64) { - val |= (pcibus_t)pci_get_long(d->config + upper) << 32; - } - return val; -} - -static pcibus_t pci_bridge_get_base(PCIDevice *bridge, uint8_t type) -{ - pcibus_t base; - if (type & PCI_BASE_ADDRESS_SPACE_IO) { - base = pci_config_get_io_base(bridge, - PCI_IO_BASE, PCI_IO_BASE_UPPER16); - } else { - if (type & PCI_BASE_ADDRESS_MEM_PREFETCH) { - base = pci_config_get_pref_base( - bridge, PCI_PREF_MEMORY_BASE, PCI_PREF_BASE_UPPER32); - } else { - base = pci_config_get_memory_base(bridge, PCI_MEMORY_BASE); - } - } - - return base; -} - -static pcibus_t pci_bridge_get_limit(PCIDevice *bridge, uint8_t type) -{ - pcibus_t limit; - if (type & PCI_BASE_ADDRESS_SPACE_IO) { - limit = pci_config_get_io_base(bridge, - PCI_IO_LIMIT, PCI_IO_LIMIT_UPPER16); - limit |= 0xfff; /* PCI bridge spec 3.2.5.6. */ - } else { - if (type & PCI_BASE_ADDRESS_MEM_PREFETCH) { - limit = pci_config_get_pref_base( - bridge, PCI_PREF_MEMORY_LIMIT, PCI_PREF_LIMIT_UPPER32); - } else { - limit = pci_config_get_memory_base(bridge, PCI_MEMORY_LIMIT); - } - limit |= 0xfffff; /* PCI bridge spec 3.2.5.{1, 8}. */ - } - return limit; -} - static void pci_bridge_filter(PCIDevice *d, pcibus_t *addr, pcibus_t *size, uint8_t type) { @@ -1518,7 +1430,7 @@ static void pci_bridge_update_mappings_fn(PCIBus *b, PCIDevice *d) pci_update_mappings(d); } -static void pci_bridge_update_mappings(PCIBus *b) +void pci_bridge_update_mappings(PCIBus *b) { PCIBus *child; @@ -1529,23 +1441,6 @@ static void pci_bridge_update_mappings(PCIBus *b) } } -static void pci_bridge_write_config(PCIDevice *d, - uint32_t address, uint32_t val, int len) -{ - pci_default_write_config(d, address, val, len); - - if (/* io base/limit */ - ranges_overlap(address, len, PCI_IO_BASE, 2) || - - /* memory base/limit, prefetchable base/limit and - io base/limit upper 16 */ - ranges_overlap(address, len, PCI_MEMORY_BASE, 20)) { - PCIBridge *s = container_of(d, PCIBridge, dev); - PCIBus *secondary_bus = &s->bus; - pci_bridge_update_mappings(secondary_bus); - } -} - PCIBus *pci_find_bus(PCIBus *bus, int bus_num) { PCIBus *sec; @@ -1589,54 +1484,6 @@ PCIDevice *pci_find_device(PCIBus *bus, int bus_num, int slot, int function) return bus->devices[PCI_DEVFN(slot, function)]; } -static int pci_bridge_initfn(PCIDevice *dev) -{ - PCIBridge *s = DO_UPCAST(PCIBridge, dev, dev); - - pci_config_set_vendor_id(s->dev.config, s->vid); - pci_config_set_device_id(s->dev.config, s->did); - - pci_set_word(dev->config + PCI_STATUS, - PCI_STATUS_66MHZ | PCI_STATUS_FAST_BACK); - pci_config_set_class(dev->config, PCI_CLASS_BRIDGE_PCI); - dev->config[PCI_HEADER_TYPE] = - (dev->config[PCI_HEADER_TYPE] & PCI_HEADER_TYPE_MULTI_FUNCTION) | - PCI_HEADER_TYPE_BRIDGE; - pci_set_word(dev->config + PCI_SEC_STATUS, - PCI_STATUS_66MHZ | PCI_STATUS_FAST_BACK); - return 0; -} - -static int pci_bridge_exitfn(PCIDevice *pci_dev) -{ - PCIBridge *s = DO_UPCAST(PCIBridge, dev, pci_dev); - PCIBus *bus = &s->bus; - pci_unregister_secondary_bus(bus); - return 0; -} - -PCIBus *pci_bridge_init(PCIBus *bus, int devfn, bool multifunction, - uint16_t vid, uint16_t did, - pci_map_irq_fn map_irq, const char *name) -{ - PCIDevice *dev; - PCIBridge *s; - - dev = pci_create_multifunction(bus, devfn, multifunction, "pci-bridge"); - qdev_prop_set_uint32(&dev->qdev, "vendorid", vid); - qdev_prop_set_uint32(&dev->qdev, "deviceid", did); - qdev_init_nofail(&dev->qdev); - - s = DO_UPCAST(PCIBridge, dev, dev); - pci_register_secondary_bus(bus, &s->bus, &s->dev, map_irq, name); - return &s->bus; -} - -PCIDevice *pci_bridge_get_device(PCIBus *bus) -{ - return bus->parent_dev; -} - static int pci_qdev_init(DeviceState *qdev, DeviceInfo *base) { PCIDevice *pci_dev = (PCIDevice *)qdev; @@ -1942,23 +1789,3 @@ static char *pcibus_get_dev_path(DeviceState *dev) return strdup(path); } -static PCIDeviceInfo bridge_info = { - .qdev.name = "pci-bridge", - .qdev.size = sizeof(PCIBridge), - .init = pci_bridge_initfn, - .exit = pci_bridge_exitfn, - .config_write = pci_bridge_write_config, - .is_bridge = 1, - .qdev.props = (Property[]) { - DEFINE_PROP_HEX32("vendorid", PCIBridge, vid, 0), - DEFINE_PROP_HEX32("deviceid", PCIBridge, did, 0), - DEFINE_PROP_END_OF_LIST(), - } -}; - -static void pci_register_devices(void) -{ - pci_qdev_register(&bridge_info); -} - -device_init(pci_register_devices) diff --git a/hw/pci.h b/hw/pci.h index 1eab7e7dda..c551f9661c 100644 --- a/hw/pci.h +++ b/hw/pci.h @@ -233,10 +233,7 @@ int pci_read_devaddr(Monitor *mon, const char *addr, int *domp, int *busp, void do_pci_info_print(Monitor *mon, const QObject *data); void do_pci_info(Monitor *mon, QObject **ret_data); -PCIBus *pci_bridge_init(PCIBus *bus, int devfn, bool multifunction, - uint16_t vid, uint16_t did, - pci_map_irq_fn map_irq, const char *name); -PCIDevice *pci_bridge_get_device(PCIBus *bus); +void pci_bridge_update_mappings(PCIBus *b); static inline void pci_set_byte(uint8_t *config, uint8_t val) diff --git a/hw/pci_bridge.c b/hw/pci_bridge.c new file mode 100644 index 0000000000..7f2787091c --- /dev/null +++ b/hw/pci_bridge.c @@ -0,0 +1,208 @@ +/* + * QEMU PCI bus manager + * + * Copyright (c) 2004 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to dea + + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM + + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +/* + * split out from pci.c + * Copyright (c) 2010 Isaku Yamahata + * VA Linux Systems Japan K.K. + */ + +#include "pci_bridge.h" +#include "pci_internals.h" + +PCIDevice *pci_bridge_get_device(PCIBus *bus) +{ + return bus->parent_dev; +} + +static void pci_register_secondary_bus(PCIBus *parent, + PCIBus *bus, + PCIDevice *dev, + pci_map_irq_fn map_irq, + const char *name) +{ + qbus_create_inplace(&bus->qbus, &pci_bus_info, &dev->qdev, name); + bus->map_irq = map_irq; + bus->parent_dev = dev; + + QLIST_INIT(&bus->child); + QLIST_INSERT_HEAD(&parent->child, bus, sibling); +} + +static void pci_unregister_secondary_bus(PCIBus *bus) +{ + assert(QLIST_EMPTY(&bus->child)); + QLIST_REMOVE(bus, sibling); +} + +static uint32_t pci_config_get_io_base(PCIDevice *d, + uint32_t base, uint32_t base_upper16) +{ + uint32_t val; + + val = ((uint32_t)d->config[base] & PCI_IO_RANGE_MASK) << 8; + if (d->config[base] & PCI_IO_RANGE_TYPE_32) { + val |= (uint32_t)pci_get_word(d->config + base_upper16) << 16; + } + return val; +} + +static pcibus_t pci_config_get_memory_base(PCIDevice *d, uint32_t base) +{ + return ((pcibus_t)pci_get_word(d->config + base) & PCI_MEMORY_RANGE_MASK) + << 16; +} + +static pcibus_t pci_config_get_pref_base(PCIDevice *d, + uint32_t base, uint32_t upper) +{ + pcibus_t tmp; + pcibus_t val; + + tmp = (pcibus_t)pci_get_word(d->config + base); + val = (tmp & PCI_PREF_RANGE_MASK) << 16; + if (tmp & PCI_PREF_RANGE_TYPE_64) { + val |= (pcibus_t)pci_get_long(d->config + upper) << 32; + } + return val; +} + +pcibus_t pci_bridge_get_base(PCIDevice *bridge, uint8_t type) +{ + pcibus_t base; + if (type & PCI_BASE_ADDRESS_SPACE_IO) { + base = pci_config_get_io_base(bridge, + PCI_IO_BASE, PCI_IO_BASE_UPPER16); + } else { + if (type & PCI_BASE_ADDRESS_MEM_PREFETCH) { + base = pci_config_get_pref_base( + bridge, PCI_PREF_MEMORY_BASE, PCI_PREF_BASE_UPPER32); + } else { + base = pci_config_get_memory_base(bridge, PCI_MEMORY_BASE); + } + } + + return base; +} + +pcibus_t pci_bridge_get_limit(PCIDevice *bridge, uint8_t type) +{ + pcibus_t limit; + if (type & PCI_BASE_ADDRESS_SPACE_IO) { + limit = pci_config_get_io_base(bridge, + PCI_IO_LIMIT, PCI_IO_LIMIT_UPPER16); + limit |= 0xfff; /* PCI bridge spec 3.2.5.6. */ + } else { + if (type & PCI_BASE_ADDRESS_MEM_PREFETCH) { + limit = pci_config_get_pref_base( + bridge, PCI_PREF_MEMORY_LIMIT, PCI_PREF_LIMIT_UPPER32); + } else { + limit = pci_config_get_memory_base(bridge, PCI_MEMORY_LIMIT); + } + limit |= 0xfffff; /* PCI bridge spec 3.2.5.{1, 8}. */ + } + return limit; +} + +static void pci_bridge_write_config(PCIDevice *d, + uint32_t address, uint32_t val, int len) +{ + pci_default_write_config(d, address, val, len); + + if (/* io base/limit */ + ranges_overlap(address, len, PCI_IO_BASE, 2) || + + /* memory base/limit, prefetchable base/limit and + io base/limit upper 16 */ + ranges_overlap(address, len, PCI_MEMORY_BASE, 20)) { + PCIBridge *s = container_of(d, PCIBridge, dev); + PCIBus *secondary_bus = &s->bus; + pci_bridge_update_mappings(secondary_bus); + } +} + +static int pci_bridge_initfn(PCIDevice *dev) +{ + PCIBridge *s = DO_UPCAST(PCIBridge, dev, dev); + + pci_config_set_vendor_id(s->dev.config, s->vid); + pci_config_set_device_id(s->dev.config, s->did); + + pci_set_word(dev->config + PCI_STATUS, + PCI_STATUS_66MHZ | PCI_STATUS_FAST_BACK); + pci_config_set_class(dev->config, PCI_CLASS_BRIDGE_PCI); + dev->config[PCI_HEADER_TYPE] = + (dev->config[PCI_HEADER_TYPE] & PCI_HEADER_TYPE_MULTI_FUNCTION) | + PCI_HEADER_TYPE_BRIDGE; + pci_set_word(dev->config + PCI_SEC_STATUS, + PCI_STATUS_66MHZ | PCI_STATUS_FAST_BACK); + return 0; +} + +static int pci_bridge_exitfn(PCIDevice *pci_dev) +{ + PCIBridge *s = DO_UPCAST(PCIBridge, dev, pci_dev); + PCIBus *bus = &s->bus; + pci_unregister_secondary_bus(bus); + return 0; +} + +PCIBus *pci_bridge_init(PCIBus *bus, int devfn, bool multifunction, + uint16_t vid, uint16_t did, + pci_map_irq_fn map_irq, const char *name) +{ + PCIDevice *dev; + PCIBridge *s; + + dev = pci_create_multifunction(bus, devfn, multifunction, "pci-bridge"); + qdev_prop_set_uint32(&dev->qdev, "vendorid", vid); + qdev_prop_set_uint32(&dev->qdev, "deviceid", did); + qdev_init_nofail(&dev->qdev); + + s = DO_UPCAST(PCIBridge, dev, dev); + pci_register_secondary_bus(bus, &s->bus, &s->dev, map_irq, name); + return &s->bus; +} + +static PCIDeviceInfo bridge_info = { + .qdev.name = "pci-bridge", + .qdev.size = sizeof(PCIBridge), + .init = pci_bridge_initfn, + .exit = pci_bridge_exitfn, + .config_write = pci_bridge_write_config, + .is_bridge = 1, + .qdev.props = (Property[]) { + DEFINE_PROP_HEX32("vendorid", PCIBridge, vid, 0), + DEFINE_PROP_HEX32("deviceid", PCIBridge, did, 0), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void pci_register_devices(void) +{ + pci_qdev_register(&bridge_info); +} + +device_init(pci_register_devices) diff --git a/hw/pci_bridge.h b/hw/pci_bridge.h new file mode 100644 index 0000000000..ddb2c82e25 --- /dev/null +++ b/hw/pci_bridge.h @@ -0,0 +1,48 @@ +/* + * QEMU PCI bridge + * + * Copyright (c) 2004 Fabrice Bellard + * + * 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, write to the Free Software + * Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * split out pci bus specific stuff from pci.[hc] to pci_bridge.[hc] + * Copyright (c) 2009 Isaku Yamahata + * VA Linux Systems Japan K.K. + * + */ + +#ifndef QEMU_PCI_BRIDGE_H +#define QEMU_PCI_BRIDGE_H + +#include "pci.h" + +PCIDevice *pci_bridge_get_device(PCIBus *bus); + +pcibus_t pci_bridge_get_base(PCIDevice *bridge, uint8_t type); +pcibus_t pci_bridge_get_limit(PCIDevice *bridge, uint8_t type); + +PCIBus *pci_bridge_init(PCIBus *bus, int devfn, bool multifunction, + uint16_t vid, uint16_t did, + pci_map_irq_fn map_irq, const char *name); + +#endif /* QEMU_PCI_BRIDGE_H */ +/* + * Local variables: + * c-indent-level: 4 + * c-basic-offset: 4 + * tab-width: 8 + * indent-tab-mode: nil + * End: + */ From 7e98e3af4e7454d53707b7b4d16b6e9bd5c21334 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Tue, 13 Jul 2010 13:01:40 +0900 Subject: [PATCH 03/39] pci_bridge: rename PCIBridge::bus -> PCIBridge::sec_bus. To avoid confusion of primary bus with secondary bus, rename PCIBridge::bus to PCIBridge::sec_bus. Signed-off-by: Isaku Yamahata Signed-off-by: Michael S. Tsirkin --- hw/pci_bridge.c | 10 ++++------ hw/pci_internals.h | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/hw/pci_bridge.c b/hw/pci_bridge.c index 7f2787091c..63052fe605 100644 --- a/hw/pci_bridge.c +++ b/hw/pci_bridge.c @@ -138,8 +138,7 @@ static void pci_bridge_write_config(PCIDevice *d, io base/limit upper 16 */ ranges_overlap(address, len, PCI_MEMORY_BASE, 20)) { PCIBridge *s = container_of(d, PCIBridge, dev); - PCIBus *secondary_bus = &s->bus; - pci_bridge_update_mappings(secondary_bus); + pci_bridge_update_mappings(&s->sec_bus); } } @@ -164,8 +163,7 @@ static int pci_bridge_initfn(PCIDevice *dev) static int pci_bridge_exitfn(PCIDevice *pci_dev) { PCIBridge *s = DO_UPCAST(PCIBridge, dev, pci_dev); - PCIBus *bus = &s->bus; - pci_unregister_secondary_bus(bus); + pci_unregister_secondary_bus(&s->sec_bus); return 0; } @@ -182,8 +180,8 @@ PCIBus *pci_bridge_init(PCIBus *bus, int devfn, bool multifunction, qdev_init_nofail(&dev->qdev); s = DO_UPCAST(PCIBridge, dev, dev); - pci_register_secondary_bus(bus, &s->bus, &s->dev, map_irq, name); - return &s->bus; + pci_register_secondary_bus(bus, &s->sec_bus, &s->dev, map_irq, name); + return &s->sec_bus; } static PCIDeviceInfo bridge_info = { diff --git a/hw/pci_internals.h b/hw/pci_internals.h index 8a3026bdaf..fa844abee1 100644 --- a/hw/pci_internals.h +++ b/hw/pci_internals.h @@ -32,7 +32,7 @@ struct PCIBus { typedef struct { PCIDevice dev; - PCIBus bus; + PCIBus sec_bus; uint32_t vid; uint32_t did; } PCIBridge; From 51a92333f8eb6d0fe685544f20ad56fc9af702f5 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Tue, 13 Jul 2010 13:01:41 +0900 Subject: [PATCH 04/39] pci_bridge: clean up: remove pci_{register, unregister}_secondary_bus() Remove pci_{register, unregister}_secondary_bus() by open code. They are old stype API and aren't used any more by others. So eliminate it. Signed-off-by: Isaku Yamahata Signed-off-by: Michael S. Tsirkin --- hw/pci_bridge.c | 32 ++++++++++---------------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/hw/pci_bridge.c b/hw/pci_bridge.c index 63052fe605..2f13c7dd5d 100644 --- a/hw/pci_bridge.c +++ b/hw/pci_bridge.c @@ -37,26 +37,6 @@ PCIDevice *pci_bridge_get_device(PCIBus *bus) return bus->parent_dev; } -static void pci_register_secondary_bus(PCIBus *parent, - PCIBus *bus, - PCIDevice *dev, - pci_map_irq_fn map_irq, - const char *name) -{ - qbus_create_inplace(&bus->qbus, &pci_bus_info, &dev->qdev, name); - bus->map_irq = map_irq; - bus->parent_dev = dev; - - QLIST_INIT(&bus->child); - QLIST_INSERT_HEAD(&parent->child, bus, sibling); -} - -static void pci_unregister_secondary_bus(PCIBus *bus) -{ - assert(QLIST_EMPTY(&bus->child)); - QLIST_REMOVE(bus, sibling); -} - static uint32_t pci_config_get_io_base(PCIDevice *d, uint32_t base, uint32_t base_upper16) { @@ -163,7 +143,8 @@ static int pci_bridge_initfn(PCIDevice *dev) static int pci_bridge_exitfn(PCIDevice *pci_dev) { PCIBridge *s = DO_UPCAST(PCIBridge, dev, pci_dev); - pci_unregister_secondary_bus(&s->sec_bus); + assert(QLIST_EMPTY(&s->sec_bus.child)); + QLIST_REMOVE(&s->sec_bus, sibling); return 0; } @@ -173,6 +154,7 @@ PCIBus *pci_bridge_init(PCIBus *bus, int devfn, bool multifunction, { PCIDevice *dev; PCIBridge *s; + PCIBus *sec_bus; dev = pci_create_multifunction(bus, devfn, multifunction, "pci-bridge"); qdev_prop_set_uint32(&dev->qdev, "vendorid", vid); @@ -180,7 +162,13 @@ PCIBus *pci_bridge_init(PCIBus *bus, int devfn, bool multifunction, qdev_init_nofail(&dev->qdev); s = DO_UPCAST(PCIBridge, dev, dev); - pci_register_secondary_bus(bus, &s->sec_bus, &s->dev, map_irq, name); + sec_bus = &s->sec_bus; + qbus_create_inplace(&sec_bus->qbus, &pci_bus_info, &dev->qdev, name); + sec_bus->parent_dev = dev; + sec_bus->map_irq = map_irq; + + QLIST_INIT(&sec_bus->child); + QLIST_INSERT_HEAD(&bus->child, sec_bus, sibling); return &s->sec_bus; } From 68f799944b72387c0ef9535612a212a5ea492059 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Tue, 13 Jul 2010 13:01:42 +0900 Subject: [PATCH 05/39] pci_bridge: introduce pci bridge library. introduce pci bridge library. convert apb bridge and dec p2p bridge to use new pci bridge library. save/restore is supported as a side effect. This is also preparation for pci express root/upstream/downstream port. Signed-off-by: Isaku Yamahata Signed-off-by: Michael S. Tsirkin --- hw/apb_pci.c | 53 +++++++++++++++----- hw/dec_pci.c | 45 +++++++++++++---- hw/pci_bridge.c | 122 +++++++++++++++++++++++++-------------------- hw/pci_bridge.h | 24 +++++++-- hw/pci_internals.h | 15 ++++-- qemu-common.h | 1 + 6 files changed, 177 insertions(+), 83 deletions(-) diff --git a/hw/apb_pci.c b/hw/apb_pci.c index 88ee4a9d92..c619112b12 100644 --- a/hw/apb_pci.c +++ b/hw/apb_pci.c @@ -30,6 +30,7 @@ #include "pci.h" #include "pci_host.h" #include "pci_bridge.h" +#include "pci_internals.h" #include "rwhandler.h" #include "apb_pci.h" #include "sysemu.h" @@ -294,9 +295,17 @@ static void pci_apb_set_irq(void *opaque, int irq_num, int level) } } -static void apb_pci_bridge_init(PCIBus *b) +static int apb_pci_bridge_initfn(PCIDevice *dev) { - PCIDevice *dev = pci_bridge_get_device(b); + int rc; + + rc = pci_bridge_initfn(dev); + if (rc < 0) { + return rc; + } + + pci_config_set_vendor_id(dev->config, PCI_VENDOR_ID_SUN); + pci_config_set_device_id(dev->config, PCI_DEVICE_ID_SUN_SIMBA); /* * command register: @@ -313,6 +322,7 @@ static void apb_pci_bridge_init(PCIBus *b) PCI_STATUS_FAST_BACK | PCI_STATUS_66MHZ | PCI_STATUS_DEVSEL_MEDIUM); pci_set_byte(dev->config + PCI_REVISION_ID, 0x11); + return 0; } PCIBus *pci_apb_init(target_phys_addr_t special_base, @@ -323,6 +333,8 @@ PCIBus *pci_apb_init(target_phys_addr_t special_base, SysBusDevice *s; APBState *d; unsigned int i; + PCIDevice *pci_dev; + PCIBridge *br; /* Ultrasparc PBM main bus */ dev = qdev_create(NULL, "pbm"); @@ -348,17 +360,21 @@ PCIBus *pci_apb_init(target_phys_addr_t special_base, pci_create_simple(d->bus, 0, "pbm"); /* APB secondary busses */ - *bus2 = pci_bridge_init(d->bus, PCI_DEVFN(1, 0), true, - PCI_VENDOR_ID_SUN, PCI_DEVICE_ID_SUN_SIMBA, - pci_apb_map_irq, - "Advanced PCI Bus secondary bridge 1"); - apb_pci_bridge_init(*bus2); + pci_dev = pci_create_multifunction(d->bus, PCI_DEVFN(1, 0), true, + "pbm-bridge"); + br = DO_UPCAST(PCIBridge, dev, pci_dev); + pci_bridge_map_irq(br, "Advanced PCI Bus secondary bridge 1", + pci_apb_map_irq); + qdev_init_nofail(&pci_dev->qdev); + *bus2 = pci_bridge_get_sec_bus(br); - *bus3 = pci_bridge_init(d->bus, PCI_DEVFN(1, 1), true, - PCI_VENDOR_ID_SUN, PCI_DEVICE_ID_SUN_SIMBA, - pci_apb_map_irq, - "Advanced PCI Bus secondary bridge 2"); - apb_pci_bridge_init(*bus3); + pci_dev = pci_create_multifunction(d->bus, PCI_DEVFN(1, 1), true, + "pbm-bridge"); + br = DO_UPCAST(PCIBridge, dev, pci_dev); + pci_bridge_map_irq(br, "Advanced PCI Bus secondary bridge 2", + pci_apb_map_irq); + qdev_init_nofail(&pci_dev->qdev); + *bus3 = pci_bridge_get_sec_bus(br); return d->bus; } @@ -441,10 +457,23 @@ static SysBusDeviceInfo pbm_host_info = { .qdev.reset = pci_pbm_reset, .init = pci_pbm_init_device, }; + +static PCIDeviceInfo pbm_pci_bridge_info = { + .qdev.name = "pbm-bridge", + .qdev.size = sizeof(PCIBridge), + .qdev.vmsd = &vmstate_pci_device, + .qdev.reset = pci_bridge_reset, + .init = apb_pci_bridge_initfn, + .exit = pci_bridge_exitfn, + .config_write = pci_bridge_write_config, + .is_bridge = 1, +}; + static void pbm_register_devices(void) { sysbus_register_withprop(&pbm_host_info); pci_qdev_register(&pbm_pci_host_info); + pci_qdev_register(&pbm_pci_bridge_info); } device_init(pbm_register_devices) diff --git a/hw/dec_pci.c b/hw/dec_pci.c index f7a9cdcfc3..aa07ab7d84 100644 --- a/hw/dec_pci.c +++ b/hw/dec_pci.c @@ -28,6 +28,7 @@ #include "pci.h" #include "pci_host.h" #include "pci_bridge.h" +#include "pci_internals.h" /* debug DEC */ //#define DEBUG_DEC @@ -49,18 +50,43 @@ static int dec_map_irq(PCIDevice *pci_dev, int irq_num) return irq_num; } +static int dec_21154_initfn(PCIDevice *dev) +{ + int rc; + + rc = pci_bridge_initfn(dev); + if (rc < 0) { + return rc; + } + + pci_config_set_vendor_id(dev->config, PCI_VENDOR_ID_DEC); + pci_config_set_device_id(dev->config, PCI_DEVICE_ID_DEC_21154); + return 0; +} + +static PCIDeviceInfo dec_21154_pci_bridge_info = { + .qdev.name = "dec-21154-p2p-bridge", + .qdev.desc = "DEC 21154 PCI-PCI bridge", + .qdev.size = sizeof(PCIBridge), + .qdev.vmsd = &vmstate_pci_device, + .qdev.reset = pci_bridge_reset, + .init = dec_21154_initfn, + .exit = pci_bridge_exitfn, + .config_write = pci_bridge_write_config, + .is_bridge = 1, +}; + PCIBus *pci_dec_21154_init(PCIBus *parent_bus, int devfn) { - DeviceState *dev; - PCIBus *ret; + PCIDevice *dev; + PCIBridge *br; - dev = qdev_create(NULL, "dec-21154"); - qdev_init_nofail(dev); - ret = pci_bridge_init(parent_bus, devfn, false, - PCI_VENDOR_ID_DEC, PCI_DEVICE_ID_DEC_21154, - dec_map_irq, "DEC 21154 PCI-PCI bridge"); - - return ret; + dev = pci_create_multifunction(parent_bus, devfn, false, + "dec-21154-p2p-bridge"); + br = DO_UPCAST(PCIBridge, dev, dev); + pci_bridge_map_irq(br, "DEC 21154 PCI-PCI bridge", dec_map_irq); + qdev_init_nofail(&dev->qdev); + return pci_bridge_get_sec_bus(br); } static int pci_dec_21154_init_device(SysBusDevice *dev) @@ -99,6 +125,7 @@ static void dec_register_devices(void) sysbus_register_dev("dec-21154", sizeof(DECState), pci_dec_21154_init_device); pci_qdev_register(&dec_21154_pci_host_info); + pci_qdev_register(&dec_21154_pci_bridge_info); } device_init(dec_register_devices) diff --git a/hw/pci_bridge.c b/hw/pci_bridge.c index 2f13c7dd5d..198c3c7908 100644 --- a/hw/pci_bridge.c +++ b/hw/pci_bridge.c @@ -32,12 +32,19 @@ #include "pci_bridge.h" #include "pci_internals.h" +/* Accessor function to get parent bridge device from pci bus. */ PCIDevice *pci_bridge_get_device(PCIBus *bus) { return bus->parent_dev; } -static uint32_t pci_config_get_io_base(PCIDevice *d, +/* Accessor function to get secondary bus from pci-to-pci bridge device */ +PCIBus *pci_bridge_get_sec_bus(PCIBridge *br) +{ + return &br->sec_bus; +} + +static uint32_t pci_config_get_io_base(const PCIDevice *d, uint32_t base, uint32_t base_upper16) { uint32_t val; @@ -49,13 +56,13 @@ static uint32_t pci_config_get_io_base(PCIDevice *d, return val; } -static pcibus_t pci_config_get_memory_base(PCIDevice *d, uint32_t base) +static pcibus_t pci_config_get_memory_base(const PCIDevice *d, uint32_t base) { return ((pcibus_t)pci_get_word(d->config + base) & PCI_MEMORY_RANGE_MASK) << 16; } -static pcibus_t pci_config_get_pref_base(PCIDevice *d, +static pcibus_t pci_config_get_pref_base(const PCIDevice *d, uint32_t base, uint32_t upper) { pcibus_t tmp; @@ -69,7 +76,8 @@ static pcibus_t pci_config_get_pref_base(PCIDevice *d, return val; } -pcibus_t pci_bridge_get_base(PCIDevice *bridge, uint8_t type) +/* accessor function to get bridge filtering base address */ +pcibus_t pci_bridge_get_base(const PCIDevice *bridge, uint8_t type) { pcibus_t base; if (type & PCI_BASE_ADDRESS_SPACE_IO) { @@ -87,7 +95,8 @@ pcibus_t pci_bridge_get_base(PCIDevice *bridge, uint8_t type) return base; } -pcibus_t pci_bridge_get_limit(PCIDevice *bridge, uint8_t type) +/* accessor funciton to get bridge filtering limit */ +pcibus_t pci_bridge_get_limit(const PCIDevice *bridge, uint8_t type) { pcibus_t limit; if (type & PCI_BASE_ADDRESS_SPACE_IO) { @@ -106,7 +115,8 @@ pcibus_t pci_bridge_get_limit(PCIDevice *bridge, uint8_t type) return limit; } -static void pci_bridge_write_config(PCIDevice *d, +/* default write_config function for PCI-to-PCI bridge */ +void pci_bridge_write_config(PCIDevice *d, uint32_t address, uint32_t val, int len) { pci_default_write_config(d, address, val, len); @@ -122,12 +132,41 @@ static void pci_bridge_write_config(PCIDevice *d, } } -static int pci_bridge_initfn(PCIDevice *dev) +/* reset bridge specific configuration registers */ +void pci_bridge_reset_reg(PCIDevice *dev) { - PCIBridge *s = DO_UPCAST(PCIBridge, dev, dev); + uint8_t *conf = dev->config; - pci_config_set_vendor_id(s->dev.config, s->vid); - pci_config_set_device_id(s->dev.config, s->did); + conf[PCI_PRIMARY_BUS] = 0; + conf[PCI_SECONDARY_BUS] = 0; + conf[PCI_SUBORDINATE_BUS] = 0; + conf[PCI_SEC_LATENCY_TIMER] = 0; + + conf[PCI_IO_BASE] = 0; + conf[PCI_IO_LIMIT] = 0; + pci_set_word(conf + PCI_MEMORY_BASE, 0); + pci_set_word(conf + PCI_MEMORY_LIMIT, 0); + pci_set_word(conf + PCI_PREF_MEMORY_BASE, 0); + pci_set_word(conf + PCI_PREF_MEMORY_LIMIT, 0); + pci_set_word(conf + PCI_PREF_BASE_UPPER32, 0); + pci_set_word(conf + PCI_PREF_LIMIT_UPPER32, 0); + + pci_set_word(conf + PCI_BRIDGE_CONTROL, 0); +} + +/* default reset function for PCI-to-PCI bridge */ +void pci_bridge_reset(DeviceState *qdev) +{ + PCIDevice *dev = DO_UPCAST(PCIDevice, qdev, qdev); + pci_bridge_reset_reg(dev); +} + +/* default qdev initialization function for PCI-to-PCI bridge */ +int pci_bridge_initfn(PCIDevice *dev) +{ + PCIBus *parent = dev->bus; + PCIBridge *br = DO_UPCAST(PCIBridge, dev, dev); + PCIBus *sec_bus = &br->sec_bus; pci_set_word(dev->config + PCI_STATUS, PCI_STATUS_66MHZ | PCI_STATUS_FAST_BACK); @@ -137,58 +176,35 @@ static int pci_bridge_initfn(PCIDevice *dev) PCI_HEADER_TYPE_BRIDGE; pci_set_word(dev->config + PCI_SEC_STATUS, PCI_STATUS_66MHZ | PCI_STATUS_FAST_BACK); + + qbus_create_inplace(&sec_bus->qbus, &pci_bus_info, &dev->qdev, + br->bus_name); + sec_bus->parent_dev = dev; + sec_bus->map_irq = br->map_irq; + + QLIST_INIT(&sec_bus->child); + QLIST_INSERT_HEAD(&parent->child, sec_bus, sibling); return 0; } -static int pci_bridge_exitfn(PCIDevice *pci_dev) +/* default qdev clean up function for PCI-to-PCI bridge */ +int pci_bridge_exitfn(PCIDevice *pci_dev) { PCIBridge *s = DO_UPCAST(PCIBridge, dev, pci_dev); assert(QLIST_EMPTY(&s->sec_bus.child)); QLIST_REMOVE(&s->sec_bus, sibling); + /* qbus_free() is called automatically by qdev_free() */ return 0; } -PCIBus *pci_bridge_init(PCIBus *bus, int devfn, bool multifunction, - uint16_t vid, uint16_t did, - pci_map_irq_fn map_irq, const char *name) +/* + * before qdev initialization(qdev_init()), this function sets bus_name and + * map_irq callback which are necessry for pci_bridge_initfn() to + * initialize bus. + */ +void pci_bridge_map_irq(PCIBridge *br, const char* bus_name, + pci_map_irq_fn map_irq) { - PCIDevice *dev; - PCIBridge *s; - PCIBus *sec_bus; - - dev = pci_create_multifunction(bus, devfn, multifunction, "pci-bridge"); - qdev_prop_set_uint32(&dev->qdev, "vendorid", vid); - qdev_prop_set_uint32(&dev->qdev, "deviceid", did); - qdev_init_nofail(&dev->qdev); - - s = DO_UPCAST(PCIBridge, dev, dev); - sec_bus = &s->sec_bus; - qbus_create_inplace(&sec_bus->qbus, &pci_bus_info, &dev->qdev, name); - sec_bus->parent_dev = dev; - sec_bus->map_irq = map_irq; - - QLIST_INIT(&sec_bus->child); - QLIST_INSERT_HEAD(&bus->child, sec_bus, sibling); - return &s->sec_bus; + br->map_irq = map_irq; + br->bus_name = bus_name; } - -static PCIDeviceInfo bridge_info = { - .qdev.name = "pci-bridge", - .qdev.size = sizeof(PCIBridge), - .init = pci_bridge_initfn, - .exit = pci_bridge_exitfn, - .config_write = pci_bridge_write_config, - .is_bridge = 1, - .qdev.props = (Property[]) { - DEFINE_PROP_HEX32("vendorid", PCIBridge, vid, 0), - DEFINE_PROP_HEX32("deviceid", PCIBridge, did, 0), - DEFINE_PROP_END_OF_LIST(), - } -}; - -static void pci_register_devices(void) -{ - pci_qdev_register(&bridge_info); -} - -device_init(pci_register_devices) diff --git a/hw/pci_bridge.h b/hw/pci_bridge.h index ddb2c82e25..63ada199a5 100644 --- a/hw/pci_bridge.h +++ b/hw/pci_bridge.h @@ -29,13 +29,27 @@ #include "pci.h" PCIDevice *pci_bridge_get_device(PCIBus *bus); +PCIBus *pci_bridge_get_sec_bus(PCIBridge *br); -pcibus_t pci_bridge_get_base(PCIDevice *bridge, uint8_t type); -pcibus_t pci_bridge_get_limit(PCIDevice *bridge, uint8_t type); +pcibus_t pci_bridge_get_base(const PCIDevice *bridge, uint8_t type); +pcibus_t pci_bridge_get_limit(const PCIDevice *bridge, uint8_t type); -PCIBus *pci_bridge_init(PCIBus *bus, int devfn, bool multifunction, - uint16_t vid, uint16_t did, - pci_map_irq_fn map_irq, const char *name); +void pci_bridge_write_config(PCIDevice *d, + uint32_t address, uint32_t val, int len); +void pci_bridge_reset_reg(PCIDevice *dev); +void pci_bridge_reset(DeviceState *qdev); + +int pci_bridge_initfn(PCIDevice *pci_dev); +int pci_bridge_exitfn(PCIDevice *pci_dev); + + +/* + * before qdev initialization(qdev_init()), this function sets bus_name and + * map_irq callback which are necessry for pci_bridge_initfn() to + * initialize bus. + */ +void pci_bridge_map_irq(PCIBridge *br, const char* bus_name, + pci_map_irq_fn map_irq); #endif /* QEMU_PCI_BRIDGE_H */ /* diff --git a/hw/pci_internals.h b/hw/pci_internals.h index fa844abee1..e3c93a3cc5 100644 --- a/hw/pci_internals.h +++ b/hw/pci_internals.h @@ -5,6 +5,11 @@ * This header files is private to pci.c and pci_bridge.c * So following structures are opaque to others and shouldn't be * accessed. + * + * For pci-to-pci bridge needs to include this header file to embed + * PCIBridge in its structure or to get sizeof(PCIBridge), + * However, they shouldn't access those following members directly. + * Use accessor function in pci.h, pci_bridge.h */ extern struct BusInfo pci_bus_info; @@ -30,11 +35,13 @@ struct PCIBus { int *irq_count; }; -typedef struct { +struct PCIBridge { PCIDevice dev; + + /* private member */ PCIBus sec_bus; - uint32_t vid; - uint32_t did; -} PCIBridge; + pci_map_irq_fn map_irq; + const char *bus_name; +}; #endif /* QEMU_PCI_INTERNALS_H */ diff --git a/qemu-common.h b/qemu-common.h index 3fb2f0b375..d735235f84 100644 --- a/qemu-common.h +++ b/qemu-common.h @@ -219,6 +219,7 @@ typedef struct PCIHostState PCIHostState; typedef struct PCIExpressHost PCIExpressHost; typedef struct PCIBus PCIBus; typedef struct PCIDevice PCIDevice; +typedef struct PCIBridge PCIBridge; typedef struct SerialState SerialState; typedef struct IRQState *qemu_irq; typedef struct PCMCIACardState PCMCIACardState; From ca77089d2d8e73283bfc73f03d954504561e1ce8 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Mon, 6 Sep 2010 16:46:16 +0900 Subject: [PATCH 06/39] pci: consolidate pci_add_capability_at_offset() into pci_add_capability(). By making pci_add_capability() the special case of pci_add_capability_at_offset() of offset = 0, consolidate pci_add_capability_at_offset() into pci_add_capability(). Cc: Stefan Weil Cc: Michael S. Tsirkin Signed-off-by: Isaku Yamahata Signed-off-by: Michael S. Tsirkin --- hw/eepro100.c | 4 ++-- hw/msix.c | 3 ++- hw/pci.c | 33 ++++++++++++++++++--------------- hw/pci.h | 5 ++--- 4 files changed, 24 insertions(+), 21 deletions(-) diff --git a/hw/eepro100.c b/hw/eepro100.c index 2b75c8f495..8cbc3aa7a2 100644 --- a/hw/eepro100.c +++ b/hw/eepro100.c @@ -539,8 +539,8 @@ static void e100_pci_reset(EEPRO100State * s, E100PCIDeviceInfo *e100_device) if (e100_device->power_management) { /* Power Management Capabilities */ int cfg_offset = 0xdc; - int r = pci_add_capability_at_offset(&s->dev, PCI_CAP_ID_PM, - cfg_offset, PCI_PM_SIZEOF); + int r = pci_add_capability(&s->dev, PCI_CAP_ID_PM, + cfg_offset, PCI_PM_SIZEOF); assert(r >= 0); pci_set_word(pci_conf + cfg_offset + PCI_PM_PMC, 0x7e21); #if 0 /* TODO: replace dummy code for power management emulation. */ diff --git a/hw/msix.c b/hw/msix.c index d99403a0e9..7ce63ebe63 100644 --- a/hw/msix.c +++ b/hw/msix.c @@ -73,7 +73,8 @@ static int msix_add_config(struct PCIDevice *pdev, unsigned short nentries, } pdev->msix_bar_size = new_size; - config_offset = pci_add_capability(pdev, PCI_CAP_ID_MSIX, MSIX_CAP_LENGTH); + config_offset = pci_add_capability(pdev, PCI_CAP_ID_MSIX, + 0, MSIX_CAP_LENGTH); if (config_offset < 0) return config_offset; config = pdev->config + config_offset; diff --git a/hw/pci.c b/hw/pci.c index 2dc157724a..754ffb3c3d 100644 --- a/hw/pci.c +++ b/hw/pci.c @@ -1682,11 +1682,25 @@ static void pci_del_option_rom(PCIDevice *pdev) pdev->rom_offset = 0; } -/* Reserve space and add capability to the linked list in pci config space */ -int pci_add_capability_at_offset(PCIDevice *pdev, uint8_t cap_id, - uint8_t offset, uint8_t size) +/* + * if !offset + * Reserve space and add capability to the linked list in pci config space + * + * if offset = 0, + * Find and reserve space and add capability to the linked list + * in pci config space */ +int pci_add_capability(PCIDevice *pdev, uint8_t cap_id, + uint8_t offset, uint8_t size) { - uint8_t *config = pdev->config + offset; + uint8_t *config; + if (!offset) { + offset = pci_find_space(pdev, size); + if (!offset) { + return -ENOSPC; + } + } + + config = pdev->config + offset; config[PCI_CAP_LIST_ID] = cap_id; config[PCI_CAP_LIST_NEXT] = pdev->config[PCI_CAPABILITY_LIST]; pdev->config[PCI_CAPABILITY_LIST] = offset; @@ -1699,17 +1713,6 @@ int pci_add_capability_at_offset(PCIDevice *pdev, uint8_t cap_id, return offset; } -/* Find and reserve space and add capability to the linked list - * in pci config space */ -int pci_add_capability(PCIDevice *pdev, uint8_t cap_id, uint8_t size) -{ - uint8_t offset = pci_find_space(pdev, size); - if (!offset) { - return -ENOSPC; - } - return pci_add_capability_at_offset(pdev, cap_id, offset, size); -} - /* Unlink capability from the pci config space. */ void pci_del_capability(PCIDevice *pdev, uint8_t cap_id, uint8_t size) { diff --git a/hw/pci.h b/hw/pci.h index c551f9661c..2ddba59589 100644 --- a/hw/pci.h +++ b/hw/pci.h @@ -183,9 +183,8 @@ void pci_register_bar(PCIDevice *pci_dev, int region_num, pcibus_t size, int type, PCIMapIORegionFunc *map_func); -int pci_add_capability(PCIDevice *pci_dev, uint8_t cap_id, uint8_t cap_size); -int pci_add_capability_at_offset(PCIDevice *pci_dev, uint8_t cap_id, - uint8_t cap_offset, uint8_t cap_size); +int pci_add_capability(PCIDevice *pdev, uint8_t cap_id, + uint8_t offset, uint8_t size); void pci_del_capability(PCIDevice *pci_dev, uint8_t cap_id, uint8_t cap_size); From f4c817e000e50e9a0db8f95ce6496628bd70733d Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Mon, 6 Sep 2010 16:46:17 +0900 Subject: [PATCH 07/39] pci bridge: add helper function for ssvid capability. helper function to add ssvid capability. Signed-off-by: Isaku Yamahata Signed-off-by: Michael S. Tsirkin --- hw/pci_bridge.c | 19 +++++++++++++++++++ hw/pci_bridge.h | 3 +++ 2 files changed, 22 insertions(+) diff --git a/hw/pci_bridge.c b/hw/pci_bridge.c index 198c3c7908..638e3b35eb 100644 --- a/hw/pci_bridge.c +++ b/hw/pci_bridge.c @@ -32,6 +32,25 @@ #include "pci_bridge.h" #include "pci_internals.h" +/* PCI bridge subsystem vendor ID helper functions */ +#define PCI_SSVID_SIZEOF 8 +#define PCI_SSVID_SVID 4 +#define PCI_SSVID_SSID 6 + +int pci_bridge_ssvid_init(PCIDevice *dev, uint8_t offset, + uint16_t svid, uint16_t ssid) +{ + int pos; + pos = pci_add_capability(dev, PCI_CAP_ID_SSVID, offset, PCI_SSVID_SIZEOF); + if (pos < 0) { + return pos; + } + + pci_set_word(dev->config + pos + PCI_SSVID_SVID, svid); + pci_set_word(dev->config + pos + PCI_SSVID_SSID, ssid); + return pos; +} + /* Accessor function to get parent bridge device from pci bus. */ PCIDevice *pci_bridge_get_device(PCIBus *bus) { diff --git a/hw/pci_bridge.h b/hw/pci_bridge.h index 63ada199a5..f6fade0322 100644 --- a/hw/pci_bridge.h +++ b/hw/pci_bridge.h @@ -28,6 +28,9 @@ #include "pci.h" +int pci_bridge_ssvid_init(PCIDevice *dev, uint8_t offset, + uint16_t svid, uint16_t ssid); + PCIDevice *pci_bridge_get_device(PCIBus *bus); PCIBus *pci_bridge_get_sec_bus(PCIBridge *br); From 5beb8ad503c88a76f2b8106c3b74b4ce485a60e1 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Mon, 6 Sep 2010 16:46:18 +0900 Subject: [PATCH 08/39] pci: call hotplug callback even when not hotplug case for later use. call hotplug callback even when not hotplug case for later use. And move hotplug check into hotplug callback. PCIE slot needs this for card presence detection. Signed-off-by: Isaku Yamahata Signed-off-by: Michael S. Tsirkin --- hw/acpi_piix4.c | 3 +++ hw/pci.c | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/hw/acpi_piix4.c b/hw/acpi_piix4.c index bfa1d9a1ae..24dfcf2039 100644 --- a/hw/acpi_piix4.c +++ b/hw/acpi_piix4.c @@ -611,6 +611,9 @@ static int piix4_device_hotplug(DeviceState *qdev, PCIDevice *dev, int state) PIIX4PMState *s = DO_UPCAST(PIIX4PMState, dev, DO_UPCAST(PCIDevice, qdev, qdev)); + if (!dev->qdev.hotplugged) + return 0; + s->pci0_status.up = 0; s->pci0_status.down = 0; if (state) { diff --git a/hw/pci.c b/hw/pci.c index 754ffb3c3d..bb9ddea733 100644 --- a/hw/pci.c +++ b/hw/pci.c @@ -1514,7 +1514,8 @@ static int pci_qdev_init(DeviceState *qdev, DeviceInfo *base) pci_dev->romfile = qemu_strdup(info->romfile); pci_add_option_rom(pci_dev); - if (qdev->hotplugged) { + if (bus->hotplug) { + /* lower layer must check qdev->hotplugged */ rc = bus->hotplug(bus->hotplug_qdev, pci_dev, 1); if (rc != 0) { int r = pci_unregister_device(&pci_dev->qdev); From 43c945f16a172f07145ca1f30abb958070228690 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Mon, 6 Sep 2010 16:46:19 +0900 Subject: [PATCH 09/39] pci: make pci_parse_devfn() aware of func. make pci_parse_devfn() aware of func. With func = NULL it behave as before. This will be used later. Signed-off-by: Isaku Yamahata Signed-off-by: Michael S. Tsirkin --- hw/pci.c | 34 ++++++++++++++++++++++++++-------- hw/pci.h | 2 ++ 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/hw/pci.c b/hw/pci.c index bb9ddea733..f03b83e3cf 100644 --- a/hw/pci.c +++ b/hw/pci.c @@ -424,15 +424,18 @@ static void pci_set_default_subsystem_id(PCIDevice *pci_dev) } /* - * Parse [[:]:], return -1 on error + * Parse [[:]:], return -1 on error if funcp == NULL + * [[:]:]., return -1 on error */ -static int pci_parse_devaddr(const char *addr, int *domp, int *busp, unsigned *slotp) +int pci_parse_devaddr(const char *addr, int *domp, int *busp, + unsigned int *slotp, unsigned int *funcp) { const char *p; char *e; unsigned long val; unsigned long dom = 0, bus = 0; - unsigned slot = 0; + unsigned int slot = 0; + unsigned int func = 0; p = addr; val = strtoul(p, &e, 16); @@ -454,11 +457,24 @@ static int pci_parse_devaddr(const char *addr, int *domp, int *busp, unsigned *s } } - if (dom > 0xffff || bus > 0xff || val > 0x1f) - return -1; - slot = val; + if (funcp != NULL) { + if (*e != '.') + return -1; + + p = e + 1; + val = strtoul(p, &e, 16); + if (e == p) + return -1; + + func = val; + } + + /* if funcp == NULL func is 0 */ + if (dom > 0xffff || bus > 0xff || slot > 0x1f || func > 7) + return -1; + if (*e) return -1; @@ -469,6 +485,8 @@ static int pci_parse_devaddr(const char *addr, int *domp, int *busp, unsigned *s *domp = dom; *busp = bus; *slotp = slot; + if (funcp != NULL) + *funcp = func; return 0; } @@ -479,7 +497,7 @@ int pci_read_devaddr(Monitor *mon, const char *addr, int *domp, int *busp, if (!strncmp(addr, "pci_addr=", 9)) { addr += 9; } - if (pci_parse_devaddr(addr, domp, busp, slotp)) { + if (pci_parse_devaddr(addr, domp, busp, slotp, NULL)) { monitor_printf(mon, "Invalid pci address\n"); return -1; } @@ -496,7 +514,7 @@ PCIBus *pci_get_bus_devfn(int *devfnp, const char *devaddr) return pci_find_bus(pci_find_root_bus(0), 0); } - if (pci_parse_devaddr(devaddr, &dom, &bus, &slot) < 0) { + if (pci_parse_devaddr(devaddr, &dom, &bus, &slot, NULL) < 0) { return NULL; } diff --git a/hw/pci.h b/hw/pci.h index 2ddba59589..2b4c318898 100644 --- a/hw/pci.h +++ b/hw/pci.h @@ -227,6 +227,8 @@ PCIBus *pci_find_bus(PCIBus *bus, int bus_num); PCIDevice *pci_find_device(PCIBus *bus, int bus_num, int slot, int function); PCIBus *pci_get_bus_devfn(int *devfnp, const char *devaddr); +int pci_parse_devaddr(const char *addr, int *domp, int *busp, + unsigned int *slotp, unsigned int *funcp); int pci_read_devaddr(Monitor *mon, const char *addr, int *domp, int *busp, unsigned *slotp); From cf4c01fde264416dc8b1a1904bc9068a4af78cb7 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Mon, 6 Sep 2010 16:46:20 +0900 Subject: [PATCH 10/39] pci_ids.h: add vendor id of Texas Intesruments add vendor id of Texas Intesruments. Signed-off-by: Isaku Yamahata Signed-off-by: Michael S. Tsirkin --- hw/pci_ids.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hw/pci_ids.h b/hw/pci_ids.h index 39e9f1d89d..82cba7eeb3 100644 --- a/hw/pci_ids.h +++ b/hw/pci_ids.h @@ -57,6 +57,8 @@ #define PCI_VENDOR_ID_AMD 0x1022 #define PCI_DEVICE_ID_AMD_LANCE 0x2000 +#define PCI_VENDOR_ID_TI 0x104c + #define PCI_VENDOR_ID_MOTOROLA 0x1057 #define PCI_DEVICE_ID_MOTOROLA_MPC106 0x0002 #define PCI_DEVICE_ID_MOTOROLA_RAVEN 0x4801 From 5a9ff3819a1023b63b94ad4fb82da973f93f65d0 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Thu, 9 Sep 2010 11:48:55 +0900 Subject: [PATCH 11/39] pci: sorting out type confusion in pci_register_bar(). This patch sorts out invalid use of pcibus_t. In pci_register_bar(), pcibus_t wmask is used. It should, however, be uint64_t because it is used to set pci configuration space value(PCIDevice::wmask) by pci_set_quad() or pci_set_long(). Signed-off-by: Isaku Yamahata Signed-off-by: Michael S. Tsirkin --- hw/pci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hw/pci.c b/hw/pci.c index f03b83e3cf..c28b8a1835 100644 --- a/hw/pci.c +++ b/hw/pci.c @@ -763,7 +763,7 @@ void pci_register_bar(PCIDevice *pci_dev, int region_num, { PCIIORegion *r; uint32_t addr; - pcibus_t wmask; + uint64_t wmask; if ((unsigned int)region_num >= PCI_NUM_REGIONS) return; From 2bbb9c2f7f36d0457cda5f27d7e4422219b3acd8 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Thu, 9 Sep 2010 11:48:56 +0900 Subject: [PATCH 12/39] pci: don't ignore invalid parameter for pci_register_bar(). Abort when invalid value for region_num is passed to pci_register_bar. That is caller's bug. Abort instead of silently ignoring invalid value. Signed-off-by: Isaku Yamahata Signed-off-by: Michael S. Tsirkin --- hw/pci.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/hw/pci.c b/hw/pci.c index c28b8a1835..8f48d9b080 100644 --- a/hw/pci.c +++ b/hw/pci.c @@ -765,9 +765,8 @@ void pci_register_bar(PCIDevice *pci_dev, int region_num, uint32_t addr; uint64_t wmask; - if ((unsigned int)region_num >= PCI_NUM_REGIONS) - return; - + assert(region_num >= 0); + assert(region_num < PCI_NUM_REGIONS); if (size & (size-1)) { fprintf(stderr, "ERROR: PCI region size must be pow2 " "type=0x%x, size=0x%"FMT_PCIBUS"\n", type, size); From 0bb750ef9e897ba5f4d9899ddc7e222e809bcbbd Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Thu, 9 Sep 2010 11:48:57 +0900 Subject: [PATCH 13/39] pci: improve signature of pci_register_bar(). Make type uint8_t from int because PCIIORegion::type is uint8_t. Signed-off-by: Isaku Yamahata --- hw/pci.c | 2 +- hw/pci.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hw/pci.c b/hw/pci.c index 8f48d9b080..97a7b232ac 100644 --- a/hw/pci.c +++ b/hw/pci.c @@ -758,7 +758,7 @@ static int pci_unregister_device(DeviceState *dev) } void pci_register_bar(PCIDevice *pci_dev, int region_num, - pcibus_t size, int type, + pcibus_t size, uint8_t type, PCIMapIORegionFunc *map_func) { PCIIORegion *r; diff --git a/hw/pci.h b/hw/pci.h index 2b4c318898..1c6075eb40 100644 --- a/hw/pci.h +++ b/hw/pci.h @@ -180,7 +180,7 @@ PCIDevice *pci_register_device(PCIBus *bus, const char *name, PCIConfigWriteFunc *config_write); void pci_register_bar(PCIDevice *pci_dev, int region_num, - pcibus_t size, int type, + pcibus_t size, uint8_t type, PCIMapIORegionFunc *map_func); int pci_add_capability(PCIDevice *pdev, uint8_t cap_id, From 92ba5f51c305911cbfc0fcff9f259b0604681222 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Wed, 15 Sep 2010 14:38:15 +0900 Subject: [PATCH 14/39] pci: implement RW1C register framework. Implement RW1C register framework. With this patch, it would be easy to implement W1C(Write 1 to Clear) register by just setting w1cmask. Later RW1C register will be used by pcie. Signed-off-by: Isaku Yamahata Signed-off-by: Michael S. Tsirkin --- hw/pci.c | 5 +++++ hw/pci.h | 3 +++ 2 files changed, 8 insertions(+) diff --git a/hw/pci.c b/hw/pci.c index 97a7b232ac..abddc6d239 100644 --- a/hw/pci.c +++ b/hw/pci.c @@ -627,6 +627,7 @@ static void pci_config_alloc(PCIDevice *pci_dev) pci_dev->config = qemu_mallocz(config_size); pci_dev->cmask = qemu_mallocz(config_size); pci_dev->wmask = qemu_mallocz(config_size); + pci_dev->w1cmask = qemu_mallocz(config_size); pci_dev->used = qemu_mallocz(config_size); } @@ -635,6 +636,7 @@ static void pci_config_free(PCIDevice *pci_dev) qemu_free(pci_dev->config); qemu_free(pci_dev->cmask); qemu_free(pci_dev->wmask); + qemu_free(pci_dev->w1cmask); qemu_free(pci_dev->used); } @@ -997,7 +999,10 @@ void pci_default_write_config(PCIDevice *d, uint32_t addr, uint32_t val, int l) for (i = 0; i < l && addr + i < config_size; val >>= 8, ++i) { uint8_t wmask = d->wmask[addr + i]; + uint8_t w1cmask = d->w1cmask[addr + i]; + assert(!(wmask & w1cmask)); d->config[addr + i] = (d->config[addr + i] & ~wmask) | (val & wmask); + d->config[addr + i] &= ~(val & w1cmask); /* W1C: Write 1 to Clear */ } if (ranges_overlap(addr, l, PCI_BASE_ADDRESS_0, 24) || ranges_overlap(addr, l, PCI_ROM_ADDRESS, 4) || diff --git a/hw/pci.h b/hw/pci.h index 1c6075eb40..d8b399f4d5 100644 --- a/hw/pci.h +++ b/hw/pci.h @@ -129,6 +129,9 @@ struct PCIDevice { /* Used to implement R/W bytes */ uint8_t *wmask; + /* Used to implement RW1C(Write 1 to Clear) bytes */ + uint8_t *w1cmask; + /* Used to allocate config space for capabilities. */ uint8_t *used; From 57c6db2e2dec77a60bcfe6f93d756653f444a1f2 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Wed, 15 Sep 2010 14:38:26 +0900 Subject: [PATCH 15/39] msix: clear not only INTA, but all INTx when MSI-X is enabled. clear not only INTA, but all INTx when MSI-X is enabled. Signed-off-by: Isaku Yamahata Signed-off-by: Michael S. Tsirkin --- hw/msix.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/hw/msix.c b/hw/msix.c index 7ce63ebe63..b202ff7d85 100644 --- a/hw/msix.c +++ b/hw/msix.c @@ -158,6 +158,7 @@ void msix_write_config(PCIDevice *dev, uint32_t addr, { unsigned enable_pos = dev->msix_cap + MSIX_CONTROL_OFFSET; int vector; + int i; if (!range_covers_byte(addr, len, enable_pos)) { return; @@ -167,7 +168,9 @@ void msix_write_config(PCIDevice *dev, uint32_t addr, return; } - qemu_set_irq(dev->irq[0], 0); + for (i = 0; i < PCI_NUM_PINS; ++i) { + qemu_set_irq(dev->irq[i], 0); + } if (msix_function_masked(dev)) { return; From 1a4f5971b6f785db7cb2b964754d04103a0d2033 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Mon, 18 Oct 2010 12:17:42 +0900 Subject: [PATCH 16/39] pci: make pci_del_capability() update for w1cmask Clear w1cmask when deleting a pci capability. Signed-off-by: Isaku Yamahata Signed-off-by: Michael S. Tsirkin --- hw/pci.c | 1 + 1 file changed, 1 insertion(+) diff --git a/hw/pci.c b/hw/pci.c index abddc6d239..e3462a9384 100644 --- a/hw/pci.c +++ b/hw/pci.c @@ -1745,6 +1745,7 @@ void pci_del_capability(PCIDevice *pdev, uint8_t cap_id, uint8_t size) pdev->config[prev] = pdev->config[offset + PCI_CAP_LIST_NEXT]; /* Make capability writeable again */ memset(pdev->wmask + offset, 0xff, size); + memset(pdev->w1cmask + offset, 0, size); /* Clear cmask as device-specific registers can't be checked */ memset(pdev->cmask + offset, 0, size); memset(pdev->used + offset, 0, size); From aabcf5266f94e637afd4c38d46d1fc1d1381d99e Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Tue, 19 Oct 2010 18:06:28 +0900 Subject: [PATCH 17/39] pci: introduce helper functions to test-and-{clear, set} mask in configuration space This patch introduces helper functions to test-and-{clear, set} mask in configuration space. pci_{byte, word, long, quad}_test_and_{clear, set}_mask(). They will be used later. Signed-off-by: Isaku Yamahata Signed-off-by: Michael S. Tsirkin --- hw/pci.h | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/hw/pci.h b/hw/pci.h index d8b399f4d5..752e652eca 100644 --- a/hw/pci.h +++ b/hw/pci.h @@ -323,6 +323,76 @@ pci_config_set_interrupt_pin(uint8_t *pci_config, uint8_t val) pci_set_byte(&pci_config[PCI_INTERRUPT_PIN], val); } +/* + * helper functions to do bit mask operation on configuration space. + * Just to set bit, use test-and-set and discard returned value. + * Just to clear bit, use test-and-clear and discard returned value. + * NOTE: They aren't atomic. + */ +static inline uint8_t +pci_byte_test_and_clear_mask(uint8_t *config, uint8_t mask) +{ + uint8_t val = pci_get_byte(config); + pci_set_byte(config, val & ~mask); + return val & mask; +} + +static inline uint8_t +pci_byte_test_and_set_mask(uint8_t *config, uint8_t mask) +{ + uint8_t val = pci_get_byte(config); + pci_set_byte(config, val | mask); + return val & mask; +} + +static inline uint16_t +pci_word_test_and_clear_mask(uint8_t *config, uint16_t mask) +{ + uint16_t val = pci_get_word(config); + pci_set_word(config, val & ~mask); + return val & mask; +} + +static inline uint16_t +pci_word_test_and_set_mask(uint8_t *config, uint16_t mask) +{ + uint16_t val = pci_get_word(config); + pci_set_word(config, val | mask); + return val & mask; +} + +static inline uint32_t +pci_long_test_and_clear_mask(uint8_t *config, uint32_t mask) +{ + uint32_t val = pci_get_long(config); + pci_set_long(config, val & ~mask); + return val & mask; +} + +static inline uint32_t +pci_long_test_and_set_mask(uint8_t *config, uint32_t mask) +{ + uint32_t val = pci_get_long(config); + pci_set_long(config, val | mask); + return val & mask; +} + +static inline uint64_t +pci_quad_test_and_clear_mask(uint8_t *config, uint64_t mask) +{ + uint64_t val = pci_get_quad(config); + pci_set_quad(config, val & ~mask); + return val & mask; +} + +static inline uint64_t +pci_quad_test_and_set_mask(uint8_t *config, uint64_t mask) +{ + uint64_t val = pci_get_quad(config); + pci_set_quad(config, val | mask); + return val & mask; +} + typedef int (*pci_qdev_initfn)(PCIDevice *dev); typedef struct { DeviceInfo qdev; From a5d1fd20ccfd1fbe840729378e4adbc3eb0f8306 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Tue, 19 Oct 2010 18:06:29 +0900 Subject: [PATCH 18/39] pci: introduce helper function to handle msi-x and msi. this patch implements helper functions to handle msi-x and msi uniformly. They will be used later. Signed-off-by: Isaku Yamahata Signed-off-by: Michael S. Tsirkin --- hw/pci.c | 19 +++++++++++++++++++ hw/pci.h | 3 +++ 2 files changed, 22 insertions(+) diff --git a/hw/pci.c b/hw/pci.c index e3462a9384..300079f4af 100644 --- a/hw/pci.c +++ b/hw/pci.c @@ -25,6 +25,8 @@ #include "pci.h" #include "pci_bridge.h" #include "pci_internals.h" +#include "msix.h" +#include "msi.h" #include "monitor.h" #include "net.h" #include "sysemu.h" @@ -1034,6 +1036,23 @@ static void pci_set_irq(void *opaque, int irq_num, int level) pci_change_irq_level(pci_dev, irq_num, change); } +bool pci_msi_enabled(PCIDevice *dev) +{ + return msix_enabled(dev) || msi_enabled(dev); +} + +void pci_msi_notify(PCIDevice *dev, unsigned int vector) +{ + if (msix_enabled(dev)) { + msix_notify(dev, vector); + } else if (msi_enabled(dev)) { + msi_notify(dev, vector); + } else { + /* MSI/MSI-X must be enabled */ + abort(); + } +} + /***********************************************************/ /* monitor info on PCI */ diff --git a/hw/pci.h b/hw/pci.h index 752e652eca..3072a5f854 100644 --- a/hw/pci.h +++ b/hw/pci.h @@ -239,6 +239,9 @@ void do_pci_info_print(Monitor *mon, const QObject *data); void do_pci_info(Monitor *mon, QObject **ret_data); void pci_bridge_update_mappings(PCIBus *b); +bool pci_msi_enabled(PCIDevice *dev); +void pci_msi_notify(PCIDevice *dev, unsigned int vector); + static inline void pci_set_byte(uint8_t *config, uint8_t val) { From 99443c21b06aa433d74880f9d2a0e4320631b906 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Tue, 19 Oct 2010 18:06:30 +0900 Subject: [PATCH 19/39] pci: use pci_word_test_and_clear_mask() in pci_device_reset() use pci_clear_bit_word() in pci_device_reset() where appropriate. Signed-off-by: Isaku Yamahata Signed-off-by: Michael S. Tsirkin --- hw/pci.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/hw/pci.c b/hw/pci.c index 300079f4af..409e2c079b 100644 --- a/hw/pci.c +++ b/hw/pci.c @@ -139,9 +139,8 @@ static void pci_device_reset(PCIDevice *dev) dev->irq_state = 0; pci_update_irq_status(dev); /* Clear all writeable bits */ - pci_set_word(dev->config + PCI_COMMAND, - pci_get_word(dev->config + PCI_COMMAND) & - ~pci_get_word(dev->wmask + PCI_COMMAND)); + pci_word_test_and_clear_mask(dev->config + PCI_COMMAND, + pci_get_word(dev->wmask + PCI_COMMAND)); dev->config[PCI_CACHE_LINE_SIZE] = 0x0; dev->config[PCI_INTERRUPT_LINE] = 0x0; for (r = 0; r < PCI_NUM_REGIONS; ++r) { From e4c7d2aef899780f9b9b86343bca4ac34c9e252f Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Tue, 19 Oct 2010 18:06:32 +0900 Subject: [PATCH 20/39] msi: implements msi implements msi related functions. Signed-off-by: Isaku Yamahata Signed-off-by: Michael S. Tsirkin --- Makefile.objs | 2 +- hw/msi.c | 352 ++++++++++++++++++++++++++++++++++++++++++++++++++ hw/msi.h | 41 ++++++ hw/pci.h | 10 +- 4 files changed, 401 insertions(+), 4 deletions(-) create mode 100644 hw/msi.c create mode 100644 hw/msi.h diff --git a/Makefile.objs b/Makefile.objs index 594894bbed..5f5a4c5493 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -186,7 +186,7 @@ hw-obj-$(CONFIG_PIIX4) += piix4.o # PCI watchdog devices hw-obj-y += wdt_i6300esb.o -hw-obj-y += msix.o +hw-obj-y += msix.o msi.o # PCI network cards hw-obj-y += ne2000.o diff --git a/hw/msi.c b/hw/msi.c new file mode 100644 index 0000000000..a949d821fd --- /dev/null +++ b/hw/msi.c @@ -0,0 +1,352 @@ +/* + * msi.c + * + * Copyright (c) 2010 Isaku Yamahata + * VA Linux Systems Japan K.K. + * + * 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 . + */ + +#include "msi.h" + +/* Eventually those constants should go to Linux pci_regs.h */ +#define PCI_MSI_PENDING_32 0x10 +#define PCI_MSI_PENDING_64 0x14 + +/* PCI_MSI_ADDRESS_LO */ +#define PCI_MSI_ADDRESS_LO_MASK (~0x3) + +/* If we get rid of cap allocator, we won't need those. */ +#define PCI_MSI_32_SIZEOF 0x0a +#define PCI_MSI_64_SIZEOF 0x0e +#define PCI_MSI_32M_SIZEOF 0x14 +#define PCI_MSI_64M_SIZEOF 0x18 + +#define PCI_MSI_VECTORS_MAX 32 + +/* If we get rid of cap allocator, we won't need this. */ +static inline uint8_t msi_cap_sizeof(uint16_t flags) +{ + switch (flags & (PCI_MSI_FLAGS_MASKBIT | PCI_MSI_FLAGS_64BIT)) { + case PCI_MSI_FLAGS_MASKBIT | PCI_MSI_FLAGS_64BIT: + return PCI_MSI_64M_SIZEOF; + case PCI_MSI_FLAGS_64BIT: + return PCI_MSI_64_SIZEOF; + case PCI_MSI_FLAGS_MASKBIT: + return PCI_MSI_32M_SIZEOF; + case 0: + return PCI_MSI_32_SIZEOF; + default: + abort(); + break; + } + return 0; +} + +//#define MSI_DEBUG + +#ifdef MSI_DEBUG +# define MSI_DPRINTF(fmt, ...) \ + fprintf(stderr, "%s:%d " fmt, __func__, __LINE__, ## __VA_ARGS__) +#else +# define MSI_DPRINTF(fmt, ...) do { } while (0) +#endif +#define MSI_DEV_PRINTF(dev, fmt, ...) \ + MSI_DPRINTF("%s:%x " fmt, (dev)->name, (dev)->devfn, ## __VA_ARGS__) + +static inline unsigned int msi_nr_vectors(uint16_t flags) +{ + return 1U << + ((flags & PCI_MSI_FLAGS_QSIZE) >> (ffs(PCI_MSI_FLAGS_QSIZE) - 1)); +} + +static inline uint8_t msi_flags_off(const PCIDevice* dev) +{ + return dev->msi_cap + PCI_MSI_FLAGS; +} + +static inline uint8_t msi_address_lo_off(const PCIDevice* dev) +{ + return dev->msi_cap + PCI_MSI_ADDRESS_LO; +} + +static inline uint8_t msi_address_hi_off(const PCIDevice* dev) +{ + return dev->msi_cap + PCI_MSI_ADDRESS_HI; +} + +static inline uint8_t msi_data_off(const PCIDevice* dev, bool msi64bit) +{ + return dev->msi_cap + (msi64bit ? PCI_MSI_DATA_64 : PCI_MSI_DATA_32); +} + +static inline uint8_t msi_mask_off(const PCIDevice* dev, bool msi64bit) +{ + return dev->msi_cap + (msi64bit ? PCI_MSI_MASK_64 : PCI_MSI_MASK_32); +} + +static inline uint8_t msi_pending_off(const PCIDevice* dev, bool msi64bit) +{ + return dev->msi_cap + (msi64bit ? PCI_MSI_PENDING_64 : PCI_MSI_PENDING_32); +} + +bool msi_enabled(const PCIDevice *dev) +{ + return msi_present(dev) && + (pci_get_word(dev->config + msi_flags_off(dev)) & + PCI_MSI_FLAGS_ENABLE); +} + +int msi_init(struct PCIDevice *dev, uint8_t offset, + unsigned int nr_vectors, bool msi64bit, bool msi_per_vector_mask) +{ + unsigned int vectors_order; + uint16_t flags; + uint8_t cap_size; + int config_offset; + MSI_DEV_PRINTF(dev, + "init offset: 0x%"PRIx8" vector: %"PRId8 + " 64bit %d mask %d\n", + offset, nr_vectors, msi64bit, msi_per_vector_mask); + + assert(!(nr_vectors & (nr_vectors - 1))); /* power of 2 */ + assert(nr_vectors > 0); + assert(nr_vectors <= PCI_MSI_VECTORS_MAX); + /* the nr of MSI vectors is up to 32 */ + vectors_order = ffs(nr_vectors) - 1; + + flags = vectors_order << (ffs(PCI_MSI_FLAGS_QMASK) - 1); + if (msi64bit) { + flags |= PCI_MSI_FLAGS_64BIT; + } + if (msi_per_vector_mask) { + flags |= PCI_MSI_FLAGS_MASKBIT; + } + + cap_size = msi_cap_sizeof(flags); + config_offset = pci_add_capability(dev, PCI_CAP_ID_MSI, offset, cap_size); + if (config_offset < 0) { + return config_offset; + } + + dev->msi_cap = config_offset; + dev->cap_present |= QEMU_PCI_CAP_MSI; + + pci_set_word(dev->config + msi_flags_off(dev), flags); + pci_set_word(dev->wmask + msi_flags_off(dev), + PCI_MSI_FLAGS_QSIZE | PCI_MSI_FLAGS_ENABLE); + pci_set_long(dev->wmask + msi_address_lo_off(dev), + PCI_MSI_ADDRESS_LO_MASK); + if (msi64bit) { + pci_set_long(dev->wmask + msi_address_hi_off(dev), 0xffffffff); + } + pci_set_word(dev->wmask + msi_data_off(dev, msi64bit), 0xffff); + + if (msi_per_vector_mask) { + pci_set_long(dev->wmask + msi_mask_off(dev, msi64bit), + /* (1U << nr_vectors) - 1 is undefined + when nr_vectors = 32 */ + 0xffffffff >> (PCI_MSI_VECTORS_MAX - nr_vectors)); + } + return config_offset; +} + +void msi_uninit(struct PCIDevice *dev) +{ + uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev)); + uint8_t cap_size = msi_cap_sizeof(flags); + pci_del_capability(dev, PCI_CAP_ID_MSIX, cap_size); + MSI_DEV_PRINTF(dev, "uninit\n"); +} + +void msi_reset(PCIDevice *dev) +{ + uint16_t flags; + bool msi64bit; + + flags = pci_get_word(dev->config + msi_flags_off(dev)); + flags &= ~(PCI_MSI_FLAGS_QSIZE | PCI_MSI_FLAGS_ENABLE); + msi64bit = flags & PCI_MSI_FLAGS_64BIT; + + pci_set_word(dev->config + msi_flags_off(dev), flags); + pci_set_long(dev->config + msi_address_lo_off(dev), 0); + if (msi64bit) { + pci_set_long(dev->config + msi_address_hi_off(dev), 0); + } + pci_set_word(dev->config + msi_data_off(dev, msi64bit), 0); + if (flags & PCI_MSI_FLAGS_MASKBIT) { + pci_set_long(dev->config + msi_mask_off(dev, msi64bit), 0); + pci_set_long(dev->config + msi_pending_off(dev, msi64bit), 0); + } + MSI_DEV_PRINTF(dev, "reset\n"); +} + +static bool msi_is_masked(const PCIDevice *dev, unsigned int vector) +{ + uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev)); + uint32_t mask; + assert(vector < PCI_MSI_VECTORS_MAX); + + if (!(flags & PCI_MSI_FLAGS_MASKBIT)) { + return false; + } + + mask = pci_get_long(dev->config + + msi_mask_off(dev, flags & PCI_MSI_FLAGS_64BIT)); + return mask & (1U << vector); +} + +void msi_notify(PCIDevice *dev, unsigned int vector) +{ + uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev)); + bool msi64bit = flags & PCI_MSI_FLAGS_64BIT; + unsigned int nr_vectors = msi_nr_vectors(flags); + uint64_t address; + uint32_t data; + + assert(vector < nr_vectors); + if (msi_is_masked(dev, vector)) { + assert(flags & PCI_MSI_FLAGS_MASKBIT); + pci_long_test_and_set_mask( + dev->config + msi_pending_off(dev, msi64bit), 1U << vector); + MSI_DEV_PRINTF(dev, "pending vector 0x%x\n", vector); + return; + } + + if (msi64bit){ + address = pci_get_quad(dev->config + msi_address_lo_off(dev)); + } else { + address = pci_get_long(dev->config + msi_address_lo_off(dev)); + } + + /* upper bit 31:16 is zero */ + data = pci_get_word(dev->config + msi_data_off(dev, msi64bit)); + if (nr_vectors > 1) { + data &= ~(nr_vectors - 1); + data |= vector; + } + + MSI_DEV_PRINTF(dev, + "notify vector 0x%x" + " address: 0x%"PRIx64" data: 0x%"PRIx32"\n", + vector, address, data); + stl_phys(address, data); +} + +/* call this function after updating configs by pci_default_write_config(). */ +void msi_write_config(PCIDevice *dev, uint32_t addr, uint32_t val, int len) +{ + uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev)); + bool msi64bit = flags & PCI_MSI_FLAGS_64BIT; + bool msi_per_vector_mask = flags & PCI_MSI_FLAGS_MASKBIT; + unsigned int nr_vectors; + uint8_t log_num_vecs; + uint8_t log_max_vecs; + unsigned int vector; + uint32_t pending; + int i; + +#ifdef MSI_DEBUG + if (ranges_overlap(addr, len, dev->msi_cap, msi_cap_sizeof(flags))) { + MSI_DEV_PRINTF(dev, "addr 0x%"PRIx32" val 0x%"PRIx32" len %d\n", + addr, val, len); + MSI_DEV_PRINTF(dev, "ctrl: 0x%"PRIx16" address: 0x%"PRIx32, + flags, + pci_get_long(dev->config + msi_address_lo_off(dev))); + if (msi64bit) { + fprintf(stderr, " addrss-hi: 0x%"PRIx32, + pci_get_long(dev->config + msi_address_hi_off(dev))); + } + fprintf(stderr, " data: 0x%"PRIx16, + pci_get_word(dev->config + msi_data_off(dev, msi64bit))); + if (flags & PCI_MSI_FLAGS_MASKBIT) { + fprintf(stderr, " mask 0x%"PRIx32" pending 0x%"PRIx32, + pci_get_long(dev->config + msi_mask_off(dev, msi64bit)), + pci_get_long(dev->config + msi_pending_off(dev, msi64bit))); + } + fprintf(stderr, "\n"); + } +#endif + + /* Are we modified? */ + if (!(ranges_overlap(addr, len, msi_flags_off(dev), 2) || + (msi_per_vector_mask && + ranges_overlap(addr, len, msi_mask_off(dev, msi64bit), 4)))) { + return; + } + + if (!(flags & PCI_MSI_FLAGS_ENABLE)) { + return; + } + + /* + * Now MSI is enabled, clear INTx# interrupts. + * the driver is prohibited from writing enable bit to mask + * a service request. But the guest OS could do this. + * So we just discard the interrupts as moderate fallback. + * + * 6.8.3.3. Enabling Operation + * While enabled for MSI or MSI-X operation, a function is prohibited + * from using its INTx# pin (if implemented) to request + * service (MSI, MSI-X, and INTx# are mutually exclusive). + */ + for (i = 0; i < PCI_NUM_PINS; ++i) { + qemu_set_irq(dev->irq[i], 0); + } + + /* + * nr_vectors might be set bigger than capable. So clamp it. + * This is not legal by spec, so we can do anything we like, + * just don't crash the host + */ + log_num_vecs = + (flags & PCI_MSI_FLAGS_QSIZE) >> (ffs(PCI_MSI_FLAGS_QSIZE) - 1); + log_max_vecs = + (flags & PCI_MSI_FLAGS_QMASK) >> (ffs(PCI_MSI_FLAGS_QMASK) - 1); + if (log_num_vecs > log_max_vecs) { + flags &= ~PCI_MSI_FLAGS_QSIZE; + flags |= log_max_vecs << (ffs(PCI_MSI_FLAGS_QSIZE) - 1); + pci_set_word(dev->config + msi_flags_off(dev), flags); + } + + if (!msi_per_vector_mask) { + /* if per vector masking isn't supported, + there is no pending interrupt. */ + return; + } + + nr_vectors = msi_nr_vectors(flags); + + /* This will discard pending interrupts, if any. */ + pending = pci_get_long(dev->config + msi_pending_off(dev, msi64bit)); + pending &= 0xffffffff >> (PCI_MSI_VECTORS_MAX - nr_vectors); + pci_set_long(dev->config + msi_pending_off(dev, msi64bit), pending); + + /* deliver pending interrupts which are unmasked */ + for (vector = 0; vector < nr_vectors; ++vector) { + if (msi_is_masked(dev, vector) || !(pending & (1U << vector))) { + continue; + } + + pci_long_test_and_clear_mask( + dev->config + msi_pending_off(dev, msi64bit), 1U << vector); + msi_notify(dev, vector); + } +} + +unsigned int msi_nr_vectors_allocated(const PCIDevice *dev) +{ + uint16_t flags = pci_get_word(dev->config + msi_flags_off(dev)); + return msi_nr_vectors(flags); +} diff --git a/hw/msi.h b/hw/msi.h new file mode 100644 index 0000000000..5766018d79 --- /dev/null +++ b/hw/msi.h @@ -0,0 +1,41 @@ +/* + * msi.h + * + * Copyright (c) 2010 Isaku Yamahata + * VA Linux Systems Japan K.K. + * + * 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 . + */ + +#ifndef QEMU_MSI_H +#define QEMU_MSI_H + +#include "qemu-common.h" +#include "pci.h" + +bool msi_enabled(const PCIDevice *dev); +int msi_init(struct PCIDevice *dev, uint8_t offset, + unsigned int nr_vectors, bool msi64bit, bool msi_per_vector_mask); +void msi_uninit(struct PCIDevice *dev); +void msi_reset(PCIDevice *dev); +void msi_notify(PCIDevice *dev, unsigned int vector); +void msi_write_config(PCIDevice *dev, uint32_t addr, uint32_t val, int len); +unsigned int msi_nr_vectors_allocated(const PCIDevice *dev); + +static inline bool msi_present(const PCIDevice *dev) +{ + return dev->cap_present & QEMU_PCI_CAP_MSI; +} + +#endif /* QEMU_MSI_H */ diff --git a/hw/pci.h b/hw/pci.h index 3072a5f854..9e2f27d372 100644 --- a/hw/pci.h +++ b/hw/pci.h @@ -109,11 +109,12 @@ typedef struct PCIIORegion { /* Bits in cap_present field. */ enum { - QEMU_PCI_CAP_MSIX = 0x1, - QEMU_PCI_CAP_EXPRESS = 0x2, + QEMU_PCI_CAP_MSI = 0x1, + QEMU_PCI_CAP_MSIX = 0x2, + QEMU_PCI_CAP_EXPRESS = 0x4, /* multifunction capable device */ -#define QEMU_PCI_CAP_MULTIFUNCTION_BITNR 2 +#define QEMU_PCI_CAP_MULTIFUNCTION_BITNR 3 QEMU_PCI_CAP_MULTIFUNCTION = (1 << QEMU_PCI_CAP_MULTIFUNCTION_BITNR), }; @@ -171,6 +172,9 @@ struct PCIDevice { /* Version id needed for VMState */ int32_t version_id; + /* Offset of MSI capability in config space */ + uint8_t msi_cap; + /* Location of option rom */ char *romfile; ram_addr_t rom_offset; From 08f3dcf13f49dea3106b09c69bef9543e56fa629 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Tue, 19 Oct 2010 18:06:33 +0900 Subject: [PATCH 21/39] pcie: add pcie constants to pcie_regs.h add pcie constants to pcie_regs.h. Those constants should go to Linux pci_regs.h and then the file should go away eventually. Signed-off-by: Isaku Yamahata Signed-off-by: Michael S. Tsirkin --- hw/pcie_regs.h | 154 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 hw/pcie_regs.h diff --git a/hw/pcie_regs.h b/hw/pcie_regs.h new file mode 100644 index 0000000000..3461a1b6b8 --- /dev/null +++ b/hw/pcie_regs.h @@ -0,0 +1,154 @@ +/* + * constants for pcie configurations space from pci express spec. + * + * TODO: + * Those constants and macros should go to Linux pci_regs.h + * Once they're merged, they will go away. + */ +#ifndef QEMU_PCIE_REGS_H +#define QEMU_PCIE_REGS_H + + +/* express capability */ + +#define PCI_EXP_VER2_SIZEOF 0x3c /* express capability of ver. 2 */ +#define PCI_EXT_CAP_VER_SHIFT 16 +#define PCI_EXT_CAP_NEXT_SHIFT 20 +#define PCI_EXT_CAP_NEXT_MASK (0xffc << PCI_EXT_CAP_NEXT_SHIFT) + +#define PCI_EXT_CAP(id, ver, next) \ + ((id) | \ + ((ver) << PCI_EXT_CAP_VER_SHIFT) | \ + ((next) << PCI_EXT_CAP_NEXT_SHIFT)) + +#define PCI_EXT_CAP_ALIGN 4 +#define PCI_EXT_CAP_ALIGNUP(x) \ + (((x) + PCI_EXT_CAP_ALIGN - 1) & ~(PCI_EXT_CAP_ALIGN - 1)) + +/* PCI_EXP_FLAGS */ +#define PCI_EXP_FLAGS_VER2 2 /* for now, supports only ver. 2 */ +#define PCI_EXP_FLAGS_IRQ_SHIFT (ffs(PCI_EXP_FLAGS_IRQ) - 1) +#define PCI_EXP_FLAGS_TYPE_SHIFT (ffs(PCI_EXP_FLAGS_TYPE) - 1) + + +/* PCI_EXP_LINK{CAP, STA} */ +/* link speed */ +#define PCI_EXP_LNK_LS_25 1 + +#define PCI_EXP_LNK_MLW_SHIFT (ffs(PCI_EXP_LNKCAP_MLW) - 1) +#define PCI_EXP_LNK_MLW_1 (1 << PCI_EXP_LNK_MLW_SHIFT) + +/* PCI_EXP_LINKCAP */ +#define PCI_EXP_LNKCAP_ASPMS_SHIFT (ffs(PCI_EXP_LNKCAP_ASPMS) - 1) +#define PCI_EXP_LNKCAP_ASPMS_0S (1 << PCI_EXP_LNKCAP_ASPMS_SHIFT) + +#define PCI_EXP_LNKCAP_PN_SHIFT (ffs(PCI_EXP_LNKCAP_PN) - 1) + +#define PCI_EXP_SLTCAP_PSN_SHIFT (ffs(PCI_EXP_SLTCAP_PSN) - 1) + +#define PCI_EXP_SLTCTL_IND_RESERVED 0x0 +#define PCI_EXP_SLTCTL_IND_ON 0x1 +#define PCI_EXP_SLTCTL_IND_BLINK 0x2 +#define PCI_EXP_SLTCTL_IND_OFF 0x3 +#define PCI_EXP_SLTCTL_AIC_SHIFT (ffs(PCI_EXP_SLTCTL_AIC) - 1) +#define PCI_EXP_SLTCTL_AIC_OFF \ + (PCI_EXP_SLTCTL_IND_OFF << PCI_EXP_SLTCTL_AIC_SHIFT) + +#define PCI_EXP_SLTCTL_PIC_SHIFT (ffs(PCI_EXP_SLTCTL_PIC) - 1) +#define PCI_EXP_SLTCTL_PIC_OFF \ + (PCI_EXP_SLTCTL_IND_OFF << PCI_EXP_SLTCTL_PIC_SHIFT) + +#define PCI_EXP_SLTCTL_SUPPORTED \ + (PCI_EXP_SLTCTL_ABPE | \ + PCI_EXP_SLTCTL_PDCE | \ + PCI_EXP_SLTCTL_CCIE | \ + PCI_EXP_SLTCTL_HPIE | \ + PCI_EXP_SLTCTL_AIC | \ + PCI_EXP_SLTCTL_PCC | \ + PCI_EXP_SLTCTL_EIC) + +#define PCI_EXP_DEVCAP2_EFF 0x100000 +#define PCI_EXP_DEVCAP2_EETLPP 0x200000 + +#define PCI_EXP_DEVCTL2_EETLPPB 0x80 + +/* ARI */ +#define PCI_ARI_VER 1 +#define PCI_ARI_SIZEOF 8 + +/* AER */ +#define PCI_ERR_VER 2 +#define PCI_ERR_SIZEOF 0x48 + +#define PCI_ERR_UNC_SDN 0x00000020 /* surprise down */ +#define PCI_ERR_UNC_ACSV 0x00200000 /* ACS Violation */ +#define PCI_ERR_UNC_INTN 0x00400000 /* Internal Error */ +#define PCI_ERR_UNC_MCBTLP 0x00800000 /* MC Blcoked TLP */ +#define PCI_ERR_UNC_ATOP_EBLOCKED 0x01000000 /* atomic op egress blocked */ +#define PCI_ERR_UNC_TLP_PRF_BLOCKED 0x02000000 /* TLP Prefix Blocked */ +#define PCI_ERR_COR_ADV_NONFATAL 0x00002000 /* Advisory Non-Fatal */ +#define PCI_ERR_COR_INTERNAL 0x00004000 /* Corrected Internal */ +#define PCI_ERR_COR_HL_OVERFLOW 0x00008000 /* Header Long Overflow */ +#define PCI_ERR_CAP_FEP_MASK 0x0000001f +#define PCI_ERR_CAP_MHRC 0x00000200 +#define PCI_ERR_CAP_MHRE 0x00000400 +#define PCI_ERR_CAP_TLP 0x00000800 + +#define PCI_ERR_TLP_PREFIX_LOG 0x38 + +#define PCI_SEC_STATUS_RCV_SYSTEM_ERROR 0x4000 + +/* aer root error command/status */ +#define PCI_ERR_ROOT_CMD_EN_MASK (PCI_ERR_ROOT_CMD_COR_EN | \ + PCI_ERR_ROOT_CMD_NONFATAL_EN | \ + PCI_ERR_ROOT_CMD_FATAL_EN) + +#define PCI_ERR_ROOT_IRQ_MAX 32 +#define PCI_ERR_ROOT_IRQ 0xf8000000 +#define PCI_ERR_ROOT_IRQ_SHIFT (ffs(PCI_ERR_ROOT_IRQ) - 1) +#define PCI_ERR_ROOT_STATUS_REPORT_MASK (PCI_ERR_ROOT_COR_RCV | \ + PCI_ERR_ROOT_MULTI_COR_RCV | \ + PCI_ERR_ROOT_UNCOR_RCV | \ + PCI_ERR_ROOT_MULTI_UNCOR_RCV | \ + PCI_ERR_ROOT_FIRST_FATAL | \ + PCI_ERR_ROOT_NONFATAL_RCV | \ + PCI_ERR_ROOT_FATAL_RCV) + +#define PCI_ERR_UNC_SUPPORTED (PCI_ERR_UNC_DLP | \ + PCI_ERR_UNC_SDN | \ + PCI_ERR_UNC_POISON_TLP | \ + PCI_ERR_UNC_FCP | \ + PCI_ERR_UNC_COMP_TIME | \ + PCI_ERR_UNC_COMP_ABORT | \ + PCI_ERR_UNC_UNX_COMP | \ + PCI_ERR_UNC_RX_OVER | \ + PCI_ERR_UNC_MALF_TLP | \ + PCI_ERR_UNC_ECRC | \ + PCI_ERR_UNC_UNSUP | \ + PCI_ERR_UNC_ACSV | \ + PCI_ERR_UNC_INTN | \ + PCI_ERR_UNC_MCBTLP | \ + PCI_ERR_UNC_ATOP_EBLOCKED | \ + PCI_ERR_UNC_TLP_PRF_BLOCKED) + +#define PCI_ERR_UNC_SEVERITY_DEFAULT (PCI_ERR_UNC_DLP | \ + PCI_ERR_UNC_SDN | \ + PCI_ERR_UNC_FCP | \ + PCI_ERR_UNC_RX_OVER | \ + PCI_ERR_UNC_MALF_TLP | \ + PCI_ERR_UNC_INTN) + +#define PCI_ERR_COR_SUPPORTED (PCI_ERR_COR_RCVR | \ + PCI_ERR_COR_BAD_TLP | \ + PCI_ERR_COR_BAD_DLLP | \ + PCI_ERR_COR_REP_ROLL | \ + PCI_ERR_COR_REP_TIMER | \ + PCI_ERR_COR_ADV_NONFATAL | \ + PCI_ERR_COR_INTERNAL | \ + PCI_ERR_COR_HL_OVERFLOW) + +#define PCI_ERR_COR_MASK_DEFAULT (PCI_ERR_COR_ADV_NONFATAL | \ + PCI_ERR_COR_INTERNAL | \ + PCI_ERR_COR_HL_OVERFLOW) + +#endif /* QEMU_PCIE_REGS_H */ From 0428527c621c3edfd258b4d34fc178ef5df41071 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Tue, 19 Oct 2010 18:06:34 +0900 Subject: [PATCH 22/39] pcie: helper functions for pcie capability and extended capability This patch implements helper functions for pci express capability and pci express extended capability allocation. NOTE: presence detection depends on pci_qdev_init() change. Signed-off-by: Isaku Yamahata Signed-off-by: Michael S. Tsirkin --- Makefile.objs | 1 + hw/pci.h | 5 + hw/pcie.c | 540 ++++++++++++++++++++++++++++++++++++++++++++++++++ hw/pcie.h | 107 ++++++++++ qemu-common.h | 1 + 5 files changed, 654 insertions(+) create mode 100644 hw/pcie.c create mode 100644 hw/pcie.h diff --git a/Makefile.objs b/Makefile.objs index 5f5a4c5493..eeb5134600 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -186,6 +186,7 @@ hw-obj-$(CONFIG_PIIX4) += piix4.o # PCI watchdog devices hw-obj-y += wdt_i6300esb.o +hw-obj-y += pcie.o hw-obj-y += msix.o msi.o # PCI network cards diff --git a/hw/pci.h b/hw/pci.h index 9e2f27d372..d6c522b8dd 100644 --- a/hw/pci.h +++ b/hw/pci.h @@ -9,6 +9,8 @@ /* PCI includes legacy ISA access. */ #include "isa.h" +#include "pcie.h" + /* PCI bus */ #define PCI_DEVFN(slot, func) ((((slot) & 0x1f) << 3) | ((func) & 0x07)) @@ -175,6 +177,9 @@ struct PCIDevice { /* Offset of MSI capability in config space */ uint8_t msi_cap; + /* PCI Express */ + PCIExpressDevice exp; + /* Location of option rom */ char *romfile; ram_addr_t rom_offset; diff --git a/hw/pcie.c b/hw/pcie.c new file mode 100644 index 0000000000..53d1fce7c7 --- /dev/null +++ b/hw/pcie.c @@ -0,0 +1,540 @@ +/* + * pcie.c + * + * Copyright (c) 2010 Isaku Yamahata + * VA Linux Systems Japan K.K. + * + * 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 . + */ + +#include "sysemu.h" +#include "pci_bridge.h" +#include "pcie.h" +#include "msix.h" +#include "msi.h" +#include "pci_internals.h" +#include "pcie_regs.h" + +//#define DEBUG_PCIE +#ifdef DEBUG_PCIE +# define PCIE_DPRINTF(fmt, ...) \ + fprintf(stderr, "%s:%d " fmt, __func__, __LINE__, ## __VA_ARGS__) +#else +# define PCIE_DPRINTF(fmt, ...) do {} while (0) +#endif +#define PCIE_DEV_PRINTF(dev, fmt, ...) \ + PCIE_DPRINTF("%s:%x "fmt, (dev)->name, (dev)->devfn, ## __VA_ARGS__) + + +/*************************************************************************** + * pci express capability helper functions + */ +int pcie_cap_init(PCIDevice *dev, uint8_t offset, uint8_t type, uint8_t port) +{ + int pos; + uint8_t *exp_cap; + + assert(pci_is_express(dev)); + + pos = pci_add_capability(dev, PCI_CAP_ID_EXP, offset, + PCI_EXP_VER2_SIZEOF); + if (pos < 0) { + return pos; + } + dev->exp.exp_cap = pos; + exp_cap = dev->config + pos; + + /* capability register + interrupt message number defaults to 0 */ + pci_set_word(exp_cap + PCI_EXP_FLAGS, + ((type << PCI_EXP_FLAGS_TYPE_SHIFT) & PCI_EXP_FLAGS_TYPE) | + PCI_EXP_FLAGS_VER2); + + /* device capability register + * table 7-12: + * roll based error reporting bit must be set by all + * Functions conforming to the ECN, PCI Express Base + * Specification, Revision 1.1., or subsequent PCI Express Base + * Specification revisions. + */ + pci_set_long(exp_cap + PCI_EXP_DEVCAP, PCI_EXP_DEVCAP_RBER); + + pci_set_long(exp_cap + PCI_EXP_LNKCAP, + (port << PCI_EXP_LNKCAP_PN_SHIFT) | + PCI_EXP_LNKCAP_ASPMS_0S | + PCI_EXP_LNK_MLW_1 | + PCI_EXP_LNK_LS_25); + + pci_set_word(exp_cap + PCI_EXP_LNKSTA, + PCI_EXP_LNK_MLW_1 | PCI_EXP_LNK_LS_25); + + pci_set_long(exp_cap + PCI_EXP_DEVCAP2, + PCI_EXP_DEVCAP2_EFF | PCI_EXP_DEVCAP2_EETLPP); + + pci_set_word(dev->wmask + pos, PCI_EXP_DEVCTL2_EETLPPB); + return pos; +} + +void pcie_cap_exit(PCIDevice *dev) +{ + pci_del_capability(dev, PCI_CAP_ID_EXP, PCI_EXP_VER2_SIZEOF); +} + +uint8_t pcie_cap_get_type(const PCIDevice *dev) +{ + uint32_t pos = dev->exp.exp_cap; + assert(pos > 0); + return (pci_get_word(dev->config + pos + PCI_EXP_FLAGS) & + PCI_EXP_FLAGS_TYPE) >> PCI_EXP_FLAGS_TYPE_SHIFT; +} + +/* MSI/MSI-X */ +/* pci express interrupt message number */ +/* 7.8.2 PCI Express Capabilities Register: Interrupt Message Number */ +void pcie_cap_flags_set_vector(PCIDevice *dev, uint8_t vector) +{ + uint8_t *exp_cap = dev->config + dev->exp.exp_cap; + assert(vector < 32); + pci_word_test_and_clear_mask(exp_cap + PCI_EXP_FLAGS, PCI_EXP_FLAGS_IRQ); + pci_word_test_and_set_mask(exp_cap + PCI_EXP_FLAGS, + vector << PCI_EXP_FLAGS_IRQ_SHIFT); +} + +uint8_t pcie_cap_flags_get_vector(PCIDevice *dev) +{ + return (pci_get_word(dev->config + dev->exp.exp_cap + PCI_EXP_FLAGS) & + PCI_EXP_FLAGS_IRQ) >> PCI_EXP_FLAGS_IRQ_SHIFT; +} + +void pcie_cap_deverr_init(PCIDevice *dev) +{ + uint32_t pos = dev->exp.exp_cap; + pci_long_test_and_set_mask(dev->config + pos + PCI_EXP_DEVCAP, + PCI_EXP_DEVCAP_RBER); + pci_long_test_and_set_mask(dev->wmask + pos + PCI_EXP_DEVCTL, + PCI_EXP_DEVCTL_CERE | PCI_EXP_DEVCTL_NFERE | + PCI_EXP_DEVCTL_FERE | PCI_EXP_DEVCTL_URRE); + pci_long_test_and_set_mask(dev->w1cmask + pos + PCI_EXP_DEVSTA, + PCI_EXP_DEVSTA_CED | PCI_EXP_DEVSTA_NFED | + PCI_EXP_DEVSTA_URD | PCI_EXP_DEVSTA_URD); +} + +void pcie_cap_deverr_reset(PCIDevice *dev) +{ + uint8_t *devctl = dev->config + dev->exp.exp_cap + PCI_EXP_DEVCTL; + pci_long_test_and_clear_mask(devctl, + PCI_EXP_DEVCTL_CERE | PCI_EXP_DEVCTL_NFERE | + PCI_EXP_DEVCTL_FERE | PCI_EXP_DEVCTL_URRE); +} + +/* + * A PCI Express Hot-Plug Event has occured, so update slot status register + * and notify OS of the event if necessary. + * + * 6.7.3 PCI Express Hot-Plug Events + * 6.7.3.4 Software Notification of Hot-Plug Events + */ +static void pcie_cap_slot_event(PCIDevice *dev, PCIExpressHotPlugEvent event) +{ + uint8_t *exp_cap = dev->config + dev->exp.exp_cap; + uint16_t sltctl = pci_get_word(exp_cap + PCI_EXP_SLTCTL); + uint16_t sltsta = pci_get_word(exp_cap + PCI_EXP_SLTSTA); + + PCIE_DEV_PRINTF(dev, + "sltctl: 0x%02"PRIx16" sltsta: 0x%02"PRIx16" event: %x\n", + sltctl, sltsta, event); + + if (pci_word_test_and_set_mask(exp_cap + PCI_EXP_SLTSTA, event)) { + return; + } + sltsta = pci_get_word(exp_cap + PCI_EXP_SLTSTA); + PCIE_DEV_PRINTF(dev, "sltsta -> %02"PRIx16"\n", sltsta); + + if ((sltctl & PCI_EXP_SLTCTL_HPIE) && + (sltctl & event & PCI_EXP_HP_EV_SUPPORTED)) { + if (pci_msi_enabled(dev)) { + pci_msi_notify(dev, pcie_cap_flags_get_vector(dev)); + } else { + qemu_set_irq(dev->irq[dev->exp.hpev_intx], 1); + } + } +} + +static int pcie_cap_slot_hotplug(DeviceState *qdev, + PCIDevice *pci_dev, int state) +{ + PCIDevice *d = DO_UPCAST(PCIDevice, qdev, qdev); + uint8_t *exp_cap = d->config + d->exp.exp_cap; + uint16_t sltsta = pci_get_word(exp_cap + PCI_EXP_SLTSTA); + + if (!pci_dev->qdev.hotplugged) { + assert(state); /* this case only happens at machine creation. */ + pci_word_test_and_set_mask(exp_cap + PCI_EXP_SLTSTA, + PCI_EXP_SLTSTA_PDS); + return 0; + } + + PCIE_DEV_PRINTF(pci_dev, "hotplug state: %d\n", state); + if (sltsta & PCI_EXP_SLTSTA_EIS) { + /* the slot is electromechanically locked. + * This error is propagated up to qdev and then to HMP/QMP. + */ + return -EBUSY; + } + + /* TODO: multifunction hot-plug. + * Right now, only a device of function = 0 is allowed to be + * hot plugged/unplugged. + */ + assert(PCI_FUNC(pci_dev->devfn) == 0); + + if (state) { + pci_word_test_and_set_mask(exp_cap + PCI_EXP_SLTSTA, + PCI_EXP_SLTSTA_PDS); + pcie_cap_slot_event(d, PCI_EXP_HP_EV_PDC); + } else { + qdev_free(&pci_dev->qdev); + pci_word_test_and_clear_mask(exp_cap + PCI_EXP_SLTSTA, + PCI_EXP_SLTSTA_PDS); + pcie_cap_slot_event(d, PCI_EXP_HP_EV_PDC); + } + return 0; +} + +/* pci express slot for pci express root/downstream port + PCI express capability slot registers */ +void pcie_cap_slot_init(PCIDevice *dev, uint16_t slot) +{ + uint32_t pos = dev->exp.exp_cap; + + pci_word_test_and_set_mask(dev->config + pos + PCI_EXP_FLAGS, + PCI_EXP_FLAGS_SLOT); + + pci_long_test_and_clear_mask(dev->config + pos + PCI_EXP_SLTCAP, + ~PCI_EXP_SLTCAP_PSN); + pci_long_test_and_set_mask(dev->config + pos + PCI_EXP_SLTCAP, + (slot << PCI_EXP_SLTCAP_PSN_SHIFT) | + PCI_EXP_SLTCAP_EIP | + PCI_EXP_SLTCAP_HPS | + PCI_EXP_SLTCAP_HPC | + PCI_EXP_SLTCAP_PIP | + PCI_EXP_SLTCAP_AIP | + PCI_EXP_SLTCAP_ABP); + + pci_word_test_and_clear_mask(dev->config + pos + PCI_EXP_SLTCTL, + PCI_EXP_SLTCTL_PIC | + PCI_EXP_SLTCTL_AIC); + pci_word_test_and_set_mask(dev->config + pos + PCI_EXP_SLTCTL, + PCI_EXP_SLTCTL_PIC_OFF | + PCI_EXP_SLTCTL_AIC_OFF); + pci_word_test_and_set_mask(dev->wmask + pos + PCI_EXP_SLTCTL, + PCI_EXP_SLTCTL_PIC | + PCI_EXP_SLTCTL_AIC | + PCI_EXP_SLTCTL_HPIE | + PCI_EXP_SLTCTL_CCIE | + PCI_EXP_SLTCTL_PDCE | + PCI_EXP_SLTCTL_ABPE); + /* Although reading PCI_EXP_SLTCTL_EIC returns always 0, + * make the bit writable here in order to detect 1b is written. + * pcie_cap_slot_write_config() test-and-clear the bit, so + * this bit always returns 0 to the guest. + */ + pci_word_test_and_set_mask(dev->wmask + pos + PCI_EXP_SLTCTL, + PCI_EXP_SLTCTL_EIC); + + pci_word_test_and_set_mask(dev->w1cmask + pos + PCI_EXP_SLTSTA, + PCI_EXP_HP_EV_SUPPORTED); + + pci_bus_hotplug(pci_bridge_get_sec_bus(DO_UPCAST(PCIBridge, dev, dev)), + pcie_cap_slot_hotplug, &dev->qdev); +} + +void pcie_cap_slot_reset(PCIDevice *dev) +{ + uint8_t *exp_cap = dev->config + dev->exp.exp_cap; + + PCIE_DEV_PRINTF(dev, "reset\n"); + + pci_word_test_and_clear_mask(exp_cap + PCI_EXP_SLTCTL, + PCI_EXP_SLTCTL_EIC | + PCI_EXP_SLTCTL_PIC | + PCI_EXP_SLTCTL_AIC | + PCI_EXP_SLTCTL_HPIE | + PCI_EXP_SLTCTL_CCIE | + PCI_EXP_SLTCTL_PDCE | + PCI_EXP_SLTCTL_ABPE); + pci_word_test_and_set_mask(exp_cap + PCI_EXP_SLTCTL, + PCI_EXP_SLTCTL_PIC_OFF | + PCI_EXP_SLTCTL_AIC_OFF); + + pci_word_test_and_clear_mask(exp_cap + PCI_EXP_SLTSTA, + PCI_EXP_SLTSTA_EIS |/* on reset, + the lock is released */ + PCI_EXP_SLTSTA_CC | + PCI_EXP_SLTSTA_PDC | + PCI_EXP_SLTSTA_ABP); +} + +void pcie_cap_slot_write_config(PCIDevice *dev, + uint32_t addr, uint32_t val, int len, + uint16_t sltctl_prev) +{ + uint32_t pos = dev->exp.exp_cap; + uint8_t *exp_cap = dev->config + pos; + uint16_t sltctl = pci_get_word(exp_cap + PCI_EXP_SLTCTL); + uint16_t sltsta = pci_get_word(exp_cap + PCI_EXP_SLTSTA); + + PCIE_DEV_PRINTF(dev, + "addr: 0x%"PRIx32" val: 0x%"PRIx32" len: %d\n" + "\tsltctl_prev: 0x%02"PRIx16" sltctl: 0x%02"PRIx16 + " sltsta: 0x%02"PRIx16"\n", + addr, val, len, sltctl_prev, sltctl, sltsta); + + /* SLTCTL */ + if (ranges_overlap(addr, len, pos + PCI_EXP_SLTCTL, 2)) { + PCIE_DEV_PRINTF(dev, "sltctl: 0x%02"PRIx16" -> 0x%02"PRIx16"\n", + sltctl_prev, sltctl); + if (pci_word_test_and_clear_mask(exp_cap + PCI_EXP_SLTCTL, + PCI_EXP_SLTCTL_EIC)) { + sltsta ^= PCI_EXP_SLTSTA_EIS; /* toggle PCI_EXP_SLTSTA_EIS bit */ + pci_set_word(exp_cap + PCI_EXP_SLTSTA, sltsta); + PCIE_DEV_PRINTF(dev, "PCI_EXP_SLTCTL_EIC: " + "sltsta -> 0x%02"PRIx16"\n", + sltsta); + } + + /* + * The events control bits might be enabled or disabled, + * Check if the software notificastion condition is satisfied + * or disatisfied. + * + * 6.7.3.4 Software Notification of Hot-plug events + */ + if (pci_msi_enabled(dev)) { + bool msi_trigger = + (sltctl & PCI_EXP_SLTCTL_HPIE) && + ((sltctl_prev ^ sltctl) & sltctl & /* stlctl: 0 -> 1 */ + sltsta & PCI_EXP_HP_EV_SUPPORTED); + if (msi_trigger) { + pci_msi_notify(dev, pcie_cap_flags_get_vector(dev)); + } + } else { + int int_level = + (sltctl & PCI_EXP_SLTCTL_HPIE) && + (sltctl & sltsta & PCI_EXP_HP_EV_SUPPORTED); + qemu_set_irq(dev->irq[dev->exp.hpev_intx], int_level); + } + + if (!((sltctl_prev ^ sltctl) & PCI_EXP_SLTCTL_SUPPORTED)) { + PCIE_DEV_PRINTF(dev, + "sprious command completion slctl " + "0x%"PRIx16" -> 0x%"PRIx16"\n", + sltctl_prev, sltctl); + } + + /* command completion. + * Real hardware might take a while to complete + * requested command because physical movement would be involved + * like locking the electromechanical lock. + * However in our case, command is completed instantaneously above, + * so send a command completion event right now. + * + * 6.7.3.2 Command Completed Events + */ + /* set command completed bit */ + pcie_cap_slot_event(dev, PCI_EXP_HP_EV_CCI); + } +} + +void pcie_cap_slot_push_attention_button(PCIDevice *dev) +{ + pcie_cap_slot_event(dev, PCI_EXP_HP_EV_ABP); +} + +/* root control/capabilities/status. PME isn't emulated for now */ +void pcie_cap_root_init(PCIDevice *dev) +{ + pci_set_word(dev->wmask + dev->exp.exp_cap + PCI_EXP_RTCTL, + PCI_EXP_RTCTL_SECEE | PCI_EXP_RTCTL_SENFEE | + PCI_EXP_RTCTL_SEFEE); +} + +void pcie_cap_root_reset(PCIDevice *dev) +{ + pci_set_word(dev->config + dev->exp.exp_cap + PCI_EXP_RTCTL, 0); +} + +/* + * TODO: implement FLR: + * Right now sets the bit which indicates FLR is supported. + */ +/* function level reset(FLR) */ +void pcie_cap_flr_init(PCIDevice *dev) +{ + pci_long_test_and_set_mask(dev->config + dev->exp.exp_cap + PCI_EXP_DEVCAP, + PCI_EXP_DEVCAP_FLR); + + /* Although reading BCR_FLR returns always 0, + * the bit is made writable here in order to detect the 1b is written + * pcie_cap_flr_write_config() test-and-clear the bit, so + * this bit always returns 0 to the guest. + */ + pci_word_test_and_set_mask(dev->wmask + dev->exp.exp_cap + PCI_EXP_DEVCTL, + PCI_EXP_DEVCTL_BCR_FLR); +} + +void pcie_cap_flr_write_config(PCIDevice *dev, + uint32_t addr, uint32_t val, int len) +{ + uint8_t *devctl = dev->config + dev->exp.exp_cap + PCI_EXP_DEVCTL; + if (pci_word_test_and_clear_mask(devctl, PCI_EXP_DEVCTL_BCR_FLR)) { + /* TODO: implement FLR */ + } +} + +/* Alternative Routing-ID Interpretation (ARI) */ +/* ari forwarding support for down stream port */ +void pcie_cap_ari_init(PCIDevice *dev) +{ + uint32_t pos = dev->exp.exp_cap; + pci_long_test_and_set_mask(dev->config + pos + PCI_EXP_DEVCAP2, + PCI_EXP_DEVCAP2_ARI); + pci_long_test_and_set_mask(dev->wmask + pos + PCI_EXP_DEVCTL2, + PCI_EXP_DEVCTL2_ARI); +} + +void pcie_cap_ari_reset(PCIDevice *dev) +{ + uint8_t *devctl2 = dev->config + dev->exp.exp_cap + PCI_EXP_DEVCTL2; + pci_long_test_and_clear_mask(devctl2, PCI_EXP_DEVCTL2_ARI); +} + +bool pcie_cap_is_ari_enabled(const PCIDevice *dev) +{ + if (!pci_is_express(dev)) { + return false; + } + if (!dev->exp.exp_cap) { + return false; + } + + return pci_get_long(dev->config + dev->exp.exp_cap + PCI_EXP_DEVCTL2) & + PCI_EXP_DEVCTL2_ARI; +} + +/************************************************************************** + * pci express extended capability allocation functions + * uint16_t ext_cap_id (16 bit) + * uint8_t cap_ver (4 bit) + * uint16_t cap_offset (12 bit) + * uint16_t ext_cap_size + */ + +static uint16_t pcie_find_capability_list(PCIDevice *dev, uint16_t cap_id, + uint16_t *prev_p) +{ + uint16_t prev = 0; + uint16_t next; + uint32_t header = pci_get_long(dev->config + PCI_CONFIG_SPACE_SIZE); + + if (!header) { + /* no extended capability */ + next = 0; + goto out; + } + for (next = PCI_CONFIG_SPACE_SIZE; next; + prev = next, next = PCI_EXT_CAP_NEXT(header)) { + + assert(next >= PCI_CONFIG_SPACE_SIZE); + assert(next <= PCIE_CONFIG_SPACE_SIZE - 8); + + header = pci_get_long(dev->config + next); + if (PCI_EXT_CAP_ID(header) == cap_id) { + break; + } + } + +out: + if (prev_p) { + *prev_p = prev; + } + return next; +} + +uint16_t pcie_find_capability(PCIDevice *dev, uint16_t cap_id) +{ + return pcie_find_capability_list(dev, cap_id, NULL); +} + +static void pcie_ext_cap_set_next(PCIDevice *dev, uint16_t pos, uint16_t next) +{ + uint16_t header = pci_get_long(dev->config + pos); + assert(!(next & (PCI_EXT_CAP_ALIGN - 1))); + header = (header & ~PCI_EXT_CAP_NEXT_MASK) | + ((next << PCI_EXT_CAP_NEXT_SHIFT) & PCI_EXT_CAP_NEXT_MASK); + pci_set_long(dev->config + pos, header); +} + +/* + * caller must supply valid (offset, size) * such that the range shouldn't + * overlap with other capability or other registers. + * This function doesn't check it. + */ +void pcie_add_capability(PCIDevice *dev, + uint16_t cap_id, uint8_t cap_ver, + uint16_t offset, uint16_t size) +{ + uint32_t header; + uint16_t next; + + assert(offset >= PCI_CONFIG_SPACE_SIZE); + assert(offset < offset + size); + assert(offset + size < PCIE_CONFIG_SPACE_SIZE); + assert(size >= 8); + assert(pci_is_express(dev)); + + if (offset == PCI_CONFIG_SPACE_SIZE) { + header = pci_get_long(dev->config + offset); + next = PCI_EXT_CAP_NEXT(header); + } else { + uint16_t prev; + + /* 0 is reserved cap id. use internally to find the last capability + in the linked list */ + next = pcie_find_capability_list(dev, 0, &prev); + + assert(prev >= PCI_CONFIG_SPACE_SIZE); + assert(next == 0); + pcie_ext_cap_set_next(dev, prev, offset); + } + pci_set_long(dev->config + offset, PCI_EXT_CAP(cap_id, cap_ver, next)); + + /* Make capability read-only by default */ + memset(dev->wmask + offset, 0, size); + memset(dev->w1cmask + offset, 0, size); + /* Check capability by default */ + memset(dev->cmask + offset, 0xFF, size); +} + +/************************************************************************** + * pci express extended capability helper functions + */ + +/* ARI */ +void pcie_ari_init(PCIDevice *dev, uint16_t offset, uint16_t nextfn) +{ + pcie_add_capability(dev, PCI_EXT_CAP_ID_ARI, PCI_ARI_VER, + offset, PCI_ARI_SIZEOF); + pci_set_long(dev->config + offset + PCI_ARI_CAP, PCI_ARI_CAP_NFN(nextfn)); +} diff --git a/hw/pcie.h b/hw/pcie.h new file mode 100644 index 0000000000..68327d8d0d --- /dev/null +++ b/hw/pcie.h @@ -0,0 +1,107 @@ +/* + * pcie.h + * + * Copyright (c) 2010 Isaku Yamahata + * VA Linux Systems Japan K.K. + * + * 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 . + */ + +#ifndef QEMU_PCIE_H +#define QEMU_PCIE_H + +#include "hw.h" +#include "pci_regs.h" +#include "pcie_regs.h" + +typedef enum { + /* for attention and power indicator */ + PCI_EXP_HP_IND_RESERVED = PCI_EXP_SLTCTL_IND_RESERVED, + PCI_EXP_HP_IND_ON = PCI_EXP_SLTCTL_IND_ON, + PCI_EXP_HP_IND_BLINK = PCI_EXP_SLTCTL_IND_BLINK, + PCI_EXP_HP_IND_OFF = PCI_EXP_SLTCTL_IND_OFF, +} PCIExpressIndicator; + +typedef enum { + /* these bits must match the bits in Slot Control/Status registers. + * PCI_EXP_HP_EV_xxx = PCI_EXP_SLTCTL_xxxE = PCI_EXP_SLTSTA_xxx + * + * Not all the bits of slot control register match with the ones of + * slot status. Not some bits of slot status register is used to + * show status, not to report event occurence. + * So such bits must be masked out when checking the software + * notification condition. + */ + PCI_EXP_HP_EV_ABP = PCI_EXP_SLTCTL_ABPE, + /* attention button pressed */ + PCI_EXP_HP_EV_PDC = PCI_EXP_SLTCTL_PDCE, + /* presence detect changed */ + PCI_EXP_HP_EV_CCI = PCI_EXP_SLTCTL_CCIE, + /* command completed */ + + PCI_EXP_HP_EV_SUPPORTED = PCI_EXP_HP_EV_ABP | + PCI_EXP_HP_EV_PDC | + PCI_EXP_HP_EV_CCI, + /* supported event mask */ + + /* events not listed aren't supported */ +} PCIExpressHotPlugEvent; + +struct PCIExpressDevice { + /* Offset of express capability in config space */ + uint8_t exp_cap; + + /* TODO FLR */ + + /* SLOT */ + unsigned int hpev_intx; /* INTx for hot plug event */ +}; + +/* PCI express capability helper functions */ +int pcie_cap_init(PCIDevice *dev, uint8_t offset, uint8_t type, uint8_t port); +void pcie_cap_exit(PCIDevice *dev); +uint8_t pcie_cap_get_type(const PCIDevice *dev); +void pcie_cap_flags_set_vector(PCIDevice *dev, uint8_t vector); +uint8_t pcie_cap_flags_get_vector(PCIDevice *dev); + +void pcie_cap_deverr_init(PCIDevice *dev); +void pcie_cap_deverr_reset(PCIDevice *dev); + +void pcie_cap_slot_init(PCIDevice *dev, uint16_t slot); +void pcie_cap_slot_reset(PCIDevice *dev); +void pcie_cap_slot_write_config(PCIDevice *dev, + uint32_t addr, uint32_t val, int len, + uint16_t sltctl_prev); +void pcie_cap_slot_push_attention_button(PCIDevice *dev); + +void pcie_cap_root_init(PCIDevice *dev); +void pcie_cap_root_reset(PCIDevice *dev); + +void pcie_cap_flr_init(PCIDevice *dev); +void pcie_cap_flr_write_config(PCIDevice *dev, + uint32_t addr, uint32_t val, int len); + +void pcie_cap_ari_init(PCIDevice *dev); +void pcie_cap_ari_reset(PCIDevice *dev); +bool pcie_cap_is_ari_enabled(const PCIDevice *dev); + +/* PCI express extended capability helper functions */ +uint16_t pcie_find_capability(PCIDevice *dev, uint16_t cap_id); +void pcie_add_capability(PCIDevice *dev, + uint16_t cap_id, uint8_t cap_ver, + uint16_t offset, uint16_t size); + +void pcie_ari_init(PCIDevice *dev, uint16_t offset, uint16_t nextfn); + +#endif /* QEMU_PCIE_H */ diff --git a/qemu-common.h b/qemu-common.h index d735235f84..6d9ee26df0 100644 --- a/qemu-common.h +++ b/qemu-common.h @@ -219,6 +219,7 @@ typedef struct PCIHostState PCIHostState; typedef struct PCIExpressHost PCIExpressHost; typedef struct PCIBus PCIBus; typedef struct PCIDevice PCIDevice; +typedef struct PCIExpressDevice PCIExpressDevice; typedef struct PCIBridge PCIBridge; typedef struct SerialState SerialState; typedef struct IRQState *qemu_irq; From 6da6d29fa63ab7adcc2959355497a44654f3703e Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Wed, 20 Oct 2010 17:18:50 +0900 Subject: [PATCH 23/39] pcie: comment on hpev_intx document hpev_intx. Signed-off-by: Isaku Yamahata Signed-off-by: Michael S. Tsirkin --- hw/pcie.h | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/hw/pcie.h b/hw/pcie.h index 68327d8d0d..2871e27012 100644 --- a/hw/pcie.h +++ b/hw/pcie.h @@ -65,7 +65,15 @@ struct PCIExpressDevice { /* TODO FLR */ /* SLOT */ - unsigned int hpev_intx; /* INTx for hot plug event */ + unsigned int hpev_intx; /* INTx for hot plug event (0-3:INT[A-D]#) + * default is 0 = INTA# + * If the chip wants to use other interrupt + * line, initialize this member with the + * desired number. + * If the chip dynamically changes this member, + * also initialize it when loaded as + * appropreately. + */ }; /* PCI express capability helper functions */ From 0208def1cadd4f72f862e62548c2af268a543b20 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Wed, 20 Oct 2010 17:18:51 +0900 Subject: [PATCH 24/39] pci/bridge: fix pci_bridge_reset() The lower bits of base/limit registers is RO and shouldn't be zero cleared on reset. This patch fixes it. In fact, the default value of base/limit registers aren't specified in the spec. And some bridges disable forwarding on reset instead of zeroing base/limit registers. So introduce one function to disable bridge forwarding so that such bridges can use it. It will be used later. Signed-off-by: Isaku Yamahata Signed-off-by: Michael S. Tsirkin --- hw/pci_bridge.c | 48 ++++++++++++++++++++++++++++++++++++++++++------ hw/pci_bridge.h | 1 + 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/hw/pci_bridge.c b/hw/pci_bridge.c index 638e3b35eb..7e8488a4fb 100644 --- a/hw/pci_bridge.c +++ b/hw/pci_bridge.c @@ -151,6 +151,26 @@ void pci_bridge_write_config(PCIDevice *d, } } +void pci_bridge_disable_base_limit(PCIDevice *dev) +{ + uint8_t *conf = dev->config; + + pci_byte_test_and_set_mask(conf + PCI_IO_BASE, + PCI_IO_RANGE_MASK & 0xff); + pci_byte_test_and_clear_mask(conf + PCI_IO_LIMIT, + PCI_IO_RANGE_MASK & 0xff); + pci_word_test_and_set_mask(conf + PCI_MEMORY_BASE, + PCI_MEMORY_RANGE_MASK & 0xffff); + pci_word_test_and_clear_mask(conf + PCI_MEMORY_LIMIT, + PCI_MEMORY_RANGE_MASK & 0xffff); + pci_word_test_and_set_mask(conf + PCI_PREF_MEMORY_BASE, + PCI_PREF_RANGE_MASK & 0xffff); + pci_word_test_and_clear_mask(conf + PCI_PREF_MEMORY_LIMIT, + PCI_PREF_RANGE_MASK & 0xffff); + pci_set_word(conf + PCI_PREF_BASE_UPPER32, 0); + pci_set_word(conf + PCI_PREF_LIMIT_UPPER32, 0); +} + /* reset bridge specific configuration registers */ void pci_bridge_reset_reg(PCIDevice *dev) { @@ -161,12 +181,28 @@ void pci_bridge_reset_reg(PCIDevice *dev) conf[PCI_SUBORDINATE_BUS] = 0; conf[PCI_SEC_LATENCY_TIMER] = 0; - conf[PCI_IO_BASE] = 0; - conf[PCI_IO_LIMIT] = 0; - pci_set_word(conf + PCI_MEMORY_BASE, 0); - pci_set_word(conf + PCI_MEMORY_LIMIT, 0); - pci_set_word(conf + PCI_PREF_MEMORY_BASE, 0); - pci_set_word(conf + PCI_PREF_MEMORY_LIMIT, 0); + /* + * the default values for base/limit registers aren't specified + * in the PCI-to-PCI-bridge spec. So we don't thouch them here. + * Each implementation can override it. + * typical implementation does + * zero base/limit registers or + * disable forwarding: pci_bridge_disable_base_limit() + * If disable forwarding is wanted, call pci_bridge_disable_base_limit() + * after this function. + */ + pci_byte_test_and_clear_mask(conf + PCI_IO_BASE, + PCI_IO_RANGE_MASK & 0xff); + pci_byte_test_and_clear_mask(conf + PCI_IO_LIMIT, + PCI_IO_RANGE_MASK & 0xff); + pci_word_test_and_clear_mask(conf + PCI_MEMORY_BASE, + PCI_MEMORY_RANGE_MASK & 0xffff); + pci_word_test_and_clear_mask(conf + PCI_MEMORY_LIMIT, + PCI_MEMORY_RANGE_MASK & 0xffff); + pci_word_test_and_clear_mask(conf + PCI_PREF_MEMORY_BASE, + PCI_PREF_RANGE_MASK & 0xffff); + pci_word_test_and_clear_mask(conf + PCI_PREF_MEMORY_LIMIT, + PCI_PREF_RANGE_MASK & 0xffff); pci_set_word(conf + PCI_PREF_BASE_UPPER32, 0); pci_set_word(conf + PCI_PREF_LIMIT_UPPER32, 0); diff --git a/hw/pci_bridge.h b/hw/pci_bridge.h index f6fade0322..84411a69dc 100644 --- a/hw/pci_bridge.h +++ b/hw/pci_bridge.h @@ -39,6 +39,7 @@ pcibus_t pci_bridge_get_limit(const PCIDevice *bridge, uint8_t type); void pci_bridge_write_config(PCIDevice *d, uint32_t address, uint32_t val, int len); +void pci_bridge_disable_base_limit(PCIDevice *dev); void pci_bridge_reset_reg(PCIDevice *dev); void pci_bridge_reset(DeviceState *qdev); From bc20ba98b1a04c9e60de10f2a5626af2c528422b Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Wed, 20 Oct 2010 17:18:52 +0900 Subject: [PATCH 25/39] pcie port: define struct PCIEPort/PCIESlot and helper functions define struct PCIEPort which represents common part of pci express port.(root, upstream and downstream.) add a helper function for pcie port which can be used commonly by root/upstream/downstream port. define struct PCIESlot which represents common part of pcie slot.(root and downstream.) and helper functions for it. helper functions for chassis, slot -> PCIESlot conversion. Signed-off-by: Isaku Yamahata Signed-off-by: Michael S. Tsirkin --- Makefile.objs | 2 +- hw/pcie_port.c | 116 +++++++++++++++++++++++++++++++++++++++++++++++++ hw/pcie_port.h | 51 ++++++++++++++++++++++ qemu-common.h | 2 + 4 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 hw/pcie_port.c create mode 100644 hw/pcie_port.h diff --git a/Makefile.objs b/Makefile.objs index eeb5134600..c73d12b651 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -186,7 +186,7 @@ hw-obj-$(CONFIG_PIIX4) += piix4.o # PCI watchdog devices hw-obj-y += wdt_i6300esb.o -hw-obj-y += pcie.o +hw-obj-y += pcie.o pcie_port.o hw-obj-y += msix.o msi.o # PCI network cards diff --git a/hw/pcie_port.c b/hw/pcie_port.c new file mode 100644 index 0000000000..117de6186e --- /dev/null +++ b/hw/pcie_port.c @@ -0,0 +1,116 @@ +/* + * pcie_port.c + * + * Copyright (c) 2010 Isaku Yamahata + * VA Linux Systems Japan K.K. + * + * 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 . + */ + +#include "pcie_port.h" + +void pcie_port_init_reg(PCIDevice *d) +{ + /* Unlike pci bridge, + 66MHz and fast back to back don't apply to pci express port. */ + pci_set_word(d->config + PCI_STATUS, 0); + pci_set_word(d->config + PCI_SEC_STATUS, 0); + + /* 7.5.3.5 Prefetchable Memory Base Limit + * The Prefetchable Memory Base and Prefetchable Memory Limit registers + * must indicate that 64-bit addresses are supported, as defined in + * PCI-to-PCI Bridge Architecture Specification, Revision 1.2. + */ + pci_word_test_and_set_mask(d->config + PCI_PREF_MEMORY_BASE, + PCI_PREF_RANGE_TYPE_64); + pci_word_test_and_set_mask(d->config + PCI_PREF_MEMORY_LIMIT, + PCI_PREF_RANGE_TYPE_64); +} + +/************************************************************************** + * (chassis number, pcie physical slot number) -> pcie slot conversion + */ +struct PCIEChassis { + uint8_t number; + + QLIST_HEAD(, PCIESlot) slots; + QLIST_ENTRY(PCIEChassis) next; +}; + +static QLIST_HEAD(, PCIEChassis) chassis = QLIST_HEAD_INITIALIZER(chassis); + +static struct PCIEChassis *pcie_chassis_find(uint8_t chassis_number) +{ + struct PCIEChassis *c; + QLIST_FOREACH(c, &chassis, next) { + if (c->number == chassis_number) { + break; + } + } + return c; +} + +void pcie_chassis_create(uint8_t chassis_number) +{ + struct PCIEChassis *c; + c = pcie_chassis_find(chassis_number); + if (c) { + return; + } + c = qemu_mallocz(sizeof(*c)); + c->number = chassis_number; + QLIST_INIT(&c->slots); + QLIST_INSERT_HEAD(&chassis, c, next); +} + +static PCIESlot *pcie_chassis_find_slot_with_chassis(struct PCIEChassis *c, + uint8_t slot) +{ + PCIESlot *s; + QLIST_FOREACH(s, &c->slots, next) { + if (s->slot == slot) { + break; + } + } + return s; +} + +PCIESlot *pcie_chassis_find_slot(uint8_t chassis_number, uint16_t slot) +{ + struct PCIEChassis *c; + c = pcie_chassis_find(chassis_number); + if (!c) { + return NULL; + } + return pcie_chassis_find_slot_with_chassis(c, slot); +} + +int pcie_chassis_add_slot(struct PCIESlot *slot) +{ + struct PCIEChassis *c; + c = pcie_chassis_find(slot->chassis); + if (!c) { + return -ENODEV; + } + if (pcie_chassis_find_slot_with_chassis(c, slot->slot)) { + return -EBUSY; + } + QLIST_INSERT_HEAD(&c->slots, slot, next); + return 0; +} + +void pcie_chassis_del_slot(PCIESlot *s) +{ + QLIST_REMOVE(s, next); +} diff --git a/hw/pcie_port.h b/hw/pcie_port.h new file mode 100644 index 0000000000..3709583cc0 --- /dev/null +++ b/hw/pcie_port.h @@ -0,0 +1,51 @@ +/* + * pcie_port.h + * + * Copyright (c) 2010 Isaku Yamahata + * VA Linux Systems Japan K.K. + * + * 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 . + */ + +#ifndef QEMU_PCIE_PORT_H +#define QEMU_PCIE_PORT_H + +#include "pci_bridge.h" +#include "pci_internals.h" + +struct PCIEPort { + PCIBridge br; + + /* pci express switch port */ + uint8_t port; +}; + +void pcie_port_init_reg(PCIDevice *d); + +struct PCIESlot { + PCIEPort port; + + /* pci express switch port with slot */ + uint8_t chassis; + uint16_t slot; + QLIST_ENTRY(PCIESlot) next; +}; + +void pcie_chassis_create(uint8_t chassis_number); +void pcie_main_chassis_create(void); +PCIESlot *pcie_chassis_find_slot(uint8_t chassis, uint16_t slot); +int pcie_chassis_add_slot(struct PCIESlot *slot); +void pcie_chassis_del_slot(PCIESlot *s); + +#endif /* QEMU_PCIE_PORT_H */ diff --git a/qemu-common.h b/qemu-common.h index 6d9ee26df0..b97b16eeca 100644 --- a/qemu-common.h +++ b/qemu-common.h @@ -221,6 +221,8 @@ typedef struct PCIBus PCIBus; typedef struct PCIDevice PCIDevice; typedef struct PCIExpressDevice PCIExpressDevice; typedef struct PCIBridge PCIBridge; +typedef struct PCIEPort PCIEPort; +typedef struct PCIESlot PCIESlot; typedef struct SerialState SerialState; typedef struct IRQState *qemu_irq; typedef struct PCMCIACardState PCMCIACardState; From 8135aeed0f0b370a7978d06a49de20f50181e7b9 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Wed, 20 Oct 2010 17:18:53 +0900 Subject: [PATCH 26/39] ioh3420: pcie root port in X58 ioh Implements pcie root port switch in intel X58 ioh whose device id is 0x3420. Signed-off-by: Isaku Yamahata Signed-off-by: Michael S. Tsirkin --- Makefile.objs | 1 + hw/ioh3420.c | 188 ++++++++++++++++++++++++++++++++++++++++++++++++++ hw/ioh3420.h | 10 +++ 3 files changed, 199 insertions(+) create mode 100644 hw/ioh3420.c create mode 100644 hw/ioh3420.h diff --git a/Makefile.objs b/Makefile.objs index c73d12b651..3a05322ccd 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -140,6 +140,7 @@ hw-obj-y = hw-obj-y += vl.o loader.o hw-obj-y += virtio.o virtio-console.o hw-obj-y += fw_cfg.o pci.o pci_host.o pcie_host.o pci_bridge.o +hw-obj-y += ioh3420.o hw-obj-y += watchdog.o hw-obj-$(CONFIG_ISA_MMIO) += isa_mmio.o hw-obj-$(CONFIG_ECC) += ecc.o diff --git a/hw/ioh3420.c b/hw/ioh3420.c new file mode 100644 index 0000000000..1f340d3223 --- /dev/null +++ b/hw/ioh3420.c @@ -0,0 +1,188 @@ +/* + * ioh3420.c + * Intel X58 north bridge IOH + * PCI Express root port device id 3420 + * + * Copyright (c) 2010 Isaku Yamahata + * VA Linux Systems Japan K.K. + * + * 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 . + */ + +#include "pci_ids.h" +#include "msi.h" +#include "pcie.h" +#include "ioh3420.h" + +#define PCI_DEVICE_ID_IOH_EPORT 0x3420 /* D0:F0 express mode */ +#define PCI_DEVICE_ID_IOH_REV 0x2 +#define IOH_EP_SSVID_OFFSET 0x40 +#define IOH_EP_SSVID_SVID PCI_VENDOR_ID_INTEL +#define IOH_EP_SSVID_SSID 0 +#define IOH_EP_MSI_OFFSET 0x60 +#define IOH_EP_MSI_SUPPORTED_FLAGS PCI_MSI_FLAGS_MASKBIT +#define IOH_EP_MSI_NR_VECTOR 2 +#define IOH_EP_EXP_OFFSET 0x90 +#define IOH_EP_AER_OFFSET 0x100 + +static void ioh3420_write_config(PCIDevice *d, + uint32_t address, uint32_t val, int len) +{ + uint16_t sltctl = + pci_get_word(d->config + d->exp.exp_cap + PCI_EXP_SLTCTL); + + pci_bridge_write_config(d, address, val, len); + msi_write_config(d, address, val, len); + pcie_cap_slot_write_config(d, address, val, len, sltctl); + /* TODO: AER */ +} + +static void ioh3420_reset(DeviceState *qdev) +{ + PCIDevice *d = DO_UPCAST(PCIDevice, qdev, qdev); + msi_reset(d); + pcie_cap_root_reset(d); + pcie_cap_deverr_reset(d); + pcie_cap_slot_reset(d); + pci_bridge_reset(qdev); + pci_bridge_disable_base_limit(d); + /* TODO: AER */ +} + +static int ioh3420_initfn(PCIDevice *d) +{ + PCIBridge* br = DO_UPCAST(PCIBridge, dev, d); + PCIEPort *p = DO_UPCAST(PCIEPort, br, br); + PCIESlot *s = DO_UPCAST(PCIESlot, port, p); + int rc; + + rc = pci_bridge_initfn(d); + if (rc < 0) { + return rc; + } + + d->config[PCI_REVISION_ID] = PCI_DEVICE_ID_IOH_REV; + pcie_port_init_reg(d); + + pci_config_set_vendor_id(d->config, PCI_VENDOR_ID_INTEL); + pci_config_set_device_id(d->config, PCI_DEVICE_ID_IOH_EPORT); + + rc = pci_bridge_ssvid_init(d, IOH_EP_SSVID_OFFSET, + IOH_EP_SSVID_SVID, IOH_EP_SSVID_SSID); + if (rc < 0) { + return rc; + } + rc = msi_init(d, IOH_EP_MSI_OFFSET, IOH_EP_MSI_NR_VECTOR, + IOH_EP_MSI_SUPPORTED_FLAGS & PCI_MSI_FLAGS_64BIT, + IOH_EP_MSI_SUPPORTED_FLAGS & PCI_MSI_FLAGS_MASKBIT); + if (rc < 0) { + return rc; + } + rc = pcie_cap_init(d, IOH_EP_EXP_OFFSET, PCI_EXP_TYPE_ROOT_PORT, p->port); + if (rc < 0) { + return rc; + } + pcie_cap_deverr_init(d); + pcie_cap_slot_init(d, s->slot); + pcie_chassis_create(s->chassis); + rc = pcie_chassis_add_slot(s); + if (rc < 0) { + return rc; + } + pcie_cap_root_init(d); + /* TODO: AER */ + return 0; +} + +static int ioh3420_exitfn(PCIDevice *d) +{ + /* TODO: AER */ + msi_uninit(d); + pcie_cap_exit(d); + return pci_bridge_exitfn(d); +} + +PCIESlot *ioh3420_init(PCIBus *bus, int devfn, bool multifunction, + const char *bus_name, pci_map_irq_fn map_irq, + uint8_t port, uint8_t chassis, uint16_t slot) +{ + PCIDevice *d; + PCIBridge *br; + DeviceState *qdev; + + d = pci_create_multifunction(bus, devfn, multifunction, "ioh3420"); + if (!d) { + return NULL; + } + br = DO_UPCAST(PCIBridge, dev, d); + + qdev = &br->dev.qdev; + pci_bridge_map_irq(br, bus_name, map_irq); + qdev_prop_set_uint8(qdev, "port", port); + qdev_prop_set_uint8(qdev, "chassis", chassis); + qdev_prop_set_uint16(qdev, "slot", slot); + qdev_init_nofail(qdev); + + return DO_UPCAST(PCIESlot, port, DO_UPCAST(PCIEPort, br, br)); +} + +static const VMStateDescription vmstate_ioh3420 = { + .name = "ioh-3240-express-root-port", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_PCIE_DEVICE(port.br.dev, PCIESlot), + /* TODO: AER */ + VMSTATE_END_OF_LIST() + } +}; + +static PCIDeviceInfo ioh3420_info = { + .qdev.name = "ioh3420", + .qdev.desc = "Intel IOH device id 3420 PCIE Root Port", + .qdev.size = sizeof(PCIESlot), + .qdev.reset = ioh3420_reset, + .qdev.vmsd = &vmstate_ioh3420, + + .is_express = 1, + .is_bridge = 1, + .config_write = ioh3420_write_config, + .init = ioh3420_initfn, + .exit = ioh3420_exitfn, + + .qdev.props = (Property[]) { + DEFINE_PROP_UINT8("port", PCIESlot, port.port, 0), + DEFINE_PROP_UINT8("chassis", PCIESlot, chassis, 0), + DEFINE_PROP_UINT16("slot", PCIESlot, slot, 0), + /* TODO: AER */ + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void ioh3420_register(void) +{ + pci_qdev_register(&ioh3420_info); +} + +device_init(ioh3420_register); + +/* + * Local variables: + * c-indent-level: 4 + * c-basic-offset: 4 + * tab-width: 8 + * indent-tab-mode: nil + * End: + */ diff --git a/hw/ioh3420.h b/hw/ioh3420.h new file mode 100644 index 0000000000..68c523ab46 --- /dev/null +++ b/hw/ioh3420.h @@ -0,0 +1,10 @@ +#ifndef QEMU_IOH3420_H +#define QEMU_IOH3420_H + +#include "pcie_port.h" + +PCIESlot *ioh3420_init(PCIBus *bus, int devfn, bool multifunction, + const char *bus_name, pci_map_irq_fn map_irq, + uint8_t port, uint8_t chassis, uint16_t slot); + +#endif /* QEMU_IOH3420_H */ From faf1e708d5b432757d88b7229fc3b5f2e71cfb2e Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Wed, 20 Oct 2010 17:18:54 +0900 Subject: [PATCH 27/39] x3130: pcie upstream port Implement TI x3130 pcie upstream port switch. Signed-off-by: Isaku Yamahata Signed-off-by: Michael S. Tsirkin --- Makefile.objs | 2 +- hw/xio3130_upstream.c | 174 ++++++++++++++++++++++++++++++++++++++++++ hw/xio3130_upstream.h | 10 +++ 3 files changed, 185 insertions(+), 1 deletion(-) create mode 100644 hw/xio3130_upstream.c create mode 100644 hw/xio3130_upstream.h diff --git a/Makefile.objs b/Makefile.objs index 3a05322ccd..b1ef2bbebc 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -140,7 +140,7 @@ hw-obj-y = hw-obj-y += vl.o loader.o hw-obj-y += virtio.o virtio-console.o hw-obj-y += fw_cfg.o pci.o pci_host.o pcie_host.o pci_bridge.o -hw-obj-y += ioh3420.o +hw-obj-y += ioh3420.o xio3130_upstream.o hw-obj-y += watchdog.o hw-obj-$(CONFIG_ISA_MMIO) += isa_mmio.o hw-obj-$(CONFIG_ECC) += ecc.o diff --git a/hw/xio3130_upstream.c b/hw/xio3130_upstream.c new file mode 100644 index 0000000000..d9d637fdae --- /dev/null +++ b/hw/xio3130_upstream.c @@ -0,0 +1,174 @@ +/* + * xio3130_upstream.c + * TI X3130 pci express upstream port switch + * + * Copyright (c) 2010 Isaku Yamahata + * VA Linux Systems Japan K.K. + * + * 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 . + */ + +#include "pci_ids.h" +#include "msi.h" +#include "pcie.h" +#include "xio3130_upstream.h" + +#define PCI_DEVICE_ID_TI_XIO3130U 0x8232 /* upstream port */ +#define XIO3130_REVISION 0x2 +#define XIO3130_MSI_OFFSET 0x70 +#define XIO3130_MSI_SUPPORTED_FLAGS PCI_MSI_FLAGS_64BIT +#define XIO3130_MSI_NR_VECTOR 1 +#define XIO3130_SSVID_OFFSET 0x80 +#define XIO3130_SSVID_SVID 0 +#define XIO3130_SSVID_SSID 0 +#define XIO3130_EXP_OFFSET 0x90 +#define XIO3130_AER_OFFSET 0x100 + +static void xio3130_upstream_write_config(PCIDevice *d, uint32_t address, + uint32_t val, int len) +{ + pci_bridge_write_config(d, address, val, len); + pcie_cap_flr_write_config(d, address, val, len); + msi_write_config(d, address, val, len); + /* TODO: AER */ +} + +static void xio3130_upstream_reset(DeviceState *qdev) +{ + PCIDevice *d = DO_UPCAST(PCIDevice, qdev, qdev); + msi_reset(d); + pci_bridge_reset(qdev); + pcie_cap_deverr_reset(d); +} + +static int xio3130_upstream_initfn(PCIDevice *d) +{ + PCIBridge* br = DO_UPCAST(PCIBridge, dev, d); + PCIEPort *p = DO_UPCAST(PCIEPort, br, br); + int rc; + + rc = pci_bridge_initfn(d); + if (rc < 0) { + return rc; + } + + pcie_port_init_reg(d); + pci_config_set_vendor_id(d->config, PCI_VENDOR_ID_TI); + pci_config_set_device_id(d->config, PCI_DEVICE_ID_TI_XIO3130U); + d->config[PCI_REVISION_ID] = XIO3130_REVISION; + + rc = msi_init(d, XIO3130_MSI_OFFSET, XIO3130_MSI_NR_VECTOR, + XIO3130_MSI_SUPPORTED_FLAGS & PCI_MSI_FLAGS_64BIT, + XIO3130_MSI_SUPPORTED_FLAGS & PCI_MSI_FLAGS_MASKBIT); + if (rc < 0) { + return rc; + } + rc = pci_bridge_ssvid_init(d, XIO3130_SSVID_OFFSET, + XIO3130_SSVID_SVID, XIO3130_SSVID_SSID); + if (rc < 0) { + return rc; + } + rc = pcie_cap_init(d, XIO3130_EXP_OFFSET, PCI_EXP_TYPE_UPSTREAM, + p->port); + if (rc < 0) { + return rc; + } + + /* TODO: implement FLR */ + pcie_cap_flr_init(d); + + pcie_cap_deverr_init(d); + /* TODO: AER */ + + return 0; +} + +static int xio3130_upstream_exitfn(PCIDevice *d) +{ + /* TODO: AER */ + msi_uninit(d); + pcie_cap_exit(d); + return pci_bridge_exitfn(d); +} + +PCIEPort *xio3130_upstream_init(PCIBus *bus, int devfn, bool multifunction, + const char *bus_name, pci_map_irq_fn map_irq, + uint8_t port) +{ + PCIDevice *d; + PCIBridge *br; + DeviceState *qdev; + + d = pci_create_multifunction(bus, devfn, multifunction, "x3130-upstream"); + if (!d) { + return NULL; + } + br = DO_UPCAST(PCIBridge, dev, d); + + qdev = &br->dev.qdev; + pci_bridge_map_irq(br, bus_name, map_irq); + qdev_prop_set_uint8(qdev, "port", port); + qdev_init_nofail(qdev); + + return DO_UPCAST(PCIEPort, br, br); +} + +static const VMStateDescription vmstate_xio3130_upstream = { + .name = "xio3130-express-upstream-port", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_PCIE_DEVICE(br.dev, PCIEPort), + /* TODO: AER */ + VMSTATE_END_OF_LIST() + } +}; + +static PCIDeviceInfo xio3130_upstream_info = { + .qdev.name = "x3130-upstream", + .qdev.desc = "TI X3130 Upstream Port of PCI Express Switch", + .qdev.size = sizeof(PCIEPort), + .qdev.reset = xio3130_upstream_reset, + .qdev.vmsd = &vmstate_xio3130_upstream, + + .is_express = 1, + .is_bridge = 1, + .config_write = xio3130_upstream_write_config, + .init = xio3130_upstream_initfn, + .exit = xio3130_upstream_exitfn, + + .qdev.props = (Property[]) { + DEFINE_PROP_UINT8("port", PCIEPort, port, 0), + /* TODO: AER */ + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void xio3130_upstream_register(void) +{ + pci_qdev_register(&xio3130_upstream_info); +} + +device_init(xio3130_upstream_register); + + +/* + * Local variables: + * c-indent-level: 4 + * c-basic-offset: 4 + * tab-width: 8 + * indent-tab-mode: nil + * End: + */ diff --git a/hw/xio3130_upstream.h b/hw/xio3130_upstream.h new file mode 100644 index 0000000000..e9969975ff --- /dev/null +++ b/hw/xio3130_upstream.h @@ -0,0 +1,10 @@ +#ifndef QEMU_XIO3130_UPSTREAM_H +#define QEMU_XIO3130_UPSTREAM_H + +#include "pcie_port.h" + +PCIEPort *xio3130_upstream_init(PCIBus *bus, int devfn, bool multifunction, + const char *bus_name, pci_map_irq_fn map_irq, + uint8_t port); + +#endif /* QEMU_XIO3130_H */ From 48ebf2f90f8fba8c03c0bfdb3bd4fe1e8fd5d61b Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Wed, 20 Oct 2010 17:18:55 +0900 Subject: [PATCH 28/39] x3130: pcie downstream port Implement TI x3130 pcie downstream port switch. Signed-off-by: Isaku Yamahata Signed-off-by: Michael S. Tsirkin --- Makefile.objs | 2 +- hw/xio3130_downstream.c | 190 ++++++++++++++++++++++++++++++++++++++++ hw/xio3130_downstream.h | 11 +++ 3 files changed, 202 insertions(+), 1 deletion(-) create mode 100644 hw/xio3130_downstream.c create mode 100644 hw/xio3130_downstream.h diff --git a/Makefile.objs b/Makefile.objs index b1ef2bbebc..138e545a09 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -140,7 +140,7 @@ hw-obj-y = hw-obj-y += vl.o loader.o hw-obj-y += virtio.o virtio-console.o hw-obj-y += fw_cfg.o pci.o pci_host.o pcie_host.o pci_bridge.o -hw-obj-y += ioh3420.o xio3130_upstream.o +hw-obj-y += ioh3420.o xio3130_upstream.o xio3130_downstream.o hw-obj-y += watchdog.o hw-obj-$(CONFIG_ISA_MMIO) += isa_mmio.o hw-obj-$(CONFIG_ECC) += ecc.o diff --git a/hw/xio3130_downstream.c b/hw/xio3130_downstream.c new file mode 100644 index 0000000000..a44e188190 --- /dev/null +++ b/hw/xio3130_downstream.c @@ -0,0 +1,190 @@ +/* + * x3130_downstream.c + * TI X3130 pci express downstream port switch + * + * Copyright (c) 2010 Isaku Yamahata + * VA Linux Systems Japan K.K. + * + * 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 . + */ + +#include "pci_ids.h" +#include "msi.h" +#include "pcie.h" +#include "xio3130_downstream.h" + +#define PCI_DEVICE_ID_TI_XIO3130D 0x8233 /* downstream port */ +#define XIO3130_REVISION 0x1 +#define XIO3130_MSI_OFFSET 0x70 +#define XIO3130_MSI_SUPPORTED_FLAGS PCI_MSI_FLAGS_64BIT +#define XIO3130_MSI_NR_VECTOR 1 +#define XIO3130_SSVID_OFFSET 0x80 +#define XIO3130_SSVID_SVID 0 +#define XIO3130_SSVID_SSID 0 +#define XIO3130_EXP_OFFSET 0x90 +#define XIO3130_AER_OFFSET 0x100 + +static void xio3130_downstream_write_config(PCIDevice *d, uint32_t address, + uint32_t val, int len) +{ + uint16_t sltctl = + pci_get_word(d->config + d->exp.exp_cap + PCI_EXP_SLTCTL); + + pci_bridge_write_config(d, address, val, len); + pcie_cap_flr_write_config(d, address, val, len); + pcie_cap_slot_write_config(d, address, val, len, sltctl); + msi_write_config(d, address, val, len); + /* TODO: AER */ +} + +static void xio3130_downstream_reset(DeviceState *qdev) +{ + PCIDevice *d = DO_UPCAST(PCIDevice, qdev, qdev); + msi_reset(d); + pcie_cap_deverr_reset(d); + pcie_cap_slot_reset(d); + pcie_cap_ari_reset(d); + pci_bridge_reset(qdev); +} + +static int xio3130_downstream_initfn(PCIDevice *d) +{ + PCIBridge* br = DO_UPCAST(PCIBridge, dev, d); + PCIEPort *p = DO_UPCAST(PCIEPort, br, br); + PCIESlot *s = DO_UPCAST(PCIESlot, port, p); + int rc; + + rc = pci_bridge_initfn(d); + if (rc < 0) { + return rc; + } + + pcie_port_init_reg(d); + pci_config_set_vendor_id(d->config, PCI_VENDOR_ID_TI); + pci_config_set_device_id(d->config, PCI_DEVICE_ID_TI_XIO3130D); + d->config[PCI_REVISION_ID] = XIO3130_REVISION; + + rc = msi_init(d, XIO3130_MSI_OFFSET, XIO3130_MSI_NR_VECTOR, + XIO3130_MSI_SUPPORTED_FLAGS & PCI_MSI_FLAGS_64BIT, + XIO3130_MSI_SUPPORTED_FLAGS & PCI_MSI_FLAGS_MASKBIT); + if (rc < 0) { + return rc; + } + rc = pci_bridge_ssvid_init(d, XIO3130_SSVID_OFFSET, + XIO3130_SSVID_SVID, XIO3130_SSVID_SSID); + if (rc < 0) { + return rc; + } + rc = pcie_cap_init(d, XIO3130_EXP_OFFSET, PCI_EXP_TYPE_DOWNSTREAM, + p->port); + if (rc < 0) { + return rc; + } + pcie_cap_flr_init(d); /* TODO: implement FLR */ + pcie_cap_deverr_init(d); + pcie_cap_slot_init(d, s->slot); + pcie_chassis_create(s->chassis); + rc = pcie_chassis_add_slot(s); + if (rc < 0) { + return rc; + } + pcie_cap_ari_init(d); + /* TODO: AER */ + + return 0; +} + +static int xio3130_downstream_exitfn(PCIDevice *d) +{ + /* TODO: AER */ + msi_uninit(d); + pcie_cap_exit(d); + return pci_bridge_exitfn(d); +} + +PCIESlot *xio3130_downstream_init(PCIBus *bus, int devfn, bool multifunction, + const char *bus_name, pci_map_irq_fn map_irq, + uint8_t port, uint8_t chassis, + uint16_t slot) +{ + PCIDevice *d; + PCIBridge *br; + DeviceState *qdev; + + d = pci_create_multifunction(bus, devfn, multifunction, + "xio3130-downstream"); + if (!d) { + return NULL; + } + br = DO_UPCAST(PCIBridge, dev, d); + + qdev = &br->dev.qdev; + pci_bridge_map_irq(br, bus_name, map_irq); + qdev_prop_set_uint8(qdev, "port", port); + qdev_prop_set_uint8(qdev, "chassis", chassis); + qdev_prop_set_uint16(qdev, "slot", slot); + qdev_init_nofail(qdev); + + return DO_UPCAST(PCIESlot, port, DO_UPCAST(PCIEPort, br, br)); +} + +static const VMStateDescription vmstate_xio3130_downstream = { + .name = "xio3130-express-downstream-port", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_PCIE_DEVICE(port.br.dev, PCIESlot), + /* TODO: AER */ + VMSTATE_END_OF_LIST() + } +}; + +static PCIDeviceInfo xio3130_downstream_info = { + .qdev.name = "xio3130-downstream", + .qdev.desc = "TI X3130 Downstream Port of PCI Express Switch", + .qdev.size = sizeof(PCIESlot), + .qdev.reset = xio3130_downstream_reset, + .qdev.vmsd = &vmstate_xio3130_downstream, + + .is_express = 1, + .is_bridge = 1, + .config_write = xio3130_downstream_write_config, + .init = xio3130_downstream_initfn, + .exit = xio3130_downstream_exitfn, + + .qdev.props = (Property[]) { + DEFINE_PROP_UINT8("port", PCIESlot, port.port, 0), + DEFINE_PROP_UINT8("chassis", PCIESlot, chassis, 0), + DEFINE_PROP_UINT16("slot", PCIESlot, slot, 0), + /* TODO: AER */ + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void xio3130_downstream_register(void) +{ + pci_qdev_register(&xio3130_downstream_info); +} + +device_init(xio3130_downstream_register); + +/* + * Local variables: + * c-indent-level: 4 + * c-basic-offset: 4 + * tab-width: 8 + * indent-tab-mode: nil + * End: + */ diff --git a/hw/xio3130_downstream.h b/hw/xio3130_downstream.h new file mode 100644 index 0000000000..010487f2d9 --- /dev/null +++ b/hw/xio3130_downstream.h @@ -0,0 +1,11 @@ +#ifndef QEMU_XIO3130_DOWNSTREAM_H +#define QEMU_XIO3130_DOWNSTREAM_H + +#include "pcie_port.h" + +PCIESlot *xio3130_downstream_init(PCIBus *bus, int devfn, bool multifunction, + const char *bus_name, pci_map_irq_fn map_irq, + uint8_t port, uint8_t chassis, + uint16_t slot); + +#endif /* QEMU_XIO3130_DOWNSTREAM_H */ From 492fb99c4bef274a71178f5fab9b700c469a4230 Mon Sep 17 00:00:00 2001 From: "Michael S. Tsirkin" Date: Sun, 17 Oct 2010 20:43:40 +0200 Subject: [PATCH 29/39] migration: don't segfault on invalid input host_from_stream_offset returns NULL on error, return error instead of trying to use that address, to avoid segfault on invalid stream. Signed-off-by: Michael S. Tsirkin --- arch_init.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch_init.c b/arch_init.c index a910033c8c..4caadd0c0a 100644 --- a/arch_init.c +++ b/arch_init.c @@ -390,6 +390,9 @@ int ram_load(QEMUFile *f, void *opaque, int version_id) host = qemu_get_ram_ptr(addr); else host = host_from_stream_offset(f, addr, flags); + if (!host) { + return -EINVAL; + } ch = qemu_get_byte(f); memset(host, ch, TARGET_PAGE_SIZE); From 258dc7c96bb4b7ca71d5bee811e73933310e168c Mon Sep 17 00:00:00 2001 From: "Michael S. Tsirkin" Date: Sun, 17 Oct 2010 20:23:48 +0200 Subject: [PATCH 30/39] virtio: sanity-check available index Checking available index upon load instead of only when vm is running makes is easier to debug failures. Signed-off-by: Michael S. Tsirkin --- hw/virtio.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/hw/virtio.c b/hw/virtio.c index c8a0fc6ff0..a2a657e132 100644 --- a/hw/virtio.c +++ b/hw/virtio.c @@ -681,6 +681,7 @@ int virtio_load(VirtIODevice *vdev, QEMUFile *f) uint32_t features; uint32_t supported_features = vdev->binding->get_features(vdev->binding_opaque); + uint16_t num_heads; if (vdev->binding->load_config) { ret = vdev->binding->load_config(vdev->binding_opaque, f); @@ -713,6 +714,16 @@ int virtio_load(VirtIODevice *vdev, QEMUFile *f) if (vdev->vq[i].pa) { virtqueue_init(&vdev->vq[i]); } + num_heads = vring_avail_idx(&vdev->vq[i]) - vdev->vq[i].last_avail_idx; + /* Check it isn't doing very strange things with descriptor numbers. */ + if (num_heads > vdev->vq[i].vring.num) { + fprintf(stderr, "VQ %d size 0x%x Guest index 0x%x " + "inconsistent with Host index 0x%x: delta 0x%x\n", + i, vdev->vq[i].vring.num, + vring_avail_idx(&vdev->vq[i]), + vdev->vq[i].last_avail_idx, num_heads); + return -1; + } if (vdev->binding->load_queue) { ret = vdev->binding->load_queue(vdev->binding_opaque, i, f); if (ret) From f7c31d6381f2cbac03e82fc23133f6863606edd8 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Mon, 25 Oct 2010 13:39:59 +0800 Subject: [PATCH 31/39] net: properly handle illegal fd/vhostfd from command line When hanlding fd/vhostfd form command line through net_handle_fd_param(), we need to check mon and return value of strtol() otherwise we could get segmentation fault or invalid fd when user type an illegal fd/vhostfd. This patch is based on the suggestions from Luiz Capitulino . Signed-off-by: Jason Wang Reviewed-by: Luiz Capitulino Signed-off-by: Michael S. Tsirkin --- net.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/net.c b/net.c index ed74c7f103..c5e6063fcf 100644 --- a/net.c +++ b/net.c @@ -774,19 +774,25 @@ int qemu_find_nic_model(NICInfo *nd, const char * const *models, int net_handle_fd_param(Monitor *mon, const char *param) { - if (!qemu_isdigit(param[0])) { - int fd; + int fd; + + if (!qemu_isdigit(param[0]) && mon) { fd = monitor_get_fd(mon, param); if (fd == -1) { error_report("No file descriptor named %s found", param); return -1; } - - return fd; } else { - return strtol(param, NULL, 0); + char *endptr = NULL; + + fd = strtol(param, &endptr, 10); + if (*endptr || (fd == 0 && param == endptr)) { + return -1; + } } + + return fd; } static int net_init_nic(QemuOpts *opts, From df2943ba3c73ca21dbda063f15fa3e80064af864 Mon Sep 17 00:00:00 2001 From: "Michael S. Tsirkin" Date: Tue, 26 Oct 2010 17:53:41 +0200 Subject: [PATCH 32/39] qemu-options.def: add to generated header list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All files include qemu-options.h which pulls in qemu-options.def from the root directory. Thus generating qemu-options.def from Makefile.objs under the target directory is not effective. Further, people expect .def file to get cleaned with make clean: it does not have state so no reason to defer removing it until distclean. Also add a rule to remove old files that might be around. This fixes the error: ‘QEMU_OPTION_spice’ undeclared (first use in this function) error that some people reported which is really down to an out of date .def file. Signed-off-by: Michael S. Tsirkin --- Makefile | 7 +++++-- Makefile.objs | 7 ------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index a1434b1bd9..cf8f48a476 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # Makefile for QEMU. -GENERATED_HEADERS = config-host.h trace.h +GENERATED_HEADERS = config-host.h trace.h qemu-options.def ifneq ($(wildcard config-host.mak),) # Put the all: rule here so that config-host.mak can contain dependencies. @@ -71,6 +71,8 @@ build-all: $(DOCS) $(TOOLS) recurse-all config-host.h: config-host.h-timestamp config-host.h-timestamp: config-host.mak +qemu-options.def: qemu-options.hx + $(call quiet-command,sh $(SRC_PATH)/hxtool -h < $< > $@," GEN $@") SUBDIR_RULES=$(patsubst %,subdir-%, $(TARGET_DIRS)) @@ -150,6 +152,7 @@ check-qjson: check-qjson.o qfloat.o qint.o qdict.o qstring.o qlist.o qbool.o qjs clean: # avoid old build problems by removing potentially incorrect old files rm -f config.mak op-i386.h opc-i386.h gen-op-i386.h op-arm.h opc-arm.h gen-op-arm.h + rm -f qemu-options.def rm -f *.o *.d *.a $(TOOLS) TAGS cscope.* *.pod *~ */*~ rm -f slirp/*.o slirp/*.d audio/*.o audio/*.d block/*.o block/*.d net/*.o net/*.d fsdev/*.o fsdev/*.d ui/*.o ui/*.d rm -f qemu-img-cmds.h @@ -157,11 +160,11 @@ clean: $(MAKE) -C tests clean for d in $(ALL_SUBDIRS) libhw32 libhw64 libuser libdis libdis-user; do \ if test -d $$d; then $(MAKE) -C $$d $@ || exit 1; fi; \ + rm -f $$d/qemu-options.def; \ done distclean: clean rm -f config-host.mak config-host.h* config-host.ld $(DOCS) qemu-options.texi qemu-img-cmds.texi qemu-monitor.texi - rm -f qemu-options.def rm -f config-all-devices.mak rm -f roms/seabios/config.mak roms/vgabios/config.mak rm -f qemu-doc.info qemu-doc.aux qemu-doc.cp qemu-doc.dvi qemu-doc.fn qemu-doc.info qemu-doc.ky qemu-doc.log qemu-doc.pdf qemu-doc.pg qemu-doc.toc qemu-doc.tp qemu-doc.vr diff --git a/Makefile.objs b/Makefile.objs index f07fb01bc3..231219c050 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -285,10 +285,3 @@ vl.o: QEMU_CFLAGS+=$(GPROF_CFLAGS) vl.o: QEMU_CFLAGS+=$(SDL_CFLAGS) -vl.o: qemu-options.def -os-posix.o: qemu-options.def -os-win32.o: qemu-options.def - -qemu-options.def: $(SRC_PATH)/qemu-options.hx - $(call quiet-command,sh $(SRC_PATH)/hxtool -h < $< > $@," GEN $(TARGET_DIR)$@") - From 5afb9869171c15c777eee09bc98624221009555f Mon Sep 17 00:00:00 2001 From: Blue Swirl Date: Sat, 18 Sep 2010 05:53:14 +0000 Subject: [PATCH 33/39] Introduce range.h Extract range functions from pci.h. These will be used by later patches by non-PCI devices. Adjust current users. Signed-off-by: Blue Swirl (cherry picked from commit bf1b00712375bea65f2254dea8281fa646eebbd5) --- hw/msi.c | 1 + hw/pci_bridge.c | 1 + hw/pcie.c | 1 + 3 files changed, 3 insertions(+) diff --git a/hw/msi.c b/hw/msi.c index a949d821fd..0f2913aaa2 100644 --- a/hw/msi.c +++ b/hw/msi.c @@ -19,6 +19,7 @@ */ #include "msi.h" +#include "range.h" /* Eventually those constants should go to Linux pci_regs.h */ #define PCI_MSI_PENDING_32 0x10 diff --git a/hw/pci_bridge.c b/hw/pci_bridge.c index 7e8488a4fb..58cc2e4cee 100644 --- a/hw/pci_bridge.c +++ b/hw/pci_bridge.c @@ -31,6 +31,7 @@ #include "pci_bridge.h" #include "pci_internals.h" +#include "range.h" /* PCI bridge subsystem vendor ID helper functions */ #define PCI_SSVID_SIZEOF 8 diff --git a/hw/pcie.c b/hw/pcie.c index 53d1fce7c7..881af7878c 100644 --- a/hw/pcie.c +++ b/hw/pcie.c @@ -25,6 +25,7 @@ #include "msi.h" #include "pci_internals.h" #include "pcie_regs.h" +#include "range.h" //#define DEBUG_PCIE #ifdef DEBUG_PCIE From ac0cdda347abee6c1aa8a08a7441fc52c6d7badc Mon Sep 17 00:00:00 2001 From: "Michael S. Tsirkin" Date: Mon, 25 Oct 2010 07:03:24 +0200 Subject: [PATCH 34/39] pcie: simplify range check Simplify code slighly by reversing the polarity for the range check Signed-off-by: Michael S. Tsirkin Signed-off-by: Isaku Yamahata --- hw/pcie.c | 112 +++++++++++++++++++++++++++++------------------------- 1 file changed, 61 insertions(+), 51 deletions(-) diff --git a/hw/pcie.c b/hw/pcie.c index 881af7878c..bfccf5ec78 100644 --- a/hw/pcie.c +++ b/hw/pcie.c @@ -19,6 +19,7 @@ */ #include "sysemu.h" +#include "range.h" #include "pci_bridge.h" #include "pcie.h" #include "msix.h" @@ -296,6 +297,10 @@ void pcie_cap_slot_write_config(PCIDevice *dev, uint16_t sltctl = pci_get_word(exp_cap + PCI_EXP_SLTCTL); uint16_t sltsta = pci_get_word(exp_cap + PCI_EXP_SLTSTA); + if (!ranges_overlap(addr, len, pos + PCI_EXP_SLTCTL, 2)) { + return; + } + PCIE_DEV_PRINTF(dev, "addr: 0x%"PRIx32" val: 0x%"PRIx32" len: %d\n" "\tsltctl_prev: 0x%02"PRIx16" sltctl: 0x%02"PRIx16 @@ -303,59 +308,64 @@ void pcie_cap_slot_write_config(PCIDevice *dev, addr, val, len, sltctl_prev, sltctl, sltsta); /* SLTCTL */ - if (ranges_overlap(addr, len, pos + PCI_EXP_SLTCTL, 2)) { - PCIE_DEV_PRINTF(dev, "sltctl: 0x%02"PRIx16" -> 0x%02"PRIx16"\n", - sltctl_prev, sltctl); - if (pci_word_test_and_clear_mask(exp_cap + PCI_EXP_SLTCTL, - PCI_EXP_SLTCTL_EIC)) { - sltsta ^= PCI_EXP_SLTSTA_EIS; /* toggle PCI_EXP_SLTSTA_EIS bit */ - pci_set_word(exp_cap + PCI_EXP_SLTSTA, sltsta); - PCIE_DEV_PRINTF(dev, "PCI_EXP_SLTCTL_EIC: " - "sltsta -> 0x%02"PRIx16"\n", - sltsta); - } + PCIE_DEV_PRINTF(dev, "sltctl: 0x%02"PRIx16" -> 0x%02"PRIx16"\n", + sltctl_prev, sltctl); - /* - * The events control bits might be enabled or disabled, - * Check if the software notificastion condition is satisfied - * or disatisfied. - * - * 6.7.3.4 Software Notification of Hot-plug events - */ - if (pci_msi_enabled(dev)) { - bool msi_trigger = - (sltctl & PCI_EXP_SLTCTL_HPIE) && - ((sltctl_prev ^ sltctl) & sltctl & /* stlctl: 0 -> 1 */ - sltsta & PCI_EXP_HP_EV_SUPPORTED); - if (msi_trigger) { - pci_msi_notify(dev, pcie_cap_flags_get_vector(dev)); - } - } else { - int int_level = - (sltctl & PCI_EXP_SLTCTL_HPIE) && - (sltctl & sltsta & PCI_EXP_HP_EV_SUPPORTED); - qemu_set_irq(dev->irq[dev->exp.hpev_intx], int_level); - } - - if (!((sltctl_prev ^ sltctl) & PCI_EXP_SLTCTL_SUPPORTED)) { - PCIE_DEV_PRINTF(dev, - "sprious command completion slctl " - "0x%"PRIx16" -> 0x%"PRIx16"\n", - sltctl_prev, sltctl); - } - - /* command completion. - * Real hardware might take a while to complete - * requested command because physical movement would be involved - * like locking the electromechanical lock. - * However in our case, command is completed instantaneously above, - * so send a command completion event right now. - * - * 6.7.3.2 Command Completed Events - */ - /* set command completed bit */ - pcie_cap_slot_event(dev, PCI_EXP_HP_EV_CCI); + if (pci_word_test_and_clear_mask(exp_cap + PCI_EXP_SLTCTL, + PCI_EXP_SLTCTL_EIC)) { + sltsta ^= PCI_EXP_SLTSTA_EIS; /* toggle PCI_EXP_SLTSTA_EIS bit */ + pci_set_word(exp_cap + PCI_EXP_SLTSTA, sltsta); + PCIE_DEV_PRINTF(dev, "PCI_EXP_SLTCTL_EIC: " + "sltsta -> 0x%02"PRIx16"\n", + sltsta); } + + /* + * The events control bits might be enabled or disabled, + * Check if the software notificastion condition is satisfied + * or disatisfied. + * + * 6.7.3.4 Software Notification of Hot-plug events + */ + if (pci_msi_enabled(dev)) { + bool msi_trigger = + (sltctl & PCI_EXP_SLTCTL_HPIE) && + ((sltctl_prev ^ sltctl) & sltctl & /* stlctl: 0 -> 1 */ + sltsta & PCI_EXP_HP_EV_SUPPORTED); + if (msi_trigger) { + pci_msi_notify(dev, pcie_cap_flags_get_vector(dev)); + } + } else { + int int_level = + (sltctl & PCI_EXP_SLTCTL_HPIE) && + (sltctl & sltsta & PCI_EXP_HP_EV_SUPPORTED); + qemu_set_irq(dev->irq[dev->exp.hpev_intx], int_level); + } + + if (!((sltctl_prev ^ sltctl) & PCI_EXP_SLTCTL_SUPPORTED)) { + PCIE_DEV_PRINTF(dev, + "sprious command completion slctl " + "0x%"PRIx16" -> 0x%"PRIx16"\n", + sltctl_prev, sltctl); + } + + /* + * 6.7.3.2 Command Completed Events + * + * Software issues a command to a hot-plug capable Downstream Port by + * issuing a write transaction that targets any portion of the Port’s Slot + * Control register. A single write to the Slot Control register is + * considered to be a single command, even if the write affects more than + * one field in the Slot Control register. In response to this transaction, + * the Port must carry out the requested actions and then set the + * associated status field for the command completed event. */ + + /* Real hardware might take a while to complete requested command because + * physical movement would be involved like locking the electromechanical + * lock. However in our case, command is completed instantaneously above, + * so send a command completion event right now. + */ + pcie_cap_slot_event(dev, PCI_EXP_HP_EV_CCI); } void pcie_cap_slot_push_attention_button(PCIDevice *dev) From 6bde6aaac6f2af14557ef65f5eb053cb135ca173 Mon Sep 17 00:00:00 2001 From: "Michael S. Tsirkin" Date: Mon, 25 Oct 2010 07:46:47 +0200 Subject: [PATCH 35/39] pcie: clean up hot plug notification Simplify logic for hotplug notification, by tracking state of the logical interrupt condition. We then simply use this variable to make the interrupt decision, according to spec. API is made cleaner as we no longer force users to pass in old slot control value. Includes fixes by Isaku Yamahata. Signed-off-by: Michael S. Tsirkin Signed-off-by: Isaku Yamahata --- hw/ioh3420.c | 6 +-- hw/pcie.c | 112 ++++++++++++++++++---------------------- hw/pcie.h | 9 +++- hw/xio3130_downstream.c | 6 +-- 4 files changed, 62 insertions(+), 71 deletions(-) diff --git a/hw/ioh3420.c b/hw/ioh3420.c index 1f340d3223..3cc129f50b 100644 --- a/hw/ioh3420.c +++ b/hw/ioh3420.c @@ -39,12 +39,9 @@ static void ioh3420_write_config(PCIDevice *d, uint32_t address, uint32_t val, int len) { - uint16_t sltctl = - pci_get_word(d->config + d->exp.exp_cap + PCI_EXP_SLTCTL); - pci_bridge_write_config(d, address, val, len); msi_write_config(d, address, val, len); - pcie_cap_slot_write_config(d, address, val, len, sltctl); + pcie_cap_slot_write_config(d, address, val, len); /* TODO: AER */ } @@ -142,6 +139,7 @@ static const VMStateDescription vmstate_ioh3420 = { .version_id = 1, .minimum_version_id = 1, .minimum_version_id_old = 1, + .post_load = pcie_cap_slot_post_load, .fields = (VMStateField[]) { VMSTATE_PCIE_DEVICE(port.br.dev, PCIESlot), /* TODO: AER */ diff --git a/hw/pcie.c b/hw/pcie.c index bfccf5ec78..373e33e741 100644 --- a/hw/pcie.c +++ b/hw/pcie.c @@ -140,6 +140,40 @@ void pcie_cap_deverr_reset(PCIDevice *dev) PCI_EXP_DEVCTL_FERE | PCI_EXP_DEVCTL_URRE); } +static void hotplug_event_update_event_status(PCIDevice *dev) +{ + uint32_t pos = dev->exp.exp_cap; + uint8_t *exp_cap = dev->config + pos; + uint16_t sltctl = pci_get_word(exp_cap + PCI_EXP_SLTCTL); + uint16_t sltsta = pci_get_word(exp_cap + PCI_EXP_SLTSTA); + + dev->exp.hpev_notified = (sltctl & PCI_EXP_SLTCTL_HPIE) && + (sltsta & sltctl & PCI_EXP_HP_EV_SUPPORTED); +} + +static void hotplug_event_notify(PCIDevice *dev) +{ + bool prev = dev->exp.hpev_notified; + + hotplug_event_update_event_status(dev); + + if (prev == dev->exp.hpev_notified) { + return; + } + + /* Note: the logic above does not take into account whether interrupts + * are masked. The result is that interrupt will be sent when it is + * subsequently unmasked. This appears to be legal: Section 6.7.3.4: + * The Port may optionally send an MSI when there are hot-plug events that + * occur while interrupt generation is disabled, and interrupt generation is + * subsequently enabled. */ + if (!pci_msi_enabled(dev)) { + qemu_set_irq(dev->irq[dev->exp.hpev_intx], dev->exp.hpev_notified); + } else if (dev->exp.hpev_notified) { + pci_msi_notify(dev, pcie_cap_flags_get_vector(dev)); + } +} + /* * A PCI Express Hot-Plug Event has occured, so update slot status register * and notify OS of the event if necessary. @@ -149,28 +183,12 @@ void pcie_cap_deverr_reset(PCIDevice *dev) */ static void pcie_cap_slot_event(PCIDevice *dev, PCIExpressHotPlugEvent event) { - uint8_t *exp_cap = dev->config + dev->exp.exp_cap; - uint16_t sltctl = pci_get_word(exp_cap + PCI_EXP_SLTCTL); - uint16_t sltsta = pci_get_word(exp_cap + PCI_EXP_SLTSTA); - - PCIE_DEV_PRINTF(dev, - "sltctl: 0x%02"PRIx16" sltsta: 0x%02"PRIx16" event: %x\n", - sltctl, sltsta, event); - - if (pci_word_test_and_set_mask(exp_cap + PCI_EXP_SLTSTA, event)) { + /* Minor optimization: if nothing changed - no event is needed. */ + if (pci_word_test_and_set_mask(dev->config + dev->exp.exp_cap + + PCI_EXP_SLTSTA, event)) { return; } - sltsta = pci_get_word(exp_cap + PCI_EXP_SLTSTA); - PCIE_DEV_PRINTF(dev, "sltsta -> %02"PRIx16"\n", sltsta); - - if ((sltctl & PCI_EXP_SLTCTL_HPIE) && - (sltctl & event & PCI_EXP_HP_EV_SUPPORTED)) { - if (pci_msi_enabled(dev)) { - pci_msi_notify(dev, pcie_cap_flags_get_vector(dev)); - } else { - qemu_set_irq(dev->irq[dev->exp.hpev_intx], 1); - } - } + hotplug_event_notify(dev); } static int pcie_cap_slot_hotplug(DeviceState *qdev, @@ -258,6 +276,8 @@ void pcie_cap_slot_init(PCIDevice *dev, uint16_t slot) pci_word_test_and_set_mask(dev->w1cmask + pos + PCI_EXP_SLTSTA, PCI_EXP_HP_EV_SUPPORTED); + dev->exp.hpev_notified = false; + pci_bus_hotplug(pci_bridge_get_sec_bus(DO_UPCAST(PCIBridge, dev, dev)), pcie_cap_slot_hotplug, &dev->qdev); } @@ -286,31 +306,21 @@ void pcie_cap_slot_reset(PCIDevice *dev) PCI_EXP_SLTSTA_CC | PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_ABP); + + hotplug_event_notify(dev); } void pcie_cap_slot_write_config(PCIDevice *dev, - uint32_t addr, uint32_t val, int len, - uint16_t sltctl_prev) + uint32_t addr, uint32_t val, int len) { uint32_t pos = dev->exp.exp_cap; uint8_t *exp_cap = dev->config + pos; - uint16_t sltctl = pci_get_word(exp_cap + PCI_EXP_SLTCTL); uint16_t sltsta = pci_get_word(exp_cap + PCI_EXP_SLTSTA); if (!ranges_overlap(addr, len, pos + PCI_EXP_SLTCTL, 2)) { return; } - PCIE_DEV_PRINTF(dev, - "addr: 0x%"PRIx32" val: 0x%"PRIx32" len: %d\n" - "\tsltctl_prev: 0x%02"PRIx16" sltctl: 0x%02"PRIx16 - " sltsta: 0x%02"PRIx16"\n", - addr, val, len, sltctl_prev, sltctl, sltsta); - - /* SLTCTL */ - PCIE_DEV_PRINTF(dev, "sltctl: 0x%02"PRIx16" -> 0x%02"PRIx16"\n", - sltctl_prev, sltctl); - if (pci_word_test_and_clear_mask(exp_cap + PCI_EXP_SLTCTL, PCI_EXP_SLTCTL_EIC)) { sltsta ^= PCI_EXP_SLTSTA_EIS; /* toggle PCI_EXP_SLTSTA_EIS bit */ @@ -320,34 +330,7 @@ void pcie_cap_slot_write_config(PCIDevice *dev, sltsta); } - /* - * The events control bits might be enabled or disabled, - * Check if the software notificastion condition is satisfied - * or disatisfied. - * - * 6.7.3.4 Software Notification of Hot-plug events - */ - if (pci_msi_enabled(dev)) { - bool msi_trigger = - (sltctl & PCI_EXP_SLTCTL_HPIE) && - ((sltctl_prev ^ sltctl) & sltctl & /* stlctl: 0 -> 1 */ - sltsta & PCI_EXP_HP_EV_SUPPORTED); - if (msi_trigger) { - pci_msi_notify(dev, pcie_cap_flags_get_vector(dev)); - } - } else { - int int_level = - (sltctl & PCI_EXP_SLTCTL_HPIE) && - (sltctl & sltsta & PCI_EXP_HP_EV_SUPPORTED); - qemu_set_irq(dev->irq[dev->exp.hpev_intx], int_level); - } - - if (!((sltctl_prev ^ sltctl) & PCI_EXP_SLTCTL_SUPPORTED)) { - PCIE_DEV_PRINTF(dev, - "sprious command completion slctl " - "0x%"PRIx16" -> 0x%"PRIx16"\n", - sltctl_prev, sltctl); - } + hotplug_event_notify(dev); /* * 6.7.3.2 Command Completed Events @@ -368,6 +351,13 @@ void pcie_cap_slot_write_config(PCIDevice *dev, pcie_cap_slot_event(dev, PCI_EXP_HP_EV_CCI); } +int pcie_cap_slot_post_load(void *opaque, int version_id) +{ + PCIDevice *dev = opaque; + hotplug_event_update_event_status(dev); + return 0; +} + void pcie_cap_slot_push_attention_button(PCIDevice *dev) { pcie_cap_slot_event(dev, PCI_EXP_HP_EV_ABP); diff --git a/hw/pcie.h b/hw/pcie.h index 2871e27012..87085041f2 100644 --- a/hw/pcie.h +++ b/hw/pcie.h @@ -74,6 +74,11 @@ struct PCIExpressDevice { * also initialize it when loaded as * appropreately. */ + bool hpev_notified; /* Logical AND of conditions for hot plug event. + Following 6.7.3.4: + Software Notification of Hot-Plug Events, an interrupt + is sent whenever the logical and of these conditions + transitions from false to true. */ }; /* PCI express capability helper functions */ @@ -89,8 +94,8 @@ void pcie_cap_deverr_reset(PCIDevice *dev); void pcie_cap_slot_init(PCIDevice *dev, uint16_t slot); void pcie_cap_slot_reset(PCIDevice *dev); void pcie_cap_slot_write_config(PCIDevice *dev, - uint32_t addr, uint32_t val, int len, - uint16_t sltctl_prev); + uint32_t addr, uint32_t val, int len); +int pcie_cap_slot_post_load(void *opaque, int version_id); void pcie_cap_slot_push_attention_button(PCIDevice *dev); void pcie_cap_root_init(PCIDevice *dev); diff --git a/hw/xio3130_downstream.c b/hw/xio3130_downstream.c index a44e188190..854eba8931 100644 --- a/hw/xio3130_downstream.c +++ b/hw/xio3130_downstream.c @@ -38,12 +38,9 @@ static void xio3130_downstream_write_config(PCIDevice *d, uint32_t address, uint32_t val, int len) { - uint16_t sltctl = - pci_get_word(d->config + d->exp.exp_cap + PCI_EXP_SLTCTL); - pci_bridge_write_config(d, address, val, len); pcie_cap_flr_write_config(d, address, val, len); - pcie_cap_slot_write_config(d, address, val, len, sltctl); + pcie_cap_slot_write_config(d, address, val, len); msi_write_config(d, address, val, len); /* TODO: AER */ } @@ -144,6 +141,7 @@ static const VMStateDescription vmstate_xio3130_downstream = { .version_id = 1, .minimum_version_id = 1, .minimum_version_id_old = 1, + .post_load = pcie_cap_slot_post_load, .fields = (VMStateField[]) { VMSTATE_PCIE_DEVICE(port.br.dev, PCIESlot), /* TODO: AER */ From f9aebe2ef52ff0dcb733999f57e00a7b430303c6 Mon Sep 17 00:00:00 2001 From: "Michael S. Tsirkin" Date: Wed, 27 Oct 2010 16:01:25 +0200 Subject: [PATCH 36/39] pci: improve w1c mask handling - save/restore must not check w1c bits since they are in fact guest controlled - clear w1c bits on reset Note: for express there are different kinds of reset, some leave part of config space alone. We will likely need a sticky bit mask to implement this. Signed-off-by: Michael S. Tsirkin --- hw/pci.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/hw/pci.c b/hw/pci.c index 409e2c079b..5386f5a083 100644 --- a/hw/pci.c +++ b/hw/pci.c @@ -140,7 +140,8 @@ static void pci_device_reset(PCIDevice *dev) pci_update_irq_status(dev); /* Clear all writeable bits */ pci_word_test_and_clear_mask(dev->config + PCI_COMMAND, - pci_get_word(dev->wmask + PCI_COMMAND)); + pci_get_word(dev->wmask + PCI_COMMAND) | + pci_get_word(dev->w1cmask + PCI_COMMAND)); dev->config[PCI_CACHE_LINE_SIZE] = 0x0; dev->config[PCI_INTERRUPT_LINE] = 0x0; for (r = 0; r < PCI_NUM_REGIONS; ++r) { @@ -292,7 +293,8 @@ static int get_pci_config_device(QEMUFile *f, void *pv, size_t size) qemu_get_buffer(f, config, size); for (i = 0; i < size; ++i) { - if ((config[i] ^ s->config[i]) & s->cmask[i] & ~s->wmask[i]) { + if ((config[i] ^ s->config[i]) & + s->cmask[i] & ~s->wmask[i] & ~s->w1cmask[i]) { qemu_free(config); return -EINVAL; } From 531a0b82dd0ad352819d4deffe1ecd7f52975fbf Mon Sep 17 00:00:00 2001 From: "Michael S. Tsirkin" Date: Wed, 27 Oct 2010 16:14:56 +0200 Subject: [PATCH 37/39] msi: simplify range checks config write handlers should be idempotent. So no need for complex range checks: a simple one checking that we are touching the relevant capability will do. Signed-off-by: Michael S. Tsirkin --- hw/msi.c | 47 +++++++++++++++++++++-------------------------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/hw/msi.c b/hw/msi.c index 0f2913aaa2..016e7a478f 100644 --- a/hw/msi.c +++ b/hw/msi.c @@ -258,35 +258,30 @@ void msi_write_config(PCIDevice *dev, uint32_t addr, uint32_t val, int len) uint32_t pending; int i; -#ifdef MSI_DEBUG - if (ranges_overlap(addr, len, dev->msi_cap, msi_cap_sizeof(flags))) { - MSI_DEV_PRINTF(dev, "addr 0x%"PRIx32" val 0x%"PRIx32" len %d\n", - addr, val, len); - MSI_DEV_PRINTF(dev, "ctrl: 0x%"PRIx16" address: 0x%"PRIx32, - flags, - pci_get_long(dev->config + msi_address_lo_off(dev))); - if (msi64bit) { - fprintf(stderr, " addrss-hi: 0x%"PRIx32, - pci_get_long(dev->config + msi_address_hi_off(dev))); - } - fprintf(stderr, " data: 0x%"PRIx16, - pci_get_word(dev->config + msi_data_off(dev, msi64bit))); - if (flags & PCI_MSI_FLAGS_MASKBIT) { - fprintf(stderr, " mask 0x%"PRIx32" pending 0x%"PRIx32, - pci_get_long(dev->config + msi_mask_off(dev, msi64bit)), - pci_get_long(dev->config + msi_pending_off(dev, msi64bit))); - } - fprintf(stderr, "\n"); - } -#endif - - /* Are we modified? */ - if (!(ranges_overlap(addr, len, msi_flags_off(dev), 2) || - (msi_per_vector_mask && - ranges_overlap(addr, len, msi_mask_off(dev, msi64bit), 4)))) { + if (!ranges_overlap(addr, len, dev->msi_cap, msi_cap_sizeof(flags))) { return; } +#ifdef MSI_DEBUG + MSI_DEV_PRINTF(dev, "addr 0x%"PRIx32" val 0x%"PRIx32" len %d\n", + addr, val, len); + MSI_DEV_PRINTF(dev, "ctrl: 0x%"PRIx16" address: 0x%"PRIx32, + flags, + pci_get_long(dev->config + msi_address_lo_off(dev))); + if (msi64bit) { + fprintf(stderr, " addrss-hi: 0x%"PRIx32, + pci_get_long(dev->config + msi_address_hi_off(dev))); + } + fprintf(stderr, " data: 0x%"PRIx16, + pci_get_word(dev->config + msi_data_off(dev, msi64bit))); + if (flags & PCI_MSI_FLAGS_MASKBIT) { + fprintf(stderr, " mask 0x%"PRIx32" pending 0x%"PRIx32, + pci_get_long(dev->config + msi_mask_off(dev, msi64bit)), + pci_get_long(dev->config + msi_pending_off(dev, msi64bit))); + } + fprintf(stderr, "\n"); +#endif + if (!(flags & PCI_MSI_FLAGS_ENABLE)) { return; } From b794ec7ce8ac1aaac825e554c20d1aae1422374e Mon Sep 17 00:00:00 2001 From: "Michael S. Tsirkin" Date: Wed, 27 Oct 2010 16:28:22 +0200 Subject: [PATCH 38/39] msi: minor cleanups Comment fixup (tell what it does not what it does not do), typo fix, whitespace fix. Signed-off-by: Michael S. Tsirkin --- hw/msi.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/hw/msi.c b/hw/msi.c index 016e7a478f..f03f519a2e 100644 --- a/hw/msi.c +++ b/hw/msi.c @@ -155,9 +155,8 @@ int msi_init(struct PCIDevice *dev, uint8_t offset, pci_set_word(dev->wmask + msi_data_off(dev, msi64bit), 0xffff); if (msi_per_vector_mask) { + /* Make mask bits 0 to nr_vectors - 1 writeable. */ pci_set_long(dev->wmask + msi_mask_off(dev, msi64bit), - /* (1U << nr_vectors) - 1 is undefined - when nr_vectors = 32 */ 0xffffffff >> (PCI_MSI_VECTORS_MAX - nr_vectors)); } return config_offset; @@ -225,7 +224,7 @@ void msi_notify(PCIDevice *dev, unsigned int vector) return; } - if (msi64bit){ + if (msi64bit) { address = pci_get_quad(dev->config + msi_address_lo_off(dev)); } else { address = pci_get_long(dev->config + msi_address_lo_off(dev)); @@ -269,7 +268,7 @@ void msi_write_config(PCIDevice *dev, uint32_t addr, uint32_t val, int len) flags, pci_get_long(dev->config + msi_address_lo_off(dev))); if (msi64bit) { - fprintf(stderr, " addrss-hi: 0x%"PRIx32, + fprintf(stderr, " address-hi: 0x%"PRIx32, pci_get_long(dev->config + msi_address_hi_off(dev))); } fprintf(stderr, " data: 0x%"PRIx16, From 804b207170cdccca3672b63caaf82312ad205a7f Mon Sep 17 00:00:00 2001 From: "Michael S. Tsirkin" Date: Wed, 27 Oct 2010 17:48:42 +0200 Subject: [PATCH 39/39] pcie: update satus on reset Reset never triggers a new event, so it's enough to update status. Signed-off-by: Michael S. Tsirkin --- hw/pcie.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hw/pcie.c b/hw/pcie.c index 373e33e741..35918f7c2c 100644 --- a/hw/pcie.c +++ b/hw/pcie.c @@ -307,7 +307,7 @@ void pcie_cap_slot_reset(PCIDevice *dev) PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_ABP); - hotplug_event_notify(dev); + hotplug_event_update_event_status(dev); } void pcie_cap_slot_write_config(PCIDevice *dev,