diff options
Diffstat (limited to 'hw/s390x')
-rw-r--r-- | hw/s390x/s390-pci-bus.c | 244 | ||||
-rw-r--r-- | hw/s390x/s390-pci-bus.h | 4 |
2 files changed, 163 insertions, 85 deletions
diff --git a/hw/s390x/s390-pci-bus.c b/hw/s390x/s390-pci-bus.c index f017c1d..80ff1ce 100644 --- a/hw/s390x/s390-pci-bus.c +++ b/hw/s390x/s390-pci-bus.c @@ -148,6 +148,22 @@ out: psccb->header.response_code = cpu_to_be16(rc); } +static void s390_pci_perform_unplug(S390PCIBusDevice *pbdev) +{ + HotplugHandler *hotplug_ctrl; + + /* Unplug the PCI device */ + if (pbdev->pdev) { + hotplug_ctrl = qdev_get_hotplug_handler(DEVICE(pbdev->pdev)); + hotplug_handler_unplug(hotplug_ctrl, DEVICE(pbdev->pdev), + &error_abort); + } + + /* Unplug the zPCI device */ + hotplug_ctrl = qdev_get_hotplug_handler(DEVICE(pbdev)); + hotplug_handler_unplug(hotplug_ctrl, DEVICE(pbdev), &error_abort); +} + void s390_pci_sclp_deconfigure(SCCB *sccb) { IoaCfgSccb *psccb = (IoaCfgSccb *)sccb; @@ -178,8 +194,8 @@ void s390_pci_sclp_deconfigure(SCCB *sccb) pbdev->state = ZPCI_FS_STANDBY; rc = SCLP_RC_NORMAL_COMPLETION; - if (pbdev->release_timer) { - qdev_unplug(DEVICE(pbdev->pdev), NULL); + if (pbdev->unplug_requested) { + s390_pci_perform_unplug(pbdev); } } out: @@ -217,6 +233,24 @@ S390PCIBusDevice *s390_pci_find_dev_by_target(S390pciState *s, return NULL; } +static S390PCIBusDevice *s390_pci_find_dev_by_pci(S390pciState *s, + PCIDevice *pci_dev) +{ + S390PCIBusDevice *pbdev; + + if (!pci_dev) { + return NULL; + } + + QTAILQ_FOREACH(pbdev, &s->zpci_devs, link) { + if (pbdev->pdev == pci_dev) { + return pbdev; + } + } + + return NULL; +} + S390PCIBusDevice *s390_pci_find_dev_by_idx(S390pciState *s, uint32_t idx) { return g_hash_table_lookup(s->zpci_table, &idx); @@ -826,6 +860,12 @@ static void s390_pcihost_pre_plug(HotplugHandler *hotplug_dev, DeviceState *dev, { S390pciState *s = S390_PCI_HOST_BRIDGE(hotplug_dev); + if (!s390_has_feat(S390_FEAT_ZPCI)) { + warn_report("Plugging a PCI/zPCI device without the 'zpci' CPU " + "feature enabled; the guest will not be able to see/use " + "this device"); + } + if (object_dynamic_cast(OBJECT(dev), TYPE_PCI_DEVICE)) { PCIDevice *pdev = PCI_DEVICE(dev); @@ -843,6 +883,21 @@ static void s390_pcihost_pre_plug(HotplugHandler *hotplug_dev, DeviceState *dev, } } +static void s390_pci_update_subordinate(PCIDevice *dev, uint32_t nr) +{ + uint32_t old_nr; + + pci_default_write_config(dev, PCI_SUBORDINATE_BUS, nr, 1); + while (!pci_bus_is_root(pci_get_bus(dev))) { + dev = pci_get_bus(dev)->parent_dev; + + old_nr = pci_default_read_config(dev, PCI_SUBORDINATE_BUS, 1); + if (old_nr < nr) { + pci_default_write_config(dev, PCI_SUBORDINATE_BUS, nr, 1); + } + } +} + static void s390_pcihost_plug(HotplugHandler *hotplug_dev, DeviceState *dev, Error **errp) { @@ -851,25 +906,21 @@ static void s390_pcihost_plug(HotplugHandler *hotplug_dev, DeviceState *dev, S390PCIBusDevice *pbdev = NULL; if (object_dynamic_cast(OBJECT(dev), TYPE_PCI_BRIDGE)) { - BusState *bus; PCIBridge *pb = PCI_BRIDGE(dev); - PCIDevice *pdev = PCI_DEVICE(dev); + pdev = PCI_DEVICE(dev); pci_bridge_map_irq(pb, dev->id, s390_pci_map_irq); pci_setup_iommu(&pb->sec_bus, s390_pci_dma_iommu, s); - bus = BUS(&pb->sec_bus); - qbus_set_hotplug_handler(bus, DEVICE(s), errp); + qbus_set_hotplug_handler(BUS(&pb->sec_bus), DEVICE(s), errp); if (dev->hotplugged) { - pci_default_write_config(pdev, PCI_PRIMARY_BUS, s->bus_no, 1); + pci_default_write_config(pdev, PCI_PRIMARY_BUS, + pci_dev_bus_num(pdev), 1); s->bus_no += 1; pci_default_write_config(pdev, PCI_SECONDARY_BUS, s->bus_no, 1); - do { - pdev = pci_get_bus(pdev)->parent_dev; - pci_default_write_config(pdev, PCI_SUBORDINATE_BUS, - s->bus_no, 1); - } while (pci_get_bus(pdev) && pci_dev_bus_num(pdev)); + + s390_pci_update_subordinate(pdev, s->bus_no); } } else if (object_dynamic_cast(OBJECT(dev), TYPE_PCI_DEVICE)) { pdev = PCI_DEVICE(dev); @@ -925,99 +976,96 @@ static void s390_pcihost_plug(HotplugHandler *hotplug_dev, DeviceState *dev, } } -static void s390_pcihost_timer_cb(void *opaque) +static void s390_pcihost_unplug(HotplugHandler *hotplug_dev, DeviceState *dev, + Error **errp) { - S390PCIBusDevice *pbdev = opaque; + S390pciState *s = S390_PCI_HOST_BRIDGE(hotplug_dev); + S390PCIBusDevice *pbdev = NULL; - if (pbdev->summary_ind) { - pci_dereg_irqs(pbdev); - } - if (pbdev->iommu->enabled) { - pci_dereg_ioat(pbdev->iommu); - } + if (object_dynamic_cast(OBJECT(dev), TYPE_PCI_DEVICE)) { + PCIDevice *pci_dev = PCI_DEVICE(dev); + PCIBus *bus; + int32_t devfn; - pbdev->state = ZPCI_FS_STANDBY; - s390_pci_generate_plug_event(HP_EVENT_CONFIGURED_TO_STBRES, - pbdev->fh, pbdev->fid); - qdev_unplug(DEVICE(pbdev), NULL); + pbdev = s390_pci_find_dev_by_pci(s, PCI_DEVICE(dev)); + g_assert(pbdev); + + s390_pci_generate_plug_event(HP_EVENT_STANDBY_TO_RESERVED, + pbdev->fh, pbdev->fid); + bus = pci_get_bus(pci_dev); + devfn = pci_dev->devfn; + object_unparent(OBJECT(pci_dev)); + + s390_pci_msix_free(pbdev); + s390_pci_iommu_free(s, bus, devfn); + pbdev->pdev = NULL; + pbdev->state = ZPCI_FS_RESERVED; + } else if (object_dynamic_cast(OBJECT(dev), TYPE_S390_PCI_DEVICE)) { + pbdev = S390_PCI_DEVICE(dev); + pbdev->fid = 0; + QTAILQ_REMOVE(&s->zpci_devs, pbdev, link); + g_hash_table_remove(s->zpci_table, &pbdev->idx); + object_unparent(OBJECT(pbdev)); + } } -static void s390_pcihost_unplug(HotplugHandler *hotplug_dev, DeviceState *dev, - Error **errp) +static void s390_pcihost_unplug_request(HotplugHandler *hotplug_dev, + DeviceState *dev, + Error **errp) { S390pciState *s = S390_PCI_HOST_BRIDGE(hotplug_dev); - PCIDevice *pci_dev = NULL; - PCIBus *bus; - int32_t devfn; - S390PCIBusDevice *pbdev = NULL; + S390PCIBusDevice *pbdev; if (object_dynamic_cast(OBJECT(dev), TYPE_PCI_BRIDGE)) { error_setg(errp, "PCI bridge hot unplug currently not supported"); - return; } else if (object_dynamic_cast(OBJECT(dev), TYPE_PCI_DEVICE)) { - pci_dev = PCI_DEVICE(dev); - - QTAILQ_FOREACH(pbdev, &s->zpci_devs, link) { - if (pbdev->pdev == pci_dev) { - break; - } - } - assert(pbdev != NULL); + /* + * Redirect the unplug request to the zPCI device and remember that + * we've checked the PCI device already (to prevent endless recursion). + */ + pbdev = s390_pci_find_dev_by_pci(s, PCI_DEVICE(dev)); + g_assert(pbdev); + pbdev->pci_unplug_request_processed = true; + qdev_unplug(DEVICE(pbdev), errp); } else if (object_dynamic_cast(OBJECT(dev), TYPE_S390_PCI_DEVICE)) { pbdev = S390_PCI_DEVICE(dev); - pci_dev = pbdev->pdev; - } else { - g_assert_not_reached(); - } - switch (pbdev->state) { - case ZPCI_FS_RESERVED: - goto out; - case ZPCI_FS_STANDBY: - break; - default: - if (pbdev->release_timer) { + /* + * If unplug was initially requested for the zPCI device, we + * first have to redirect to the PCI device, which will in return + * redirect back to us after performing its checks (if the request + * is not blocked, e.g. because it's a PCI bridge). + */ + if (pbdev->pdev && !pbdev->pci_unplug_request_processed) { + qdev_unplug(DEVICE(pbdev->pdev), errp); return; } - s390_pci_generate_plug_event(HP_EVENT_DECONFIGURE_REQUEST, - pbdev->fh, pbdev->fid); - pbdev->release_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, - s390_pcihost_timer_cb, - pbdev); - timer_mod(pbdev->release_timer, - qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + HOT_UNPLUG_TIMEOUT); - return; - } + pbdev->pci_unplug_request_processed = false; - if (pbdev->release_timer) { - timer_del(pbdev->release_timer); - timer_free(pbdev->release_timer); - pbdev->release_timer = NULL; + switch (pbdev->state) { + case ZPCI_FS_STANDBY: + case ZPCI_FS_RESERVED: + s390_pci_perform_unplug(pbdev); + break; + default: + /* + * Allow to send multiple requests, e.g. if the guest crashed + * before releasing the device, we would not be able to send + * another request to the same VM (e.g. fresh OS). + */ + pbdev->unplug_requested = true; + s390_pci_generate_plug_event(HP_EVENT_DECONFIGURE_REQUEST, + pbdev->fh, pbdev->fid); + } + } else { + g_assert_not_reached(); } - - s390_pci_generate_plug_event(HP_EVENT_STANDBY_TO_RESERVED, - pbdev->fh, pbdev->fid); - bus = pci_get_bus(pci_dev); - devfn = pci_dev->devfn; - object_unparent(OBJECT(pci_dev)); - fmb_timer_free(pbdev); - s390_pci_msix_free(pbdev); - s390_pci_iommu_free(s, bus, devfn); - pbdev->pdev = NULL; - pbdev->state = ZPCI_FS_RESERVED; -out: - pbdev->fid = 0; - QTAILQ_REMOVE(&s->zpci_devs, pbdev, link); - g_hash_table_remove(s->zpci_table, &pbdev->idx); - object_unparent(OBJECT(pbdev)); } static void s390_pci_enumerate_bridge(PCIBus *bus, PCIDevice *pdev, void *opaque) { S390pciState *s = opaque; - unsigned int primary = s->bus_no; - unsigned int subordinate = 0xff; PCIBus *sec_bus = NULL; if ((pci_default_read_config(pdev, PCI_HEADER_TYPE, 1) != @@ -1026,7 +1074,7 @@ static void s390_pci_enumerate_bridge(PCIBus *bus, PCIDevice *pdev, } (s->bus_no)++; - pci_default_write_config(pdev, PCI_PRIMARY_BUS, primary, 1); + pci_default_write_config(pdev, PCI_PRIMARY_BUS, pci_dev_bus_num(pdev), 1); pci_default_write_config(pdev, PCI_SECONDARY_BUS, s->bus_no, 1); pci_default_write_config(pdev, PCI_SUBORDINATE_BUS, s->bus_no, 1); @@ -1035,7 +1083,7 @@ static void s390_pci_enumerate_bridge(PCIBus *bus, PCIDevice *pdev, return; } - pci_default_write_config(pdev, PCI_SUBORDINATE_BUS, subordinate, 1); + /* Assign numbers to all child bridges. The last is the highest number. */ pci_for_each_device(sec_bus, pci_bus_num(sec_bus), s390_pci_enumerate_bridge, s); pci_default_write_config(pdev, PCI_SUBORDINATE_BUS, s->bus_no, 1); @@ -1045,7 +1093,26 @@ static void s390_pcihost_reset(DeviceState *dev) { S390pciState *s = S390_PCI_HOST_BRIDGE(dev); PCIBus *bus = s->parent_obj.bus; + S390PCIBusDevice *pbdev, *next; + + /* Process all pending unplug requests */ + QTAILQ_FOREACH_SAFE(pbdev, &s->zpci_devs, link, next) { + if (pbdev->unplug_requested) { + if (pbdev->summary_ind) { + pci_dereg_irqs(pbdev); + } + if (pbdev->iommu->enabled) { + pci_dereg_ioat(pbdev->iommu); + } + pbdev->state = ZPCI_FS_STANDBY; + s390_pci_perform_unplug(pbdev); + } + } + /* + * When resetting a PCI bridge, the assigned numbers are set to 0. So + * on every system reset, we also have to reassign numbers. + */ s->bus_no = 0; pci_for_each_device(bus, pci_bus_num(bus), s390_pci_enumerate_bridge, s); } @@ -1059,6 +1126,7 @@ static void s390_pcihost_class_init(ObjectClass *klass, void *data) dc->realize = s390_pcihost_realize; hc->pre_plug = s390_pcihost_pre_plug; hc->plug = s390_pcihost_plug; + hc->unplug_request = s390_pcihost_unplug_request; hc->unplug = s390_pcihost_unplug; msi_nonbroken = true; } @@ -1219,6 +1287,15 @@ static Property s390_pci_device_properties[] = { DEFINE_PROP_END_OF_LIST(), }; +static const VMStateDescription s390_pci_device_vmstate = { + .name = TYPE_S390_PCI_DEVICE, + /* + * TODO: add state handling here, so migration works at least with + * emulated pci devices on s390x + */ + .unmigratable = 1, +}; + static void s390_pci_device_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); @@ -1229,6 +1306,7 @@ static void s390_pci_device_class_init(ObjectClass *klass, void *data) dc->bus_type = TYPE_S390_PCI_BUS; dc->realize = s390_pci_device_realize; dc->props = s390_pci_device_properties; + dc->vmsd = &s390_pci_device_vmstate; } static const TypeInfo s390_pci_device_info = { diff --git a/hw/s390x/s390-pci-bus.h b/hw/s390x/s390-pci-bus.h index dadad1f..550f3cc 100644 --- a/hw/s390x/s390-pci-bus.h +++ b/hw/s390x/s390-pci-bus.h @@ -35,7 +35,6 @@ #define ZPCI_MAX_UID 0xffff #define UID_UNDEFINED 0 #define UID_CHECKING_ENABLED 0x01 -#define HOT_UNPLUG_TIMEOUT (NANOSECONDS_PER_SECOND * 60 * 5) #define S390_PCI_HOST_BRIDGE(obj) \ OBJECT_CHECK(S390pciState, (obj), TYPE_S390_PCI_HOST_BRIDGE) @@ -335,7 +334,8 @@ struct S390PCIBusDevice { MemoryRegion msix_notify_mr; IndAddr *summary_ind; IndAddr *indicator; - QEMUTimer *release_timer; + bool pci_unplug_request_processed; + bool unplug_requested; QTAILQ_ENTRY(S390PCIBusDevice) link; }; |