From bba5065963f8ade14f3caa5b0f5b4a53d3054dfd Mon Sep 17 00:00:00 2001 From: Vidya Sagar Date: Thu, 12 Jan 2023 12:51:11 +0530 Subject: PCI/AER: Configure ECRC only if AER is native As the ECRC configuration bits are part of AER registers, configure ECRC only if AER is natively owned by the kernel. Link: https://lore.kernel.org/r/20230112072111.20063-1-vidyas@nvidia.com Signed-off-by: Vidya Sagar Signed-off-by: Bjorn Helgaas --- Documentation/admin-guide/kernel-parameters.txt | 4 +++- drivers/pci/pcie/aer.c | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 6cfa6e3996cf..ecdb9530af8a 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -4296,7 +4296,9 @@ specified, e.g., 12@pci:8086:9c22:103c:198f for 4096-byte alignment. ecrc= Enable/disable PCIe ECRC (transaction layer - end-to-end CRC checking). + end-to-end CRC checking). Only effective if + OS has native AER control (either granted by + ACPI _OSC or forced via "pcie_ports=native") bios: Use BIOS/firmware settings. This is the the default. off: Turn ECRC off diff --git a/drivers/pci/pcie/aer.c b/drivers/pci/pcie/aer.c index 625f7b2cafe4..d7ee79d7b192 100644 --- a/drivers/pci/pcie/aer.c +++ b/drivers/pci/pcie/aer.c @@ -184,6 +184,9 @@ static int disable_ecrc_checking(struct pci_dev *dev) */ void pcie_set_ecrc_checking(struct pci_dev *dev) { + if (!pcie_aer_is_native(dev)) + return; + switch (ecrc_policy) { case ECRC_POLICY_DEFAULT: return; -- cgit v1.2.3 From 8133844a8f2434be9576850c6978179d7cca5c81 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 12 Jan 2023 21:51:24 +0100 Subject: PCI/ACPI: Account for _S0W of the target bridge in acpi_pci_bridge_d3() It is questionable to allow a PCI bridge to go into D3 if it has _S0W returning D2 or a shallower power state, so modify acpi_pci_bridge_d3(() to always take the return value of _S0W for the target bridge into account. That is, make it return 'false' if _S0W returns D2 or a shallower power state for the target bridge regardless of its ancestor Root Port properties. Of course, this also causes 'false' to be returned if the Root Port itself is the target and its _S0W returns D2 or a shallower power state. However, still allow bridges without _S0W that are power-manageable via ACPI to enter D3 to retain the current code behavior in that case. This fixes problems where a hotplug notification is missed because a bridge is in D3. That means hot-added devices such as USB4 docks (and the devices they contain) and Thunderbolt 3 devices may not work. Link: https://lore.kernel.org/linux-pci/20221031223356.32570-1-mario.limonciello@amd.com/ Link: https://lore.kernel.org/r/12155458.O9o76ZdvQC@kreacher Reported-by: Mario Limonciello Signed-off-by: Rafael J. Wysocki Signed-off-by: Bjorn Helgaas --- drivers/acpi/device_pm.c | 19 +++++++++++++++++++ drivers/pci/pci-acpi.c | 45 +++++++++++++++++++++++++++++++-------------- include/acpi/acpi_bus.h | 1 + 3 files changed, 51 insertions(+), 14 deletions(-) diff --git a/drivers/acpi/device_pm.c b/drivers/acpi/device_pm.c index 97450f4003cc..f007116a8427 100644 --- a/drivers/acpi/device_pm.c +++ b/drivers/acpi/device_pm.c @@ -484,6 +484,25 @@ void acpi_dev_power_up_children_with_adr(struct acpi_device *adev) acpi_dev_for_each_child(adev, acpi_power_up_if_adr_present, NULL); } +/** + * acpi_dev_power_state_for_wake - Deepest power state for wakeup signaling + * @adev: ACPI companion of the target device. + * + * Evaluate _S0W for @adev and return the value produced by it or return + * ACPI_STATE_UNKNOWN on errors (including _S0W not present). + */ +u8 acpi_dev_power_state_for_wake(struct acpi_device *adev) +{ + unsigned long long state; + acpi_status status; + + status = acpi_evaluate_integer(adev->handle, "_S0W", NULL, &state); + if (ACPI_FAILURE(status)) + return ACPI_STATE_UNKNOWN; + + return state; +} + #ifdef CONFIG_PM static DEFINE_MUTEX(acpi_pm_notifier_lock); static DEFINE_MUTEX(acpi_pm_notifier_install_lock); diff --git a/drivers/pci/pci-acpi.c b/drivers/pci/pci-acpi.c index 068d6745bf98..052a611081ec 100644 --- a/drivers/pci/pci-acpi.c +++ b/drivers/pci/pci-acpi.c @@ -976,24 +976,41 @@ bool acpi_pci_power_manageable(struct pci_dev *dev) bool acpi_pci_bridge_d3(struct pci_dev *dev) { struct pci_dev *rpdev; - struct acpi_device *adev; - acpi_status status; - unsigned long long state; + struct acpi_device *adev, *rpadev; const union acpi_object *obj; if (acpi_pci_disabled || !dev->is_hotplug_bridge) return false; - /* Assume D3 support if the bridge is power-manageable by ACPI. */ - if (acpi_pci_power_manageable(dev)) - return true; + adev = ACPI_COMPANION(&dev->dev); + if (adev) { + /* + * If the bridge has _S0W, whether or not it can go into D3 + * depends on what is returned by that object. In particular, + * if the power state returned by _S0W is D2 or shallower, + * entering D3 should not be allowed. + */ + if (acpi_dev_power_state_for_wake(adev) <= ACPI_STATE_D2) + return false; + + /* + * Otherwise, assume that the bridge can enter D3 so long as it + * is power-manageable via ACPI. + */ + if (acpi_device_power_manageable(adev)) + return true; + } rpdev = pcie_find_root_port(dev); if (!rpdev) return false; - adev = ACPI_COMPANION(&rpdev->dev); - if (!adev) + if (rpdev == dev) + rpadev = adev; + else + rpadev = ACPI_COMPANION(&rpdev->dev); + + if (!rpadev) return false; /* @@ -1001,15 +1018,15 @@ bool acpi_pci_bridge_d3(struct pci_dev *dev) * doesn't supply a wakeup GPE via _PRW, it cannot signal hotplug * events from low-power states including D3hot and D3cold. */ - if (!adev->wakeup.flags.valid) + if (!rpadev->wakeup.flags.valid) return false; /* - * If the Root Port cannot wake itself from D3hot or D3cold, we - * can't use D3. + * In the bridge-below-a-Root-Port case, evaluate _S0W for the Root Port + * to verify whether or not it can signal wakeup from D3. */ - status = acpi_evaluate_integer(adev->handle, "_S0W", NULL, &state); - if (ACPI_SUCCESS(status) && state < ACPI_STATE_D3_HOT) + if (rpadev != adev && + acpi_dev_power_state_for_wake(rpadev) <= ACPI_STATE_D2) return false; /* @@ -1018,7 +1035,7 @@ bool acpi_pci_bridge_d3(struct pci_dev *dev) * bridges *below* that Root Port can also signal hotplug events * while in D3. */ - if (!acpi_dev_get_property(adev, "HotPlugSupportInD3", + if (!acpi_dev_get_property(rpadev, "HotPlugSupportInD3", ACPI_TYPE_INTEGER, &obj) && obj->integer.value == 1) return true; diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index cd3b75e08ec3..3412297a6b8b 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h @@ -533,6 +533,7 @@ int acpi_bus_update_power(acpi_handle handle, int *state_p); int acpi_device_update_power(struct acpi_device *device, int *state_p); bool acpi_bus_power_manageable(acpi_handle handle); void acpi_dev_power_up_children_with_adr(struct acpi_device *adev); +u8 acpi_dev_power_state_for_wake(struct acpi_device *adev); int acpi_device_power_add_dependent(struct acpi_device *adev, struct device *dev); void acpi_device_power_remove_dependent(struct acpi_device *adev, -- cgit v1.2.3 From 1af5ea1dc2df627ac11361ee9795cf4f240f66f1 Mon Sep 17 00:00:00 2001 From: Richard Zhu Date: Mon, 16 Jan 2023 13:41:11 +0800 Subject: dt-bindings: imx6q-pcie: Add i.MX8MM PCIe EP mode compatible string Add i.MX8MM PCIe endpoint mode compatible string. Link: https://lore.kernel.org/r/1673847684-31893-2-git-send-email-hongxing.zhu@nxp.com Signed-off-by: Richard Zhu Signed-off-by: Lorenzo Pieralisi Acked-by: Rob Herring --- Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.yaml b/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.yaml index bad980902f66..6a547aabe729 100644 --- a/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.yaml +++ b/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.yaml @@ -24,6 +24,7 @@ properties: - fsl,imx8mq-pcie - fsl,imx8mm-pcie - fsl,imx8mp-pcie + - fsl,imx8mm-pcie-ep reg: items: -- cgit v1.2.3 From dea44b629ae186410d822e2bf576143bebd932f1 Mon Sep 17 00:00:00 2001 From: Richard Zhu Date: Mon, 16 Jan 2023 13:41:12 +0800 Subject: dt-bindings: imx6q-pcie: Add i.MX8MQ PCIe EP mode compatible string Add i.MX8MQ PCIe endpoint mode compatible string. Link: https://lore.kernel.org/r/1673847684-31893-3-git-send-email-hongxing.zhu@nxp.com Signed-off-by: Richard Zhu Signed-off-by: Lorenzo Pieralisi Acked-by: Rob Herring --- Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.yaml b/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.yaml index 6a547aabe729..b57f748acafa 100644 --- a/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.yaml +++ b/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.yaml @@ -25,6 +25,7 @@ properties: - fsl,imx8mm-pcie - fsl,imx8mp-pcie - fsl,imx8mm-pcie-ep + - fsl,imx8mq-pcie-ep reg: items: -- cgit v1.2.3 From 2dd6dc57d2da459983ded133767d0389194f15b8 Mon Sep 17 00:00:00 2001 From: Richard Zhu Date: Mon, 16 Jan 2023 13:41:13 +0800 Subject: dt-bindings: imx6q-pcie: Add i.MX8MP PCIe EP mode compatible string Add i.MX8MP PCIe endpoint mode compatible string. Link: https://lore.kernel.org/r/1673847684-31893-4-git-send-email-hongxing.zhu@nxp.com Signed-off-by: Richard Zhu Signed-off-by: Lorenzo Pieralisi Acked-by: Rob Herring --- Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.yaml b/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.yaml index b57f748acafa..f13f87fddb3d 100644 --- a/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.yaml +++ b/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.yaml @@ -26,6 +26,7 @@ properties: - fsl,imx8mp-pcie - fsl,imx8mm-pcie-ep - fsl,imx8mq-pcie-ep + - fsl,imx8mp-pcie-ep reg: items: -- cgit v1.2.3 From 01ea5ede419733fdc39e75875f0861d16a829fe6 Mon Sep 17 00:00:00 2001 From: Richard Zhu Date: Mon, 16 Jan 2023 13:41:20 +0800 Subject: misc: pci_endpoint_test: Add i.MX8 PCIe EP device support Set the DEVICE_ID of i.MX8 PCIe and add i.MX8 PCIE EP device support in pci_endpoint_test driver. Link: https://lore.kernel.org/r/1673847684-31893-11-git-send-email-hongxing.zhu@nxp.com Signed-off-by: Richard Zhu Signed-off-by: Lorenzo Pieralisi --- drivers/misc/pci_endpoint_test.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/misc/pci_endpoint_test.c b/drivers/misc/pci_endpoint_test.c index 11530b4ec389..e2687229955d 100644 --- a/drivers/misc/pci_endpoint_test.c +++ b/drivers/misc/pci_endpoint_test.c @@ -72,6 +72,7 @@ #define PCI_DEVICE_ID_TI_J7200 0xb00f #define PCI_DEVICE_ID_TI_AM64 0xb010 #define PCI_DEVICE_ID_LS1088A 0x80c0 +#define PCI_DEVICE_ID_IMX8 0x0808 #define is_am654_pci_dev(pdev) \ ((pdev)->device == PCI_DEVICE_ID_TI_AM654) @@ -980,6 +981,7 @@ static const struct pci_device_id pci_endpoint_test_tbl[] = { { PCI_DEVICE(PCI_VENDOR_ID_FREESCALE, 0x81c0), .driver_data = (kernel_ulong_t)&default_data, }, + { PCI_DEVICE(PCI_VENDOR_ID_FREESCALE, PCI_DEVICE_ID_IMX8),}, { PCI_DEVICE(PCI_VENDOR_ID_FREESCALE, PCI_DEVICE_ID_LS1088A), .driver_data = (kernel_ulong_t)&default_data, }, -- cgit v1.2.3 From 75c2f26da03f93e988cd7678722ea893a8c63796 Mon Sep 17 00:00:00 2001 From: Richard Zhu Date: Mon, 16 Jan 2023 13:41:21 +0800 Subject: PCI: imx6: Add i.MX PCIe EP mode support i.MX PCIe is one dual mode PCIe controller. Add i.MX PCIe EP mode support here, and split the PCIe modes to the Root Complex mode and Endpoint mode. Link: https://lore.kernel.org/r/1673847684-31893-12-git-send-email-hongxing.zhu@nxp.com Signed-off-by: Richard Zhu Signed-off-by: Lorenzo Pieralisi --- drivers/pci/controller/dwc/Kconfig | 23 +++++- drivers/pci/controller/dwc/pci-imx6.c | 128 ++++++++++++++++++++++++++++++---- 2 files changed, 137 insertions(+), 14 deletions(-) diff --git a/drivers/pci/controller/dwc/Kconfig b/drivers/pci/controller/dwc/Kconfig index a0d2713f0e88..dffd7fbdfb98 100644 --- a/drivers/pci/controller/dwc/Kconfig +++ b/drivers/pci/controller/dwc/Kconfig @@ -92,10 +92,31 @@ config PCI_EXYNOS functions to implement the driver. config PCI_IMX6 - bool "Freescale i.MX6/7/8 PCIe controller" + bool + +config PCI_IMX6_HOST + bool "Freescale i.MX6/7/8 PCIe controller host mode" depends on ARCH_MXC || COMPILE_TEST depends on PCI_MSI select PCIE_DW_HOST + select PCI_IMX6 + help + Enables support for the PCIe controller in the i.MX SoCs to + work in Root Complex mode. The PCI controller on i.MX is based + on DesignWare hardware and therefore the driver re-uses the + DesignWare core functions to implement the driver. + +config PCI_IMX6_EP + bool "Freescale i.MX6/7/8 PCIe controller endpoint mode" + depends on ARCH_MXC || COMPILE_TEST + depends on PCI_ENDPOINT + select PCIE_DW_EP + select PCI_IMX6 + help + Enables support for the PCIe controller in the i.MX SoCs to + work in endpoint mode. The PCI controller on i.MX is based + on DesignWare hardware and therefore the driver re-uses the + DesignWare core functions to implement the driver. config PCIE_SPEAR13XX bool "STMicroelectronics SPEAr PCIe controller" diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index 1dde5c579edc..572faa91eea7 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -60,6 +60,7 @@ enum imx6_pcie_variants { struct imx6_pcie_drvdata { enum imx6_pcie_variants variant; + enum dw_pcie_device_mode mode; u32 flags; int dbi_length; const char *gpr; @@ -159,17 +160,20 @@ static unsigned int imx6_pcie_grp_offset(const struct imx6_pcie *imx6_pcie) static void imx6_pcie_configure_type(struct imx6_pcie *imx6_pcie) { - unsigned int mask, val; + unsigned int mask, val, mode; + + if (imx6_pcie->drvdata->mode == DW_PCIE_EP_TYPE) + mode = PCI_EXP_TYPE_ENDPOINT; + else + mode = PCI_EXP_TYPE_ROOT_PORT; if (imx6_pcie->drvdata->variant == IMX8MQ && imx6_pcie->controller_id == 1) { mask = IMX8MQ_GPR12_PCIE2_CTRL_DEVICE_TYPE; - val = FIELD_PREP(IMX8MQ_GPR12_PCIE2_CTRL_DEVICE_TYPE, - PCI_EXP_TYPE_ROOT_PORT); + val = FIELD_PREP(IMX8MQ_GPR12_PCIE2_CTRL_DEVICE_TYPE, mode); } else { mask = IMX6Q_GPR12_DEVICE_TYPE; - val = FIELD_PREP(IMX6Q_GPR12_DEVICE_TYPE, - PCI_EXP_TYPE_ROOT_PORT); + val = FIELD_PREP(IMX6Q_GPR12_DEVICE_TYPE, mode); } regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12, mask, val); @@ -1003,8 +1007,99 @@ static const struct dw_pcie_host_ops imx6_pcie_host_ops = { static const struct dw_pcie_ops dw_pcie_ops = { .start_link = imx6_pcie_start_link, + .stop_link = imx6_pcie_stop_link, +}; + +static void imx6_pcie_ep_init(struct dw_pcie_ep *ep) +{ + enum pci_barno bar; + struct dw_pcie *pci = to_dw_pcie_from_ep(ep); + + for (bar = BAR_0; bar <= BAR_5; bar++) + dw_pcie_ep_reset_bar(pci, bar); +} + +static int imx6_pcie_ep_raise_irq(struct dw_pcie_ep *ep, u8 func_no, + enum pci_epc_irq_type type, + u16 interrupt_num) +{ + struct dw_pcie *pci = to_dw_pcie_from_ep(ep); + + switch (type) { + case PCI_EPC_IRQ_LEGACY: + return dw_pcie_ep_raise_legacy_irq(ep, func_no); + case PCI_EPC_IRQ_MSI: + return dw_pcie_ep_raise_msi_irq(ep, func_no, interrupt_num); + case PCI_EPC_IRQ_MSIX: + return dw_pcie_ep_raise_msix_irq(ep, func_no, interrupt_num); + default: + dev_err(pci->dev, "UNKNOWN IRQ type\n"); + return -EINVAL; + } + + return 0; +} + +static const struct pci_epc_features imx8m_pcie_epc_features = { + .linkup_notifier = false, + .msi_capable = true, + .msix_capable = false, + .reserved_bar = 1 << BAR_1 | 1 << BAR_3, + .align = SZ_64K, +}; + +static const struct pci_epc_features* +imx6_pcie_ep_get_features(struct dw_pcie_ep *ep) +{ + return &imx8m_pcie_epc_features; +} + +static const struct dw_pcie_ep_ops pcie_ep_ops = { + .ep_init = imx6_pcie_ep_init, + .raise_irq = imx6_pcie_ep_raise_irq, + .get_features = imx6_pcie_ep_get_features, }; +static int imx6_add_pcie_ep(struct imx6_pcie *imx6_pcie, + struct platform_device *pdev) +{ + int ret; + unsigned int pcie_dbi2_offset; + struct dw_pcie_ep *ep; + struct resource *res; + struct dw_pcie *pci = imx6_pcie->pci; + struct dw_pcie_rp *pp = &pci->pp; + struct device *dev = pci->dev; + + imx6_pcie_host_init(pp); + ep = &pci->ep; + ep->ops = &pcie_ep_ops; + + switch (imx6_pcie->drvdata->variant) { + default: + pcie_dbi2_offset = SZ_4K; + break; + } + pci->dbi_base2 = pci->dbi_base + pcie_dbi2_offset; + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "addr_space"); + if (!res) + return -EINVAL; + + ep->phys_base = res->start; + ep->addr_size = resource_size(res); + ep->page_size = SZ_64K; + + ret = dw_pcie_ep_init(ep); + if (ret) { + dev_err(dev, "failed to initialize endpoint\n"); + return ret; + } + /* Start LTSSM. */ + imx6_pcie_ltssm_enable(dev); + + return 0; +} + static void imx6_pcie_pm_turnoff(struct imx6_pcie *imx6_pcie) { struct device *dev = imx6_pcie->pci->dev; @@ -1279,15 +1374,22 @@ static int imx6_pcie_probe(struct platform_device *pdev) if (ret) return ret; - ret = dw_pcie_host_init(&pci->pp); - if (ret < 0) - return ret; + if (imx6_pcie->drvdata->mode == DW_PCIE_EP_TYPE) { + ret = imx6_add_pcie_ep(imx6_pcie, pdev); + if (ret < 0) + return ret; + } else { + ret = dw_pcie_host_init(&pci->pp); + if (ret < 0) + return ret; + + if (pci_msi_enabled()) { + u8 offset = dw_pcie_find_capability(pci, PCI_CAP_ID_MSI); - if (pci_msi_enabled()) { - u8 offset = dw_pcie_find_capability(pci, PCI_CAP_ID_MSI); - val = dw_pcie_readw_dbi(pci, offset + PCI_MSI_FLAGS); - val |= PCI_MSI_FLAGS_ENABLE; - dw_pcie_writew_dbi(pci, offset + PCI_MSI_FLAGS, val); + val = dw_pcie_readw_dbi(pci, offset + PCI_MSI_FLAGS); + val |= PCI_MSI_FLAGS_ENABLE; + dw_pcie_writew_dbi(pci, offset + PCI_MSI_FLAGS, val); + } } return 0; -- cgit v1.2.3 From 530ba41250b69db4b5beb9fc03bd7183881c5e7f Mon Sep 17 00:00:00 2001 From: Richard Zhu Date: Mon, 16 Jan 2023 13:41:22 +0800 Subject: PCI: imx6: Add i.MX8MQ PCIe EP support Add i.MX8MQ PCIe EP support. Link: https://lore.kernel.org/r/1673847684-31893-13-git-send-email-hongxing.zhu@nxp.com Signed-off-by: Richard Zhu Signed-off-by: Lorenzo Pieralisi --- drivers/pci/controller/dwc/pci-imx6.c | 38 ++++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index 572faa91eea7..8190b1de67f9 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -52,6 +52,7 @@ enum imx6_pcie_variants { IMX8MQ, IMX8MM, IMX8MP, + IMX8MQ_EP, }; #define IMX6_PCIE_FLAG_IMX6_PHY BIT(0) @@ -153,6 +154,7 @@ struct imx6_pcie { static unsigned int imx6_pcie_grp_offset(const struct imx6_pcie *imx6_pcie) { WARN_ON(imx6_pcie->drvdata->variant != IMX8MQ && + imx6_pcie->drvdata->variant != IMX8MQ_EP && imx6_pcie->drvdata->variant != IMX8MM && imx6_pcie->drvdata->variant != IMX8MP); return imx6_pcie->controller_id == 1 ? IOMUXC_GPR16 : IOMUXC_GPR14; @@ -167,13 +169,22 @@ static void imx6_pcie_configure_type(struct imx6_pcie *imx6_pcie) else mode = PCI_EXP_TYPE_ROOT_PORT; - if (imx6_pcie->drvdata->variant == IMX8MQ && - imx6_pcie->controller_id == 1) { - mask = IMX8MQ_GPR12_PCIE2_CTRL_DEVICE_TYPE; - val = FIELD_PREP(IMX8MQ_GPR12_PCIE2_CTRL_DEVICE_TYPE, mode); - } else { + switch (imx6_pcie->drvdata->variant) { + case IMX8MQ: + case IMX8MQ_EP: + if (imx6_pcie->controller_id == 1) { + mask = IMX8MQ_GPR12_PCIE2_CTRL_DEVICE_TYPE; + val = FIELD_PREP(IMX8MQ_GPR12_PCIE2_CTRL_DEVICE_TYPE, + mode); + } else { + mask = IMX6Q_GPR12_DEVICE_TYPE; + val = FIELD_PREP(IMX6Q_GPR12_DEVICE_TYPE, mode); + } + break; + default: mask = IMX6Q_GPR12_DEVICE_TYPE; val = FIELD_PREP(IMX6Q_GPR12_DEVICE_TYPE, mode); + break; } regmap_update_bits(imx6_pcie->iomuxc_gpr, IOMUXC_GPR12, mask, val); @@ -315,6 +326,7 @@ static void imx6_pcie_init_phy(struct imx6_pcie *imx6_pcie) */ break; case IMX8MQ: + case IMX8MQ_EP: /* * TODO: Currently this code assumes external * oscillator is being used @@ -566,6 +578,7 @@ static int imx6_pcie_enable_ref_clk(struct imx6_pcie *imx6_pcie) break; case IMX8MM: case IMX8MQ: + case IMX8MQ_EP: case IMX8MP: ret = clk_prepare_enable(imx6_pcie->pcie_aux); if (ret) { @@ -611,6 +624,7 @@ static void imx6_pcie_disable_ref_clk(struct imx6_pcie *imx6_pcie) break; case IMX8MM: case IMX8MQ: + case IMX8MQ_EP: case IMX8MP: clk_disable_unprepare(imx6_pcie->pcie_aux); break; @@ -676,6 +690,7 @@ static void imx6_pcie_assert_core_reset(struct imx6_pcie *imx6_pcie) switch (imx6_pcie->drvdata->variant) { case IMX7D: case IMX8MQ: + case IMX8MQ_EP: reset_control_assert(imx6_pcie->pciephy_reset); fallthrough; case IMX8MM: @@ -717,6 +732,7 @@ static int imx6_pcie_deassert_core_reset(struct imx6_pcie *imx6_pcie) switch (imx6_pcie->drvdata->variant) { case IMX8MQ: + case IMX8MQ_EP: reset_control_deassert(imx6_pcie->pciephy_reset); break; case IMX7D: @@ -804,6 +820,7 @@ static void imx6_pcie_ltssm_enable(struct device *dev) break; case IMX7D: case IMX8MQ: + case IMX8MQ_EP: case IMX8MM: case IMX8MP: reset_control_deassert(imx6_pcie->apps_reset); @@ -824,6 +841,7 @@ static void imx6_pcie_ltssm_disable(struct device *dev) break; case IMX7D: case IMX8MQ: + case IMX8MQ_EP: case IMX8MM: case IMX8MP: reset_control_assert(imx6_pcie->apps_reset); @@ -1076,6 +1094,9 @@ static int imx6_add_pcie_ep(struct imx6_pcie *imx6_pcie, ep->ops = &pcie_ep_ops; switch (imx6_pcie->drvdata->variant) { + case IMX8MQ_EP: + pcie_dbi2_offset = SZ_1M; + break; default: pcie_dbi2_offset = SZ_4K; break; @@ -1261,6 +1282,7 @@ static int imx6_pcie_probe(struct platform_device *pdev) "pcie_inbound_axi clock missing or invalid\n"); break; case IMX8MQ: + case IMX8MQ_EP: imx6_pcie->pcie_aux = devm_clk_get(dev, "pcie_aux"); if (IS_ERR(imx6_pcie->pcie_aux)) return dev_err_probe(dev, PTR_ERR(imx6_pcie->pcie_aux), @@ -1445,6 +1467,11 @@ static const struct imx6_pcie_drvdata drvdata[] = { .flags = IMX6_PCIE_FLAG_SUPPORTS_SUSPEND, .gpr = "fsl,imx8mp-iomuxc-gpr", }, + [IMX8MQ_EP] = { + .variant = IMX8MQ_EP, + .mode = DW_PCIE_EP_TYPE, + .gpr = "fsl,imx8mq-iomuxc-gpr", + }, }; static const struct of_device_id imx6_pcie_of_match[] = { @@ -1455,6 +1482,7 @@ static const struct of_device_id imx6_pcie_of_match[] = { { .compatible = "fsl,imx8mq-pcie", .data = &drvdata[IMX8MQ], }, { .compatible = "fsl,imx8mm-pcie", .data = &drvdata[IMX8MM], }, { .compatible = "fsl,imx8mp-pcie", .data = &drvdata[IMX8MP], }, + { .compatible = "fsl,imx8mq-pcie-ep", .data = &drvdata[IMX8MQ_EP], }, {}, }; -- cgit v1.2.3 From fb3217e2cfc6a577481b6accc3e17032f242e0ac Mon Sep 17 00:00:00 2001 From: Richard Zhu Date: Mon, 16 Jan 2023 13:41:23 +0800 Subject: PCI: imx6: Add i.MX8MM PCIe EP support Add i.MX8MM PCIe EP support. Link: https://lore.kernel.org/r/1673847684-31893-14-git-send-email-hongxing.zhu@nxp.com Signed-off-by: Richard Zhu Signed-off-by: Lorenzo Pieralisi --- drivers/pci/controller/dwc/pci-imx6.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index 8190b1de67f9..eab332eb4c36 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -53,6 +53,7 @@ enum imx6_pcie_variants { IMX8MM, IMX8MP, IMX8MQ_EP, + IMX8MM_EP, }; #define IMX6_PCIE_FLAG_IMX6_PHY BIT(0) @@ -156,6 +157,7 @@ static unsigned int imx6_pcie_grp_offset(const struct imx6_pcie *imx6_pcie) WARN_ON(imx6_pcie->drvdata->variant != IMX8MQ && imx6_pcie->drvdata->variant != IMX8MQ_EP && imx6_pcie->drvdata->variant != IMX8MM && + imx6_pcie->drvdata->variant != IMX8MM_EP && imx6_pcie->drvdata->variant != IMX8MP); return imx6_pcie->controller_id == 1 ? IOMUXC_GPR16 : IOMUXC_GPR14; } @@ -319,6 +321,7 @@ static void imx6_pcie_init_phy(struct imx6_pcie *imx6_pcie) { switch (imx6_pcie->drvdata->variant) { case IMX8MM: + case IMX8MM_EP: case IMX8MP: /* * The PHY initialization had been done in the PHY @@ -577,6 +580,7 @@ static int imx6_pcie_enable_ref_clk(struct imx6_pcie *imx6_pcie) case IMX7D: break; case IMX8MM: + case IMX8MM_EP: case IMX8MQ: case IMX8MQ_EP: case IMX8MP: @@ -623,6 +627,7 @@ static void imx6_pcie_disable_ref_clk(struct imx6_pcie *imx6_pcie) IMX7D_GPR12_PCIE_PHY_REFCLK_SEL); break; case IMX8MM: + case IMX8MM_EP: case IMX8MQ: case IMX8MQ_EP: case IMX8MP: @@ -694,6 +699,7 @@ static void imx6_pcie_assert_core_reset(struct imx6_pcie *imx6_pcie) reset_control_assert(imx6_pcie->pciephy_reset); fallthrough; case IMX8MM: + case IMX8MM_EP: case IMX8MP: reset_control_assert(imx6_pcie->apps_reset); break; @@ -771,6 +777,7 @@ static int imx6_pcie_deassert_core_reset(struct imx6_pcie *imx6_pcie) break; case IMX6Q: /* Nothing to do */ case IMX8MM: + case IMX8MM_EP: case IMX8MP: break; } @@ -822,6 +829,7 @@ static void imx6_pcie_ltssm_enable(struct device *dev) case IMX8MQ: case IMX8MQ_EP: case IMX8MM: + case IMX8MM_EP: case IMX8MP: reset_control_deassert(imx6_pcie->apps_reset); break; @@ -843,6 +851,7 @@ static void imx6_pcie_ltssm_disable(struct device *dev) case IMX8MQ: case IMX8MQ_EP: case IMX8MM: + case IMX8MM_EP: case IMX8MP: reset_control_assert(imx6_pcie->apps_reset); break; @@ -1095,6 +1104,7 @@ static int imx6_add_pcie_ep(struct imx6_pcie *imx6_pcie, switch (imx6_pcie->drvdata->variant) { case IMX8MQ_EP: + case IMX8MM_EP: pcie_dbi2_offset = SZ_1M; break; default: @@ -1307,6 +1317,7 @@ static int imx6_pcie_probe(struct platform_device *pdev) } break; case IMX8MM: + case IMX8MM_EP: case IMX8MP: imx6_pcie->pcie_aux = devm_clk_get(dev, "pcie_aux"); if (IS_ERR(imx6_pcie->pcie_aux)) @@ -1472,6 +1483,11 @@ static const struct imx6_pcie_drvdata drvdata[] = { .mode = DW_PCIE_EP_TYPE, .gpr = "fsl,imx8mq-iomuxc-gpr", }, + [IMX8MM_EP] = { + .variant = IMX8MM_EP, + .mode = DW_PCIE_EP_TYPE, + .gpr = "fsl,imx8mm-iomuxc-gpr", + }, }; static const struct of_device_id imx6_pcie_of_match[] = { @@ -1483,6 +1499,7 @@ static const struct of_device_id imx6_pcie_of_match[] = { { .compatible = "fsl,imx8mm-pcie", .data = &drvdata[IMX8MM], }, { .compatible = "fsl,imx8mp-pcie", .data = &drvdata[IMX8MP], }, { .compatible = "fsl,imx8mq-pcie-ep", .data = &drvdata[IMX8MQ_EP], }, + { .compatible = "fsl,imx8mm-pcie-ep", .data = &drvdata[IMX8MM_EP], }, {}, }; -- cgit v1.2.3 From c435669a41dd4ee063572e17b48c14806ad31f72 Mon Sep 17 00:00:00 2001 From: Richard Zhu Date: Mon, 16 Jan 2023 13:41:24 +0800 Subject: PCI: imx6: Add i.MX8MP PCIe EP support Add the i.MX8MP PCIe EP support. Link: https://lore.kernel.org/r/1673847684-31893-15-git-send-email-hongxing.zhu@nxp.com Signed-off-by: Richard Zhu Signed-off-by: Lorenzo Pieralisi --- drivers/pci/controller/dwc/pci-imx6.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index eab332eb4c36..55a0405b921d 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -54,6 +54,7 @@ enum imx6_pcie_variants { IMX8MP, IMX8MQ_EP, IMX8MM_EP, + IMX8MP_EP, }; #define IMX6_PCIE_FLAG_IMX6_PHY BIT(0) @@ -158,7 +159,8 @@ static unsigned int imx6_pcie_grp_offset(const struct imx6_pcie *imx6_pcie) imx6_pcie->drvdata->variant != IMX8MQ_EP && imx6_pcie->drvdata->variant != IMX8MM && imx6_pcie->drvdata->variant != IMX8MM_EP && - imx6_pcie->drvdata->variant != IMX8MP); + imx6_pcie->drvdata->variant != IMX8MP && + imx6_pcie->drvdata->variant != IMX8MP_EP); return imx6_pcie->controller_id == 1 ? IOMUXC_GPR16 : IOMUXC_GPR14; } @@ -323,6 +325,7 @@ static void imx6_pcie_init_phy(struct imx6_pcie *imx6_pcie) case IMX8MM: case IMX8MM_EP: case IMX8MP: + case IMX8MP_EP: /* * The PHY initialization had been done in the PHY * driver, break here directly. @@ -584,6 +587,7 @@ static int imx6_pcie_enable_ref_clk(struct imx6_pcie *imx6_pcie) case IMX8MQ: case IMX8MQ_EP: case IMX8MP: + case IMX8MP_EP: ret = clk_prepare_enable(imx6_pcie->pcie_aux); if (ret) { dev_err(dev, "unable to enable pcie_aux clock\n"); @@ -631,6 +635,7 @@ static void imx6_pcie_disable_ref_clk(struct imx6_pcie *imx6_pcie) case IMX8MQ: case IMX8MQ_EP: case IMX8MP: + case IMX8MP_EP: clk_disable_unprepare(imx6_pcie->pcie_aux); break; default: @@ -701,6 +706,7 @@ static void imx6_pcie_assert_core_reset(struct imx6_pcie *imx6_pcie) case IMX8MM: case IMX8MM_EP: case IMX8MP: + case IMX8MP_EP: reset_control_assert(imx6_pcie->apps_reset); break; case IMX6SX: @@ -779,6 +785,7 @@ static int imx6_pcie_deassert_core_reset(struct imx6_pcie *imx6_pcie) case IMX8MM: case IMX8MM_EP: case IMX8MP: + case IMX8MP_EP: break; } @@ -831,6 +838,7 @@ static void imx6_pcie_ltssm_enable(struct device *dev) case IMX8MM: case IMX8MM_EP: case IMX8MP: + case IMX8MP_EP: reset_control_deassert(imx6_pcie->apps_reset); break; } @@ -853,6 +861,7 @@ static void imx6_pcie_ltssm_disable(struct device *dev) case IMX8MM: case IMX8MM_EP: case IMX8MP: + case IMX8MP_EP: reset_control_assert(imx6_pcie->apps_reset); break; } @@ -1105,6 +1114,7 @@ static int imx6_add_pcie_ep(struct imx6_pcie *imx6_pcie, switch (imx6_pcie->drvdata->variant) { case IMX8MQ_EP: case IMX8MM_EP: + case IMX8MP_EP: pcie_dbi2_offset = SZ_1M; break; default: @@ -1319,6 +1329,7 @@ static int imx6_pcie_probe(struct platform_device *pdev) case IMX8MM: case IMX8MM_EP: case IMX8MP: + case IMX8MP_EP: imx6_pcie->pcie_aux = devm_clk_get(dev, "pcie_aux"); if (IS_ERR(imx6_pcie->pcie_aux)) return dev_err_probe(dev, PTR_ERR(imx6_pcie->pcie_aux), @@ -1488,6 +1499,11 @@ static const struct imx6_pcie_drvdata drvdata[] = { .mode = DW_PCIE_EP_TYPE, .gpr = "fsl,imx8mm-iomuxc-gpr", }, + [IMX8MP_EP] = { + .variant = IMX8MP_EP, + .mode = DW_PCIE_EP_TYPE, + .gpr = "fsl,imx8mp-iomuxc-gpr", + }, }; static const struct of_device_id imx6_pcie_of_match[] = { @@ -1500,6 +1516,7 @@ static const struct of_device_id imx6_pcie_of_match[] = { { .compatible = "fsl,imx8mp-pcie", .data = &drvdata[IMX8MP], }, { .compatible = "fsl,imx8mq-pcie-ep", .data = &drvdata[IMX8MQ_EP], }, { .compatible = "fsl,imx8mm-pcie-ep", .data = &drvdata[IMX8MM_EP], }, + { .compatible = "fsl,imx8mp-pcie-ep", .data = &drvdata[IMX8MP_EP], }, {}, }; -- cgit v1.2.3 From 1aa3f2b02fcd3817a0b1caa0a4654e40433a33be Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Thu, 12 Jan 2023 22:39:37 -0800 Subject: misc: pci_endpoint_test: Drop initial kernel-doc marker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This beginning comment is not kernel-doc, so change the "/**" to a normal "/*" comment to prevent a kernel-doc warning: drivers/misc/pci_endpoint_test.c:3: warning: This comment starts with '/**', but isn't a kernel-doc comment. Refer Documentation/doc-guide/kernel-doc.rst Link: https://lore.kernel.org/r/20230113063937.20912-1-rdunlap@infradead.org Signed-off-by: Randy Dunlap Signed-off-by: Bjorn Helgaas Cc: Lorenzo Pieralisi Cc: Krzysztof Wilczyński Cc: Manivannan Sadhasivam Cc: Kishon Vijay Abraham I Cc: linux-pci@vger.kernel.org Cc: Arnd Bergmann Cc: Greg Kroah-Hartman --- drivers/misc/pci_endpoint_test.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/misc/pci_endpoint_test.c b/drivers/misc/pci_endpoint_test.c index 11530b4ec389..e85cabd9764f 100644 --- a/drivers/misc/pci_endpoint_test.c +++ b/drivers/misc/pci_endpoint_test.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only -/** +/* * Host side test driver to test endpoint functionality * * Copyright (C) 2017 Texas Instruments -- cgit v1.2.3 From ea0b5aa5f184cf8293c93163f0fb00505190d431 Mon Sep 17 00:00:00 2001 From: "Alexey V. Vissarionov" Date: Sun, 18 Dec 2022 06:33:47 +0300 Subject: PCI/IOV: Enlarge virtfn sysfs name buffer The sysfs link name "virtfn%u" constructed by pci_iov_sysfs_link() requires 17 bytes to contain the longest possible string. Increase VIRTFN_ID_LEN to accommodate that. Found by Linux Verification Center (linuxtesting.org) with SVACE. [bhelgaas: commit log, comment at #define] Fixes: dd7cc44d0bce ("PCI: add SR-IOV API for Physical Function driver") Link: https://lore.kernel.org/r/20221218033347.23743-1-gremlin@altlinux.org Signed-off-by: Alexey V. Vissarionov Signed-off-by: Bjorn Helgaas --- drivers/pci/iov.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pci/iov.c b/drivers/pci/iov.c index 952217572113..b2e8322755c1 100644 --- a/drivers/pci/iov.c +++ b/drivers/pci/iov.c @@ -14,7 +14,7 @@ #include #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) { -- cgit v1.2.3 From 4e353ff40a830c9c5a9feee13d1cacbc3f803e8f Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Fri, 16 Dec 2022 10:21:25 -0600 Subject: PCI: switchtec: Simplify switchtec_dma_mrpc_isr() The "ret" variable in switchtec_dma_mrpc_isr() is superfluous. Remove it and just return the value. No functional change intended. Link: https://lore.kernel.org/r/20221216162126.207863-2-helgaas@kernel.org Signed-off-by: Bjorn Helgaas Reviewed-by: Logan Gunthorpe --- drivers/pci/switch/switchtec.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/pci/switch/switchtec.c b/drivers/pci/switch/switchtec.c index 75be4fe22509..d7ae84070e29 100644 --- a/drivers/pci/switch/switchtec.c +++ b/drivers/pci/switch/switchtec.c @@ -1480,15 +1480,13 @@ static irqreturn_t switchtec_event_isr(int irq, void *dev) static irqreturn_t switchtec_dma_mrpc_isr(int irq, void *dev) { struct switchtec_dev *stdev = dev; - irqreturn_t ret = IRQ_NONE; iowrite32(SWITCHTEC_EVENT_CLEAR | SWITCHTEC_EVENT_EN_IRQ, &stdev->mmio_part_cfg->mrpc_comp_hdr); schedule_work(&stdev->mrpc_work); - ret = IRQ_HANDLED; - return ret; + return IRQ_HANDLED; } static int switchtec_init_isr(struct switchtec_dev *stdev) -- cgit v1.2.3 From ddc10938e08cd7aac63d8385f7305f7889df5179 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Fri, 16 Dec 2022 10:21:26 -0600 Subject: PCI: switchtec: Return -EFAULT for copy_to_user() errors switchtec_dev_read() didn't handle copy_to_user() errors correctly: it assigned "rc = -EFAULT", but actually returned either "size", -ENXIO, or -EBADMSG instead. Update the failure cases to unlock mrpc_mutex and return -EFAULT directly. Link: https://lore.kernel.org/r/20221216162126.207863-3-helgaas@kernel.org Fixes: 080b47def5e5 ("MicroSemi Switchtec management interface driver") Signed-off-by: Bjorn Helgaas Reviewed-by: Logan Gunthorpe --- drivers/pci/switch/switchtec.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/pci/switch/switchtec.c b/drivers/pci/switch/switchtec.c index d7ae84070e29..3d6f17ff2429 100644 --- a/drivers/pci/switch/switchtec.c +++ b/drivers/pci/switch/switchtec.c @@ -606,21 +606,20 @@ static ssize_t switchtec_dev_read(struct file *filp, char __user *data, rc = copy_to_user(data, &stuser->return_code, sizeof(stuser->return_code)); if (rc) { - rc = -EFAULT; - goto out; + mutex_unlock(&stdev->mrpc_mutex); + return -EFAULT; } data += sizeof(stuser->return_code); rc = copy_to_user(data, &stuser->data, size - sizeof(stuser->return_code)); if (rc) { - rc = -EFAULT; - goto out; + mutex_unlock(&stdev->mrpc_mutex); + return -EFAULT; } stuser_set_state(stuser, MRPC_IDLE); -out: mutex_unlock(&stdev->mrpc_mutex); if (stuser->status == SWITCHTEC_MRPC_STATUS_DONE || -- cgit v1.2.3 From 7952e71653d986f73c5517e50920866d580d2394 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 6 Jan 2023 09:12:01 +0100 Subject: dt-bindings: PCI: qcom: Add oneOf to compatible match Prepare for extending compatible list by adding oneOf keyword. No functional changes. Link: https://lore.kernel.org/r/20230106081203.14118-1-krzysztof.kozlowski@linaro.org Signed-off-by: Krzysztof Kozlowski Signed-off-by: Bjorn Helgaas Acked-by: Rob Herring --- .../devicetree/bindings/pci/qcom,pcie.yaml | 39 +++++++++++----------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/Documentation/devicetree/bindings/pci/qcom,pcie.yaml b/Documentation/devicetree/bindings/pci/qcom,pcie.yaml index a5859bb3dc28..09a4e3c330f1 100644 --- a/Documentation/devicetree/bindings/pci/qcom,pcie.yaml +++ b/Documentation/devicetree/bindings/pci/qcom,pcie.yaml @@ -16,25 +16,26 @@ description: | properties: compatible: - enum: - - qcom,pcie-ipq8064 - - qcom,pcie-ipq8064-v2 - - qcom,pcie-apq8064 - - qcom,pcie-apq8084 - - qcom,pcie-msm8996 - - qcom,pcie-ipq4019 - - qcom,pcie-ipq8074 - - qcom,pcie-qcs404 - - qcom,pcie-sa8540p - - qcom,pcie-sc7280 - - qcom,pcie-sc8180x - - qcom,pcie-sc8280xp - - qcom,pcie-sdm845 - - qcom,pcie-sm8150 - - qcom,pcie-sm8250 - - qcom,pcie-sm8450-pcie0 - - qcom,pcie-sm8450-pcie1 - - qcom,pcie-ipq6018 + oneOf: + - enum: + - qcom,pcie-ipq8064 + - qcom,pcie-ipq8064-v2 + - qcom,pcie-apq8064 + - qcom,pcie-apq8084 + - qcom,pcie-msm8996 + - qcom,pcie-ipq4019 + - qcom,pcie-ipq8074 + - qcom,pcie-qcs404 + - qcom,pcie-sa8540p + - qcom,pcie-sc7280 + - qcom,pcie-sc8180x + - qcom,pcie-sc8280xp + - qcom,pcie-sdm845 + - qcom,pcie-sm8150 + - qcom,pcie-sm8250 + - qcom,pcie-sm8450-pcie0 + - qcom,pcie-sm8450-pcie1 + - qcom,pcie-ipq6018 reg: minItems: 4 -- cgit v1.2.3 From fd858402c6d0a80e0b543886b9f7865c6d76d5d6 Mon Sep 17 00:00:00 2001 From: Yang Yingliang Date: Tue, 3 Jan 2023 10:49:07 +0800 Subject: PCI: endpoint: pci-epf-vntb: Add epf_ntb_mw_bar_clear() num_mws kernel-doc 8e4bfbe644a6 ("PCI: endpoint: pci-epf-vntb: fix error handle in epf_ntb_mw_bar_init()") added a "num_mws" parameter to epf_ntb_mw_bar_clear() but failed to add kernel-doc for num_mws. Add kernel-doc for num_mws on epf_ntb_mw_bar_clear(). Fixes: 8e4bfbe644a6 ("PCI: endpoint: pci-epf-vntb: fix error handle in epf_ntb_mw_bar_init()") Link: https://lore.kernel.org/r/20230103024907.293853-1-yangyingliang@huawei.com Signed-off-by: Yang Yingliang Signed-off-by: Bjorn Helgaas --- drivers/pci/endpoint/functions/pci-epf-vntb.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/pci/endpoint/functions/pci-epf-vntb.c b/drivers/pci/endpoint/functions/pci-epf-vntb.c index 04698e7995a5..b7c7a8af99f4 100644 --- a/drivers/pci/endpoint/functions/pci-epf-vntb.c +++ b/drivers/pci/endpoint/functions/pci-epf-vntb.c @@ -652,6 +652,7 @@ err_alloc_mem: /** * epf_ntb_mw_bar_clear() - Clear Memory window BARs * @ntb: NTB device that facilitates communication between HOST and VHOST + * @num_mws: the number of Memory window BARs that to be cleared */ static void epf_ntb_mw_bar_clear(struct epf_ntb *ntb, int num_mws) { -- cgit v1.2.3 From 6b985af556e5c50e89d00a79864423582bfd3c69 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Wed, 18 Jan 2023 17:46:04 -0600 Subject: PCI/AER: Remove redundant Device Control Error Reporting Enable The following bits in the PCIe Device Control register enable sending of ERR_COR, ERR_NONFATAL, or ERR_FATAL Messages (or reporting internally in the case of Root Ports): Correctable Error Reporting Enable Non-Fatal Error Reporting Enable Fatal Error Reporting Enable Unsupported Request Reporting Enable These enable bits are set by pci_enable_pcie_error_reporting(), and since f26e58bf6f54 ("PCI/AER: Enable error reporting when AER is native"), we do that in this path during enumeration: pci_init_capabilities pci_aer_init pci_enable_pcie_error_reporting Previously, the AER service driver also traversed the hierarchy when claiming a Root Port, enabling error reporting for downstream devices, but this is redundant. Remove the code that enables this error reporting in the AER .probe() path. Also remove similar code that disables error reporting in the AER .remove() path. Note that these Device Control Reporting Enable bits do not control interrupt generation. That's done by the similarly-named bits in the AER Root Error Command register, which are still set by aer_probe() and cleared by aer_remove(), since the AER service driver handles those interrupts. See PCIe r6.0, sec 6.2.6. Link: https://lore.kernel.org/r/20230118234612.272916-2-helgaas@kernel.org Signed-off-by: Bjorn Helgaas Reviewed-by: Stefan Roese Reviewed-by: Kuppuswamy Sathyanarayanan Cc: Ashok Raj Cc: Keith Busch --- drivers/pci/pcie/aer.c | 48 ------------------------------------------------ 1 file changed, 48 deletions(-) diff --git a/drivers/pci/pcie/aer.c b/drivers/pci/pcie/aer.c index d7ee79d7b192..f6c24ded134c 100644 --- a/drivers/pci/pcie/aer.c +++ b/drivers/pci/pcie/aer.c @@ -1227,42 +1227,6 @@ static irqreturn_t aer_irq(int irq, void *context) return IRQ_WAKE_THREAD; } -static int set_device_error_reporting(struct pci_dev *dev, void *data) -{ - bool enable = *((bool *)data); - int type = pci_pcie_type(dev); - - if ((type == PCI_EXP_TYPE_ROOT_PORT) || - (type == PCI_EXP_TYPE_RC_EC) || - (type == PCI_EXP_TYPE_UPSTREAM) || - (type == PCI_EXP_TYPE_DOWNSTREAM)) { - if (enable) - pci_enable_pcie_error_reporting(dev); - else - pci_disable_pcie_error_reporting(dev); - } - - return 0; -} - -/** - * set_downstream_devices_error_reporting - enable/disable the error reporting bits on the root port and its downstream ports. - * @dev: pointer to root port's pci_dev data structure - * @enable: true = enable error reporting, false = disable error reporting. - */ -static void set_downstream_devices_error_reporting(struct pci_dev *dev, - bool enable) -{ - set_device_error_reporting(dev, &enable); - - if (pci_pcie_type(dev) == PCI_EXP_TYPE_RC_EC) - pcie_walk_rcec(dev, set_device_error_reporting, &enable); - else if (dev->subordinate) - pci_walk_bus(dev->subordinate, set_device_error_reporting, - &enable); - -} - /** * aer_enable_rootport - enable Root Port's interrupts when receiving messages * @rpc: pointer to a Root Port data structure @@ -1292,12 +1256,6 @@ static void aer_enable_rootport(struct aer_rpc *rpc) pci_read_config_dword(pdev, aer + PCI_ERR_UNCOR_STATUS, ®32); pci_write_config_dword(pdev, aer + PCI_ERR_UNCOR_STATUS, reg32); - /* - * Enable error reporting for the root port device and downstream port - * devices. - */ - set_downstream_devices_error_reporting(pdev, true); - /* Enable Root Port's interrupt in response to error messages */ pci_read_config_dword(pdev, aer + PCI_ERR_ROOT_COMMAND, ®32); reg32 |= ROOT_PORT_INTR_ON_MESG_MASK; @@ -1316,12 +1274,6 @@ static void aer_disable_rootport(struct aer_rpc *rpc) int aer = pdev->aer_cap; u32 reg32; - /* - * Disable error reporting for the root port device and downstream port - * devices. - */ - set_downstream_devices_error_reporting(pdev, false); - /* Disable Root's interrupt in response to error messages */ pci_read_config_dword(pdev, aer + PCI_ERR_ROOT_COMMAND, ®32); reg32 &= ~ROOT_PORT_INTR_ON_MESG_MASK; -- cgit v1.2.3 From 37fe46051dc94c589b616018ba9d2fd4bacfbd8a Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 13 Jan 2023 20:13:43 +0300 Subject: dmaengine: Fix dma_slave_config.dst_addr description The dst_addr member of the dma_slave_config structure has been mistakenly marked as ignored if the *source* address belongs to the memory. That is relevant to the src_addr field of the structure, while the dst_addr field contains a destination device address that should be ignored if the destination is the CPU memory. Correct the @dst_addr description accordingly. Link: https://lore.kernel.org/r/20230113171409.30470-2-Sergey.Semin@baikalelectronics.ru Tested-by: Manivannan Sadhasivam Signed-off-by: Serge Semin Signed-off-by: Lorenzo Pieralisi Signed-off-by: Bjorn Helgaas Reviewed-by: Manivannan Sadhasivam Acked-by: Vinod Koul --- include/linux/dmaengine.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/dmaengine.h b/include/linux/dmaengine.h index c923f4e60f24..0c020682d894 100644 --- a/include/linux/dmaengine.h +++ b/include/linux/dmaengine.h @@ -394,7 +394,7 @@ enum dma_slave_buswidth { * should be read (RX), if the source is memory this argument is * ignored. * @dst_addr: this is the physical address where DMA slave data - * should be written (TX), if the source is memory this argument + * should be written (TX), if the destination is memory this argument * is ignored. * @src_addr_width: this is the width in bytes of the source (RX) * register where DMA data shall be read. If the source -- cgit v1.2.3 From 0278067445626de4d9c46919d0ee1af0b987ee45 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 13 Jan 2023 20:13:44 +0300 Subject: dmaengine: dw-edma: Release requested IRQs on failure If dw_edma_irq_request() fails to initialize an IRQ handler, any previously requested IRQs will be left initialized. Release the previously requested IRQs in the cleanup-on-error path of dw_edma_irq_request(). Link: https://lore.kernel.org/r/20230113171409.30470-3-Sergey.Semin@baikalelectronics.ru Fixes: e63d79d1ffcd ("dmaengine: Add Synopsys eDMA IP core driver") Tested-by: Manivannan Sadhasivam Signed-off-by: Serge Semin Signed-off-by: Lorenzo Pieralisi Signed-off-by: Bjorn Helgaas Reviewed-by: Manivannan Sadhasivam Acked-by: Vinod Koul --- drivers/dma/dw-edma/dw-edma-core.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c index c54b24ff5206..a8c1bd9c7ae9 100644 --- a/drivers/dma/dw-edma/dw-edma-core.c +++ b/drivers/dma/dw-edma/dw-edma-core.c @@ -893,10 +893,8 @@ static int dw_edma_irq_request(struct dw_edma *dw, dw_edma_interrupt_read, IRQF_SHARED, dw->name, &dw->irq[i]); - if (err) { - dw->nr_irqs = i; - return err; - } + if (err) + goto err_irq_free; if (irq_get_msi_desc(irq)) get_cached_msi_msg(irq, &dw->irq[i].msi); @@ -905,6 +903,14 @@ static int dw_edma_irq_request(struct dw_edma *dw, dw->nr_irqs = i; } + return 0; + +err_irq_free: + for (i--; i >= 0; i--) { + irq = chip->ops->irq_vector(dev, i); + free_irq(irq, &dw->irq[i]); + } + return err; } -- cgit v1.2.3 From 002bbaa2f60e07d465f92a163365569481d34108 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 13 Jan 2023 20:13:45 +0300 Subject: dmaengine: dw-edma: Convert ll/dt phys address to PCI bus/DMA address The dw_edma_region.paddr field should be a memory base address visible by the DW eDMA controller. If the DMA engine is embedded in the DW PCIe Host/Endpoint controller, the address should belong to the Local CPU/ Application memory. If eDMA is remotely accessible across the PCI bus via PCI memory IOs, the address should be part of the PCI bus memory space. The latter case hasn't been well covered in the corresponding glue-driver. Since pci_dev.resource[] contains resources defined in the CPU memory space, they need to be converted to the PCI bus address space. Convert the LL, DT and CSRs PCI memory ranges with pci_bus_address(). In addition, extend the dw_edma_region.paddr field size. The field normally contains a memory range base address to be set in the DW eDMA Linked-List pointer register or as a base address of the Linked-List data buffer. In accordance with [1] the LL range is supposed to be created in the Local CPU/Application memory, but depending on the DW eDMA utilization the memory can be created as a part of the PCI bus address space (as in the case of the DW PCIe Endpoint prototype kit). In the former case dw_edma_region.paddr should be a dma_addr_t, while in the latter one it should be a pci_bus_addr_t. Since the corresponding CSRs are always 64 bits wide, convert dw_edma_region.paddr to be u64, and let the client make sure it has a valid address visible by the DW eDMA controller. For instance, the DW eDMA PCIe glue-driver initializes the field with addresses from the PCI bus memory space. [1] DesignWare Cores PCI Express Controller Databook - DWC PCIe Root Port, v.5.40a, March 2019, p.1103 Link: https://lore.kernel.org/r/20230113171409.30470-4-Sergey.Semin@baikalelectronics.ru Fixes: 41aaff2a2ac0 ("dmaengine: Add Synopsys eDMA IP PCIe glue-logic") Tested-by: Manivannan Sadhasivam Signed-off-by: Serge Semin Signed-off-by: Lorenzo Pieralisi Signed-off-by: Bjorn Helgaas Reviewed-by: Manivannan Sadhasivam Acked-by: Vinod Koul --- drivers/dma/dw-edma/dw-edma-pcie.c | 8 ++++---- include/linux/dma/edma.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/dma/dw-edma/dw-edma-pcie.c b/drivers/dma/dw-edma/dw-edma-pcie.c index d6b5e2463884..04c95cba1244 100644 --- a/drivers/dma/dw-edma/dw-edma-pcie.c +++ b/drivers/dma/dw-edma/dw-edma-pcie.c @@ -231,7 +231,7 @@ static int dw_edma_pcie_probe(struct pci_dev *pdev, return -ENOMEM; ll_region->vaddr += ll_block->off; - ll_region->paddr = pdev->resource[ll_block->bar].start; + ll_region->paddr = pci_bus_address(pdev, ll_block->bar); ll_region->paddr += ll_block->off; ll_region->sz = ll_block->sz; @@ -240,7 +240,7 @@ static int dw_edma_pcie_probe(struct pci_dev *pdev, return -ENOMEM; dt_region->vaddr += dt_block->off; - dt_region->paddr = pdev->resource[dt_block->bar].start; + dt_region->paddr = pci_bus_address(pdev, dt_block->bar); dt_region->paddr += dt_block->off; dt_region->sz = dt_block->sz; } @@ -256,7 +256,7 @@ static int dw_edma_pcie_probe(struct pci_dev *pdev, return -ENOMEM; ll_region->vaddr += ll_block->off; - ll_region->paddr = pdev->resource[ll_block->bar].start; + ll_region->paddr = pci_bus_address(pdev, ll_block->bar); ll_region->paddr += ll_block->off; ll_region->sz = ll_block->sz; @@ -265,7 +265,7 @@ static int dw_edma_pcie_probe(struct pci_dev *pdev, return -ENOMEM; dt_region->vaddr += dt_block->off; - dt_region->paddr = pdev->resource[dt_block->bar].start; + dt_region->paddr = pci_bus_address(pdev, dt_block->bar); dt_region->paddr += dt_block->off; dt_region->sz = dt_block->sz; } diff --git a/include/linux/dma/edma.h b/include/linux/dma/edma.h index 7d8062e9c544..a864978ddd27 100644 --- a/include/linux/dma/edma.h +++ b/include/linux/dma/edma.h @@ -18,7 +18,7 @@ struct dw_edma; struct dw_edma_region { - phys_addr_t paddr; + u64 paddr; void __iomem *vaddr; size_t sz; }; -- cgit v1.2.3 From 13b6299cf66165a442089fa895a7f70250703584 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 13 Jan 2023 20:13:46 +0300 Subject: dmaengine: dw-edma: Fix missing src/dst address of interleaved xfers Interleaved DMA transfer support was added by 85e7518f42c8 ("dmaengine: dw-edma: Add device_prep_interleave_dma() support"), but depending on the selected channel, either source or destination address are left uninitialized which was obviously wrong. Initialize the destination address of the eDMA burst descriptors for DEV_TO_MEM interleaved operations and the source address for MEM_TO_DEV operations. Link: https://lore.kernel.org/r/20230113171409.30470-5-Sergey.Semin@baikalelectronics.ru Fixes: 85e7518f42c8 ("dmaengine: dw-edma: Add device_prep_interleave_dma() support") Tested-by: Manivannan Sadhasivam Signed-off-by: Serge Semin Signed-off-by: Lorenzo Pieralisi Signed-off-by: Bjorn Helgaas Reviewed-by: Manivannan Sadhasivam Acked-by: Vinod Koul --- drivers/dma/dw-edma/dw-edma-core.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c index a8c1bd9c7ae9..778d91d9fc1b 100644 --- a/drivers/dma/dw-edma/dw-edma-core.c +++ b/drivers/dma/dw-edma/dw-edma-core.c @@ -455,6 +455,8 @@ dw_edma_device_transfer(struct dw_edma_transfer *xfer) * and destination addresses are increased * by the same portion (data length) */ + } else if (xfer->type == EDMA_XFER_INTERLEAVED) { + burst->dar = dst_addr; } } else { burst->dar = dst_addr; @@ -470,6 +472,8 @@ dw_edma_device_transfer(struct dw_edma_transfer *xfer) * and destination addresses are increased * by the same portion (data length) */ + } else if (xfer->type == EDMA_XFER_INTERLEAVED) { + burst->sar = src_addr; } } -- cgit v1.2.3 From c8ed49182286cb9c64f0c503b98090bd11a3fb60 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 13 Jan 2023 20:13:47 +0300 Subject: dmaengine: dw-edma: Don't permit non-inc interleaved xfers The DW eDMA controller always increments both source and destination addresses. Permitting DMA interleaved transfers with no src_inc/dst_inc flags set may lead to unexpected behaviour for the device users. Terminate interleaved transfers if at least one of the dma_interleaved_template.{src_inc,dst_inc} flag is initialized to "false". Note that in addition, we need to increase the source and destination addresses after each iteration. Link: https://lore.kernel.org/r/20230113171409.30470-6-Sergey.Semin@baikalelectronics.ru Fixes: 85e7518f42c8 ("dmaengine: dw-edma: Add device_prep_interleave_dma() support") Tested-by: Manivannan Sadhasivam Signed-off-by: Serge Semin Signed-off-by: Lorenzo Pieralisi Signed-off-by: Bjorn Helgaas Reviewed-by: Manivannan Sadhasivam Acked-by: Vinod Koul --- drivers/dma/dw-edma/dw-edma-core.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c index 778d91d9fc1b..35588e14f79a 100644 --- a/drivers/dma/dw-edma/dw-edma-core.c +++ b/drivers/dma/dw-edma/dw-edma-core.c @@ -385,6 +385,8 @@ dw_edma_device_transfer(struct dw_edma_transfer *xfer) return NULL; if (xfer->xfer.il->numf > 0 && xfer->xfer.il->frame_size > 0) return NULL; + if (!xfer->xfer.il->src_inc || !xfer->xfer.il->dst_inc) + return NULL; } else { return NULL; } @@ -484,15 +486,13 @@ dw_edma_device_transfer(struct dw_edma_transfer *xfer) struct dma_interleaved_template *il = xfer->xfer.il; struct data_chunk *dc = &il->sgl[i]; - if (il->src_sgl) { - src_addr += burst->sz; + src_addr += burst->sz; + if (il->src_sgl) src_addr += dmaengine_get_src_icg(il, dc); - } - if (il->dst_sgl) { - dst_addr += burst->sz; + dst_addr += burst->sz; + if (il->dst_sgl) dst_addr += dmaengine_get_dst_icg(il, dc); - } } } -- cgit v1.2.3 From 7ad06f218491bf30f13fd88933bb807de5330cdd Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 13 Jan 2023 20:13:48 +0300 Subject: dmaengine: dw-edma: Fix invalid interleaved xfers semantics The interleaved DMA transfer support added by 85e7518f42c8 ("dmaengine: dw-edma: Add device_prep_interleave_dma() support") seems contradictory to what the DMA engine defines. The next conditional statements: if (!xfer->xfer.il->numf) return NULL; if (xfer->xfer.il->numf > 0 && xfer->xfer.il->frame_size > 0) return NULL; mean that numf can't be zero and frame_size must always be zero, otherwise the transfer won't be executed. Furthermore, the transfer execution method takes the frame size from the dma_interleaved_template.sgl[] array for each frame. That array in accordance with [1] is supposed to be of dma_interleaved_template.frame_size size, which as we discovered before the code expects to be zero. So judging by the dw_edma_device_transfer() implementation, the method implies the dma_interleaved_template.sgl[] array being of dma_interleaved_template.numf size, which is wrong. Since the dw_edma_device_transfer() method doesn't permit dma_interleaved_template.frame_size being non-zero, the multi-chunk interleaved transfer turns to be unsupported even though the code implies having it supported. Add fully functioning support of interleaved DMA transfers. First of all, dma_interleaved_template.frame_size is supposed to be greater or equal to one thus having at least simple linear chunked frames. Secondly, we can create a walk-through over all the chunks and frames by initializing the number of the eDMA burst transactions as a multiple of dma_interleaved_template.numf and dma_interleaved_template.frame_size and getting the frame_size-modulo of the iteration step as an index of the dma_interleaved_template.sgl[] array. [1] include/linux/dmaengine.h: doc struct dma_interleaved_template Link: https://lore.kernel.org/r/20230113171409.30470-7-Sergey.Semin@baikalelectronics.ru Fixes: 85e7518f42c8 ("dmaengine: dw-edma: Add device_prep_interleave_dma() support") Tested-by: Manivannan Sadhasivam Signed-off-by: Serge Semin Signed-off-by: Lorenzo Pieralisi Signed-off-by: Bjorn Helgaas Reviewed-by: Manivannan Sadhasivam Acked-by: Vinod Koul --- drivers/dma/dw-edma/dw-edma-core.c | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c index 35588e14f79a..d5c4192141ef 100644 --- a/drivers/dma/dw-edma/dw-edma-core.c +++ b/drivers/dma/dw-edma/dw-edma-core.c @@ -332,6 +332,7 @@ dw_edma_device_transfer(struct dw_edma_transfer *xfer) struct dw_edma_chunk *chunk; struct dw_edma_burst *burst; struct dw_edma_desc *desc; + size_t fsz = 0; u32 cnt = 0; int i; @@ -381,9 +382,7 @@ dw_edma_device_transfer(struct dw_edma_transfer *xfer) if (xfer->xfer.sg.len < 1) return NULL; } else if (xfer->type == EDMA_XFER_INTERLEAVED) { - if (!xfer->xfer.il->numf) - return NULL; - if (xfer->xfer.il->numf > 0 && xfer->xfer.il->frame_size > 0) + if (!xfer->xfer.il->numf || xfer->xfer.il->frame_size < 1) return NULL; if (!xfer->xfer.il->src_inc || !xfer->xfer.il->dst_inc) return NULL; @@ -413,10 +412,8 @@ dw_edma_device_transfer(struct dw_edma_transfer *xfer) cnt = xfer->xfer.sg.len; sg = xfer->xfer.sg.sgl; } else if (xfer->type == EDMA_XFER_INTERLEAVED) { - if (xfer->xfer.il->numf > 0) - cnt = xfer->xfer.il->numf; - else - cnt = xfer->xfer.il->frame_size; + cnt = xfer->xfer.il->numf * xfer->xfer.il->frame_size; + fsz = xfer->xfer.il->frame_size; } for (i = 0; i < cnt; i++) { @@ -438,7 +435,7 @@ dw_edma_device_transfer(struct dw_edma_transfer *xfer) else if (xfer->type == EDMA_XFER_SCATTER_GATHER) burst->sz = sg_dma_len(sg); else if (xfer->type == EDMA_XFER_INTERLEAVED) - burst->sz = xfer->xfer.il->sgl[i].size; + burst->sz = xfer->xfer.il->sgl[i % fsz].size; chunk->ll_region.sz += burst->sz; desc->alloc_sz += burst->sz; @@ -481,10 +478,9 @@ dw_edma_device_transfer(struct dw_edma_transfer *xfer) if (xfer->type == EDMA_XFER_SCATTER_GATHER) { sg = sg_next(sg); - } else if (xfer->type == EDMA_XFER_INTERLEAVED && - xfer->xfer.il->frame_size > 0) { + } else if (xfer->type == EDMA_XFER_INTERLEAVED) { struct dma_interleaved_template *il = xfer->xfer.il; - struct data_chunk *dc = &il->sgl[i]; + struct data_chunk *dc = &il->sgl[i % fsz]; src_addr += burst->sz; if (il->src_sgl) -- cgit v1.2.3 From 993d57bbaacf7ad7a742e51cb717e21e0eb4ef72 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 13 Jan 2023 20:13:49 +0300 Subject: dmaengine: dw-edma: Add CPU to PCI bus address translation Since 9575632052ba ("dmaengine: make slave address physical"), the source and destination addresses of the DMA slave device have been converted to physical addresses in the CPU address space. It's the DMA device driver's responsibility to convert them to the DMA bus address space. In case of the DW eDMA device, the source or destination peripheral (slave) devices reside in PCI bus space. Thus we need to perform the PCI Host/Endpoint windows- based (i.e. DT "ranges" property) address translation; otherwise the eDMA transactions won't work as expected (or can be even harmful) if the CPU and PCI address spaces don't match. Note 1: Even though the DMA interleaved template has both source and destination addresses declared as dma_addr_t, only the CPU memory range should be mapped to be seen by the DMA device since it's a subject of the DMA getting towards the system side. The device part must not be mapped since the slave device resides in the PCI bus space, which isn't affected by IOMMUs or iATU translations. DW PCIe eDMA generates corresponding MWr/MRd TLPs on its own. Note 2: This functionality is mainly required for the remote eDMA setup since the CPU address must be manually translated into the PCI bus space before being written to LLI.{SAR,DAR}. If eDMA is embedded in the locally accessible DW PCIe Root Port/Endpoint, software-based translation isn't required since hardware will translate it via the Outbound iATU as long as the DMA_BYPASS flag is cleared. If DMA_BYPASS is set or there is no Outbound iATU entry that contains the SAR or DAR (for Read and Write channel respectively), there won't be any translation performed but DMA will proceed with the corresponding source/destination address as-is. Link: https://lore.kernel.org/r/20230113171409.30470-8-Sergey.Semin@baikalelectronics.ru Tested-by: Manivannan Sadhasivam Signed-off-by: Serge Semin Signed-off-by: Lorenzo Pieralisi Signed-off-by: Bjorn Helgaas Reviewed-by: Manivannan Sadhasivam Acked-by: Vinod Koul --- drivers/dma/dw-edma/dw-edma-core.c | 18 +++++++++++++++++- include/linux/dma/edma.h | 15 +++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c index d5c4192141ef..6c9f95a8e397 100644 --- a/drivers/dma/dw-edma/dw-edma-core.c +++ b/drivers/dma/dw-edma/dw-edma-core.c @@ -39,6 +39,17 @@ struct dw_edma_desc *vd2dw_edma_desc(struct virt_dma_desc *vd) return container_of(vd, struct dw_edma_desc, vd); } +static inline +u64 dw_edma_get_pci_address(struct dw_edma_chan *chan, phys_addr_t cpu_addr) +{ + struct dw_edma_chip *chip = chan->dw->chip; + + if (chip->ops->pci_address) + return chip->ops->pci_address(chip->dev, cpu_addr); + + return cpu_addr; +} + static struct dw_edma_burst *dw_edma_alloc_burst(struct dw_edma_chunk *chunk) { struct dw_edma_burst *burst; @@ -327,11 +338,11 @@ dw_edma_device_transfer(struct dw_edma_transfer *xfer) { struct dw_edma_chan *chan = dchan2dw_edma_chan(xfer->dchan); enum dma_transfer_direction dir = xfer->direction; - phys_addr_t src_addr, dst_addr; struct scatterlist *sg = NULL; struct dw_edma_chunk *chunk; struct dw_edma_burst *burst; struct dw_edma_desc *desc; + u64 src_addr, dst_addr; size_t fsz = 0; u32 cnt = 0; int i; @@ -406,6 +417,11 @@ dw_edma_device_transfer(struct dw_edma_transfer *xfer) dst_addr = chan->config.dst_addr; } + if (dir == DMA_DEV_TO_MEM) + src_addr = dw_edma_get_pci_address(chan, (phys_addr_t)src_addr); + else + dst_addr = dw_edma_get_pci_address(chan, (phys_addr_t)dst_addr); + if (xfer->type == EDMA_XFER_CYCLIC) { cnt = xfer->xfer.cyclic.cnt; } else if (xfer->type == EDMA_XFER_SCATTER_GATHER) { diff --git a/include/linux/dma/edma.h b/include/linux/dma/edma.h index a864978ddd27..07a23ecc834f 100644 --- a/include/linux/dma/edma.h +++ b/include/linux/dma/edma.h @@ -23,8 +23,23 @@ struct dw_edma_region { size_t sz; }; +/** + * struct dw_edma_core_ops - platform-specific eDMA methods + * @irq_vector: Get IRQ number of the passed eDMA channel. Note the + * method accepts the channel id in the end-to-end + * numbering with the eDMA write channels being placed + * first in the row. + * @pci_address: Get PCIe bus address corresponding to the passed CPU + * address. Note there is no need in specifying this + * function if the address translation is performed by + * the DW PCIe RP/EP controller with the DW eDMA device in + * subject and DMA_BYPASS isn't set for all the outbound + * iATU windows. That will be done by the controller + * automatically. + */ struct dw_edma_core_ops { int (*irq_vector)(struct device *dev, unsigned int nr); + u64 (*pci_address)(struct device *dev, phys_addr_t cpu_addr); }; enum dw_edma_map_format { -- cgit v1.2.3 From aa92fa1e53befd7c0ff50f8346cb7980584f0beb Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 13 Jan 2023 20:13:50 +0300 Subject: dmaengine: dw-edma: Add PCI bus address getter to the remote EP glue driver The Synopsys PCIe Endpoint IP prototype kit can be attached via any PCI host controller, including one where the PCI bus address space is different from the CPU address space. Therefore, we need to make sure the source and destination addresses of the DMA slave devices are converted to the PCI bus address space; otherwise DMA transactions may cause memory corruption. Add a new dw_edma_pcie_address() interface to perform this translation by using pcibios_resource_to_bus(). Link: https://lore.kernel.org/r/20230113171409.30470-9-Sergey.Semin@baikalelectronics.ru Fixes: 41aaff2a2ac0 ("dmaengine: Add Synopsys eDMA IP PCIe glue-logic") Tested-by: Manivannan Sadhasivam Signed-off-by: Serge Semin Signed-off-by: Lorenzo Pieralisi Signed-off-by: Bjorn Helgaas Reviewed-by: Manivannan Sadhasivam Acked-by: Vinod Koul --- drivers/dma/dw-edma/dw-edma-pcie.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/drivers/dma/dw-edma/dw-edma-pcie.c b/drivers/dma/dw-edma/dw-edma-pcie.c index 04c95cba1244..f530bacfd716 100644 --- a/drivers/dma/dw-edma/dw-edma-pcie.c +++ b/drivers/dma/dw-edma/dw-edma-pcie.c @@ -95,8 +95,23 @@ static int dw_edma_pcie_irq_vector(struct device *dev, unsigned int nr) return pci_irq_vector(to_pci_dev(dev), nr); } +static u64 dw_edma_pcie_address(struct device *dev, phys_addr_t cpu_addr) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct pci_bus_region region; + struct resource res = { + .flags = IORESOURCE_MEM, + .start = cpu_addr, + .end = cpu_addr, + }; + + pcibios_resource_to_bus(pdev->bus, ®ion, &res); + return region.start; +} + static const struct dw_edma_core_ops dw_edma_pcie_core_ops = { .irq_vector = dw_edma_pcie_irq_vector, + .pci_address = dw_edma_pcie_address, }; static void dw_edma_pcie_get_vsec_dma_data(struct pci_dev *pdev, -- cgit v1.2.3 From 2271216e0aac2409f2cd9a0650c460b89f7d2446 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 13 Jan 2023 20:13:51 +0300 Subject: dmaengine: dw-edma: Drop chancnt initialization The DMA engine core manages dma_device.chancnt itself, e.g., in dma_async_device_register(). DMA device drivers should not initialize chancnt because it causes the wrong number of channels printed in the device summary. Drop the dw-edma chancnt initialization. Link: https://lore.kernel.org/r/20230113171409.30470-10-Sergey.Semin@baikalelectronics.ru Fixes: e63d79d1ffcd ("dmaengine: Add Synopsys eDMA IP core driver") Tested-by: Manivannan Sadhasivam Signed-off-by: Serge Semin Signed-off-by: Lorenzo Pieralisi Signed-off-by: Bjorn Helgaas Reviewed-by: Manivannan Sadhasivam Acked-by: Vinod Koul --- drivers/dma/dw-edma/dw-edma-core.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c index 6c9f95a8e397..ecd3e8f7ac5d 100644 --- a/drivers/dma/dw-edma/dw-edma-core.c +++ b/drivers/dma/dw-edma/dw-edma-core.c @@ -817,7 +817,6 @@ static int dw_edma_channel_setup(struct dw_edma *dw, bool write, dma->src_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_4_BYTES); dma->dst_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_4_BYTES); dma->residue_granularity = DMA_RESIDUE_GRANULARITY_DESCRIPTOR; - dma->chancnt = cnt; /* Set DMA channel callbacks */ dma->dev = chip->dev; -- cgit v1.2.3 From 7ca9f025a731b3391f9321a324d8eb7a5b008c5a Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 13 Jan 2023 20:13:52 +0300 Subject: dmaengine: dw-edma: Drop unnecessary debugfs reg casts The debugfs_entries structure declared in dw-edma-v0-debugfs.c contains the debugfs node register address. The address is declared as dma_addr_t type, but is cast to "void *". Change the type to "void __iomem *" and drop the unnecessary casts. Link: https://lore.kernel.org/r/20230113171409.30470-11-Sergey.Semin@baikalelectronics.ru Fixes: 305aebeff879 ("dmaengine: Add Synopsys eDMA IP version 0 debugfs support") Tested-by: Manivannan Sadhasivam Signed-off-by: Serge Semin Signed-off-by: Lorenzo Pieralisi Signed-off-by: Bjorn Helgaas Reviewed-by: Manivannan Sadhasivam Acked-by: Vinod Koul --- drivers/dma/dw-edma/dw-edma-v0-debugfs.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/dma/dw-edma/dw-edma-v0-debugfs.c b/drivers/dma/dw-edma/dw-edma-v0-debugfs.c index 5226c9014703..8e61810dea4b 100644 --- a/drivers/dma/dw-edma/dw-edma-v0-debugfs.c +++ b/drivers/dma/dw-edma/dw-edma-v0-debugfs.c @@ -14,7 +14,7 @@ #include "dw-edma-core.h" #define REGS_ADDR(name) \ - ((void __force *)®s->name) + ((void __iomem *)®s->name) #define REGISTER(name) \ { #name, REGS_ADDR(name) } @@ -48,12 +48,13 @@ static struct { struct debugfs_entries { const char *name; - dma_addr_t *reg; + void __iomem *reg; }; static int dw_edma_debugfs_u32_get(void *data, u64 *val) { - void __iomem *reg = (void __force __iomem *)data; + void __iomem *reg = data; + if (dw->chip->mf == EDMA_MF_EDMA_LEGACY && reg >= (void __iomem *)®s->type.legacy.ch) { void __iomem *ptr = ®s->type.legacy.ch; -- cgit v1.2.3 From 37d058aae75b965a1c597979a194c9dba79ac913 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 13 Jan 2023 20:13:53 +0300 Subject: dmaengine: dw-edma: Stop checking debugfs_create_*() return value The debugfs_create_*() functions never return NULL, so checking their return value for NULL is pointless. Secondly, the debugfs subsystem is designed to be as simple as possible, so if one of the debugfs_create_*() method in a hierarchy fails, the following methods should silently return the passed erroneous parental dentry. Finally, the code should work no matter whether anything debugfs-related fails. To make code simpler and debugfs-independent, stop checking the debugfs_create_*() return values. If the debugfs file system is unavailable, skip the debugfs node initialization altogether to preserve some memory space. Link: https://lore.kernel.org/r/20230113171409.30470-12-Sergey.Semin@baikalelectronics.ru Tested-by: Manivannan Sadhasivam Signed-off-by: Serge Semin Signed-off-by: Lorenzo Pieralisi Signed-off-by: Bjorn Helgaas Reviewed-by: Manivannan Sadhasivam Acked-by: Vinod Koul --- drivers/dma/dw-edma/dw-edma-v0-debugfs.c | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/drivers/dma/dw-edma/dw-edma-v0-debugfs.c b/drivers/dma/dw-edma/dw-edma-v0-debugfs.c index 8e61810dea4b..6e7f3ef60ca7 100644 --- a/drivers/dma/dw-edma/dw-edma-v0-debugfs.c +++ b/drivers/dma/dw-edma/dw-edma-v0-debugfs.c @@ -100,9 +100,8 @@ static void dw_edma_debugfs_create_x32(const struct debugfs_entries entries[], int i; for (i = 0; i < nr_entries; i++) { - if (!debugfs_create_file_unsafe(entries[i].name, 0444, dir, - entries[i].reg, &fops_x32)) - break; + debugfs_create_file_unsafe(entries[i].name, 0444, dir, + entries[i].reg, &fops_x32); } } @@ -168,8 +167,6 @@ static void dw_edma_debugfs_regs_wr(struct dentry *dir) char name[16]; regs_dir = debugfs_create_dir(WRITE_STR, dir); - if (!regs_dir) - return; nr_entries = ARRAY_SIZE(debugfs_regs); dw_edma_debugfs_create_x32(debugfs_regs, nr_entries, regs_dir); @@ -184,8 +181,6 @@ static void dw_edma_debugfs_regs_wr(struct dentry *dir) snprintf(name, sizeof(name), "%s:%d", CHANNEL_STR, i); ch_dir = debugfs_create_dir(name, regs_dir); - if (!ch_dir) - return; dw_edma_debugfs_regs_ch(®s->type.unroll.ch[i].wr, ch_dir); @@ -237,8 +232,6 @@ static void dw_edma_debugfs_regs_rd(struct dentry *dir) char name[16]; regs_dir = debugfs_create_dir(READ_STR, dir); - if (!regs_dir) - return; nr_entries = ARRAY_SIZE(debugfs_regs); dw_edma_debugfs_create_x32(debugfs_regs, nr_entries, regs_dir); @@ -253,8 +246,6 @@ static void dw_edma_debugfs_regs_rd(struct dentry *dir) snprintf(name, sizeof(name), "%s:%d", CHANNEL_STR, i); ch_dir = debugfs_create_dir(name, regs_dir); - if (!ch_dir) - return; dw_edma_debugfs_regs_ch(®s->type.unroll.ch[i].rd, ch_dir); @@ -273,8 +264,6 @@ static void dw_edma_debugfs_regs(void) int nr_entries; regs_dir = debugfs_create_dir(REGISTERS_STR, dw->debugfs); - if (!regs_dir) - return; nr_entries = ARRAY_SIZE(debugfs_regs); dw_edma_debugfs_create_x32(debugfs_regs, nr_entries, regs_dir); @@ -285,6 +274,9 @@ static void dw_edma_debugfs_regs(void) void dw_edma_v0_debugfs_on(struct dw_edma *_dw) { + if (!debugfs_initialized()) + return; + dw = _dw; if (!dw) return; @@ -294,8 +286,6 @@ void dw_edma_v0_debugfs_on(struct dw_edma *_dw) return; dw->debugfs = debugfs_create_dir(dw->name, NULL); - if (!dw->debugfs) - return; debugfs_create_u32("mf", 0444, dw->debugfs, &dw->chip->mf); debugfs_create_u16("wr_ch_cnt", 0444, dw->debugfs, &dw->wr_ch_cnt); -- cgit v1.2.3 From 345e3a95b2fb469eb62c54f650ffa560ba5ee79a Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 13 Jan 2023 20:13:54 +0300 Subject: dmaengine: dw-edma: Add dw_edma prefix to debugfs nodes descriptor Other local names include a "dw_edma" prefix. Add a "dw_edma" prefix to the debugfs_entries structure, too, so it won't be confused with global debugfs things. Link: https://lore.kernel.org/r/20230113171409.30470-13-Sergey.Semin@baikalelectronics.ru Tested-by: Manivannan Sadhasivam Signed-off-by: Serge Semin Signed-off-by: Lorenzo Pieralisi Signed-off-by: Bjorn Helgaas Reviewed-by: Manivannan Sadhasivam Acked-by: Vinod Koul --- drivers/dma/dw-edma/dw-edma-v0-debugfs.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/dma/dw-edma/dw-edma-v0-debugfs.c b/drivers/dma/dw-edma/dw-edma-v0-debugfs.c index 6e7f3ef60ca7..2121ffc33cf3 100644 --- a/drivers/dma/dw-edma/dw-edma-v0-debugfs.c +++ b/drivers/dma/dw-edma/dw-edma-v0-debugfs.c @@ -46,7 +46,7 @@ static struct { void __iomem *end; } lim[2][EDMA_V0_MAX_NR_CH]; -struct debugfs_entries { +struct dw_edma_debugfs_entry { const char *name; void __iomem *reg; }; @@ -94,7 +94,7 @@ legacy_sel_wr: } DEFINE_DEBUGFS_ATTRIBUTE(fops_x32, dw_edma_debugfs_u32_get, NULL, "0x%08llx\n"); -static void dw_edma_debugfs_create_x32(const struct debugfs_entries entries[], +static void dw_edma_debugfs_create_x32(const struct dw_edma_debugfs_entry entries[], int nr_entries, struct dentry *dir) { int i; @@ -108,8 +108,7 @@ static void dw_edma_debugfs_create_x32(const struct debugfs_entries entries[], static void dw_edma_debugfs_regs_ch(struct dw_edma_v0_ch_regs __iomem *regs, struct dentry *dir) { - int nr_entries; - const struct debugfs_entries debugfs_regs[] = { + const struct dw_edma_debugfs_entry debugfs_regs[] = { REGISTER(ch_control1), REGISTER(ch_control2), REGISTER(transfer_size), @@ -120,6 +119,7 @@ static void dw_edma_debugfs_regs_ch(struct dw_edma_v0_ch_regs __iomem *regs, REGISTER(llp.lsb), REGISTER(llp.msb), }; + int nr_entries; nr_entries = ARRAY_SIZE(debugfs_regs); dw_edma_debugfs_create_x32(debugfs_regs, nr_entries, dir); @@ -127,7 +127,7 @@ static void dw_edma_debugfs_regs_ch(struct dw_edma_v0_ch_regs __iomem *regs, static void dw_edma_debugfs_regs_wr(struct dentry *dir) { - const struct debugfs_entries debugfs_regs[] = { + const struct dw_edma_debugfs_entry debugfs_regs[] = { /* eDMA global registers */ WR_REGISTER(engine_en), WR_REGISTER(doorbell), @@ -148,7 +148,7 @@ static void dw_edma_debugfs_regs_wr(struct dentry *dir) WR_REGISTER(ch67_imwr_data), WR_REGISTER(linked_list_err_en), }; - const struct debugfs_entries debugfs_unroll_regs[] = { + const struct dw_edma_debugfs_entry debugfs_unroll_regs[] = { /* eDMA channel context grouping */ WR_REGISTER_UNROLL(engine_chgroup), WR_REGISTER_UNROLL(engine_hshake_cnt.lsb), @@ -191,7 +191,7 @@ static void dw_edma_debugfs_regs_wr(struct dentry *dir) static void dw_edma_debugfs_regs_rd(struct dentry *dir) { - const struct debugfs_entries debugfs_regs[] = { + const struct dw_edma_debugfs_entry debugfs_regs[] = { /* eDMA global registers */ RD_REGISTER(engine_en), RD_REGISTER(doorbell), @@ -213,7 +213,7 @@ static void dw_edma_debugfs_regs_rd(struct dentry *dir) RD_REGISTER(ch45_imwr_data), RD_REGISTER(ch67_imwr_data), }; - const struct debugfs_entries debugfs_unroll_regs[] = { + const struct dw_edma_debugfs_entry debugfs_unroll_regs[] = { /* eDMA channel context grouping */ RD_REGISTER_UNROLL(engine_chgroup), RD_REGISTER_UNROLL(engine_hshake_cnt.lsb), @@ -256,7 +256,7 @@ static void dw_edma_debugfs_regs_rd(struct dentry *dir) static void dw_edma_debugfs_regs(void) { - const struct debugfs_entries debugfs_regs[] = { + const struct dw_edma_debugfs_entry debugfs_regs[] = { REGISTER(ctrl_data_arb_prior), REGISTER(ctrl), }; -- cgit v1.2.3 From 782536aac16166e85232e20e63596e9d6946dd15 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 13 Jan 2023 20:13:55 +0300 Subject: dmaengine: dw-edma: Convert debugfs descs to being heap-allocated Currently DW eDMA debugfs node descriptors are allocated on the stack, which won't work for multi-eDMA platforms. As a preparation to supporting multi-eDMA systems, allocate each debugfs node separately. Afterwards we'll add info like Read/Write channel flag, channel ID, DW eDMA private data reference. Note: this conversion is mainly required due to having the legacy DW eDMA controllers with indirect Read/Write channels context CSRs access. If we didn't need to synchronize access to these registers, the debugfs code of the driver would have been much simpler. Link: https://lore.kernel.org/r/20230113171409.30470-14-Sergey.Semin@baikalelectronics.ru Tested-by: Manivannan Sadhasivam Signed-off-by: Serge Semin Signed-off-by: Lorenzo Pieralisi Signed-off-by: Bjorn Helgaas Reviewed-by: Manivannan Sadhasivam Acked-by: Vinod Koul --- drivers/dma/dw-edma/dw-edma-v0-debugfs.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/drivers/dma/dw-edma/dw-edma-v0-debugfs.c b/drivers/dma/dw-edma/dw-edma-v0-debugfs.c index 2121ffc33cf3..78f15e4b07ac 100644 --- a/drivers/dma/dw-edma/dw-edma-v0-debugfs.c +++ b/drivers/dma/dw-edma/dw-edma-v0-debugfs.c @@ -53,7 +53,8 @@ struct dw_edma_debugfs_entry { static int dw_edma_debugfs_u32_get(void *data, u64 *val) { - void __iomem *reg = data; + struct dw_edma_debugfs_entry *entry = data; + void __iomem *reg = entry->reg; if (dw->chip->mf == EDMA_MF_EDMA_LEGACY && reg >= (void __iomem *)®s->type.legacy.ch) { @@ -94,14 +95,22 @@ legacy_sel_wr: } DEFINE_DEBUGFS_ATTRIBUTE(fops_x32, dw_edma_debugfs_u32_get, NULL, "0x%08llx\n"); -static void dw_edma_debugfs_create_x32(const struct dw_edma_debugfs_entry entries[], +static void dw_edma_debugfs_create_x32(const struct dw_edma_debugfs_entry ini[], int nr_entries, struct dentry *dir) { + struct dw_edma_debugfs_entry *entries; int i; + entries = devm_kcalloc(dw->chip->dev, nr_entries, sizeof(*entries), + GFP_KERNEL); + if (!entries) + return; + for (i = 0; i < nr_entries; i++) { + entries[i] = ini[i]; + debugfs_create_file_unsafe(entries[i].name, 0444, dir, - entries[i].reg, &fops_x32); + &entries[i], &fops_x32); } } -- cgit v1.2.3 From 95c55b7836f579ea6e46002226b02dddd6642a0d Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 13 Jan 2023 20:13:56 +0300 Subject: dmaengine: dw-edma: Rename debugfs dentry variables to 'dent' Since we are about to add the eDMA channels direction support to the debugfs module it will be confusing to have both the debugfs directory and the channels direction short names used in the same code. Rename the debugfs dentry 'dir' variables to 'dent' to prevent confusion. Suggested-by: Manivannan Sadhasivam Link: https://lore.kernel.org/r/20230113171409.30470-15-Sergey.Semin@baikalelectronics.ru Tested-by: Manivannan Sadhasivam Signed-off-by: Serge Semin Signed-off-by: Lorenzo Pieralisi Signed-off-by: Bjorn Helgaas Reviewed-by: Manivannan Sadhasivam Acked-by: Vinod Koul --- drivers/dma/dw-edma/dw-edma-v0-debugfs.c | 46 ++++++++++++++++---------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/drivers/dma/dw-edma/dw-edma-v0-debugfs.c b/drivers/dma/dw-edma/dw-edma-v0-debugfs.c index 78f15e4b07ac..7bb3363b40e4 100644 --- a/drivers/dma/dw-edma/dw-edma-v0-debugfs.c +++ b/drivers/dma/dw-edma/dw-edma-v0-debugfs.c @@ -96,7 +96,7 @@ legacy_sel_wr: DEFINE_DEBUGFS_ATTRIBUTE(fops_x32, dw_edma_debugfs_u32_get, NULL, "0x%08llx\n"); static void dw_edma_debugfs_create_x32(const struct dw_edma_debugfs_entry ini[], - int nr_entries, struct dentry *dir) + int nr_entries, struct dentry *dent) { struct dw_edma_debugfs_entry *entries; int i; @@ -109,13 +109,13 @@ static void dw_edma_debugfs_create_x32(const struct dw_edma_debugfs_entry ini[], for (i = 0; i < nr_entries; i++) { entries[i] = ini[i]; - debugfs_create_file_unsafe(entries[i].name, 0444, dir, + debugfs_create_file_unsafe(entries[i].name, 0444, dent, &entries[i], &fops_x32); } } static void dw_edma_debugfs_regs_ch(struct dw_edma_v0_ch_regs __iomem *regs, - struct dentry *dir) + struct dentry *dent) { const struct dw_edma_debugfs_entry debugfs_regs[] = { REGISTER(ch_control1), @@ -131,10 +131,10 @@ static void dw_edma_debugfs_regs_ch(struct dw_edma_v0_ch_regs __iomem *regs, int nr_entries; nr_entries = ARRAY_SIZE(debugfs_regs); - dw_edma_debugfs_create_x32(debugfs_regs, nr_entries, dir); + dw_edma_debugfs_create_x32(debugfs_regs, nr_entries, dent); } -static void dw_edma_debugfs_regs_wr(struct dentry *dir) +static void dw_edma_debugfs_regs_wr(struct dentry *dent) { const struct dw_edma_debugfs_entry debugfs_regs[] = { /* eDMA global registers */ @@ -171,34 +171,34 @@ static void dw_edma_debugfs_regs_wr(struct dentry *dir) WR_REGISTER_UNROLL(ch6_pwr_en), WR_REGISTER_UNROLL(ch7_pwr_en), }; - struct dentry *regs_dir, *ch_dir; + struct dentry *regs_dent, *ch_dent; int nr_entries, i; char name[16]; - regs_dir = debugfs_create_dir(WRITE_STR, dir); + regs_dent = debugfs_create_dir(WRITE_STR, dent); nr_entries = ARRAY_SIZE(debugfs_regs); - dw_edma_debugfs_create_x32(debugfs_regs, nr_entries, regs_dir); + dw_edma_debugfs_create_x32(debugfs_regs, nr_entries, regs_dent); if (dw->chip->mf == EDMA_MF_HDMA_COMPAT) { nr_entries = ARRAY_SIZE(debugfs_unroll_regs); dw_edma_debugfs_create_x32(debugfs_unroll_regs, nr_entries, - regs_dir); + regs_dent); } for (i = 0; i < dw->wr_ch_cnt; i++) { snprintf(name, sizeof(name), "%s:%d", CHANNEL_STR, i); - ch_dir = debugfs_create_dir(name, regs_dir); + ch_dent = debugfs_create_dir(name, regs_dent); - dw_edma_debugfs_regs_ch(®s->type.unroll.ch[i].wr, ch_dir); + dw_edma_debugfs_regs_ch(®s->type.unroll.ch[i].wr, ch_dent); lim[0][i].start = ®s->type.unroll.ch[i].wr; lim[0][i].end = ®s->type.unroll.ch[i].padding_1[0]; } } -static void dw_edma_debugfs_regs_rd(struct dentry *dir) +static void dw_edma_debugfs_regs_rd(struct dentry *dent) { const struct dw_edma_debugfs_entry debugfs_regs[] = { /* eDMA global registers */ @@ -236,27 +236,27 @@ static void dw_edma_debugfs_regs_rd(struct dentry *dir) RD_REGISTER_UNROLL(ch6_pwr_en), RD_REGISTER_UNROLL(ch7_pwr_en), }; - struct dentry *regs_dir, *ch_dir; + struct dentry *regs_dent, *ch_dent; int nr_entries, i; char name[16]; - regs_dir = debugfs_create_dir(READ_STR, dir); + regs_dent = debugfs_create_dir(READ_STR, dent); nr_entries = ARRAY_SIZE(debugfs_regs); - dw_edma_debugfs_create_x32(debugfs_regs, nr_entries, regs_dir); + dw_edma_debugfs_create_x32(debugfs_regs, nr_entries, regs_dent); if (dw->chip->mf == EDMA_MF_HDMA_COMPAT) { nr_entries = ARRAY_SIZE(debugfs_unroll_regs); dw_edma_debugfs_create_x32(debugfs_unroll_regs, nr_entries, - regs_dir); + regs_dent); } for (i = 0; i < dw->rd_ch_cnt; i++) { snprintf(name, sizeof(name), "%s:%d", CHANNEL_STR, i); - ch_dir = debugfs_create_dir(name, regs_dir); + ch_dent = debugfs_create_dir(name, regs_dent); - dw_edma_debugfs_regs_ch(®s->type.unroll.ch[i].rd, ch_dir); + dw_edma_debugfs_regs_ch(®s->type.unroll.ch[i].rd, ch_dent); lim[1][i].start = ®s->type.unroll.ch[i].rd; lim[1][i].end = ®s->type.unroll.ch[i].padding_2[0]; @@ -269,16 +269,16 @@ static void dw_edma_debugfs_regs(void) REGISTER(ctrl_data_arb_prior), REGISTER(ctrl), }; - struct dentry *regs_dir; + struct dentry *regs_dent; int nr_entries; - regs_dir = debugfs_create_dir(REGISTERS_STR, dw->debugfs); + regs_dent = debugfs_create_dir(REGISTERS_STR, dw->debugfs); nr_entries = ARRAY_SIZE(debugfs_regs); - dw_edma_debugfs_create_x32(debugfs_regs, nr_entries, regs_dir); + dw_edma_debugfs_create_x32(debugfs_regs, nr_entries, regs_dent); - dw_edma_debugfs_regs_wr(regs_dir); - dw_edma_debugfs_regs_rd(regs_dir); + dw_edma_debugfs_regs_wr(regs_dent); + dw_edma_debugfs_regs_rd(regs_dent); } void dw_edma_v0_debugfs_on(struct dw_edma *_dw) -- cgit v1.2.3 From 00498167650b682a053b85e0e705f59a54f427a3 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 13 Jan 2023 20:13:57 +0300 Subject: dmaengine: dw-edma: Simplify debugfs context CSRs init procedure DW eDMA v4.70a and older have the read and write channels context CSRs indirectly accessible, which means CSRs like Channel Control, Xfer size, SAR, DAR and LLP address are accessed at a fixed MMIO address, with their reference to the corresponding channel determined by the Viewport CSR. To have a coherent access to these registers the CSR IOs are supposed to be protected with a spinlock. DW eDMA v4.80a and newer normally have unrolled Read/Write channel context registers, with these CSRs directly mapped in the controller MMIO space. Both normal and viewport-based registers are exposed via debugfs nodes, and the original algorithm was based on the unrolled CSRs mapping and recalculated the viewport addresses when required. This is unscalable (it only supports a platform with a single eDMA since a base address is statically preserved) and also needlessly overcomplicated (it loops over all Rd/Wr context addresses and recalculates the viewport base address on each debugfs node access). Simplify the algorithm by adding the channel ID and its direction fields in the eDMA debugfs node descriptor. These new fields can be used to find a CSR offset in the channel register space. The DW eDMA debugfs node getter will also use them to activate the respective context CSRs viewport before reading data from the specified register. For the unrolled CSR mapping, no spinlock or viewport activation is needed. Note: this replaces some REGISTER() uses with CTX_REGISTER(), which avoids an implicit dependency on a local variable name. The same problem with the rest of the macro will be fixed in the next commit. Link: https://lore.kernel.org/r/20230113171409.30470-16-Sergey.Semin@baikalelectronics.ru Tested-by: Manivannan Sadhasivam Signed-off-by: Serge Semin Signed-off-by: Lorenzo Pieralisi Signed-off-by: Bjorn Helgaas Reviewed-by: Manivannan Sadhasivam Acked-by: Vinod Koul --- drivers/dma/dw-edma/dw-edma-v0-debugfs.c | 84 +++++++++++++++----------------- 1 file changed, 38 insertions(+), 46 deletions(-) diff --git a/drivers/dma/dw-edma/dw-edma-v0-debugfs.c b/drivers/dma/dw-edma/dw-edma-v0-debugfs.c index 7bb3363b40e4..fa63d1dd75b2 100644 --- a/drivers/dma/dw-edma/dw-edma-v0-debugfs.c +++ b/drivers/dma/dw-edma/dw-edma-v0-debugfs.c @@ -15,9 +15,27 @@ #define REGS_ADDR(name) \ ((void __iomem *)®s->name) + +#define REGS_CH_ADDR(name, _dir, _ch) \ + ({ \ + struct dw_edma_v0_ch_regs __iomem *__ch_regs; \ + \ + if ((dw)->chip->mf == EDMA_MF_EDMA_LEGACY) \ + __ch_regs = ®s->type.legacy.ch; \ + else if (_dir == EDMA_DIR_READ) \ + __ch_regs = ®s->type.unroll.ch[_ch].rd; \ + else \ + __ch_regs = ®s->type.unroll.ch[_ch].wr; \ + \ + (void __iomem *)&__ch_regs->name; \ + }) + #define REGISTER(name) \ { #name, REGS_ADDR(name) } +#define CTX_REGISTER(name, dir, ch) \ + { #name, REGS_CH_ADDR(name, dir, ch), dir, ch } + #define WR_REGISTER(name) \ { #name, REGS_ADDR(wr_##name) } #define RD_REGISTER(name) \ @@ -41,14 +59,11 @@ static struct dw_edma *dw; static struct dw_edma_v0_regs __iomem *regs; -static struct { - void __iomem *start; - void __iomem *end; -} lim[2][EDMA_V0_MAX_NR_CH]; - struct dw_edma_debugfs_entry { const char *name; void __iomem *reg; + enum dw_edma_dir dir; + u16 ch; }; static int dw_edma_debugfs_u32_get(void *data, u64 *val) @@ -58,33 +73,16 @@ static int dw_edma_debugfs_u32_get(void *data, u64 *val) if (dw->chip->mf == EDMA_MF_EDMA_LEGACY && reg >= (void __iomem *)®s->type.legacy.ch) { - void __iomem *ptr = ®s->type.legacy.ch; - u32 viewport_sel = 0; unsigned long flags; - u16 ch; - - for (ch = 0; ch < dw->wr_ch_cnt; ch++) - if (lim[0][ch].start >= reg && reg < lim[0][ch].end) { - ptr += (reg - lim[0][ch].start); - goto legacy_sel_wr; - } - - for (ch = 0; ch < dw->rd_ch_cnt; ch++) - if (lim[1][ch].start >= reg && reg < lim[1][ch].end) { - ptr += (reg - lim[1][ch].start); - goto legacy_sel_rd; - } - - return 0; -legacy_sel_rd: - viewport_sel = BIT(31); -legacy_sel_wr: - viewport_sel |= FIELD_PREP(EDMA_V0_VIEWPORT_MASK, ch); + u32 viewport_sel; + + viewport_sel = entry->dir == EDMA_DIR_READ ? BIT(31) : 0; + viewport_sel |= FIELD_PREP(EDMA_V0_VIEWPORT_MASK, entry->ch); raw_spin_lock_irqsave(&dw->lock, flags); writel(viewport_sel, ®s->type.legacy.viewport_sel); - *val = readl(ptr); + *val = readl(reg); raw_spin_unlock_irqrestore(&dw->lock, flags); } else { @@ -114,19 +112,19 @@ static void dw_edma_debugfs_create_x32(const struct dw_edma_debugfs_entry ini[], } } -static void dw_edma_debugfs_regs_ch(struct dw_edma_v0_ch_regs __iomem *regs, +static void dw_edma_debugfs_regs_ch(enum dw_edma_dir dir, u16 ch, struct dentry *dent) { - const struct dw_edma_debugfs_entry debugfs_regs[] = { - REGISTER(ch_control1), - REGISTER(ch_control2), - REGISTER(transfer_size), - REGISTER(sar.lsb), - REGISTER(sar.msb), - REGISTER(dar.lsb), - REGISTER(dar.msb), - REGISTER(llp.lsb), - REGISTER(llp.msb), + struct dw_edma_debugfs_entry debugfs_regs[] = { + CTX_REGISTER(ch_control1, dir, ch), + CTX_REGISTER(ch_control2, dir, ch), + CTX_REGISTER(transfer_size, dir, ch), + CTX_REGISTER(sar.lsb, dir, ch), + CTX_REGISTER(sar.msb, dir, ch), + CTX_REGISTER(dar.lsb, dir, ch), + CTX_REGISTER(dar.msb, dir, ch), + CTX_REGISTER(llp.lsb, dir, ch), + CTX_REGISTER(llp.msb, dir, ch), }; int nr_entries; @@ -191,10 +189,7 @@ static void dw_edma_debugfs_regs_wr(struct dentry *dent) ch_dent = debugfs_create_dir(name, regs_dent); - dw_edma_debugfs_regs_ch(®s->type.unroll.ch[i].wr, ch_dent); - - lim[0][i].start = ®s->type.unroll.ch[i].wr; - lim[0][i].end = ®s->type.unroll.ch[i].padding_1[0]; + dw_edma_debugfs_regs_ch(EDMA_DIR_WRITE, i, ch_dent); } } @@ -256,10 +251,7 @@ static void dw_edma_debugfs_regs_rd(struct dentry *dent) ch_dent = debugfs_create_dir(name, regs_dent); - dw_edma_debugfs_regs_ch(®s->type.unroll.ch[i].rd, ch_dent); - - lim[1][i].start = ®s->type.unroll.ch[i].rd; - lim[1][i].end = ®s->type.unroll.ch[i].padding_2[0]; + dw_edma_debugfs_regs_ch(EDMA_DIR_READ, i, ch_dent); } } -- cgit v1.2.3 From 63ba51db24ed1b8f8088a897290eb6c036c5435d Mon Sep 17 00:00:00 2001 From: Damien Le Moal Date: Sat, 28 Jan 2023 10:39:51 +0900 Subject: PCI: Avoid FLR for AMD FCH AHCI adapters PCI passthrough to VMs does not work with AMD FCH AHCI adapters: the guest OS fails to correctly probe devices attached to the controller due to FIS communication failures: ata4: softreset failed (1st FIS failed) ... ata4.00: qc timeout after 5000 msecs (cmd 0xec) ata4.00: failed to IDENTIFY (I/O error, err_mask=0x4) Forcing the "bus" reset method before unbinding & binding the adapter to the vfio-pci driver solves this issue, e.g.: echo "bus" > /sys/bus/pci/devices//reset_method gives a working guest OS, indicating that the default FLR reset method doesn't work correctly. Apply quirk_no_flr() to AMD FCH AHCI devices to work around this issue. Link: https://lore.kernel.org/r/20230128013951.523247-1-damien.lemoal@opensource.wdc.com Reported-by: Niklas Cassel Signed-off-by: Damien Le Moal Signed-off-by: Bjorn Helgaas Cc: stable@vger.kernel.org --- drivers/pci/quirks.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index 285acc4aaccc..20ac67d59034 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -5340,6 +5340,7 @@ static void quirk_no_flr(struct pci_dev *dev) DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_AMD, 0x1487, quirk_no_flr); DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_AMD, 0x148c, quirk_no_flr); DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_AMD, 0x149c, quirk_no_flr); +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_AMD, 0x7901, quirk_no_flr); DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, 0x1502, quirk_no_flr); DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, 0x1503, quirk_no_flr); -- cgit v1.2.3 From 62b6dee1b44aa23b3935543aff7df80399ec726b Mon Sep 17 00:00:00 2001 From: Huacai Chen Date: Wed, 1 Feb 2023 12:30:17 +0800 Subject: PCI/portdrv: Prevent LS7A Bus Master clearing on shutdown After cc27b735ad3a ("PCI/portdrv: Turn off PCIe services during shutdown") we observe hangs during poweroff/reboot on systems with LS7A chipset. This happens because the portdrv .shutdown() method (pcie_portdrv_remove()) clears PCI_COMMAND_MASTER via pci_disable_device(), which prevents bridges from forwarding memory or I/O Requests in the upstream direction (PCIe r6.0, sec 7.5.1.1.3). LS7A Root Ports have a hardware defect: clearing PCI_COMMAND_MASTER *also* prevents the bridge from forwarding CPU MMIO requests in the downstream direction, and these MMIO accesses to devices below the bridge happen even after .shutdown(), e.g., to print console messages. LS7A neither forwards the requests nor sends an unsuccessful completion to the CPU, so the CPU waits forever, resulting in the hang. The purpose of .shutdown() is to disable interrupts and DMA from the device. PCIe ports may generate interrupts (either MSI/MSI-X or INTx) for AER, DPC, PME, hotplug, etc., but they never perform DMA except MSI/MSI-X. Clearing PCI_COMMAND_MASTER effectively disables MSI/MSI-X, but not INTx. The port service driver .remove() methods clear the interrupt enables in PCI_ERR_ROOT_COMMAND, PCI_EXP_DPC_CTL, PCI_EXP_SLTCTL, and PCI_EXP_RTCTL, etc., which disables interrupts regardless of whether they are MSI/MSI-X or INTx. Add a pcie_portdrv_shutdown() method that calls all the port service driver .remove() methods to clear the interrupt enables for each service but does not clear Bus Mastering on the port itself. [bhelgaas: commit log] Link: https://lore.kernel.org/r/20230201043018.778499-2-chenhuacai@loongson.cn Signed-off-by: Huacai Chen Signed-off-by: Bjorn Helgaas --- drivers/pci/pcie/portdrv.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/drivers/pci/pcie/portdrv.c b/drivers/pci/pcie/portdrv.c index 2cc2e60bcb39..46fad0d813b2 100644 --- a/drivers/pci/pcie/portdrv.c +++ b/drivers/pci/pcie/portdrv.c @@ -501,7 +501,6 @@ static void pcie_port_device_remove(struct pci_dev *dev) { device_for_each_child(&dev->dev, NULL, remove_iter); pci_free_irq_vectors(dev); - pci_disable_device(dev); } /** @@ -727,6 +726,19 @@ static void pcie_portdrv_remove(struct pci_dev *dev) } pcie_port_device_remove(dev); + + pci_disable_device(dev); +} + +static void pcie_portdrv_shutdown(struct pci_dev *dev) +{ + if (pci_bridge_d3_possible(dev)) { + pm_runtime_forbid(&dev->dev); + pm_runtime_get_noresume(&dev->dev); + pm_runtime_dont_use_autosuspend(&dev->dev); + } + + pcie_port_device_remove(dev); } static pci_ers_result_t pcie_portdrv_error_detected(struct pci_dev *dev, @@ -777,7 +789,7 @@ static struct pci_driver pcie_portdriver = { .probe = pcie_portdrv_probe, .remove = pcie_portdrv_remove, - .shutdown = pcie_portdrv_remove, + .shutdown = pcie_portdrv_shutdown, .err_handler = &pcie_portdrv_err_handler, -- cgit v1.2.3 From 8b3517f88ff2983f52698893519227c10aac90b2 Mon Sep 17 00:00:00 2001 From: Huacai Chen Date: Wed, 1 Feb 2023 12:30:18 +0800 Subject: PCI: loongson: Prevent LS7A MRRS increases Except for isochronous-configured devices, software may set Max_Read_Request_Size (MRRS) to any value up to 4096. If a device issues a read request with size greater than the completer's Max_Payload_Size (MPS), the completer is required to break the response into multiple completions. Instead of correctly responding with multiple completions to a large read request, some LS7A Root Ports respond with a Completer Abort. To prevent this, the MRRS must be limited to an implementation-specific value. The OS cannot detect that value, so rely on BIOS to configure MRRS before booting, and quirk the Root Ports so we never set an MRRS larger than that BIOS value for any downstream device. N.B. Hot-added devices are not configured by BIOS, and they power up with MRRS = 512 bytes, so these devices will be limited to 512 bytes. If the LS7A limit is smaller, those hot-added devices may not work correctly, but per [1], hotplug is not supported with this chipset revision. [1] https://lore.kernel.org/r/073638a7-ae68-2847-ac3d-29e5e760d6af@loongson.cn [bhelgaas: commit log] Link: https://bugzilla.kernel.org/show_bug.cgi?id=216884 Link: https://lore.kernel.org/r/20230201043018.778499-3-chenhuacai@loongson.cn Signed-off-by: Huacai Chen Signed-off-by: Bjorn Helgaas --- drivers/pci/controller/pci-loongson.c | 44 ++++++++++++----------------------- drivers/pci/pci.c | 10 ++++++++ include/linux/pci.h | 1 + 3 files changed, 26 insertions(+), 29 deletions(-) diff --git a/drivers/pci/controller/pci-loongson.c b/drivers/pci/controller/pci-loongson.c index 05c50408f13b..759ec211c17b 100644 --- a/drivers/pci/controller/pci-loongson.c +++ b/drivers/pci/controller/pci-loongson.c @@ -75,37 +75,23 @@ DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, DEV_LS7A_LPC, system_bus_quirk); -static void loongson_mrrs_quirk(struct pci_dev *dev) +static void loongson_mrrs_quirk(struct pci_dev *pdev) { - struct pci_bus *bus = dev->bus; - struct pci_dev *bridge; - static const struct pci_device_id bridge_devids[] = { - { PCI_VDEVICE(LOONGSON, DEV_PCIE_PORT_0) }, - { PCI_VDEVICE(LOONGSON, DEV_PCIE_PORT_1) }, - { PCI_VDEVICE(LOONGSON, DEV_PCIE_PORT_2) }, - { 0, }, - }; - - /* look for the matching bridge */ - while (!pci_is_root_bus(bus)) { - bridge = bus->self; - bus = bus->parent; - /* - * Some Loongson PCIe ports have a h/w limitation of - * 256 bytes maximum read request size. They can't handle - * anything larger than this. So force this limit on - * any devices attached under these ports. - */ - if (pci_match_id(bridge_devids, bridge)) { - if (pcie_get_readrq(dev) > 256) { - pci_info(dev, "limiting MRRS to 256\n"); - pcie_set_readrq(dev, 256); - } - break; - } - } + /* + * Some Loongson PCIe ports have h/w limitations of maximum read + * request size. They can't handle anything larger than this. So + * force this limit on any devices attached under these ports. + */ + struct pci_host_bridge *bridge = pci_find_host_bridge(pdev->bus); + + bridge->no_inc_mrrs = 1; } -DECLARE_PCI_FIXUP_ENABLE(PCI_ANY_ID, PCI_ANY_ID, loongson_mrrs_quirk); +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, + DEV_PCIE_PORT_0, loongson_mrrs_quirk); +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, + DEV_PCIE_PORT_1, loongson_mrrs_quirk); +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, + DEV_PCIE_PORT_2, loongson_mrrs_quirk); static void loongson_pci_pin_quirk(struct pci_dev *pdev) { diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index fba95486caaf..9d98bba72af9 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -6033,6 +6033,7 @@ int pcie_set_readrq(struct pci_dev *dev, int rq) { u16 v; int ret; + struct pci_host_bridge *bridge = pci_find_host_bridge(dev->bus); if (rq < 128 || rq > 4096 || !is_power_of_2(rq)) return -EINVAL; @@ -6051,6 +6052,15 @@ int pcie_set_readrq(struct pci_dev *dev, int rq) v = (ffs(rq) - 8) << 12; + if (bridge->no_inc_mrrs) { + int max_mrrs = pcie_get_readrq(dev); + + if (rq > max_mrrs) { + pci_info(dev, "can't set Max_Read_Request_Size to %d; max is %d\n", rq, max_mrrs); + return -EINVAL; + } + } + ret = pcie_capability_clear_and_set_word(dev, PCI_EXP_DEVCTL, PCI_EXP_DEVCTL_READRQ, v); diff --git a/include/linux/pci.h b/include/linux/pci.h index adffd65e84b4..3df2049ec4a8 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -572,6 +572,7 @@ struct pci_host_bridge { void *release_data; unsigned int ignore_reset_delay:1; /* For entire hierarchy */ unsigned int no_ext_tags:1; /* No Extended Tags */ + unsigned int no_inc_mrrs:1; /* No Increase MRRS */ unsigned int native_aer:1; /* OS may use PCIe AER */ unsigned int native_pcie_hotplug:1; /* OS may use PCIe hotplug */ unsigned int native_shpc_hotplug:1; /* OS may use SHPC hotplug */ -- cgit v1.2.3 From de82f60f9c86b72635ce49f7ab822e6a00a90dca Mon Sep 17 00:00:00 2001 From: Michael Bottini Date: Thu, 19 Jan 2023 19:15:19 -0800 Subject: PCI/ASPM: Add pci_enable_link_state() Add pci_enable_link_state() to allow devices to change the default BIOS configured states. Clears the BIOS default settings then sets the new states and reconfigures the link under the semaphore. Also add PCIE_LINK_STATE_ALL macro for convenience for callers that want to enable all link states. Link: https://lore.kernel.org/r/20230120031522.2304439-2-david.e.box@linux.intel.com Signed-off-by: Michael Bottini Signed-off-by: David E. Box Signed-off-by: Lorenzo Pieralisi Reviewed-by: Kuppuswamy Sathyanarayanan Acked-by: Bjorn Helgaas --- drivers/pci/pcie/aspm.c | 54 +++++++++++++++++++++++++++++++++++++++++++++++++ include/linux/pci.h | 7 +++++++ 2 files changed, 61 insertions(+) diff --git a/drivers/pci/pcie/aspm.c b/drivers/pci/pcie/aspm.c index 53a1fa306e1e..339c686a5094 100644 --- a/drivers/pci/pcie/aspm.c +++ b/drivers/pci/pcie/aspm.c @@ -1181,6 +1181,60 @@ int pci_disable_link_state(struct pci_dev *pdev, int state) } EXPORT_SYMBOL(pci_disable_link_state); +/** + * pci_enable_link_state - Clear and set the default device link state so that + * the link may be allowed to enter the specified states. Note that if the + * BIOS didn't grant ASPM control to the OS, this does nothing because we can't + * touch the LNKCTL register. Also note that this does not enable states + * disabled by pci_disable_link_state(). Return 0 or a negative errno. + * + * @pdev: PCI device + * @state: Mask of ASPM link states to enable + */ +int pci_enable_link_state(struct pci_dev *pdev, int state) +{ + struct pcie_link_state *link = pcie_aspm_get_link(pdev); + + if (!link) + return -EINVAL; + /* + * A driver requested that ASPM be enabled on this device, but + * if we don't have permission to manage ASPM (e.g., on ACPI + * systems we have to observe the FADT ACPI_FADT_NO_ASPM bit and + * the _OSC method), we can't honor that request. + */ + if (aspm_disabled) { + pci_warn(pdev, "can't override BIOS ASPM; OS doesn't have ASPM control\n"); + return -EPERM; + } + + down_read(&pci_bus_sem); + mutex_lock(&aspm_lock); + link->aspm_default = 0; + if (state & PCIE_LINK_STATE_L0S) + link->aspm_default |= ASPM_STATE_L0S; + if (state & PCIE_LINK_STATE_L1) + /* L1 PM substates require L1 */ + link->aspm_default |= ASPM_STATE_L1 | ASPM_STATE_L1SS; + if (state & PCIE_LINK_STATE_L1_1) + link->aspm_default |= ASPM_STATE_L1_1; + if (state & PCIE_LINK_STATE_L1_2) + link->aspm_default |= ASPM_STATE_L1_2; + if (state & PCIE_LINK_STATE_L1_1_PCIPM) + link->aspm_default |= ASPM_STATE_L1_1_PCIPM; + if (state & PCIE_LINK_STATE_L1_2_PCIPM) + link->aspm_default |= ASPM_STATE_L1_2_PCIPM; + pcie_config_aspm_link(link, policy_to_aspm_state(link)); + + link->clkpm_default = (state & PCIE_LINK_STATE_CLKPM) ? 1 : 0; + pcie_set_clkpm(link, policy_to_clkpm_state(link)); + mutex_unlock(&aspm_lock); + up_read(&pci_bus_sem); + + return 0; +} +EXPORT_SYMBOL(pci_enable_link_state); + static int pcie_aspm_set_policy(const char *val, const struct kernel_param *kp) { diff --git a/include/linux/pci.h b/include/linux/pci.h index adffd65e84b4..ea601e6fbbda 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -1685,10 +1685,15 @@ extern bool pcie_ports_native; #define PCIE_LINK_STATE_L1_2 BIT(4) #define PCIE_LINK_STATE_L1_1_PCIPM BIT(5) #define PCIE_LINK_STATE_L1_2_PCIPM BIT(6) +#define PCIE_LINK_STATE_ALL (PCIE_LINK_STATE_L0S | PCIE_LINK_STATE_L1 |\ + PCIE_LINK_STATE_CLKPM | PCIE_LINK_STATE_L1_1 |\ + PCIE_LINK_STATE_L1_2 | PCIE_LINK_STATE_L1_1_PCIPM |\ + PCIE_LINK_STATE_L1_2_PCIPM) #ifdef CONFIG_PCIEASPM int pci_disable_link_state(struct pci_dev *pdev, int state); int pci_disable_link_state_locked(struct pci_dev *pdev, int state); +int pci_enable_link_state(struct pci_dev *pdev, int state); void pcie_no_aspm(void); bool pcie_aspm_support_enabled(void); bool pcie_aspm_enabled(struct pci_dev *pdev); @@ -1697,6 +1702,8 @@ static inline int pci_disable_link_state(struct pci_dev *pdev, int state) { return 0; } static inline int pci_disable_link_state_locked(struct pci_dev *pdev, int state) { return 0; } +static inline int pci_enable_link_state(struct pci_dev *pdev, int state) +{ return 0; } static inline void pcie_no_aspm(void) { } static inline bool pcie_aspm_support_enabled(void) { return false; } static inline bool pcie_aspm_enabled(struct pci_dev *pdev) { return false; } -- cgit v1.2.3 From cca0dfecdba3f37c08b7ae99ce07197be84b9281 Mon Sep 17 00:00:00 2001 From: "David E. Box" Date: Thu, 19 Jan 2023 19:15:20 -0800 Subject: PCI: vmd: Use PCI_VDEVICE in device list Use PCI_VDEVICE to simplify the device table. Link: https://lore.kernel.org/r/20230120031522.2304439-3-david.e.box@linux.intel.com Signed-off-by: David E. Box Signed-off-by: Lorenzo Pieralisi Reviewed-by: Jon Derrick Reviewed-by: Nirmal Patel Reviewed-by: Kuppuswamy Sathyanarayanan --- drivers/pci/controller/vmd.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/pci/controller/vmd.c b/drivers/pci/controller/vmd.c index 769eedeb8802..1fc3f2174359 100644 --- a/drivers/pci/controller/vmd.c +++ b/drivers/pci/controller/vmd.c @@ -1017,33 +1017,33 @@ static int vmd_resume(struct device *dev) static SIMPLE_DEV_PM_OPS(vmd_dev_pm_ops, vmd_suspend, vmd_resume); static const struct pci_device_id vmd_ids[] = { - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_VMD_201D), + {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_VMD_201D), .driver_data = VMD_FEAT_HAS_MEMBAR_SHADOW_VSCAP,}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_VMD_28C0), + {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_VMD_28C0), .driver_data = VMD_FEAT_HAS_MEMBAR_SHADOW | VMD_FEAT_HAS_BUS_RESTRICTIONS | VMD_FEAT_CAN_BYPASS_MSI_REMAP,}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x467f), + {PCI_VDEVICE(INTEL, 0x467f), .driver_data = VMD_FEAT_HAS_MEMBAR_SHADOW_VSCAP | VMD_FEAT_HAS_BUS_RESTRICTIONS | VMD_FEAT_OFFSET_FIRST_VECTOR,}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x4c3d), + {PCI_VDEVICE(INTEL, 0x4c3d), .driver_data = VMD_FEAT_HAS_MEMBAR_SHADOW_VSCAP | VMD_FEAT_HAS_BUS_RESTRICTIONS | VMD_FEAT_OFFSET_FIRST_VECTOR,}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xa77f), + {PCI_VDEVICE(INTEL, 0xa77f), .driver_data = VMD_FEAT_HAS_MEMBAR_SHADOW_VSCAP | VMD_FEAT_HAS_BUS_RESTRICTIONS | VMD_FEAT_OFFSET_FIRST_VECTOR,}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x7d0b), + {PCI_VDEVICE(INTEL, 0x7d0b), .driver_data = VMD_FEAT_HAS_MEMBAR_SHADOW_VSCAP | VMD_FEAT_HAS_BUS_RESTRICTIONS | VMD_FEAT_OFFSET_FIRST_VECTOR,}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xad0b), + {PCI_VDEVICE(INTEL, 0xad0b), .driver_data = VMD_FEAT_HAS_MEMBAR_SHADOW_VSCAP | VMD_FEAT_HAS_BUS_RESTRICTIONS | VMD_FEAT_OFFSET_FIRST_VECTOR,}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_VMD_9A0B), + {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_VMD_9A0B), .driver_data = VMD_FEAT_HAS_MEMBAR_SHADOW_VSCAP | VMD_FEAT_HAS_BUS_RESTRICTIONS | VMD_FEAT_OFFSET_FIRST_VECTOR,}, -- cgit v1.2.3 From 14d2079af64835494fe5c59ed63222d91a38a624 Mon Sep 17 00:00:00 2001 From: "David E. Box" Date: Thu, 19 Jan 2023 19:15:21 -0800 Subject: PCI: vmd: Create feature grouping for client products Simplify the device ID list by creating a grouping of features shared by client products. Suggested-by: Jon Derrick Link: https://lore.kernel.org/r/20230120031522.2304439-4-david.e.box@linux.intel.com Signed-off-by: David E. Box Signed-off-by: Lorenzo Pieralisi Reviewed-by: Kuppuswamy Sathyanarayanan --- drivers/pci/controller/vmd.c | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/drivers/pci/controller/vmd.c b/drivers/pci/controller/vmd.c index 1fc3f2174359..47fa3e5f2dc5 100644 --- a/drivers/pci/controller/vmd.c +++ b/drivers/pci/controller/vmd.c @@ -68,6 +68,10 @@ enum vmd_features { VMD_FEAT_CAN_BYPASS_MSI_REMAP = (1 << 4), }; +#define VMD_FEATS_CLIENT (VMD_FEAT_HAS_MEMBAR_SHADOW_VSCAP | \ + VMD_FEAT_HAS_BUS_RESTRICTIONS | \ + VMD_FEAT_OFFSET_FIRST_VECTOR) + static DEFINE_IDA(vmd_instance_ida); /* @@ -1024,29 +1028,17 @@ static const struct pci_device_id vmd_ids[] = { VMD_FEAT_HAS_BUS_RESTRICTIONS | VMD_FEAT_CAN_BYPASS_MSI_REMAP,}, {PCI_VDEVICE(INTEL, 0x467f), - .driver_data = VMD_FEAT_HAS_MEMBAR_SHADOW_VSCAP | - VMD_FEAT_HAS_BUS_RESTRICTIONS | - VMD_FEAT_OFFSET_FIRST_VECTOR,}, + .driver_data = VMD_FEATS_CLIENT,}, {PCI_VDEVICE(INTEL, 0x4c3d), - .driver_data = VMD_FEAT_HAS_MEMBAR_SHADOW_VSCAP | - VMD_FEAT_HAS_BUS_RESTRICTIONS | - VMD_FEAT_OFFSET_FIRST_VECTOR,}, + .driver_data = VMD_FEATS_CLIENT,}, {PCI_VDEVICE(INTEL, 0xa77f), - .driver_data = VMD_FEAT_HAS_MEMBAR_SHADOW_VSCAP | - VMD_FEAT_HAS_BUS_RESTRICTIONS | - VMD_FEAT_OFFSET_FIRST_VECTOR,}, + .driver_data = VMD_FEATS_CLIENT,}, {PCI_VDEVICE(INTEL, 0x7d0b), - .driver_data = VMD_FEAT_HAS_MEMBAR_SHADOW_VSCAP | - VMD_FEAT_HAS_BUS_RESTRICTIONS | - VMD_FEAT_OFFSET_FIRST_VECTOR,}, + .driver_data = VMD_FEATS_CLIENT,}, {PCI_VDEVICE(INTEL, 0xad0b), - .driver_data = VMD_FEAT_HAS_MEMBAR_SHADOW_VSCAP | - VMD_FEAT_HAS_BUS_RESTRICTIONS | - VMD_FEAT_OFFSET_FIRST_VECTOR,}, + .driver_data = VMD_FEATS_CLIENT,}, {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_VMD_9A0B), - .driver_data = VMD_FEAT_HAS_MEMBAR_SHADOW_VSCAP | - VMD_FEAT_HAS_BUS_RESTRICTIONS | - VMD_FEAT_OFFSET_FIRST_VECTOR,}, + .driver_data = VMD_FEATS_CLIENT,}, {0,} }; MODULE_DEVICE_TABLE(pci, vmd_ids); -- cgit v1.2.3 From f492edb40b54862cbd65e6aa5eb39fa40f9949bf Mon Sep 17 00:00:00 2001 From: "David E. Box" Date: Thu, 19 Jan 2023 19:15:22 -0800 Subject: PCI: vmd: Add quirk to configure PCIe ASPM and LTR PCIe ports reserved for VMD use are not visible to BIOS and therefore not configured to enable PCIe ASPM or LTR values (which BIOS will configure if they are not set). Lack of this programming results in high power consumption on laptops as reported in bugzilla. For affected products use pci_enable_link_state to set the allowed link states for devices on the root ports. Also set the LTR value to the maximum value needed for the SoC. This is a workaround for products from Rocket Lake through Alder Lake. Raptor Lake, the latest product at this time, has already implemented LTR configuring in BIOS. Future products will move ASPM configuration back to BIOS as well. As this solution is intended for laptops, support is not added for hotplug or for devices downstream of a switch on the root port. Link: https://bugzilla.kernel.org/show_bug.cgi?id=212355 Link: https://bugzilla.kernel.org/show_bug.cgi?id=215063 Link: https://bugzilla.kernel.org/show_bug.cgi?id=213717 Link: https://lore.kernel.org/r/20230120031522.2304439-5-david.e.box@linux.intel.com Signed-off-by: Michael Bottini Signed-off-by: David E. Box Signed-off-by: Lorenzo Pieralisi Reviewed-by: Jon Derrick Reviewed-by: Nirmal Patel Reviewed-by: Kuppuswamy Sathyanarayanan --- drivers/pci/controller/vmd.c | 55 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/drivers/pci/controller/vmd.c b/drivers/pci/controller/vmd.c index 47fa3e5f2dc5..990630ec57c6 100644 --- a/drivers/pci/controller/vmd.c +++ b/drivers/pci/controller/vmd.c @@ -66,11 +66,22 @@ enum vmd_features { * interrupt handling. */ VMD_FEAT_CAN_BYPASS_MSI_REMAP = (1 << 4), + + /* + * Enable ASPM on the PCIE root ports and set the default LTR of the + * storage devices on platforms where these values are not configured by + * BIOS. This is needed for laptops, which require these settings for + * proper power management of the SoC. + */ + VMD_FEAT_BIOS_PM_QUIRK = (1 << 5), }; +#define VMD_BIOS_PM_QUIRK_LTR 0x1003 /* 3145728 ns */ + #define VMD_FEATS_CLIENT (VMD_FEAT_HAS_MEMBAR_SHADOW_VSCAP | \ VMD_FEAT_HAS_BUS_RESTRICTIONS | \ - VMD_FEAT_OFFSET_FIRST_VECTOR) + VMD_FEAT_OFFSET_FIRST_VECTOR | \ + VMD_FEAT_BIOS_PM_QUIRK) static DEFINE_IDA(vmd_instance_ida); @@ -713,6 +724,46 @@ static void vmd_copy_host_bridge_flags(struct pci_host_bridge *root_bridge, vmd_bridge->native_dpc = root_bridge->native_dpc; } +/* + * Enable ASPM and LTR settings on devices that aren't configured by BIOS. + */ +static int vmd_pm_enable_quirk(struct pci_dev *pdev, void *userdata) +{ + unsigned long features = *(unsigned long *)userdata; + u16 ltr = VMD_BIOS_PM_QUIRK_LTR; + u32 ltr_reg; + int pos; + + if (!(features & VMD_FEAT_BIOS_PM_QUIRK)) + return 0; + + pci_enable_link_state(pdev, PCIE_LINK_STATE_ALL); + + pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_LTR); + if (!pos) + return 0; + + /* + * Skip if the max snoop LTR is non-zero, indicating BIOS has set it + * so the LTR quirk is not needed. + */ + pci_read_config_dword(pdev, pos + PCI_LTR_MAX_SNOOP_LAT, <r_reg); + if (!!(ltr_reg & (PCI_LTR_VALUE_MASK | PCI_LTR_SCALE_MASK))) + return 0; + + /* + * Set the default values to the maximum required by the platform to + * allow the deepest power management savings. Write as a DWORD where + * the lower word is the max snoop latency and the upper word is the + * max non-snoop latency. + */ + ltr_reg = (ltr << 16) | ltr; + pci_write_config_dword(pdev, pos + PCI_LTR_MAX_SNOOP_LAT, ltr_reg); + pci_info(pdev, "VMD: Default LTR value set by driver\n"); + + return 0; +} + static int vmd_enable_domain(struct vmd_dev *vmd, unsigned long features) { struct pci_sysdata *sd = &vmd->sysdata; @@ -885,6 +936,8 @@ static int vmd_enable_domain(struct vmd_dev *vmd, unsigned long features) pci_assign_unassigned_bus_resources(vmd->bus); + pci_walk_bus(vmd->bus, vmd_pm_enable_quirk, &features); + /* * VMD root buses are virtual and don't return true on pci_is_pcie() * and will fail pcie_bus_configure_settings() early. It can instead be -- cgit v1.2.3 From 0cb2a8f3456ff1cc51d571e287a48e8fddc98ec2 Mon Sep 17 00:00:00 2001 From: Sergio Paracuellos Date: Sat, 31 Dec 2022 08:40:41 +0100 Subject: PCI: mt7621: Delay phy ports initialization Some devices like ZBT WE1326 and ZBT WF3526-P and some Netgear models need to delay phy port initialization after calling the mt7621_pcie_init_port() driver function to get into reliable boots for both warm and hard resets. The delay required to detect the ports seems to be in the range [75-100] milliseconds. If the ports are not detected the controller is not functional. There is no datasheet or something similar to really understand why this extra delay is needed only for these devices and it is not for most of the boards that are built on mt7621 SoC. This issue has been reported by openWRT community and the complete discussion is in [0]. The 100 milliseconds delay has been tested in all devices to validate it. Add the extra 100 milliseconds delay to fix the issue. [0]: https://github.com/openwrt/openwrt/pull/11220 Link: https://lore.kernel.org/r/20221231074041.264738-1-sergio.paracuellos@gmail.com Fixes: 2bdd5238e756 ("PCI: mt7621: Add MediaTek MT7621 PCIe host controller driver") Signed-off-by: Sergio Paracuellos Signed-off-by: Lorenzo Pieralisi --- drivers/pci/controller/pcie-mt7621.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/pci/controller/pcie-mt7621.c b/drivers/pci/controller/pcie-mt7621.c index ee7aad09d627..63a5f4463a9f 100644 --- a/drivers/pci/controller/pcie-mt7621.c +++ b/drivers/pci/controller/pcie-mt7621.c @@ -60,6 +60,7 @@ #define PCIE_PORT_LINKUP BIT(0) #define PCIE_PORT_CNT 3 +#define INIT_PORTS_DELAY_MS 100 #define PERST_DELAY_MS 100 /** @@ -369,6 +370,7 @@ static int mt7621_pcie_init_ports(struct mt7621_pcie *pcie) } } + msleep(INIT_PORTS_DELAY_MS); mt7621_pcie_reset_ep_deassert(pcie); tmp = NULL; -- cgit v1.2.3 From 08f0a15ee8adb4846b08ca5d5c175fbf0f652bc9 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Tue, 31 Jan 2023 11:24:03 +0200 Subject: PCI: Align extra resources for hotplug bridges properly After division the extra resource space per hotplug bridge may not be aligned according to the window alignment, so align it before passing it down for further distribution. Link: https://lore.kernel.org/r/20230131092405.29121-2-mika.westerberg@linux.intel.com Signed-off-by: Mika Westerberg Signed-off-by: Bjorn Helgaas --- drivers/pci/setup-bus.c | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/drivers/pci/setup-bus.c b/drivers/pci/setup-bus.c index b4096598dbcb..e440f264accb 100644 --- a/drivers/pci/setup-bus.c +++ b/drivers/pci/setup-bus.c @@ -1891,6 +1891,7 @@ static void pci_bus_distribute_available_resources(struct pci_bus *bus, * resource space between hotplug bridges. */ for_each_pci_bridge(dev, bus) { + struct resource *res; struct pci_bus *b; b = dev->subordinate; @@ -1902,16 +1903,28 @@ static void pci_bus_distribute_available_resources(struct pci_bus *bus, * hotplug-capable downstream ports taking alignment into * account. */ - io.end = io.start + io_per_hp - 1; - mmio.end = mmio.start + mmio_per_hp - 1; - mmio_pref.end = mmio_pref.start + mmio_pref_per_hp - 1; + res = &dev->resource[PCI_BRIDGE_IO_WINDOW]; + align = pci_resource_alignment(dev, res); + io.end = align ? io.start + ALIGN_DOWN(io_per_hp, align) - 1 + : io.start + io_per_hp - 1; + + res = &dev->resource[PCI_BRIDGE_MEM_WINDOW]; + align = pci_resource_alignment(dev, res); + mmio.end = align ? mmio.start + ALIGN_DOWN(mmio_per_hp, align) - 1 + : mmio.start + mmio_per_hp - 1; + + res = &dev->resource[PCI_BRIDGE_PREF_MEM_WINDOW]; + align = pci_resource_alignment(dev, res); + mmio_pref.end = align ? mmio_pref.start + + ALIGN_DOWN(mmio_pref_per_hp, align) - 1 + : mmio_pref.start + mmio_pref_per_hp - 1; pci_bus_distribute_available_resources(b, add_list, io, mmio, mmio_pref); - io.start += io_per_hp; - mmio.start += mmio_per_hp; - mmio_pref.start += mmio_pref_per_hp; + io.start += io.end + 1; + mmio.start += mmio.end + 1; + mmio_pref.start += mmio_pref.end + 1; } } -- cgit v1.2.3 From 9db0b9b6a14249ef65a5f1e5e3b37762af96f425 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Tue, 31 Jan 2023 11:24:04 +0200 Subject: PCI: Take other bus devices into account when distributing resources A PCI bridge may reside on a bus with other devices as well. The resource distribution code does not take this into account and therefore it expands the bridge resource windows too much, not leaving space for the other devices (or functions of a multifunction device). This leads to an issue that Jonathan reported when running QEMU with the following topology (QEMU parameters): -device pcie-root-port,port=0,id=root_port13,chassis=0,slot=2 \ -device x3130-upstream,id=sw1,bus=root_port13,multifunction=on \ -device e1000,bus=root_port13,addr=0.1 \ -device xio3130-downstream,id=fun1,bus=sw1,chassis=0,slot=3 \ -device e1000,bus=fun1 The first e1000 NIC here is another function in the switch upstream port. This leads to following errors: pci 0000:00:04.0: bridge window [mem 0x10200000-0x103fffff] to [bus 02-04] pci 0000:02:00.0: bridge window [mem 0x10200000-0x103fffff] to [bus 03-04] pci 0000:02:00.1: BAR 0: failed to assign [mem size 0x00020000] e1000 0000:02:00.1: can't ioremap BAR 0: [??? 0x00000000 flags 0x0] Fix this by taking into account bridge windows, device BARs and SR-IOV PF BARs on the bus (PF BARs include space for VF BARS so only account PF BARs), including the ones belonging to bridges themselves if it has any. Link: https://lore.kernel.org/linux-pci/20221014124553.0000696f@huawei.com/ Link: https://lore.kernel.org/linux-pci/6053736d-1923-41e7-def9-7585ce1772d9@ixsystems.com/ Link: https://lore.kernel.org/r/20230131092405.29121-3-mika.westerberg@linux.intel.com Reported-by: Jonathan Cameron Reported-by: Alexander Motin Signed-off-by: Mika Westerberg Signed-off-by: Bjorn Helgaas --- drivers/pci/setup-bus.c | 176 +++++++++++++++++++++++++++++------------------- 1 file changed, 106 insertions(+), 70 deletions(-) diff --git a/drivers/pci/setup-bus.c b/drivers/pci/setup-bus.c index e440f264accb..b7b8dddb7772 100644 --- a/drivers/pci/setup-bus.c +++ b/drivers/pci/setup-bus.c @@ -1765,12 +1765,67 @@ static void adjust_bridge_window(struct pci_dev *bridge, struct resource *res, add_size = size - new_size; pci_dbg(bridge, "bridge window %pR shrunken by %pa\n", res, &add_size); + } else { + return; } res->end = res->start + new_size - 1; remove_from_list(add_list, res); } +static void remove_dev_resource(struct resource *avail, struct pci_dev *dev, + struct resource *res) +{ + resource_size_t size, align, tmp; + + size = resource_size(res); + if (!size) + return; + + align = pci_resource_alignment(dev, res); + align = align ? ALIGN(avail->start, align) - avail->start : 0; + tmp = align + size; + avail->start = min(avail->start + tmp, avail->end + 1); +} + +static void remove_dev_resources(struct pci_dev *dev, struct resource *io, + struct resource *mmio, + struct resource *mmio_pref) +{ + int i; + + for (i = 0; i < PCI_NUM_RESOURCES; i++) { + struct resource *res = &dev->resource[i]; + + if (resource_type(res) == IORESOURCE_IO) { + remove_dev_resource(io, dev, res); + } else if (resource_type(res) == IORESOURCE_MEM) { + + /* + * Make sure prefetchable memory is reduced from + * the correct resource. Specifically we put 32-bit + * prefetchable memory in non-prefetchable window + * if there is an 64-bit pretchable window. + * + * See comments in __pci_bus_size_bridges() for + * more information. + */ + if ((res->flags & IORESOURCE_PREFETCH) && + ((res->flags & IORESOURCE_MEM_64) == + (mmio_pref->flags & IORESOURCE_MEM_64))) + remove_dev_resource(mmio_pref, dev, res); + else + remove_dev_resource(mmio, dev, res); + } + } +} + +/* + * io, mmio and mmio_pref contain the total amount of bridge window space + * available. This includes the minimal space needed to cover all the + * existing devices on the bus and the possible extra space that can be + * shared with the bridges. + */ static void pci_bus_distribute_available_resources(struct pci_bus *bus, struct list_head *add_list, struct resource io, @@ -1780,7 +1835,7 @@ static void pci_bus_distribute_available_resources(struct pci_bus *bus, unsigned int normal_bridges = 0, hotplug_bridges = 0; struct resource *io_res, *mmio_res, *mmio_pref_res; struct pci_dev *dev, *bridge = bus->self; - resource_size_t io_per_hp, mmio_per_hp, mmio_pref_per_hp, align; + resource_size_t io_per_b, mmio_per_b, mmio_pref_per_b, align; io_res = &bridge->resource[PCI_BRIDGE_IO_WINDOW]; mmio_res = &bridge->resource[PCI_BRIDGE_MEM_WINDOW]; @@ -1824,100 +1879,81 @@ static void pci_bus_distribute_available_resources(struct pci_bus *bus, normal_bridges++; } - /* - * There is only one bridge on the bus so it gets all available - * resources which it can then distribute to the possible hotplug - * bridges below. - */ - if (hotplug_bridges + normal_bridges == 1) { - dev = list_first_entry(&bus->devices, struct pci_dev, bus_list); - if (dev->subordinate) - pci_bus_distribute_available_resources(dev->subordinate, - add_list, io, mmio, mmio_pref); - return; - } - - if (hotplug_bridges == 0) + if (!(hotplug_bridges + normal_bridges)) return; /* - * Calculate the total amount of extra resource space we can - * pass to bridges below this one. This is basically the - * extra space reduced by the minimal required space for the - * non-hotplug bridges. + * Calculate the amount of space we can forward from "bus" to any + * downstream buses, i.e., the space left over after assigning the + * BARs and windows on "bus". */ - for_each_pci_bridge(dev, bus) { - resource_size_t used_size; - struct resource *res; - - if (dev->is_hotplug_bridge) - continue; - - /* - * Reduce the available resource space by what the - * bridge and devices below it occupy. - */ - res = &dev->resource[PCI_BRIDGE_IO_WINDOW]; - align = pci_resource_alignment(dev, res); - align = align ? ALIGN(io.start, align) - io.start : 0; - used_size = align + resource_size(res); - if (!res->parent) - io.start = min(io.start + used_size, io.end + 1); - - res = &dev->resource[PCI_BRIDGE_MEM_WINDOW]; - align = pci_resource_alignment(dev, res); - align = align ? ALIGN(mmio.start, align) - mmio.start : 0; - used_size = align + resource_size(res); - if (!res->parent) - mmio.start = min(mmio.start + used_size, mmio.end + 1); - - res = &dev->resource[PCI_BRIDGE_PREF_MEM_WINDOW]; - align = pci_resource_alignment(dev, res); - align = align ? ALIGN(mmio_pref.start, align) - - mmio_pref.start : 0; - used_size = align + resource_size(res); - if (!res->parent) - mmio_pref.start = min(mmio_pref.start + used_size, - mmio_pref.end + 1); + list_for_each_entry(dev, &bus->devices, bus_list) { + if (!dev->is_virtfn) + remove_dev_resources(dev, &io, &mmio, &mmio_pref); } - io_per_hp = div64_ul(resource_size(&io), hotplug_bridges); - mmio_per_hp = div64_ul(resource_size(&mmio), hotplug_bridges); - mmio_pref_per_hp = div64_ul(resource_size(&mmio_pref), - hotplug_bridges); - /* - * Go over devices on this bus and distribute the remaining - * resource space between hotplug bridges. + * If there is at least one hotplug bridge on this bus it gets all + * the extra resource space that was left after the reductions + * above. + * + * If there are no hotplug bridges the extra resource space is + * split between non-hotplug bridges. This is to allow possible + * hotplug bridges below them to get the extra space as well. */ + if (hotplug_bridges) { + io_per_b = div64_ul(resource_size(&io), hotplug_bridges); + mmio_per_b = div64_ul(resource_size(&mmio), hotplug_bridges); + mmio_pref_per_b = div64_ul(resource_size(&mmio_pref), + hotplug_bridges); + } else { + io_per_b = div64_ul(resource_size(&io), normal_bridges); + mmio_per_b = div64_ul(resource_size(&mmio), normal_bridges); + mmio_pref_per_b = div64_ul(resource_size(&mmio_pref), + normal_bridges); + } + for_each_pci_bridge(dev, bus) { struct resource *res; struct pci_bus *b; b = dev->subordinate; - if (!b || !dev->is_hotplug_bridge) + if (!b) continue; + if (hotplug_bridges && !dev->is_hotplug_bridge) + continue; + + res = &dev->resource[PCI_BRIDGE_IO_WINDOW]; /* - * Distribute available extra resources equally between - * hotplug-capable downstream ports taking alignment into - * account. + * Make sure the split resource space is properly aligned + * for bridge windows (align it down to avoid going above + * what is available). */ - res = &dev->resource[PCI_BRIDGE_IO_WINDOW]; align = pci_resource_alignment(dev, res); - io.end = align ? io.start + ALIGN_DOWN(io_per_hp, align) - 1 - : io.start + io_per_hp - 1; + io.end = align ? io.start + ALIGN_DOWN(io_per_b, align) - 1 + : io.start + io_per_b - 1; + + /* + * The x_per_b holds the extra resource space that can be + * added for each bridge but there is the minimal already + * reserved as well so adjust x.start down accordingly to + * cover the whole space. + */ + io.start -= resource_size(res); res = &dev->resource[PCI_BRIDGE_MEM_WINDOW]; align = pci_resource_alignment(dev, res); - mmio.end = align ? mmio.start + ALIGN_DOWN(mmio_per_hp, align) - 1 - : mmio.start + mmio_per_hp - 1; + mmio.end = align ? mmio.start + ALIGN_DOWN(mmio_per_b, align) - 1 + : mmio.start + mmio_per_b - 1; + mmio.start -= resource_size(res); res = &dev->resource[PCI_BRIDGE_PREF_MEM_WINDOW]; align = pci_resource_alignment(dev, res); mmio_pref.end = align ? mmio_pref.start + - ALIGN_DOWN(mmio_pref_per_hp, align) - 1 - : mmio_pref.start + mmio_pref_per_hp - 1; + ALIGN_DOWN(mmio_pref_per_b, align) - 1 + : mmio_pref.start + mmio_pref_per_b - 1; + mmio_pref.start -= resource_size(res); pci_bus_distribute_available_resources(b, add_list, io, mmio, mmio_pref); -- cgit v1.2.3 From 7180c1d08639f28e63110ad35815f7a1785b8a19 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Tue, 31 Jan 2023 11:24:05 +0200 Subject: PCI: Distribute available resources for root buses, too Previously we distributed spare resources only upon hot-add, so if the initial root bus scan found devices that had not been fully configured by the BIOS, we allocated only enough resources to cover what was then present. If some of those devices were hotplug bridges, we did not leave any additional resource space for future expansion. Distribute the available resources for root buses, too, to make this work the same way as the normal hotplug case. A previous commit to do this was reverted due to a regression reported by Jonathan Cameron: e96e27fc6f79 ("PCI: Distribute available resources for root buses, too") 5632e2beaf9d ("Revert "PCI: Distribute available resources for root buses, too"") This commit changes pci_bridge_resources_not_assigned() to work with bridges that do not have all the resource windows programmed by the boot firmware (previously we expected all I/O, memory and prefetchable memory were programmed). Link: https://bugzilla.kernel.org/show_bug.cgi?id=216000 Link: https://lore.kernel.org/r/20220905080232.36087-5-mika.westerberg@linux.intel.com Link: https://lore.kernel.org/r/20230131092405.29121-4-mika.westerberg@linux.intel.com Reported-by: Chris Chiu Signed-off-by: Mika Westerberg Signed-off-by: Bjorn Helgaas --- drivers/pci/setup-bus.c | 57 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/drivers/pci/setup-bus.c b/drivers/pci/setup-bus.c index b7b8dddb7772..c690572b10ce 100644 --- a/drivers/pci/setup-bus.c +++ b/drivers/pci/setup-bus.c @@ -1770,7 +1770,10 @@ static void adjust_bridge_window(struct pci_dev *bridge, struct resource *res, } res->end = res->start + new_size - 1; - remove_from_list(add_list, res); + + /* If the resource is part of the add_list, remove it now */ + if (add_list) + remove_from_list(add_list, res); } static void remove_dev_resource(struct resource *avail, struct pci_dev *dev, @@ -1972,6 +1975,8 @@ static void pci_bridge_distribute_available_resources(struct pci_dev *bridge, if (!bridge->is_hotplug_bridge) return; + pci_dbg(bridge, "distributing available resources\n"); + /* Take the initial extra resources from the hotplug port */ available_io = bridge->resource[PCI_BRIDGE_IO_WINDOW]; available_mmio = bridge->resource[PCI_BRIDGE_MEM_WINDOW]; @@ -1983,6 +1988,54 @@ static void pci_bridge_distribute_available_resources(struct pci_dev *bridge, available_mmio_pref); } +static bool pci_bridge_resources_not_assigned(struct pci_dev *dev) +{ + const struct resource *r; + + /* + * If the child device's resources are not yet assigned it means we + * are configuring them (not the boot firmware), so we should be + * able to extend the upstream bridge resources in the same way we + * do with the normal hotplug case. + */ + r = &dev->resource[PCI_BRIDGE_IO_WINDOW]; + if (r->flags && !(r->flags & IORESOURCE_STARTALIGN)) + return false; + r = &dev->resource[PCI_BRIDGE_MEM_WINDOW]; + if (r->flags && !(r->flags & IORESOURCE_STARTALIGN)) + return false; + r = &dev->resource[PCI_BRIDGE_PREF_MEM_WINDOW]; + if (r->flags && !(r->flags & IORESOURCE_STARTALIGN)) + return false; + + return true; +} + +static void +pci_root_bus_distribute_available_resources(struct pci_bus *bus, + struct list_head *add_list) +{ + struct pci_dev *dev, *bridge = bus->self; + + for_each_pci_bridge(dev, bus) { + struct pci_bus *b; + + b = dev->subordinate; + if (!b) + continue; + + /* + * Need to check "bridge" here too because it is NULL + * in case of root bus. + */ + if (bridge && pci_bridge_resources_not_assigned(dev)) + pci_bridge_distribute_available_resources(bridge, + add_list); + else + pci_root_bus_distribute_available_resources(b, add_list); + } +} + /* * First try will not touch PCI bridge res. * Second and later try will clear small leaf bridge res. @@ -2022,6 +2075,8 @@ again: */ __pci_bus_size_bridges(bus, add_list); + pci_root_bus_distribute_available_resources(bus, add_list); + /* Depth last, allocate resources and update the hardware. */ __pci_bus_assign_resources(bus, add_list, &fail_head); if (add_list) -- cgit v1.2.3 From 8ef0217227b42e2c34a18de316cee3da16c9bf1e Mon Sep 17 00:00:00 2001 From: Lukas Wunner Date: Sun, 15 Jan 2023 09:20:31 +0100 Subject: PCI/PM: Observe reset delay irrespective of bridge_d3 If a PCI bridge is suspended to D3cold upon entering system sleep, resuming it entails a Fundamental Reset per PCIe r6.0 sec 5.8. The delay prescribed after a Fundamental Reset in PCIe r6.0 sec 6.6.1 is sought to be observed by: pci_pm_resume_noirq() pci_pm_bridge_power_up_actions() pci_bridge_wait_for_secondary_bus() However, pci_bridge_wait_for_secondary_bus() bails out if the bridge_d3 flag is not set. That flag indicates whether a bridge is allowed to suspend to D3cold at *runtime*. Hence *no* delay is observed on resume from system sleep if runtime D3cold is forbidden. That doesn't make any sense, so drop the bridge_d3 check from pci_bridge_wait_for_secondary_bus(). The purpose of the bridge_d3 check was probably to avoid delays if a bridge remained in D0 during suspend. However the sole caller of pci_bridge_wait_for_secondary_bus(), pci_pm_bridge_power_up_actions(), is only invoked if the previous power state was D3cold. Hence the additional bridge_d3 check seems superfluous. Fixes: ad9001f2f411 ("PCI/PM: Add missing link delays required by the PCIe spec") Link: https://lore.kernel.org/r/eb37fa345285ec8bacabbf06b020b803f77bdd3d.1673769517.git.lukas@wunner.de Tested-by: Ravi Kishore Koppuravuri Signed-off-by: Lukas Wunner Signed-off-by: Bjorn Helgaas Reviewed-by: Mika Westerberg Reviewed-by: Kuppuswamy Sathyanarayanan Cc: stable@vger.kernel.org # v5.5+ --- drivers/pci/pci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index fba95486caaf..f43f3e84f634 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -4964,7 +4964,7 @@ void pci_bridge_wait_for_secondary_bus(struct pci_dev *dev) if (pci_dev_is_disconnected(dev)) return; - if (!pci_is_bridge(dev) || !dev->bridge_d3) + if (!pci_is_bridge(dev)) return; down_read(&pci_bus_sem); -- cgit v1.2.3 From ac91e6980563ed53afadd925fa6585ffd2bc4a2c Mon Sep 17 00:00:00 2001 From: Lukas Wunner Date: Sun, 15 Jan 2023 09:20:32 +0100 Subject: PCI: Unify delay handling for reset and resume Sheng Bi reports that pci_bridge_secondary_bus_reset() may fail to wait for devices on the secondary bus to become accessible after reset: Although it does call pci_dev_wait(), it erroneously passes the bridge's pci_dev rather than that of a child. The bridge of course is always accessible while its secondary bus is reset, so pci_dev_wait() returns immediately. Sheng Bi proposes introducing a new pci_bridge_secondary_bus_wait() function which is called from pci_bridge_secondary_bus_reset(): https://lore.kernel.org/linux-pci/20220523171517.32407-1-windy.bi.enflame@gmail.com/ However we already have pci_bridge_wait_for_secondary_bus() which does almost exactly what we need. So far it's only called on resume from D3cold (which implies a Fundamental Reset per PCIe r6.0 sec 5.8). Re-using it for Secondary Bus Resets is a leaner and more rational approach than introducing a new function. That only requires a few minor tweaks: - Amend pci_bridge_wait_for_secondary_bus() to await accessibility of the first device on the secondary bus by calling pci_dev_wait() after performing the prescribed delays. pci_dev_wait() needs two parameters, a reset reason and a timeout, which callers must now pass to pci_bridge_wait_for_secondary_bus(). The timeout is 1 sec for resume (PCIe r6.0 sec 6.6.1) and 60 sec for reset (commit 821cdad5c46c ("PCI: Wait up to 60 seconds for device to become ready after FLR")). Introduce a PCI_RESET_WAIT macro for the 1 sec timeout. - Amend pci_bridge_wait_for_secondary_bus() to return 0 on success or -ENOTTY on error for consumption by pci_bridge_secondary_bus_reset(). - Drop an unnecessary 1 sec delay from pci_reset_secondary_bus() which is now performed by pci_bridge_wait_for_secondary_bus(). A static delay this long is only necessary for Conventional PCI, so modern PCIe systems benefit from shorter reset times as a side effect. Fixes: 6b2f1351af56 ("PCI: Wait for device to become ready after secondary bus reset") Link: https://lore.kernel.org/r/da77c92796b99ec568bd070cbe4725074a117038.1673769517.git.lukas@wunner.de Reported-by: Sheng Bi Tested-by: Ravi Kishore Koppuravuri Signed-off-by: Lukas Wunner Signed-off-by: Bjorn Helgaas Reviewed-by: Mika Westerberg Reviewed-by: Kuppuswamy Sathyanarayanan Cc: stable@vger.kernel.org # v4.17+ --- drivers/pci/pci-driver.c | 2 +- drivers/pci/pci.c | 54 +++++++++++++++++++++--------------------------- drivers/pci/pci.h | 10 ++++++++- 3 files changed, 34 insertions(+), 32 deletions(-) diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c index a2ceeacc33eb..7a19f11daca3 100644 --- a/drivers/pci/pci-driver.c +++ b/drivers/pci/pci-driver.c @@ -572,7 +572,7 @@ static void pci_pm_default_resume_early(struct pci_dev *pci_dev) static void pci_pm_bridge_power_up_actions(struct pci_dev *pci_dev) { - pci_bridge_wait_for_secondary_bus(pci_dev); + pci_bridge_wait_for_secondary_bus(pci_dev, "resume", PCI_RESET_WAIT); /* * When powering on a bridge from D3cold, the whole hierarchy may be * powered on into D0uninitialized state, resume them to give them a diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index f43f3e84f634..509f6b5c9e14 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -1174,7 +1174,7 @@ static int pci_dev_wait(struct pci_dev *dev, char *reset_type, int timeout) return -ENOTTY; } - if (delay > 1000) + if (delay > PCI_RESET_WAIT) pci_info(dev, "not ready %dms after %s; waiting\n", delay - 1, reset_type); @@ -1183,7 +1183,7 @@ static int pci_dev_wait(struct pci_dev *dev, char *reset_type, int timeout) pci_read_config_dword(dev, PCI_COMMAND, &id); } - if (delay > 1000) + if (delay > PCI_RESET_WAIT) pci_info(dev, "ready %dms after %s\n", delay - 1, reset_type); @@ -4948,24 +4948,31 @@ static int pci_bus_max_d3cold_delay(const struct pci_bus *bus) /** * pci_bridge_wait_for_secondary_bus - Wait for secondary bus to be accessible * @dev: PCI bridge + * @reset_type: reset type in human-readable form + * @timeout: maximum time to wait for devices on secondary bus (milliseconds) * * Handle necessary delays before access to the devices on the secondary - * side of the bridge are permitted after D3cold to D0 transition. + * side of the bridge are permitted after D3cold to D0 transition + * or Conventional Reset. * * For PCIe this means the delays in PCIe 5.0 section 6.6.1. For * conventional PCI it means Tpvrh + Trhfa specified in PCI 3.0 section * 4.3.2. + * + * Return 0 on success or -ENOTTY if the first device on the secondary bus + * failed to become accessible. */ -void pci_bridge_wait_for_secondary_bus(struct pci_dev *dev) +int pci_bridge_wait_for_secondary_bus(struct pci_dev *dev, char *reset_type, + int timeout) { struct pci_dev *child; int delay; if (pci_dev_is_disconnected(dev)) - return; + return 0; if (!pci_is_bridge(dev)) - return; + return 0; down_read(&pci_bus_sem); @@ -4977,14 +4984,14 @@ void pci_bridge_wait_for_secondary_bus(struct pci_dev *dev) */ if (!dev->subordinate || list_empty(&dev->subordinate->devices)) { up_read(&pci_bus_sem); - return; + return 0; } /* Take d3cold_delay requirements into account */ delay = pci_bus_max_d3cold_delay(dev->subordinate); if (!delay) { up_read(&pci_bus_sem); - return; + return 0; } child = list_first_entry(&dev->subordinate->devices, struct pci_dev, @@ -4993,14 +5000,12 @@ void pci_bridge_wait_for_secondary_bus(struct pci_dev *dev) /* * Conventional PCI and PCI-X we need to wait Tpvrh + Trhfa before - * accessing the device after reset (that is 1000 ms + 100 ms). In - * practice this should not be needed because we don't do power - * management for them (see pci_bridge_d3_possible()). + * accessing the device after reset (that is 1000 ms + 100 ms). */ if (!pci_is_pcie(dev)) { pci_dbg(dev, "waiting %d ms for secondary bus\n", 1000 + delay); msleep(1000 + delay); - return; + return 0; } /* @@ -5017,11 +5022,11 @@ void pci_bridge_wait_for_secondary_bus(struct pci_dev *dev) * configuration requests if we only wait for 100 ms (see * https://bugzilla.kernel.org/show_bug.cgi?id=203885). * - * Therefore we wait for 100 ms and check for the device presence. - * If it is still not present give it an additional 100 ms. + * Therefore we wait for 100 ms and check for the device presence + * until the timeout expires. */ if (!pcie_downstream_port(dev)) - return; + return 0; if (pcie_get_speed_cap(dev) <= PCIE_SPEED_5_0GT) { pci_dbg(dev, "waiting %d ms for downstream link\n", delay); @@ -5032,14 +5037,11 @@ void pci_bridge_wait_for_secondary_bus(struct pci_dev *dev) if (!pcie_wait_for_link_delay(dev, true, delay)) { /* Did not train, no need to wait any further */ pci_info(dev, "Data Link Layer Link Active not set in 1000 msec\n"); - return; + return -ENOTTY; } } - if (!pci_device_is_present(child)) { - pci_dbg(child, "waiting additional %d ms to become accessible\n", delay); - msleep(delay); - } + return pci_dev_wait(child, reset_type, timeout - delay); } void pci_reset_secondary_bus(struct pci_dev *dev) @@ -5058,15 +5060,6 @@ void pci_reset_secondary_bus(struct pci_dev *dev) ctrl &= ~PCI_BRIDGE_CTL_BUS_RESET; pci_write_config_word(dev, PCI_BRIDGE_CONTROL, ctrl); - - /* - * Trhfa for conventional PCI is 2^25 clock cycles. - * Assuming a minimum 33MHz clock this results in a 1s - * delay before we can consider subordinate devices to - * be re-initialized. PCIe has some ways to shorten this, - * but we don't make use of them yet. - */ - ssleep(1); } void __weak pcibios_reset_secondary_bus(struct pci_dev *dev) @@ -5085,7 +5078,8 @@ int pci_bridge_secondary_bus_reset(struct pci_dev *dev) { pcibios_reset_secondary_bus(dev); - return pci_dev_wait(dev, "bus reset", PCIE_RESET_READY_POLL_MS); + return pci_bridge_wait_for_secondary_bus(dev, "bus reset", + PCIE_RESET_READY_POLL_MS); } EXPORT_SYMBOL_GPL(pci_bridge_secondary_bus_reset); diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index 9ed3b5550043..ce1fc3a90b3f 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -64,6 +64,13 @@ struct pci_cap_saved_state *pci_find_saved_ext_cap(struct pci_dev *dev, #define PCI_PM_D3HOT_WAIT 10 /* msec */ #define PCI_PM_D3COLD_WAIT 100 /* msec */ +/* + * Following exit from Conventional Reset, devices must be ready within 1 sec + * (PCIe r6.0 sec 6.6.1). A D3cold to D0 transition implies a Conventional + * Reset (PCIe r6.0 sec 5.8). + */ +#define PCI_RESET_WAIT 1000 /* msec */ + void pci_update_current_state(struct pci_dev *dev, pci_power_t state); void pci_refresh_power_state(struct pci_dev *dev); int pci_power_up(struct pci_dev *dev); @@ -86,8 +93,9 @@ void pci_msi_init(struct pci_dev *dev); void pci_msix_init(struct pci_dev *dev); bool pci_bridge_d3_possible(struct pci_dev *dev); void pci_bridge_d3_update(struct pci_dev *dev); -void pci_bridge_wait_for_secondary_bus(struct pci_dev *dev); void pci_bridge_reconfigure_ltr(struct pci_dev *dev); +int pci_bridge_wait_for_secondary_bus(struct pci_dev *dev, char *reset_type, + int timeout); static inline void pci_wakeup_event(struct pci_dev *dev) { -- cgit v1.2.3 From b3574f579ece24439c90e9a179742c61205fbcfa Mon Sep 17 00:00:00 2001 From: Pali Rohár Date: Sat, 14 Jan 2023 17:41:25 +0100 Subject: PCI: mvebu: Mark driver as BROKEN MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit People are reporting that pci-mvebu.c driver does not work with recent mainline kernel. There are more bugs which prevents its for daily usage. So lets mark it as broken for now, until somebody would be able to fix it in mainline kernel. Link: https://lore.kernel.org/r/20230114164125.1298-1-pali@kernel.org Signed-off-by: Pali Rohár Signed-off-by: Lorenzo Pieralisi --- drivers/pci/controller/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/pci/controller/Kconfig b/drivers/pci/controller/Kconfig index 1569d9a3ada0..b4a4d84a358b 100644 --- a/drivers/pci/controller/Kconfig +++ b/drivers/pci/controller/Kconfig @@ -9,6 +9,7 @@ config PCI_MVEBU depends on MVEBU_MBUS depends on ARM depends on OF + depends on BROKEN select PCI_BRIDGE_EMUL help Add support for Marvell EBU PCIe controller. This PCIe controller -- cgit v1.2.3 From 53b54ad074de1896f8b021615f65b27f557ce874 Mon Sep 17 00:00:00 2001 From: Lukas Wunner Date: Sun, 15 Jan 2023 09:20:33 +0100 Subject: PCI/DPC: Await readiness of secondary bus after reset pci_bridge_wait_for_secondary_bus() is called after a Secondary Bus Reset, but not after a DPC-induced Hot Reset. As a result, the delays prescribed by PCIe r6.0 sec 6.6.1 are not observed and devices on the secondary bus may be accessed before they're ready. One affected device is Intel's Ponte Vecchio HPC GPU. It comprises a PCIe switch whose upstream port is not immediately ready after reset. Because its config space is restored too early, it remains in D0uninitialized, its subordinate devices remain inaccessible and DPC recovery fails with messages such as: i915 0000:8c:00.0: can't change power state from D3cold to D0 (config space inaccessible) intel_vsec 0000:8e:00.1: can't change power state from D3cold to D0 (config space inaccessible) pcieport 0000:89:02.0: AER: device recovery failed Fix it. Link: https://lore.kernel.org/r/9f5ff00e1593d8d9a4b452398b98aa14d23fca11.1673769517.git.lukas@wunner.de Tested-by: Ravi Kishore Koppuravuri Signed-off-by: Lukas Wunner Signed-off-by: Bjorn Helgaas Reviewed-by: Mika Westerberg Cc: stable@vger.kernel.org --- drivers/pci/pci.c | 3 --- drivers/pci/pci.h | 6 ++++++ drivers/pci/pcie/dpc.c | 4 ++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 509f6b5c9e14..d31c21ea9688 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -167,9 +167,6 @@ static int __init pcie_port_pm_setup(char *str) } __setup("pcie_port_pm=", pcie_port_pm_setup); -/* Time to wait after a reset for device to become responsive */ -#define PCIE_RESET_READY_POLL_MS 60000 - /** * pci_bus_max_busnr - returns maximum PCI bus number of given bus' children * @bus: pointer to PCI bus structure to search diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index ce1fc3a90b3f..8f5d4bd5b410 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -70,6 +70,12 @@ struct pci_cap_saved_state *pci_find_saved_ext_cap(struct pci_dev *dev, * Reset (PCIe r6.0 sec 5.8). */ #define PCI_RESET_WAIT 1000 /* msec */ +/* + * Devices may extend the 1 sec period through Request Retry Status completions + * (PCIe r6.0 sec 2.3.1). The spec does not provide an upper limit, but 60 sec + * ought to be enough for any device to become responsive. + */ +#define PCIE_RESET_READY_POLL_MS 60000 /* msec */ void pci_update_current_state(struct pci_dev *dev, pci_power_t state); void pci_refresh_power_state(struct pci_dev *dev); diff --git a/drivers/pci/pcie/dpc.c b/drivers/pci/pcie/dpc.c index f5ffea17c7f8..a5d7c69b764e 100644 --- a/drivers/pci/pcie/dpc.c +++ b/drivers/pci/pcie/dpc.c @@ -170,8 +170,8 @@ pci_ers_result_t dpc_reset_link(struct pci_dev *pdev) pci_write_config_word(pdev, cap + PCI_EXP_DPC_STATUS, PCI_EXP_DPC_STATUS_TRIGGER); - if (!pcie_wait_for_link(pdev, true)) { - pci_info(pdev, "Data Link Layer Link Active not set in 1000 msec\n"); + if (pci_bridge_wait_for_secondary_bus(pdev, "DPC", + PCIE_RESET_READY_POLL_MS)) { clear_bit(PCI_DPC_RECOVERED, &pdev->priv_flags); ret = PCI_ERS_RESULT_DISCONNECT; } else { -- cgit v1.2.3 From 6f5e55dfcb061fe6c877c68fc7e3eb84d199f4d0 Mon Sep 17 00:00:00 2001 From: Kunihiko Hayashi Date: Fri, 23 Dec 2022 16:07:13 +0900 Subject: dt-bindings: PCI: uniphier-ep: Clean up reg, clocks, resets, and their names Instead of "oneOf:" choices, use "allOf:" and "if:" to define reg, clocks, resets, and their names that can be taken by the compatible string. The order of their names doesn't change here. Link: https://lore.kernel.org/r/20221223070713.20549-2-hayashi.kunihiko@socionext.com Signed-off-by: Kunihiko Hayashi Signed-off-by: Lorenzo Pieralisi Signed-off-by: Bjorn Helgaas Reviewed-by: Rob Herring --- .../bindings/pci/socionext,uniphier-pcie-ep.yaml | 76 ++++++++++++++-------- 1 file changed, 49 insertions(+), 27 deletions(-) diff --git a/Documentation/devicetree/bindings/pci/socionext,uniphier-pcie-ep.yaml b/Documentation/devicetree/bindings/pci/socionext,uniphier-pcie-ep.yaml index 437e61618d06..f0d8e486a07d 100644 --- a/Documentation/devicetree/bindings/pci/socionext,uniphier-pcie-ep.yaml +++ b/Documentation/devicetree/bindings/pci/socionext,uniphier-pcie-ep.yaml @@ -15,9 +15,6 @@ description: | maintainers: - Kunihiko Hayashi -allOf: - - $ref: /schemas/pci/snps,dw-pcie-ep.yaml# - properties: compatible: enum: @@ -29,40 +26,25 @@ properties: maxItems: 5 reg-names: - oneOf: - - items: - - const: dbi - - const: dbi2 - - const: link - - const: addr_space - - items: - - const: dbi - - const: dbi2 - - const: link - - const: addr_space - - const: atu + minItems: 4 + items: + - const: dbi + - const: dbi2 + - const: link + - const: addr_space + - const: atu clocks: minItems: 1 maxItems: 2 - clock-names: - oneOf: - - items: # for Pro5 - - const: gio - - const: link - - const: link # for NX1 + clock-names: true resets: minItems: 1 maxItems: 2 - reset-names: - oneOf: - - items: # for Pro5 - - const: gio - - const: link - - const: link # for NX1 + reset-names: true num-ib-windows: const: 16 @@ -78,6 +60,46 @@ properties: phy-names: const: pcie-phy +allOf: + - $ref: /schemas/pci/snps,dw-pcie-ep.yaml# + - if: + properties: + compatible: + contains: + const: socionext,uniphier-pro5-pcie-ep + then: + properties: + reg: + maxItems: 4 + reg-names: + maxItems: 4 + clocks: + minItems: 2 + clock-names: + items: + - const: gio + - const: link + resets: + minItems: 2 + reset-names: + items: + - const: gio + - const: link + else: + properties: + reg: + minItems: 5 + reg-names: + minItems: 5 + clocks: + maxItems: 1 + clock-names: + const: link + resets: + maxItems: 1 + reset-names: + const: link + required: - compatible - reg -- cgit v1.2.3 From d0152168538ea05695a403bc424da3e65aca67f3 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 13 Jan 2023 20:13:58 +0300 Subject: dmaengine: dw-edma: Move eDMA data pointer to debugfs node descriptor The last thing that stops the debugfs part of the eDMA driver from supporting multi-eDMA platforms is keeping the eDMA private data pointer in the static area of the debugfs module. Since the debugfs node descriptors are now heap-allocated, we can freely move that pointer to being preserved in the descriptors. After the debugfs initialization procedure, that pointer will be used in the debugfs files getter to access the common CSRs space and the context CSRs spinlock. So the main part of this change is connected with the debugfs nodes descriptors initialization macros, which aside with already defined prototypes now require to have the DW eDMA private data pointer passed. [bhelgaas: squash in https://lore.kernel.org/r/20230130185101.2883245-1-arnd@kernel.org] Link: https://lore.kernel.org/r/20230113171409.30470-17-Sergey.Semin@baikalelectronics.ru Tested-by: Manivannan Sadhasivam Signed-off-by: Serge Semin Signed-off-by: Lorenzo Pieralisi Signed-off-by: Bjorn Helgaas Reviewed-by: Manivannan Sadhasivam Acked-by: Vinod Koul --- drivers/dma/dw-edma/dw-edma-v0-debugfs.c | 244 +++++++++++++++---------------- 1 file changed, 119 insertions(+), 125 deletions(-) diff --git a/drivers/dma/dw-edma/dw-edma-v0-debugfs.c b/drivers/dma/dw-edma/dw-edma-v0-debugfs.c index fa63d1dd75b2..70cdcef7cc61 100644 --- a/drivers/dma/dw-edma/dw-edma-v0-debugfs.c +++ b/drivers/dma/dw-edma/dw-edma-v0-debugfs.c @@ -13,53 +13,55 @@ #include "dw-edma-v0-regs.h" #include "dw-edma-core.h" -#define REGS_ADDR(name) \ - ((void __iomem *)®s->name) +#define REGS_ADDR(dw, name) \ + ({ \ + struct dw_edma_v0_regs __iomem *__regs = (dw)->chip->reg_base; \ + \ + (void __iomem *)&__regs->name; \ + }) -#define REGS_CH_ADDR(name, _dir, _ch) \ +#define REGS_CH_ADDR(dw, name, _dir, _ch) \ ({ \ struct dw_edma_v0_ch_regs __iomem *__ch_regs; \ \ if ((dw)->chip->mf == EDMA_MF_EDMA_LEGACY) \ - __ch_regs = ®s->type.legacy.ch; \ + __ch_regs = REGS_ADDR(dw, type.legacy.ch); \ else if (_dir == EDMA_DIR_READ) \ - __ch_regs = ®s->type.unroll.ch[_ch].rd; \ + __ch_regs = REGS_ADDR(dw, type.unroll.ch[_ch].rd); \ else \ - __ch_regs = ®s->type.unroll.ch[_ch].wr; \ + __ch_regs = REGS_ADDR(dw, type.unroll.ch[_ch].wr); \ \ (void __iomem *)&__ch_regs->name; \ }) -#define REGISTER(name) \ - { #name, REGS_ADDR(name) } +#define REGISTER(dw, name) \ + { dw, #name, REGS_ADDR(dw, name) } -#define CTX_REGISTER(name, dir, ch) \ - { #name, REGS_CH_ADDR(name, dir, ch), dir, ch } +#define CTX_REGISTER(dw, name, dir, ch) \ + { dw, #name, REGS_CH_ADDR(dw, name, dir, ch), dir, ch } -#define WR_REGISTER(name) \ - { #name, REGS_ADDR(wr_##name) } -#define RD_REGISTER(name) \ - { #name, REGS_ADDR(rd_##name) } +#define WR_REGISTER(dw, name) \ + { dw, #name, REGS_ADDR(dw, wr_##name) } +#define RD_REGISTER(dw, name) \ + { dw, #name, REGS_ADDR(dw, rd_##name) } -#define WR_REGISTER_LEGACY(name) \ - { #name, REGS_ADDR(type.legacy.wr_##name) } +#define WR_REGISTER_LEGACY(dw, name) \ + { dw, #name, REGS_ADDR(dw, type.legacy.wr_##name) } #define RD_REGISTER_LEGACY(name) \ - { #name, REGS_ADDR(type.legacy.rd_##name) } + { dw, #name, REGS_ADDR(dw, type.legacy.rd_##name) } -#define WR_REGISTER_UNROLL(name) \ - { #name, REGS_ADDR(type.unroll.wr_##name) } -#define RD_REGISTER_UNROLL(name) \ - { #name, REGS_ADDR(type.unroll.rd_##name) } +#define WR_REGISTER_UNROLL(dw, name) \ + { dw, #name, REGS_ADDR(dw, type.unroll.wr_##name) } +#define RD_REGISTER_UNROLL(dw, name) \ + { dw, #name, REGS_ADDR(dw, type.unroll.rd_##name) } #define WRITE_STR "write" #define READ_STR "read" #define CHANNEL_STR "channel" #define REGISTERS_STR "registers" -static struct dw_edma *dw; -static struct dw_edma_v0_regs __iomem *regs; - struct dw_edma_debugfs_entry { + struct dw_edma *dw; const char *name; void __iomem *reg; enum dw_edma_dir dir; @@ -69,10 +71,11 @@ struct dw_edma_debugfs_entry { static int dw_edma_debugfs_u32_get(void *data, u64 *val) { struct dw_edma_debugfs_entry *entry = data; + struct dw_edma *dw = entry->dw; void __iomem *reg = entry->reg; if (dw->chip->mf == EDMA_MF_EDMA_LEGACY && - reg >= (void __iomem *)®s->type.legacy.ch) { + reg >= REGS_ADDR(dw, type.legacy.ch)) { unsigned long flags; u32 viewport_sel; @@ -81,7 +84,7 @@ static int dw_edma_debugfs_u32_get(void *data, u64 *val) raw_spin_lock_irqsave(&dw->lock, flags); - writel(viewport_sel, ®s->type.legacy.viewport_sel); + writel(viewport_sel, REGS_ADDR(dw, type.legacy.viewport_sel)); *val = readl(reg); raw_spin_unlock_irqrestore(&dw->lock, flags); @@ -93,7 +96,8 @@ static int dw_edma_debugfs_u32_get(void *data, u64 *val) } DEFINE_DEBUGFS_ATTRIBUTE(fops_x32, dw_edma_debugfs_u32_get, NULL, "0x%08llx\n"); -static void dw_edma_debugfs_create_x32(const struct dw_edma_debugfs_entry ini[], +static void dw_edma_debugfs_create_x32(struct dw_edma *dw, + const struct dw_edma_debugfs_entry ini[], int nr_entries, struct dentry *dent) { struct dw_edma_debugfs_entry *entries; @@ -112,62 +116,63 @@ static void dw_edma_debugfs_create_x32(const struct dw_edma_debugfs_entry ini[], } } -static void dw_edma_debugfs_regs_ch(enum dw_edma_dir dir, u16 ch, - struct dentry *dent) +static void dw_edma_debugfs_regs_ch(struct dw_edma *dw, enum dw_edma_dir dir, + u16 ch, struct dentry *dent) { struct dw_edma_debugfs_entry debugfs_regs[] = { - CTX_REGISTER(ch_control1, dir, ch), - CTX_REGISTER(ch_control2, dir, ch), - CTX_REGISTER(transfer_size, dir, ch), - CTX_REGISTER(sar.lsb, dir, ch), - CTX_REGISTER(sar.msb, dir, ch), - CTX_REGISTER(dar.lsb, dir, ch), - CTX_REGISTER(dar.msb, dir, ch), - CTX_REGISTER(llp.lsb, dir, ch), - CTX_REGISTER(llp.msb, dir, ch), + CTX_REGISTER(dw, ch_control1, dir, ch), + CTX_REGISTER(dw, ch_control2, dir, ch), + CTX_REGISTER(dw, transfer_size, dir, ch), + CTX_REGISTER(dw, sar.lsb, dir, ch), + CTX_REGISTER(dw, sar.msb, dir, ch), + CTX_REGISTER(dw, dar.lsb, dir, ch), + CTX_REGISTER(dw, dar.msb, dir, ch), + CTX_REGISTER(dw, llp.lsb, dir, ch), + CTX_REGISTER(dw, llp.msb, dir, ch), }; int nr_entries; nr_entries = ARRAY_SIZE(debugfs_regs); - dw_edma_debugfs_create_x32(debugfs_regs, nr_entries, dent); + dw_edma_debugfs_create_x32(dw, debugfs_regs, nr_entries, dent); } -static void dw_edma_debugfs_regs_wr(struct dentry *dent) +static noinline_for_stack void +dw_edma_debugfs_regs_wr(struct dw_edma *dw, struct dentry *dent) { const struct dw_edma_debugfs_entry debugfs_regs[] = { /* eDMA global registers */ - WR_REGISTER(engine_en), - WR_REGISTER(doorbell), - WR_REGISTER(ch_arb_weight.lsb), - WR_REGISTER(ch_arb_weight.msb), + WR_REGISTER(dw, engine_en), + WR_REGISTER(dw, doorbell), + WR_REGISTER(dw, ch_arb_weight.lsb), + WR_REGISTER(dw, ch_arb_weight.msb), /* eDMA interrupts registers */ - WR_REGISTER(int_status), - WR_REGISTER(int_mask), - WR_REGISTER(int_clear), - WR_REGISTER(err_status), - WR_REGISTER(done_imwr.lsb), - WR_REGISTER(done_imwr.msb), - WR_REGISTER(abort_imwr.lsb), - WR_REGISTER(abort_imwr.msb), - WR_REGISTER(ch01_imwr_data), - WR_REGISTER(ch23_imwr_data), - WR_REGISTER(ch45_imwr_data), - WR_REGISTER(ch67_imwr_data), - WR_REGISTER(linked_list_err_en), + WR_REGISTER(dw, int_status), + WR_REGISTER(dw, int_mask), + WR_REGISTER(dw, int_clear), + WR_REGISTER(dw, err_status), + WR_REGISTER(dw, done_imwr.lsb), + WR_REGISTER(dw, done_imwr.msb), + WR_REGISTER(dw, abort_imwr.lsb), + WR_REGISTER(dw, abort_imwr.msb), + WR_REGISTER(dw, ch01_imwr_data), + WR_REGISTER(dw, ch23_imwr_data), + WR_REGISTER(dw, ch45_imwr_data), + WR_REGISTER(dw, ch67_imwr_data), + WR_REGISTER(dw, linked_list_err_en), }; const struct dw_edma_debugfs_entry debugfs_unroll_regs[] = { /* eDMA channel context grouping */ - WR_REGISTER_UNROLL(engine_chgroup), - WR_REGISTER_UNROLL(engine_hshake_cnt.lsb), - WR_REGISTER_UNROLL(engine_hshake_cnt.msb), - WR_REGISTER_UNROLL(ch0_pwr_en), - WR_REGISTER_UNROLL(ch1_pwr_en), - WR_REGISTER_UNROLL(ch2_pwr_en), - WR_REGISTER_UNROLL(ch3_pwr_en), - WR_REGISTER_UNROLL(ch4_pwr_en), - WR_REGISTER_UNROLL(ch5_pwr_en), - WR_REGISTER_UNROLL(ch6_pwr_en), - WR_REGISTER_UNROLL(ch7_pwr_en), + WR_REGISTER_UNROLL(dw, engine_chgroup), + WR_REGISTER_UNROLL(dw, engine_hshake_cnt.lsb), + WR_REGISTER_UNROLL(dw, engine_hshake_cnt.msb), + WR_REGISTER_UNROLL(dw, ch0_pwr_en), + WR_REGISTER_UNROLL(dw, ch1_pwr_en), + WR_REGISTER_UNROLL(dw, ch2_pwr_en), + WR_REGISTER_UNROLL(dw, ch3_pwr_en), + WR_REGISTER_UNROLL(dw, ch4_pwr_en), + WR_REGISTER_UNROLL(dw, ch5_pwr_en), + WR_REGISTER_UNROLL(dw, ch6_pwr_en), + WR_REGISTER_UNROLL(dw, ch7_pwr_en), }; struct dentry *regs_dent, *ch_dent; int nr_entries, i; @@ -176,11 +181,11 @@ static void dw_edma_debugfs_regs_wr(struct dentry *dent) regs_dent = debugfs_create_dir(WRITE_STR, dent); nr_entries = ARRAY_SIZE(debugfs_regs); - dw_edma_debugfs_create_x32(debugfs_regs, nr_entries, regs_dent); + dw_edma_debugfs_create_x32(dw, debugfs_regs, nr_entries, regs_dent); if (dw->chip->mf == EDMA_MF_HDMA_COMPAT) { nr_entries = ARRAY_SIZE(debugfs_unroll_regs); - dw_edma_debugfs_create_x32(debugfs_unroll_regs, nr_entries, + dw_edma_debugfs_create_x32(dw, debugfs_unroll_regs, nr_entries, regs_dent); } @@ -189,47 +194,48 @@ static void dw_edma_debugfs_regs_wr(struct dentry *dent) ch_dent = debugfs_create_dir(name, regs_dent); - dw_edma_debugfs_regs_ch(EDMA_DIR_WRITE, i, ch_dent); + dw_edma_debugfs_regs_ch(dw, EDMA_DIR_WRITE, i, ch_dent); } } -static void dw_edma_debugfs_regs_rd(struct dentry *dent) +static noinline_for_stack void dw_edma_debugfs_regs_rd(struct dw_edma *dw, + struct dentry *dent) { const struct dw_edma_debugfs_entry debugfs_regs[] = { /* eDMA global registers */ - RD_REGISTER(engine_en), - RD_REGISTER(doorbell), - RD_REGISTER(ch_arb_weight.lsb), - RD_REGISTER(ch_arb_weight.msb), + RD_REGISTER(dw, engine_en), + RD_REGISTER(dw, doorbell), + RD_REGISTER(dw, ch_arb_weight.lsb), + RD_REGISTER(dw, ch_arb_weight.msb), /* eDMA interrupts registers */ - RD_REGISTER(int_status), - RD_REGISTER(int_mask), - RD_REGISTER(int_clear), - RD_REGISTER(err_status.lsb), - RD_REGISTER(err_status.msb), - RD_REGISTER(linked_list_err_en), - RD_REGISTER(done_imwr.lsb), - RD_REGISTER(done_imwr.msb), - RD_REGISTER(abort_imwr.lsb), - RD_REGISTER(abort_imwr.msb), - RD_REGISTER(ch01_imwr_data), - RD_REGISTER(ch23_imwr_data), - RD_REGISTER(ch45_imwr_data), - RD_REGISTER(ch67_imwr_data), + RD_REGISTER(dw, int_status), + RD_REGISTER(dw, int_mask), + RD_REGISTER(dw, int_clear), + RD_REGISTER(dw, err_status.lsb), + RD_REGISTER(dw, err_status.msb), + RD_REGISTER(dw, linked_list_err_en), + RD_REGISTER(dw, done_imwr.lsb), + RD_REGISTER(dw, done_imwr.msb), + RD_REGISTER(dw, abort_imwr.lsb), + RD_REGISTER(dw, abort_imwr.msb), + RD_REGISTER(dw, ch01_imwr_data), + RD_REGISTER(dw, ch23_imwr_data), + RD_REGISTER(dw, ch45_imwr_data), + RD_REGISTER(dw, ch67_imwr_data), }; const struct dw_edma_debugfs_entry debugfs_unroll_regs[] = { /* eDMA channel context grouping */ - RD_REGISTER_UNROLL(engine_chgroup), - RD_REGISTER_UNROLL(engine_hshake_cnt.lsb), - RD_REGISTER_UNROLL(engine_hshake_cnt.msb), - RD_REGISTER_UNROLL(ch0_pwr_en), - RD_REGISTER_UNROLL(ch1_pwr_en), - RD_REGISTER_UNROLL(ch2_pwr_en), - RD_REGISTER_UNROLL(ch3_pwr_en), - RD_REGISTER_UNROLL(ch4_pwr_en), - RD_REGISTER_UNROLL(ch5_pwr_en), - RD_REGISTER_UNROLL(ch6_pwr_en), - RD_REGISTER_UNROLL(ch7_pwr_en), + RD_REGISTER_UNROLL(dw, engine_chgroup), + RD_REGISTER_UNROLL(dw, engine_hshake_cnt.lsb), + RD_REGISTER_UNROLL(dw, engine_hshake_cnt.msb), + RD_REGISTER_UNROLL(dw, ch0_pwr_en), + RD_REGISTER_UNROLL(dw, ch1_pwr_en), + RD_REGISTER_UNROLL(dw, ch2_pwr_en), + RD_REGISTER_UNROLL(dw, ch3_pwr_en), + RD_REGISTER_UNROLL(dw, ch4_pwr_en), + RD_REGISTER_UNROLL(dw, ch5_pwr_en), + RD_REGISTER_UNROLL(dw, ch6_pwr_en), + RD_REGISTER_UNROLL(dw, ch7_pwr_en), }; struct dentry *regs_dent, *ch_dent; int nr_entries, i; @@ -238,11 +244,11 @@ static void dw_edma_debugfs_regs_rd(struct dentry *dent) regs_dent = debugfs_create_dir(READ_STR, dent); nr_entries = ARRAY_SIZE(debugfs_regs); - dw_edma_debugfs_create_x32(debugfs_regs, nr_entries, regs_dent); + dw_edma_debugfs_create_x32(dw, debugfs_regs, nr_entries, regs_dent); if (dw->chip->mf == EDMA_MF_HDMA_COMPAT) { nr_entries = ARRAY_SIZE(debugfs_unroll_regs); - dw_edma_debugfs_create_x32(debugfs_unroll_regs, nr_entries, + dw_edma_debugfs_create_x32(dw, debugfs_unroll_regs, nr_entries, regs_dent); } @@ -251,15 +257,15 @@ static void dw_edma_debugfs_regs_rd(struct dentry *dent) ch_dent = debugfs_create_dir(name, regs_dent); - dw_edma_debugfs_regs_ch(EDMA_DIR_READ, i, ch_dent); + dw_edma_debugfs_regs_ch(dw, EDMA_DIR_READ, i, ch_dent); } } -static void dw_edma_debugfs_regs(void) +static void dw_edma_debugfs_regs(struct dw_edma *dw) { const struct dw_edma_debugfs_entry debugfs_regs[] = { - REGISTER(ctrl_data_arb_prior), - REGISTER(ctrl), + REGISTER(dw, ctrl_data_arb_prior), + REGISTER(dw, ctrl), }; struct dentry *regs_dent; int nr_entries; @@ -267,40 +273,28 @@ static void dw_edma_debugfs_regs(void) regs_dent = debugfs_create_dir(REGISTERS_STR, dw->debugfs); nr_entries = ARRAY_SIZE(debugfs_regs); - dw_edma_debugfs_create_x32(debugfs_regs, nr_entries, regs_dent); + dw_edma_debugfs_create_x32(dw, debugfs_regs, nr_entries, regs_dent); - dw_edma_debugfs_regs_wr(regs_dent); - dw_edma_debugfs_regs_rd(regs_dent); + dw_edma_debugfs_regs_wr(dw, regs_dent); + dw_edma_debugfs_regs_rd(dw, regs_dent); } -void dw_edma_v0_debugfs_on(struct dw_edma *_dw) +void dw_edma_v0_debugfs_on(struct dw_edma *dw) { if (!debugfs_initialized()) return; - dw = _dw; - if (!dw) - return; - - regs = dw->chip->reg_base; - if (!regs) - return; - dw->debugfs = debugfs_create_dir(dw->name, NULL); debugfs_create_u32("mf", 0444, dw->debugfs, &dw->chip->mf); debugfs_create_u16("wr_ch_cnt", 0444, dw->debugfs, &dw->wr_ch_cnt); debugfs_create_u16("rd_ch_cnt", 0444, dw->debugfs, &dw->rd_ch_cnt); - dw_edma_debugfs_regs(); + dw_edma_debugfs_regs(dw); } -void dw_edma_v0_debugfs_off(struct dw_edma *_dw) +void dw_edma_v0_debugfs_off(struct dw_edma *dw) { - dw = _dw; - if (!dw) - return; - debugfs_remove_recursive(dw->debugfs); dw->debugfs = NULL; } -- cgit v1.2.3 From 3883d64449ffe80661a3280323bd89c0eb496fe3 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 13 Jan 2023 20:13:59 +0300 Subject: dmaengine: dw-edma: Join read/write channels into a single device There is no point in splitting read/write channels. First of all, eDMA read and write channels belong to one physical controller. Secondly, channel differentiation can be done by filtering and dma_get_slave_caps(). Finally, having these channels handled separately needlessly complicates the code and causes this debugfs warning: debugfs: Directory '1f052000.pcie' with parent 'dmaengine' already present! Join the read/write channels into a single DMA device. Client drivers can choose the correct channel via the DMA slave direction setting. The default value is overridden by the dw_edma_device_caps() callback in accordance with the channel type. Link: https://lore.kernel.org/r/20230113171409.30470-18-Sergey.Semin@baikalelectronics.ru Tested-by: Manivannan Sadhasivam Signed-off-by: Serge Semin Signed-off-by: Lorenzo Pieralisi Signed-off-by: Bjorn Helgaas Reviewed-by: Manivannan Sadhasivam Acked-by: Vinod Koul --- drivers/dma/dw-edma/dw-edma-core.c | 116 +++++++++++++++++++------------------ drivers/dma/dw-edma/dw-edma-core.h | 5 +- 2 files changed, 61 insertions(+), 60 deletions(-) diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c index ecd3e8f7ac5d..c3ecae4287d0 100644 --- a/drivers/dma/dw-edma/dw-edma-core.c +++ b/drivers/dma/dw-edma/dw-edma-core.c @@ -208,6 +208,24 @@ static void dw_edma_start_transfer(struct dw_edma_chan *chan) desc->chunks_alloc--; } +static void dw_edma_device_caps(struct dma_chan *dchan, + struct dma_slave_caps *caps) +{ + struct dw_edma_chan *chan = dchan2dw_edma_chan(dchan); + + if (chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) { + if (chan->dir == EDMA_DIR_READ) + caps->directions = BIT(DMA_DEV_TO_MEM); + else + caps->directions = BIT(DMA_MEM_TO_DEV); + } else { + if (chan->dir == EDMA_DIR_WRITE) + caps->directions = BIT(DMA_DEV_TO_MEM); + else + caps->directions = BIT(DMA_MEM_TO_DEV); + } +} + static int dw_edma_device_config(struct dma_chan *dchan, struct dma_slave_config *config) { @@ -717,8 +735,7 @@ static void dw_edma_free_chan_resources(struct dma_chan *dchan) } } -static int dw_edma_channel_setup(struct dw_edma *dw, bool write, - u32 wr_alloc, u32 rd_alloc) +static int dw_edma_channel_setup(struct dw_edma *dw, u32 wr_alloc, u32 rd_alloc) { struct dw_edma_chip *chip = dw->chip; struct dw_edma_region *dt_region; @@ -726,27 +743,15 @@ static int dw_edma_channel_setup(struct dw_edma *dw, bool write, struct dw_edma_chan *chan; struct dw_edma_irq *irq; struct dma_device *dma; - u32 alloc, off_alloc; - u32 i, j, cnt; - int err = 0; + u32 i, ch_cnt; u32 pos; - if (write) { - i = 0; - cnt = dw->wr_ch_cnt; - dma = &dw->wr_edma; - alloc = wr_alloc; - off_alloc = 0; - } else { - i = dw->wr_ch_cnt; - cnt = dw->rd_ch_cnt; - dma = &dw->rd_edma; - alloc = rd_alloc; - off_alloc = wr_alloc; - } + ch_cnt = dw->wr_ch_cnt + dw->rd_ch_cnt; + dma = &dw->dma; INIT_LIST_HEAD(&dma->channels); - for (j = 0; (alloc || dw->nr_irqs == 1) && j < cnt; j++, i++) { + + for (i = 0; i < ch_cnt; i++) { chan = &dw->chan[i]; dt_region = devm_kzalloc(dev, sizeof(*dt_region), GFP_KERNEL); @@ -756,52 +761,62 @@ static int dw_edma_channel_setup(struct dw_edma *dw, bool write, chan->vc.chan.private = dt_region; chan->dw = dw; - chan->id = j; - chan->dir = write ? EDMA_DIR_WRITE : EDMA_DIR_READ; + + if (i < dw->wr_ch_cnt) { + chan->id = i; + chan->dir = EDMA_DIR_WRITE; + } else { + chan->id = i - dw->wr_ch_cnt; + chan->dir = EDMA_DIR_READ; + } + chan->configured = false; chan->request = EDMA_REQ_NONE; chan->status = EDMA_ST_IDLE; - if (write) - chan->ll_max = (chip->ll_region_wr[j].sz / EDMA_LL_SZ); + if (chan->dir == EDMA_DIR_WRITE) + chan->ll_max = (chip->ll_region_wr[chan->id].sz / EDMA_LL_SZ); else - chan->ll_max = (chip->ll_region_rd[j].sz / EDMA_LL_SZ); + chan->ll_max = (chip->ll_region_rd[chan->id].sz / EDMA_LL_SZ); chan->ll_max -= 1; dev_vdbg(dev, "L. List:\tChannel %s[%u] max_cnt=%u\n", - write ? "write" : "read", j, chan->ll_max); + chan->dir == EDMA_DIR_WRITE ? "write" : "read", + chan->id, chan->ll_max); if (dw->nr_irqs == 1) pos = 0; + else if (chan->dir == EDMA_DIR_WRITE) + pos = chan->id % wr_alloc; else - pos = off_alloc + (j % alloc); + pos = wr_alloc + chan->id % rd_alloc; irq = &dw->irq[pos]; - if (write) - irq->wr_mask |= BIT(j); + if (chan->dir == EDMA_DIR_WRITE) + irq->wr_mask |= BIT(chan->id); else - irq->rd_mask |= BIT(j); + irq->rd_mask |= BIT(chan->id); irq->dw = dw; memcpy(&chan->msi, &irq->msi, sizeof(chan->msi)); dev_vdbg(dev, "MSI:\t\tChannel %s[%u] addr=0x%.8x%.8x, data=0x%.8x\n", - write ? "write" : "read", j, + chan->dir == EDMA_DIR_WRITE ? "write" : "read", chan->id, chan->msi.address_hi, chan->msi.address_lo, chan->msi.data); chan->vc.desc_free = vchan_free_desc; vchan_init(&chan->vc, dma); - if (write) { - dt_region->paddr = chip->dt_region_wr[j].paddr; - dt_region->vaddr = chip->dt_region_wr[j].vaddr; - dt_region->sz = chip->dt_region_wr[j].sz; + if (chan->dir == EDMA_DIR_WRITE) { + dt_region->paddr = chip->dt_region_wr[chan->id].paddr; + dt_region->vaddr = chip->dt_region_wr[chan->id].vaddr; + dt_region->sz = chip->dt_region_wr[chan->id].sz; } else { - dt_region->paddr = chip->dt_region_rd[j].paddr; - dt_region->vaddr = chip->dt_region_rd[j].vaddr; - dt_region->sz = chip->dt_region_rd[j].sz; + dt_region->paddr = chip->dt_region_rd[chan->id].paddr; + dt_region->vaddr = chip->dt_region_rd[chan->id].vaddr; + dt_region->sz = chip->dt_region_rd[chan->id].sz; } dw_edma_v0_core_device_config(chan); @@ -813,7 +828,7 @@ static int dw_edma_channel_setup(struct dw_edma *dw, bool write, dma_cap_set(DMA_CYCLIC, dma->cap_mask); dma_cap_set(DMA_PRIVATE, dma->cap_mask); dma_cap_set(DMA_INTERLEAVE, dma->cap_mask); - dma->directions = BIT(write ? DMA_DEV_TO_MEM : DMA_MEM_TO_DEV); + dma->directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV); dma->src_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_4_BYTES); dma->dst_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_4_BYTES); dma->residue_granularity = DMA_RESIDUE_GRANULARITY_DESCRIPTOR; @@ -822,6 +837,7 @@ static int dw_edma_channel_setup(struct dw_edma *dw, bool write, dma->dev = chip->dev; dma->device_alloc_chan_resources = dw_edma_alloc_chan_resources; dma->device_free_chan_resources = dw_edma_free_chan_resources; + dma->device_caps = dw_edma_device_caps; dma->device_config = dw_edma_device_config; dma->device_pause = dw_edma_device_pause; dma->device_resume = dw_edma_device_resume; @@ -835,9 +851,7 @@ static int dw_edma_channel_setup(struct dw_edma *dw, bool write, dma_set_max_seg_size(dma->dev, U32_MAX); /* Register DMA device */ - err = dma_async_device_register(dma); - - return err; + return dma_async_device_register(dma); } static inline void dw_edma_dec_irq_alloc(int *nr_irqs, u32 *alloc, u16 cnt) @@ -982,13 +996,8 @@ int dw_edma_probe(struct dw_edma_chip *chip) if (err) return err; - /* Setup write channels */ - err = dw_edma_channel_setup(dw, true, wr_alloc, rd_alloc); - if (err) - goto err_irq_free; - - /* Setup read channels */ - err = dw_edma_channel_setup(dw, false, wr_alloc, rd_alloc); + /* Setup write/read channels */ + err = dw_edma_channel_setup(dw, wr_alloc, rd_alloc); if (err) goto err_irq_free; @@ -1022,15 +1031,8 @@ int dw_edma_remove(struct dw_edma_chip *chip) free_irq(chip->ops->irq_vector(dev, i), &dw->irq[i]); /* Deregister eDMA device */ - dma_async_device_unregister(&dw->wr_edma); - list_for_each_entry_safe(chan, _chan, &dw->wr_edma.channels, - vc.chan.device_node) { - tasklet_kill(&chan->vc.task); - list_del(&chan->vc.chan.device_node); - } - - dma_async_device_unregister(&dw->rd_edma); - list_for_each_entry_safe(chan, _chan, &dw->rd_edma.channels, + dma_async_device_unregister(&dw->dma); + list_for_each_entry_safe(chan, _chan, &dw->dma.channels, vc.chan.device_node) { tasklet_kill(&chan->vc.task); list_del(&chan->vc.chan.device_node); diff --git a/drivers/dma/dw-edma/dw-edma-core.h b/drivers/dma/dw-edma/dw-edma-core.h index 85df2d511907..b576a8fff45a 100644 --- a/drivers/dma/dw-edma/dw-edma-core.h +++ b/drivers/dma/dw-edma/dw-edma-core.h @@ -98,10 +98,9 @@ struct dw_edma_irq { struct dw_edma { char name[20]; - struct dma_device wr_edma; - u16 wr_ch_cnt; + struct dma_device dma; - struct dma_device rd_edma; + u16 wr_ch_cnt; u16 rd_ch_cnt; struct dw_edma_irq *irq; -- cgit v1.2.3 From 6cb6e9c6050ac32c33466899b6488d5b6dd1d8e8 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 13 Jan 2023 20:14:00 +0300 Subject: dmaengine: dw-edma: Use DMA engine device debugfs subdirectory Since all DW eDMA read and write channels are now installed in a framework of a single DMA engine device, move all the DW eDMA-specific debugfs nodes into a ready-to-use DMA-engine debugfs subdirectory. It's created during the DMA-device registration and can be found in the dma_device.dbg_dev_root field. Link: https://lore.kernel.org/r/20230113171409.30470-19-Sergey.Semin@baikalelectronics.ru Tested-by: Manivannan Sadhasivam Signed-off-by: Serge Semin Signed-off-by: Lorenzo Pieralisi Signed-off-by: Bjorn Helgaas Reviewed-by: Manivannan Sadhasivam Acked-by: Vinod Koul --- drivers/dma/dw-edma/dw-edma-core.c | 3 --- drivers/dma/dw-edma/dw-edma-core.h | 3 --- drivers/dma/dw-edma/dw-edma-v0-core.c | 5 ----- drivers/dma/dw-edma/dw-edma-v0-core.h | 1 - drivers/dma/dw-edma/dw-edma-v0-debugfs.c | 16 ++++------------ drivers/dma/dw-edma/dw-edma-v0-debugfs.h | 5 ----- 6 files changed, 4 insertions(+), 29 deletions(-) diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c index c3ecae4287d0..58808bec4148 100644 --- a/drivers/dma/dw-edma/dw-edma-core.c +++ b/drivers/dma/dw-edma/dw-edma-core.c @@ -1038,9 +1038,6 @@ int dw_edma_remove(struct dw_edma_chip *chip) list_del(&chan->vc.chan.device_node); } - /* Turn debugfs off */ - dw_edma_v0_core_debugfs_off(dw); - return 0; } EXPORT_SYMBOL_GPL(dw_edma_remove); diff --git a/drivers/dma/dw-edma/dw-edma-core.h b/drivers/dma/dw-edma/dw-edma-core.h index b576a8fff45a..e3ad3e372b55 100644 --- a/drivers/dma/dw-edma/dw-edma-core.h +++ b/drivers/dma/dw-edma/dw-edma-core.h @@ -111,9 +111,6 @@ struct dw_edma { raw_spinlock_t lock; /* Only for legacy */ struct dw_edma_chip *chip; -#ifdef CONFIG_DEBUG_FS - struct dentry *debugfs; -#endif /* CONFIG_DEBUG_FS */ }; struct dw_edma_sg { diff --git a/drivers/dma/dw-edma/dw-edma-v0-core.c b/drivers/dma/dw-edma/dw-edma-v0-core.c index 77e6cfe52e0a..66f296daac5a 100644 --- a/drivers/dma/dw-edma/dw-edma-v0-core.c +++ b/drivers/dma/dw-edma/dw-edma-v0-core.c @@ -504,8 +504,3 @@ void dw_edma_v0_core_debugfs_on(struct dw_edma *dw) { dw_edma_v0_debugfs_on(dw); } - -void dw_edma_v0_core_debugfs_off(struct dw_edma *dw) -{ - dw_edma_v0_debugfs_off(dw); -} diff --git a/drivers/dma/dw-edma/dw-edma-v0-core.h b/drivers/dma/dw-edma/dw-edma-v0-core.h index 75aec6d31b21..ab96a1f48080 100644 --- a/drivers/dma/dw-edma/dw-edma-v0-core.h +++ b/drivers/dma/dw-edma/dw-edma-v0-core.h @@ -23,6 +23,5 @@ void dw_edma_v0_core_start(struct dw_edma_chunk *chunk, bool first); int dw_edma_v0_core_device_config(struct dw_edma_chan *chan); /* eDMA debug fs callbacks */ void dw_edma_v0_core_debugfs_on(struct dw_edma *dw); -void dw_edma_v0_core_debugfs_off(struct dw_edma *dw); #endif /* _DW_EDMA_V0_CORE_H */ diff --git a/drivers/dma/dw-edma/dw-edma-v0-debugfs.c b/drivers/dma/dw-edma/dw-edma-v0-debugfs.c index 70cdcef7cc61..0745d9e7d259 100644 --- a/drivers/dma/dw-edma/dw-edma-v0-debugfs.c +++ b/drivers/dma/dw-edma/dw-edma-v0-debugfs.c @@ -270,7 +270,7 @@ static void dw_edma_debugfs_regs(struct dw_edma *dw) struct dentry *regs_dent; int nr_entries; - regs_dent = debugfs_create_dir(REGISTERS_STR, dw->debugfs); + regs_dent = debugfs_create_dir(REGISTERS_STR, dw->dma.dbg_dev_root); nr_entries = ARRAY_SIZE(debugfs_regs); dw_edma_debugfs_create_x32(dw, debugfs_regs, nr_entries, regs_dent); @@ -284,17 +284,9 @@ void dw_edma_v0_debugfs_on(struct dw_edma *dw) if (!debugfs_initialized()) return; - dw->debugfs = debugfs_create_dir(dw->name, NULL); - - debugfs_create_u32("mf", 0444, dw->debugfs, &dw->chip->mf); - debugfs_create_u16("wr_ch_cnt", 0444, dw->debugfs, &dw->wr_ch_cnt); - debugfs_create_u16("rd_ch_cnt", 0444, dw->debugfs, &dw->rd_ch_cnt); + debugfs_create_u32("mf", 0444, dw->dma.dbg_dev_root, &dw->chip->mf); + debugfs_create_u16("wr_ch_cnt", 0444, dw->dma.dbg_dev_root, &dw->wr_ch_cnt); + debugfs_create_u16("rd_ch_cnt", 0444, dw->dma.dbg_dev_root, &dw->rd_ch_cnt); dw_edma_debugfs_regs(dw); } - -void dw_edma_v0_debugfs_off(struct dw_edma *dw) -{ - debugfs_remove_recursive(dw->debugfs); - dw->debugfs = NULL; -} diff --git a/drivers/dma/dw-edma/dw-edma-v0-debugfs.h b/drivers/dma/dw-edma/dw-edma-v0-debugfs.h index 3391b86edf5a..fb3342d97d6d 100644 --- a/drivers/dma/dw-edma/dw-edma-v0-debugfs.h +++ b/drivers/dma/dw-edma/dw-edma-v0-debugfs.h @@ -13,15 +13,10 @@ #ifdef CONFIG_DEBUG_FS void dw_edma_v0_debugfs_on(struct dw_edma *dw); -void dw_edma_v0_debugfs_off(struct dw_edma *dw); #else static inline void dw_edma_v0_debugfs_on(struct dw_edma *dw) { } - -static inline void dw_edma_v0_debugfs_off(struct dw_edma *dw) -{ -} #endif /* CONFIG_DEBUG_FS */ #endif /* _DW_EDMA_V0_DEBUG_FS_H */ -- cgit v1.2.3 From 5fdca4a995bcd4cf61bda40af154a730589dc524 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 25 Jan 2023 18:19:00 +0300 Subject: dmaengine: dw-edma: Fix readq_ch() return value truncation Previously, readq_ch() did a 64-bit readq(), but truncated the result by storing it in the u32 "value". Change "value" to u64 to avoid the truncation. Note: the method is currently unused, so the bug hasn't caused any problem so far. Fixes: 04e0a39fc10f ("dmaengine: dw-edma: Add writeq() and readq() for 64 bits architectures") Signed-off-by: Serge Semin Signed-off-by: Bjorn Helgaas --- drivers/dma/dw-edma/dw-edma-v0-core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/dma/dw-edma/dw-edma-v0-core.c b/drivers/dma/dw-edma/dw-edma-v0-core.c index 66f296daac5a..13f40560571b 100644 --- a/drivers/dma/dw-edma/dw-edma-v0-core.c +++ b/drivers/dma/dw-edma/dw-edma-v0-core.c @@ -192,7 +192,7 @@ static inline void writeq_ch(struct dw_edma *dw, enum dw_edma_dir dir, u16 ch, static inline u64 readq_ch(struct dw_edma *dw, enum dw_edma_dir dir, u16 ch, const void __iomem *addr) { - u32 value; + u64 value; if (dw->chip->mf == EDMA_MF_EDMA_LEGACY) { u32 viewport_sel; -- cgit v1.2.3 From b73bdc5054742d3945ae6e4daa18234c13871240 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Tue, 1 Feb 2022 23:46:14 +0300 Subject: dmaengine: dw-edma: Use non-atomic io-64 methods Instead of splitting 64-bits IOs up into two 32-bits ones, use the existing non-atomic readq()/writeq() functions. By doing so we can discard CONFIG_64BIT #ifdefs from the code. Tested-by: Manivannan Sadhasivam Signed-off-by: Serge Semin Signed-off-by: Bjorn Helgaas Reviewed-by: Manivannan Sadhasivam Acked-by: Vinod Koul --- drivers/dma/dw-edma/dw-edma-v0-core.c | 32 ++++++-------------------------- 1 file changed, 6 insertions(+), 26 deletions(-) diff --git a/drivers/dma/dw-edma/dw-edma-v0-core.c b/drivers/dma/dw-edma/dw-edma-v0-core.c index 13f40560571b..6bcc57512258 100644 --- a/drivers/dma/dw-edma/dw-edma-v0-core.c +++ b/drivers/dma/dw-edma/dw-edma-v0-core.c @@ -8,6 +8,8 @@ #include +#include + #include "dw-edma-core.h" #include "dw-edma-v0-core.h" #include "dw-edma-v0-regs.h" @@ -53,8 +55,6 @@ static inline struct dw_edma_v0_regs __iomem *__dw_regs(struct dw_edma *dw) SET_32(dw, rd_##name, value); \ } while (0) -#ifdef CONFIG_64BIT - #define SET_64(dw, name, value) \ writeq(value, &(__dw_regs(dw)->name)) @@ -80,8 +80,6 @@ static inline struct dw_edma_v0_regs __iomem *__dw_regs(struct dw_edma *dw) SET_64(dw, rd_##name, value); \ } while (0) -#endif /* CONFIG_64BIT */ - #define SET_COMPAT(dw, name, value) \ writel(value, &(__dw_regs(dw)->type.unroll.name)) @@ -164,8 +162,6 @@ static inline u32 readl_ch(struct dw_edma *dw, enum dw_edma_dir dir, u16 ch, #define SET_LL_32(ll, value) \ writel(value, ll) -#ifdef CONFIG_64BIT - static inline void writeq_ch(struct dw_edma *dw, enum dw_edma_dir dir, u16 ch, u64 value, void __iomem *addr) { @@ -225,8 +221,6 @@ static inline u64 readq_ch(struct dw_edma *dw, enum dw_edma_dir dir, u16 ch, #define SET_LL_64(ll, value) \ writeq(value, ll) -#endif /* CONFIG_64BIT */ - /* eDMA management callbacks */ void dw_edma_v0_core_off(struct dw_edma *dw) { @@ -325,19 +319,10 @@ static void dw_edma_v0_core_write_chunk(struct dw_edma_chunk *chunk) /* Transfer size */ SET_LL_32(&lli[i].transfer_size, child->sz); /* SAR */ - #ifdef CONFIG_64BIT - SET_LL_64(&lli[i].sar.reg, child->sar); - #else /* CONFIG_64BIT */ - SET_LL_32(&lli[i].sar.lsb, lower_32_bits(child->sar)); - SET_LL_32(&lli[i].sar.msb, upper_32_bits(child->sar)); - #endif /* CONFIG_64BIT */ + SET_LL_64(&lli[i].sar.reg, child->sar); /* DAR */ - #ifdef CONFIG_64BIT - SET_LL_64(&lli[i].dar.reg, child->dar); - #else /* CONFIG_64BIT */ - SET_LL_32(&lli[i].dar.lsb, lower_32_bits(child->dar)); - SET_LL_32(&lli[i].dar.msb, upper_32_bits(child->dar)); - #endif /* CONFIG_64BIT */ + SET_LL_64(&lli[i].dar.reg, child->dar); + i++; } @@ -349,12 +334,7 @@ static void dw_edma_v0_core_write_chunk(struct dw_edma_chunk *chunk) /* Channel control */ SET_LL_32(&llp->control, control); /* Linked list */ - #ifdef CONFIG_64BIT - SET_LL_64(&llp->llp.reg, chunk->ll_region.paddr); - #else /* CONFIG_64BIT */ - SET_LL_32(&llp->llp.lsb, lower_32_bits(chunk->ll_region.paddr)); - SET_LL_32(&llp->llp.msb, upper_32_bits(chunk->ll_region.paddr)); - #endif /* CONFIG_64BIT */ + SET_LL_64(&llp->llp.reg, chunk->ll_region.paddr); } void dw_edma_v0_core_start(struct dw_edma_chunk *chunk, bool first) -- cgit v1.2.3 From 7119685cf49033b777c559ae4da093be2a9b225c Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 13 Jan 2023 20:14:02 +0300 Subject: dmaengine: dw-edma: Drop DT-region allocation There is no point in allocating additional memory for the data target regions passed to the client drivers. Use the already available structures defined in the dw_edma_chip instance. Note: these regions are unused in normal circumstances since they are specific to the case of eDMA being embedded into the DW PCIe Endpoint and having its CSRs accessible via an Endpoint BAR. This case is only known to be implemented as a part of the Synopsys PCIe Endpoint IP prototype kit. Link: https://lore.kernel.org/r/20230113171409.30470-21-Sergey.Semin@baikalelectronics.ru Tested-by: Manivannan Sadhasivam Signed-off-by: Serge Semin Signed-off-by: Lorenzo Pieralisi Signed-off-by: Bjorn Helgaas Reviewed-by: Manivannan Sadhasivam Acked-by: Vinod Koul --- drivers/dma/dw-edma/dw-edma-core.c | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c index 58808bec4148..040a88cfe070 100644 --- a/drivers/dma/dw-edma/dw-edma-core.c +++ b/drivers/dma/dw-edma/dw-edma-core.c @@ -738,7 +738,6 @@ static void dw_edma_free_chan_resources(struct dma_chan *dchan) static int dw_edma_channel_setup(struct dw_edma *dw, u32 wr_alloc, u32 rd_alloc) { struct dw_edma_chip *chip = dw->chip; - struct dw_edma_region *dt_region; struct device *dev = chip->dev; struct dw_edma_chan *chan; struct dw_edma_irq *irq; @@ -754,12 +753,6 @@ static int dw_edma_channel_setup(struct dw_edma *dw, u32 wr_alloc, u32 rd_alloc) for (i = 0; i < ch_cnt; i++) { chan = &dw->chan[i]; - dt_region = devm_kzalloc(dev, sizeof(*dt_region), GFP_KERNEL); - if (!dt_region) - return -ENOMEM; - - chan->vc.chan.private = dt_region; - chan->dw = dw; if (i < dw->wr_ch_cnt) { @@ -807,17 +800,11 @@ static int dw_edma_channel_setup(struct dw_edma *dw, u32 wr_alloc, u32 rd_alloc) chan->msi.data); chan->vc.desc_free = vchan_free_desc; - vchan_init(&chan->vc, dma); + chan->vc.chan.private = chan->dir == EDMA_DIR_WRITE ? + &dw->chip->dt_region_wr[chan->id] : + &dw->chip->dt_region_rd[chan->id]; - if (chan->dir == EDMA_DIR_WRITE) { - dt_region->paddr = chip->dt_region_wr[chan->id].paddr; - dt_region->vaddr = chip->dt_region_wr[chan->id].vaddr; - dt_region->sz = chip->dt_region_wr[chan->id].sz; - } else { - dt_region->paddr = chip->dt_region_rd[chan->id].paddr; - dt_region->vaddr = chip->dt_region_rd[chan->id].vaddr; - dt_region->sz = chip->dt_region_rd[chan->id].sz; - } + vchan_init(&chan->vc, dma); dw_edma_v0_core_device_config(chan); } -- cgit v1.2.3 From 53c0e2f9b808fafaea7bec91eeec48046bcaaba0 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 13 Jan 2023 20:14:03 +0300 Subject: dmaengine: dw-edma: Replace chip ID number with device name Using an abstract number as the DW eDMA chip identifier isn't practical because there can be more than one DW eDMA controller on the platform. Some may be detected as the PCIe Endpoints, and others may be embedded in DW PCIe Root Port/Endpoint controllers. An abstract number in, for instance, the IRQ handlers list, doesn't give a notion regarding their reference to the particular DMA controller. To preserve the code simplicity and support multi-eDMA platforms, use the parental device name to create the DW eDMA controller name. Link: https://lore.kernel.org/r/20230113171409.30470-22-Sergey.Semin@baikalelectronics.ru Tested-by: Manivannan Sadhasivam Signed-off-by: Serge Semin Signed-off-by: Lorenzo Pieralisi Signed-off-by: Bjorn Helgaas Reviewed-by: Manivannan Sadhasivam Acked-by: Vinod Koul --- drivers/dma/dw-edma/dw-edma-core.c | 3 ++- drivers/dma/dw-edma/dw-edma-core.h | 2 +- drivers/dma/dw-edma/dw-edma-pcie.c | 1 - include/linux/dma/edma.h | 1 - 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c index 040a88cfe070..e3671bfbe186 100644 --- a/drivers/dma/dw-edma/dw-edma-core.c +++ b/drivers/dma/dw-edma/dw-edma-core.c @@ -973,7 +973,8 @@ int dw_edma_probe(struct dw_edma_chip *chip) if (!dw->chan) return -ENOMEM; - snprintf(dw->name, sizeof(dw->name), "dw-edma-core:%d", chip->id); + snprintf(dw->name, sizeof(dw->name), "dw-edma-core:%s", + dev_name(chip->dev)); /* Disable eDMA, only to establish the ideal initial conditions */ dw_edma_v0_core_off(dw); diff --git a/drivers/dma/dw-edma/dw-edma-core.h b/drivers/dma/dw-edma/dw-edma-core.h index e3ad3e372b55..0ab2b6dba880 100644 --- a/drivers/dma/dw-edma/dw-edma-core.h +++ b/drivers/dma/dw-edma/dw-edma-core.h @@ -96,7 +96,7 @@ struct dw_edma_irq { }; struct dw_edma { - char name[20]; + char name[32]; struct dma_device dma; diff --git a/drivers/dma/dw-edma/dw-edma-pcie.c b/drivers/dma/dw-edma/dw-edma-pcie.c index f530bacfd716..3f9dadc73854 100644 --- a/drivers/dma/dw-edma/dw-edma-pcie.c +++ b/drivers/dma/dw-edma/dw-edma-pcie.c @@ -222,7 +222,6 @@ static int dw_edma_pcie_probe(struct pci_dev *pdev, /* Data structure initialization */ chip->dev = dev; - chip->id = pdev->devfn; chip->mf = vsec_data.mf; chip->nr_irqs = nr_irqs; diff --git a/include/linux/dma/edma.h b/include/linux/dma/edma.h index 07a23ecc834f..96bfd0173f3a 100644 --- a/include/linux/dma/edma.h +++ b/include/linux/dma/edma.h @@ -76,7 +76,6 @@ enum dw_edma_chip_flags { */ struct dw_edma_chip { struct device *dev; - int id; int nr_irqs; const struct dw_edma_core_ops *ops; u32 flags; -- cgit v1.2.3 From 4ac1662105926c963d19e66caab16c77de01e681 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 13 Jan 2023 20:14:04 +0300 Subject: dmaengine: dw-edma: Skip cleanup procedure if no private data found DW eDMA driver private data is preserved in the passed DW eDMA chip info structure. If the probe fails or for some reason the passed info object doesn't have the private data pointer initialized, halt the DMA device cleanup procedure to prevent system crashes. Link: https://lore.kernel.org/r/20230113171409.30470-23-Sergey.Semin@baikalelectronics.ru Tested-by: Manivannan Sadhasivam Signed-off-by: Serge Semin Signed-off-by: Lorenzo Pieralisi Signed-off-by: Bjorn Helgaas Reviewed-by: Manivannan Sadhasivam Acked-by: Vinod Koul --- drivers/dma/dw-edma/dw-edma-core.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/dma/dw-edma/dw-edma-core.c b/drivers/dma/dw-edma/dw-edma-core.c index e3671bfbe186..1906a836f0aa 100644 --- a/drivers/dma/dw-edma/dw-edma-core.c +++ b/drivers/dma/dw-edma/dw-edma-core.c @@ -1011,6 +1011,10 @@ int dw_edma_remove(struct dw_edma_chip *chip) struct dw_edma *dw = chip->dw; int i; + /* Skip removal if no private data found */ + if (!dw) + return -ENODEV; + /* Disable eDMA */ dw_edma_v0_core_off(dw); -- cgit v1.2.3 From c768f8c5f40fcdc6f058cc2f02592163d6c6716c Mon Sep 17 00:00:00 2001 From: Huacai Chen Date: Sat, 11 Feb 2023 10:33:21 +0800 Subject: PCI: loongson: Add more devices that need MRRS quirk Loongson-2K SOC and LS7A2000 chipset add new PCI IDs that need MRRS quirk. Add them. Link: https://lore.kernel.org/r/20230211023321.3530080-1-chenhuacai@loongson.cn Signed-off-by: Huacai Chen Signed-off-by: Bjorn Helgaas --- drivers/pci/controller/pci-loongson.c | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/drivers/pci/controller/pci-loongson.c b/drivers/pci/controller/pci-loongson.c index 759ec211c17b..fe0f732f6e43 100644 --- a/drivers/pci/controller/pci-loongson.c +++ b/drivers/pci/controller/pci-loongson.c @@ -15,9 +15,14 @@ #include "../pci.h" /* Device IDs */ -#define DEV_PCIE_PORT_0 0x7a09 -#define DEV_PCIE_PORT_1 0x7a19 -#define DEV_PCIE_PORT_2 0x7a29 +#define DEV_LS2K_PCIE_PORT0 0x1a05 +#define DEV_LS7A_PCIE_PORT0 0x7a09 +#define DEV_LS7A_PCIE_PORT1 0x7a19 +#define DEV_LS7A_PCIE_PORT2 0x7a29 +#define DEV_LS7A_PCIE_PORT3 0x7a39 +#define DEV_LS7A_PCIE_PORT4 0x7a49 +#define DEV_LS7A_PCIE_PORT5 0x7a59 +#define DEV_LS7A_PCIE_PORT6 0x7a69 #define DEV_LS2K_APB 0x7a02 #define DEV_LS7A_GMAC 0x7a03 @@ -53,11 +58,11 @@ static void bridge_class_quirk(struct pci_dev *dev) dev->class = PCI_CLASS_BRIDGE_PCI_NORMAL; } DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, - DEV_PCIE_PORT_0, bridge_class_quirk); + DEV_LS7A_PCIE_PORT0, bridge_class_quirk); DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, - DEV_PCIE_PORT_1, bridge_class_quirk); + DEV_LS7A_PCIE_PORT1, bridge_class_quirk); DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, - DEV_PCIE_PORT_2, bridge_class_quirk); + DEV_LS7A_PCIE_PORT2, bridge_class_quirk); static void system_bus_quirk(struct pci_dev *pdev) { @@ -87,11 +92,21 @@ static void loongson_mrrs_quirk(struct pci_dev *pdev) bridge->no_inc_mrrs = 1; } DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, - DEV_PCIE_PORT_0, loongson_mrrs_quirk); + DEV_LS2K_PCIE_PORT0, loongson_mrrs_quirk); DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, - DEV_PCIE_PORT_1, loongson_mrrs_quirk); + DEV_LS7A_PCIE_PORT0, loongson_mrrs_quirk); DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, - DEV_PCIE_PORT_2, loongson_mrrs_quirk); + DEV_LS7A_PCIE_PORT1, loongson_mrrs_quirk); +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, + DEV_LS7A_PCIE_PORT2, loongson_mrrs_quirk); +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, + DEV_LS7A_PCIE_PORT3, loongson_mrrs_quirk); +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, + DEV_LS7A_PCIE_PORT4, loongson_mrrs_quirk); +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, + DEV_LS7A_PCIE_PORT5, loongson_mrrs_quirk); +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, + DEV_LS7A_PCIE_PORT6, loongson_mrrs_quirk); static void loongson_pci_pin_quirk(struct pci_dev *pdev) { -- cgit v1.2.3 From 6fffbc7ae1373e10b989afe23a9eeb9c49fe15c3 Mon Sep 17 00:00:00 2001 From: Rob Herring Date: Fri, 10 Feb 2023 10:43:51 -0600 Subject: PCI: Honor firmware's device disabled status If a device has a firmware node (DT/ACPI), and the device is marked disabled, that is currently ignored. Add a check for this condition and bail out creating the pci_dev. This assumes the config space for the device can still be accessed because they already have by this point in order to identify the device. Link: https://lore.kernel.org/r/20230210164351.2687475-1-robh@kernel.org Tested-by: Binbin Zhou Signed-off-by: Rob Herring Signed-off-by: Bjorn Helgaas Cc: Liu Peibao Cc: Huacai Chen --- drivers/pci/probe.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 1779582fb500..b1d80c1d7a69 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -1841,6 +1841,8 @@ int pci_setup_device(struct pci_dev *dev) pci_set_of_node(dev); pci_set_acpi_fwnode(dev); + if (dev->dev.fwnode && !fwnode_device_is_available(dev->dev.fwnode)) + return -ENODEV; pci_dev_assign_slot(dev); -- cgit v1.2.3 From da87d35a6e51480703d6f055e7a1b52c85731d7e Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Tue, 24 Jan 2023 12:41:54 +0530 Subject: PCI: dra7xx: Use threaded IRQ handler for "dra7xx-pcie-main" IRQ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "dra7xx-pcie-main" hard IRQ handler is just printing the IRQ status and calling the dw_pcie_ep_linkup() API if LINK_UP status is set. But the execution of dw_pcie_ep_linkup() depends on the EPF driver and may take more time depending on the EPF implementation. In general, hard IRQ handlers are supposed to return quickly and not block for so long. Moreover, there is no real need of the current IRQ handler to be a hard IRQ handler. So switch to the threaded IRQ handler for the "dra7xx-pcie-main" IRQ. Link: https://lore.kernel.org/linux-pci/20230124071158.5503-2-manivannan.sadhasivam@linaro.org Signed-off-by: Manivannan Sadhasivam Signed-off-by: Krzysztof Wilczyński Acked-by: Kishon Vijay Abraham I --- drivers/pci/controller/dwc/pci-dra7xx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pci/controller/dwc/pci-dra7xx.c b/drivers/pci/controller/dwc/pci-dra7xx.c index 38462ed11d07..4ae807e7cf79 100644 --- a/drivers/pci/controller/dwc/pci-dra7xx.c +++ b/drivers/pci/controller/dwc/pci-dra7xx.c @@ -840,7 +840,7 @@ static int dra7xx_pcie_probe(struct platform_device *pdev) } dra7xx->mode = mode; - ret = devm_request_irq(dev, irq, dra7xx_pcie_irq_handler, + ret = devm_request_threaded_irq(dev, irq, NULL, dra7xx_pcie_irq_handler, IRQF_SHARED, "dra7xx-pcie-main", dra7xx); if (ret) { dev_err(dev, "failed to request irq\n"); -- cgit v1.2.3 From c2cc5cdda46c0a94ff82bf61016ada2e120f1a3f Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Tue, 24 Jan 2023 12:41:55 +0530 Subject: PCI: tegra194: Move dw_pcie_ep_linkup() to threaded IRQ handler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dw_pcie_ep_linkup() may take more time to execute depending on the EPF driver implementation. Calling this API in the hard IRQ handler is not encouraged since the hard IRQ handlers are supposed to complete quickly. So move the dw_pcie_ep_linkup() call to threaded IRQ handler. Link: https://lore.kernel.org/linux-pci/20230124071158.5503-3-manivannan.sadhasivam@linaro.org Signed-off-by: Manivannan Sadhasivam Signed-off-by: Krzysztof Wilczyński Reviewed-by: Vidya Sagar --- drivers/pci/controller/dwc/pcie-tegra194.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-tegra194.c b/drivers/pci/controller/dwc/pcie-tegra194.c index 02d78a12b6e7..09825b4a075e 100644 --- a/drivers/pci/controller/dwc/pcie-tegra194.c +++ b/drivers/pci/controller/dwc/pcie-tegra194.c @@ -286,6 +286,7 @@ struct tegra_pcie_dw { struct gpio_desc *pex_refclk_sel_gpiod; unsigned int pex_rst_irq; int ep_state; + long link_status; }; static inline struct tegra_pcie_dw *to_tegra_pcie(struct dw_pcie *pci) @@ -449,9 +450,13 @@ static void pex_ep_event_hot_rst_done(struct tegra_pcie_dw *pcie) static irqreturn_t tegra_pcie_ep_irq_thread(int irq, void *arg) { struct tegra_pcie_dw *pcie = arg; + struct dw_pcie_ep *ep = &pcie->pci.ep; struct dw_pcie *pci = &pcie->pci; u32 val, speed; + if (test_and_clear_bit(0, &pcie->link_status)) + dw_pcie_ep_linkup(ep); + speed = dw_pcie_readw_dbi(pci, pcie->pcie_cap_base + PCI_EXP_LNKSTA) & PCI_EXP_LNKSTA_CLS; clk_set_rate(pcie->core_clk, pcie_gen_freq[speed - 1]); @@ -498,7 +503,6 @@ static irqreturn_t tegra_pcie_ep_irq_thread(int irq, void *arg) static irqreturn_t tegra_pcie_ep_hard_irq(int irq, void *arg) { struct tegra_pcie_dw *pcie = arg; - struct dw_pcie_ep *ep = &pcie->pci.ep; int spurious = 1; u32 status_l0, status_l1, link_status; @@ -514,7 +518,8 @@ static irqreturn_t tegra_pcie_ep_hard_irq(int irq, void *arg) link_status = appl_readl(pcie, APPL_LINK_STATUS); if (link_status & APPL_LINK_STATUS_RDLH_LINK_UP) { dev_dbg(pcie->dev, "Link is up with Host\n"); - dw_pcie_ep_linkup(ep); + set_bit(0, &pcie->link_status); + return IRQ_WAKE_THREAD; } } -- cgit v1.2.3 From d6dd5bafaabf98a99a76227ab8dc9a89e76a198f Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Tue, 24 Jan 2023 12:41:56 +0530 Subject: PCI: endpoint: Use a separate lock for protecting epc->pci_epf list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The EPC controller maintains a list of EPF drivers added to it. For protecting this list against the concurrent accesses, the epc->lock (used for protecting epc_ops) has been used so far. Since there were no users trying to use epc_ops and modify the pci_epf list simultaneously, this was not an issue. But with the addition of callback mechanism for passing the events, this will be a problem. Because the pci_epf list needs to be iterated first for getting hold of the EPF driver and then the relevant event specific callback needs to be called for the driver. If the same epc->lock is used, then it will result in a deadlock scenario. For instance, ... mutex_lock(&epc->lock); list_for_each_entry(epf, &epc->pci_epf, list) { epf->event_ops->core_init(epf); | |-> pci_epc_set_bar(); | |-> mutex_lock(&epc->lock) # DEADLOCK ... So to fix this issue, use a separate lock called "list_lock" for protecting the pci_epf list against the concurrent accesses. This lock will also be used by the callback mechanism. Link: https://lore.kernel.org/linux-pci/20230124071158.5503-4-manivannan.sadhasivam@linaro.org Signed-off-by: Manivannan Sadhasivam Signed-off-by: Krzysztof Wilczyński Acked-by: Kishon Vijay Abraham I --- drivers/pci/endpoint/pci-epc-core.c | 9 +++++---- include/linux/pci-epc.h | 2 ++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/pci/endpoint/pci-epc-core.c b/drivers/pci/endpoint/pci-epc-core.c index 2542196e8c3d..2c023db8f51c 100644 --- a/drivers/pci/endpoint/pci-epc-core.c +++ b/drivers/pci/endpoint/pci-epc-core.c @@ -613,7 +613,7 @@ int pci_epc_add_epf(struct pci_epc *epc, struct pci_epf *epf, if (type == SECONDARY_INTERFACE && epf->sec_epc) return -EBUSY; - mutex_lock(&epc->lock); + mutex_lock(&epc->list_lock); func_no = find_first_zero_bit(&epc->function_num_map, BITS_PER_LONG); if (func_no >= BITS_PER_LONG) { @@ -640,7 +640,7 @@ int pci_epc_add_epf(struct pci_epc *epc, struct pci_epf *epf, list_add_tail(list, &epc->pci_epf); ret: - mutex_unlock(&epc->lock); + mutex_unlock(&epc->list_lock); return ret; } @@ -672,11 +672,11 @@ void pci_epc_remove_epf(struct pci_epc *epc, struct pci_epf *epf, list = &epf->sec_epc_list; } - mutex_lock(&epc->lock); + mutex_lock(&epc->list_lock); clear_bit(func_no, &epc->function_num_map); list_del(list); epf->epc = NULL; - mutex_unlock(&epc->lock); + mutex_unlock(&epc->list_lock); } EXPORT_SYMBOL_GPL(pci_epc_remove_epf); @@ -777,6 +777,7 @@ __pci_epc_create(struct device *dev, const struct pci_epc_ops *ops, } mutex_init(&epc->lock); + mutex_init(&epc->list_lock); INIT_LIST_HEAD(&epc->pci_epf); ATOMIC_INIT_NOTIFIER_HEAD(&epc->notifier); diff --git a/include/linux/pci-epc.h b/include/linux/pci-epc.h index a48778e1a4ee..fe729dfe509b 100644 --- a/include/linux/pci-epc.h +++ b/include/linux/pci-epc.h @@ -122,6 +122,7 @@ struct pci_epc_mem { * struct pci_epc - represents the PCI EPC device * @dev: PCI EPC device * @pci_epf: list of endpoint functions present in this EPC device + * list_lock: Mutex for protecting pci_epf list * @ops: function pointers for performing endpoint operations * @windows: array of address space of the endpoint controller * @mem: first window of the endpoint controller, which corresponds to @@ -139,6 +140,7 @@ struct pci_epc_mem { struct pci_epc { struct device dev; struct list_head pci_epf; + struct mutex list_lock; const struct pci_epc_ops *ops; struct pci_epc_mem **windows; struct pci_epc_mem *mem; -- cgit v1.2.3 From 838125b07e7706b1a9069079a73507fd3df244f7 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Tue, 24 Jan 2023 12:41:57 +0530 Subject: PCI: endpoint: Use callback mechanism for passing events from EPC to EPF MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of using the notifiers for passing the events from EPC to EPF, let's introduce a callback based mechanism where the EPF drivers can populate relevant callbacks for EPC events they want to subscribe. The use of notifiers in kernel is not recommended if there is a real link between the sender and receiver, like in this case. Also, the existing atomic notifier forces the notification functions to be in atomic context while the caller may be in non-atomic context. For instance, the two in-kernel users of the notifiers, pcie-qcom and pcie-tegra194, both are calling the notifier functions in non-atomic context (from threaded IRQ handlers). This creates a sleeping in atomic context issue with the existing EPF_TEST driver that calls the EPC APIs that may sleep. For all these reasons, let's get rid of the notifier chains and use the simple callback mechanism for signalling the events from EPC to EPF drivers. This preserves the context of the caller and avoids the latency of going through a separate interface for triggering the notifications. As a first step of the transition, the core_init() callback is introduced in this commit, that'll replace the existing CORE_INIT notifier used for signalling the init complete event from EPC. During the occurrence of the event, EPC will go over the list of EPF drivers attached to it and will call the core_init() callback if available. Link: https://lore.kernel.org/linux-pci/20230124071158.5503-5-manivannan.sadhasivam@linaro.org Signed-off-by: Manivannan Sadhasivam Signed-off-by: Krzysztof Wilczyński Acked-by: Kishon Vijay Abraham I --- drivers/pci/endpoint/functions/pci-epf-test.c | 13 ++++++------- drivers/pci/endpoint/pci-epc-core.c | 11 ++++++++++- include/linux/pci-epf.h | 11 ++++++++++- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/drivers/pci/endpoint/functions/pci-epf-test.c b/drivers/pci/endpoint/functions/pci-epf-test.c index 55283d2379a6..2fe161747e51 100644 --- a/drivers/pci/endpoint/functions/pci-epf-test.c +++ b/drivers/pci/endpoint/functions/pci-epf-test.c @@ -826,20 +826,17 @@ static int pci_epf_test_core_init(struct pci_epf *epf) return 0; } +static const struct pci_epc_event_ops pci_epf_test_event_ops = { + .core_init = pci_epf_test_core_init, +}; + static int pci_epf_test_notifier(struct notifier_block *nb, unsigned long val, void *data) { struct pci_epf *epf = container_of(nb, struct pci_epf, nb); struct pci_epf_test *epf_test = epf_get_drvdata(epf); - int ret; switch (val) { - case CORE_INIT: - ret = pci_epf_test_core_init(epf); - if (ret) - return NOTIFY_BAD; - break; - case LINK_UP: queue_delayed_work(kpcitest_workqueue, &epf_test->cmd_handler, msecs_to_jiffies(1)); @@ -1010,6 +1007,8 @@ static int pci_epf_test_probe(struct pci_epf *epf) INIT_DELAYED_WORK(&epf_test->cmd_handler, pci_epf_test_cmd_handler); + epf->event_ops = &pci_epf_test_event_ops; + epf_set_drvdata(epf, epf_test); return 0; } diff --git a/drivers/pci/endpoint/pci-epc-core.c b/drivers/pci/endpoint/pci-epc-core.c index 2c023db8f51c..7cb9587c6548 100644 --- a/drivers/pci/endpoint/pci-epc-core.c +++ b/drivers/pci/endpoint/pci-epc-core.c @@ -707,10 +707,19 @@ EXPORT_SYMBOL_GPL(pci_epc_linkup); */ void pci_epc_init_notify(struct pci_epc *epc) { + struct pci_epf *epf; + if (!epc || IS_ERR(epc)) return; - atomic_notifier_call_chain(&epc->notifier, CORE_INIT, NULL); + mutex_lock(&epc->list_lock); + list_for_each_entry(epf, &epc->pci_epf, list) { + mutex_lock(&epf->lock); + if (epf->event_ops && epf->event_ops->core_init) + epf->event_ops->core_init(epf); + mutex_unlock(&epf->lock); + } + mutex_unlock(&epc->list_lock); } EXPORT_SYMBOL_GPL(pci_epc_init_notify); diff --git a/include/linux/pci-epf.h b/include/linux/pci-epf.h index 009a07147c61..fa629c191f00 100644 --- a/include/linux/pci-epf.h +++ b/include/linux/pci-epf.h @@ -18,7 +18,6 @@ struct pci_epf; enum pci_epc_interface_type; enum pci_notify_event { - CORE_INIT, LINK_UP, }; @@ -72,6 +71,14 @@ struct pci_epf_ops { struct config_group *group); }; +/** + * struct pci_epf_event_ops - Callbacks for capturing the EPC events + * @core_init: Callback for the EPC initialization complete event + */ +struct pci_epc_event_ops { + int (*core_init)(struct pci_epf *epf); +}; + /** * struct pci_epf_driver - represents the PCI EPF driver * @probe: ops to perform when a new EPF device has been bound to the EPF driver @@ -139,6 +146,7 @@ struct pci_epf_bar { * @is_vf: true - virtual function, false - physical function * @vfunction_num_map: bitmap to manage virtual function number * @pci_vepf: list of virtual endpoint functions associated with this function + * @event_ops: Callbacks for capturing the EPC events */ struct pci_epf { struct device dev; @@ -168,6 +176,7 @@ struct pci_epf { unsigned int is_vf; unsigned long vfunction_num_map; struct list_head pci_vepf; + const struct pci_epc_event_ops *event_ops; }; /** -- cgit v1.2.3 From f5edd8715e2ea672e6119c3764041743761fb178 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Tue, 24 Jan 2023 12:41:58 +0530 Subject: PCI: endpoint: Use link_up() callback in place of LINK_UP notifier MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As a part of the transition towards callback mechanism for signalling the events from EPC to EPF, let's use the link_up() callback in the place of the LINK_UP notifier. This also removes the notifier support completely from the PCI endpoint framework. Link: https://lore.kernel.org/linux-pci/20230124071158.5503-6-manivannan.sadhasivam@linaro.org Signed-off-by: Manivannan Sadhasivam Signed-off-by: Krzysztof Wilczyński Acked-by: Kishon Vijay Abraham I --- drivers/pci/endpoint/functions/pci-epf-test.c | 33 ++++++++------------------- drivers/pci/endpoint/pci-epc-core.c | 12 ++++++++-- include/linux/pci-epc.h | 8 ------- include/linux/pci-epf.h | 8 ++----- 4 files changed, 22 insertions(+), 39 deletions(-) diff --git a/drivers/pci/endpoint/functions/pci-epf-test.c b/drivers/pci/endpoint/functions/pci-epf-test.c index 2fe161747e51..0f9d2ec822ac 100644 --- a/drivers/pci/endpoint/functions/pci-epf-test.c +++ b/drivers/pci/endpoint/functions/pci-epf-test.c @@ -826,30 +826,21 @@ static int pci_epf_test_core_init(struct pci_epf *epf) return 0; } -static const struct pci_epc_event_ops pci_epf_test_event_ops = { - .core_init = pci_epf_test_core_init, -}; - -static int pci_epf_test_notifier(struct notifier_block *nb, unsigned long val, - void *data) +static int pci_epf_test_link_up(struct pci_epf *epf) { - struct pci_epf *epf = container_of(nb, struct pci_epf, nb); struct pci_epf_test *epf_test = epf_get_drvdata(epf); - switch (val) { - case LINK_UP: - queue_delayed_work(kpcitest_workqueue, &epf_test->cmd_handler, - msecs_to_jiffies(1)); - break; - - default: - dev_err(&epf->dev, "Invalid EPF test notifier event\n"); - return NOTIFY_BAD; - } + queue_delayed_work(kpcitest_workqueue, &epf_test->cmd_handler, + msecs_to_jiffies(1)); - return NOTIFY_OK; + return 0; } +static const struct pci_epc_event_ops pci_epf_test_event_ops = { + .core_init = pci_epf_test_core_init, + .link_up = pci_epf_test_link_up, +}; + static int pci_epf_test_alloc_space(struct pci_epf *epf) { struct pci_epf_test *epf_test = epf_get_drvdata(epf); @@ -976,12 +967,8 @@ static int pci_epf_test_bind(struct pci_epf *epf) if (ret) epf_test->dma_supported = false; - if (linkup_notifier || core_init_notifier) { - epf->nb.notifier_call = pci_epf_test_notifier; - pci_epc_register_notifier(epc, &epf->nb); - } else { + if (!linkup_notifier && !core_init_notifier) queue_work(kpcitest_workqueue, &epf_test->cmd_handler.work); - } return 0; } diff --git a/drivers/pci/endpoint/pci-epc-core.c b/drivers/pci/endpoint/pci-epc-core.c index 7cb9587c6548..c9c3c6b58a9f 100644 --- a/drivers/pci/endpoint/pci-epc-core.c +++ b/drivers/pci/endpoint/pci-epc-core.c @@ -690,10 +690,19 @@ EXPORT_SYMBOL_GPL(pci_epc_remove_epf); */ void pci_epc_linkup(struct pci_epc *epc) { + struct pci_epf *epf; + if (!epc || IS_ERR(epc)) return; - atomic_notifier_call_chain(&epc->notifier, LINK_UP, NULL); + mutex_lock(&epc->list_lock); + list_for_each_entry(epf, &epc->pci_epf, list) { + mutex_lock(&epf->lock); + if (epf->event_ops && epf->event_ops->link_up) + epf->event_ops->link_up(epf); + mutex_unlock(&epf->lock); + } + mutex_unlock(&epc->list_lock); } EXPORT_SYMBOL_GPL(pci_epc_linkup); @@ -788,7 +797,6 @@ __pci_epc_create(struct device *dev, const struct pci_epc_ops *ops, mutex_init(&epc->lock); mutex_init(&epc->list_lock); INIT_LIST_HEAD(&epc->pci_epf); - ATOMIC_INIT_NOTIFIER_HEAD(&epc->notifier); device_initialize(&epc->dev); epc->dev.class = pci_epc_class; diff --git a/include/linux/pci-epc.h b/include/linux/pci-epc.h index fe729dfe509b..301bb0e53707 100644 --- a/include/linux/pci-epc.h +++ b/include/linux/pci-epc.h @@ -135,7 +135,6 @@ struct pci_epc_mem { * @group: configfs group representing the PCI EPC device * @lock: mutex to protect pci_epc ops * @function_num_map: bitmap to manage physical function number - * @notifier: used to notify EPF of any EPC events (like linkup) */ struct pci_epc { struct device dev; @@ -151,7 +150,6 @@ struct pci_epc { /* mutex to protect against concurrent access of EP controller */ struct mutex lock; unsigned long function_num_map; - struct atomic_notifier_head notifier; }; /** @@ -194,12 +192,6 @@ static inline void *epc_get_drvdata(struct pci_epc *epc) return dev_get_drvdata(&epc->dev); } -static inline int -pci_epc_register_notifier(struct pci_epc *epc, struct notifier_block *nb) -{ - return atomic_notifier_chain_register(&epc->notifier, nb); -} - struct pci_epc * __devm_pci_epc_create(struct device *dev, const struct pci_epc_ops *ops, struct module *owner); diff --git a/include/linux/pci-epf.h b/include/linux/pci-epf.h index fa629c191f00..a215dc8ce693 100644 --- a/include/linux/pci-epf.h +++ b/include/linux/pci-epf.h @@ -17,10 +17,6 @@ struct pci_epf; enum pci_epc_interface_type; -enum pci_notify_event { - LINK_UP, -}; - enum pci_barno { NO_BAR = -1, BAR_0, @@ -74,9 +70,11 @@ struct pci_epf_ops { /** * struct pci_epf_event_ops - Callbacks for capturing the EPC events * @core_init: Callback for the EPC initialization complete event + * @link_up: Callback for the EPC link up event */ struct pci_epc_event_ops { int (*core_init)(struct pci_epf *epf); + int (*link_up)(struct pci_epf *epf); }; /** @@ -134,7 +132,6 @@ struct pci_epf_bar { * @epf_pf: the physical EPF device to which this virtual EPF device is bound * @driver: the EPF driver to which this EPF device is bound * @list: to add pci_epf as a list of PCI endpoint functions to pci_epc - * @nb: notifier block to notify EPF of any EPC events (like linkup) * @lock: mutex to protect pci_epf_ops * @sec_epc: the secondary EPC device to which this EPF device is bound * @sec_epc_list: to add pci_epf as list of PCI endpoint functions to secondary @@ -162,7 +159,6 @@ struct pci_epf { struct pci_epf *epf_pf; struct pci_epf_driver *driver; struct list_head list; - struct notifier_block nb; /* mutex to protect against concurrent access of pci_epf_ops */ struct mutex lock; -- cgit v1.2.3 From 9d8ba74a181b1c81def21168795ed96cbe6f05ed Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Fri, 10 Feb 2023 14:46:39 +0100 Subject: PCI: Fix dropping valid root bus resources with .end = zero On r8a7791/koelsch: kmemleak: 1 new suspected memory leaks (see /sys/kernel/debug/kmemleak) # cat /sys/kernel/debug/kmemleak unreferenced object 0xc3a34e00 (size 64): comm "swapper/0", pid 1, jiffies 4294937460 (age 199.080s) hex dump (first 32 bytes): b4 5d 81 f0 b4 5d 81 f0 c0 b0 a2 c3 00 00 00 00 .]...].......... 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ backtrace: [] __kmalloc+0xf0/0x140 [<34bd6bc0>] resource_list_create_entry+0x18/0x38 [<767046bc>] pci_add_resource_offset+0x20/0x68 [] devm_of_pci_get_host_bridge_resources.constprop.0+0xb0/0x390 When coalescing two resources for a contiguous aperture, the second resource is enlarged to cover the full contiguous range, while the first resource is marked invalid. This invalidation is done by clearing the flags, start, and end members. When adding the initial resources to the bus later, invalid resources are skipped. Unfortunately, the check for an invalid resource considers only the end member, causing false positives. E.g. on r8a7791/koelsch, root bus resource 0 ("bus 00") is skipped, and no longer registered with pci_bus_insert_busn_res() (causing the memory leak), nor printed: pci-rcar-gen2 ee090000.pci: host bridge /soc/pci@ee090000 ranges: pci-rcar-gen2 ee090000.pci: MEM 0x00ee080000..0x00ee08ffff -> 0x00ee080000 pci-rcar-gen2 ee090000.pci: PCI: revision 11 pci-rcar-gen2 ee090000.pci: PCI host bridge to bus 0000:00 -pci_bus 0000:00: root bus resource [bus 00] pci_bus 0000:00: root bus resource [mem 0xee080000-0xee08ffff] Fix this by only skipping resources where all of the flags, start, and end members are zero. Fixes: 7c3855c423b17f6c ("PCI: Coalesce host bridge contiguous apertures") Link: https://lore.kernel.org/r/da0fcd5e86c74239be79c7cb03651c0fce31b515.1676036673.git.geert+renesas@glider.be Tested-by: Niklas Schnelle Signed-off-by: Geert Uytterhoeven Signed-off-by: Bjorn Helgaas Acked-by: Kai-Heng Feng --- drivers/pci/probe.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 1779582fb500..598858482548 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -996,7 +996,7 @@ static int pci_register_host_bridge(struct pci_host_bridge *bridge) resource_list_for_each_entry_safe(window, n, &resources) { offset = window->offset; res = window->res; - if (!res->end) + if (!res->flags && !res->start && !res->end) continue; list_move_tail(&window->node, &bridge->windows); -- cgit v1.2.3 From a2b9b123ccac913e9f9b80337d687a2fe786a634 Mon Sep 17 00:00:00 2001 From: Mengyuan Lou Date: Tue, 7 Feb 2023 18:24:19 +0800 Subject: PCI: Add ACS quirk for Wangxun NICs Wangxun has verified there is no peer-to-peer between functions for the below selection of SFxxx, RP1000 and RP2000 NICS. They may be multi-function devices, but the hardware does not advertise ACS capability. Add an ACS quirk for these devices so the functions can be in independent IOMMU groups. Link: https://lore.kernel.org/r/20230207102419.44326-1-mengyuanlou@net-swift.com Signed-off-by: Mengyuan Lou Signed-off-by: Bjorn Helgaas --- drivers/pci/quirks.c | 22 ++++++++++++++++++++++ include/linux/pci_ids.h | 2 ++ 2 files changed, 24 insertions(+) diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index 20ac67d59034..494fa46f5767 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -4835,6 +4835,26 @@ static int pci_quirk_brcm_acs(struct pci_dev *dev, u16 acs_flags) PCI_ACS_SV | PCI_ACS_RR | PCI_ACS_CR | PCI_ACS_UF); } +/* + * Wangxun 10G/1G NICs have no ACS capability, and on multi-function + * devices, peer-to-peer transactions are not be used between the functions. + * So add an ACS quirk for below devices to isolate functions. + * SFxxx 1G NICs(em). + * RP1000/RP2000 10G NICs(sp). + */ +static int pci_quirk_wangxun_nic_acs(struct pci_dev *dev, u16 acs_flags) +{ + switch (dev->device) { + case 0x0100 ... 0x010F: + case 0x1001: + case 0x2001: + return pci_acs_ctrl_enabled(acs_flags, + PCI_ACS_SV | PCI_ACS_RR | PCI_ACS_CR | PCI_ACS_UF); + } + + return false; +} + static const struct pci_dev_acs_enabled { u16 vendor; u16 device; @@ -4980,6 +5000,8 @@ static const struct pci_dev_acs_enabled { { PCI_VENDOR_ID_NXP, 0x8d9b, pci_quirk_nxp_rp_acs }, /* Zhaoxin Root/Downstream Ports */ { PCI_VENDOR_ID_ZHAOXIN, PCI_ANY_ID, pci_quirk_zhaoxin_pcie_ports_acs }, + /* Wangxun nics */ + { PCI_VENDOR_ID_WANGXUN, PCI_ANY_ID, pci_quirk_wangxun_nic_acs }, { 0 } }; diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h index b362d90eb9b0..bc8f484cdcf3 100644 --- a/include/linux/pci_ids.h +++ b/include/linux/pci_ids.h @@ -3012,6 +3012,8 @@ #define PCI_DEVICE_ID_INTEL_VMD_9A0B 0x9a0b #define PCI_DEVICE_ID_INTEL_S21152BB 0xb152 +#define PCI_VENDOR_ID_WANGXUN 0x8088 + #define PCI_VENDOR_ID_SCALEMP 0x8686 #define PCI_DEVICE_ID_SCALEMP_VSMP_CTL 0x1010 -- cgit v1.2.3 From ff0132f7cf11307deb79a37622b11a16c0a294ed Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 6 Jan 2023 09:12:02 +0100 Subject: dt-bindings: PCI: qcom: Add MSM8998 specific compatible Add new compatible for MSM8998 (compatible with MSM8996) to allow further customizing if needed and to accurately describe the hardware. Link: https://lore.kernel.org/r/20230106081203.14118-2-krzysztof.kozlowski@linaro.org Signed-off-by: Krzysztof Kozlowski Signed-off-by: Bjorn Helgaas Acked-by: Rob Herring --- Documentation/devicetree/bindings/pci/qcom,pcie.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/pci/qcom,pcie.yaml b/Documentation/devicetree/bindings/pci/qcom,pcie.yaml index 09a4e3c330f1..612feec05267 100644 --- a/Documentation/devicetree/bindings/pci/qcom,pcie.yaml +++ b/Documentation/devicetree/bindings/pci/qcom,pcie.yaml @@ -36,6 +36,9 @@ properties: - qcom,pcie-sm8450-pcie0 - qcom,pcie-sm8450-pcie1 - qcom,pcie-ipq6018 + - items: + - const: qcom,pcie-msm8998 + - const: qcom,pcie-msm8996 reg: minItems: 4 -- cgit v1.2.3 From 0b93acc60cf36b35132ee8d959e7779db2fcf35b Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 6 Jan 2023 09:12:03 +0100 Subject: dt-bindings: PCI: qcom: Unify MSM8996 and MSM8998 clock order MSM8996 and MSM8998 use the same clocks, so use one order to make the binding simpler. Link: https://lore.kernel.org/r/20230106081203.14118-3-krzysztof.kozlowski@linaro.org Signed-off-by: Krzysztof Kozlowski Signed-off-by: Bjorn Helgaas Acked-by: Rob Herring --- .../devicetree/bindings/pci/qcom,pcie.yaml | 24 +++++++--------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/Documentation/devicetree/bindings/pci/qcom,pcie.yaml b/Documentation/devicetree/bindings/pci/qcom,pcie.yaml index 612feec05267..680502de2764 100644 --- a/Documentation/devicetree/bindings/pci/qcom,pcie.yaml +++ b/Documentation/devicetree/bindings/pci/qcom,pcie.yaml @@ -316,27 +316,17 @@ allOf: enum: - qcom,pcie-msm8996 then: - oneOf: - - properties: - clock-names: - items: - - const: pipe # Pipe Clock driving internal logic - - const: aux # Auxiliary (AUX) clock - - const: cfg # Configuration clock - - const: bus_master # Master AXI clock - - const: bus_slave # Slave AXI clock - - properties: - clock-names: - items: - - const: pipe # Pipe Clock driving internal logic - - const: bus_master # Master AXI clock - - const: bus_slave # Slave AXI clock - - const: cfg # Configuration clock - - const: aux # Auxiliary (AUX) clock properties: clocks: minItems: 5 maxItems: 5 + clock-names: + items: + - const: pipe # Pipe Clock driving internal logic + - const: aux # Auxiliary (AUX) clock + - const: cfg # Configuration clock + - const: bus_master # Master AXI clock + - const: bus_slave # Slave AXI clock resets: false reset-names: false -- cgit v1.2.3 From a0754633c32171be7b2a9b8717022f7848efd0c5 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Wed, 9 Nov 2022 12:32:02 +0100 Subject: dt-bindings: PCI: qcom-ep: Correct qcom,perst-regs qcom,perst-regs is an phandle array of one item with a phandle and its arguments. Link: https://lore.kernel.org/r/20221109113202.74406-1-krzysztof.kozlowski@linaro.org Signed-off-by: Krzysztof Kozlowski Signed-off-by: Lorenzo Pieralisi Signed-off-by: Bjorn Helgaas Reviewed-by: Rob Herring Reviewed-by: Dmitry Baryshkov Acked-by: Manivannan Sadhasivam --- Documentation/devicetree/bindings/pci/qcom,pcie-ep.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/pci/qcom,pcie-ep.yaml b/Documentation/devicetree/bindings/pci/qcom,pcie-ep.yaml index 8d7eb51edcb4..89cfdee4b89f 100644 --- a/Documentation/devicetree/bindings/pci/qcom,pcie-ep.yaml +++ b/Documentation/devicetree/bindings/pci/qcom,pcie-ep.yaml @@ -47,8 +47,10 @@ properties: enable registers $ref: "/schemas/types.yaml#/definitions/phandle-array" items: - minItems: 3 - maxItems: 3 + - items: + - description: Syscon to TCSR system registers + - description: Perst enable offset + - description: Perst separation enable offset interrupts: items: -- cgit v1.2.3 From 89a7adad3c0dfccf01643913bde7244feea85f59 Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Sat, 19 Nov 2022 01:32:35 +0200 Subject: dt-bindings: PCI: qcom: Add SM8350 Add bindings for two PCIe hosts on SM8350 platform. The only difference between them is in the aggre0 clock, which warrants the oneOf clause for the clocks properties. Link: https://lore.kernel.org/r/20221118233242.2904088-2-dmitry.baryshkov@linaro.org Signed-off-by: Dmitry Baryshkov Signed-off-by: Lorenzo Pieralisi Signed-off-by: Bjorn Helgaas Reviewed-by: Rob Herring --- .../devicetree/bindings/pci/qcom,pcie.yaml | 32 ++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/Documentation/devicetree/bindings/pci/qcom,pcie.yaml b/Documentation/devicetree/bindings/pci/qcom,pcie.yaml index 680502de2764..913595c08e41 100644 --- a/Documentation/devicetree/bindings/pci/qcom,pcie.yaml +++ b/Documentation/devicetree/bindings/pci/qcom,pcie.yaml @@ -33,6 +33,7 @@ properties: - qcom,pcie-sdm845 - qcom,pcie-sm8150 - qcom,pcie-sm8250 + - qcom,pcie-sm8350 - qcom,pcie-sm8450-pcie0 - qcom,pcie-sm8450-pcie1 - qcom,pcie-ipq6018 @@ -199,6 +200,7 @@ allOf: - qcom,pcie-sc8180x - qcom,pcie-sc8280xp - qcom,pcie-sm8250 + - qcom,pcie-sm8350 - qcom,pcie-sm8450-pcie0 - qcom,pcie-sm8450-pcie1 then: @@ -544,6 +546,35 @@ allOf: items: - const: pci # PCIe core reset + - if: + properties: + compatible: + contains: + enum: + - qcom,pcie-sm8350 + then: + properties: + clocks: + minItems: 8 + maxItems: 9 + clock-names: + minItems: 8 + items: + - const: aux # Auxiliary clock + - const: cfg # Configuration clock + - const: bus_master # Master AXI clock + - const: bus_slave # Slave AXI clock + - const: slave_q2a # Slave Q2A clock + - const: tbu # PCIe TBU clock + - const: ddrss_sf_tbu # PCIe SF TBU clock + - const: aggre1 # Aggre NoC PCIe1 AXI clock + - const: aggre0 # Aggre NoC PCIe0 AXI clock + resets: + maxItems: 1 + reset-names: + items: + - const: pci # PCIe core reset + - if: properties: compatible: @@ -686,6 +717,7 @@ allOf: - qcom,pcie-sdm845 - qcom,pcie-sm8150 - qcom,pcie-sm8250 + - qcom,pcie-sm8350 - qcom,pcie-sm8450-pcie0 - qcom,pcie-sm8450-pcie1 then: -- cgit v1.2.3 From 720e0d91c9772b60a87eb361da02deb3c0c628e4 Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Sat, 19 Nov 2022 01:32:37 +0200 Subject: PCI: qcom: Add SM8350 support Add support for the PCIe host on Qualcomm SM8350 platform. Link: https://lore.kernel.org/r/20221118233242.2904088-4-dmitry.baryshkov@linaro.org Signed-off-by: Dmitry Baryshkov Signed-off-by: Lorenzo Pieralisi Signed-off-by: Bjorn Helgaas Reviewed-by: Bjorn Andersson --- drivers/pci/controller/dwc/pcie-qcom.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c index 77e5dc7b88ad..b9350d93b4ba 100644 --- a/drivers/pci/controller/dwc/pcie-qcom.c +++ b/drivers/pci/controller/dwc/pcie-qcom.c @@ -1826,6 +1826,7 @@ static const struct of_device_id qcom_pcie_match[] = { { .compatible = "qcom,pcie-sdm845", .data = &cfg_2_7_0 }, { .compatible = "qcom,pcie-sm8150", .data = &cfg_1_9_0 }, { .compatible = "qcom,pcie-sm8250", .data = &cfg_1_9_0 }, + { .compatible = "qcom,pcie-sm8350", .data = &cfg_1_9_0 }, { .compatible = "qcom,pcie-sm8450-pcie0", .data = &cfg_1_9_0 }, { .compatible = "qcom,pcie-sm8450-pcie1", .data = &cfg_1_9_0 }, { } -- cgit v1.2.3 From 997e010de9134474dbfde52be03efd7d1bce902d Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 17 Oct 2022 13:47:04 +0200 Subject: PCI: qcom: Fix host-init error handling Implement the new host_deinit() callback so that the PHY is powered off and regulators and clocks are disabled also on late host-init errors. Link: https://lore.kernel.org/r/20221017114705.8277-2-johan+linaro@kernel.org Fixes: 82a823833f4e ("PCI: qcom: Add Qualcomm PCIe controller driver") Signed-off-by: Johan Hovold Signed-off-by: Lorenzo Pieralisi Signed-off-by: Bjorn Helgaas Reviewed-by: Manivannan Sadhasivam --- drivers/pci/controller/dwc/pcie-qcom.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c index b9350d93b4ba..0034cccf5038 100644 --- a/drivers/pci/controller/dwc/pcie-qcom.c +++ b/drivers/pci/controller/dwc/pcie-qcom.c @@ -1534,8 +1534,19 @@ err_deinit: return ret; } +static void qcom_pcie_host_deinit(struct dw_pcie_rp *pp) +{ + struct dw_pcie *pci = to_dw_pcie_from_pp(pp); + struct qcom_pcie *pcie = to_qcom_pcie(pci); + + qcom_ep_reset_assert(pcie); + phy_power_off(pcie->phy); + pcie->cfg->ops->deinit(pcie); +} + static const struct dw_pcie_host_ops qcom_pcie_dw_ops = { - .host_init = qcom_pcie_host_init, + .host_init = qcom_pcie_host_init, + .host_deinit = qcom_pcie_host_deinit, }; /* Qcom IP rev.: 2.1.0 Synopsys IP rev.: 4.01a */ -- cgit v1.2.3 From 371a6106b71aeda94a6fcdb8f7cb674dd3c34a96 Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Fri, 13 Jan 2023 17:44:45 +0100 Subject: dt-bindings: PCI: qcom: Sort compatibles alphabetically Sort the compatibles list alphabetically for maintenance. Link: https://lore.kernel.org/r/20230113164449.906002-5-robimarko@gmail.com Signed-off-by: Robert Marko Signed-off-by: Lorenzo Pieralisi Signed-off-by: Bjorn Helgaas Reviewed-by: Krzysztof Kozlowski --- Documentation/devicetree/bindings/pci/qcom,pcie.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/pci/qcom,pcie.yaml b/Documentation/devicetree/bindings/pci/qcom,pcie.yaml index 913595c08e41..2903cf4c1f12 100644 --- a/Documentation/devicetree/bindings/pci/qcom,pcie.yaml +++ b/Documentation/devicetree/bindings/pci/qcom,pcie.yaml @@ -18,13 +18,14 @@ properties: compatible: oneOf: - enum: - - qcom,pcie-ipq8064 - - qcom,pcie-ipq8064-v2 - qcom,pcie-apq8064 - qcom,pcie-apq8084 - - qcom,pcie-msm8996 - qcom,pcie-ipq4019 + - qcom,pcie-ipq6018 + - qcom,pcie-ipq8064 + - qcom,pcie-ipq8064-v2 - qcom,pcie-ipq8074 + - qcom,pcie-msm8996 - qcom,pcie-qcs404 - qcom,pcie-sa8540p - qcom,pcie-sc7280 @@ -36,7 +37,6 @@ properties: - qcom,pcie-sm8350 - qcom,pcie-sm8450-pcie0 - qcom,pcie-sm8450-pcie1 - - qcom,pcie-ipq6018 - items: - const: qcom,pcie-msm8998 - const: qcom,pcie-msm8996 -- cgit v1.2.3 From 2b1c46ce137b66d38a08aff924137a6fc4e9700e Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Fri, 13 Jan 2023 17:44:46 +0100 Subject: dt-bindings: PCI: qcom: Add IPQ8074 Gen3 port IPQ8074 has one Gen2 and one Gen3 PCIe port, with Gen2 already supported. Document Gen3 port which uses the same controller as IPQ6018. Link: https://lore.kernel.org/r/20230113164449.906002-6-robimarko@gmail.com Signed-off-by: Robert Marko Signed-off-by: Lorenzo Pieralisi Signed-off-by: Bjorn Helgaas Acked-by: Krzysztof Kozlowski --- Documentation/devicetree/bindings/pci/qcom,pcie.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Documentation/devicetree/bindings/pci/qcom,pcie.yaml b/Documentation/devicetree/bindings/pci/qcom,pcie.yaml index 2903cf4c1f12..fb32c43dd12d 100644 --- a/Documentation/devicetree/bindings/pci/qcom,pcie.yaml +++ b/Documentation/devicetree/bindings/pci/qcom,pcie.yaml @@ -25,6 +25,7 @@ properties: - qcom,pcie-ipq8064 - qcom,pcie-ipq8064-v2 - qcom,pcie-ipq8074 + - qcom,pcie-ipq8074-gen3 - qcom,pcie-msm8996 - qcom,pcie-qcs404 - qcom,pcie-sa8540p @@ -158,6 +159,7 @@ allOf: contains: enum: - qcom,pcie-ipq6018 + - qcom,pcie-ipq8074-gen3 then: properties: reg: @@ -369,6 +371,7 @@ allOf: contains: enum: - qcom,pcie-ipq6018 + - qcom,pcie-ipq8074-gen3 then: properties: clocks: @@ -689,6 +692,7 @@ allOf: - qcom,pcie-ipq8064 - qcom,pcie-ipq8064v2 - qcom,pcie-ipq8074 + - qcom,pcie-ipq8074-gen3 - qcom,pcie-qcs404 then: required: @@ -772,6 +776,7 @@ allOf: - qcom,pcie-ipq8064 - qcom,pcie-ipq8064-v2 - qcom,pcie-ipq8074 + - qcom,pcie-ipq8074-gen3 - qcom,pcie-qcs404 - qcom,pcie-sa8540p then: -- cgit v1.2.3 From f356132229b18ceef5d5ef9103bbaa9bdeb84c8d Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Fri, 13 Jan 2023 17:44:47 +0100 Subject: PCI: qcom: Add IPQ8074 Gen3 port support IPQ8074 has one Gen2 and one Gen3 port, with Gen2 port already supported. Add compatible for Gen3 port which uses the same controller as IPQ6018. Link: https://lore.kernel.org/r/20230113164449.906002-7-robimarko@gmail.com Signed-off-by: Robert Marko Signed-off-by: Lorenzo Pieralisi Signed-off-by: Bjorn Helgaas --- drivers/pci/controller/dwc/pcie-qcom.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c index 0034cccf5038..a232b04af048 100644 --- a/drivers/pci/controller/dwc/pcie-qcom.c +++ b/drivers/pci/controller/dwc/pcie-qcom.c @@ -1828,6 +1828,7 @@ static const struct of_device_id qcom_pcie_match[] = { { .compatible = "qcom,pcie-ipq8064", .data = &cfg_2_1_0 }, { .compatible = "qcom,pcie-ipq8064-v2", .data = &cfg_2_1_0 }, { .compatible = "qcom,pcie-ipq8074", .data = &cfg_2_3_3 }, + { .compatible = "qcom,pcie-ipq8074-gen3", .data = &cfg_2_9_0 }, { .compatible = "qcom,pcie-msm8996", .data = &cfg_2_3_2 }, { .compatible = "qcom,pcie-qcs404", .data = &cfg_2_4_0 }, { .compatible = "qcom,pcie-sa8540p", .data = &cfg_1_9_0 }, -- cgit v1.2.3 From 82b34b0800af8c9fc9988c290cdc813e0ca0df31 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Mon, 13 Feb 2023 20:19:22 +0530 Subject: PCI: pciehp: Add Qualcomm quirk for Command Completed erratum The Qualcomm PCI bridge device (Device ID 0x010e) found in chipsets such as SC8280XP used in Lenovo Thinkpad X13s, does not set the Command Completed bit unless writes to the Slot Command register change "Control" bits. This results in timeouts like below during boot and resume from suspend: pcieport 0002:00:00.0: pciehp: Timeout on hotplug command 0x03c0 (issued 2020 msec ago) ... pcieport 0002:00:00.0: pciehp: Timeout on hotplug command 0x13f1 (issued 107724 msec ago) Add the device to the Command Completed quirk to mark commands "completed" immediately unless they change the "Control" bits. Link: https://lore.kernel.org/r/20230213144922.89982-1-manivannan.sadhasivam@linaro.org Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas --- drivers/pci/hotplug/pciehp_hpc.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/pci/hotplug/pciehp_hpc.c b/drivers/pci/hotplug/pciehp_hpc.c index 10e9670eea0b..f8c70115b691 100644 --- a/drivers/pci/hotplug/pciehp_hpc.c +++ b/drivers/pci/hotplug/pciehp_hpc.c @@ -1088,6 +1088,8 @@ static void quirk_cmd_compl(struct pci_dev *pdev) } DECLARE_PCI_FIXUP_CLASS_EARLY(PCI_VENDOR_ID_INTEL, PCI_ANY_ID, PCI_CLASS_BRIDGE_PCI, 8, quirk_cmd_compl); +DECLARE_PCI_FIXUP_CLASS_EARLY(PCI_VENDOR_ID_QCOM, 0x010e, + PCI_CLASS_BRIDGE_PCI, 8, quirk_cmd_compl); DECLARE_PCI_FIXUP_CLASS_EARLY(PCI_VENDOR_ID_QCOM, 0x0110, PCI_CLASS_BRIDGE_PCI, 8, quirk_cmd_compl); DECLARE_PCI_FIXUP_CLASS_EARLY(PCI_VENDOR_ID_QCOM, 0x0400, -- cgit v1.2.3 From 74ff8864cc842be994853095dba6db48e716400a Mon Sep 17 00:00:00 2001 From: Lukas Wunner Date: Fri, 20 Jan 2023 10:19:02 +0100 Subject: PCI: hotplug: Allow marking devices as disconnected during bind/unbind On surprise removal, pciehp_unconfigure_device() and acpiphp's trim_stale_devices() call pci_dev_set_disconnected() to mark removed devices as permanently offline. Thereby, the PCI core and drivers know to skip device accesses. However pci_dev_set_disconnected() takes the device_lock and thus waits for a concurrent driver bind or unbind to complete. As a result, the driver's ->probe and ->remove hooks have no chance to learn that the device is gone. That doesn't make any sense, so drop the device_lock and instead use atomic xchg() and cmpxchg() operations to update the device state. As a byproduct, an AB-BA deadlock reported by Anatoli is fixed which occurs on surprise removal with AER concurrently performing a bus reset. AER bus reset: INFO: task irq/26-aerdrv:95 blocked for more than 120 seconds. Tainted: G W 6.2.0-rc3-custom-norework-jan11+ schedule rwsem_down_write_slowpath down_write_nested pciehp_reset_slot # acquires reset_lock pci_reset_hotplug_slot pci_slot_reset # acquires device_lock pci_bus_error_reset aer_root_reset pcie_do_recovery aer_process_err_devices aer_isr pciehp surprise removal: INFO: task irq/26-pciehp:96 blocked for more than 120 seconds. Tainted: G W 6.2.0-rc3-custom-norework-jan11+ schedule_preempt_disabled __mutex_lock mutex_lock_nested pci_dev_set_disconnected # acquires device_lock pci_walk_bus pciehp_unconfigure_device pciehp_disable_slot pciehp_handle_presence_or_link_change pciehp_ist # acquires reset_lock Link: https://bugzilla.kernel.org/show_bug.cgi?id=215590 Fixes: a6bd101b8f84 ("PCI: Unify device inaccessible") Link: https://lore.kernel.org/r/3dc88ea82bdc0e37d9000e413d5ebce481cbd629.1674205689.git.lukas@wunner.de Reported-by: Anatoli Antonovitch Signed-off-by: Lukas Wunner Signed-off-by: Bjorn Helgaas Cc: stable@vger.kernel.org # v4.20+ Cc: Keith Busch --- drivers/pci/pci.h | 43 +++++++++++++------------------------------ 1 file changed, 13 insertions(+), 30 deletions(-) diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index 9ed3b5550043..5d5a44aaafe8 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -310,53 +310,36 @@ struct pci_sriov { * @dev: PCI device to set new error_state * @new: the state we want dev to be in * - * Must be called with device_lock held. + * If the device is experiencing perm_failure, it has to remain in that state. + * Any other transition is allowed. * * Returns true if state has been changed to the requested state. */ static inline bool pci_dev_set_io_state(struct pci_dev *dev, pci_channel_state_t new) { - bool changed = false; + pci_channel_state_t old; - device_lock_assert(&dev->dev); switch (new) { case pci_channel_io_perm_failure: - switch (dev->error_state) { - case pci_channel_io_frozen: - case pci_channel_io_normal: - case pci_channel_io_perm_failure: - changed = true; - break; - } - break; + xchg(&dev->error_state, pci_channel_io_perm_failure); + return true; case pci_channel_io_frozen: - switch (dev->error_state) { - case pci_channel_io_frozen: - case pci_channel_io_normal: - changed = true; - break; - } - break; + old = cmpxchg(&dev->error_state, pci_channel_io_normal, + pci_channel_io_frozen); + return old != pci_channel_io_perm_failure; case pci_channel_io_normal: - switch (dev->error_state) { - case pci_channel_io_frozen: - case pci_channel_io_normal: - changed = true; - break; - } - break; + old = cmpxchg(&dev->error_state, pci_channel_io_frozen, + pci_channel_io_normal); + return old != pci_channel_io_perm_failure; + default: + return false; } - if (changed) - dev->error_state = new; - return changed; } static inline int pci_dev_set_disconnected(struct pci_dev *dev, void *unused) { - device_lock(&dev->dev); pci_dev_set_io_state(dev, pci_channel_io_perm_failure); - device_unlock(&dev->dev); return 0; } -- cgit v1.2.3 From 1047377754c33bf860d80714f66fa4a8e56cbbec Mon Sep 17 00:00:00 2001 From: Thomas Weißschuh Date: Thu, 16 Feb 2023 01:12:25 +0000 Subject: PCI/sysfs: Constify struct kobj_type pci_slot_ktype MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since commit ee6d3dd4ed48 ("driver core: make kobj_type constant.") the driver core allows the usage of const struct kobj_type. Take advantage of this to constify the structure definition to prevent modification at runtime. Link: https://lore.kernel.org/r/20230216-kobj_type-pci-v1-1-46a63c8612b5@weissschuh.net Signed-off-by: Thomas Weißschuh Signed-off-by: Bjorn Helgaas --- drivers/pci/slot.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pci/slot.c b/drivers/pci/slot.c index a0c67191a8b9..0f87cade10f7 100644 --- a/drivers/pci/slot.c +++ b/drivers/pci/slot.c @@ -98,7 +98,7 @@ static struct attribute *pci_slot_default_attrs[] = { }; ATTRIBUTE_GROUPS(pci_slot_default); -static struct kobj_type pci_slot_ktype = { +static const struct kobj_type pci_slot_ktype = { .sysfs_ops = &pci_slot_sysfs_ops, .release = &pci_slot_release, .default_groups = pci_slot_default_groups, -- cgit v1.2.3 From 6606f4c3c48c7a75b13816266d3ea562eeec0b44 Mon Sep 17 00:00:00 2001 From: Logan Gunthorpe Date: Thu, 9 Feb 2023 10:29:53 -0700 Subject: PCI/P2PDMA: Annotate RCU dereference A dereference of the __rcu pointer was noticed by sparse: drivers/pci/p2pdma.c:199:44: sparse: sparse: dereference of noderef expression Dereference the __rcu pointer using rcu_dereference_protected() instead of accessing it directly. It's safe to use rcu_dereference_protected() because a reference is held on the pgmap's percpu reference counter and thus it cannot disappear. Link: https://lore.kernel.org/r/20230209172953.4597-1-logang@deltatee.com Reported-by: kernel test robot Signed-off-by: Logan Gunthorpe Signed-off-by: Bjorn Helgaas Reviewed-by: Chaitanya Kulkarni --- drivers/pci/p2pdma.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/pci/p2pdma.c b/drivers/pci/p2pdma.c index 86812d2073ea..9e8205572830 100644 --- a/drivers/pci/p2pdma.c +++ b/drivers/pci/p2pdma.c @@ -194,11 +194,13 @@ static const struct attribute_group p2pmem_group = { static void p2pdma_page_free(struct page *page) { struct pci_p2pdma_pagemap *pgmap = to_p2p_pgmap(page->pgmap); + /* safe to dereference while a reference is held to the percpu ref */ + struct pci_p2pdma *p2pdma = + rcu_dereference_protected(pgmap->provider->p2pdma, 1); struct percpu_ref *ref; - gen_pool_free_owner(pgmap->provider->p2pdma->pool, - (uintptr_t)page_to_virt(page), PAGE_SIZE, - (void **)&ref); + gen_pool_free_owner(p2pdma->pool, (uintptr_t)page_to_virt(page), + PAGE_SIZE, (void **)&ref); percpu_ref_put(ref); } -- cgit v1.2.3 From 9574d57f2d43361e2e796ace8b055f4359261ba1 Mon Sep 17 00:00:00 2001 From: Lukas Bulwahn Date: Thu, 15 Dec 2022 11:13:10 +0100 Subject: PCI: hv: Drop duplicate PCI_MSI dependency Commit a474d3fbe287 ("PCI/MSI: Get rid of PCI_MSI_IRQ_DOMAIN") removed PCI_MSI_IRQ_DOMAIN and made all previous references to it refer to PCI_MSI instead. PCI_HYPERV_INTERFACE already depended on PCI_MSI && PCI_MSI_IRQ_DOMAIN, so we ended up with a redundant dependency on PCI_MSI && PCI_MSI. Drop the duplicate. No functional change. Just a stylistic clean-up. Link: https://lore.kernel.org/r/20221215101310.9135-1-lukas.bulwahn@gmail.com Signed-off-by: Lukas Bulwahn Signed-off-by: Bjorn Helgaas --- drivers/pci/controller/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pci/controller/Kconfig b/drivers/pci/controller/Kconfig index 1569d9a3ada0..c2261da85f7b 100644 --- a/drivers/pci/controller/Kconfig +++ b/drivers/pci/controller/Kconfig @@ -285,7 +285,7 @@ config PCIE_BRCMSTB config PCI_HYPERV_INTERFACE tristate "Hyper-V PCI Interface" - depends on ((X86 && X86_64) || ARM64) && HYPERV && PCI_MSI && PCI_MSI + depends on ((X86 && X86_64) || ARM64) && HYPERV && PCI_MSI help The Hyper-V PCI Interface is a helper driver allows other drivers to have a common interface with the Hyper-V PCI frontend driver. -- cgit v1.2.3 From f98954b293d0a0f9646117af75c82c1b89191c53 Mon Sep 17 00:00:00 2001 From: Nick Alcock Date: Thu, 16 Feb 2023 15:24:04 +0000 Subject: PCI: Remove MODULE_LICENSE so boolean drivers don't look like modules Since 8b41fc4454e3 ("kbuild: create modules.builtin without Makefile.modbuiltin or tristate.conf"), MODULE_LICENSE declarations are used to identify modules. As a consequence, MODULE_LICENSE in non-modules causes modprobe to misidentify the object file as a module when it is not, and modprobe might succeed rather than failing with a suitable error message. For tristate modules that can be either built-in or loaded at runtime, modprobe succeeds in both cases: # modprobe ext4 [exit status zero if CONFIG_EXT4_FS=y or =m] For boolean modules like the Standard Hot Plug Controller driver (shpchp) that cannot be loaded at runtime, modprobe should always fail like this: # modprobe shpchp modprobe: FATAL: Module shpchp not found in directory /lib/modules/... [exit status non-zero regardless of CONFIG_HOTPLUG_PCI_SHPC] but prior to this commit, shpchp_core.c contained MODULE_LICENSE, so "modprobe shpchp" silently succeeded when it should have failed. Remove MODULE_LICENSE in files that cannot be built as modules. [bhelgaas: commit log, squash] Suggested-by: Luis Chamberlain Link: https://lore.kernel.org/r/20230216152410.4312-1-nick.alcock@oracle.com/ Signed-off-by: Nick Alcock Signed-off-by: Bjorn Helgaas Reviewed-by: Manivannan Sadhasivam Cc: Luis Chamberlain Cc: Hitomi Hasegawa Cc: Rob Herring Cc: Lorenzo Pieralisi --- drivers/pci/controller/dwc/pcie-histb.c | 1 - drivers/pci/controller/mobiveil/pcie-mobiveil-plat.c | 1 - drivers/pci/controller/pci-tegra.c | 1 - drivers/pci/controller/pci-versatile.c | 1 - drivers/pci/controller/pcie-hisi-error.c | 1 - drivers/pci/controller/pcie-microchip-host.c | 1 - drivers/pci/endpoint/pci-ep-cfs.c | 1 - drivers/pci/endpoint/pci-epc-core.c | 1 - drivers/pci/endpoint/pci-epc-mem.c | 1 - drivers/pci/endpoint/pci-epf-core.c | 1 - drivers/pci/hotplug/acpiphp_core.c | 1 - drivers/pci/hotplug/shpchp_core.c | 1 - 12 files changed, 12 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-histb.c b/drivers/pci/controller/dwc/pcie-histb.c index 43c27812dd6d..927ae05dc920 100644 --- a/drivers/pci/controller/dwc/pcie-histb.c +++ b/drivers/pci/controller/dwc/pcie-histb.c @@ -450,4 +450,3 @@ static struct platform_driver histb_pcie_platform_driver = { module_platform_driver(histb_pcie_platform_driver); MODULE_DESCRIPTION("HiSilicon STB PCIe host controller driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/pci/controller/mobiveil/pcie-mobiveil-plat.c b/drivers/pci/controller/mobiveil/pcie-mobiveil-plat.c index f6fcd95c2bf5..c5bb87ff6d9a 100644 --- a/drivers/pci/controller/mobiveil/pcie-mobiveil-plat.c +++ b/drivers/pci/controller/mobiveil/pcie-mobiveil-plat.c @@ -56,6 +56,5 @@ static struct platform_driver mobiveil_pcie_driver = { builtin_platform_driver(mobiveil_pcie_driver); -MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("Mobiveil PCIe host controller driver"); MODULE_AUTHOR("Subrahmanya Lingappa "); diff --git a/drivers/pci/controller/pci-tegra.c b/drivers/pci/controller/pci-tegra.c index 929f9363e94b..5bb05564d6f8 100644 --- a/drivers/pci/controller/pci-tegra.c +++ b/drivers/pci/controller/pci-tegra.c @@ -2814,4 +2814,3 @@ static struct platform_driver tegra_pcie_driver = { .remove = tegra_pcie_remove, }; module_platform_driver(tegra_pcie_driver); -MODULE_LICENSE("GPL"); diff --git a/drivers/pci/controller/pci-versatile.c b/drivers/pci/controller/pci-versatile.c index 7991d334e0f1..e9a6758fe2c1 100644 --- a/drivers/pci/controller/pci-versatile.c +++ b/drivers/pci/controller/pci-versatile.c @@ -169,4 +169,3 @@ static struct platform_driver versatile_pci_driver = { module_platform_driver(versatile_pci_driver); MODULE_DESCRIPTION("Versatile PCI driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/pci/controller/pcie-hisi-error.c b/drivers/pci/controller/pcie-hisi-error.c index 7959c9c8d2bc..7d88eb696b06 100644 --- a/drivers/pci/controller/pcie-hisi-error.c +++ b/drivers/pci/controller/pcie-hisi-error.c @@ -324,4 +324,3 @@ static struct platform_driver hisi_pcie_error_handler_driver = { module_platform_driver(hisi_pcie_error_handler_driver); MODULE_DESCRIPTION("HiSilicon HIP PCIe controller error handling driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/pci/controller/pcie-microchip-host.c b/drivers/pci/controller/pcie-microchip-host.c index 0ebf7015e9af..5e710e485464 100644 --- a/drivers/pci/controller/pcie-microchip-host.c +++ b/drivers/pci/controller/pcie-microchip-host.c @@ -1135,6 +1135,5 @@ static struct platform_driver mc_pcie_driver = { }; builtin_platform_driver(mc_pcie_driver); -MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Microchip PCIe host controller driver"); MODULE_AUTHOR("Daire McNamara "); diff --git a/drivers/pci/endpoint/pci-ep-cfs.c b/drivers/pci/endpoint/pci-ep-cfs.c index d4850bdd837f..4b8ac0ac84d5 100644 --- a/drivers/pci/endpoint/pci-ep-cfs.c +++ b/drivers/pci/endpoint/pci-ep-cfs.c @@ -728,4 +728,3 @@ module_exit(pci_ep_cfs_exit); MODULE_DESCRIPTION("PCI EP CONFIGFS"); MODULE_AUTHOR("Kishon Vijay Abraham I "); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/pci/endpoint/pci-epc-core.c b/drivers/pci/endpoint/pci-epc-core.c index 2542196e8c3d..dd750ad29485 100644 --- a/drivers/pci/endpoint/pci-epc-core.c +++ b/drivers/pci/endpoint/pci-epc-core.c @@ -861,4 +861,3 @@ module_exit(pci_epc_exit); MODULE_DESCRIPTION("PCI EPC Library"); MODULE_AUTHOR("Kishon Vijay Abraham I "); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/pci/endpoint/pci-epc-mem.c b/drivers/pci/endpoint/pci-epc-mem.c index a97b56a6d2db..7dcf6f480b82 100644 --- a/drivers/pci/endpoint/pci-epc-mem.c +++ b/drivers/pci/endpoint/pci-epc-mem.c @@ -260,4 +260,3 @@ EXPORT_SYMBOL_GPL(pci_epc_mem_free_addr); MODULE_DESCRIPTION("PCI EPC Address Space Management"); MODULE_AUTHOR("Kishon Vijay Abraham I "); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/pci/endpoint/pci-epf-core.c b/drivers/pci/endpoint/pci-epf-core.c index 9ed556936f48..2036e38be093 100644 --- a/drivers/pci/endpoint/pci-epf-core.c +++ b/drivers/pci/endpoint/pci-epf-core.c @@ -568,4 +568,3 @@ module_exit(pci_epf_exit); MODULE_DESCRIPTION("PCI EPF Library"); MODULE_AUTHOR("Kishon Vijay Abraham I "); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/pci/hotplug/acpiphp_core.c b/drivers/pci/hotplug/acpiphp_core.c index 853e04ad272c..c02257f4b61c 100644 --- a/drivers/pci/hotplug/acpiphp_core.c +++ b/drivers/pci/hotplug/acpiphp_core.c @@ -45,7 +45,6 @@ static struct acpiphp_attention_info *attention_info; MODULE_AUTHOR(DRIVER_AUTHOR); MODULE_DESCRIPTION(DRIVER_DESC); -MODULE_LICENSE("GPL"); MODULE_PARM_DESC(disable, "disable acpiphp driver"); module_param_named(disable, acpiphp_disabled, bool, 0444); diff --git a/drivers/pci/hotplug/shpchp_core.c b/drivers/pci/hotplug/shpchp_core.c index 53692b048301..56c7795ed890 100644 --- a/drivers/pci/hotplug/shpchp_core.c +++ b/drivers/pci/hotplug/shpchp_core.c @@ -32,7 +32,6 @@ int shpchp_poll_time; MODULE_AUTHOR(DRIVER_AUTHOR); MODULE_DESCRIPTION(DRIVER_DESC); -MODULE_LICENSE("GPL"); module_param(shpchp_debug, bool, 0644); module_param(shpchp_poll_mode, bool, 0644); -- cgit v1.2.3 From 16f8a08643b6d63d2e8252ddaa9e17e2408a2531 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 13 Jan 2023 20:14:05 +0300 Subject: dmaengine: dw-edma: Add mem-mapped LL-entries support Currently the DW eDMA driver only supports the linked lists memory allocated locally with respect to the remote eDMA engine setup. It means the linked lists will be accessible by the CPU via the MMIO space only. If eDMA is embedded into the DW PCIe Root Ports or local Endpoints (which support will be added in subsequent commits) the linked lists are supposed to be allocated in the CPU memory. In that case the LL-entries can be directly accessed, while the former case implies using the MMIO accessors for that. In order to have both cases supported by the driver, the dw_edma_region descriptor should be fixed to contain the MMIO-backed and just memory-based virtual addresses. The linked lists initialization procedure will use one of them depending on the eDMA device nature. If the eDMA engine is embedded into the local DW PCIe Root Port/Endpoint controllers, the list entries will be directly accessed by referencing the corresponding structure fields. Otherwise the MMIO accessors usage will be preserved. Link: https://lore.kernel.org/r/20230113171409.30470-24-Sergey.Semin@baikalelectronics.ru Signed-off-by: Serge Semin Signed-off-by: Lorenzo Pieralisi Signed-off-by: Bjorn Helgaas Acked-by: Vinod Koul --- drivers/dma/dw-edma/dw-edma-pcie.c | 32 ++++++++-------- drivers/dma/dw-edma/dw-edma-v0-core.c | 69 ++++++++++++++++++++++------------- include/linux/dma/edma.h | 5 ++- 3 files changed, 64 insertions(+), 42 deletions(-) diff --git a/drivers/dma/dw-edma/dw-edma-pcie.c b/drivers/dma/dw-edma/dw-edma-pcie.c index 3f9dadc73854..2b40f2b44f5e 100644 --- a/drivers/dma/dw-edma/dw-edma-pcie.c +++ b/drivers/dma/dw-edma/dw-edma-pcie.c @@ -240,20 +240,20 @@ static int dw_edma_pcie_probe(struct pci_dev *pdev, struct dw_edma_block *ll_block = &vsec_data.ll_wr[i]; struct dw_edma_block *dt_block = &vsec_data.dt_wr[i]; - ll_region->vaddr = pcim_iomap_table(pdev)[ll_block->bar]; - if (!ll_region->vaddr) + ll_region->vaddr.io = pcim_iomap_table(pdev)[ll_block->bar]; + if (!ll_region->vaddr.io) return -ENOMEM; - ll_region->vaddr += ll_block->off; + ll_region->vaddr.io += ll_block->off; ll_region->paddr = pci_bus_address(pdev, ll_block->bar); ll_region->paddr += ll_block->off; ll_region->sz = ll_block->sz; - dt_region->vaddr = pcim_iomap_table(pdev)[dt_block->bar]; - if (!dt_region->vaddr) + dt_region->vaddr.io = pcim_iomap_table(pdev)[dt_block->bar]; + if (!dt_region->vaddr.io) return -ENOMEM; - dt_region->vaddr += dt_block->off; + dt_region->vaddr.io += dt_block->off; dt_region->paddr = pci_bus_address(pdev, dt_block->bar); dt_region->paddr += dt_block->off; dt_region->sz = dt_block->sz; @@ -265,20 +265,20 @@ static int dw_edma_pcie_probe(struct pci_dev *pdev, struct dw_edma_block *ll_block = &vsec_data.ll_rd[i]; struct dw_edma_block *dt_block = &vsec_data.dt_rd[i]; - ll_region->vaddr = pcim_iomap_table(pdev)[ll_block->bar]; - if (!ll_region->vaddr) + ll_region->vaddr.io = pcim_iomap_table(pdev)[ll_block->bar]; + if (!ll_region->vaddr.io) return -ENOMEM; - ll_region->vaddr += ll_block->off; + ll_region->vaddr.io += ll_block->off; ll_region->paddr = pci_bus_address(pdev, ll_block->bar); ll_region->paddr += ll_block->off; ll_region->sz = ll_block->sz; - dt_region->vaddr = pcim_iomap_table(pdev)[dt_block->bar]; - if (!dt_region->vaddr) + dt_region->vaddr.io = pcim_iomap_table(pdev)[dt_block->bar]; + if (!dt_region->vaddr.io) return -ENOMEM; - dt_region->vaddr += dt_block->off; + dt_region->vaddr.io += dt_block->off; dt_region->paddr = pci_bus_address(pdev, dt_block->bar); dt_region->paddr += dt_block->off; dt_region->sz = dt_block->sz; @@ -303,24 +303,24 @@ static int dw_edma_pcie_probe(struct pci_dev *pdev, pci_dbg(pdev, "L. List:\tWRITE CH%.2u, BAR=%u, off=0x%.8lx, sz=0x%zx bytes, addr(v=%p, p=%pa)\n", i, vsec_data.ll_wr[i].bar, vsec_data.ll_wr[i].off, chip->ll_region_wr[i].sz, - chip->ll_region_wr[i].vaddr, &chip->ll_region_wr[i].paddr); + chip->ll_region_wr[i].vaddr.io, &chip->ll_region_wr[i].paddr); pci_dbg(pdev, "Data:\tWRITE CH%.2u, BAR=%u, off=0x%.8lx, sz=0x%zx bytes, addr(v=%p, p=%pa)\n", i, vsec_data.dt_wr[i].bar, vsec_data.dt_wr[i].off, chip->dt_region_wr[i].sz, - chip->dt_region_wr[i].vaddr, &chip->dt_region_wr[i].paddr); + chip->dt_region_wr[i].vaddr.io, &chip->dt_region_wr[i].paddr); } for (i = 0; i < chip->ll_rd_cnt; i++) { pci_dbg(pdev, "L. List:\tREAD CH%.2u, BAR=%u, off=0x%.8lx, sz=0x%zx bytes, addr(v=%p, p=%pa)\n", i, vsec_data.ll_rd[i].bar, vsec_data.ll_rd[i].off, chip->ll_region_rd[i].sz, - chip->ll_region_rd[i].vaddr, &chip->ll_region_rd[i].paddr); + chip->ll_region_rd[i].vaddr.io, &chip->ll_region_rd[i].paddr); pci_dbg(pdev, "Data:\tREAD CH%.2u, BAR=%u, off=0x%.8lx, sz=0x%zx bytes, addr(v=%p, p=%pa)\n", i, vsec_data.dt_rd[i].bar, vsec_data.dt_rd[i].off, chip->dt_region_rd[i].sz, - chip->dt_region_rd[i].vaddr, &chip->dt_region_rd[i].paddr); + chip->dt_region_rd[i].vaddr.io, &chip->dt_region_rd[i].paddr); } pci_dbg(pdev, "Nr. IRQs:\t%u\n", chip->nr_irqs); diff --git a/drivers/dma/dw-edma/dw-edma-v0-core.c b/drivers/dma/dw-edma/dw-edma-v0-core.c index 6bcc57512258..72e79a0c0a4e 100644 --- a/drivers/dma/dw-edma/dw-edma-v0-core.c +++ b/drivers/dma/dw-edma/dw-edma-v0-core.c @@ -159,9 +159,6 @@ static inline u32 readl_ch(struct dw_edma *dw, enum dw_edma_dir dir, u16 ch, #define GET_CH_32(dw, dir, ch, name) \ readl_ch(dw, dir, ch, &(__dw_ch_regs(dw, dir, ch)->name)) -#define SET_LL_32(ll, value) \ - writel(value, ll) - static inline void writeq_ch(struct dw_edma *dw, enum dw_edma_dir dir, u16 ch, u64 value, void __iomem *addr) { @@ -218,9 +215,6 @@ static inline u64 readq_ch(struct dw_edma *dw, enum dw_edma_dir dir, u16 ch, #define GET_CH_64(dw, dir, ch, name) \ readq_ch(dw, dir, ch, &(__dw_ch_regs(dw, dir, ch)->name)) -#define SET_LL_64(ll, value) \ - writeq(value, ll) - /* eDMA management callbacks */ void dw_edma_v0_core_off(struct dw_edma *dw) { @@ -292,17 +286,53 @@ u32 dw_edma_v0_core_status_abort_int(struct dw_edma *dw, enum dw_edma_dir dir) GET_RW_32(dw, dir, int_status)); } +static void dw_edma_v0_write_ll_data(struct dw_edma_chunk *chunk, int i, + u32 control, u32 size, u64 sar, u64 dar) +{ + ptrdiff_t ofs = i * sizeof(struct dw_edma_v0_lli); + + if (chunk->chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) { + struct dw_edma_v0_lli *lli = chunk->ll_region.vaddr.mem + ofs; + + lli->control = control; + lli->transfer_size = size; + lli->sar.reg = sar; + lli->dar.reg = dar; + } else { + struct dw_edma_v0_lli __iomem *lli = chunk->ll_region.vaddr.io + ofs; + + writel(control, &lli->control); + writel(size, &lli->transfer_size); + writeq(sar, &lli->sar.reg); + writeq(dar, &lli->dar.reg); + } +} + +static void dw_edma_v0_write_ll_link(struct dw_edma_chunk *chunk, + int i, u32 control, u64 pointer) +{ + ptrdiff_t ofs = i * sizeof(struct dw_edma_v0_lli); + + if (chunk->chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL) { + struct dw_edma_v0_llp *llp = chunk->ll_region.vaddr.mem + ofs; + + llp->control = control; + llp->llp.reg = pointer; + } else { + struct dw_edma_v0_llp __iomem *llp = chunk->ll_region.vaddr.io + ofs; + + writel(control, &llp->control); + writeq(pointer, &llp->llp.reg); + } +} + static void dw_edma_v0_core_write_chunk(struct dw_edma_chunk *chunk) { struct dw_edma_burst *child; struct dw_edma_chan *chan = chunk->chan; - struct dw_edma_v0_lli __iomem *lli; - struct dw_edma_v0_llp __iomem *llp; u32 control = 0, i = 0; int j; - lli = chunk->ll_region.vaddr; - if (chunk->cb) control = DW_EDMA_V0_CB; @@ -314,27 +344,16 @@ static void dw_edma_v0_core_write_chunk(struct dw_edma_chunk *chunk) if (!(chan->dw->chip->flags & DW_EDMA_CHIP_LOCAL)) control |= DW_EDMA_V0_RIE; } - /* Channel control */ - SET_LL_32(&lli[i].control, control); - /* Transfer size */ - SET_LL_32(&lli[i].transfer_size, child->sz); - /* SAR */ - SET_LL_64(&lli[i].sar.reg, child->sar); - /* DAR */ - SET_LL_64(&lli[i].dar.reg, child->dar); - - i++; + + dw_edma_v0_write_ll_data(chunk, i++, control, child->sz, + child->sar, child->dar); } - llp = (void __iomem *)&lli[i]; control = DW_EDMA_V0_LLP | DW_EDMA_V0_TCB; if (!chunk->cb) control |= DW_EDMA_V0_CB; - /* Channel control */ - SET_LL_32(&llp->control, control); - /* Linked list */ - SET_LL_64(&llp->llp.reg, chunk->ll_region.paddr); + dw_edma_v0_write_ll_link(chunk, i, control, chunk->ll_region.paddr); } void dw_edma_v0_core_start(struct dw_edma_chunk *chunk, bool first) diff --git a/include/linux/dma/edma.h b/include/linux/dma/edma.h index 96bfd0173f3a..3b80040b95cc 100644 --- a/include/linux/dma/edma.h +++ b/include/linux/dma/edma.h @@ -19,7 +19,10 @@ struct dw_edma; struct dw_edma_region { u64 paddr; - void __iomem *vaddr; + union { + void *mem; + void __iomem *io; + } vaddr; size_t sz; }; -- cgit v1.2.3 From acf994151dd9a59d544b5d3c196b64f326d94c52 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Wed, 25 Jan 2023 15:24:33 -0600 Subject: dmaengine: dw-edma: Depend on DW_EDMA instead of selecting it Kconfig "select" is discouraged for visible symbols like DW_EDMA because it makes it possible to set DW_EDMA even if DW_EDMA depends on things that are not set (see Documentation/kbuild/kconfig-language.txt). Convert DW_EDMA_PCIE so it depends on DW_EDMA instead of selecting it. There will likely be several future drivers that depend on DW_EDMA, so this uses "if DW_EDMA" to enclose them all rather than repeating "depends on DW_EDMA" for each. [bhelgaas: split to separate patch, commit log] Link: https://lore.kernel.org/r/20230113171409.30470-25-Sergey.Semin@baikalelectronics.ru Signed-off-by: Serge Semin Signed-off-by: Lorenzo Pieralisi Signed-off-by: Bjorn Helgaas Acked-by: Vinod Koul --- drivers/dma/dw-edma/Kconfig | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/dma/dw-edma/Kconfig b/drivers/dma/dw-edma/Kconfig index 7ff17b2db6a1..2b6f2679508d 100644 --- a/drivers/dma/dw-edma/Kconfig +++ b/drivers/dma/dw-edma/Kconfig @@ -9,11 +9,14 @@ config DW_EDMA Support the Synopsys DesignWare eDMA controller, normally implemented on endpoints SoCs. +if DW_EDMA + config DW_EDMA_PCIE tristate "Synopsys DesignWare eDMA PCIe driver" depends on PCI && PCI_MSI - select DW_EDMA help Provides a glue-logic between the Synopsys DesignWare eDMA controller and an endpoint PCIe device. This also serves as a reference design to whom desires to use this IP. + +endif # DW_EDMA -- cgit v1.2.3 From 3bc0f149405e07c7e59985a24ce96db83973f8d7 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 13 Jan 2023 20:14:06 +0300 Subject: dmaengine: dw-edma: Prepare dw_edma_probe() for builtin callers When CONFIG_DW_EDMA=m, dw_edma_probe() is built as a module. Previously edma.h declared it as extern, but the implementation isn't available for builtin callers. A subsequent commit will add calls from dw_pcie_host_init() and dw_pcie_ep_init(), which can only be built-in. Make it safe for such builtin callers to call dw_edma_probe() by using IS_REACHABLE() to define a stub when CONFIG_DW_EDMA=m. When CONFIG_DW_EDMA=m, these builtin callers will fail to detect and register eDMA devices, so eDMA won't be usable even if the dw-edma module is loaded. [bhelgaas: split to separate patch, commit log] Link: https://lore.kernel.org/r/20230113171409.30470-25-Sergey.Semin@baikalelectronics.ru Signed-off-by: Serge Semin Signed-off-by: Lorenzo Pieralisi Signed-off-by: Bjorn Helgaas Acked-by: Vinod Koul --- include/linux/dma/edma.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/dma/edma.h b/include/linux/dma/edma.h index 3b80040b95cc..d2638d9259dc 100644 --- a/include/linux/dma/edma.h +++ b/include/linux/dma/edma.h @@ -101,7 +101,7 @@ struct dw_edma_chip { }; /* Export to the platform drivers */ -#if IS_ENABLED(CONFIG_DW_EDMA) +#if IS_REACHABLE(CONFIG_DW_EDMA) int dw_edma_probe(struct dw_edma_chip *chip); int dw_edma_remove(struct dw_edma_chip *chip); #else -- cgit v1.2.3 From 6c784e21b3da735bd2c3dba73acf9b2c033564fa Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 13 Jan 2023 20:14:07 +0300 Subject: PCI: dwc: Restrict only coherent DMA mask for MSI address allocation The MSI target address must be in the lowest 4GB memory to support PCI peripherals without 64-bit MSI support. Since the allocation is done from DMA coherent memory, set only the coherent DMA mask, leaving the streaming DMA mask alone. Thus streaming DMA operations will work with no artificial limitations. It will be specifically useful for the eDMA-capable controllers so the corresponding DMA engine clients would map the DMA buffers with no need for SWIOTLB for buffers allocated above 4GB. Add a brief comment about the reason allocating the MSI target address below 4GB. Link: https://lore.kernel.org/r/20230113171409.30470-26-Sergey.Semin@baikalelectronics.ru Signed-off-by: Serge Semin Signed-off-by: Lorenzo Pieralisi Signed-off-by: Bjorn Helgaas Reviewed-by: Robin Murphy --- drivers/pci/controller/dwc/pcie-designware-host.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/pci/controller/dwc/pcie-designware-host.c b/drivers/pci/controller/dwc/pcie-designware-host.c index 3ab6ae3712c4..14f27985055e 100644 --- a/drivers/pci/controller/dwc/pcie-designware-host.c +++ b/drivers/pci/controller/dwc/pcie-designware-host.c @@ -366,7 +366,17 @@ static int dw_pcie_msi_host_init(struct dw_pcie_rp *pp) dw_chained_msi_isr, pp); } - ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); + /* + * Even though the iMSI-RX Module supports 64-bit addresses some + * peripheral PCIe devices may lack 64-bit message support. In + * order not to miss MSI TLPs from those devices the MSI target + * address has to be within the lowest 4GB. + * + * Note until there is a better alternative found the reservation is + * done by allocating from the artificially limited DMA-coherent + * memory. + */ + ret = dma_set_coherent_mask(dev, DMA_BIT_MASK(32)); if (ret) dev_warn(dev, "Failed to set DMA mask to 32-bit. Devices with only 32-bit MSI support may not work properly\n"); -- cgit v1.2.3 From 68373f2c0fd82bc66ec51e2db27ca0fe141210fd Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 13 Jan 2023 20:14:08 +0300 Subject: PCI: bt1: Set 64-bit DMA mask The DW PCIe Root Port IP core is synthesized with the 64-bit AXI address bus. Since the device is also equipped with the eDMA engine, explicitly set the device DMA mask so DMA engine clients can allocate data buffers anywhere in the 64-bit memory space. Link: https://lore.kernel.org/r/20230113171409.30470-27-Sergey.Semin@baikalelectronics.ru Signed-off-by: Serge Semin Signed-off-by: Lorenzo Pieralisi Signed-off-by: Bjorn Helgaas --- drivers/pci/controller/dwc/pcie-bt1.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/pci/controller/dwc/pcie-bt1.c b/drivers/pci/controller/dwc/pcie-bt1.c index 3346770e6654..95a723a6fd46 100644 --- a/drivers/pci/controller/dwc/pcie-bt1.c +++ b/drivers/pci/controller/dwc/pcie-bt1.c @@ -583,6 +583,10 @@ static int bt1_pcie_add_port(struct bt1_pcie *btpci) struct device *dev = &btpci->pdev->dev; int ret; + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64)); + if (ret) + return ret; + btpci->dw.version = DW_PCIE_VER_460A; btpci->dw.dev = dev; btpci->dw.ops = &bt1_pcie_ops; -- cgit v1.2.3 From 939fbcd568fd294034c96edc92ff5b9de1a5fce8 Mon Sep 17 00:00:00 2001 From: Serge Semin Date: Fri, 13 Jan 2023 20:14:09 +0300 Subject: PCI: dwc: Add Root Port and Endpoint controller eDMA engine support Since the DW eDMA core now supports eDMA controllers embedded in locally accessible DW PCIe Root Ports and Endpoints, register these controllers when possible. To do that the DW PCIe core driver needs to perform some preparations first. First of all, it needs to find the eDMA controller CSRs base address, whether they are accessible over the Port Logic or iATU unrolled space. Afterwards it can try to auto-detect the eDMA controller availability and number of read/write channels. If none are found the procedure silently returns without error. Secondly, the platform is supposed to provide either combined or per-channel IRQ signals. If no valid IRQs set is found, the procedure returns without error to be backward compatible with platforms where DW PCIe controllers have eDMA but lack the IRQ description. Finally, before actually probing the eDMA device we need to allocate LLP items buffers. After that the DW eDMA can be registered. If registration is successful, a message regarding the number of detected Read/Write eDMA channels will be printed to the system as is done for the iATU settings. Note: the DW PCI controller driver (either host or endpoint mode) is currently always built-in, so if the DW eDMA core is built as a module (CONFIG_DW_EDMA=m), eDMA controllers will not be registered even if the dw-edma module is later loaded. Link: https://lore.kernel.org/r/20230113171409.30470-28-Sergey.Semin@baikalelectronics.ru Signed-off-by: Serge Semin Signed-off-by: Lorenzo Pieralisi Signed-off-by: Bjorn Helgaas Reviewed-by: Manivannan Sadhasivam Acked-by: Vinod Koul --- drivers/pci/controller/dwc/pcie-designware-ep.c | 12 +- drivers/pci/controller/dwc/pcie-designware-host.c | 13 +- drivers/pci/controller/dwc/pcie-designware.c | 195 ++++++++++++++++++++++ drivers/pci/controller/dwc/pcie-designware.h | 21 +++ 4 files changed, 238 insertions(+), 3 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-designware-ep.c b/drivers/pci/controller/dwc/pcie-designware-ep.c index d06654895eba..f9182f8d552f 100644 --- a/drivers/pci/controller/dwc/pcie-designware-ep.c +++ b/drivers/pci/controller/dwc/pcie-designware-ep.c @@ -612,8 +612,11 @@ int dw_pcie_ep_raise_msix_irq(struct dw_pcie_ep *ep, u8 func_no, void dw_pcie_ep_exit(struct dw_pcie_ep *ep) { + struct dw_pcie *pci = to_dw_pcie_from_ep(ep); struct pci_epc *epc = ep->epc; + dw_pcie_edma_remove(pci); + pci_epc_mem_free_addr(epc, ep->msi_mem_phys, ep->msi_mem, epc->mem->window.page_size); @@ -785,6 +788,10 @@ int dw_pcie_ep_init(struct dw_pcie_ep *ep) goto err_exit_epc_mem; } + ret = dw_pcie_edma_detect(pci); + if (ret) + goto err_free_epc_mem; + if (ep->ops->get_features) { epc_features = ep->ops->get_features(ep); if (epc_features->core_init_notifier) @@ -793,10 +800,13 @@ int dw_pcie_ep_init(struct dw_pcie_ep *ep) ret = dw_pcie_ep_init_complete(ep); if (ret) - goto err_free_epc_mem; + goto err_remove_edma; return 0; +err_remove_edma: + dw_pcie_edma_remove(pci); + err_free_epc_mem: pci_epc_mem_free_addr(epc, ep->msi_mem_phys, ep->msi_mem, epc->mem->window.page_size); diff --git a/drivers/pci/controller/dwc/pcie-designware-host.c b/drivers/pci/controller/dwc/pcie-designware-host.c index 14f27985055e..9952057c8819 100644 --- a/drivers/pci/controller/dwc/pcie-designware-host.c +++ b/drivers/pci/controller/dwc/pcie-designware-host.c @@ -477,14 +477,18 @@ int dw_pcie_host_init(struct dw_pcie_rp *pp) dw_pcie_iatu_detect(pci); - ret = dw_pcie_setup_rc(pp); + ret = dw_pcie_edma_detect(pci); if (ret) goto err_free_msi; + ret = dw_pcie_setup_rc(pp); + if (ret) + goto err_remove_edma; + if (!dw_pcie_link_up(pci)) { ret = dw_pcie_start_link(pci); if (ret) - goto err_free_msi; + goto err_remove_edma; } /* Ignore errors, the link may come up later */ @@ -501,6 +505,9 @@ int dw_pcie_host_init(struct dw_pcie_rp *pp) err_stop_link: dw_pcie_stop_link(pci); +err_remove_edma: + dw_pcie_edma_remove(pci); + err_free_msi: if (pp->has_msi_ctrl) dw_pcie_free_msi(pp); @@ -522,6 +529,8 @@ void dw_pcie_host_deinit(struct dw_pcie_rp *pp) dw_pcie_stop_link(pci); + dw_pcie_edma_remove(pci); + if (pp->has_msi_ctrl) dw_pcie_free_msi(pp); diff --git a/drivers/pci/controller/dwc/pcie-designware.c b/drivers/pci/controller/dwc/pcie-designware.c index 6d5d619ab2e9..53a16b8b6ac2 100644 --- a/drivers/pci/controller/dwc/pcie-designware.c +++ b/drivers/pci/controller/dwc/pcie-designware.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -142,6 +143,18 @@ int dw_pcie_get_resources(struct dw_pcie *pci) if (!pci->atu_size) pci->atu_size = SZ_4K; + /* eDMA region can be mapped to a custom base address */ + if (!pci->edma.reg_base) { + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dma"); + if (res) { + pci->edma.reg_base = devm_ioremap_resource(pci->dev, res); + if (IS_ERR(pci->edma.reg_base)) + return PTR_ERR(pci->edma.reg_base); + } else if (pci->atu_size >= 2 * DEFAULT_DBI_DMA_OFFSET) { + pci->edma.reg_base = pci->atu_base + DEFAULT_DBI_DMA_OFFSET; + } + } + /* LLDD is supposed to manually switch the clocks and resets state */ if (dw_pcie_cap_is(pci, REQ_RES)) { ret = dw_pcie_get_clocks(pci); @@ -782,6 +795,188 @@ void dw_pcie_iatu_detect(struct dw_pcie *pci) pci->region_align / SZ_1K, (pci->region_limit + 1) / SZ_1G); } +static u32 dw_pcie_readl_dma(struct dw_pcie *pci, u32 reg) +{ + u32 val = 0; + int ret; + + if (pci->ops && pci->ops->read_dbi) + return pci->ops->read_dbi(pci, pci->edma.reg_base, reg, 4); + + ret = dw_pcie_read(pci->edma.reg_base + reg, 4, &val); + if (ret) + dev_err(pci->dev, "Read DMA address failed\n"); + + return val; +} + +static int dw_pcie_edma_irq_vector(struct device *dev, unsigned int nr) +{ + struct platform_device *pdev = to_platform_device(dev); + char name[6]; + int ret; + + if (nr >= EDMA_MAX_WR_CH + EDMA_MAX_RD_CH) + return -EINVAL; + + ret = platform_get_irq_byname_optional(pdev, "dma"); + if (ret > 0) + return ret; + + snprintf(name, sizeof(name), "dma%u", nr); + + return platform_get_irq_byname_optional(pdev, name); +} + +static struct dw_edma_core_ops dw_pcie_edma_ops = { + .irq_vector = dw_pcie_edma_irq_vector, +}; + +static int dw_pcie_edma_find_chip(struct dw_pcie *pci) +{ + u32 val; + + /* + * Indirect eDMA CSRs access has been completely removed since v5.40a + * thus no space is now reserved for the eDMA channels viewport and + * former DMA CTRL register is no longer fixed to FFs. + */ + if (dw_pcie_ver_is_ge(pci, 540A)) + val = 0xFFFFFFFF; + else + val = dw_pcie_readl_dbi(pci, PCIE_DMA_VIEWPORT_BASE + PCIE_DMA_CTRL); + + if (val == 0xFFFFFFFF && pci->edma.reg_base) { + pci->edma.mf = EDMA_MF_EDMA_UNROLL; + + val = dw_pcie_readl_dma(pci, PCIE_DMA_CTRL); + } else if (val != 0xFFFFFFFF) { + pci->edma.mf = EDMA_MF_EDMA_LEGACY; + + pci->edma.reg_base = pci->dbi_base + PCIE_DMA_VIEWPORT_BASE; + } else { + return -ENODEV; + } + + pci->edma.dev = pci->dev; + + if (!pci->edma.ops) + pci->edma.ops = &dw_pcie_edma_ops; + + pci->edma.flags |= DW_EDMA_CHIP_LOCAL; + + pci->edma.ll_wr_cnt = FIELD_GET(PCIE_DMA_NUM_WR_CHAN, val); + pci->edma.ll_rd_cnt = FIELD_GET(PCIE_DMA_NUM_RD_CHAN, val); + + /* Sanity check the channels count if the mapping was incorrect */ + if (!pci->edma.ll_wr_cnt || pci->edma.ll_wr_cnt > EDMA_MAX_WR_CH || + !pci->edma.ll_rd_cnt || pci->edma.ll_rd_cnt > EDMA_MAX_RD_CH) + return -EINVAL; + + return 0; +} + +static int dw_pcie_edma_irq_verify(struct dw_pcie *pci) +{ + struct platform_device *pdev = to_platform_device(pci->dev); + u16 ch_cnt = pci->edma.ll_wr_cnt + pci->edma.ll_rd_cnt; + char name[6]; + int ret; + + if (pci->edma.nr_irqs == 1) + return 0; + else if (pci->edma.nr_irqs > 1) + return pci->edma.nr_irqs != ch_cnt ? -EINVAL : 0; + + ret = platform_get_irq_byname_optional(pdev, "dma"); + if (ret > 0) { + pci->edma.nr_irqs = 1; + return 0; + } + + for (; pci->edma.nr_irqs < ch_cnt; pci->edma.nr_irqs++) { + snprintf(name, sizeof(name), "dma%d", pci->edma.nr_irqs); + + ret = platform_get_irq_byname_optional(pdev, name); + if (ret <= 0) + return -EINVAL; + } + + return 0; +} + +static int dw_pcie_edma_ll_alloc(struct dw_pcie *pci) +{ + struct dw_edma_region *ll; + dma_addr_t paddr; + int i; + + for (i = 0; i < pci->edma.ll_wr_cnt; i++) { + ll = &pci->edma.ll_region_wr[i]; + ll->sz = DMA_LLP_MEM_SIZE; + ll->vaddr.mem = dmam_alloc_coherent(pci->dev, ll->sz, + &paddr, GFP_KERNEL); + if (!ll->vaddr.mem) + return -ENOMEM; + + ll->paddr = paddr; + } + + for (i = 0; i < pci->edma.ll_rd_cnt; i++) { + ll = &pci->edma.ll_region_rd[i]; + ll->sz = DMA_LLP_MEM_SIZE; + ll->vaddr.mem = dmam_alloc_coherent(pci->dev, ll->sz, + &paddr, GFP_KERNEL); + if (!ll->vaddr.mem) + return -ENOMEM; + + ll->paddr = paddr; + } + + return 0; +} + +int dw_pcie_edma_detect(struct dw_pcie *pci) +{ + int ret; + + /* Don't fail if no eDMA was found (for the backward compatibility) */ + ret = dw_pcie_edma_find_chip(pci); + if (ret) + return 0; + + /* Don't fail on the IRQs verification (for the backward compatibility) */ + ret = dw_pcie_edma_irq_verify(pci); + if (ret) { + dev_err(pci->dev, "Invalid eDMA IRQs found\n"); + return 0; + } + + ret = dw_pcie_edma_ll_alloc(pci); + if (ret) { + dev_err(pci->dev, "Couldn't allocate LLP memory\n"); + return ret; + } + + /* Don't fail if the DW eDMA driver can't find the device */ + ret = dw_edma_probe(&pci->edma); + if (ret && ret != -ENODEV) { + dev_err(pci->dev, "Couldn't register eDMA device\n"); + return ret; + } + + dev_info(pci->dev, "eDMA: unroll %s, %hu wr, %hu rd\n", + pci->edma.mf == EDMA_MF_EDMA_UNROLL ? "T" : "F", + pci->edma.ll_wr_cnt, pci->edma.ll_rd_cnt); + + return 0; +} + +void dw_pcie_edma_remove(struct dw_pcie *pci) +{ + dw_edma_remove(&pci->edma); +} + void dw_pcie_setup(struct dw_pcie *pci) { u32 val; diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h index 393dfb931df6..79713ce075cc 100644 --- a/drivers/pci/controller/dwc/pcie-designware.h +++ b/drivers/pci/controller/dwc/pcie-designware.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -31,6 +32,7 @@ #define DW_PCIE_VER_480A 0x3438302a #define DW_PCIE_VER_490A 0x3439302a #define DW_PCIE_VER_520A 0x3532302a +#define DW_PCIE_VER_540A 0x3534302a #define __dw_pcie_ver_cmp(_pci, _ver, _op) \ ((_pci)->version _op DW_PCIE_VER_ ## _ver) @@ -167,6 +169,18 @@ #define PCIE_MSIX_DOORBELL 0x948 #define PCIE_MSIX_DOORBELL_PF_SHIFT 24 +/* + * eDMA CSRs. DW PCIe IP-core v4.70a and older had the eDMA registers accessible + * over the Port Logic registers space. Afterwards the unrolled mapping was + * introduced so eDMA and iATU could be accessed via a dedicated registers + * space. + */ +#define PCIE_DMA_VIEWPORT_BASE 0x970 +#define PCIE_DMA_UNROLL_BASE 0x80000 +#define PCIE_DMA_CTRL 0x008 +#define PCIE_DMA_NUM_WR_CHAN GENMASK(3, 0) +#define PCIE_DMA_NUM_RD_CHAN GENMASK(19, 16) + #define PCIE_PL_CHK_REG_CONTROL_STATUS 0xB20 #define PCIE_PL_CHK_REG_CHK_REG_START BIT(0) #define PCIE_PL_CHK_REG_CHK_REG_CONTINUOUS BIT(1) @@ -215,6 +229,7 @@ * this offset, if atu_base not set. */ #define DEFAULT_DBI_ATU_OFFSET (0x3 << 20) +#define DEFAULT_DBI_DMA_OFFSET PCIE_DMA_UNROLL_BASE #define MAX_MSI_IRQS 256 #define MAX_MSI_IRQS_PER_CTRL 32 @@ -226,6 +241,9 @@ #define MAX_IATU_IN 256 #define MAX_IATU_OUT 256 +/* Default eDMA LLP memory size */ +#define DMA_LLP_MEM_SIZE PAGE_SIZE + struct dw_pcie; struct dw_pcie_rp; struct dw_pcie_ep; @@ -369,6 +387,7 @@ struct dw_pcie { int num_lanes; int link_gen; u8 n_fts[2]; + struct dw_edma_chip edma; struct clk_bulk_data app_clks[DW_PCIE_NUM_APP_CLKS]; struct clk_bulk_data core_clks[DW_PCIE_NUM_CORE_CLKS]; struct reset_control_bulk_data app_rsts[DW_PCIE_NUM_APP_RSTS]; @@ -408,6 +427,8 @@ int dw_pcie_prog_ep_inbound_atu(struct dw_pcie *pci, u8 func_no, int index, void dw_pcie_disable_atu(struct dw_pcie *pci, u32 dir, int index); void dw_pcie_setup(struct dw_pcie *pci); void dw_pcie_iatu_detect(struct dw_pcie *pci); +int dw_pcie_edma_detect(struct dw_pcie *pci); +void dw_pcie_edma_remove(struct dw_pcie *pci); static inline void dw_pcie_writel_dbi(struct dw_pcie *pci, u32 reg, u32 val) { -- cgit v1.2.3