]> git.proxmox.com Git - qemu.git/blobdiff - hw/pcie.c
qdev: add creation function that may fail
[qemu.git] / hw / pcie.c
index 881af7878c7d80e1aec75e8013849f7af9a68c05..6a113a9327bb1e4759b064c28c4dae490f2db6e2 100644 (file)
--- 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"
@@ -139,6 +140,42 @@ 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 (msix_enabled(dev)) {
+        msix_notify(dev, pcie_cap_flags_get_vector(dev));
+    } else if (msi_enabled(dev)) {
+        msi_notify(dev, pcie_cap_flags_get_vector(dev));
+    } else {
+        qemu_set_irq(dev->irq[dev->exp.hpev_intx], dev->exp.hpev_notified);
+    }
+}
+
 /*
  * A PCI Express Hot-Plug Event has occured, so update slot status register
  * and notify OS of the event if necessary.
@@ -148,39 +185,25 @@ 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,
-                                 PCIDevice *pci_dev, int state)
+                                 PCIDevice *pci_dev, PCIHotplugState 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. */
+    /* Don't send event when device is enabled during qemu machine creation:
+     * it is present on boot, no hotplug event is necessary. We do send an
+     * event when the device is disabled later. */
+    if (state == PCI_COLDPLUG_ENABLED) {
         pci_word_test_and_set_mask(exp_cap + PCI_EXP_SLTSTA,
                                    PCI_EXP_SLTSTA_PDS);
         return 0;
@@ -200,7 +223,7 @@ static int pcie_cap_slot_hotplug(DeviceState *qdev,
      */
     assert(PCI_FUNC(pci_dev->devfn) == 0);
 
-    if (state) {
+    if (state == PCI_HOTPLUG_ENABLED) {
         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);
@@ -257,6 +280,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);
 }
@@ -285,77 +310,56 @@ void pcie_cap_slot_reset(PCIDevice *dev)
                                  PCI_EXP_SLTSTA_CC |
                                  PCI_EXP_SLTSTA_PDC |
                                  PCI_EXP_SLTSTA_ABP);
+
+    hotplug_event_update_event_status(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);
 
-    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);
-        }
+    if (!ranges_overlap(addr, len, pos + PCI_EXP_SLTCTL, 2)) {
+        return;
+    }
 
-        /*
-         * 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 (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);
+    }
 
-        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
+     *
+     * 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);
+}
 
-        /* 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);
-    }
+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)
@@ -376,10 +380,6 @@ 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)
 {
@@ -399,8 +399,11 @@ 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 */
+    if (pci_get_word(devctl) & PCI_EXP_DEVCTL_BCR_FLR) {
+        /* Clear PCI_EXP_DEVCTL_BCR_FLR after invoking the reset handler
+           so the handler can detect FLR by looking at this bit. */
+        pci_device_reset(dev);
+        pci_word_test_and_clear_mask(devctl, PCI_EXP_DEVCTL_BCR_FLR);
     }
 }