Try our best to follow more closely what the specification mandates for
function-level reset (section 6.6.2 of PCI Express Base Specification,
Revision 3.0). The previous approach was writing blindly on the command
register (setting int. disable bit), but that would lead to an
unsupported request on functions that do not expect that right away.

We now do checks for FLR support, proceed to the previous command
register write, wait for any pending transactions for some cycles (used
as a placeholder for the suggested 100ms timeout), raise the 'initiate
FLR' bit on the right register, wait on the same kind of loop again and,
finally, clear the transactions pending bit forcefully, as mandated.

This should leave us with better support for PCI devices out there.

Signed-off-by: Gustavo Lima Chaves <[email protected]>
---
 hypervisor/include/jailhouse/pci.h |  6 +++
 hypervisor/pci.c                   | 78 +++++++++++++++++++++++++++++++++++---
 2 files changed, 79 insertions(+), 5 deletions(-)

diff --git a/hypervisor/include/jailhouse/pci.h 
b/hypervisor/include/jailhouse/pci.h
index bed1498a..4befef11 100644
--- a/hypervisor/include/jailhouse/pci.h
+++ b/hypervisor/include/jailhouse/pci.h
@@ -40,7 +40,13 @@
 #define PCI_CAP_EXPRESS        0x10
 
 #define PCI_CAP_PCIE           0x10
+#define PCIE_DEV_CAPS_REG      0x04
+# define PCI_DEV_CAPS_FLR      (1 << 28)
+
 #define PCIE_CONTROL_REG       0x08
+# define PCI_CONTROL_INIT_FLR  (1 << 15)
+#define PCIE_STATUS_REG                0x0a
+# define PCI_STATUS_PENDING    (1 << 5)
 
 #define PCIE_DEVCTL_CERE       0x0001  /* Correctable Error Reporting En. */
 #define PCIE_DEVCTL_NFERE      0x0002  /* Non-Fatal Error Reporting Enable */
diff --git a/hypervisor/pci.c b/hypervisor/pci.c
index 00ba041f..5957dfb2 100644
--- a/hypervisor/pci.c
+++ b/hypervisor/pci.c
@@ -20,6 +20,7 @@
 #include <jailhouse/utils.h>
 
 #define MSIX_VECTOR_CTRL_DWORD         3
+#define PCI_RESET_TIMEOUT              250
 
 #define for_each_configured_pci_device(dev, cell)                      \
        for ((dev) = (cell)->pci_devices;                               \
@@ -610,10 +611,12 @@ void pci_prepare_handover(void)
        }
 }
 
-void pci_reset_device(struct pci_device *device)
+static void pci_reset_device_internal(struct pci_device *device)
 {
        const struct jailhouse_pci_capability *cap;
+       unsigned timeout = PCI_RESET_TIMEOUT;
        unsigned int n;
+       u32 val;
 
        memset(&device->msi_registers, 0, sizeof(device->msi_registers));
        for (n = 0; n < device->info->num_msix_vectors; n++) {
@@ -627,13 +630,79 @@ void pci_reset_device(struct pci_device *device)
                return;
        }
 
+       cap = pci_get_cap(device, PCI_CAP_PCIE | JAILHOUSE_PCI_EXT_CAP);
+       if (!cap)
+               return;
+
+       val = pci_read_config(device->info->bdf,
+                             cap->start + PCIE_DEV_CAPS_REG, 4);
+
+       if (!(val & PCI_DEV_CAPS_FLR)) {
+               printk("Skipping function level reset for "
+                      "device %02x:%02x.%x: no support\n",
+                      PCI_BDF_PARAMS(device->info->bdf));
+               return;
+       }
+
        /*
-        * Silence INTx of the physical device by setting the mask bit.
-        * This is a deviation from the specified reset state.
+        * "When a 0 is written to this register, the device is
+        * logically disconnected from the PCI bus for all accesses
+        * except configuration accesses."
+        *
+        * The bit that we set on that mask will silence INTx of the
+        * physical device.
         */
        pci_write_config(device->info->bdf, PCI_CFG_COMMAND,
                         PCI_CMD_INTX_OFF, 2);
 
+       /*
+        * Wait for any pending transaction to end. Ideally we should
+        * count for 100ms, but we count arbitrary loops for now
+        * instead, by lack of timer notion in the hypervisor.
+        */
+       do {
+               val = pci_read_config(device->info->bdf,
+                                     cap->start + PCIE_STATUS_REG, 2);
+               cpu_relax();
+               timeout--;
+       } while ((val & PCI_STATUS_PENDING) || timeout);
+
+       /*
+        * Set "initiate function level reset" bit and wait a little
+        * bit more.
+        */
+       val = pci_read_config(device->info->bdf,
+                             cap->start + PCIE_CONTROL_REG, 2);
+       pci_write_config(device->info->bdf, cap->start + PCIE_CONTROL_REG,
+                        val & PCI_CONTROL_INIT_FLR, 2);
+       timeout = PCI_RESET_TIMEOUT;
+       while (timeout) {
+               /*
+                * Read a possibly new status reg value for the write
+                * below, also making the two timing loops equal in time
+                * spans.
+                */
+               val = pci_read_config(device->info->bdf,
+                                     cap->start + PCIE_STATUS_REG, 2);
+               cpu_relax();
+               timeout--;
+       }
+
+       /*
+        * "The Transactions Pending bit must be cleared upon completion
+        * of the FLR."
+        */
+       pci_write_config(device->info->bdf, cap->start + PCIE_STATUS_REG,
+                        val & ~PCI_STATUS_PENDING, 2);
+}
+
+void pci_reset_device(struct pci_device *device)
+{
+       const struct jailhouse_pci_capability *cap;
+       unsigned int n;
+
+       pci_reset_device_internal(device);
+
        for_each_pci_cap(cap, device, n) {
                if (cap->id == PCI_CAP_MSI || cap->id == PCI_CAP_MSIX)
                        /* Disable MSI/MSI-X by clearing the control word. */
@@ -929,7 +998,6 @@ void pci_shutdown(void)
                                pci_restore_msix(device, cap);
 
                if (device->cell != &root_cell)
-                       pci_write_config(device->info->bdf, PCI_CFG_COMMAND,
-                                        PCI_CMD_INTX_OFF, 2);
+                       pci_reset_device_internal(device);
        }
 }
-- 
2.14.3

-- 
You received this message because you are subscribed to the Google Groups 
"Jailhouse" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/d/optout.

Reply via email to