[libvirt] [PATCH 1/3] Tests for ACS in PCIe switches

Jiri Denemark jdenemar at redhat.com
Mon Dec 21 13:27:17 UTC 2009


New pciDeviceIsAssignable() function for checking whether a given PCI
device can be assigned to a guest was added. Currently it only checks
for ACS being enabled on all PCIe switches between root and the PCI
device. In the future, it could be the right place to check whether a
device is unbound or bound to a stub driver.

Signed-off-by: Jiri Denemark <jdenemar at redhat.com>
---
 src/libvirt_private.syms |    3 +
 src/util/pci.c           |  147 ++++++++++++++++++++++++++++++++++++++++++++++
 src/util/pci.h           |    7 ++
 3 files changed, 157 insertions(+), 0 deletions(-)

diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index f90f269..7073e0c 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -438,6 +438,9 @@ pciDeviceListGet;
 pciDeviceListLock;
 pciDeviceListUnlock;
 pciDeviceListSteal;
+pciDeviceSetPermissive;
+pciDeviceGetPermissive;
+pciDeviceIsAssignable;
 
 
 # processinfo.h
diff --git a/src/util/pci.c b/src/util/pci.c
index 1e003c2..113299e 100644
--- a/src/util/pci.c
+++ b/src/util/pci.c
@@ -64,6 +64,7 @@ struct _pciDevice {
     unsigned      has_flr : 1;
     unsigned      has_pm_reset : 1;
     unsigned      managed : 1;
+    unsigned      permissive : 1;
 };
 
 struct _pciDeviceList {
@@ -140,6 +141,14 @@ struct _pciDeviceList {
 #define PCI_AF_CAP              0x3     /* Advanced features capabilities */
 #define  PCI_AF_CAP_FLR         0x2     /* Function Level Reset */
 
+#define PCI_EXP_FLAGS           0x2
+#define PCI_EXP_FLAGS_TYPE      0x00f0
+#define PCI_EXP_TYPE_DOWNSTREAM 0x6
+
+#define PCI_EXT_CAP_ID_ACS      0x000d
+#define PCI_EXT_ACS_CTRL        0x06
+#define PCI_EXT_CAP_ACS_ENABLED 0x1d
+
 static int
 pciOpenConfig(pciDevice *dev)
 {
@@ -326,6 +335,29 @@ pciFindCapabilityOffset(pciDevice *dev, unsigned capability)
     return 0;
 }
 
+static unsigned int
+pciFindExtendedCapabilityOffset(pciDevice *dev, unsigned capability)
+{
+    int ttl;
+    unsigned int pos;
+    uint32_t header;
+
+    ttl = 480; /* 3840 bytes, minimum 8 bytes per capability */
+    pos = PCI_CONF_LEN;
+
+    while (ttl > 0 && pos >= PCI_CONF_LEN) {
+        header = pciRead32(dev, pos);
+
+        if ((header & 0x0000ffff) == capability)
+            return pos;
+
+        pos = (header >> 20) & 0xffc; /* WTH */
+        ttl--;
+    }
+
+    return 0;
+}
+
 static unsigned
 pciDetectFunctionLevelReset(pciDevice *dev)
 {
@@ -933,6 +965,16 @@ unsigned pciDeviceGetManaged(pciDevice *dev)
     return dev->managed;
 }
 
+void pciDeviceSetPermissive(pciDevice *dev, unsigned permissive)
+{
+    dev->permissive = !!permissive;
+}
+
+unsigned pciDeviceGetPermissive(pciDevice *dev)
+{
+    return dev->permissive;
+}
+
 pciDeviceList *
 pciDeviceListNew(virConnectPtr conn)
 {
@@ -1110,3 +1152,108 @@ cleanup:
     VIR_FREE(pcidir);
     return ret;
 }
+
+static int
+pciDeviceDownstreamLacksACS(virConnectPtr conn,
+                            pciDevice *dev)
+{
+    uint16_t flags;
+    uint16_t ctrl;
+    unsigned int pos;
+
+    if (!dev->initted && pciInitDevice(conn, dev) < 0)
+        return -1;
+
+    pos = dev->pcie_cap_pos;
+    if (!pos || !pciRead16(dev, PCI_CLASS_DEVICE) == PCI_CLASS_BRIDGE_PCI)
+        return 0;
+
+    flags = pciRead16(dev, pos + PCI_EXP_FLAGS);
+    if (((flags & PCI_EXP_FLAGS_TYPE) >> 4) != PCI_EXP_TYPE_DOWNSTREAM)
+        return 0;
+
+    pos = pciFindExtendedCapabilityOffset(dev, PCI_EXT_CAP_ID_ACS);
+    if (!pos) {
+        VIR_DEBUG("%s %s: downstream port lacks ACS", dev->id, dev->name);
+        return 1;
+    }
+
+    ctrl = pciRead16(dev, pos + PCI_EXT_ACS_CTRL);
+    if ((ctrl & PCI_EXT_CAP_ACS_ENABLED) != PCI_EXT_CAP_ACS_ENABLED) {
+        VIR_DEBUG("%s %s: downstream port has ACS disabled",
+                  dev->id, dev->name);
+        return 1;
+    }
+
+    return 0;
+}
+
+static int
+pciDeviceIsBehindSwitchLackingACS(virConnectPtr conn,
+                                  pciDevice *dev)
+{
+    pciDevice *parent;
+
+    if (!(parent = pciGetParentDevice(conn, dev))) {
+        pciReportError(conn, VIR_ERR_NO_SUPPORT,
+                       _("Failed to find parent device for %s"),
+                       dev->name);
+        return -1;
+    }
+
+    /* XXX we should rather fail when we can't find device's parent and
+     * stop the loop when we get to root instead of just stopping when no
+     * parent can be found
+     */
+    do {
+        pciDevice *tmp;
+        int acs;
+
+        acs = pciDeviceDownstreamLacksACS(conn, parent);
+
+        if (acs) {
+            pciFreeDevice(conn, parent);
+            if (acs < 0)
+                return -1;
+            else
+                return 1;
+        }
+
+        tmp = parent;
+        parent = pciGetParentDevice(conn, parent);
+        pciFreeDevice(conn, tmp);
+    } while (parent);
+
+    return 0;
+}
+
+int pciDeviceIsAssignable(virConnectPtr conn,
+                          pciDevice *dev)
+{
+    int ret;
+
+    /* XXX This could be a great place to actually check that a non-managed
+     * device isn't in use, e.g. by checking that device is either un-bound
+     * or bound to a stub driver.
+     */
+
+    ret = pciDeviceIsBehindSwitchLackingACS(conn, dev);
+    if (ret < 0)
+        return 0;
+
+    if (ret) {
+        if (dev->permissive) {
+            VIR_DEBUG("%s %s: is in permissive mode; allowing it to be assigned",
+                      dev->id, dev->name);
+        }
+        else {
+            pciReportError(conn, VIR_ERR_NO_SUPPORT,
+                           _("Device %s is behind a switch lacking ACS and "
+                             "cannot be assigned"),
+                           dev->name);
+            return 0;
+        }
+    }
+
+    return 1;
+}
diff --git a/src/util/pci.h b/src/util/pci.h
index 1f0b9d2..a56f064 100644
--- a/src/util/pci.h
+++ b/src/util/pci.h
@@ -78,4 +78,11 @@ int pciDeviceFileIterate(virConnectPtr conn,
                          pciDeviceFileActor actor,
                          void *opaque);
 
+int pciDeviceIsAssignable(virConnectPtr conn,
+                          pciDevice *dev);
+
+void      pciDeviceSetPermissive(pciDevice     *dev,
+                                 unsigned       managed);
+unsigned  pciDeviceGetPermissive(pciDevice     *dev);
+
 #endif /* __VIR_PCI_H__ */
-- 
1.6.6.rc4




More information about the libvir-list mailing list