In contrast to the commit message of the fixed commit VFs whose parent
PF is not configured are not always isolated, that is put on their own
PCI domain. This is because for VFs to be added to an existing PCI
domain it is enough for that PCI domain to share the same topology ID or
PCHID. Such a matching PCI domain without a parent PF may exist when
a PF from the same PCI card created the domain with the VF being a child
of a different, non accessible, PF. While not causing technical issues
it makes the rules which VFs are isolated inconsistent.
Fix this by explicitly checking that the parent PF exists on the PCI
domain determined by the topology ID or PCHID before registering the VF.
This works because a parent PF which is under control of this Linux
instance must be enabled and configured at the point where its child VFs
appear because otherwise SR-IOV could not have been enabled on the
parent.
Fixes: 25f39d3dcb
("s390/pci: Ignore RID for isolated VFs")
Cc: stable@vger.kernel.org
Reviewed-by: Halil Pasic <pasic@linux.ibm.com>
Signed-off-by: Niklas Schnelle <schnelle@linux.ibm.com>
Signed-off-by: Vasily Gorbik <gor@linux.ibm.com>
127 lines
3.3 KiB
C
127 lines
3.3 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright IBM Corp. 2020
|
|
*
|
|
* Author(s):
|
|
* Niklas Schnelle <schnelle@linux.ibm.com>
|
|
*
|
|
*/
|
|
|
|
#define KMSG_COMPONENT "zpci"
|
|
#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/pci.h>
|
|
|
|
#include "pci_iov.h"
|
|
|
|
static struct resource iov_res = {
|
|
.name = "PCI IOV res",
|
|
.start = 0,
|
|
.end = -1,
|
|
.flags = IORESOURCE_MEM,
|
|
};
|
|
|
|
void zpci_iov_map_resources(struct pci_dev *pdev)
|
|
{
|
|
resource_size_t len;
|
|
int i;
|
|
|
|
for (i = 0; i < PCI_SRIOV_NUM_BARS; i++) {
|
|
int bar = i + PCI_IOV_RESOURCES;
|
|
|
|
len = pci_resource_len(pdev, bar);
|
|
if (!len)
|
|
continue;
|
|
pdev->resource[bar].parent = &iov_res;
|
|
}
|
|
}
|
|
|
|
void zpci_iov_remove_virtfn(struct pci_dev *pdev, int vfn)
|
|
{
|
|
pci_lock_rescan_remove();
|
|
/* Linux' vfid's start at 0 vfn at 1 */
|
|
pci_iov_remove_virtfn(pdev->physfn, vfn - 1);
|
|
pci_unlock_rescan_remove();
|
|
}
|
|
|
|
static int zpci_iov_link_virtfn(struct pci_dev *pdev, struct pci_dev *virtfn, int vfid)
|
|
{
|
|
int rc;
|
|
|
|
rc = pci_iov_sysfs_link(pdev, virtfn, vfid);
|
|
if (rc)
|
|
return rc;
|
|
|
|
virtfn->is_virtfn = 1;
|
|
virtfn->multifunction = 0;
|
|
virtfn->physfn = pci_dev_get(pdev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* zpci_iov_find_parent_pf - Find the parent PF, if any, of the given function
|
|
* @zbus: The bus that the PCI function is on, or would be added on
|
|
* @zdev: The PCI function
|
|
*
|
|
* Finds the parent PF, if it exists and is configured, of the given PCI function
|
|
* and increments its refcount. Th PF is searched for on the provided bus so the
|
|
* caller has to ensure that this is the correct bus to search. This function may
|
|
* be used before adding the PCI function to a zbus.
|
|
*
|
|
* Return: Pointer to the struct pci_dev of the parent PF or NULL if it not
|
|
* found. If the function is not a VF or has no RequesterID information,
|
|
* NULL is returned as well.
|
|
*/
|
|
struct pci_dev *zpci_iov_find_parent_pf(struct zpci_bus *zbus, struct zpci_dev *zdev)
|
|
{
|
|
int i, vfid, devfn, cand_devfn;
|
|
struct pci_dev *pdev;
|
|
|
|
if (!zbus->multifunction)
|
|
return NULL;
|
|
/* Non-VFs and VFs without RID available don't have a parent */
|
|
if (!zdev->vfn || !zdev->rid_available)
|
|
return NULL;
|
|
/* Linux vfid starts at 0 vfn at 1 */
|
|
vfid = zdev->vfn - 1;
|
|
devfn = zdev->rid & ZPCI_RID_MASK_DEVFN;
|
|
/*
|
|
* 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);
|
|
if (!pdev)
|
|
continue;
|
|
cand_devfn = pci_iov_virtfn_devfn(pdev, vfid);
|
|
if (cand_devfn == devfn)
|
|
return pdev;
|
|
/* balance pci_get_slot() */
|
|
pci_dev_put(pdev);
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
int zpci_iov_setup_virtfn(struct zpci_bus *zbus, struct pci_dev *virtfn, int vfn)
|
|
{
|
|
struct zpci_dev *zdev = to_zpci(virtfn);
|
|
struct pci_dev *pdev_pf;
|
|
int rc = 0;
|
|
|
|
pdev_pf = zpci_iov_find_parent_pf(zbus, zdev);
|
|
if (pdev_pf) {
|
|
/* Linux' vfids start at 0 while zdev->vfn starts at 1 */
|
|
rc = zpci_iov_link_virtfn(pdev_pf, virtfn, zdev->vfn - 1);
|
|
pci_dev_put(pdev_pf);
|
|
}
|
|
return rc;
|
|
}
|