* [PATCH 0/6] perf/x86/intel/pt: Fix tracing across kexec, kdump and suspend
@ 2026-11-02 7:40 Adrian Hunter
2026-11-02 7:40 ` [PATCH 1/6] irq_work: Re-raise pending irq_work after system resume Adrian Hunter
` (6 more replies)
0 siblings, 7 replies; 12+ messages in thread
From: Adrian Hunter @ 2026-11-02 7:40 UTC (permalink / raw)
To: Peter Zijlstra
Cc: Ingo Molnar, Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, James Clark,
Thomas Gleixner, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, Rafael J. Wysocki, Pavel Machek,
Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold,
Sebastian Andrzej Siewior, Mathieu Desnoyers, Jiayuan Chen,
Steven Rostedt (Google), Vineeth Pillai (Google),
Frederic Weisbecker, Andi Kleen, linux-perf-users, linux-kernel,
linux-pm, driver-core
Hi
Intel PT writes trace data directly to physical memory, so tracing must
be stopped whenever the trace buffers or the hardware state describing
them can no longer be relied on. This series fixes several cases where
that does not currently happen:
- tracing can survive kexec and kdump. After kexec, Intel PT
continues writing into buffers configured by the old kernel,
corrupting memory that the new kernel may have reused
- suspend-related state loss can leave the driver's cached PT MSR
state out of sync with hardware, allowing tracing to restart with
reset output registers and corrupt memory (typically beginning at
page 0)
- races can leave TraceEn set in hardware after perf believes tracing
has stopped, preventing tracing from being reliably disabled and
potentially causing subsequent PT MSR accesses to #GP
The fixes stop relying solely on perf's software state where it is not
authoritative: TraceEn is cleared directly in hardware during shutdown
and emergency-stop paths, tracing is prevented from restarting once
shutdown begins, cached PT MSR state is invalidated whenever hardware
state may have been lost, and active tracing is stopped and restarted
across suspend on the CPU that remains online.
The first patch is a general irq_work fix, not specific to Intel PT. It
re-raises a lost irq_work self-interrupt after resume, preventing
irq_work from remaining permanently pending if the interrupt state is
lost across suspend. The final Intel PT suspend fix depends on it.
All patches are marked for stable. The last patch also depends on commit
2e17bf3a469a ("perf/x86/intel/pt: Fix stop/start with no update") and
its two preparatory patches.
An alternative to the last patch would be for perf core to schedule out
all active events from a syscore suspend callback, and schedule them
back in from the syscore resume callback. That would be a more general
change, but the last patch keeps the fix within the Intel PT driver.
AI disclosure: all issues addressed by this series were identified using
AI assistance, and AI was also used in development of the patches.
Problems described with reproducer examples in the individual changelogs
were reproduced and fixes validated. The race fixed by
"perf/x86/intel/pt: Clear TraceEn in hardware in cpu_emergency_stop_pt()"
was identified by analysis and has not been reproduced.
Adrian Hunter (6):
irq_work: Re-raise pending irq_work after system resume
perf/x86/intel/pt: Stop tracing if VMXON has not yet happened
perf/x86/intel/pt: Stop tracing at syscore shutdown
perf/x86/intel/pt: Clear TraceEn in hardware in cpu_emergency_stop_pt()
perf/x86/intel/pt: Reset stale MSR state on CPU start and resume
perf/x86/intel/pt: Stop tracing across system suspend
arch/x86/events/intel/core.c | 9 ++
arch/x86/events/intel/pt.c | 170 ++++++++++++++++++++++++++++++++++++--
arch/x86/events/intel/pt.h | 6 +-
arch/x86/events/perf_event.h | 4 +
arch/x86/include/asm/perf_event.h | 3 +
arch/x86/power/cpu.c | 3 +-
drivers/base/syscore.c | 10 +++
include/linux/irq_work.h | 2 +
include/linux/perf_event.h | 6 --
kernel/irq_work.c | 12 +++
10 files changed, 208 insertions(+), 17 deletions(-)
base-commit: f96ebf32b3135db4c9eeb868e4cb98e5dfe79ac0
Regards
Adrian
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH 1/6] irq_work: Re-raise pending irq_work after system resume
2026-11-02 7:40 [PATCH 0/6] perf/x86/intel/pt: Fix tracing across kexec, kdump and suspend Adrian Hunter
@ 2026-11-02 7:40 ` Adrian Hunter
2026-11-02 9:06 ` Peter Zijlstra
2026-11-02 7:40 ` [PATCH 2/6] perf/x86/intel/pt: Stop tracing if VMXON has not yet happened Adrian Hunter
` (5 subsequent siblings)
6 siblings, 1 reply; 12+ messages in thread
From: Adrian Hunter @ 2026-11-02 7:40 UTC (permalink / raw)
To: Peter Zijlstra
Cc: Ingo Molnar, Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, James Clark,
Thomas Gleixner, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, Rafael J. Wysocki, Pavel Machek,
Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold,
Sebastian Andrzej Siewior, Mathieu Desnoyers, Jiayuan Chen,
Steven Rostedt (Google), Vineeth Pillai (Google),
Frederic Weisbecker, Andi Kleen, linux-perf-users, linux-kernel,
linux-pm, driver-core
irq_work_queue() raises the irq_work self-interrupt only when adding
work to an empty raised_list. Once raised_list is non-empty, further
queueing assumes the interrupt is already pending and will eventually
process the list.
That assumption can fail across system suspend. irq_work queued after
arch_suspend_disable_irqs() and before the interrupt controller is
suspended, for example from NMI context, may leave its self-interrupt
pending in hardware. If that pending interrupt state is lost during
suspend, hibernation restore, or kexec jump, raised_list remains
non-empty after resume but nothing will process it.
Subsequent irq_work added to raised_list will not raise another
interrupt because the list is already non-empty. Architectures that use
an irq_work interrupt and do not process raised_list from the tick can
therefore leave irq_work pending indefinitely. A caller waiting in
irq_work_sync() may then spin forever, for example when closing a perf
event whose pending irq_work never executes.
For suspend, hibernation, and kexec jump, only the CPU that remains
online is affected. The other CPUs are taken offline beforehand, and
smpcfd_dying_cpu() runs irq_work_run() on each CPU before it is removed,
so checking the local raised_list is sufficient.
Add irq_work_resend(), which re-raises the irq_work self-interrupt when
raised_list is non-empty, and invoke it from syscore_resume() after
interrupt controllers have been restored. Also invoke it from the
syscore_suspend() failure path, after the already-suspended callbacks
have been resumed, because callers do not call syscore_resume() in that
case. This restores a lost irq_work interrupt on the CPU that remains
online for suspend-to-RAM, hibernation, and kexec jump.
If the original interrupt was not lost, the extra self-interrupt is
harmless.
Example S3 with perf record that always loses a self-IPI:
# echo deep > /sys/power/mem_sleep
# cat /sys/power/mem_sleep
s2idle [deep]
# perf record -e power:suspend_resume --no-buffering -C 0 -o /tmp/s3.data &
[2] 2147
# rtcwake -m mem -s 10
rtcwake: wakeup from "mem" using /dev/rtc0
# kill -INT %2
[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0.892 MB /tmp/s3.data ]
Now perf record is stuck in irq_work_sync() which spins forever:
# perf record -C 0 -- sleep 1 | perf report --stdio
# Overhead Command Shared Object Symbol
# ........ ....... ................. ................................
#
99.98% perf [kernel.kallsyms] [k] irq_work_sync
0.02% perf [kernel.kallsyms] [k] ktime_get_update_offsets_now
Eventually resulting in hung tasks:
INFO: task kworker/u8:1:35 blocked for more than 122 seconds.
Not tainted 7.3.0 #2
task:kworker/u8:1 state:D stack:0 pid:35 tgid:35 ppid:2 task_flags:0x4208060 flags:0x00080000
Workqueue: events_unbound fsnotify_mark_destroy_workfn
Fixes: 76a33061b932 ("irq_work: Force raised irq work to run on irq work interrupt")
Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
Cc: stable@vger.kernel.org
---
drivers/base/syscore.c | 10 ++++++++++
include/linux/irq_work.h | 2 ++
kernel/irq_work.c | 12 ++++++++++++
3 files changed, 24 insertions(+)
diff --git a/drivers/base/syscore.c b/drivers/base/syscore.c
index 483adb796654..3f645da46e77 100644
--- a/drivers/base/syscore.c
+++ b/drivers/base/syscore.c
@@ -6,6 +6,7 @@
*/
#include <linux/syscore_ops.h>
+#include <linux/irq_work.h>
#include <linux/mutex.h>
#include <linux/module.h>
#include <linux/suspend.h>
@@ -81,6 +82,8 @@ int syscore_suspend(void)
if (syscore->ops->resume)
syscore->ops->resume(syscore->data);
+ irq_work_resend();
+
return ret;
}
EXPORT_SYMBOL_GPL(syscore_suspend);
@@ -106,6 +109,13 @@ void syscore_resume(void)
"Interrupts enabled after %pS\n",
syscore->ops->resume);
}
+
+ /*
+ * A pending irq_work self-interrupt may have been lost while suspended.
+ * Resend it now that interrupt controllers have been restored.
+ */
+ irq_work_resend();
+
trace_suspend_resume(TPS("syscore_resume"), 0, false);
}
EXPORT_SYMBOL_GPL(syscore_resume);
diff --git a/include/linux/irq_work.h b/include/linux/irq_work.h
index 7184e1273965..0040ea94a4fe 100644
--- a/include/linux/irq_work.h
+++ b/include/linux/irq_work.h
@@ -62,6 +62,7 @@ void irq_work_run(void);
void irq_work_run_cpu(unsigned int cpu);
bool irq_work_needs_cpu(void);
void irq_work_single(void *arg);
+void irq_work_resend(void);
void arch_irq_work_raise(void);
@@ -70,6 +71,7 @@ static inline bool irq_work_needs_cpu(void) { return false; }
static inline void irq_work_run(void) { }
static inline void irq_work_run_cpu(unsigned int cpu) { }
static inline void irq_work_single(void *arg) { }
+static inline void irq_work_resend(void) { }
#endif
#endif /* _LINUX_IRQ_WORK_H */
diff --git a/kernel/irq_work.c b/kernel/irq_work.c
index 73eabcbdcd50..5d186ec35770 100644
--- a/kernel/irq_work.c
+++ b/kernel/irq_work.c
@@ -274,6 +274,18 @@ void irq_work_run_cpu(unsigned int cpu)
irq_work_run_list(per_cpu_ptr(&lazy_list, cpu));
}
+/*
+ * Raise the self-interrupt again if irq_work is queued, because it may have
+ * been lost across system suspend.
+ */
+void irq_work_resend(void)
+{
+ lockdep_assert_preemption_disabled();
+
+ if (!llist_empty(this_cpu_ptr(&raised_list)))
+ arch_irq_work_raise();
+}
+
void irq_work_tick(void)
{
struct llist_head *raised = this_cpu_ptr(&raised_list);
--
2.53.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH 2/6] perf/x86/intel/pt: Stop tracing if VMXON has not yet happened
2026-11-02 7:40 [PATCH 0/6] perf/x86/intel/pt: Fix tracing across kexec, kdump and suspend Adrian Hunter
2026-11-02 7:40 ` [PATCH 1/6] irq_work: Re-raise pending irq_work after system resume Adrian Hunter
@ 2026-11-02 7:40 ` Adrian Hunter
2026-11-02 9:57 ` Peter Zijlstra
2026-11-02 7:40 ` [PATCH 3/6] perf/x86/intel/pt: Stop tracing at syscore shutdown Adrian Hunter
` (4 subsequent siblings)
6 siblings, 1 reply; 12+ messages in thread
From: Adrian Hunter @ 2026-11-02 7:40 UTC (permalink / raw)
To: Peter Zijlstra
Cc: Ingo Molnar, Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, James Clark,
Thomas Gleixner, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, Rafael J. Wysocki, Pavel Machek,
Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold,
Sebastian Andrzej Siewior, Mathieu Desnoyers, Jiayuan Chen,
Steven Rostedt (Google), Vineeth Pillai (Google),
Frederic Weisbecker, Andi Kleen, linux-perf-users, linux-kernel,
linux-pm, driver-core
On Broadwell, which does not support Intel PT in VMX operation
(IA32_VMX_MISC[14] = 0), VMXON clears IA32_RTIT_CTL.TraceEn and any
subsequent write to IA32_RTIT_CTL causes #GP.
To avoid that, intel_pt_handle_vmx() sets pt->vmx_on so that
pt_config_start() and pt_config_stop() skip writes to
IA32_RTIT_CTL while VMX is active.
However, pt->vmx_on is set before VMXON executes. An NMI in the window
between setting pt->vmx_on and executing VMXON can then observe
vmx_on == true while tracing is still enabled.
In that case pt_config_stop() skips the write that would clear TraceEn,
leaving tracing active. This can subsequently cause:
- intel_pt_interrupt() to reprogram PT output registers while
TraceEn is set, resulting in #GP, or
- cpu_emergency_stop_pt() to leave tracing active during kdump,
allowing tracing to continue into the capture kernel.
If pt->vmx_on is set, read IA32_RTIT_CTL and still clear TraceEn when
it remains enabled. Writing IA32_RTIT_CTL is valid until VMXON
executes, while after VMXON TraceEn is guaranteed clear and cannot be
re-enabled. Consequently no write is attempted while VMX is active.
Example (on a Broadwell):
Instrument x86_vmx_enable_virtualization_cpu() to enlarge the
window e.g.
intel_pt_handle_vmx(1);
+ for (int j = 0; j < 6; j ++) {
+ int cpu = smp_processor_id();
+
+ pr_info("VMX: cpu %d: spinning %d\n", cpu, j);
+ for (int i = 0; i < 10000; i++)
+ cpu_relax();
+ }
r = x86_virt_cpu_vmxon();
Start Intel PT tracing on a CPU:
perf record -e intel_pt//k -C 1 &
Load KVM:
modprobe kvm_intel
Observe kernel messages. Without the fix, there is an unchecked
MSR access:
VMX: cpu 1: spinning 0
VMX: cpu 1: spinning 1
VMX: cpu 1: spinning 2
VMX: cpu 1: spinning 3
unchecked MSR access error: WRMSR to 0x571 ...
Call Trace:
<NMI>
intel_pt_interrupt+0x190/0x490
handle_pmi_common+0x6fc/0xc10
intel_pmu_handle_irq+0x1c9/0x5e0
perf_event_nmi_handler+0x62/0x90
nmi_handle.part.0+0xbd/0x270
default_do_nmi+0x3c/0x120
exc_nmi+0xe3/0x110
<TASK>
x86_vmx_enable_virtualization_cpu+0x88/0xd0
x86_virt_get_ref+0x3f/0x80
kvm_arch_enable_virtualization_cpu+0x1c4/0x820 [kvm]
kvm_online_cpu+0x31/0x50 [kvm]
cpuhp_invoke_callback+0x25c/0xf90
cpuhp_thread_fun+0x298/0x7e0
smpboot_thread_fn+0x28c/0x6d0
kthread+0x2cb/0x3a0
ret_from_fork+0x5ff/0x7f0
Fixes: 1c5ac21a0e9b ("perf/x86/intel/pt: Don't die on VMXON")
Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
Cc: stable@vger.kernel.org
---
arch/x86/events/intel/pt.c | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/arch/x86/events/intel/pt.c b/arch/x86/events/intel/pt.c
index 49349afee611..36bf039668d7 100644
--- a/arch/x86/events/intel/pt.c
+++ b/arch/x86/events/intel/pt.c
@@ -567,6 +567,15 @@ static void pt_config(struct perf_event *event)
pt_config_enable(event);
}
+static bool pt_trace_enabled(void)
+{
+ u64 ctl;
+
+ rdmsrq(MSR_IA32_RTIT_CTL, ctl);
+
+ return ctl & RTIT_CTL_TRACEEN;
+}
+
static void pt_config_stop(struct perf_event *event)
{
struct pt *pt = this_cpu_ptr(&pt_ctx);
@@ -577,7 +586,12 @@ static void pt_config_stop(struct perf_event *event)
return;
ctl &= ~RTIT_CTL_TRACEEN;
- if (!READ_ONCE(pt->vmx_on))
+ /*
+ * vmx_on is set before VMXON, so tracing may not have stopped yet.
+ * RTIT_CTL can be written until VMXON, and after VMXON TraceEn is
+ * clear, so still write RTIT_CTL if TraceEn is set.
+ */
+ if (!READ_ONCE(pt->vmx_on) || pt_trace_enabled())
wrmsrq(MSR_IA32_RTIT_CTL, ctl);
WRITE_ONCE(event->hw.aux_config, ctl);
--
2.53.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH 3/6] perf/x86/intel/pt: Stop tracing at syscore shutdown
2026-11-02 7:40 [PATCH 0/6] perf/x86/intel/pt: Fix tracing across kexec, kdump and suspend Adrian Hunter
2026-11-02 7:40 ` [PATCH 1/6] irq_work: Re-raise pending irq_work after system resume Adrian Hunter
2026-11-02 7:40 ` [PATCH 2/6] perf/x86/intel/pt: Stop tracing if VMXON has not yet happened Adrian Hunter
@ 2026-11-02 7:40 ` Adrian Hunter
2026-11-02 10:04 ` Peter Zijlstra
2026-11-02 7:40 ` [PATCH 4/6] perf/x86/intel/pt: Clear TraceEn in hardware in cpu_emergency_stop_pt() Adrian Hunter
` (3 subsequent siblings)
6 siblings, 1 reply; 12+ messages in thread
From: Adrian Hunter @ 2026-11-02 7:40 UTC (permalink / raw)
To: Peter Zijlstra
Cc: Ingo Molnar, Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, James Clark,
Thomas Gleixner, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, Rafael J. Wysocki, Pavel Machek,
Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold,
Sebastian Andrzej Siewior, Mathieu Desnoyers, Jiayuan Chen,
Steven Rostedt (Google), Vineeth Pillai (Google),
Frederic Weisbecker, Andi Kleen, linux-perf-users, linux-kernel,
linux-pm, driver-core
Intel PT tracing can survive a shutdown sequence and continue into a
kexec'ed kernel.
The perf reboot notifier removes only per-CPU events, so per-task
events can remain active, for example:
perf record -e intel_pt// -- kexec -e
Tracing can also remain enabled on CPUs running traced tasks when
stop_other_cpus() halts them. On systems where Intel PT is unavailable
in VMX operation (IA32_VMX_MISC[14] = 0), VMXOFF during KVM shutdown can
re-enable TraceEn via intel_pt_handle_vmx(0) if a tracing event is
still active.
As a result, the new kernel may inherit TraceEn set and Intel PT can
continue writing into buffers allocated by the old kernel. Those pages
may subsequently be reused for unrelated purposes. In addition,
pt_init() detects TraceEn already enabled and refuses to register the
PMU.
Stopping events from a shutdown callback is not sufficient because they
may be scheduled back in until interrupts are finally disabled.
Instead, add a syscore shutdown handler that:
- prevents TraceEn from being enabled again, and
- clears TraceEn on every CPU.
syscore_shutdown() is executed for kexec, reboot, halt and poweroff,
and still runs while CPUs can service IPIs.
To block tracing from restarting, introduce a shutdown flag checked by
both pt_config_start() and intel_pt_handle_vmx(), the only places that
can enable TraceEn. After shutdown begins, events remain in a
self-consistent perf state but no longer generate trace data, matching
the existing VMX-on handling.
Clear TraceEn by reading IA32_RTIT_CTL on each CPU and updating the
hardware state directly rather than relying on aux_config, ensuring
tracing is disabled regardless of perf's software state. Once the
shutdown flag is set, no racing path can re-enable TraceEn.
kexec jump (KEXEC_PRESERVE_CONTEXT) is not covered because it does not
execute syscore shutdown. kdump continues to rely on
cpu_emergency_stop_pt().
Example:
On first kernel:
perf record -e intel_pt// -- kexec -e
On second kernel:
# Without the fix, PT is enabled at boot
dmesg | grep -i 'PT is enabled at boot time'
[ 2.065889] pt: PT is enabled at boot time, doing nothing
Fixes: 52ca9ced3f70 ("perf/x86/intel/pt: Add Intel PT PMU driver")
Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
Cc: stable@vger.kernel.org
---
arch/x86/events/intel/pt.c | 46 +++++++++++++++++++++++++++++++++++---
1 file changed, 43 insertions(+), 3 deletions(-)
diff --git a/arch/x86/events/intel/pt.c b/arch/x86/events/intel/pt.c
index 36bf039668d7..7453e9880b25 100644
--- a/arch/x86/events/intel/pt.c
+++ b/arch/x86/events/intel/pt.c
@@ -18,6 +18,7 @@
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/kvm_types.h>
+#include <linux/syscore_ops.h>
#include <asm/cpuid/api.h>
#include <asm/perf_event.h>
@@ -34,6 +35,9 @@ static DEFINE_PER_CPU(struct pt, pt_ctx);
static struct pt_pmu pt_pmu;
+/* Set at shutdown, after which TraceEn must not be set */
+static bool pt_shutdown;
+
/*
* Capabilities of Intel PT hardware, such as number of address bits or
* supported output schemes, are cached and exported to userspace as "caps"
@@ -425,7 +429,7 @@ static void pt_config_start(struct perf_event *event)
return;
ctl |= RTIT_CTL_TRACEEN;
- if (READ_ONCE(pt->vmx_on))
+ if (READ_ONCE(pt->vmx_on) || READ_ONCE(pt_shutdown))
perf_aux_output_flag(&pt->handle, PERF_AUX_FLAG_PARTIAL);
else
wrmsrq(MSR_IA32_RTIT_CTL, ctl);
@@ -1609,7 +1613,7 @@ void intel_pt_handle_vmx(int on)
PERF_AUX_FLAG_PARTIAL);
/* Turn PTs back on */
- if (!on && event)
+ if (!on && event && !READ_ONCE(pt_shutdown))
wrmsrq(MSR_IA32_RTIT_CTL, event->hw.aux_config);
local_irq_restore(flags);
@@ -1860,6 +1864,38 @@ static int pt_event_init(struct perf_event *event)
return 0;
}
+static void pt_shutdown_cpu(void *info)
+{
+ u64 ctl;
+
+ /*
+ * After VMXON on CPUs without PT support in VMX operation,
+ * TraceEn is already clear and RTIT_CTL must not be written.
+ */
+ rdmsrq(MSR_IA32_RTIT_CTL, ctl);
+ if (ctl & RTIT_CTL_TRACEEN)
+ wrmsrq(MSR_IA32_RTIT_CTL, ctl & ~RTIT_CTL_TRACEEN);
+}
+
+/*
+ * Tracing must not continue into a kexec'ed kernel, which may reuse the
+ * trace buffer memory, so stop tracing on all CPUs and prevent it from
+ * being started again. Events are left in place, but record nothing more.
+ */
+static void pt_syscore_shutdown(void *data)
+{
+ WRITE_ONCE(pt_shutdown, true);
+ on_each_cpu(pt_shutdown_cpu, NULL, 1);
+}
+
+static const struct syscore_ops pt_syscore_ops = {
+ .shutdown = pt_syscore_shutdown,
+};
+
+static struct syscore pt_syscore = {
+ .ops = &pt_syscore_ops,
+};
+
void cpu_emergency_stop_pt(void)
{
struct pt *pt = this_cpu_ptr(&pt_ctx);
@@ -1933,7 +1969,11 @@ static __init int pt_init(void)
intel_pt_validate_hw_cap(PT_CAP_num_address_ranges);
ret = perf_pmu_register(&pt_pmu.pmu, "intel_pt", -1);
+ if (ret)
+ return ret;
- return ret;
+ register_syscore(&pt_syscore);
+
+ return 0;
}
arch_initcall(pt_init);
--
2.53.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH 4/6] perf/x86/intel/pt: Clear TraceEn in hardware in cpu_emergency_stop_pt()
2026-11-02 7:40 [PATCH 0/6] perf/x86/intel/pt: Fix tracing across kexec, kdump and suspend Adrian Hunter
` (2 preceding siblings ...)
2026-11-02 7:40 ` [PATCH 3/6] perf/x86/intel/pt: Stop tracing at syscore shutdown Adrian Hunter
@ 2026-11-02 7:40 ` Adrian Hunter
2026-11-02 10:07 ` Peter Zijlstra
2026-11-02 7:40 ` [PATCH 5/6] perf/x86/intel/pt: Reset stale MSR state on CPU start and resume Adrian Hunter
` (2 subsequent siblings)
6 siblings, 1 reply; 12+ messages in thread
From: Adrian Hunter @ 2026-11-02 7:40 UTC (permalink / raw)
To: Peter Zijlstra
Cc: Ingo Molnar, Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, James Clark,
Thomas Gleixner, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, Rafael J. Wysocki, Pavel Machek,
Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold,
Sebastian Andrzej Siewior, Mathieu Desnoyers, Jiayuan Chen,
Steven Rostedt (Google), Vineeth Pillai (Google),
Frederic Weisbecker, Andi Kleen, linux-perf-users, linux-kernel,
linux-pm, driver-core
cpu_emergency_stop_pt() stops tracing through pt_event_stop(), which
assumes the software state accurately reflects whether tracing is
enabled. That assumption can fail when a crash NMI interrupts
pt_config_start().
pt_config_start() sets TraceEn in IA32_RTIT_CTL before updating
event->hw.aux_config. If a crash NMI arrives in that window,
pt_config_stop() sees TraceEn clear in aux_config and skips the MSR
write needed to disable tracing. Tracing remains active in hardware.
Alternatively, an earlier NMI in the same window can stop the event
without restarting it, for example a group throttle calling
pt_event_stop(event, 0), or a PT PMI for which perf_aux_output_begin()
fails. pt_config_stop() again skips the write, so the event is stopped
only in software: TraceEn remains set in IA32_RTIT_CTL, and
pt->handle.event becomes NULL. When pt_config_start() continues, it
sets TraceEn in aux_config, so tracing continues until the event is
next stopped. If a crash occurs before then, cpu_emergency_stop_pt()
sees no event and does nothing.
In both cases tracing can remain active into the kdump kernel. Moreover,
pt_event_stop() may access PT MSRs while tracing is still enabled and
can trigger a #GP by writing IA32_RTIT_STATUS with TraceEn set.
Fix this by making cpu_emergency_stop_pt() disable tracing directly from
the hardware state. Set pt_shutdown and invoke pt_shutdown_cpu(), which
reads IA32_RTIT_CTL and clears TraceEn whenever it is set, independent
of perf's software state. The shutdown flag prevents any path from
re-enabling tracing afterwards, including intel_pt_handle_vmx(0) during
the emergency VMXOFF sequence on other CPUs.
Continue to call pt_event_stop() afterwards so that AUX state, including
the buffer head, is updated for vmcore collection. Its PT MSR accesses
now occur with tracing disabled.
Do this only if the driver initialized successfully. In particular, if
pt_init() finds tracing already enabled at boot, it leaves Intel PT
alone, and the emergency path must do the same.
The race is reachable only through restart paths that call
pt_config_start() with TraceEn clear in the software copy. These were
introduced by the commits with the Fixes tags below. The restart path
in the PT PMI handler runs in NMI context, so only a machine check can
interrupt it.
Fixes: 8e105a1fc2a0 ("perf/x86/intel/pt: Factor out pt_config_start()")
Fixes: 25e8920b301c ("perf/x86/intel/pt: Add sampling support")
Fixes: 08c7454ceb94 ("perf/x86/intel/pt: Add support for pause / resume")
Fixes: 2e17bf3a469a ("perf/x86/intel/pt: Fix stop/start with no update")
Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
Cc: stable@vger.kernel.org
---
arch/x86/events/intel/pt.c | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/arch/x86/events/intel/pt.c b/arch/x86/events/intel/pt.c
index 7453e9880b25..8d71c00f9ff6 100644
--- a/arch/x86/events/intel/pt.c
+++ b/arch/x86/events/intel/pt.c
@@ -38,6 +38,8 @@ static struct pt_pmu pt_pmu;
/* Set at shutdown, after which TraceEn must not be set */
static bool pt_shutdown;
+static bool pt_initialized __ro_after_init;
+
/*
* Capabilities of Intel PT hardware, such as number of address bits or
* supported output schemes, are cached and exported to userspace as "caps"
@@ -1900,6 +1902,12 @@ void cpu_emergency_stop_pt(void)
{
struct pt *pt = this_cpu_ptr(&pt_ctx);
+ if (!pt_initialized)
+ return;
+
+ WRITE_ONCE(pt_shutdown, true);
+ pt_shutdown_cpu(NULL);
+
if (pt->handle.event)
pt_event_stop(pt->handle.event, PERF_EF_UPDATE);
}
@@ -1973,6 +1981,7 @@ static __init int pt_init(void)
return ret;
register_syscore(&pt_syscore);
+ pt_initialized = true;
return 0;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH 5/6] perf/x86/intel/pt: Reset stale MSR state on CPU start and resume
2026-11-02 7:40 [PATCH 0/6] perf/x86/intel/pt: Fix tracing across kexec, kdump and suspend Adrian Hunter
` (3 preceding siblings ...)
2026-11-02 7:40 ` [PATCH 4/6] perf/x86/intel/pt: Clear TraceEn in hardware in cpu_emergency_stop_pt() Adrian Hunter
@ 2026-11-02 7:40 ` Adrian Hunter
2026-11-02 7:40 ` [PATCH 6/6] perf/x86/intel/pt: Stop tracing across system suspend Adrian Hunter
2026-11-02 10:10 ` [PATCH 0/6] perf/x86/intel/pt: Fix tracing across kexec, kdump and suspend Peter Zijlstra
6 siblings, 0 replies; 12+ messages in thread
From: Adrian Hunter @ 2026-11-02 7:40 UTC (permalink / raw)
To: Peter Zijlstra
Cc: Ingo Molnar, Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, James Clark,
Thomas Gleixner, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, Rafael J. Wysocki, Pavel Machek,
Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold,
Sebastian Andrzej Siewior, Mathieu Desnoyers, Jiayuan Chen,
Steven Rostedt (Google), Vineeth Pillai (Google),
Frederic Weisbecker, Andi Kleen, linux-perf-users, linux-kernel,
linux-pm, driver-core
The Intel PT driver caches the last values written to
IA32_RTIT_OUTPUT_BASE, IA32_RTIT_OUTPUT_MASK_PTRS and IA32_RTIT_ADDRn_A/B,
and skips redundant WRMSRs. Those cached values are not invalidated when
the hardware state changes outside the driver's control.
After S3, hibernation, CPU replacement, or similar events, the hardware
MSRs may be reset while the cached copies retain their old values.
Conversely, after warm reboot, kexec or kdump, the MSRs may retain values
from a previous kernel while the cached copies start at zero. In either
case, the cached state no longer matches the hardware state and the
driver can wrongly skip WRMSRs that must be issued.
In particular, after resume the driver can restart tracing with
OUTPUT_BASE left at its reset value of 0. Depending on the output mode,
this may cause Intel PT to write trace data to unintended physical
addresses (typically page 0) and corrupt arbitrary physical memory,
including kernel memory or MMIO.
IA32_RTIT_STATUS can also retain Error or Stopped bits from a previous
kernel. Because STATUS is only cleared on an event's first start, stale
bits can suppress packet generation and trigger spurious "ToPA ERROR"
messages.
Add intel_pt_reset_cpu_state() to invalidate all cached PT MSR values and
clear stale STATUS bits. Call it from intel_pmu_cpu_starting() (every
CPU with Intel PT also has architectural perfmon), ensuring that PT state
is reinitialized whenever a CPU comes online, and also call it from
restore_processor_state() to handle the boot CPU.
intel_pt_reset_cpu_state() is an Intel PMU implementation detail. Rather
than expose it directly to arch/x86/power, add a single x86 perf hook,
perf_restore_processor_state(), declared in <asm/perf_event.h>, that
restores all perf state lost across suspend and resume. The new hook
invokes both perf_restore_debug_store() and intel_pt_reset_cpu_state().
As a result, perf_restore_debug_store() no longer needs a public
declaration in <linux/perf_event.h>, and can be moved into the x86 perf
internals.
Use BIT_ULL(63) as the invalid cached value because it is not a valid
value for any of the affected PT MSRs. Replace the cached pt_filters
structure with dedicated u64 arrays so the invalid value can also be
represented on 32-bit systems.
Note, an event that remains active on the boot CPU across S3 or
hibernation is not addressed here.
Example showing page 0 corruption with S3:
After S3, when Intel PT is scheduled back in, the MSRs match the
saved values, so are not updated, but IA32_RTIT_OUTPUT_BASE is 0.
# cat /sys/power/mem_sleep
s2idle [deep]
# dd if=/dev/mem bs=4096 count=1 | od -t x1 -A x
1+0 records in
1+0 records out
4096 bytes (4.1 kB, 4.0 KiB) copied, 0.000237849 s, 4.1 MB/s
000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
001000
# perf record -e intel_pt// -- rtcwake -m mem -s 10
rtcwake: wakeup from "mem" using /dev/rtc0 at Mon Oct 26 18:04:26 2026
[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 5.343 MB perf.data ]
# dd if=/dev/mem bs=4096 count=1 | od -t x1 -A x
1+0 records in
1+0 records out
4096 bytes (4.1 kB, 4.0 KiB) copied, 0.000236374 s, 4.1 MB/s
000000 80 d2 06 59 03 b2 c8 dc be ae ce c0 ec 82 b4 0c
000010 59 04 82 9a c0 e8 84 cc e6 92 b8 fc de 04 59 05
000020 bc 86 b0 8a d4 82 b4 c0 ac d0 8a d4 82 b4 c0 59
000030 06 ac d0 8a d4 82 b4 c0 ac 34 59 07 82 b4 c0 ac
...
Fixes: 295c52ee1485 ("perf/x86/intel/pt: Prevent redundant WRMSRs")
Fixes: eadf48cab4b6 ("perf/x86/intel/pt: Add support for address range filtering in PT")
Fixes: 9a6694cfa239 ("perf/x86/intel/pt: Do not force sync packets on every schedule-in")
Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
Cc: stable@vger.kernel.org
---
arch/x86/events/intel/core.c | 9 +++++++
arch/x86/events/intel/pt.c | 39 +++++++++++++++++++++++++++----
arch/x86/events/intel/pt.h | 6 +++--
arch/x86/events/perf_event.h | 4 ++++
arch/x86/include/asm/perf_event.h | 3 +++
arch/x86/power/cpu.c | 3 ++-
include/linux/perf_event.h | 6 -----
7 files changed, 57 insertions(+), 13 deletions(-)
diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c
index 0a34d674df59..88281559026d 100644
--- a/arch/x86/events/intel/core.c
+++ b/arch/x86/events/intel/core.c
@@ -6489,6 +6489,8 @@ static void intel_pmu_cpu_starting(int cpu)
int core_id = topology_core_id(cpu);
int i;
+ intel_pt_reset_cpu_state();
+
if (is_hybrid() && !init_hybrid_pmu(cpu))
return;
@@ -6573,6 +6575,13 @@ static void intel_pmu_cpu_starting(int cpu)
}
}
+/* The boot CPU is not hotplugged across S3, hibernation or kexec jump */
+void perf_restore_processor_state(void)
+{
+ perf_restore_debug_store();
+ intel_pt_reset_cpu_state();
+}
+
static void free_excl_cntrs(struct cpu_hw_events *cpuc)
{
struct intel_excl_cntrs *c;
diff --git a/arch/x86/events/intel/pt.c b/arch/x86/events/intel/pt.c
index 8d71c00f9ff6..272808f2b483 100644
--- a/arch/x86/events/intel/pt.c
+++ b/arch/x86/events/intel/pt.c
@@ -492,14 +492,14 @@ static u64 pt_config_filters(struct perf_event *event)
*/
/* avoid redundant msr writes */
- if (pt->filters.filter[range].msr_a != filter->msr_a) {
+ if (pt->addr_a[range] != filter->msr_a) {
wrmsrq(pt_address_ranges[range].msr_a, filter->msr_a);
- pt->filters.filter[range].msr_a = filter->msr_a;
+ pt->addr_a[range] = filter->msr_a;
}
- if (pt->filters.filter[range].msr_b != filter->msr_b) {
+ if (pt->addr_b[range] != filter->msr_b) {
wrmsrq(pt_address_ranges[range].msr_b, filter->msr_b);
- pt->filters.filter[range].msr_b = filter->msr_b;
+ pt->addr_b[range] = filter->msr_b;
}
rtit_ctl |= (u64)filter->config << pt_address_ranges[range].reg_off;
@@ -1912,6 +1912,37 @@ void cpu_emergency_stop_pt(void)
pt_event_stop(pt->handle.event, PERF_EF_UPDATE);
}
+/* Not a valid value for any of the cached Intel PT MSRs */
+#define PT_MSR_INVALID BIT_ULL(63)
+
+/*
+ * Intel PT MSRs may have been reset, or left set by a previous kernel, so
+ * invalidate the cached copies and clear stale STATUS bits.
+ */
+void intel_pt_reset_cpu_state(void)
+{
+ struct pt *pt = this_cpu_ptr(&pt_ctx);
+ u64 status;
+ int i;
+
+ /* Can run before pt_init(), so check the feature bit */
+ if (!boot_cpu_has(X86_FEATURE_INTEL_PT))
+ return;
+
+ pt->output_base = PT_MSR_INVALID;
+ pt->output_mask = PT_MSR_INVALID;
+ for (i = 0; i < PT_FILTERS_NUM; i++) {
+ pt->addr_a[i] = PT_MSR_INVALID;
+ pt->addr_b[i] = PT_MSR_INVALID;
+ }
+
+ rdmsrq(MSR_IA32_RTIT_STATUS, status);
+ if (status & (RTIT_STATUS_ERROR | RTIT_STATUS_STOPPED)) {
+ /* Fails if a previous kernel left TraceEn set */
+ wrmsrq_safe(MSR_IA32_RTIT_STATUS, 0);
+ }
+}
+
int is_intel_pt_event(struct perf_event *event)
{
return event->pmu == &pt_pmu.pmu;
diff --git a/arch/x86/events/intel/pt.h b/arch/x86/events/intel/pt.h
index 700e1a915d25..9fd2c02a90e1 100644
--- a/arch/x86/events/intel/pt.h
+++ b/arch/x86/events/intel/pt.h
@@ -111,7 +111,8 @@ struct pt_filters {
/**
* struct pt - per-cpu pt context
* @handle: perf output handle
- * @filters: last configured filters
+ * @addr_a: cached RTIT_ADDRn_A MSR values
+ * @addr_b: cached RTIT_ADDRn_B MSR values
* @handle_nmi: do handle PT PMI on this cpu, there's an active event
* @vmx_on: 1 if VMX is ON on this cpu
* @pause_allowed: PERF_EF_PAUSE is allowed to stop tracing
@@ -121,7 +122,8 @@ struct pt_filters {
*/
struct pt {
struct perf_output_handle handle;
- struct pt_filters filters;
+ u64 addr_a[PT_FILTERS_NUM];
+ u64 addr_b[PT_FILTERS_NUM];
int handle_nmi;
int vmx_on;
int pause_allowed;
diff --git a/arch/x86/events/perf_event.h b/arch/x86/events/perf_event.h
index e274802ef062..32f1297fd9fe 100644
--- a/arch/x86/events/perf_event.h
+++ b/arch/x86/events/perf_event.h
@@ -1867,6 +1867,8 @@ void init_debug_store_on_cpu(int cpu);
void fini_debug_store_on_cpu(int cpu);
+void perf_restore_debug_store(void);
+
void release_ds_buffers(void);
void reserve_ds_buffers(void);
@@ -2035,6 +2037,8 @@ int intel_pmu_setup_lbr_filter(struct perf_event *event);
void intel_pt_interrupt(void);
+void intel_pt_reset_cpu_state(void);
+
int intel_bts_interrupt(void);
void intel_bts_enable_local(void);
diff --git a/arch/x86/include/asm/perf_event.h b/arch/x86/include/asm/perf_event.h
index 5c92d43bbef8..49130b28a21a 100644
--- a/arch/x86/include/asm/perf_event.h
+++ b/arch/x86/include/asm/perf_event.h
@@ -828,6 +828,7 @@ struct x86_guest_pebs {
extern struct perf_guest_switch_msr *perf_guest_get_msrs(int *nr,
struct x86_guest_pebs *guest_pebs);
extern void x86_perf_get_lbr(struct x86_pmu_lbr *lbr);
+extern void perf_restore_processor_state(void);
#else
struct perf_guest_switch_msr *perf_guest_get_msrs(int *nr,
struct x86_guest_pebs *guest_pebs);
@@ -835,6 +836,8 @@ static inline void x86_perf_get_lbr(struct x86_pmu_lbr *lbr)
{
memset(lbr, 0, sizeof(*lbr));
}
+
+static inline void perf_restore_processor_state(void) { }
#endif
#ifdef CONFIG_CPU_SUP_INTEL
diff --git a/arch/x86/power/cpu.c b/arch/x86/power/cpu.c
index 702f30eaf9c4..ad8108faa743 100644
--- a/arch/x86/power/cpu.c
+++ b/arch/x86/power/cpu.c
@@ -29,6 +29,7 @@
#include <asm/microcode.h>
#include <asm/msr.h>
#include <asm/fred.h>
+#include <asm/perf_event.h>
#ifdef CONFIG_X86_32
__visible unsigned long saved_context_ebx;
@@ -278,7 +279,7 @@ static void notrace __restore_processor_state(struct saved_context *ctxt)
tsc_verify_tsc_adjust(true);
x86_platform.restore_sched_clock_state();
cache_bp_restore();
- perf_restore_debug_store();
+ perf_restore_processor_state();
c = &cpu_data(smp_processor_id());
if (cpu_has(c, X86_FEATURE_MSR_IA32_FEAT_CTL))
diff --git a/include/linux/perf_event.h b/include/linux/perf_event.h
index eb3f7bab498c..4551de15efbe 100644
--- a/include/linux/perf_event.h
+++ b/include/linux/perf_event.h
@@ -2031,12 +2031,6 @@ static inline int perf_allow_tracepoint(void)
#endif /* !CONFIG_PERF_EVENTS */
-#if defined(CONFIG_PERF_EVENTS) && defined(CONFIG_CPU_SUP_INTEL)
-extern void perf_restore_debug_store(void);
-#else
-static inline void perf_restore_debug_store(void) { }
-#endif
-
#define perf_output_put(handle, x) perf_output_copy((handle), &(x), sizeof(x))
struct perf_pmu_events_attr {
--
2.53.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH 6/6] perf/x86/intel/pt: Stop tracing across system suspend
2026-11-02 7:40 [PATCH 0/6] perf/x86/intel/pt: Fix tracing across kexec, kdump and suspend Adrian Hunter
` (4 preceding siblings ...)
2026-11-02 7:40 ` [PATCH 5/6] perf/x86/intel/pt: Reset stale MSR state on CPU start and resume Adrian Hunter
@ 2026-11-02 7:40 ` Adrian Hunter
2026-11-02 10:10 ` [PATCH 0/6] perf/x86/intel/pt: Fix tracing across kexec, kdump and suspend Peter Zijlstra
6 siblings, 0 replies; 12+ messages in thread
From: Adrian Hunter @ 2026-11-02 7:40 UTC (permalink / raw)
To: Peter Zijlstra
Cc: Ingo Molnar, Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, James Clark,
Thomas Gleixner, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, Rafael J. Wysocki, Pavel Machek,
Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold,
Sebastian Andrzej Siewior, Mathieu Desnoyers, Jiayuan Chen,
Steven Rostedt (Google), Vineeth Pillai (Google),
Frederic Weisbecker, Andi Kleen, linux-perf-users, linux-kernel,
linux-pm, driver-core
During S3, hibernation and kexec jump, all CPUs except one are taken
offline, stopping their Intel PT events. The CPU that remains online
retains an active event, but its Intel PT state is lost when power is
removed.
After resume, perf still believes the event is running even though the
PT MSRs have returned to their reset values. Paths that set TraceEn
without fully reprogramming Intel PT can therefore resume tracing with
invalid OUTPUT_BASE/OUTPUT_MASK state. In ToPA mode, stopping such an
event reads back OUTPUT_BASE = 0 and derives an invalid AUX head from
it.
Stop the remaining Intel PT event in a syscore suspend callback and
restart it from a syscore resume callback. By resume time,
perf_restore_processor_state() has invalidated the cached PT MSR values,
so the restart path reprograms them.
Normal mode already updates the AUX head when the event is stopped and
starts a new AUX transaction on restart. Snapshot mode requires
additional handling because a stop without PERF_EF_UPDATE leaves the AUX
transaction open and a subsequent restart would normally only re-enable
TraceEn. Save the output offsets during suspend and force the full
configuration path on resume so that the output MSRs are restored from
the saved state. Until resume has reprogrammed the output MSRs, use the
saved offsets so that an NMI (for example an AUX sample) cannot consume
reset PT MSR values.
Stopping the event can queue irq_work when ending the AUX transaction,
for example a wakeup or a deferred disable when the buffer becomes full.
The irq_work self-interrupt may be lost across suspend, but
irq_work_resend() re-raises it during syscore_resume(). The irq_work
then executes normally once interrupts are enabled.
A deferred disable queued during suspend is therefore processed after
the event has been restarted, as it would be without suspend.
Restart tracing only if the event remains in PERF_EVENT_STATE_ACTIVE.
Nothing currently changes the event state between the syscore callbacks,
but this avoids restarting the event if perf core were ever to schedule
it out in the meantime.
This also prevents the CPU that remains online during kexec jump from
continuing to trace into the other kernel image.
s2idle does not lose Intel PT state and does not invoke syscore
callbacks.
Example showing page 0 corruption with S3:
After S3, Intel PT driver samples the trace data (snapshot mode)
by stopping, copying, and restarting the trace without amending
the configuration - but IA32_RTIT_OUTPUT_BASE is 0.
# cat /sys/power/mem_sleep
s2idle [deep]
# dd if=/dev/mem bs=4096 count=1 | od -t x1 -A x
000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
001000
# perf record -C 0 --aux-sample -c100000 -e '{intel_pt//,cycles}' -- rtcwake -m mem -s 10
rtcwake: wakeup from "mem" using /dev/rtc0 at Mon Oct 26 19:17:35 2026
[ perf record: Woken up 6 times to write data ]
[ perf record: Captured and wrote 2.781 MB perf.data ]
# dd if=/dev/mem bs=4096 count=2 | od -t x1 -A x
000000 bc 9c 1e 59 1e 22 59 1f 08 59 20 06 4d 20 c4 97
000010 96 06 4d b0 9f 56 96 59 21 18 5d 8b c3 5f 96 01
000020 53 8a 77 73 59 11 6d 80 00 20 96 ff ff 02 43 00
000030 30 88 00 00 00 e0 90 80 04 59 12 e0 8c 59 13 de
...
Fixes: 52ca9ced3f70 ("perf/x86/intel/pt: Add Intel PT PMU driver")
Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
Cc: stable@vger.kernel.org # c6df51779618: perf/x86/intel/pt: Factor out pt_config_enable()
Cc: stable@vger.kernel.org # 265bb4ef75fa: perf/x86/intel/pt: Use bitwise access for PERF_HES_STOPPED
Cc: stable@vger.kernel.org # 2e17bf3a469a: perf/x86/intel/pt: Fix stop/start with no update
Cc: stable@vger.kernel.org
---
arch/x86/events/intel/pt.c | 60 ++++++++++++++++++++++++++++++++++++++
1 file changed, 60 insertions(+)
diff --git a/arch/x86/events/intel/pt.c b/arch/x86/events/intel/pt.c
index 272808f2b483..1d304ba6d9c6 100644
--- a/arch/x86/events/intel/pt.c
+++ b/arch/x86/events/intel/pt.c
@@ -40,6 +40,8 @@ static bool pt_shutdown;
static bool pt_initialized __ro_after_init;
+static struct perf_event *pt_suspended_event;
+
/*
* Capabilities of Intel PT hardware, such as number of address bits or
* supported output schemes, are cached and exported to userspace as "caps"
@@ -1012,6 +1014,10 @@ static void pt_read_offset(struct pt_buffer *buf)
struct pt *pt = this_cpu_ptr(&pt_ctx);
struct topa_page *tp;
+ /* MSRs may be reset across suspend, so use saved offsets */
+ if (READ_ONCE(pt_suspended_event))
+ return;
+
if (!buf->single) {
rdmsrq(MSR_IA32_RTIT_OUTPUT_BASE, pt->output_base);
tp = phys_to_virt(pt->output_base);
@@ -1659,6 +1665,11 @@ static void pt_event_start(struct perf_event *event, int mode)
*/
if (!(hwc->state & PERF_HES_UPTODATE)) {
hwc->state &= ~PERF_HES_STOPPED;
+ /* After suspend, need to fully re-configure */
+ if (READ_ONCE(pt_suspended_event)) {
+ buf = perf_get_aux(&pt->handle);
+ goto fully_configure;
+ }
pt_config_enable(event);
return;
}
@@ -1675,7 +1686,11 @@ static void pt_event_start(struct perf_event *event, int mode)
hwc->state &= ~(PERF_HES_STOPPED | PERF_HES_UPTODATE);
+fully_configure:
pt_config_buffer(buf);
+ /* Output MSRs are valid again, so pt_read_offset() may read them */
+ if (unlikely(READ_ONCE(pt_suspended_event)))
+ WRITE_ONCE(pt_suspended_event, NULL);
pt_config(event);
return;
@@ -1866,6 +1881,49 @@ static int pt_event_init(struct perf_event *event)
return 0;
}
+/*
+ * The CPU that stays online during system suspend may lose its PT state, so
+ * stop its event beforehand and start it again afterwards. The other CPUs
+ * were taken offline, which stopped their events.
+ */
+static int pt_syscore_suspend(void *data)
+{
+ struct pt *pt = this_cpu_ptr(&pt_ctx);
+ struct perf_event *event = pt->handle.event;
+ struct pt_buffer *buf;
+
+ if (!event)
+ return 0;
+
+ pt_event_stop(event, 0);
+
+ /*
+ * Save the output offsets while the MSRs are still valid, for
+ * pt_event_snapshot_aux() and for reprogramming them on resume.
+ */
+ buf = perf_get_aux(&pt->handle);
+ if (buf && buf->snapshot)
+ pt_read_offset(buf);
+
+ WRITE_ONCE(pt_suspended_event, event);
+
+ return 0;
+}
+
+static void pt_syscore_resume(void *data)
+{
+ struct perf_event *event = pt_suspended_event;
+
+ if (!event)
+ return;
+
+ /* Guard against perf core having scheduled the event out meanwhile */
+ if (event->state == PERF_EVENT_STATE_ACTIVE)
+ pt_event_start(event, 0);
+
+ WRITE_ONCE(pt_suspended_event, NULL);
+}
+
static void pt_shutdown_cpu(void *info)
{
u64 ctl;
@@ -1891,6 +1949,8 @@ static void pt_syscore_shutdown(void *data)
}
static const struct syscore_ops pt_syscore_ops = {
+ .suspend = pm_sleep_ptr(pt_syscore_suspend),
+ .resume = pm_sleep_ptr(pt_syscore_resume),
.shutdown = pt_syscore_shutdown,
};
--
2.53.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [PATCH 1/6] irq_work: Re-raise pending irq_work after system resume
2026-11-02 7:40 ` [PATCH 1/6] irq_work: Re-raise pending irq_work after system resume Adrian Hunter
@ 2026-11-02 9:06 ` Peter Zijlstra
0 siblings, 0 replies; 12+ messages in thread
From: Peter Zijlstra @ 2026-11-02 9:06 UTC (permalink / raw)
To: Adrian Hunter
Cc: Ingo Molnar, Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, James Clark,
Thomas Gleixner, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, Rafael J. Wysocki, Pavel Machek,
Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold,
Sebastian Andrzej Siewior, Mathieu Desnoyers, Jiayuan Chen,
Steven Rostedt (Google), Vineeth Pillai (Google),
Frederic Weisbecker, Andi Kleen, linux-perf-users, linux-kernel,
linux-pm, driver-core
On Mon, Nov 02, 2026 at 09:40:34AM +0200, Adrian Hunter wrote:
> irq_work_queue() raises the irq_work self-interrupt only when adding
> work to an empty raised_list. Once raised_list is non-empty, further
> queueing assumes the interrupt is already pending and will eventually
> process the list.
>
> That assumption can fail across system suspend. irq_work queued after
> arch_suspend_disable_irqs() and before the interrupt controller is
> suspended, for example from NMI context, may leave its self-interrupt
> pending in hardware. If that pending interrupt state is lost during
> suspend, hibernation restore, or kexec jump, raised_list remains
> non-empty after resume but nothing will process it.
>
> Subsequent irq_work added to raised_list will not raise another
> interrupt because the list is already non-empty. Architectures that use
> an irq_work interrupt and do not process raised_list from the tick can
> therefore leave irq_work pending indefinitely. A caller waiting in
> irq_work_sync() may then spin forever, for example when closing a perf
> event whose pending irq_work never executes.
>
> For suspend, hibernation, and kexec jump, only the CPU that remains
> online is affected. The other CPUs are taken offline beforehand, and
> smpcfd_dying_cpu() runs irq_work_run() on each CPU before it is removed,
> so checking the local raised_list is sufficient.
I thought here was a suspend2idle option where all the CPUs remain
online?
Anyway, I'm torn; I would much rather make this trigger a WARN and have
suspend pause/resume perf in a more controlled manner. Also, ISTR there
being hooks in kexec, but I can't seem to find them :/
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH 2/6] perf/x86/intel/pt: Stop tracing if VMXON has not yet happened
2026-11-02 7:40 ` [PATCH 2/6] perf/x86/intel/pt: Stop tracing if VMXON has not yet happened Adrian Hunter
@ 2026-11-02 9:57 ` Peter Zijlstra
0 siblings, 0 replies; 12+ messages in thread
From: Peter Zijlstra @ 2026-11-02 9:57 UTC (permalink / raw)
To: Adrian Hunter
Cc: Ingo Molnar, Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, James Clark,
Thomas Gleixner, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, Rafael J. Wysocki, Pavel Machek,
Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold,
Sebastian Andrzej Siewior, Mathieu Desnoyers, Jiayuan Chen,
Steven Rostedt (Google), Vineeth Pillai (Google),
Frederic Weisbecker, Andi Kleen, linux-perf-users, linux-kernel,
linux-pm, driver-core
On Mon, Nov 02, 2026 at 09:40:35AM +0200, Adrian Hunter wrote:
> arch/x86/events/intel/pt.c | 16 +++++++++++++++-
> 1 file changed, 15 insertions(+), 1 deletion(-)
>
> diff --git a/arch/x86/events/intel/pt.c b/arch/x86/events/intel/pt.c
> index 49349afee611..36bf039668d7 100644
> --- a/arch/x86/events/intel/pt.c
> +++ b/arch/x86/events/intel/pt.c
> @@ -567,6 +567,15 @@ static void pt_config(struct perf_event *event)
> pt_config_enable(event);
> }
>
> +static bool pt_trace_enabled(void)
> +{
> + u64 ctl;
> +
> + rdmsrq(MSR_IA32_RTIT_CTL, ctl);
> +
> + return ctl & RTIT_CTL_TRACEEN;
> +}
> +
> static void pt_config_stop(struct perf_event *event)
> {
> struct pt *pt = this_cpu_ptr(&pt_ctx);
> @@ -577,7 +586,12 @@ static void pt_config_stop(struct perf_event *event)
> return;
>
> ctl &= ~RTIT_CTL_TRACEEN;
> - if (!READ_ONCE(pt->vmx_on))
> + /*
> + * vmx_on is set before VMXON, so tracing may not have stopped yet.
> + * RTIT_CTL can be written until VMXON, and after VMXON TraceEn is
> + * clear, so still write RTIT_CTL if TraceEn is set.
> + */
> + if (!READ_ONCE(pt->vmx_on) || pt_trace_enabled())
> wrmsrq(MSR_IA32_RTIT_CTL, ctl);
>
> WRITE_ONCE(event->hw.aux_config, ctl);
It seems rather unfortunate to issue a RDMSR for state we fully control.
Surely we can track this?
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH 3/6] perf/x86/intel/pt: Stop tracing at syscore shutdown
2026-11-02 7:40 ` [PATCH 3/6] perf/x86/intel/pt: Stop tracing at syscore shutdown Adrian Hunter
@ 2026-11-02 10:04 ` Peter Zijlstra
0 siblings, 0 replies; 12+ messages in thread
From: Peter Zijlstra @ 2026-11-02 10:04 UTC (permalink / raw)
To: Adrian Hunter
Cc: Ingo Molnar, Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, James Clark,
Thomas Gleixner, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, Rafael J. Wysocki, Pavel Machek,
Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold,
Sebastian Andrzej Siewior, Mathieu Desnoyers, Jiayuan Chen,
Steven Rostedt (Google), Vineeth Pillai (Google),
Frederic Weisbecker, Andi Kleen, linux-perf-users, linux-kernel,
linux-pm, driver-core
On Mon, Nov 02, 2026 at 09:40:36AM +0200, Adrian Hunter wrote:
> +static void pt_shutdown_cpu(void *info)
> +{
> + u64 ctl;
> +
> + /*
> + * After VMXON on CPUs without PT support in VMX operation,
> + * TraceEn is already clear and RTIT_CTL must not be written.
> + */
> + rdmsrq(MSR_IA32_RTIT_CTL, ctl);
> + if (ctl & RTIT_CTL_TRACEEN)
> + wrmsrq(MSR_IA32_RTIT_CTL, ctl & ~RTIT_CTL_TRACEEN);
> +}
Why not unconditionally do: wrmsrq(MSR_IA32_RTIT_CTL, 0), at this point?
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH 4/6] perf/x86/intel/pt: Clear TraceEn in hardware in cpu_emergency_stop_pt()
2026-11-02 7:40 ` [PATCH 4/6] perf/x86/intel/pt: Clear TraceEn in hardware in cpu_emergency_stop_pt() Adrian Hunter
@ 2026-11-02 10:07 ` Peter Zijlstra
0 siblings, 0 replies; 12+ messages in thread
From: Peter Zijlstra @ 2026-11-02 10:07 UTC (permalink / raw)
To: Adrian Hunter
Cc: Ingo Molnar, Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, James Clark,
Thomas Gleixner, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, Rafael J. Wysocki, Pavel Machek,
Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold,
Sebastian Andrzej Siewior, Mathieu Desnoyers, Jiayuan Chen,
Steven Rostedt (Google), Vineeth Pillai (Google),
Frederic Weisbecker, Andi Kleen, linux-perf-users, linux-kernel,
linux-pm, driver-core
On Mon, Nov 02, 2026 at 09:40:37AM +0200, Adrian Hunter wrote:
> cpu_emergency_stop_pt() stops tracing through pt_event_stop(), which
> assumes the software state accurately reflects whether tracing is
> enabled. That assumption can fail when a crash NMI interrupts
> pt_config_start().
>
> pt_config_start() sets TraceEn in IA32_RTIT_CTL before updating
> event->hw.aux_config. If a crash NMI arrives in that window,
> pt_config_stop() sees TraceEn clear in aux_config and skips the MSR
> write needed to disable tracing. Tracing remains active in hardware.
>
> Alternatively, an earlier NMI in the same window can stop the event
> without restarting it, for example a group throttle calling
> pt_event_stop(event, 0), or a PT PMI for which perf_aux_output_begin()
> fails. pt_config_stop() again skips the write, so the event is stopped
> only in software: TraceEn remains set in IA32_RTIT_CTL, and
> pt->handle.event becomes NULL. When pt_config_start() continues, it
> sets TraceEn in aux_config, so tracing continues until the event is
> next stopped. If a crash occurs before then, cpu_emergency_stop_pt()
> sees no event and does nothing.
>
> In both cases tracing can remain active into the kdump kernel. Moreover,
> pt_event_stop() may access PT MSRs while tracing is still enabled and
> can trigger a #GP by writing IA32_RTIT_STATUS with TraceEn set.
>
> Fix this by making cpu_emergency_stop_pt() disable tracing directly from
> the hardware state. Set pt_shutdown and invoke pt_shutdown_cpu(), which
> reads IA32_RTIT_CTL and clears TraceEn whenever it is set, independent
> of perf's software state. The shutdown flag prevents any path from
> re-enabling tracing afterwards, including intel_pt_handle_vmx(0) during
> the emergency VMXOFF sequence on other CPUs.
>
> Continue to call pt_event_stop() afterwards so that AUX state, including
> the buffer head, is updated for vmcore collection. Its PT MSR accesses
> now occur with tracing disabled.
>
> Do this only if the driver initialized successfully. In particular, if
> pt_init() finds tracing already enabled at boot, it leaves Intel PT
> alone, and the emergency path must do the same.
>
> The race is reachable only through restart paths that call
> pt_config_start() with TraceEn clear in the software copy. These were
> introduced by the commits with the Fixes tags below. The restart path
> in the PT PMI handler runs in NMI context, so only a machine check can
> interrupt it.
>
> Fixes: 8e105a1fc2a0 ("perf/x86/intel/pt: Factor out pt_config_start()")
> Fixes: 25e8920b301c ("perf/x86/intel/pt: Add sampling support")
> Fixes: 08c7454ceb94 ("perf/x86/intel/pt: Add support for pause / resume")
> Fixes: 2e17bf3a469a ("perf/x86/intel/pt: Fix stop/start with no update")
> Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
> Cc: stable@vger.kernel.org
> ---
> arch/x86/events/intel/pt.c | 9 +++++++++
> 1 file changed, 9 insertions(+)
>
> diff --git a/arch/x86/events/intel/pt.c b/arch/x86/events/intel/pt.c
> index 7453e9880b25..8d71c00f9ff6 100644
> --- a/arch/x86/events/intel/pt.c
> +++ b/arch/x86/events/intel/pt.c
> @@ -38,6 +38,8 @@ static struct pt_pmu pt_pmu;
> /* Set at shutdown, after which TraceEn must not be set */
> static bool pt_shutdown;
>
> +static bool pt_initialized __ro_after_init;
> +
> /*
> * Capabilities of Intel PT hardware, such as number of address bits or
> * supported output schemes, are cached and exported to userspace as "caps"
> @@ -1900,6 +1902,12 @@ void cpu_emergency_stop_pt(void)
> {
> struct pt *pt = this_cpu_ptr(&pt_ctx);
>
> + if (!pt_initialized)
> + return;
> +
> + WRITE_ONCE(pt_shutdown, true);
> + pt_shutdown_cpu(NULL);
> +
> if (pt->handle.event)
> pt_event_stop(pt->handle.event, PERF_EF_UPDATE);
So in general pmu::stop() is reachable from PTI/NMI context, through
things like throttling. So arguably it not functioning correctly when
interleaving with pmu::start() is a problem.
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH 0/6] perf/x86/intel/pt: Fix tracing across kexec, kdump and suspend
2026-11-02 7:40 [PATCH 0/6] perf/x86/intel/pt: Fix tracing across kexec, kdump and suspend Adrian Hunter
` (5 preceding siblings ...)
2026-11-02 7:40 ` [PATCH 6/6] perf/x86/intel/pt: Stop tracing across system suspend Adrian Hunter
@ 2026-11-02 10:10 ` Peter Zijlstra
6 siblings, 0 replies; 12+ messages in thread
From: Peter Zijlstra @ 2026-11-02 10:10 UTC (permalink / raw)
To: Adrian Hunter
Cc: Ingo Molnar, Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, James Clark,
Thomas Gleixner, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, Rafael J. Wysocki, Pavel Machek,
Greg Kroah-Hartman, Danilo Krummrich, Johan Hovold,
Sebastian Andrzej Siewior, Mathieu Desnoyers, Jiayuan Chen,
Steven Rostedt (Google), Vineeth Pillai (Google),
Frederic Weisbecker, Andi Kleen, linux-perf-users, linux-kernel,
linux-pm, driver-core
On Mon, Nov 02, 2026 at 09:40:33AM +0200, Adrian Hunter wrote:
> Hi
>
> Intel PT writes trace data directly to physical memory, so tracing must
> be stopped whenever the trace buffers or the hardware state describing
> them can no longer be relied on. This series fixes several cases where
> that does not currently happen:
Same is true for PEBS, no? I also saw that AMD/IBS is already
registering a syscore handler.
Perhaps it should be a perf wide thing, than than having individual bits
and pieces do their own thing?
^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2026-11-02 10:10 UTC | newest]
Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-02 7:40 [PATCH 0/6] perf/x86/intel/pt: Fix tracing across kexec, kdump and suspend Adrian Hunter
2026-11-02 7:40 ` [PATCH 1/6] irq_work: Re-raise pending irq_work after system resume Adrian Hunter
2026-11-02 9:06 ` Peter Zijlstra
2026-11-02 7:40 ` [PATCH 2/6] perf/x86/intel/pt: Stop tracing if VMXON has not yet happened Adrian Hunter
2026-11-02 9:57 ` Peter Zijlstra
2026-11-02 7:40 ` [PATCH 3/6] perf/x86/intel/pt: Stop tracing at syscore shutdown Adrian Hunter
2026-11-02 10:04 ` Peter Zijlstra
2026-11-02 7:40 ` [PATCH 4/6] perf/x86/intel/pt: Clear TraceEn in hardware in cpu_emergency_stop_pt() Adrian Hunter
2026-11-02 10:07 ` Peter Zijlstra
2026-11-02 7:40 ` [PATCH 5/6] perf/x86/intel/pt: Reset stale MSR state on CPU start and resume Adrian Hunter
2026-11-02 7:40 ` [PATCH 6/6] perf/x86/intel/pt: Stop tracing across system suspend Adrian Hunter
2026-11-02 10:10 ` [PATCH 0/6] perf/x86/intel/pt: Fix tracing across kexec, kdump and suspend Peter Zijlstra
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox