Merge branch 'pci/controller/root-port-reset'

* pci/controller/root-port-reset:
  misc: pci_endpoint_test: Add AER error handlers
  PCI: dw-rockchip: Implement .reset_root_port() and use for link down
  PCI: qcom: Implement .reset_root_port() and use for link down
  PCI: host-common: Add link down handling for Root Ports
  PCI/ERR: Add support for resetting the Root Ports in a platform-specific way
  PCI: dwc: ep: Clear MSI iATU mapping in dw_pcie_ep_cleanup()
This commit is contained in:
Bjorn Helgaas
2026-08-21 16:40:40 -05:00
10 changed files with 351 additions and 9 deletions

View File

@@ -1326,6 +1326,8 @@ static int pci_endpoint_test_probe(struct pci_dev *pdev,
misc_device->parent = &pdev->dev;
misc_device->fops = &pci_endpoint_test_fops;
pci_save_state(pdev);
ret = misc_register(misc_device);
if (ret) {
dev_err(dev, "Failed to register device\n");
@@ -1453,12 +1455,33 @@ static const struct pci_device_id pci_endpoint_test_tbl[] = {
};
MODULE_DEVICE_TABLE(pci, pci_endpoint_test_tbl);
static pci_ers_result_t pci_endpoint_test_error_detected(struct pci_dev *pdev,
pci_channel_state_t state)
{
if (state == pci_channel_io_perm_failure)
return PCI_ERS_RESULT_DISCONNECT;
return PCI_ERS_RESULT_NEED_RESET;
}
static pci_ers_result_t pci_endpoint_test_slot_reset(struct pci_dev *pdev)
{
pci_restore_state(pdev);
return PCI_ERS_RESULT_RECOVERED;
}
static const struct pci_error_handlers pci_endpoint_test_err_handler = {
.error_detected = pci_endpoint_test_error_detected,
.slot_reset = pci_endpoint_test_slot_reset,
};
static struct pci_driver pci_endpoint_test_driver = {
.name = DRV_MODULE_NAME,
.id_table = pci_endpoint_test_tbl,
.probe = pci_endpoint_test_probe,
.remove = pci_endpoint_test_remove,
.sriov_configure = pci_sriov_configure_simple,
.err_handler = &pci_endpoint_test_err_handler,
};
module_pci_driver(pci_endpoint_test_driver);

View File

@@ -374,6 +374,7 @@ config PCIE_ROCKCHIP_DW_HOST
depends on OF
select PCIE_DW_HOST
select PCIE_ROCKCHIP_DW
select PCI_HOST_COMMON
help
Enables support for the DesignWare PCIe controller in the
Rockchip SoC (except RK3399) to work in host mode.

View File

@@ -1153,6 +1153,11 @@ void dw_pcie_ep_cleanup(struct dw_pcie_ep *ep)
{
struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
if (ep->msi_iatu_mapped) {
dw_pcie_ep_unmap_addr(ep->epc, 0, 0, ep->msi_mem_phys);
ep->msi_iatu_mapped = false;
}
dwc_pcie_debugfs_deinit(pci);
dw_pcie_edma_remove(pci);
}

View File

@@ -26,6 +26,7 @@
#include <trace/events/pci_controller.h>
#include "../../pci.h"
#include "../pci-host-common.h"
#include "pcie-designware.h"
/*
@@ -122,6 +123,9 @@ struct rockchip_pcie_of_data {
const struct pci_epc_features *epc_features;
};
static int rockchip_pcie_rc_reset_root_port(struct pci_host_bridge *bridge,
struct pci_dev *pdev);
static int rockchip_pcie_readl_apb(struct rockchip_pcie *rockchip, u32 reg)
{
return readl_relaxed(rockchip->apb_base + reg);
@@ -436,6 +440,7 @@ static int rockchip_pcie_host_init(struct dw_pcie_rp *pp)
rockchip_pcie_configure_l1ss(pci);
rockchip_pcie_enable_l0s(pci);
pp->bridge->reset_root_port = rockchip_pcie_rc_reset_root_port;
/* Disable Root Ports BAR0 and BAR1 as they report bogus size */
dw_pcie_writel_dbi2(pci, PCI_BASE_ADDRESS_0, 0x0);
@@ -634,6 +639,32 @@ static const struct dw_pcie_ops dw_pcie_ops = {
.get_ltssm = rockchip_pcie_get_ltssm,
};
static irqreturn_t rockchip_pcie_rc_sys_irq_thread(int irq, void *arg)
{
struct rockchip_pcie *rockchip = arg;
struct dw_pcie *pci = &rockchip->pci;
struct dw_pcie_rp *pp = &pci->pp;
struct device *dev = pci->dev;
struct pci_dev *port;
u32 reg;
reg = rockchip_pcie_readl_apb(rockchip, PCIE_CLIENT_INTR_STATUS_MISC);
rockchip_pcie_writel_apb(rockchip, reg, PCIE_CLIENT_INTR_STATUS_MISC);
dev_dbg(dev, "PCIE_CLIENT_INTR_STATUS_MISC: %#x\n", reg);
dev_dbg(dev, "LTSSM_STATUS: %#x\n", rockchip_pcie_get_ltssm_reg(rockchip));
if (reg & PCIE_LINK_REQ_RST_NOT_INT) {
dev_dbg(dev, "hot reset or link-down reset\n");
for_each_pci_bridge(port, pp->bridge->bus) {
if (pci_pcie_type(port) == PCI_EXP_TYPE_ROOT_PORT)
pci_host_handle_link_down(port);
}
}
return IRQ_HANDLED;
}
static irqreturn_t rockchip_pcie_ep_sys_irq_thread(int irq, void *arg)
{
struct rockchip_pcie *rockchip = arg;
@@ -666,14 +697,29 @@ static irqreturn_t rockchip_pcie_ep_sys_irq_thread(int irq, void *arg)
return IRQ_HANDLED;
}
static int rockchip_pcie_configure_rc(struct rockchip_pcie *rockchip)
static int rockchip_pcie_configure_rc(struct platform_device *pdev,
struct rockchip_pcie *rockchip)
{
struct device *dev = &pdev->dev;
struct dw_pcie_rp *pp;
int irq, ret;
u32 val;
if (!IS_ENABLED(CONFIG_PCIE_ROCKCHIP_DW_HOST))
return -ENODEV;
irq = platform_get_irq_byname(pdev, "sys");
if (irq < 0)
return irq;
ret = devm_request_threaded_irq(dev, irq, NULL,
rockchip_pcie_rc_sys_irq_thread,
IRQF_ONESHOT, "pcie-sys-rc", rockchip);
if (ret) {
dev_err(dev, "failed to request PCIe sys IRQ\n");
return ret;
}
/* LTSSM enable control mode */
val = FIELD_PREP_WM16(PCIE_LTSSM_ENABLE_ENHANCE, 1);
rockchip_pcie_writel_apb(rockchip, val, PCIE_CLIENT_HOT_RESET_CTRL);
@@ -685,7 +731,17 @@ static int rockchip_pcie_configure_rc(struct rockchip_pcie *rockchip)
pp = &rockchip->pci.pp;
pp->ops = &rockchip_pcie_host_ops;
return dw_pcie_host_init(pp);
ret = dw_pcie_host_init(pp);
if (ret) {
dev_err(dev, "failed to initialize host\n");
return ret;
}
/* unmask hot reset/link-down reset */
val = FIELD_PREP_WM16(PCIE_LINK_REQ_RST_NOT_INT, 0);
rockchip_pcie_writel_apb(rockchip, val, PCIE_CLIENT_INTR_MASK_MISC);
return ret;
}
static int rockchip_pcie_configure_ep(struct platform_device *pdev,
@@ -804,7 +860,7 @@ static int rockchip_pcie_probe(struct platform_device *pdev)
switch (data->mode) {
case DW_PCIE_RC_TYPE:
ret = rockchip_pcie_configure_rc(rockchip);
ret = rockchip_pcie_configure_rc(pdev, rockchip);
if (ret)
goto deinit_clk;
break;
@@ -829,6 +885,78 @@ static int rockchip_pcie_probe(struct platform_device *pdev)
return ret;
}
static int rockchip_pcie_rc_reset_root_port(struct pci_host_bridge *bridge,
struct pci_dev *pdev)
{
struct pci_bus *bus = bridge->bus;
struct dw_pcie_rp *pp = bus->sysdata;
struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
struct rockchip_pcie *rockchip = to_rockchip_pcie(pci);
struct device *dev = rockchip->pci.dev;
u32 val;
int ret;
dw_pcie_stop_link(pci);
clk_bulk_disable_unprepare(rockchip->clk_cnt, rockchip->clks);
rockchip_pcie_phy_deinit(rockchip);
ret = reset_control_assert(rockchip->rst);
if (ret)
return ret;
ret = rockchip_pcie_phy_init(rockchip);
if (ret)
return ret;
ret = reset_control_deassert(rockchip->rst);
if (ret)
goto deinit_phy;
ret = rockchip_pcie_clk_init(rockchip);
if (ret)
goto deinit_phy;
ret = pp->ops->init(pp);
if (ret) {
dev_err(dev, "Host init failed: %d\n", ret);
goto deinit_clk;
}
/* LTSSM enable control mode */
val = FIELD_PREP_WM16(PCIE_LTSSM_ENABLE_ENHANCE, 1);
rockchip_pcie_writel_apb(rockchip, val, PCIE_CLIENT_HOT_RESET_CTRL);
rockchip_pcie_writel_apb(rockchip,
PCIE_CLIENT_SET_MODE(PCIE_CLIENT_MODE_RC),
PCIE_CLIENT_GENERAL_CON);
ret = dw_pcie_setup_rc(pp);
if (ret) {
dev_err(dev, "Failed to setup RC: %d\n", ret);
goto deinit_clk;
}
/* unmask hot reset/link-down reset */
val = FIELD_PREP_WM16(PCIE_LINK_REQ_RST_NOT_INT, 0);
rockchip_pcie_writel_apb(rockchip, val, PCIE_CLIENT_INTR_MASK_MISC);
ret = dw_pcie_start_link(pci);
if (ret)
goto deinit_clk;
/* Ignore errors, the link may come up later */
dw_pcie_wait_for_link(pci);
dev_dbg(dev, "Root Port reset completed\n");
return ret;
deinit_clk:
clk_bulk_disable_unprepare(rockchip->clk_cnt, rockchip->clks);
deinit_phy:
rockchip_pcie_phy_deinit(rockchip);
return ret;
}
static const struct rockchip_pcie_of_data rockchip_pcie_rc_of_data_rk3568 = {
.mode = DW_PCIE_RC_TYPE,
};

View File

@@ -56,6 +56,10 @@
#define PARF_AXI_MSTR_WR_ADDR_HALT_V2 0x1a8
#define PARF_Q2A_FLUSH 0x1ac
#define PARF_LTSSM 0x1b0
#define PARF_INT_ALL_STATUS 0x224
#define PARF_INT_ALL_CLEAR 0x228
#define PARF_INT_ALL_MASK 0x22c
#define PARF_STATUS 0x230
#define PARF_SID_OFFSET 0x234
#define PARF_BDF_TRANSLATE_CFG 0x24c
#define PARF_DBI_BASE_ADDR_V2 0x350
@@ -133,6 +137,13 @@
/* PARF_LTSSM register fields */
#define LTSSM_EN BIT(8)
#define PARF_LTSSM_STATE_MASK GENMASK(5, 0)
#define SW_CLEAR_FLUSH_MODE BIT(10)
#define FLUSH_MODE BIT(11)
/* PARF_INT_ALL_{STATUS/CLEAR/MASK} register fields */
#define INT_ALL_LINK_DOWN 1
#define PARF_INT_ALL_LINK_DOWN BIT(INT_ALL_LINK_DOWN)
#define PARF_INT_MSI_DEV_0_7 GENMASK(30, 23)
/* PARF_NO_SNOOP_OVERRIDE register fields */
#define WR_NO_SNOOP_OVERRIDE_EN BIT(1)
@@ -144,6 +155,9 @@
/* PARF_BDF_TO_SID_CFG fields */
#define BDF_TO_SID_BYPASS BIT(0)
/* PARF_STATUS fields */
#define FLUSH_COMPLETED BIT(8)
/* ELBI_SYS_CTRL register fields */
#define ELBI_SYS_CTRL_LT_ENABLE BIT(0)
#define ELBI_SYS_CTRL_PME_TURNOFF_MSG BIT(4)
@@ -172,6 +186,7 @@
PCIE_CAP_SLOT_POWER_LIMIT_SCALE)
#define PERST_DELAY_US 1000
#define FLUSH_TIMEOUT_US 100
#define QCOM_PCIE_CRC8_POLYNOMIAL (BIT(2) | BIT(1) | BIT(0))
@@ -291,10 +306,13 @@ struct qcom_pcie {
struct dentry *debugfs;
struct list_head ports;
struct gpio_desc *reset;
int global_irq;
bool use_pm_opp;
};
#define to_qcom_pcie(x) dev_get_drvdata((x)->dev)
static int qcom_pcie_reset_root_port(struct pci_host_bridge *bridge,
struct pci_dev *pdev);
static void __qcom_pcie_perst_assert(struct qcom_pcie *pcie, bool assert)
{
@@ -1406,6 +1424,8 @@ static int qcom_pcie_host_init(struct dw_pcie_rp *pp)
goto err_assert_reset;
}
pp->bridge->reset_root_port = qcom_pcie_reset_root_port;
return 0;
err_assert_reset:
@@ -1734,6 +1754,75 @@ static int qcom_pcie_set_max_opp(struct device *dev)
return ret;
}
/*
* Qcom PCIe controllers only support one Root Port per controller instance. So
* this function ignores the 'pci_dev' associated with the Root Port and just
* resets the host bridge, which in turn resets the Root Port also.
*/
static int qcom_pcie_reset_root_port(struct pci_host_bridge *bridge,
struct pci_dev *pdev)
{
struct device *dev = bridge->dev.parent;
struct qcom_pcie *pcie = dev_get_drvdata(dev);
struct dw_pcie *pci = pcie->pci;
struct dw_pcie_rp *pp = &pci->pp;
u32 val;
int ret;
/* Wait for the pending transactions to be completed */
ret = readl_relaxed_poll_timeout(pcie->parf + PARF_STATUS, val,
val & FLUSH_COMPLETED, 10,
FLUSH_TIMEOUT_US);
if (ret) {
dev_err(dev, "Flush completion failed: %d\n", ret);
return ret;
}
/* Clear the FLUSH_MODE to allow the core to be reset */
val = readl(pcie->parf + PARF_LTSSM);
val |= SW_CLEAR_FLUSH_MODE;
writel(val, pcie->parf + PARF_LTSSM);
/* Wait for the FLUSH_MODE to clear */
ret = readl_relaxed_poll_timeout(pcie->parf + PARF_LTSSM, val,
!(val & FLUSH_MODE), 10,
FLUSH_TIMEOUT_US);
if (ret) {
dev_err(dev, "Flush mode clear failed: %d\n", ret);
return ret;
}
qcom_pcie_host_deinit(pp);
ret = qcom_pcie_host_init(pp);
if (ret) {
dev_err(dev, "Host init failed\n");
return ret;
}
ret = dw_pcie_setup_rc(pp);
if (ret)
return ret;
/*
* Re-enable global IRQ events as the PARF_INT_ALL_MASK register is
* non-sticky.
*/
if (pcie->global_irq)
writel_relaxed(PARF_INT_ALL_LINK_DOWN | PARF_INT_MSI_DEV_0_7,
pcie->parf + PARF_INT_ALL_MASK);
qcom_pcie_start_link(pci);
ret = dw_pcie_wait_for_link(pci);
if (ret)
return ret;
dev_dbg(dev, "Root Port reset completed\n");
return 0;
}
static int qcom_pcie_link_transition_count(struct seq_file *s, void *data)
{
struct qcom_pcie *pcie = (struct qcom_pcie *)dev_get_drvdata(s->private);
@@ -1771,6 +1860,27 @@ static void qcom_pcie_init_debugfs(struct qcom_pcie *pcie)
qcom_pcie_link_transition_count);
}
static irqreturn_t qcom_pcie_global_irq_thread(int irq, void *data)
{
struct qcom_pcie *pcie = data;
struct dw_pcie_rp *pp = &pcie->pci->pp;
struct device *dev = pcie->pci->dev;
struct pci_dev *port;
unsigned long status = readl_relaxed(pcie->parf + PARF_INT_ALL_STATUS);
writel_relaxed(status, pcie->parf + PARF_INT_ALL_CLEAR);
if (test_and_clear_bit(INT_ALL_LINK_DOWN, &status)) {
dev_dbg(dev, "Received Link down event\n");
for_each_pci_bridge(port, pp->bridge->bus) {
if (pci_pcie_type(port) == PCI_EXP_TYPE_ROOT_PORT)
pci_host_handle_link_down(port);
}
}
return IRQ_HANDLED;
}
static void qcom_pci_free_msi(void *ptr)
{
struct dw_pcie_rp *pp = (struct dw_pcie_rp *)ptr;
@@ -1990,7 +2100,7 @@ static int qcom_pcie_probe(struct platform_device *pdev)
struct dw_pcie_rp *pp;
struct resource *res;
struct dw_pcie *pci;
int ret;
int ret, irq;
pcie_cfg = of_device_get_match_data(dev);
if (!pcie_cfg) {
@@ -2135,6 +2245,32 @@ static int qcom_pcie_probe(struct platform_device *pdev)
goto err_phy_exit;
}
irq = platform_get_irq_byname_optional(pdev, "global");
if (irq > 0) {
const char *name;
name = devm_kasprintf(dev, GFP_KERNEL, "qcom_pcie_global_irq%d",
pci_domain_nr(pp->bridge->bus));
if (!name) {
ret = -ENOMEM;
goto err_host_deinit;
}
ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
qcom_pcie_global_irq_thread,
IRQF_ONESHOT, name, pcie);
if (ret) {
dev_err_probe(&pdev->dev, ret,
"Failed to request Global IRQ\n");
goto err_host_deinit;
}
writel_relaxed(PARF_INT_ALL_LINK_DOWN | PARF_INT_MSI_DEV_0_7,
pcie->parf + PARF_INT_ALL_MASK);
pcie->global_irq = irq;
}
qcom_pcie_icc_opp_update(pcie);
if (pcie->mhi)
@@ -2142,6 +2278,8 @@ static int qcom_pcie_probe(struct platform_device *pdev)
return 0;
err_host_deinit:
dw_pcie_host_deinit(pp);
err_phy_exit:
list_for_each_entry_safe(port, tmp_port, &pcie->ports, list) {
list_for_each_entry_safe(perst, tmp_perst, &port->perst, list)

View File

@@ -13,9 +13,11 @@
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_pci.h>
#include <linux/pci.h>
#include <linux/pci-ecam.h>
#include <linux/platform_device.h>
#include "../pci.h"
#include "pci-host-common.h"
/**
@@ -342,5 +344,38 @@ bool pci_host_common_d3cold_possible(struct pci_host_bridge *bridge,
}
EXPORT_SYMBOL_GPL(pci_host_common_d3cold_possible);
static pci_ers_result_t pci_host_reset_root_port(struct pci_dev *dev)
{
int ret;
pci_lock_rescan_remove();
ret = pci_bus_error_reset(dev);
pci_unlock_rescan_remove();
if (ret) {
pci_err(dev, "Failed to reset Root Port: %d\n", ret);
return PCI_ERS_RESULT_DISCONNECT;
}
pci_info(dev, "Root Port has been reset\n");
return PCI_ERS_RESULT_RECOVERED;
}
static void pci_host_recover_root_port(struct pci_dev *port)
{
#if IS_ENABLED(CONFIG_PCIEAER)
pcie_do_recovery(port, pci_channel_io_frozen, pci_host_reset_root_port);
#else
pci_host_reset_root_port(port);
#endif
}
void pci_host_handle_link_down(struct pci_dev *port)
{
pci_info(port, "Recovering Root Port due to Link Down\n");
pci_host_recover_root_port(port);
}
EXPORT_SYMBOL_GPL(pci_host_handle_link_down);
MODULE_DESCRIPTION("Common library for PCI host controller drivers");
MODULE_LICENSE("GPL v2");

View File

@@ -48,6 +48,7 @@ int pci_host_common_init(struct platform_device *pdev,
struct pci_host_bridge *bridge,
const struct pci_ecam_ops *ops);
void pci_host_common_remove(struct platform_device *pdev);
void pci_host_handle_link_down(struct pci_dev *port);
struct pci_config_window *pci_host_common_ecam_create(struct device *dev,
struct pci_host_bridge *bridge, const struct pci_ecam_ops *ops);

View File

@@ -4852,6 +4852,19 @@ void pci_reset_secondary_bus(struct pci_dev *dev)
void __weak pcibios_reset_secondary_bus(struct pci_dev *dev)
{
struct pci_host_bridge *host = pci_find_host_bridge(dev->bus);
int ret;
if (pci_is_root_bus(dev->bus) && host->reset_root_port) {
ret = host->reset_root_port(host, dev);
if (ret)
pci_err(dev, "Failed to reset Root Port: %d\n", ret);
else
pci_restore_state(dev);
return;
}
pci_reset_secondary_bus(dev);
}
@@ -5698,6 +5711,7 @@ int pci_bus_error_reset(struct pci_dev *bridge)
{
return pci_reset_bridge(bridge, PCI_RESET_NO_RESTORE);
}
EXPORT_SYMBOL_GPL(pci_bus_error_reset);
int pci_try_reset_bridge(struct pci_dev *bridge)
{

View File

@@ -256,11 +256,6 @@ pci_ers_result_t pcie_do_recovery(struct pci_dev *dev,
}
if (status == PCI_ERS_RESULT_NEED_RESET) {
/*
* TODO: Should call platform-specific
* functions to reset slot before calling
* drivers' slot_reset callbacks?
*/
status = PCI_ERS_RESULT_RECOVERED;
pci_dbg(bridge, "broadcast slot_reset message\n");
pci_walk_bridge(bridge, report_slot_reset, &status);
@@ -297,3 +292,4 @@ pci_ers_result_t pcie_do_recovery(struct pci_dev *dev,
return status;
}
EXPORT_SYMBOL_GPL(pcie_do_recovery);

View File

@@ -648,6 +648,7 @@ struct pci_host_bridge {
void (*release_fn)(struct pci_host_bridge *);
int (*enable_device)(struct pci_host_bridge *bridge, struct pci_dev *dev);
void (*disable_device)(struct pci_host_bridge *bridge, struct pci_dev *dev);
int (*reset_root_port)(struct pci_host_bridge *bridge, struct pci_dev *dev);
void *release_data;
unsigned int ignore_reset_delay:1; /* For entire hierarchy */
unsigned int no_ext_tags:1; /* No Extended Tags */