| 1 | // SPDX-License-Identifier: GPL-2.0 |
| 2 | /* |
| 3 | * Copyright IBM Corp. 2020 |
| 4 | * |
| 5 | * Author(s): |
| 6 | * Niklas Schnelle <schnelle@linux.ibm.com> |
| 7 | * |
| 8 | */ |
| 9 | |
| 10 | #define pr_fmt(fmt) "zpci: " fmt |
| 11 | |
| 12 | #include <linux/kernel.h> |
| 13 | #include <linux/pci.h> |
| 14 | |
| 15 | #include "pci_iov.h" |
| 16 | |
| 17 | static struct resource iov_res = { |
| 18 | .name = "PCI IOV res" , |
| 19 | .start = 0, |
| 20 | .end = -1, |
| 21 | .flags = IORESOURCE_MEM, |
| 22 | }; |
| 23 | |
| 24 | void zpci_iov_map_resources(struct pci_dev *pdev) |
| 25 | { |
| 26 | resource_size_t len; |
| 27 | int i; |
| 28 | |
| 29 | for (i = 0; i < PCI_SRIOV_NUM_BARS; i++) { |
| 30 | int bar = i + PCI_IOV_RESOURCES; |
| 31 | |
| 32 | len = pci_resource_len(pdev, bar); |
| 33 | if (!len) |
| 34 | continue; |
| 35 | pdev->resource[bar].parent = &iov_res; |
| 36 | } |
| 37 | } |
| 38 | |
| 39 | void zpci_iov_remove_virtfn(struct pci_dev *pdev, int vfn) |
| 40 | { |
| 41 | pci_lock_rescan_remove(); |
| 42 | /* Linux' vfid's start at 0 vfn at 1 */ |
| 43 | pci_iov_remove_virtfn(dev: pdev->physfn, id: vfn - 1); |
| 44 | pci_unlock_rescan_remove(); |
| 45 | } |
| 46 | |
| 47 | static int zpci_iov_link_virtfn(struct pci_dev *pdev, struct pci_dev *virtfn, int vfid) |
| 48 | { |
| 49 | int rc; |
| 50 | |
| 51 | rc = pci_iov_sysfs_link(dev: pdev, virtfn, id: vfid); |
| 52 | if (rc) |
| 53 | return rc; |
| 54 | |
| 55 | virtfn->is_virtfn = 1; |
| 56 | virtfn->multifunction = 0; |
| 57 | virtfn->physfn = pci_dev_get(dev: pdev); |
| 58 | |
| 59 | return 0; |
| 60 | } |
| 61 | |
| 62 | /** |
| 63 | * zpci_iov_find_parent_pf - Find the parent PF, if any, of the given function |
| 64 | * @zbus: The bus that the PCI function is on, or would be added on |
| 65 | * @zdev: The PCI function |
| 66 | * |
| 67 | * Finds the parent PF, if it exists and is configured, of the given PCI function |
| 68 | * and increments its refcount. Th PF is searched for on the provided bus so the |
| 69 | * caller has to ensure that this is the correct bus to search. This function may |
| 70 | * be used before adding the PCI function to a zbus. |
| 71 | * |
| 72 | * Return: Pointer to the struct pci_dev of the parent PF or NULL if it not |
| 73 | * found. If the function is not a VF or has no RequesterID information, |
| 74 | * NULL is returned as well. |
| 75 | */ |
| 76 | struct pci_dev *zpci_iov_find_parent_pf(struct zpci_bus *zbus, struct zpci_dev *zdev) |
| 77 | { |
| 78 | int i, vfid, devfn, cand_devfn; |
| 79 | struct pci_dev *pdev; |
| 80 | |
| 81 | if (!zbus->multifunction) |
| 82 | return NULL; |
| 83 | /* Non-VFs and VFs without RID available don't have a parent */ |
| 84 | if (!zdev->vfn || !zdev->rid_available) |
| 85 | return NULL; |
| 86 | /* Linux vfid starts at 0 vfn at 1 */ |
| 87 | vfid = zdev->vfn - 1; |
| 88 | devfn = zdev->rid & ZPCI_RID_MASK_DEVFN; |
| 89 | /* |
| 90 | * If the parent PF for the given VF is also configured in the |
| 91 | * instance, it must be on the same zbus. |
| 92 | * We can then identify the parent PF by checking what |
| 93 | * devfn the VF would have if it belonged to that PF using the PF's |
| 94 | * stride and offset. Only if this candidate devfn matches the |
| 95 | * actual devfn will we link both functions. |
| 96 | */ |
| 97 | for (i = 0; i < ZPCI_FUNCTIONS_PER_BUS; i++) { |
| 98 | zdev = zbus->function[i]; |
| 99 | if (zdev && zdev->is_physfn) { |
| 100 | pdev = pci_get_slot(bus: zbus->bus, devfn: zdev->devfn); |
| 101 | if (!pdev) |
| 102 | continue; |
| 103 | cand_devfn = pci_iov_virtfn_devfn(dev: pdev, id: vfid); |
| 104 | if (cand_devfn == devfn) |
| 105 | return pdev; |
| 106 | /* balance pci_get_slot() */ |
| 107 | pci_dev_put(dev: pdev); |
| 108 | } |
| 109 | } |
| 110 | return NULL; |
| 111 | } |
| 112 | |
| 113 | int zpci_iov_setup_virtfn(struct zpci_bus *zbus, struct pci_dev *virtfn, int vfn) |
| 114 | { |
| 115 | struct zpci_dev *zdev = to_zpci(virtfn); |
| 116 | struct pci_dev *pdev_pf; |
| 117 | int rc = 0; |
| 118 | |
| 119 | pdev_pf = zpci_iov_find_parent_pf(zbus, zdev); |
| 120 | if (pdev_pf) { |
| 121 | /* Linux' vfids start at 0 while zdev->vfn starts at 1 */ |
| 122 | rc = zpci_iov_link_virtfn(pdev: pdev_pf, virtfn, vfid: zdev->vfn - 1); |
| 123 | pci_dev_put(dev: pdev_pf); |
| 124 | } |
| 125 | return rc; |
| 126 | } |
| 127 | |