@@ -854,16 +854,18 @@ static unsigned int pnv_ioda_dma_weight(struct pci_dev *dev)
}
#ifdef CONFIG_PCI_IOV
-static void pnv_pci_vf_resource_shift(struct pci_dev *dev, int offset)
+static int pnv_pci_vf_resource_shift(struct pci_dev *dev, int offset)
{
struct pci_dn *pdn = pci_get_pdn(dev);
int i;
struct resource *res;
resource_size_t size;
+ u16 vf_num;
if (!dev->is_physfn)
- return;
+ return -EINVAL;
+ vf_num = pdn->vf_pes;
for (i = PCI_IOV_RESOURCES; i <= PCI_IOV_RESOURCE_END; i++) {
res = &dev->resource[i];
if (!res->flags || !res->parent)
@@ -875,11 +877,49 @@ static void pnv_pci_vf_resource_shift(struct pci_dev *dev, int offset)
dev_info(&dev->dev, " Shifting VF BAR %pR to\n", res);
size = pci_iov_resource_size(dev, i);
res->start += size*offset;
-
dev_info(&dev->dev, " %pR\n", res);
+
+ /*
+ * The actual IOV BAR range is determined by the start address
+ * and the actual size for vf_num VFs BAR. The check here is
+ * to make sure after shifting, the range will not overlap
+ * with other device.
+ */
+ if ((res->start + (size * vf_num)) > res->end) {
+ dev_err(&dev->dev, "VF BAR%d: %pR will conflict with"
+ " other device after shift\n");
+ goto failed;
+ }
+ }
+
+ for (i = PCI_IOV_RESOURCES; i <= PCI_IOV_RESOURCE_END; i++) {
+ res = &dev->resource[i];
+ if (!res->flags || !res->parent)
+ continue;
+
+ if (!pnv_pci_is_mem_pref_64(res->flags))
+ continue;
+
pci_update_resource(dev, i);
}
pdn->max_vfs -= offset;
+ return 0;
+
+failed:
+ for (; i >= PCI_IOV_RESOURCES; i--) {
+ res = &dev->resource[i];
+ if (!res->flags || !res->parent)
+ continue;
+
+ if (!pnv_pci_is_mem_pref_64(res->flags))
+ continue;
+
+ dev_info(&dev->dev, " Shifting VF BAR %pR to\n", res);
+ size = pci_iov_resource_size(dev, i);
+ res->start += size*(-offset);
+ dev_info(&dev->dev, " %pR\n", res);
+ }
+ return -EBUSY;
}
#endif /* CONFIG_PCI_IOV */
@@ -1480,8 +1520,11 @@ int pnv_pci_sriov_enable(struct pci_dev *pdev, u16 vf_num)
}
/* Do some magic shift */
- if (pdn->m64_per_iov == 1)
- pnv_pci_vf_resource_shift(pdev, pdn->offset);
+ if (pdn->m64_per_iov == 1) {
+ ret = pnv_pci_vf_resource_shift(pdev, pdn->offset);
+ if (ret)
+ goto m64_failed;
+ }
}
/* Setup VF PEs */
The actual IOV BAR range is determined by the start address and the actual size for vf_num VFs BAR. After shifting the IOV BAR, there would be a chance the actual end address exceed the limit and overlap with other devices. This patch adds a check to make sure after shifting, the range will not overlap with other devices. Signed-off-by: Wei Yang <weiyang@linux.vnet.ibm.com> --- arch/powerpc/platforms/powernv/pci-ioda.c | 53 ++++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 5 deletions(-)