summaryrefslogtreecommitdiff
path: root/drivers/pci/iov.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/pci/iov.c')
-rw-r--r--drivers/pci/iov.c219
1 files changed, 185 insertions, 34 deletions
diff --git a/drivers/pci/iov.c b/drivers/pci/iov.c
index 952217572113..00784a60ba80 100644
--- a/drivers/pci/iov.c
+++ b/drivers/pci/iov.c
@@ -7,14 +7,19 @@
* Copyright (C) 2009 Intel Corporation, Yu Zhao <yu.zhao@intel.com>
*/
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/log2.h>
#include <linux/pci.h>
+#include <linux/sizes.h>
#include <linux/slab.h>
#include <linux/export.h>
#include <linux/string.h>
#include <linux/delay.h>
+#include <asm/div64.h>
#include "pci.h"
-#define VIRTFN_ID_LEN 16
+#define VIRTFN_ID_LEN 17 /* "virtfn%u\0" for 2^32 - 1 */
int pci_iov_virtfn_bus(struct pci_dev *dev, int vf_id)
{
@@ -41,8 +46,7 @@ int pci_iov_vf_id(struct pci_dev *dev)
return -EINVAL;
pf = pci_physfn(dev);
- return (((dev->bus->number << 8) + dev->devfn) -
- ((pf->bus->number << 8) + pf->devfn + pf->sriov->offset)) /
+ return (pci_dev_id(dev) - (pci_dev_id(pf) + pf->sriov->offset)) /
pf->sriov->stride;
}
EXPORT_SYMBOL_GPL(pci_iov_vf_id);
@@ -151,7 +155,28 @@ 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];
+ return dev->sriov->barsz[pci_resource_num_to_vf_bar(resno)];
+}
+
+void pci_iov_resource_set_size(struct pci_dev *dev, int resno, int size)
+{
+ if (!pci_resource_is_iov(resno)) {
+ pci_warn(dev, "%s is not an IOV resource\n",
+ pci_resource_name(dev, resno));
+ return;
+ }
+
+ resno = pci_resource_num_to_vf_bar(resno);
+ dev->sriov->barsz[resno] = pci_rebar_size_to_bytes(size);
+}
+
+bool pci_iov_is_memory_decoding_enabled(struct pci_dev *dev)
+{
+ u16 cmd;
+
+ pci_read_config_word(dev, dev->sriov->pos + PCI_SRIOV_CTRL, &cmd);
+
+ return cmd & PCI_SRIOV_CTRL_MSE;
}
static void pci_read_vf_config_common(struct pci_dev *virtfn)
@@ -286,23 +311,16 @@ const struct attribute_group sriov_vf_dev_attr_group = {
.is_visible = sriov_vf_attrs_are_visible,
};
-int pci_iov_add_virtfn(struct pci_dev *dev, int id)
+static struct pci_dev *pci_iov_scan_device(struct pci_dev *dev, int id,
+ struct pci_bus *bus)
{
- int i;
- int rc = -ENOMEM;
- u64 size;
- struct pci_dev *virtfn;
- struct resource *res;
struct pci_sriov *iov = dev->sriov;
- struct pci_bus *bus;
-
- bus = virtfn_add_bus(dev->bus, pci_iov_virtfn_bus(dev, id));
- if (!bus)
- goto failed;
+ struct pci_dev *virtfn;
+ int rc;
virtfn = pci_alloc_dev(bus);
if (!virtfn)
- goto failed0;
+ return ERR_PTR(-ENOMEM);
virtfn->devfn = pci_iov_virtfn_devfn(dev, id);
virtfn->vendor = dev->vendor;
@@ -315,21 +333,50 @@ int pci_iov_add_virtfn(struct pci_dev *dev, int id)
pci_read_vf_config_common(virtfn);
rc = pci_setup_device(virtfn);
- if (rc)
- goto failed1;
+ if (rc) {
+ pci_dev_put(dev);
+ pci_bus_put(virtfn->bus);
+ kfree(virtfn);
+ return ERR_PTR(rc);
+ }
+
+ return virtfn;
+}
+
+int pci_iov_add_virtfn(struct pci_dev *dev, int id)
+{
+ struct pci_bus *bus;
+ struct pci_dev *virtfn;
+ struct resource *res;
+ int rc, i;
+ u64 size;
+
+ bus = virtfn_add_bus(dev->bus, pci_iov_virtfn_bus(dev, id));
+ if (!bus) {
+ rc = -ENOMEM;
+ goto failed;
+ }
+
+ virtfn = pci_iov_scan_device(dev, id, bus);
+ if (IS_ERR(virtfn)) {
+ rc = PTR_ERR(virtfn);
+ goto failed0;
+ }
virtfn->dev.parent = dev->dev.parent;
virtfn->multifunction = 0;
for (i = 0; i < PCI_SRIOV_NUM_BARS; i++) {
- res = &dev->resource[i + PCI_IOV_RESOURCES];
+ int idx = pci_resource_num_from_vf_bar(i);
+
+ res = &dev->resource[idx];
if (!res->parent)
continue;
virtfn->resource[i].name = pci_name(virtfn);
virtfn->resource[i].flags = res->flags;
- 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;
+ size = pci_iov_resource_size(dev, idx);
+ resource_set_range(&virtfn->resource[i],
+ res->start + size * id, size);
rc = request_resource(res, &virtfn->resource[i]);
BUG_ON(rc);
}
@@ -582,15 +629,18 @@ static int sriov_add_vfs(struct pci_dev *dev, u16 num_vfs)
if (dev->no_vf_scan)
return 0;
+ pci_lock_rescan_remove();
for (i = 0; i < num_vfs; i++) {
rc = pci_iov_add_virtfn(dev, i);
if (rc)
goto failed;
}
+ pci_unlock_rescan_remove();
return 0;
failed:
while (i--)
pci_iov_remove_virtfn(dev, i);
+ pci_unlock_rescan_remove();
return rc;
}
@@ -624,8 +674,13 @@ 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[i + PCI_IOV_RESOURCES];
+ int idx = pci_resource_num_from_vf_bar(i);
+ resource_size_t vf_bar_sz = pci_iov_resource_size(dev, idx);
+
+ bars |= (1 << idx);
+ res = &dev->resource[idx];
+ if (vf_bar_sz * nr_virtfn > resource_size(res))
+ continue;
if (res->parent)
nres++;
}
@@ -710,8 +765,10 @@ static void sriov_del_vfs(struct pci_dev *dev)
struct pci_sriov *iov = dev->sriov;
int i;
+ pci_lock_rescan_remove();
for (i = 0; i < iov->num_VFs; i++)
pci_iov_remove_virtfn(dev, i);
+ pci_unlock_rescan_remove();
}
static void sriov_disable(struct pci_dev *dev)
@@ -746,7 +803,9 @@ static int sriov_init(struct pci_dev *dev, int pos)
u16 ctrl, total;
struct pci_sriov *iov;
struct resource *res;
+ const char *res_name;
struct pci_dev *pdev;
+ u32 sriovbars[PCI_SRIOV_NUM_BARS];
pci_read_config_word(dev, pos + PCI_SRIOV_CTRL, &ctrl);
if (ctrl & PCI_SRIOV_CTRL_VFE) {
@@ -783,9 +842,17 @@ found:
if (!iov)
return -ENOMEM;
+ /* Sizing SR-IOV BARs with VF Enable cleared - no decode */
+ __pci_size_stdbars(dev, PCI_SRIOV_NUM_BARS,
+ pos + PCI_SRIOV_BAR, sriovbars);
+
nres = 0;
for (i = 0; i < PCI_SRIOV_NUM_BARS; i++) {
- res = &dev->resource[i + PCI_IOV_RESOURCES];
+ int idx = pci_resource_num_from_vf_bar(i);
+
+ res = &dev->resource[idx];
+ res_name = pci_resource_name(dev, idx);
+
/*
* If it is already FIXED, don't change it, something
* (perhaps EA or header fixups) wants it this way.
@@ -794,7 +861,8 @@ found:
bar64 = (res->flags & IORESOURCE_MEM_64) ? 1 : 0;
else
bar64 = __pci_read_base(dev, pci_bar_unknown, res,
- pos + PCI_SRIOV_BAR + i * 4);
+ pos + PCI_SRIOV_BAR + i * 4,
+ &sriovbars[i]);
if (!res->flags)
continue;
if (resource_size(res) & (PAGE_SIZE - 1)) {
@@ -802,9 +870,9 @@ found:
goto failed;
}
iov->barsz[i] = resource_size(res);
- res->end = res->start + resource_size(res) * total - 1;
- pci_info(dev, "VF(n) BAR%d space: %pR (contains BAR%d for %d VFs)\n",
- i, res, i, total);
+ resource_set_size(res, resource_size(res) * total);
+ pci_info(dev, "%s %pR: contains BAR %d for %d VFs\n",
+ res_name, res, i, total);
i += bar64;
nres++;
}
@@ -822,6 +890,7 @@ found:
pci_read_config_byte(dev, pos + PCI_SRIOV_FUNC_LINK, &iov->link);
if (pci_pcie_type(dev) == PCI_EXP_TYPE_RC_END)
iov->link = PCI_DEVFN(PCI_SLOT(dev->devfn), iov->link);
+ iov->vf_rebar_cap = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_VF_REBAR);
if (pdev)
iov->dev = pci_dev_get(pdev);
@@ -841,7 +910,7 @@ fail_max_buses:
dev->is_physfn = 0;
failed:
for (i = 0; i < PCI_SRIOV_NUM_BARS; i++) {
- res = &dev->resource[i + PCI_IOV_RESOURCES];
+ res = &dev->resource[pci_resource_num_from_vf_bar(i)];
res->flags = 0;
}
@@ -860,6 +929,30 @@ static void sriov_release(struct pci_dev *dev)
dev->sriov = NULL;
}
+static void sriov_restore_vf_rebar_state(struct pci_dev *dev)
+{
+ unsigned int pos, nbars, i;
+ u32 ctrl;
+
+ pos = pci_iov_vf_rebar_cap(dev);
+ if (!pos)
+ return;
+
+ pci_read_config_dword(dev, pos + PCI_VF_REBAR_CTRL, &ctrl);
+ nbars = FIELD_GET(PCI_VF_REBAR_CTRL_NBAR_MASK, ctrl);
+
+ for (i = 0; i < nbars; i++, pos += 8) {
+ int bar_idx, size;
+
+ pci_read_config_dword(dev, pos + PCI_VF_REBAR_CTRL, &ctrl);
+ bar_idx = FIELD_GET(PCI_VF_REBAR_CTRL_BAR_IDX, ctrl);
+ size = pci_rebar_bytes_to_size(dev->sriov->barsz[bar_idx]);
+ ctrl &= ~PCI_VF_REBAR_CTRL_BAR_SIZE;
+ ctrl |= FIELD_PREP(PCI_VF_REBAR_CTRL_BAR_SIZE, size);
+ pci_write_config_dword(dev, pos + PCI_VF_REBAR_CTRL, ctrl);
+ }
+}
+
static void sriov_restore_state(struct pci_dev *dev)
{
int i;
@@ -879,7 +972,7 @@ static void sriov_restore_state(struct pci_dev *dev)
pci_write_config_word(dev, iov->pos + PCI_SRIOV_CTRL, ctrl);
for (i = 0; i < PCI_SRIOV_NUM_BARS; i++)
- pci_update_resource(dev, i + PCI_IOV_RESOURCES);
+ pci_update_resource(dev, pci_resource_num_from_vf_bar(i));
pci_write_config_dword(dev, iov->pos + PCI_SRIOV_SYS_PGSIZE, iov->pgsz);
pci_iov_set_numvfs(dev, iov->num_VFs);
@@ -944,8 +1037,8 @@ void pci_iov_remove(struct pci_dev *dev)
void pci_iov_update_resource(struct pci_dev *dev, int resno)
{
struct pci_sriov *iov = dev->is_physfn ? dev->sriov : NULL;
- struct resource *res = dev->resource + resno;
- int vf_bar = resno - PCI_IOV_RESOURCES;
+ struct resource *res = pci_resource_n(dev, resno);
+ int vf_bar = pci_resource_num_to_vf_bar(resno);
struct pci_bus_region region;
u16 cmd;
u32 new;
@@ -1019,8 +1112,10 @@ resource_size_t pci_sriov_resource_alignment(struct pci_dev *dev, int resno)
*/
void pci_restore_iov_state(struct pci_dev *dev)
{
- if (dev->is_physfn)
+ if (dev->is_physfn) {
+ sriov_restore_vf_rebar_state(dev);
sriov_restore_state(dev);
+ }
}
/**
@@ -1227,3 +1322,59 @@ int pci_sriov_configure_simple(struct pci_dev *dev, int nr_virtfn)
return nr_virtfn;
}
EXPORT_SYMBOL_GPL(pci_sriov_configure_simple);
+
+/**
+ * pci_iov_vf_bar_set_size - set a new size for a VF BAR
+ * @dev: the PCI device
+ * @resno: the resource number
+ * @size: new size as defined in the spec (0=1MB, 31=128TB)
+ *
+ * Set the new size of a VF BAR that supports VF resizable BAR capability.
+ * Unlike pci_resize_resource(), this does not cause the resource that
+ * reserves the MMIO space (originally up to total_VFs) to be resized, which
+ * means that following calls to pci_enable_sriov() can fail if the resources
+ * no longer fit.
+ *
+ * Return: 0 on success, or negative on failure.
+ */
+int pci_iov_vf_bar_set_size(struct pci_dev *dev, int resno, int size)
+{
+ if (!pci_resource_is_iov(resno))
+ return -EINVAL;
+
+ if (pci_iov_is_memory_decoding_enabled(dev))
+ return -EBUSY;
+
+ if (!pci_rebar_size_supported(dev, resno, size))
+ return -EINVAL;
+
+ return pci_rebar_set_size(dev, resno, size);
+}
+EXPORT_SYMBOL_GPL(pci_iov_vf_bar_set_size);
+
+/**
+ * pci_iov_vf_bar_get_sizes - get VF BAR sizes allowing to create up to num_vfs
+ * @dev: the PCI device
+ * @resno: the resource number
+ * @num_vfs: number of VFs
+ *
+ * Get the sizes of a VF resizable BAR that can accommodate @num_vfs within
+ * the currently assigned size of the resource @resno.
+ *
+ * Return: A bitmask of sizes in format defined in the spec (bit 0=1MB,
+ * bit 31=128TB).
+ */
+u32 pci_iov_vf_bar_get_sizes(struct pci_dev *dev, int resno, int num_vfs)
+{
+ u64 vf_len = pci_resource_len(dev, resno);
+ u64 sizes;
+
+ if (!num_vfs)
+ return 0;
+
+ do_div(vf_len, num_vfs);
+ sizes = (roundup_pow_of_two(vf_len + 1) - 1) >> ilog2(SZ_1M);
+
+ return sizes & pci_rebar_get_possible_sizes(dev, resno);
+}
+EXPORT_SYMBOL_GPL(pci_iov_vf_bar_get_sizes);