Merge tag 'dm-4.1-changes' of git://git.kernel.org/pub/scm/linux/kernel/git/device...
[firefly-linux-kernel-4.4.55.git] / drivers / pci / iov.c
index c4c33ead03bc5f3ea7af66550b5aa74382806f97..ee0ebff103a412bc3db69c05f0a30ecc1872be48 100644 (file)
 
 #define VIRTFN_ID_LEN  16
 
-static inline u8 virtfn_bus(struct pci_dev *dev, int id)
+int pci_iov_virtfn_bus(struct pci_dev *dev, int vf_id)
 {
+       if (!dev->is_physfn)
+               return -EINVAL;
        return dev->bus->number + ((dev->devfn + dev->sriov->offset +
-                                   dev->sriov->stride * id) >> 8);
+                                   dev->sriov->stride * vf_id) >> 8);
 }
 
-static inline u8 virtfn_devfn(struct pci_dev *dev, int id)
+int pci_iov_virtfn_devfn(struct pci_dev *dev, int vf_id)
 {
+       if (!dev->is_physfn)
+               return -EINVAL;
        return (dev->devfn + dev->sriov->offset +
-               dev->sriov->stride * id) & 0xff;
+               dev->sriov->stride * vf_id) & 0xff;
+}
+
+/*
+ * Per SR-IOV spec sec 3.3.10 and 3.3.11, First VF Offset and VF Stride may
+ * change when NumVFs changes.
+ *
+ * Update iov->offset and iov->stride when NumVFs is written.
+ */
+static inline void pci_iov_set_numvfs(struct pci_dev *dev, int nr_virtfn)
+{
+       struct pci_sriov *iov = dev->sriov;
+
+       pci_write_config_word(dev, iov->pos + PCI_SRIOV_NUM_VF, nr_virtfn);
+       pci_read_config_word(dev, iov->pos + PCI_SRIOV_VF_OFFSET, &iov->offset);
+       pci_read_config_word(dev, iov->pos + PCI_SRIOV_VF_STRIDE, &iov->stride);
+}
+
+/*
+ * The PF consumes one bus number.  NumVFs, First VF Offset, and VF Stride
+ * determine how many additional bus numbers will be consumed by VFs.
+ *
+ * Iterate over all valid NumVFs and calculate the maximum number of bus
+ * numbers that could ever be required.
+ */
+static inline u8 virtfn_max_buses(struct pci_dev *dev)
+{
+       struct pci_sriov *iov = dev->sriov;
+       int nr_virtfn;
+       u8 max = 0;
+       int busnr;
+
+       for (nr_virtfn = 1; nr_virtfn <= iov->total_VFs; nr_virtfn++) {
+               pci_iov_set_numvfs(dev, nr_virtfn);
+               busnr = pci_iov_virtfn_bus(dev, nr_virtfn - 1);
+               if (busnr > max)
+                       max = busnr;
+       }
+
+       return max;
 }
 
 static struct pci_bus *virtfn_add_bus(struct pci_bus *bus, int busnr)
@@ -57,6 +100,14 @@ static void virtfn_remove_bus(struct pci_bus *physbus, struct pci_bus *virtbus)
                pci_remove_bus(virtbus);
 }
 
+resource_size_t pci_iov_resource_size(struct pci_dev *dev, int resno)
+{
+       if (!dev->is_physfn)
+               return 0;
+
+       return dev->sriov->barsz[resno - PCI_IOV_RESOURCES];
+}
+
 static int virtfn_add(struct pci_dev *dev, int id, int reset)
 {
        int i;
@@ -69,7 +120,7 @@ static int virtfn_add(struct pci_dev *dev, int id, int reset)
        struct pci_bus *bus;
 
        mutex_lock(&iov->dev->sriov->lock);
-       bus = virtfn_add_bus(dev->bus, virtfn_bus(dev, id));
+       bus = virtfn_add_bus(dev->bus, pci_iov_virtfn_bus(dev, id));
        if (!bus)
                goto failed;
 
@@ -77,7 +128,7 @@ static int virtfn_add(struct pci_dev *dev, int id, int reset)
        if (!virtfn)
                goto failed0;
 
-       virtfn->devfn = virtfn_devfn(dev, id);
+       virtfn->devfn = pci_iov_virtfn_devfn(dev, id);
        virtfn->vendor = dev->vendor;
        pci_read_config_word(dev, iov->pos + PCI_SRIOV_VF_DID, &virtfn->device);
        pci_setup_device(virtfn);
@@ -87,13 +138,12 @@ static int virtfn_add(struct pci_dev *dev, int id, int reset)
        virtfn->multifunction = 0;
 
        for (i = 0; i < PCI_SRIOV_NUM_BARS; i++) {
-               res = dev->resource + PCI_IOV_RESOURCES + i;
+               res = &dev->resource[i + PCI_IOV_RESOURCES];
                if (!res->parent)
                        continue;
                virtfn->resource[i].name = pci_name(virtfn);
                virtfn->resource[i].flags = res->flags;
-               size = resource_size(res);
-               do_div(size, iov->total_VFs);
+               size = pci_iov_resource_size(dev, i + PCI_IOV_RESOURCES);
                virtfn->resource[i].start = res->start + size * id;
                virtfn->resource[i].end = virtfn->resource[i].start + size - 1;
                rc = request_resource(res, &virtfn->resource[i]);
@@ -140,8 +190,8 @@ static void virtfn_remove(struct pci_dev *dev, int id, int reset)
        struct pci_sriov *iov = dev->sriov;
 
        virtfn = pci_get_domain_bus_and_slot(pci_domain_nr(dev->bus),
-                                            virtfn_bus(dev, id),
-                                            virtfn_devfn(dev, id));
+                                            pci_iov_virtfn_bus(dev, id),
+                                            pci_iov_virtfn_devfn(dev, id));
        if (!virtfn)
                return;
 
@@ -170,6 +220,11 @@ static void virtfn_remove(struct pci_dev *dev, int id, int reset)
        pci_dev_put(dev);
 }
 
