[PATCH v3 4/4] drivers/perf: Fix the condition where the root port is runtime suspended
From: Yicong Yang
Date: Wed Jul 08 2026 - 04:46:44 EST
The PMU is based on the corresponding root port's VSEC capability,
which could be inaccessible if the target root port is runtime
suspended to D3Cold. Fix this by getting/putting the root port's
runtime pm when the event of this PMU is init/destroy. It cannot
be done in the add()/del() callbacks where the events is actually
scheduled, since there's not way to runtime resume the device
synchronously in the atomic context (add()/del()).
With this patch, the root port will keep D0 while counting and suspend
after counting finished (for prove the correctness since my board does
not support D3Cold):
root@localhost:~# cat /sys/bus/pci/devices/0008\:00\:00.0/power_state
D3hot
root@localhost:~# perf stat -e dwc_rootport_8000/one_cycle/ &
[1] 14580
root@localhost:~# cat /sys/bus/pci/devices/0008\:00\:00.0/power_state
D0
root@localhost:~# kill %1
[1]+ Terminated perf stat -e dwc_rootport_8000/one_cycle/
root@localhost:~# cat /sys/bus/pci/devices/0008\:00\:00.0/power_state
D3hot
The root port also needs to be runtime resumed during PCIe PMU
device registration and probe, so get/put the runtime pm there
as well.
Fixes: af9597adc2f1 ("drivers/perf: add DesignWare PCIe PMU driver")
Closes: https://lore.kernel.org/linux-pci/20260629094234.F25E71F000E9@xxxxxxxxxxxxxxx/
Reported-by: Sashiko <sashiko-bot@xxxxxxxxxx>
Signed-off-by: Yicong Yang <yang.yicong@xxxxxxxxxxxxx>
---
drivers/perf/dwc_pcie_pmu.c | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/drivers/perf/dwc_pcie_pmu.c b/drivers/perf/dwc_pcie_pmu.c
index 7b4ee71b966b..aea387d0d0fe 100644
--- a/drivers/perf/dwc_pcie_pmu.c
+++ b/drivers/perf/dwc_pcie_pmu.c
@@ -17,6 +17,7 @@
#include <linux/list.h>
#include <linux/pcie-dwc.h>
#include <linux/perf_event.h>
+#include <linux/pm_runtime.h>
#include <linux/pci.h>
#include <linux/smp.h>
#include <linux/sysfs.h>
@@ -474,12 +475,20 @@ static enum hrtimer_restart dwc_pcie_pmu_hrtimer_callback(struct hrtimer *hrtime
return HRTIMER_RESTART;
}
+static void dwc_pcie_pmu_event_destroy(struct perf_event *event)
+{
+ struct dwc_pcie_pmu *pcie_pmu = to_dwc_pcie_pmu(event->pmu);
+
+ pm_runtime_put_autosuspend(&pcie_pmu->pdev->dev);
+}
+
static int dwc_pcie_pmu_event_init(struct perf_event *event)
{
struct dwc_pcie_pmu *pcie_pmu = to_dwc_pcie_pmu(event->pmu);
enum dwc_pcie_event_type type = DWC_PCIE_EVENT_TYPE(event);
struct perf_event *sibling;
u32 lane;
+ int ret;
if (event->attr.type != event->pmu->type)
return -ENOENT;
@@ -509,7 +518,16 @@ static int dwc_pcie_pmu_event_init(struct perf_event *event)
if (dwc_pcie_pmu_validate_group(event))
return -ENOSPC;
+ /*
+ * Resume the root port for VSEC access. Use RPM_TRANSPARENT as the
+ * VSEC is accessible if root port's runtime pm is disabled.
+ */
+ ret = pm_runtime_get_active(&pcie_pmu->pdev->dev, RPM_TRANSPARENT);
+ if (ret < 0)
+ return ret;
+
event->cpu = pcie_pmu->on_cpu;
+ event->destroy = dwc_pcie_pmu_event_destroy;
return 0;
}
@@ -776,6 +794,7 @@ static int dwc_pcie_pmu_probe(struct faux_device *fdev)
if (!name)
return -ENOMEM;
+ guard(pm_runtime_active_auto)(&pdev->dev);
vsec = dwc_pcie_des_cap(pdev);
if (!vsec)
return -ENODEV;
@@ -909,6 +928,8 @@ static int __init dwc_pcie_pmu_init(void)
dwc_pcie_pmu_hp_state = ret;
for_each_pci_dev(pdev) {
+ guard(pm_runtime_active_auto)(&pdev->dev);
+
if (!dwc_pcie_des_cap(pdev))
continue;
--
2.50.1 (Apple Git-155)