fpga: dfl-pci: locate DFLs by PCIe vendor specific capability

A PCIe vendor specific extended capability is introduced by Intel to
specify the start of a number of DFLs.

Signed-off-by: Matthew Gerlach <matthew.gerlach@linux.intel.com>
Signed-off-by: Moritz Fischer <mdf@kernel.org>
Link: https://lore.kernel.org/r/20210107043714.991646-3-mdf@kernel.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Matthew Gerlach 2021-01-06 20:37:08 -08:00 committed by Greg Kroah-Hartman
parent 3e265f836e
commit fa41d10589
2 changed files with 113 additions and 1 deletions

View File

@ -501,6 +501,33 @@ Developer only needs to provide a sub feature driver with matched feature id.
FME Partial Reconfiguration Sub Feature driver (see drivers/fpga/dfl-fme-pr.c) FME Partial Reconfiguration Sub Feature driver (see drivers/fpga/dfl-fme-pr.c)
could be a reference. could be a reference.
Location of DFLs on a PCI Device
===========================
The original method for finding a DFL on a PCI device assumed the start of the
first DFL to offset 0 of bar 0. If the first node of the DFL is an FME,
then further DFLs in the port(s) are specified in FME header registers.
Alternatively, a PCIe vendor specific capability structure can be used to
specify the location of all the DFLs on the device, providing flexibility
for the type of starting node in the DFL. Intel has reserved the
VSEC ID of 0x43 for this purpose. The vendor specific
data begins with a 4 byte vendor specific register for the number of DFLs followed 4 byte
Offset/BIR vendor specific registers for each DFL. Bits 2:0 of Offset/BIR register
indicates the BAR, and bits 31:3 form the 8 byte aligned offset where bits 2:0 are
zero.
+----------------------------+
|31 Number of DFLS 0|
+----------------------------+
|31 Offset 3|2 BIR 0|
+----------------------------+
. . .
+----------------------------+
|31 Offset 3|2 BIR 0|
+----------------------------+
Being able to specify more than one DFL per BAR has been considered, but it
was determined the use case did not provide value. Specifying a single DFL
per BAR simplifies the implementation and allows for extra error checking.
Open discussion Open discussion
=============== ===============

View File

@ -27,6 +27,14 @@
#define DRV_VERSION "0.8" #define DRV_VERSION "0.8"
#define DRV_NAME "dfl-pci" #define DRV_NAME "dfl-pci"
#define PCI_VSEC_ID_INTEL_DFLS 0x43
#define PCI_VNDR_DFLS_CNT 0x8
#define PCI_VNDR_DFLS_RES 0xc
#define PCI_VNDR_DFLS_RES_BAR_MASK GENMASK(2, 0)
#define PCI_VNDR_DFLS_RES_OFF_MASK GENMASK(31, 3)
struct cci_drvdata { struct cci_drvdata {
struct dfl_fpga_cdev *cdev; /* container device */ struct dfl_fpga_cdev *cdev; /* container device */
}; };
@ -119,6 +127,80 @@ static int *cci_pci_create_irq_table(struct pci_dev *pcidev, unsigned int nvec)
return table; return table;
} }
static int find_dfls_by_vsec(struct pci_dev *pcidev, struct dfl_fpga_enum_info *info)
{
u32 bir, offset, vndr_hdr, dfl_cnt, dfl_res;
int dfl_res_off, i, bars, voff = 0;
resource_size_t start, len;
while ((voff = pci_find_next_ext_capability(pcidev, voff, PCI_EXT_CAP_ID_VNDR))) {
vndr_hdr = 0;
pci_read_config_dword(pcidev, voff + PCI_VNDR_HEADER, &vndr_hdr);
if (PCI_VNDR_HEADER_ID(vndr_hdr) == PCI_VSEC_ID_INTEL_DFLS &&
pcidev->vendor == PCI_VENDOR_ID_INTEL)
break;
}
if (!voff) {
dev_dbg(&pcidev->dev, "%s no DFL VSEC found\n", __func__);
return -ENODEV;
}
dfl_cnt = 0;
pci_read_config_dword(pcidev, voff + PCI_VNDR_DFLS_CNT, &dfl_cnt);
if (dfl_cnt > PCI_STD_NUM_BARS) {
dev_err(&pcidev->dev, "%s too many DFLs %d > %d\n",
__func__, dfl_cnt, PCI_STD_NUM_BARS);
return -EINVAL;
}
dfl_res_off = voff + PCI_VNDR_DFLS_RES;
if (dfl_res_off + (dfl_cnt * sizeof(u32)) > PCI_CFG_SPACE_EXP_SIZE) {
dev_err(&pcidev->dev, "%s DFL VSEC too big for PCIe config space\n",
__func__);
return -EINVAL;
}
for (i = 0, bars = 0; i < dfl_cnt; i++, dfl_res_off += sizeof(u32)) {
dfl_res = GENMASK(31, 0);
pci_read_config_dword(pcidev, dfl_res_off, &dfl_res);
bir = dfl_res & PCI_VNDR_DFLS_RES_BAR_MASK;
if (bir >= PCI_STD_NUM_BARS) {
dev_err(&pcidev->dev, "%s bad bir number %d\n",
__func__, bir);
return -EINVAL;
}
if (bars & BIT(bir)) {
dev_err(&pcidev->dev, "%s DFL for BAR %d already specified\n",
__func__, bir);
return -EINVAL;
}
bars |= BIT(bir);
len = pci_resource_len(pcidev, bir);
offset = dfl_res & PCI_VNDR_DFLS_RES_OFF_MASK;
if (offset >= len) {
dev_err(&pcidev->dev, "%s bad offset %u >= %pa\n",
__func__, offset, &len);
return -EINVAL;
}
dev_dbg(&pcidev->dev, "%s BAR %d offset 0x%x\n", __func__, bir, offset);
len -= offset;
start = pci_resource_start(pcidev, bir) + offset;
dfl_fpga_enum_info_add_dfl(info, start, len);
}
return 0;
}
/* default method of finding dfls starting at offset 0 of bar 0 */ /* default method of finding dfls starting at offset 0 of bar 0 */
static int find_dfls_by_default(struct pci_dev *pcidev, static int find_dfls_by_default(struct pci_dev *pcidev,
struct dfl_fpga_enum_info *info) struct dfl_fpga_enum_info *info)
@ -220,7 +302,10 @@ static int cci_enumerate_feature_devs(struct pci_dev *pcidev)
goto irq_free_exit; goto irq_free_exit;
} }
ret = find_dfls_by_default(pcidev, info); ret = find_dfls_by_vsec(pcidev, info);
if (ret == -ENODEV)
ret = find_dfls_by_default(pcidev, info);
if (ret) if (ret)
goto irq_free_exit; goto irq_free_exit;