summaryrefslogtreecommitdiffstats
path: root/arch/s390/pci/pci_bus.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/s390/pci/pci_bus.c')
-rw-r--r--arch/s390/pci/pci_bus.c72
1 files changed, 70 insertions, 2 deletions
diff --git a/arch/s390/pci/pci_bus.c b/arch/s390/pci/pci_bus.c
index ada571d1c630..642a99384688 100644
--- a/arch/s390/pci/pci_bus.c
+++ b/arch/s390/pci/pci_bus.c
@@ -126,6 +126,64 @@ static struct zpci_bus *zpci_bus_alloc(int pchid)
return zbus;
}
+#ifdef CONFIG_PCI_IOV
+static int zpci_bus_link_virtfn(struct pci_dev *pdev,
+ struct pci_dev *virtfn, int vfid)
+{
+ int rc;
+
+ virtfn->physfn = pci_dev_get(pdev);
+ rc = pci_iov_sysfs_link(pdev, virtfn, vfid);
+ if (rc) {
+ pci_dev_put(pdev);
+ virtfn->physfn = NULL;
+ return rc;
+ }
+ return 0;
+}
+
+static int zpci_bus_setup_virtfn(struct zpci_bus *zbus,
+ struct pci_dev *virtfn, int vfn)
+{
+ int i, cand_devfn;
+ struct zpci_dev *zdev;
+ struct pci_dev *pdev;
+ int vfid = vfn - 1; /* Linux' vfid's start at 0 vfn at 1*/
+ int rc = 0;
+
+ virtfn->is_virtfn = 1;
+ virtfn->multifunction = 0;
+ WARN_ON(vfid < 0);
+ /* If the parent PF for the given VF is also configured in the
+ * instance, it must be on the same zbus.
+ * We can then identify the parent PF by checking what
+ * devfn the VF would have if it belonged to that PF using the PF's
+ * stride and offset. Only if this candidate devfn matches the
+ * actual devfn will we link both functions.
+ */
+ for (i = 0; i < ZPCI_FUNCTIONS_PER_BUS; i++) {
+ zdev = zbus->function[i];
+ if (zdev && zdev->is_physfn) {
+ pdev = pci_get_slot(zbus->bus, zdev->devfn);
+ cand_devfn = pci_iov_virtfn_devfn(pdev, vfid);
+ if (cand_devfn == virtfn->devfn) {
+ rc = zpci_bus_link_virtfn(pdev, virtfn, vfid);
+ break;
+ }
+ }
+ }
+ return rc;
+}
+#else
+static inline int zpci_bus_setup_virtfn(struct zpci_bus *zbus,
+ struct pci_dev *virtfn, int vfn)
+{
+ virtfn->is_virtfn = 1;
+ virtfn->multifunction = 0;
+ return 0;
+}
+#endif
+
static int zpci_bus_add_device(struct zpci_bus *zbus, struct zpci_dev *zdev)
{
struct pci_bus *bus;
@@ -156,10 +214,20 @@ static int zpci_bus_add_device(struct zpci_bus *zbus, struct zpci_dev *zdev)
}
pdev = pci_scan_single_device(bus, zdev->devfn);
- if (pdev)
+ if (pdev) {
+ if (!zdev->is_physfn) {
+ rc = zpci_bus_setup_virtfn(zbus, pdev, zdev->vfn);
+ if (rc)
+ goto failed_with_pdev;
+ }
pci_bus_add_device(pdev);
-
+ }
return 0;
+
+failed_with_pdev:
+ pci_stop_and_remove_bus_device(pdev);
+ pci_dev_put(pdev);
+ return rc;
}
static void zpci_bus_add_devices(struct zpci_bus *zbus)