aboutsummaryrefslogtreecommitdiff
path: root/hw/pci/pcie.c
diff options
context:
space:
mode:
authorRichard Henderson <richard.henderson@linaro.org>2021-11-15 21:56:15 +0100
committerRichard Henderson <richard.henderson@linaro.org>2021-11-15 21:56:15 +0100
commit757b8dd4e970038538b2e027120ab4594bebdebc (patch)
treeb70c7fa0d161d12a3278e7d7153dca7f0d5d14fb /hw/pci/pcie.c
parent42f6c9179be4401974dd3a75ee72defd16b5092d (diff)
parent18416c62e36a79823a9e28f6b2260aa13c25e1d9 (diff)
Merge tag 'for_upstream' of git://git.kernel.org/pub/scm/virt/kvm/mst/qemu into staging
pci,pc,virtio: bugfixes pci power management fixes acpi hotplug fixes misc other fixes Signed-off-by: Michael S. Tsirkin <mst@redhat.com> # gpg: Signature made Mon 15 Nov 2021 05:15:09 PM CET # gpg: using RSA key 5D09FD0871C8F85B94CA8A0D281F0DB8D28D5469 # gpg: issuer "mst@redhat.com" # gpg: Good signature from "Michael S. Tsirkin <mst@kernel.org>" [full] # gpg: aka "Michael S. Tsirkin <mst@redhat.com>" [full] * tag 'for_upstream' of git://git.kernel.org/pub/scm/virt/kvm/mst/qemu: pcie: expire pending delete pcie: fast unplug when slot power is off pcie: factor out pcie_cap_slot_unplug() pcie: add power indicator blink check pcie: implement slot power control for pcie root ports pci: implement power state vdpa: Check for existence of opts.vhostdev vdpa: Replace qemu_open_old by qemu_open at virtio: use virtio accessor to access packed event virtio: use virtio accessor to access packed descriptor flags tests: bios-tables-test update expected blobs hw/i386/acpi-build: Deny control on PCIe Native Hot-plug in _OSC bios-tables-test: Allow changes in DSDT ACPI tables hw/acpi/ich9: Add compat prop to keep HPC bit set for 6.1 machine type pcie: rename 'native-hotplug' to 'x-native-hotplug' hw/mem/pc-dimm: Restrict NUMA-specific code to NUMA machines vhost: Fix last vq queue index of devices with no cvq vhost: Rename last_index to vq_index_end softmmu/qdev-monitor: fix use-after-free in qdev_set_id() net/vhost-vdpa: fix memory leak in vhost_vdpa_get_max_queue_pairs() Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
Diffstat (limited to 'hw/pci/pcie.c')
-rw-r--r--hw/pci/pcie.c79
1 files changed, 67 insertions, 12 deletions
diff --git a/hw/pci/pcie.c b/hw/pci/pcie.c
index 914a9bf3d1..c5ed266337 100644
--- a/hw/pci/pcie.c
+++ b/hw/pci/pcie.c
@@ -366,6 +366,29 @@ static void hotplug_event_clear(PCIDevice *dev)
}
}
+static void pcie_set_power_device(PCIBus *bus, PCIDevice *dev, void *opaque)
+{
+ bool *power = opaque;
+
+ pci_set_power(dev, *power);
+}
+
+static void pcie_cap_update_power(PCIDevice *hotplug_dev)
+{
+ uint8_t *exp_cap = hotplug_dev->config + hotplug_dev->exp.exp_cap;
+ PCIBus *sec_bus = pci_bridge_get_sec_bus(PCI_BRIDGE(hotplug_dev));
+ uint32_t sltcap = pci_get_long(exp_cap + PCI_EXP_SLTCAP);
+ uint16_t sltctl = pci_get_word(exp_cap + PCI_EXP_SLTCTL);
+ bool power = true;
+
+ if (sltcap & PCI_EXP_SLTCAP_PCP) {
+ power = (sltctl & PCI_EXP_SLTCTL_PCC) == PCI_EXP_SLTCTL_PWR_ON;
+ }
+
+ pci_for_each_device(sec_bus, pci_bus_num(sec_bus),
+ pcie_set_power_device, &power);
+}
+
/*
* A PCI Express Hot-Plug Event has occurred, so update slot status register
* and notify OS of the event if necessary.
@@ -434,6 +457,7 @@ void pcie_cap_slot_plug_cb(HotplugHandler *hotplug_dev, DeviceState *dev,
pci_word_test_and_set_mask(exp_cap + PCI_EXP_LNKSTA,
PCI_EXP_LNKSTA_DLLLA);
}
+ pcie_cap_update_power(hotplug_pdev);
return;
}
@@ -451,6 +475,7 @@ void pcie_cap_slot_plug_cb(HotplugHandler *hotplug_dev, DeviceState *dev,
}
pcie_cap_slot_event(hotplug_pdev,
PCI_EXP_HP_EV_PDC | PCI_EXP_HP_EV_ABP);
+ pcie_cap_update_power(hotplug_pdev);
}
}
@@ -472,6 +497,25 @@ static void pcie_unplug_device(PCIBus *bus, PCIDevice *dev, void *opaque)
object_unparent(OBJECT(dev));
}
+static void pcie_cap_slot_do_unplug(PCIDevice *dev)
+{
+ PCIBus *sec_bus = pci_bridge_get_sec_bus(PCI_BRIDGE(dev));
+ uint8_t *exp_cap = dev->config + dev->exp.exp_cap;
+ uint32_t lnkcap = pci_get_long(exp_cap + PCI_EXP_LNKCAP);
+
+ pci_for_each_device_under_bus(sec_bus, pcie_unplug_device, NULL);
+
+ pci_word_test_and_clear_mask(exp_cap + PCI_EXP_SLTSTA,
+ PCI_EXP_SLTSTA_PDS);
+ if (dev->cap_present & QEMU_PCIE_LNKSTA_DLLLA ||
+ (lnkcap & PCI_EXP_LNKCAP_DLLLARC)) {
+ pci_word_test_and_clear_mask(exp_cap + PCI_EXP_LNKSTA,
+ PCI_EXP_LNKSTA_DLLLA);
+ }
+ pci_word_test_and_set_mask(exp_cap + PCI_EXP_SLTSTA,
+ PCI_EXP_SLTSTA_PDC);
+}
+
void pcie_cap_slot_unplug_request_cb(HotplugHandler *hotplug_dev,
DeviceState *dev, Error **errp)
{
@@ -481,6 +525,7 @@ void pcie_cap_slot_unplug_request_cb(HotplugHandler *hotplug_dev,
PCIDevice *hotplug_pdev = PCI_DEVICE(hotplug_dev);
uint8_t *exp_cap = hotplug_pdev->config + hotplug_pdev->exp.exp_cap;
uint32_t sltcap = pci_get_word(exp_cap + PCI_EXP_SLTCAP);
+ uint16_t sltctl = pci_get_word(exp_cap + PCI_EXP_SLTCTL);
/* Check if hot-unplug is disabled on the slot */
if ((sltcap & PCI_EXP_SLTCAP_HPC) == 0) {
@@ -496,7 +541,15 @@ void pcie_cap_slot_unplug_request_cb(HotplugHandler *hotplug_dev,
return;
}
+ if ((sltctl & PCI_EXP_SLTCTL_PIC) == PCI_EXP_SLTCTL_PWR_IND_BLINK) {
+ error_setg(errp, "Hot-unplug failed: "
+ "guest is busy (power indicator blinking)");
+ return;
+ }
+
dev->pending_deleted_event = true;
+ dev->pending_deleted_expires_ms =
+ qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + 5000; /* 5 secs */
/* In case user cancel the operation of multi-function hot-add,
* remove the function that is unexposed to guest individually,
@@ -509,6 +562,16 @@ void pcie_cap_slot_unplug_request_cb(HotplugHandler *hotplug_dev,
return;
}
+ if (((sltctl & PCI_EXP_SLTCTL_PIC) == PCI_EXP_SLTCTL_PWR_IND_OFF) &&
+ ((sltctl & PCI_EXP_SLTCTL_PCC) == PCI_EXP_SLTCTL_PWR_OFF)) {
+ /* slot is powered off -> unplug without round-trip to the guest */
+ pcie_cap_slot_do_unplug(hotplug_pdev);
+ hotplug_event_notify(hotplug_pdev);
+ pci_word_test_and_clear_mask(exp_cap + PCI_EXP_SLTSTA,
+ PCI_EXP_SLTSTA_ABP);
+ return;
+ }
+
pcie_cap_slot_push_attention_button(hotplug_pdev);
}
@@ -625,6 +688,7 @@ void pcie_cap_slot_reset(PCIDevice *dev)
PCI_EXP_SLTSTA_PDC |
PCI_EXP_SLTSTA_ABP);
+ pcie_cap_update_power(dev);
hotplug_event_update_event_status(dev);
}
@@ -643,7 +707,6 @@ void pcie_cap_slot_write_config(PCIDevice *dev,
uint32_t pos = dev->exp.exp_cap;
uint8_t *exp_cap = dev->config + pos;
uint16_t sltsta = pci_get_word(exp_cap + PCI_EXP_SLTSTA);
- uint32_t lnkcap = pci_get_long(exp_cap + PCI_EXP_LNKCAP);
if (ranges_overlap(addr, len, pos + PCI_EXP_SLTSTA, 2)) {
/*
@@ -693,18 +756,9 @@ void pcie_cap_slot_write_config(PCIDevice *dev,
(val & PCI_EXP_SLTCTL_PIC_OFF) == PCI_EXP_SLTCTL_PIC_OFF &&
(!(old_slt_ctl & PCI_EXP_SLTCTL_PCC) ||
(old_slt_ctl & PCI_EXP_SLTCTL_PIC_OFF) != PCI_EXP_SLTCTL_PIC_OFF)) {
- PCIBus *sec_bus = pci_bridge_get_sec_bus(PCI_BRIDGE(dev));
- pci_for_each_device_under_bus(sec_bus, pcie_unplug_device, NULL);
- pci_word_test_and_clear_mask(exp_cap + PCI_EXP_SLTSTA,
- PCI_EXP_SLTSTA_PDS);
- if (dev->cap_present & QEMU_PCIE_LNKSTA_DLLLA ||
- (lnkcap & PCI_EXP_LNKCAP_DLLLARC)) {
- pci_word_test_and_clear_mask(exp_cap + PCI_EXP_LNKSTA,
- PCI_EXP_LNKSTA_DLLLA);
- }
- pci_word_test_and_set_mask(exp_cap + PCI_EXP_SLTSTA,
- PCI_EXP_SLTSTA_PDC);
+ pcie_cap_slot_do_unplug(dev);
}
+ pcie_cap_update_power(dev);
hotplug_event_notify(dev);
@@ -731,6 +785,7 @@ int pcie_cap_slot_post_load(void *opaque, int version_id)
{
PCIDevice *dev = opaque;
hotplug_event_update_event_status(dev);
+ pcie_cap_update_power(dev);
return 0;
}