+int __weak pcibios_sriov_enable(struct pci_dev *pdev, u16 num_vfs)
+{
+       return 0;
+}
+
 static int sriov_enable(struct pci_dev *dev, int nr_virtfn)
 {
        int rc;
@@ -180,7 +235,8 @@ static int sriov_enable(struct pci_dev *dev, int nr_virtfn)
        struct pci_dev *pdev;
        struct pci_sriov *iov = dev->sriov;
        int bars = 0;
-       u8 bus;
+       int bus;
+       int retval;
 
        if (!nr_virtfn)
                return 0;
@@ -205,7 +261,7 @@ static int sriov_enable(struct pci_dev *dev, int nr_virtfn)
        nres = 0;
        for (i = 0; i < PCI_SRIOV_NUM_BARS; i++) {
                bars |= (1 << (i + PCI_IOV_RESOURCES));
-               res = dev->resource + PCI_IOV_RESOURCES + i;
+               res = &dev->resource[i + PCI_IOV_RESOURCES];
                if (res->parent)
                        nres++;
        }
@@ -217,7 +273,7 @@ static int sriov_enable(struct pci_dev *dev, int nr_virtfn)
        iov->offset = offset;
        iov->stride = stride;
 
-       bus = virtfn_bus(dev, nr_virtfn - 1);
+       bus = pci_iov_virtfn_bus(dev, nr_virtfn - 1);
        if (bus > dev->bus->busn_res.end) {
                dev_err(&dev->dev, "can't enable %d VFs (bus %02x out of range of %pR)\n",
                        nr_virtfn, bus, &dev->bus->busn_res);
@@ -246,7 +302,7 @@ static int sriov_enable(struct pci_dev *dev, int nr_virtfn)
                        return rc;
        }
 
-       pci_write_config_word(dev, iov->pos + PCI_SRIOV_NUM_VF, nr_virtfn);
+       pci_iov_set_numvfs(dev, nr_virtfn);
        iov->ctrl |= PCI_SRIOV_CTRL_VFE | PCI_SRIOV_CTRL_MSE;
        pci_cfg_access_lock(dev);
        pci_write_config_word(dev, iov->pos + PCI_SRIOV_CTRL, iov->ctrl);
@@ -257,6 +313,12 @@ static int sriov_enable(struct pci_dev *dev, int nr_virtfn)
        if (nr_virtfn < initial)
                initial = nr_virtfn;
 
+       if ((retval = pcibios_sriov_enable(dev, initial))) {
+               dev_err(&dev->dev, "failure %d from pcibios_sriov_enable()\n",
+                       retval);
+               return retval;
+       }
+
        for (i = 0; i < initial; i++) {
                rc = virtfn_add(dev, i, 0);
                if (rc)
@@ -275,7 +337,7 @@ failed:
        iov->ctrl &= ~(PCI_SRIOV_CTRL_VFE | PCI_SRIOV_CTRL_MSE);
        pci_cfg_access_lock(dev);
        pci_write_config_word(dev, iov->pos + PCI_SRIOV_CTRL, iov->ctrl);
-       pci_write_config_word(dev, iov->pos + PCI_SRIOV_NUM_VF, 0);
+       pci_iov_set_numvfs(dev, 0);
        ssleep(1);
        pci_cfg_access_unlock(dev);
 
@@ -285,6 +347,11 @@ failed:
        return rc;
 }
 
+int __weak pcibios_sriov_disable(struct pci_dev *pdev)
+{
+       return 0;
+}
+
 static void sriov_disable(struct pci_dev *dev)
 {
        int i;
@@ -296,6 +363,8 @@ static void sriov_disable(struct pci_dev *dev)
        for (i = 0; i < iov->num_VFs; i++)
                virtfn_remove(dev, i, 0);
 
+       pcibios_sriov_disable(dev);
+
        iov->ctrl &= ~(PCI_SRIOV_CTRL_VFE | PCI_SRIOV_CTRL_MSE);
        pci_cfg_access_lock(dev);
        pci_write_config_word(dev, iov->pos + PCI_SRIOV_CTRL, iov->ctrl);
@@ -306,12 +375,12 @@ static void sriov_disable(struct pci_dev *dev)
                sysfs_remove_link(&dev->dev.kobj, "dep_link");
 
        iov->num_VFs = 0;
-       pci_write_config_word(dev, iov->pos + PCI_SRIOV_NUM_VF, 0);
+       pci_iov_set_numvfs(dev, 0);
 }
 
 static int sriov_init(struct pci_dev *dev, int pos)
 {
-       int i;
+       int i, bar64;
        int rc;
        int nres;
        u32 pgsz;
@@ -360,27 +429,29 @@ found:
        pgsz &= ~(pgsz - 1);
        pci_write_config_dword(dev, pos + PCI_SRIOV_SYS_PGSIZE, pgsz);
 
+       iov = kzalloc(sizeof(*iov), GFP_KERNEL);
+       if (!iov)
+               return -ENOMEM;
+
        nres = 0;
        for (i = 0; i < PCI_SRIOV_NUM_BARS; i++) {
-               res = dev->resource + PCI_IOV_RESOURCES + i;
-               i += __pci_read_base(dev, pci_bar_unknown, res,
-                                    pos + PCI_SRIOV_BAR + i * 4);
+               res = &dev->resource[i + PCI_IOV_RESOURCES];
+               bar64 = __pci_read_base(dev, pci_bar_unknown, res,
+                                       pos + PCI_SRIOV_BAR + i * 4);
                if (!res->flags)
                        continue;
                if (resource_size(res) & (PAGE_SIZE - 1)) {
                        rc = -EIO;
                        goto failed;
                }
+               iov->barsz[i] = resource_size(res);
                res->end = res->start + resource_size(res) * total - 1;
+               dev_info(&dev->dev, "VF(n) BAR%d space: %pR (contains BAR%d for %d VFs)\n",
+                        i, res, i, total);
+               i += bar64;
                nres++;
        }
 
-       iov = kzalloc(sizeof(*iov), GFP_KERNEL);
-       if (!iov) {
-               rc = -ENOMEM;
-               goto failed;
-       }
-
        iov->pos = pos;
        iov->nres = nres;
        iov->ctrl = ctrl;
@@ -403,15 +474,17 @@ found:
 
        dev->sriov = iov;
        dev->is_physfn = 1;
+       iov->max_VF_buses = virtfn_max_buses(dev);
 
        return 0;
 
 failed:
        for (i = 0; i < PCI_SRIOV_NUM_BARS; i++) {
-               res = dev->resource + PCI_IOV_RESOURCES + i;
+               res = &dev->resource[i + PCI_IOV_RESOURCES];
                res->flags = 0;
        }
 
+       kfree(iov);
        return rc;
 }
 
@@ -442,7 +515,7 @@ static void sriov_restore_state(struct pci_dev *dev)
                pci_update_resource(dev, i);
 
        pci_write_config_dword(dev, iov->pos + PCI_SRIOV_SYS_PGSIZE, iov->pgsz);
-       pci_write_config_word(dev, iov->pos + PCI_SRIOV_NUM_VF, iov->num_VFs);
+       pci_iov_set_numvfs(dev, iov->num_VFs);
        pci_write_config_word(dev, iov->pos + PCI_SRIOV_CTRL, iov->ctrl);
        if (iov->ctrl & PCI_SRIOV_CTRL_VFE)
                msleep(100);
@@ -496,6 +569,12 @@ int pci_iov_resource_bar(struct pci_dev *dev, int resno)
                4 * (resno - PCI_IOV_RESOURCES);
 }
 
