From 3c936e0ec0e412a3ce6072883da8682fb723d573 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Mon, 27 Jan 2025 17:12:42 +0100 Subject: PCI: endpoint: pci-epf-test: Handle endianness properly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The struct pci_epf_test_reg is the actual data in pci-epf-test's test_reg BAR (usually BAR0), which the host uses to send commands (etc.), and which pci-epf-test uses to send back status codes. pci-epf-test currently reads and writes this data without any endianness conversion functions, which means that pci-epf-test is completely broken on big-endian endpoint systems. PCI devices are inherently little-endian, and the data stored in the PCI BARs should be in little-endian. Use endianness conversion functions when reading and writing data to struct pci_epf_test_reg so that pci-epf-test will behave correctly on big-endian endpoint systems. Fixes: 349e7a85b25f ("PCI: endpoint: functions: Add an EP function to test PCI") Reviewed-by: Frank Li Reviewed-by: Manivannan Sadhasivam Signed-off-by: Niklas Cassel Link: https://lore.kernel.org/r/20250127161242.104651-2-cassel@kernel.org Signed-off-by: Manivannan Sadhasivam Signed-off-by: Krzysztof Wilczyński --- drivers/pci/endpoint/functions/pci-epf-test.c | 126 +++++++++++++++----------- 1 file changed, 73 insertions(+), 53 deletions(-) diff --git a/drivers/pci/endpoint/functions/pci-epf-test.c b/drivers/pci/endpoint/functions/pci-epf-test.c index b94e205ae10b..2409787cf56d 100644 --- a/drivers/pci/endpoint/functions/pci-epf-test.c +++ b/drivers/pci/endpoint/functions/pci-epf-test.c @@ -66,17 +66,17 @@ struct pci_epf_test { }; struct pci_epf_test_reg { - u32 magic; - u32 command; - u32 status; - u64 src_addr; - u64 dst_addr; - u32 size; - u32 checksum; - u32 irq_type; - u32 irq_number; - u32 flags; - u32 caps; + __le32 magic; + __le32 command; + __le32 status; + __le64 src_addr; + __le64 dst_addr; + __le32 size; + __le32 checksum; + __le32 irq_type; + __le32 irq_number; + __le32 flags; + __le32 caps; } __packed; static struct pci_epf_header test_header = { @@ -324,13 +324,17 @@ static void pci_epf_test_copy(struct pci_epf_test *epf_test, struct pci_epc *epc = epf->epc; struct device *dev = &epf->dev; struct pci_epc_map src_map, dst_map; - u64 src_addr = reg->src_addr; - u64 dst_addr = reg->dst_addr; - size_t copy_size = reg->size; + u64 src_addr = le64_to_cpu(reg->src_addr); + u64 dst_addr = le64_to_cpu(reg->dst_addr); + size_t orig_size, copy_size; ssize_t map_size = 0; + u32 flags = le32_to_cpu(reg->flags); + u32 status = 0; void *copy_buf = NULL, *buf; - if (reg->flags & FLAG_USE_DMA) { + orig_size = copy_size = le32_to_cpu(reg->size); + + if (flags & FLAG_USE_DMA) { if (!dma_has_cap(DMA_MEMCPY, epf_test->dma_chan_tx->device->cap_mask)) { dev_err(dev, "DMA controller doesn't support MEMCPY\n"); ret = -EINVAL; @@ -350,7 +354,7 @@ static void pci_epf_test_copy(struct pci_epf_test *epf_test, src_addr, copy_size, &src_map); if (ret) { dev_err(dev, "Failed to map source address\n"); - reg->status = STATUS_SRC_ADDR_INVALID; + status = STATUS_SRC_ADDR_INVALID; goto free_buf; } @@ -358,7 +362,7 @@ static void pci_epf_test_copy(struct pci_epf_test *epf_test, dst_addr, copy_size, &dst_map); if (ret) { dev_err(dev, "Failed to map destination address\n"); - reg->status = STATUS_DST_ADDR_INVALID; + status = STATUS_DST_ADDR_INVALID; pci_epc_mem_unmap(epc, epf->func_no, epf->vfunc_no, &src_map); goto free_buf; @@ -367,7 +371,7 @@ static void pci_epf_test_copy(struct pci_epf_test *epf_test, map_size = min_t(size_t, dst_map.pci_size, src_map.pci_size); ktime_get_ts64(&start); - if (reg->flags & FLAG_USE_DMA) { + if (flags & FLAG_USE_DMA) { ret = pci_epf_test_data_transfer(epf_test, dst_map.phys_addr, src_map.phys_addr, map_size, 0, DMA_MEM_TO_MEM); @@ -391,8 +395,8 @@ static void pci_epf_test_copy(struct pci_epf_test *epf_test, map_size = 0; } - pci_epf_test_print_rate(epf_test, "COPY", reg->size, &start, - &end, reg->flags & FLAG_USE_DMA); + pci_epf_test_print_rate(epf_test, "COPY", orig_size, &start, &end, + flags & FLAG_USE_DMA); unmap: if (map_size) { @@ -405,9 +409,10 @@ free_buf: set_status: if (!ret) - reg->status |= STATUS_COPY_SUCCESS; + status |= STATUS_COPY_SUCCESS; else - reg->status |= STATUS_COPY_FAIL; + status |= STATUS_COPY_FAIL; + reg->status = cpu_to_le32(status); } static void pci_epf_test_read(struct pci_epf_test *epf_test, @@ -423,9 +428,14 @@ static void pci_epf_test_read(struct pci_epf_test *epf_test, struct pci_epc *epc = epf->epc; struct device *dev = &epf->dev; struct device *dma_dev = epf->epc->dev.parent; - u64 src_addr = reg->src_addr; - size_t src_size = reg->size; + u64 src_addr = le64_to_cpu(reg->src_addr); + size_t orig_size, src_size; ssize_t map_size = 0; + u32 flags = le32_to_cpu(reg->flags); + u32 checksum = le32_to_cpu(reg->checksum); + u32 status = 0; + + orig_size = src_size = le32_to_cpu(reg->size); src_buf = kzalloc(src_size, GFP_KERNEL); if (!src_buf) { @@ -439,12 +449,12 @@ static void pci_epf_test_read(struct pci_epf_test *epf_test, src_addr, src_size, &map); if (ret) { dev_err(dev, "Failed to map address\n"); - reg->status = STATUS_SRC_ADDR_INVALID; + status = STATUS_SRC_ADDR_INVALID; goto free_buf; } map_size = map.pci_size; - if (reg->flags & FLAG_USE_DMA) { + if (flags & FLAG_USE_DMA) { dst_phys_addr = dma_map_single(dma_dev, buf, map_size, DMA_FROM_DEVICE); if (dma_mapping_error(dma_dev, dst_phys_addr)) { @@ -481,11 +491,11 @@ static void pci_epf_test_read(struct pci_epf_test *epf_test, map_size = 0; } - pci_epf_test_print_rate(epf_test, "READ", reg->size, &start, - &end, reg->flags & FLAG_USE_DMA); + pci_epf_test_print_rate(epf_test, "READ", orig_size, &start, &end, + flags & FLAG_USE_DMA); - crc32 = crc32_le(~0, src_buf, reg->size); - if (crc32 != reg->checksum) + crc32 = crc32_le(~0, src_buf, orig_size); + if (crc32 != checksum) ret = -EIO; unmap: @@ -497,9 +507,10 @@ free_buf: set_status: if (!ret) - reg->status |= STATUS_READ_SUCCESS; + status |= STATUS_READ_SUCCESS; else - reg->status |= STATUS_READ_FAIL; + status |= STATUS_READ_FAIL; + reg->status = cpu_to_le32(status); } static void pci_epf_test_write(struct pci_epf_test *epf_test, @@ -514,9 +525,13 @@ static void pci_epf_test_write(struct pci_epf_test *epf_test, struct pci_epc *epc = epf->epc; struct device *dev = &epf->dev; struct device *dma_dev = epf->epc->dev.parent; - u64 dst_addr = reg->dst_addr; - size_t dst_size = reg->size; + u64 dst_addr = le64_to_cpu(reg->dst_addr); + size_t orig_size, dst_size; ssize_t map_size = 0; + u32 flags = le32_to_cpu(reg->flags); + u32 status = 0; + + orig_size = dst_size = le32_to_cpu(reg->size); dst_buf = kzalloc(dst_size, GFP_KERNEL); if (!dst_buf) { @@ -524,7 +539,7 @@ static void pci_epf_test_write(struct pci_epf_test *epf_test, goto set_status; } get_random_bytes(dst_buf, dst_size); - reg->checksum = crc32_le(~0, dst_buf, dst_size); + reg->checksum = cpu_to_le32(crc32_le(~0, dst_buf, dst_size)); buf = dst_buf; while (dst_size) { @@ -532,12 +547,12 @@ static void pci_epf_test_write(struct pci_epf_test *epf_test, dst_addr, dst_size, &map); if (ret) { dev_err(dev, "Failed to map address\n"); - reg->status = STATUS_DST_ADDR_INVALID; + status = STATUS_DST_ADDR_INVALID; goto free_buf; } map_size = map.pci_size; - if (reg->flags & FLAG_USE_DMA) { + if (flags & FLAG_USE_DMA) { src_phys_addr = dma_map_single(dma_dev, buf, map_size, DMA_TO_DEVICE); if (dma_mapping_error(dma_dev, src_phys_addr)) { @@ -576,8 +591,8 @@ static void pci_epf_test_write(struct pci_epf_test *epf_test, map_size = 0; } - pci_epf_test_print_rate(epf_test, "WRITE", reg->size, &start, - &end, reg->flags & FLAG_USE_DMA); + pci_epf_test_print_rate(epf_test, "WRITE", orig_size, &start, &end, + flags & FLAG_USE_DMA); /* * wait 1ms inorder for the write to complete. Without this delay L3 @@ -594,9 +609,10 @@ free_buf: set_status: if (!ret) - reg->status |= STATUS_WRITE_SUCCESS; + status |= STATUS_WRITE_SUCCESS; else - reg->status |= STATUS_WRITE_FAIL; + status |= STATUS_WRITE_FAIL; + reg->status = cpu_to_le32(status); } static void pci_epf_test_raise_irq(struct pci_epf_test *epf_test, @@ -605,39 +621,42 @@ static void pci_epf_test_raise_irq(struct pci_epf_test *epf_test, struct pci_epf *epf = epf_test->epf; struct device *dev = &epf->dev; struct pci_epc *epc = epf->epc; - u32 status = reg->status | STATUS_IRQ_RAISED; + u32 status = le32_to_cpu(reg->status); + u32 irq_number = le32_to_cpu(reg->irq_number); + u32 irq_type = le32_to_cpu(reg->irq_type); int count; /* * Set the status before raising the IRQ to ensure that the host sees * the updated value when it gets the IRQ. */ - WRITE_ONCE(reg->status, status); + status |= STATUS_IRQ_RAISED; + WRITE_ONCE(reg->status, cpu_to_le32(status)); - switch (reg->irq_type) { + switch (irq_type) { case IRQ_TYPE_INTX: pci_epc_raise_irq(epc, epf->func_no, epf->vfunc_no, PCI_IRQ_INTX, 0); break; case IRQ_TYPE_MSI: count = pci_epc_get_msi(epc, epf->func_no, epf->vfunc_no); - if (reg->irq_number > count || count <= 0) { + if (irq_number > count || count <= 0) { dev_err(dev, "Invalid MSI IRQ number %d / %d\n", - reg->irq_number, count); + irq_number, count); return; } pci_epc_raise_irq(epc, epf->func_no, epf->vfunc_no, - PCI_IRQ_MSI, reg->irq_number); + PCI_IRQ_MSI, irq_number); break; case IRQ_TYPE_MSIX: count = pci_epc_get_msix(epc, epf->func_no, epf->vfunc_no); - if (reg->irq_number > count || count <= 0) { + if (irq_number > count || count <= 0) { dev_err(dev, "Invalid MSIX IRQ number %d / %d\n", - reg->irq_number, count); + irq_number, count); return; } pci_epc_raise_irq(epc, epf->func_no, epf->vfunc_no, - PCI_IRQ_MSIX, reg->irq_number); + PCI_IRQ_MSIX, irq_number); break; default: dev_err(dev, "Failed to raise IRQ, unknown type\n"); @@ -654,21 +673,22 @@ static void pci_epf_test_cmd_handler(struct work_struct *work) struct device *dev = &epf->dev; enum pci_barno test_reg_bar = epf_test->test_reg_bar; struct pci_epf_test_reg *reg = epf_test->reg[test_reg_bar]; + u32 irq_type = le32_to_cpu(reg->irq_type); - command = READ_ONCE(reg->command); + command = le32_to_cpu(READ_ONCE(reg->command)); if (!command) goto reset_handler; WRITE_ONCE(reg->command, 0); WRITE_ONCE(reg->status, 0); - if ((READ_ONCE(reg->flags) & FLAG_USE_DMA) && + if ((le32_to_cpu(READ_ONCE(reg->flags)) & FLAG_USE_DMA) && !epf_test->dma_supported) { dev_err(dev, "Cannot transfer data using DMA\n"); goto reset_handler; } - if (reg->irq_type > IRQ_TYPE_MSIX) { + if (irq_type > IRQ_TYPE_MSIX) { dev_err(dev, "Failed to detect IRQ type\n"); goto reset_handler; } -- cgit v1.2.3 From 52132f3a63b33fd38ceef07392ed176db84d579f Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Fri, 31 Jan 2025 19:29:50 +0100 Subject: PCI: endpoint: Allow EPF drivers to configure the size of Resizable BARs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A resizable BAR is different from a normal BAR in a few ways: - The minimum size of a resizable BAR is 1 MB. - Each BAR that is resizable has a Capability and Control register in the Resizable BAR Capability structure. These registers contain the supported sizes and the currently selected size of a resizable BAR. The supported sizes is a bitmap of the supported sizes. The selected size is a single value that is equal to one of the supported sizes. A resizable BAR thus has to be configured differently than a BAR_PROGRAMMABLE BAR, which usually sets the BAR size/mask in a vendor specific way. The PCI endpoint framework currently does not support resizable BARs. Add a BAR type BAR_RESIZABLE, so that an EPC driver can support resizable BARs properly. Note that the pci_epc_set_bar() API takes a struct pci_epf_bar which tells the EPC driver how it wants to configure the BAR. struct pci_epf_bar only has a single size struct member. This means that an EPC driver will only be able to set a single supported size. This is perfectly fine, as we do not need the complexity of allowing a host to change the size of the BAR. If someone ever wants to support resizing a resizable BAR, the pci_epc_set_bar() API can be extended in the future. With these changes, we allow an EPF driver to configure the size of Resizable BARs, rather than forcing them to a 1 MB size. Signed-off-by: Niklas Cassel Reviewed-by: Manivannan Sadhasivam Link: https://lore.kernel.org/r/20250131182949.465530-10-cassel@kernel.org Signed-off-by: Manivannan Sadhasivam [kwilczynski: commit log] Signed-off-by: Krzysztof Wilczyński --- drivers/pci/endpoint/pci-epc-core.c | 4 ++++ drivers/pci/endpoint/pci-epf-core.c | 4 ++++ include/linux/pci-epc.h | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/drivers/pci/endpoint/pci-epc-core.c b/drivers/pci/endpoint/pci-epc-core.c index 9e9ca5f8e8f8..10dfc716328e 100644 --- a/drivers/pci/endpoint/pci-epc-core.c +++ b/drivers/pci/endpoint/pci-epc-core.c @@ -609,6 +609,10 @@ int pci_epc_set_bar(struct pci_epc *epc, u8 func_no, u8 vfunc_no, if (!epc_features) return -EINVAL; + if (epc_features->bar[bar].type == BAR_RESIZABLE && + (epf_bar->size < SZ_1M || (u64)epf_bar->size > (SZ_128G * 1024))) + return -EINVAL; + if (epc_features->bar[bar].type == BAR_FIXED && (epc_features->bar[bar].fixed_size != epf_bar->size)) return -EINVAL; diff --git a/drivers/pci/endpoint/pci-epf-core.c b/drivers/pci/endpoint/pci-epf-core.c index 50bc2892a36c..394395c7f8de 100644 --- a/drivers/pci/endpoint/pci-epf-core.c +++ b/drivers/pci/endpoint/pci-epf-core.c @@ -274,6 +274,10 @@ void *pci_epf_alloc_space(struct pci_epf *epf, size_t size, enum pci_barno bar, if (size < 128) size = 128; + /* According to PCIe base spec, min size for a resizable BAR is 1 MB. */ + if (epc_features->bar[bar].type == BAR_RESIZABLE && size < SZ_1M) + size = SZ_1M; + if (epc_features->bar[bar].type == BAR_FIXED && bar_fixed_size) { if (size > bar_fixed_size) { dev_err(&epf->dev, diff --git a/include/linux/pci-epc.h b/include/linux/pci-epc.h index e818e3fdcded..91ce39dc0fd4 100644 --- a/include/linux/pci-epc.h +++ b/include/linux/pci-epc.h @@ -188,11 +188,15 @@ struct pci_epc { * enum pci_epc_bar_type - configurability of endpoint BAR * @BAR_PROGRAMMABLE: The BAR mask can be configured by the EPC. * @BAR_FIXED: The BAR mask is fixed by the hardware. + * @BAR_RESIZABLE: The BAR implements the PCI-SIG Resizable BAR Capability. + * NOTE: An EPC driver can currently only set a single supported + * size. * @BAR_RESERVED: The BAR should not be touched by an EPF driver. */ enum pci_epc_bar_type { BAR_PROGRAMMABLE = 0, BAR_FIXED, + BAR_RESIZABLE, BAR_RESERVED, }; -- cgit v1.2.3 From 4eb208424c9c49dc3298a45e8db7cf43fdf15600 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Fri, 31 Jan 2025 19:29:51 +0100 Subject: PCI: endpoint: Add pci_epc_bar_size_to_rebar_cap() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a helper function to convert a size to the representation used by the Resizable BAR Capability Register. Signed-off-by: Niklas Cassel Reviewed-by: Manivannan Sadhasivam Link: https://lore.kernel.org/r/20250131182949.465530-11-cassel@kernel.org [mani: squashed the change that added PCIe spec reference to comments from https://lore.kernel.org/linux-pci/20250219171454.2903059-2-cassel@kernel.org] Signed-off-by: Manivannan Sadhasivam Signed-off-by: Krzysztof Wilczyński --- drivers/pci/endpoint/pci-epc-core.c | 27 +++++++++++++++++++++++++++ include/linux/pci-epc.h | 1 + 2 files changed, 28 insertions(+) diff --git a/drivers/pci/endpoint/pci-epc-core.c b/drivers/pci/endpoint/pci-epc-core.c index 10dfc716328e..88cb426d3aec 100644 --- a/drivers/pci/endpoint/pci-epc-core.c +++ b/drivers/pci/endpoint/pci-epc-core.c @@ -638,6 +638,33 @@ int pci_epc_set_bar(struct pci_epc *epc, u8 func_no, u8 vfunc_no, } EXPORT_SYMBOL_GPL(pci_epc_set_bar); +/** + * pci_epc_bar_size_to_rebar_cap() - convert a size to the representation used + * by the Resizable BAR Capability Register + * @size: the size to convert + * @cap: where to store the result + * + * Returns 0 on success and a negative error code in case of error. + */ +int pci_epc_bar_size_to_rebar_cap(size_t size, u32 *cap) +{ + /* + * As per PCIe r6.0, sec 7.8.6.2, min size for a resizable BAR is 1 MB, + * thus disallow a requested BAR size smaller than 1 MB. + * Disallow a requested BAR size larger than 128 TB. + */ + if (size < SZ_1M || (u64)size > (SZ_128G * 1024)) + return -EINVAL; + + *cap = ilog2(size) - ilog2(SZ_1M); + + /* Sizes in REBAR_CAP start at BIT(4). */ + *cap = BIT(*cap + 4); + + return 0; +} +EXPORT_SYMBOL_GPL(pci_epc_bar_size_to_rebar_cap); + /** * pci_epc_write_header() - write standard configuration header * @epc: the EPC device to which the configuration header should be written diff --git a/include/linux/pci-epc.h b/include/linux/pci-epc.h index 91ce39dc0fd4..713348322dea 100644 --- a/include/linux/pci-epc.h +++ b/include/linux/pci-epc.h @@ -275,6 +275,7 @@ void pci_epc_remove_epf(struct pci_epc *epc, struct pci_epf *epf, enum pci_epc_interface_type type); int pci_epc_write_header(struct pci_epc *epc, u8 func_no, u8 vfunc_no, struct pci_epf_header *hdr); +int pci_epc_bar_size_to_rebar_cap(size_t size, u32 *cap); int pci_epc_set_bar(struct pci_epc *epc, u8 func_no, u8 vfunc_no, struct pci_epf_bar *epf_bar); void pci_epc_clear_bar(struct pci_epc *epc, u8 func_no, u8 vfunc_no, -- cgit v1.2.3 From 30a172db9fa46add11883d66c4f34e194b741528 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Fri, 31 Jan 2025 19:29:52 +0100 Subject: PCI: dwc: ep: Move dw_pcie_ep_find_ext_capability() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move dw_pcie_ep_find_ext_capability() so that it is located next to dw_pcie_ep_find_capability(). Additionally, a follow-up commit requires this to be defined earlier in order to avoid a forward declaration. Reviewed-by: Manivannan Sadhasivam Signed-off-by: Niklas Cassel Link: https://lore.kernel.org/r/20250131182949.465530-12-cassel@kernel.org Signed-off-by: Manivannan Sadhasivam Signed-off-by: Krzysztof Wilczyński --- drivers/pci/controller/dwc/pcie-designware-ep.c | 36 ++++++++++++------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-designware-ep.c b/drivers/pci/controller/dwc/pcie-designware-ep.c index 8e07d432e74f..6b494781da42 100644 --- a/drivers/pci/controller/dwc/pcie-designware-ep.c +++ b/drivers/pci/controller/dwc/pcie-designware-ep.c @@ -102,6 +102,24 @@ static u8 dw_pcie_ep_find_capability(struct dw_pcie_ep *ep, u8 func_no, u8 cap) return __dw_pcie_ep_find_next_cap(ep, func_no, next_cap_ptr, cap); } +static unsigned int dw_pcie_ep_find_ext_capability(struct dw_pcie *pci, int cap) +{ + u32 header; + int pos = PCI_CFG_SPACE_SIZE; + + while (pos) { + header = dw_pcie_readl_dbi(pci, pos); + if (PCI_EXT_CAP_ID(header) == cap) + return pos; + + pos = PCI_EXT_CAP_NEXT(header); + if (!pos) + break; + } + + return 0; +} + static int dw_pcie_ep_write_header(struct pci_epc *epc, u8 func_no, u8 vfunc_no, struct pci_epf_header *hdr) { @@ -690,24 +708,6 @@ void dw_pcie_ep_deinit(struct dw_pcie_ep *ep) } EXPORT_SYMBOL_GPL(dw_pcie_ep_deinit); -static unsigned int dw_pcie_ep_find_ext_capability(struct dw_pcie *pci, int cap) -{ - u32 header; - int pos = PCI_CFG_SPACE_SIZE; - - while (pos) { - header = dw_pcie_readl_dbi(pci, pos); - if (PCI_EXT_CAP_ID(header) == cap) - return pos; - - pos = PCI_EXT_CAP_NEXT(header); - if (!pos) - break; - } - - return 0; -} - static void dw_pcie_ep_init_non_sticky_registers(struct dw_pcie *pci) { unsigned int offset; -- cgit v1.2.3 From 3a3d4cabe681bc5d4f34626739b67d4572b0770c Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Fri, 31 Jan 2025 19:29:53 +0100 Subject: PCI: dwc: ep: Allow EPF drivers to configure the size of Resizable BARs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The DWC databook specifies three different BARn_SIZING_SCHEME_N as: - Fixed Mask (0) - Programmable Mask (1) - Resizable BAR (2) Each of these sizing schemes have different instructions for how to initialize the BAR. The DWC driver currently does not support resizable BARs. Instead, in order to somewhat support resizable BARs, the DWC EP driver currently has an ugly hack that force sets a resizable BAR to 1 MB, if such a BAR is detected. Additionally, this hack only works if the DWC glue driver also has lied in their EPC features, and claimed that the resizable BAR is a 1 MB fixed size BAR. This is unintuitive (as you somehow need to know that you need to lie in your EPC features), but other than that it is overly restrictive, since a resizable BAR is capable of supporting sizes different than 1 MB. Add proper support for resizable BARs in the DWC EP driver. Note that the pci_epc_set_bar() API takes a struct pci_epf_bar which tells the EPC driver how it wants to configure the BAR. struct pci_epf_bar only has a single size struct member. This means that an EPC driver will only be able to set a single supported size. This is perfectly fine, as we do not need the complexity of allowing a host to change the size of the BAR. If someone ever wants to support resizing a resizable BAR, the pci_epc_set_bar() API can be extended in the future. With these changes, we allow an EPF driver to configure the size of Resizable BARs, rather than forcing them to a 1 MB size. This means that an EPC driver does not need to lie in EPC features, and an EPF driver will be able to set an arbitrary size (not be forced to a 1 MB size), just like BAR_PROGRAMMABLE. Signed-off-by: Niklas Cassel Reviewed-by: Manivannan Sadhasivam Link: https://lore.kernel.org/r/20250131182949.465530-13-cassel@kernel.org Signed-off-by: Manivannan Sadhasivam [kwilczynski: commit log] Signed-off-by: Krzysztof Wilczyński --- drivers/pci/controller/dwc/pcie-designware-ep.c | 182 ++++++++++++++++++++++-- 1 file changed, 167 insertions(+), 15 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-designware-ep.c b/drivers/pci/controller/dwc/pcie-designware-ep.c index 6b494781da42..72418160e658 100644 --- a/drivers/pci/controller/dwc/pcie-designware-ep.c +++ b/drivers/pci/controller/dwc/pcie-designware-ep.c @@ -223,6 +223,125 @@ static void dw_pcie_ep_clear_bar(struct pci_epc *epc, u8 func_no, u8 vfunc_no, ep->bar_to_atu[bar] = 0; } +static unsigned int dw_pcie_ep_get_rebar_offset(struct dw_pcie *pci, + enum pci_barno bar) +{ + u32 reg, bar_index; + unsigned int offset, nbars; + int i; + + offset = dw_pcie_ep_find_ext_capability(pci, PCI_EXT_CAP_ID_REBAR); + if (!offset) + return offset; + + reg = dw_pcie_readl_dbi(pci, offset + PCI_REBAR_CTRL); + nbars = (reg & PCI_REBAR_CTRL_NBAR_MASK) >> PCI_REBAR_CTRL_NBAR_SHIFT; + + for (i = 0; i < nbars; i++, offset += PCI_REBAR_CTRL) { + reg = dw_pcie_readl_dbi(pci, offset + PCI_REBAR_CTRL); + bar_index = reg & PCI_REBAR_CTRL_BAR_IDX; + if (bar_index == bar) + return offset; + } + + return 0; +} + +static int dw_pcie_ep_set_bar_resizable(struct dw_pcie_ep *ep, u8 func_no, + struct pci_epf_bar *epf_bar) +{ + struct dw_pcie *pci = to_dw_pcie_from_ep(ep); + enum pci_barno bar = epf_bar->barno; + size_t size = epf_bar->size; + int flags = epf_bar->flags; + u32 reg = PCI_BASE_ADDRESS_0 + (4 * bar); + unsigned int rebar_offset; + u32 rebar_cap, rebar_ctrl; + int ret; + + rebar_offset = dw_pcie_ep_get_rebar_offset(pci, bar); + if (!rebar_offset) + return -EINVAL; + + ret = pci_epc_bar_size_to_rebar_cap(size, &rebar_cap); + if (ret) + return ret; + + dw_pcie_dbi_ro_wr_en(pci); + + /* + * A BAR mask should not be written for a resizable BAR. The BAR mask + * is automatically derived by the controller every time the "selected + * size" bits are updated, see "Figure 3-26 Resizable BAR Example for + * 32-bit Memory BAR0" in DWC EP databook 5.96a. We simply need to write + * BIT(0) to set the BAR enable bit. + */ + dw_pcie_ep_writel_dbi2(ep, func_no, reg, BIT(0)); + dw_pcie_ep_writel_dbi(ep, func_no, reg, flags); + + if (flags & PCI_BASE_ADDRESS_MEM_TYPE_64) { + dw_pcie_ep_writel_dbi2(ep, func_no, reg + 4, 0); + dw_pcie_ep_writel_dbi(ep, func_no, reg + 4, 0); + } + + /* + * Bits 31:0 in PCI_REBAR_CAP define "supported sizes" bits for sizes + * 1 MB to 128 TB. Bits 31:16 in PCI_REBAR_CTRL define "supported sizes" + * bits for sizes 256 TB to 8 EB. Disallow sizes 256 TB to 8 EB. + */ + rebar_ctrl = dw_pcie_readl_dbi(pci, rebar_offset + PCI_REBAR_CTRL); + rebar_ctrl &= ~GENMASK(31, 16); + dw_pcie_writel_dbi(pci, rebar_offset + PCI_REBAR_CTRL, rebar_ctrl); + + /* + * The "selected size" (bits 13:8) in PCI_REBAR_CTRL are automatically + * updated when writing PCI_REBAR_CAP, see "Figure 3-26 Resizable BAR + * Example for 32-bit Memory BAR0" in DWC EP databook 5.96a. + */ + dw_pcie_writel_dbi(pci, rebar_offset + PCI_REBAR_CAP, rebar_cap); + + dw_pcie_dbi_ro_wr_dis(pci); + + return 0; +} + +static int dw_pcie_ep_set_bar_programmable(struct dw_pcie_ep *ep, u8 func_no, + struct pci_epf_bar *epf_bar) +{ + struct dw_pcie *pci = to_dw_pcie_from_ep(ep); + enum pci_barno bar = epf_bar->barno; + size_t size = epf_bar->size; + int flags = epf_bar->flags; + u32 reg = PCI_BASE_ADDRESS_0 + (4 * bar); + + dw_pcie_dbi_ro_wr_en(pci); + + dw_pcie_ep_writel_dbi2(ep, func_no, reg, lower_32_bits(size - 1)); + dw_pcie_ep_writel_dbi(ep, func_no, reg, flags); + + if (flags & PCI_BASE_ADDRESS_MEM_TYPE_64) { + dw_pcie_ep_writel_dbi2(ep, func_no, reg + 4, upper_32_bits(size - 1)); + dw_pcie_ep_writel_dbi(ep, func_no, reg + 4, 0); + } + + dw_pcie_dbi_ro_wr_dis(pci); + + return 0; +} + +static enum pci_epc_bar_type dw_pcie_ep_get_bar_type(struct dw_pcie_ep *ep, + enum pci_barno bar) +{ + const struct pci_epc_features *epc_features; + + if (!ep->ops->get_features) + return BAR_PROGRAMMABLE; + + epc_features = ep->ops->get_features(ep); + + return epc_features->bar[bar].type; +} + static int dw_pcie_ep_set_bar(struct pci_epc *epc, u8 func_no, u8 vfunc_no, struct pci_epf_bar *epf_bar) { @@ -230,9 +349,9 @@ static int dw_pcie_ep_set_bar(struct pci_epc *epc, u8 func_no, u8 vfunc_no, struct dw_pcie *pci = to_dw_pcie_from_ep(ep); enum pci_barno bar = epf_bar->barno; size_t size = epf_bar->size; + enum pci_epc_bar_type bar_type; int flags = epf_bar->flags; int ret, type; - u32 reg; /* * DWC does not allow BAR pairs to overlap, e.g. you cannot combine BARs @@ -264,19 +383,30 @@ static int dw_pcie_ep_set_bar(struct pci_epc *epc, u8 func_no, u8 vfunc_no, goto config_atu; } - reg = PCI_BASE_ADDRESS_0 + (4 * bar); - - dw_pcie_dbi_ro_wr_en(pci); - - dw_pcie_ep_writel_dbi2(ep, func_no, reg, lower_32_bits(size - 1)); - dw_pcie_ep_writel_dbi(ep, func_no, reg, flags); - - if (flags & PCI_BASE_ADDRESS_MEM_TYPE_64) { - dw_pcie_ep_writel_dbi2(ep, func_no, reg + 4, upper_32_bits(size - 1)); - dw_pcie_ep_writel_dbi(ep, func_no, reg + 4, 0); + bar_type = dw_pcie_ep_get_bar_type(ep, bar); + switch (bar_type) { + case BAR_FIXED: + /* + * There is no need to write a BAR mask for a fixed BAR (except + * to write 1 to the LSB of the BAR mask register, to enable the + * BAR). Write the BAR mask regardless. (The fixed bits in the + * BAR mask register will be read-only anyway.) + */ + fallthrough; + case BAR_PROGRAMMABLE: + ret = dw_pcie_ep_set_bar_programmable(ep, func_no, epf_bar); + break; + case BAR_RESIZABLE: + ret = dw_pcie_ep_set_bar_resizable(ep, func_no, epf_bar); + break; + default: + ret = -EINVAL; + dev_err(pci->dev, "Invalid BAR type\n"); + break; } - dw_pcie_dbi_ro_wr_dis(pci); + if (ret) + return ret; config_atu: if (!(flags & PCI_BASE_ADDRESS_SPACE)) @@ -710,9 +840,11 @@ EXPORT_SYMBOL_GPL(dw_pcie_ep_deinit); static void dw_pcie_ep_init_non_sticky_registers(struct dw_pcie *pci) { + struct dw_pcie_ep *ep = &pci->ep; unsigned int offset; unsigned int nbars; - u32 reg, i; + enum pci_barno bar; + u32 reg, i, val; offset = dw_pcie_ep_find_ext_capability(pci, PCI_EXT_CAP_ID_REBAR); @@ -727,9 +859,29 @@ static void dw_pcie_ep_init_non_sticky_registers(struct dw_pcie *pci) * PCIe r6.0, sec 7.8.6.2 require us to support at least one * size in the range from 1 MB to 512 GB. Advertise support * for 1 MB BAR size only. + * + * For a BAR that has been configured via dw_pcie_ep_set_bar(), + * advertise support for only that size instead. */ - for (i = 0; i < nbars; i++, offset += PCI_REBAR_CTRL) - dw_pcie_writel_dbi(pci, offset + PCI_REBAR_CAP, BIT(4)); + for (i = 0; i < nbars; i++, offset += PCI_REBAR_CTRL) { + /* + * While the RESBAR_CAP_REG_* fields are sticky, the + * RESBAR_CTRL_REG_BAR_SIZE field is non-sticky (it is + * sticky in certain versions of DWC PCIe, but not all). + * + * RESBAR_CTRL_REG_BAR_SIZE is updated automatically by + * the controller when RESBAR_CAP_REG is written, which + * is why RESBAR_CAP_REG is written here. + */ + val = dw_pcie_readl_dbi(pci, offset + PCI_REBAR_CTRL); + bar = val & PCI_REBAR_CTRL_BAR_IDX; + if (ep->epf_bar[bar]) + pci_epc_bar_size_to_rebar_cap(ep->epf_bar[bar]->size, &val); + else + val = BIT(4); + + dw_pcie_writel_dbi(pci, offset + PCI_REBAR_CAP, val); + } } dw_pcie_setup(pci); -- cgit v1.2.3 From 6a6b66f7e607e8f6e5b48e246093ed911c8b31be Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Fri, 31 Jan 2025 19:29:54 +0100 Subject: PCI: keystone: Describe Resizable BARs as Resizable BARs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Looking at section "12.2.2.4.15 PCIe Subsystem BAR Configuration" in the following Technical Reference Manual (TRM) for AM65x: https://www.ti.com/lit/ug/spruid7e/spruid7e.pdf We can see that BAR2 and BAR5 are not Fixed BARs, but actually Resizable BARs. Now when we actually have support for Resizable BARs, let's configure these BARs as such. Reviewed-by: Manivannan Sadhasivam Signed-off-by: Niklas Cassel Link: https://lore.kernel.org/r/20250131182949.465530-14-cassel@kernel.org Signed-off-by: Manivannan Sadhasivam [kwilczynski: commit log] Signed-off-by: Krzysztof Wilczyński --- drivers/pci/controller/dwc/pci-keystone.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/pci/controller/dwc/pci-keystone.c b/drivers/pci/controller/dwc/pci-keystone.c index 63bd5003da45..fdc610ec7e5e 100644 --- a/drivers/pci/controller/dwc/pci-keystone.c +++ b/drivers/pci/controller/dwc/pci-keystone.c @@ -966,10 +966,10 @@ static const struct pci_epc_features ks_pcie_am654_epc_features = { .msix_capable = true, .bar[BAR_0] = { .type = BAR_RESERVED, }, .bar[BAR_1] = { .type = BAR_RESERVED, }, - .bar[BAR_2] = { .type = BAR_FIXED, .fixed_size = SZ_1M, }, + .bar[BAR_2] = { .type = BAR_RESIZABLE, }, .bar[BAR_3] = { .type = BAR_FIXED, .fixed_size = SZ_64K, }, .bar[BAR_4] = { .type = BAR_FIXED, .fixed_size = 256, }, - .bar[BAR_5] = { .type = BAR_FIXED, .fixed_size = SZ_1M, }, + .bar[BAR_5] = { .type = BAR_RESIZABLE, }, .align = SZ_1M, }; -- cgit v1.2.3 From a2fa5f96140e30d5ff47c92c5164e0256d92c794 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Fri, 31 Jan 2025 19:29:55 +0100 Subject: PCI: keystone: Specify correct alignment requirement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The support for a specific iATU alignment was added in commit 2a9a801620ef ("PCI: endpoint: Add support to specify alignment for buffers allocated to BARs"). This commit specifically mentions both that the alignment by each DWC based EP driver should match CX_ATU_MIN_REGION_SIZE, and that AM65x specifically has a 64 KB alignment. This also matches the CX_ATU_MIN_REGION_SIZE value specified in the section "12.2.2.4.7 PCIe Subsystem Address Translation" of the Technical Reference Manual (TRM) for AM65x: https://www.ti.com/lit/ug/spruid7e/spruid7e.pdf This higher value, 1 MB, was obviously an ugly hack used to be able to handle Resizable BARs which have a minimum size of 1 MB. Now when we actually have support for Resizable BARs, let's configure the iATU alignment requirement to the actual requirement. (BARs described as Resizable will still get aligned to 1 MB.) Cc: stable+noautosel@kernel.org # Depends on PCI endpoint Resizable BARs series Fixes: 23284ad677a9 ("PCI: keystone: Add support for PCIe EP in AM654x Platforms") Signed-off-by: Niklas Cassel Reviewed-by: Manivannan Sadhasivam Link: https://lore.kernel.org/r/20250131182949.465530-15-cassel@kernel.org Signed-off-by: Manivannan Sadhasivam [kwilczynski: commit log] Signed-off-by: Krzysztof Wilczyński --- drivers/pci/controller/dwc/pci-keystone.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pci/controller/dwc/pci-keystone.c b/drivers/pci/controller/dwc/pci-keystone.c index fdc610ec7e5e..76a37368ae4f 100644 --- a/drivers/pci/controller/dwc/pci-keystone.c +++ b/drivers/pci/controller/dwc/pci-keystone.c @@ -970,7 +970,7 @@ static const struct pci_epc_features ks_pcie_am654_epc_features = { .bar[BAR_3] = { .type = BAR_FIXED, .fixed_size = SZ_64K, }, .bar[BAR_4] = { .type = BAR_FIXED, .fixed_size = 256, }, .bar[BAR_5] = { .type = BAR_RESIZABLE, }, - .align = SZ_1M, + .align = SZ_64K, }; static const struct pci_epc_features* -- cgit v1.2.3 From aba2b17810d7e2d2c4942c56fa3bb0f6e6d616ff Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Fri, 31 Jan 2025 19:29:56 +0100 Subject: PCI: dw-rockchip: Describe Resizable BARs as Resizable BARs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Looking at section "11.4.4.29 USP_PCIE_RESBAR Registers Summary" in the Technical Reference Manual (TRM) for RK3588, we can see that none of the BARs are Fixed BARs, but actually Resizable BARs. I couldn't find any reference in the TRM for RK3568, but looking at the downstream PCIe endpoint driver, both RK3568 and RK3588 are treated as the same, so the BARs on RK3568 must also be Resizable BARs. Now when we actually have support for Resizable BARs, let's configure these BARs as such. Reviewed-by: Manivannan Sadhasivam Signed-off-by: Niklas Cassel Link: https://lore.kernel.org/r/20250131182949.465530-16-cassel@kernel.org Signed-off-by: Manivannan Sadhasivam [kwilczynski: commit log] Signed-off-by: Krzysztof Wilczyński --- drivers/pci/controller/dwc/pcie-dw-rockchip.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-dw-rockchip.c b/drivers/pci/controller/dwc/pcie-dw-rockchip.c index 93698abff4d9..df2eaa35d045 100644 --- a/drivers/pci/controller/dwc/pcie-dw-rockchip.c +++ b/drivers/pci/controller/dwc/pcie-dw-rockchip.c @@ -273,12 +273,12 @@ static const struct pci_epc_features rockchip_pcie_epc_features_rk3568 = { .msi_capable = true, .msix_capable = true, .align = SZ_64K, - .bar[BAR_0] = { .type = BAR_FIXED, .fixed_size = SZ_1M, }, - .bar[BAR_1] = { .type = BAR_FIXED, .fixed_size = SZ_1M, }, - .bar[BAR_2] = { .type = BAR_FIXED, .fixed_size = SZ_1M, }, - .bar[BAR_3] = { .type = BAR_FIXED, .fixed_size = SZ_1M, }, - .bar[BAR_4] = { .type = BAR_FIXED, .fixed_size = SZ_1M, }, - .bar[BAR_5] = { .type = BAR_FIXED, .fixed_size = SZ_1M, }, + .bar[BAR_0] = { .type = BAR_RESIZABLE, }, + .bar[BAR_1] = { .type = BAR_RESIZABLE, }, + .bar[BAR_2] = { .type = BAR_RESIZABLE, }, + .bar[BAR_3] = { .type = BAR_RESIZABLE, }, + .bar[BAR_4] = { .type = BAR_RESIZABLE, }, + .bar[BAR_5] = { .type = BAR_RESIZABLE, }, }; /* @@ -293,12 +293,12 @@ static const struct pci_epc_features rockchip_pcie_epc_features_rk3588 = { .msi_capable = true, .msix_capable = true, .align = SZ_64K, - .bar[BAR_0] = { .type = BAR_FIXED, .fixed_size = SZ_1M, }, - .bar[BAR_1] = { .type = BAR_FIXED, .fixed_size = SZ_1M, }, - .bar[BAR_2] = { .type = BAR_FIXED, .fixed_size = SZ_1M, }, - .bar[BAR_3] = { .type = BAR_FIXED, .fixed_size = SZ_1M, }, + .bar[BAR_0] = { .type = BAR_RESIZABLE, }, + .bar[BAR_1] = { .type = BAR_RESIZABLE, }, + .bar[BAR_2] = { .type = BAR_RESIZABLE, }, + .bar[BAR_3] = { .type = BAR_RESIZABLE, }, .bar[BAR_4] = { .type = BAR_RESERVED, }, - .bar[BAR_5] = { .type = BAR_FIXED, .fixed_size = SZ_1M, }, + .bar[BAR_5] = { .type = BAR_RESIZABLE, }, }; static const struct pci_epc_features * -- cgit v1.2.3 From 22a01177c30fb4c0ea5e5f9f26473b5ee4660310 Mon Sep 17 00:00:00 2001 From: Zijun Hu Date: Mon, 17 Feb 2025 20:26:46 +0800 Subject: PCI: endpoint: Remove unused devm_pci_epc_destroy() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The static function devm_pci_epc_match() is only invoked within the devm_pci_epc_destroy(). However, since it was initially introduced, this new API has had no callers. Thus, remove both the unused API and the static function. Reviewed-by: Manivannan Sadhasivam Signed-off-by: Zijun Hu Link: https://lore.kernel.org/r/20250217-remove_api-v2-1-b169c9117045@quicinc.com Signed-off-by: Manivannan Sadhasivam [kwilczynski: commit log] Signed-off-by: Krzysztof Wilczyński --- Documentation/PCI/endpoint/pci-endpoint.rst | 7 +++---- drivers/pci/endpoint/pci-epc-core.c | 25 ------------------------- include/linux/pci-epc.h | 1 - 3 files changed, 3 insertions(+), 30 deletions(-) diff --git a/Documentation/PCI/endpoint/pci-endpoint.rst b/Documentation/PCI/endpoint/pci-endpoint.rst index 35f82f2d45f5..599763aa01ca 100644 --- a/Documentation/PCI/endpoint/pci-endpoint.rst +++ b/Documentation/PCI/endpoint/pci-endpoint.rst @@ -57,11 +57,10 @@ by the PCI controller driver. The PCI controller driver can then create a new EPC device by invoking devm_pci_epc_create()/pci_epc_create(). -* devm_pci_epc_destroy()/pci_epc_destroy() +* pci_epc_destroy() - The PCI controller driver can destroy the EPC device created by either - devm_pci_epc_create() or pci_epc_create() using devm_pci_epc_destroy() or - pci_epc_destroy(). + The PCI controller driver can destroy the EPC device created by + pci_epc_create() using pci_epc_destroy(). * pci_epc_linkup() diff --git a/drivers/pci/endpoint/pci-epc-core.c b/drivers/pci/endpoint/pci-epc-core.c index 88cb426d3aec..beabea00af91 100644 --- a/drivers/pci/endpoint/pci-epc-core.c +++ b/drivers/pci/endpoint/pci-epc-core.c @@ -25,13 +25,6 @@ static void devm_pci_epc_release(struct device *dev, void *res) pci_epc_destroy(epc); } -static int devm_pci_epc_match(struct device *dev, void *res, void *match_data) -{ - struct pci_epc **epc = res; - - return *epc == match_data; -} - /** * pci_epc_put() - release the PCI endpoint controller * @epc: epc returned by pci_epc_get() @@ -962,24 +955,6 @@ void pci_epc_destroy(struct pci_epc *epc) } EXPORT_SYMBOL_GPL(pci_epc_destroy); -/** - * devm_pci_epc_destroy() - destroy the EPC device - * @dev: device that wants to destroy the EPC - * @epc: the EPC device that has to be destroyed - * - * Invoke to destroy the devres associated with this - * pci_epc and destroy the EPC device. - */ -void devm_pci_epc_destroy(struct device *dev, struct pci_epc *epc) -{ - int r; - - r = devres_release(dev, devm_pci_epc_release, devm_pci_epc_match, - epc); - dev_WARN_ONCE(dev, r, "couldn't find PCI EPC resource\n"); -} -EXPORT_SYMBOL_GPL(devm_pci_epc_destroy); - static void pci_epc_release(struct device *dev) { kfree(to_pci_epc(dev)); diff --git a/include/linux/pci-epc.h b/include/linux/pci-epc.h index 713348322dea..9970ae73c8df 100644 --- a/include/linux/pci-epc.h +++ b/include/linux/pci-epc.h @@ -261,7 +261,6 @@ __devm_pci_epc_create(struct device *dev, const struct pci_epc_ops *ops, struct pci_epc * __pci_epc_create(struct device *dev, const struct pci_epc_ops *ops, struct module *owner); -void devm_pci_epc_destroy(struct device *dev, struct pci_epc *epc); void pci_epc_destroy(struct pci_epc *epc); int pci_epc_add_epf(struct pci_epc *epc, struct pci_epf *epf, enum pci_epc_interface_type type); -- cgit v1.2.3 From 934e9d137d937706004c325fa1474f9e3f1ba10a Mon Sep 17 00:00:00 2001 From: Christian Bruel Date: Fri, 24 Jan 2025 13:30:43 +0100 Subject: PCI: endpoint: pci-epf-test: Fix double free that causes kernel to oops MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix a kernel oops found while testing the stm32_pcie Endpoint driver with handling of PERST# deassertion: During EP initialization, pci_epf_test_alloc_space() allocates all BARs, which are further freed if epc_set_bar() fails (for instance, due to no free inbound window). However, when pci_epc_set_bar() fails, the error path: pci_epc_set_bar() -> pci_epf_free_space() does not clear the previous assignment to epf_test->reg[bar]. Then, if the host reboots, the PERST# deassertion restarts the BAR allocation sequence with the same allocation failure (no free inbound window), creating a double free situation since epf_test->reg[bar] was deallocated and is still non-NULL. Thus, make sure that pci_epf_alloc_space() and pci_epf_free_space() invocations are symmetric, and as such, set epf_test->reg[bar] to NULL when memory is freed. Reviewed-by: Niklas Cassel Reviewed-by: Manivannan Sadhasivam Signed-off-by: Christian Bruel Link: https://lore.kernel.org/r/20250124123043.96112-1-christian.bruel@foss.st.com [kwilczynski: commit log] Signed-off-by: Krzysztof Wilczyński --- drivers/pci/endpoint/functions/pci-epf-test.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/pci/endpoint/functions/pci-epf-test.c b/drivers/pci/endpoint/functions/pci-epf-test.c index 2409787cf56d..bce3ae2c0f65 100644 --- a/drivers/pci/endpoint/functions/pci-epf-test.c +++ b/drivers/pci/endpoint/functions/pci-epf-test.c @@ -738,6 +738,7 @@ static int pci_epf_test_set_bar(struct pci_epf *epf) if (ret) { pci_epf_free_space(epf, epf_test->reg[bar], bar, PRIMARY_INTERFACE); + epf_test->reg[bar] = NULL; dev_err(dev, "Failed to set BAR%d\n", bar); if (bar == test_reg_bar) return ret; @@ -929,6 +930,7 @@ static void pci_epf_test_free_space(struct pci_epf *epf) pci_epf_free_space(epf, epf_test->reg[bar], bar, PRIMARY_INTERFACE); + epf_test->reg[bar] = NULL; } } -- cgit v1.2.3 From a60a7084200591f57ad7e90a0497130d1c685670 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Fri, 21 Feb 2025 21:26:47 +0100 Subject: PCI: dwc: ep: Remove superfluous function dw_pcie_ep_find_ext_capability() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the superfluous function dw_pcie_ep_find_ext_capability(), as it is virtually identical to dw_pcie_find_ext_capability(). Signed-off-by: Niklas Cassel Reviewed-by: Manivannan Sadhasivam Link: https://lore.kernel.org/r/20250221202646.395252-3-cassel@kernel.org Signed-off-by: Krzysztof Wilczyński --- drivers/pci/controller/dwc/pcie-designware-ep.c | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-designware-ep.c b/drivers/pci/controller/dwc/pcie-designware-ep.c index 72418160e658..5729bf313a78 100644 --- a/drivers/pci/controller/dwc/pcie-designware-ep.c +++ b/drivers/pci/controller/dwc/pcie-designware-ep.c @@ -102,24 +102,6 @@ static u8 dw_pcie_ep_find_capability(struct dw_pcie_ep *ep, u8 func_no, u8 cap) return __dw_pcie_ep_find_next_cap(ep, func_no, next_cap_ptr, cap); } -static unsigned int dw_pcie_ep_find_ext_capability(struct dw_pcie *pci, int cap) -{ - u32 header; - int pos = PCI_CFG_SPACE_SIZE; - - while (pos) { - header = dw_pcie_readl_dbi(pci, pos); - if (PCI_EXT_CAP_ID(header) == cap) - return pos; - - pos = PCI_EXT_CAP_NEXT(header); - if (!pos) - break; - } - - return 0; -} - static int dw_pcie_ep_write_header(struct pci_epc *epc, u8 func_no, u8 vfunc_no, struct pci_epf_header *hdr) { @@ -230,7 +212,7 @@ static unsigned int dw_pcie_ep_get_rebar_offset(struct dw_pcie *pci, unsigned int offset, nbars; int i; - offset = dw_pcie_ep_find_ext_capability(pci, PCI_EXT_CAP_ID_REBAR); + offset = dw_pcie_find_ext_capability(pci, PCI_EXT_CAP_ID_REBAR); if (!offset) return offset; @@ -846,7 +828,7 @@ static void dw_pcie_ep_init_non_sticky_registers(struct dw_pcie *pci) enum pci_barno bar; u32 reg, i, val; - offset = dw_pcie_ep_find_ext_capability(pci, PCI_EXT_CAP_ID_REBAR); + offset = dw_pcie_find_ext_capability(pci, PCI_EXT_CAP_ID_REBAR); dw_pcie_dbi_ro_wr_en(pci); @@ -969,7 +951,7 @@ int dw_pcie_ep_init_registers(struct dw_pcie_ep *ep) if (ep->ops->init) ep->ops->init(ep); - ptm_cap_base = dw_pcie_ep_find_ext_capability(pci, PCI_EXT_CAP_ID_PTM); + ptm_cap_base = dw_pcie_find_ext_capability(pci, PCI_EXT_CAP_ID_PTM); /* * PTM responder capability can be disabled only after disabling -- cgit v1.2.3