After the PCIe device is hot-plugged, the device's power state is initialized as ON. However, the device isn't powered on yet, i.e. the PCI_EXP_SYSCTL_PCC bit isn't set to PCI_EXP_SLTCTL_PWR_ON. Later on, its power state will set back to OFF due to the non PCI_EXP_SLTCTL_PWR_ON state. The device is invisible until PCI_EXP_SLTCTL_PWR_ON is set.
This may be appropriate for general PCIe hot-plug cases. However, if the device is hot-plugged when the VM is in VM_STATE_PRELAUNCH state, especially the system disk device, the firmware will fail to find the system disk. As a result, the guest fails to boot. An extra flag(set_power) is added in this patch to indicate if pci_set_power is needed. After the device is powered on(PCI_EXP_SLTCTL_PWR_ON), its power state will be set as normal devices. Fixes: 090b32b8dae6 ("implement slot power control for pcie root ports") Signed-off-by: Annie Li <annie...@oracle.com> Reviewed-by: Darren Kenny <darren.ke...@oracle.com> --- hw/pci/pci.c | 1 + hw/pci/pcie.c | 29 +++++++++++++++++++++++++++-- include/hw/pci/pci.h | 1 + 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/hw/pci/pci.c b/hw/pci/pci.c index e5993c1ef5..b61c547291 100644 --- a/hw/pci/pci.c +++ b/hw/pci/pci.c @@ -2186,6 +2186,7 @@ static void pci_qdev_realize(DeviceState *qdev, Error **errp) return; } + pci_dev->set_power = true; pci_set_power(pci_dev, true); } diff --git a/hw/pci/pcie.c b/hw/pci/pcie.c index d7d73a31e4..e4ff23f3b9 100644 --- a/hw/pci/pcie.c +++ b/hw/pci/pcie.c @@ -28,6 +28,7 @@ #include "hw/pci/pcie_regs.h" #include "hw/pci/pcie_port.h" #include "qemu/range.h" +#include "sysemu/runstate.h" //#define DEBUG_PCIE #ifdef DEBUG_PCIE @@ -385,8 +386,20 @@ static void pcie_cap_update_power(PCIDevice *hotplug_dev) 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); + /* + * For devices hot-plugged in RUN_STATE_PRELAUNCH state, set_power is + * set to false to avoid unnecessary power state changes before the device + * is powered on. After the device is powered on, set_power has to be + * set back to true to allow general power state changes. + */ + if (!hotplug_dev->set_power && power) { + hotplug_dev->set_power = true; + } + + if (hotplug_dev->set_power) { + pci_for_each_device(sec_bus, pci_bus_num(sec_bus), + pcie_set_power_device, &power); + } } /* @@ -475,6 +488,18 @@ 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); + + /* + * After the system disk device is hot-plugged during + * RUN_STATE_PRELAUNCH state, its power state will be set to OFF + * before the device is actually powered on. The device is invisible + * during this period. Hence the firmware won't find the system + * disk to boot. The set_power is set to false to avoid setting the + * power state to OFF. + */ + if (runstate_check(RUN_STATE_PRELAUNCH)) { + hotplug_pdev->set_power = false; + } pcie_cap_update_power(hotplug_pdev); } } diff --git a/include/hw/pci/pci.h b/include/hw/pci/pci.h index e7cdf2d5ec..753df3523e 100644 --- a/include/hw/pci/pci.h +++ b/include/hw/pci/pci.h @@ -269,6 +269,7 @@ struct PCIDevice { DeviceState qdev; bool partially_hotplugged; bool has_power; + bool set_power; /* PCI config space */ uint8_t *config; -- 2.31.1