+resource_size_t __weak pcibios_iov_resource_alignment(struct pci_dev *dev,
+                                                     int resno)
+{
+       return pci_iov_resource_size(dev, resno);
+}
+
 /**
  * pci_sriov_resource_alignment - get resource alignment for VF BAR
  * @dev: the PCI device
@@ -508,14 +587,7 @@ int pci_iov_resource_bar(struct pci_dev *dev, int resno)
  */
 resource_size_t pci_sriov_resource_alignment(struct pci_dev *dev, int resno)
 {
-       struct resource tmp;
-       int reg = pci_iov_resource_bar(dev, resno);
-
-       if (!reg)
-               return 0;
-
-        __pci_read_base(dev, pci_bar_unknown, &tmp, reg);
-       return resource_alignment(&tmp);
+       return pcibios_iov_resource_alignment(dev, resno);
 }
 
 /**
@@ -538,15 +610,13 @@ void pci_restore_iov_state(struct pci_dev *dev)
 int pci_iov_bus_range(struct pci_bus *bus)
 {
        int max = 0;
-       u8 busnr;
        struct pci_dev *dev;
 
        list_for_each_entry(dev, &bus->devices, bus_list) {
                if (!dev->is_physfn)
                        continue;
-               busnr = virtfn_bus(dev, dev->sriov->total_VFs - 1);
-               if (busnr > max)
-                       max = busnr;
+               if (dev->sriov->max_VF_buses > max)
+                       max = dev->sriov->max_VF_buses;
        }
 
        return max ? max - bus->number : 0;