aboutsummaryrefslogtreecommitdiff
path: root/hw/pci
diff options
context:
space:
mode:
Diffstat (limited to 'hw/pci')
-rw-r--r--hw/pci/pcie.c19
1 files changed, 19 insertions, 0 deletions
diff --git a/hw/pci/pcie.c b/hw/pci/pcie.c
index f8490a00de..c605d32dd4 100644
--- a/hw/pci/pcie.c
+++ b/hw/pci/pcie.c
@@ -610,6 +610,25 @@ void pcie_cap_slot_write_config(PCIDevice *dev, uint16_t slt_ctl, uint16_t slt_s
uint16_t sltsta = pci_get_word(exp_cap + PCI_EXP_SLTSTA);
if (ranges_overlap(addr, len, pos + PCI_EXP_SLTSTA, 2)) {
+ /*
+ * Guests tend to clears all bits during init.
+ * If they clear bits that weren't set this is racy and will lose events:
+ * not a big problem for manual button presses, but a problem for us.
+ * As a work-around, detect this and revert status to what it was
+ * before the write.
+ *
+ * Note: in theory this can be detected as a duplicate button press
+ * which cancels the previous press. Does not seem to happen in
+ * practice as guests seem to only have this bug during init.
+ */
+#define PCIE_SLOT_EVENTS (PCI_EXP_SLTSTA_ABP | PCI_EXP_SLTSTA_PFD | \
+ PCI_EXP_SLTSTA_MRLSC | PCI_EXP_SLTSTA_PDC | \
+ PCI_EXP_SLTSTA_CC)
+
+ if (val & ~slt_sta & PCIE_SLOT_EVENTS) {
+ sltsta = (sltsta & ~PCIE_SLOT_EVENTS) | (slt_sta & PCIE_SLOT_EVENTS);
+ pci_set_word(exp_cap + PCI_EXP_SLTSTA, sltsta);
+ }
hotplug_event_clear(dev);
}