Skip to content

[RK3588] rk-pcie hangs forever in dw_pcie_read on the boot following a warm reboot — rk_pcie_shutdown() never asserts the controller reset #382

Description

@mjaafa

Repo: rockchip-linux/kernel (branch develop-6.6)
Labels: bug, PCIe, rk3588


Environment

SoC / board RK3588 — Radxa ROCK 5B+
Kernel 6.6.89-rockchip-standard (develop-6.6, SRCREV 1ba51b059f25)
Driver drivers/pci/controller/dwc/pcie-dw-rockchip.c
PCIe devices NVMe (fe150000, Gen3 x2), DEEPX DX-M1 accelerator (fe160000, Gen3 x2), RTL8852BE (fe170000), RTL8125 (fe190000)
PHY pcie30phy bifurcated x2+x2, data-lanes = <1 1 2 2>

Summary

After a warm reboot (reboot / systemctl reboot), the next boot intermittently hangs: the async rk-pcie kthread stalls forever inside reset_control_deassert()dw_pcie_read() and the board never reaches userspace. A cold power-cycle always boots cleanly.

The stalled read never faults or times out — it hangs indefinitely, so the RCU stall detector fires but the machine cannot recover. rockchip_panic_notify() then hangs on the same dead read, so pstore never flushes and there is no crash record.

Steps to reproduce

  1. Boot the board normally (cold power-on) — PCIe enumerates fine.
  2. reboot (warm reboot, no power cycle).
  3. Repeat. Within a handful of cycles the board fails to come up.
  4. Cold power-cycle → boots clean again.

Actual behaviour

Boot stops after the PCIe probe begins; the last useful console output is the RCU stall:

rcu: INFO: rcu_sched detected stalls on CPUs/tasks:
rcu:     Tasks blocked on level-0 rcu_node (CPUs 0-7):
task:rk-pcie         state:R  running task
Call trace:
  dw_pcie_read
  ...
  rk_pcie_establish_link
  reset_control_deassert

rk-pcie has ppid=kthreadd (async probe), and the read never returns.

Expected behaviour

A warm reboot should leave the PCIe controller and PHY in the same state a cold power-on does, so the next boot's first DBI/config read succeeds.

Root cause

The RK3588 DBI/config read stalls forever (it never faults) when the PCIe PHY / PIPE refclk / PLL is left un-reset. A warm reboot does not power-cycle the block, and rk_pcie_shutdown() only disables LTSSM and masks interrupts — it never asserts the controller reset or powers down the PHY:

static void rk_pcie_shutdown(struct platform_device *pdev)
{
        struct rk_pcie *rk_pcie = platform_get_drvdata(pdev);

        rk_pcie_disable_ltssm(rk_pcie);
        rk_pcie_writel_apb(rk_pcie, PCIE_CLIENT_INTR_MASK, 0xffffffff);
}

So the next boot calls reset_control_deassert() on a reset that was never asserted, against a block whose PLL is still in a stale state → the first DBI read hangs.

Cold boot works because power-on starts every reset asserted.

This is weaker than develop-6.1

develop-6.1 additionally marks the bus PCI_D3cold in shutdown to block further config access:

        /* Prevent any configure space access by claiming we're in D3cold */
        rk_pcie_bus_set_current_state(rk_pcie->pci->pp.bridge->bus, PCI_D3cold);
        rk_pcie_disable_ltssm(rk_pcie);
        rk_pcie_writel_apb(rk_pcie, PCIE_CLIENT_INTR_MASK, 0xffffffff);

develop-6.6 removed that guard, making the warm-reboot teardown strictly weaker than 6.1. (The probe/reset paths themselves are otherwise byte-identical between the two branches, so this is a regression-by-removed-mitigation, not a new hardware issue.)

Note that even 6.1's PCI_D3cold line is only a software guard against config access — neither branch actually resets the hardware on the reboot path.

Why rk_pcie_remove() doesn't help

The only code that fully de-initialises the block —

        phy_power_off(...);
        phy_exit(...);
        reset_control_assert(rk_pcie->rsts);
        rk_pcie_disable_power(rk_pcie);

— lives in rk_pcie_remove(), which is not invoked on reboot. Only .shutdown runs.

Proposed fix

Call the existing rk_pcie_hardware_io_unconfig() from .shutdown so a warm reboot ends in a cold-boot-equivalent state. It already asserts reset_control_assert(rk_pcie->rsts) and powers off / exits the PHY:

 static void rk_pcie_shutdown(struct platform_device *pdev)
 {
         struct rk_pcie *rk_pcie = platform_get_drvdata(pdev);

         rk_pcie_disable_ltssm(rk_pcie);
         rk_pcie_writel_apb(rk_pcie, PCIE_CLIENT_INTR_MASK, 0xffffffff);
+
+        /*
+         * A warm reboot does not power-cycle PCIe. Without asserting the
+         * controller reset and powering down the PHY here, the next boot
+         * deasserts a reset that was never asserted, onto a stale-PLL block,
+         * and the first DBI read hangs forever (never faults).
+         */
+        rk_pcie_hardware_io_unconfig(rk_pcie);
 }

This runs once per rk-pcie instance, so it covers all bridges (fe150000 / fe160000 / fe170000 / fe190000). It only affects the reboot path, so normal boot is unchanged.

Testing

Applied against develop-6.6 @ 1ba51b059f25. With the patch in place a warm reboot returned the board in ~70 s with all PCIe devices enumerated (/dev/nvme0n1 present, DX-M1 present) and zero dw_pcie_read stalls in dmesg.

Possibly related

  • Rockchip commit 47fd882 ("reboot doesn't fully reset hardware")
  • Turing-RK1 reports of DBI stalls / PLL-lock polling on RK3588
  • Independent RK3588 reports of Failed to initialize host / PCIe Link Fail, LTSSM is 0x3

Questions for maintainers

  1. Was dropping the PCI_D3cold guard in develop-6.6 intentional?
  2. Is rk_pcie_hardware_io_unconfig() the preferred hook here, or would you rather see the full rk_pcie_remove()-style teardown (including rk_pcie_disable_power()) on the shutdown path?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions