* [PATCH v3 1/9] tracing/osnoise: Variable cleanup
2026-10-14 2:26 [PATCH v3 0/9] tracing/osnoise: Synchronization fixes Crystal Wood
@ 2026-10-14 2:26 ` Crystal Wood
2026-10-14 2:26 ` [PATCH v3 2/9] tracing/osnoise: Hold a ref on osn->kthread Crystal Wood
` (7 subsequent siblings)
8 siblings, 0 replies; 12+ messages in thread
From: Crystal Wood @ 2026-10-14 2:26 UTC (permalink / raw)
To: Steven Rostedt; +Cc: Tomas Glozar, John Kacur, linux-trace-kernel, Crystal Wood
Add cpu_osn_var/cpu_tmr_var() accessors and use them.
Timerlat threads aren't always kthreads, so rename the variable to just
"thread".
rel_period isn't used at all, so remove it.
Signed-off-by: Crystal Wood <crwood@redhat.com>
---
v2: new patch
---
kernel/trace/trace_osnoise.c | 53 +++++++++++++++++++++---------------
1 file changed, 31 insertions(+), 22 deletions(-)
diff --git a/kernel/trace/trace_osnoise.c b/kernel/trace/trace_osnoise.c
index 0e1265acd1cc..953936bd5eec 100644
--- a/kernel/trace/trace_osnoise.c
+++ b/kernel/trace/trace_osnoise.c
@@ -250,6 +250,11 @@ static inline struct osnoise_variables *this_cpu_osn_var(void)
return this_cpu_ptr(&per_cpu_osnoise_var);
}
+static inline struct osnoise_variables *cpu_osn_var(int cpu)
+{
+ return per_cpu_ptr(&per_cpu_osnoise_var, cpu);
+}
+
/*
* Protect the interface.
*/
@@ -260,9 +265,8 @@ static struct mutex interface_lock;
* Runtime information for the timer mode.
*/
struct timerlat_variables {
- struct task_struct *kthread;
+ struct task_struct *thread;
struct hrtimer timer;
- u64 rel_period;
u64 abs_period;
bool tracing_thread;
u64 count;
@@ -284,6 +288,11 @@ static inline struct timerlat_variables *this_cpu_tmr_var(void)
return this_cpu_ptr(&per_cpu_timerlat_var);
}
+static inline struct timerlat_variables *cpu_tmr_var(int cpu)
+{
+ return per_cpu_ptr(&per_cpu_timerlat_var, cpu);
+}
+
/*
* tlat_var_reset - Reset the values of the given timerlat_variables
*/
@@ -300,8 +309,8 @@ static inline void tlat_var_reset(void)
* zeroing the structure is perfect.
*/
for_each_online_cpu(cpu) {
- tlat_var = per_cpu_ptr(&per_cpu_timerlat_var, cpu);
- if (tlat_var->kthread)
+ tlat_var = cpu_tmr_var(cpu);
+ if (tlat_var->thread)
hrtimer_cancel(&tlat_var->timer);
memset(tlat_var, 0, sizeof(*tlat_var));
}
@@ -330,7 +339,7 @@ static inline void osn_var_reset(void)
* zeroing the structure is perfect.
*/
for_each_online_cpu(cpu) {
- osn_var = per_cpu_ptr(&per_cpu_osnoise_var, cpu);
+ osn_var = cpu_osn_var(cpu);
memset(osn_var, 0, sizeof(*osn_var));
}
}
@@ -1226,9 +1235,9 @@ static void trace_sched_migrate_callback(void *data, struct task_struct *p, int
struct osnoise_variables *osn_var;
long cpu = task_cpu(p);
- osn_var = per_cpu_ptr(&per_cpu_osnoise_var, cpu);
+ osn_var = cpu_osn_var(cpu);
if (osn_var->pid == p->pid && dest_cpu != cpu) {
- per_cpu_ptr(&per_cpu_timerlat_var, cpu)->uthread_migrate = 1;
+ cpu_tmr_var(cpu)->uthread_migrate = 1;
osnoise_taint("timerlat user-thread migrated\n");
osnoise_stop_exception("timerlat user-thread migrated", cpu);
}
@@ -1826,13 +1835,13 @@ static enum hrtimer_restart timerlat_irq(struct hrtimer *timer)
osnoise_stop_tracing();
notify_new_max_latency(diff);
- wake_up_process(tlat->kthread);
+ wake_up_process(tlat->thread);
return HRTIMER_NORESTART;
}
}
- wake_up_process(tlat->kthread);
+ wake_up_process(tlat->thread);
if (osnoise_data.print_stack)
timerlat_save_stack(0);
@@ -1925,7 +1934,7 @@ static int timerlat_main(void *data)
tlat->tracing_thread = false;
hrtimer_setup(&tlat->timer, timerlat_irq, CLOCK_MONOTONIC, HRTIMER_MODE_ABS_PINNED_HARD);
- tlat->kthread = current;
+ tlat->thread = current;
osn_var->pid = current->pid;
/*
* Annotate the arrival time.
@@ -1980,7 +1989,7 @@ static void stop_kthread(unsigned int cpu)
{
struct task_struct *kthread;
- kthread = xchg_relaxed(&(per_cpu(per_cpu_osnoise_var, cpu).kthread), NULL);
+ kthread = xchg_relaxed(&cpu_osn_var(cpu)->kthread, NULL);
if (kthread) {
if (cpumask_test_and_clear_cpu(cpu, &kthread_cpumask) &&
!WARN_ON(!test_bit(OSN_WORKLOAD, &osnoise_options))) {
@@ -2000,7 +2009,7 @@ static void stop_kthread(unsigned int cpu)
/*
* This is set in the osnoise tracer case.
*/
- per_cpu(per_cpu_osnoise_var, cpu).sampling = false;
+ cpu_osn_var(cpu)->sampling = false;
barrier();
}
}
@@ -2034,7 +2043,7 @@ static int start_kthread(unsigned int cpu)
char comm[24];
/* Do not start a new thread if it is already running */
- if (per_cpu(per_cpu_osnoise_var, cpu).kthread)
+ if (cpu_osn_var(cpu)->kthread)
return 0;
if (timerlat_enabled()) {
@@ -2043,7 +2052,7 @@ static int start_kthread(unsigned int cpu)
} else {
/* if no workload, just return */
if (!test_bit(OSN_WORKLOAD, &osnoise_options)) {
- per_cpu(per_cpu_osnoise_var, cpu).sampling = true;
+ cpu_osn_var(cpu)->sampling = true;
barrier();
return 0;
}
@@ -2057,7 +2066,7 @@ static int start_kthread(unsigned int cpu)
return -ENOMEM;
}
- per_cpu(per_cpu_osnoise_var, cpu).kthread = kthread;
+ cpu_osn_var(cpu)->kthread = kthread;
cpumask_set_cpu(cpu, &kthread_cpumask);
return 0;
@@ -2090,7 +2099,7 @@ static int start_per_cpu_kthreads(void)
if (cpumask_test_and_clear_cpu(cpu, &kthread_cpumask)) {
struct task_struct *kthread;
- kthread = xchg_relaxed(&(per_cpu(per_cpu_osnoise_var, cpu).kthread), NULL);
+ kthread = xchg_relaxed(&cpu_osn_var(cpu)->kthread, NULL);
if (!WARN_ON(!kthread))
kthread_stop(kthread);
}
@@ -2519,7 +2528,7 @@ timerlat_fd_read(struct file *file, char __user *ubuf, size_t count,
return -EINVAL;
}
} else {
- per_cpu_ptr(&per_cpu_timerlat_var, cpu)->uthread_migrate = 1;
+ cpu_tmr_var(cpu)->uthread_migrate = 1;
osnoise_taint("timerlat user thread migrate\n");
osnoise_stop_tracing();
migrate_enable();
@@ -2563,7 +2572,7 @@ timerlat_fd_read(struct file *file, char __user *ubuf, size_t count,
}
} else {
tlat->tracing_thread = false;
- tlat->kthread = current;
+ tlat->thread = current;
/* Annotate now to drift new period */
tlat->abs_period = hrtimer_cb_get_time(&tlat->timer);
@@ -2612,10 +2621,10 @@ static int timerlat_fd_release(struct inode *inode, struct file *file)
migrate_disable();
mutex_lock(&interface_lock);
- osn_var = per_cpu_ptr(&per_cpu_osnoise_var, cpu);
- tlat_var = per_cpu_ptr(&per_cpu_timerlat_var, cpu);
+ osn_var = cpu_osn_var(cpu);
+ tlat_var = cpu_tmr_var(cpu);
- if (tlat_var->kthread)
+ if (tlat_var->thread)
hrtimer_cancel(&tlat_var->timer);
memset(tlat_var, 0, sizeof(*tlat_var));
@@ -3095,7 +3104,7 @@ static void timerlat_tracer_stop(struct trace_array *tr)
*/
if (!osnoise_has_registered_instances()) {
for_each_online_cpu(cpu)
- per_cpu(per_cpu_osnoise_var, cpu).sampling = 0;
+ cpu_osn_var(cpu)->sampling = 0;
}
osnoise_workload_stop();
--
2.54.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH v3 2/9] tracing/osnoise: Hold a ref on osn->kthread
2026-10-14 2:26 [PATCH v3 0/9] tracing/osnoise: Synchronization fixes Crystal Wood
2026-10-14 2:26 ` [PATCH v3 1/9] tracing/osnoise: Variable cleanup Crystal Wood
@ 2026-10-14 2:26 ` Crystal Wood
2026-10-14 2:26 ` [PATCH v3 3/9] tracing/osnoise: Per-cpu mutexes and fd detachment Crystal Wood
` (6 subsequent siblings)
8 siblings, 0 replies; 12+ messages in thread
From: Crystal Wood @ 2026-10-14 2:26 UTC (permalink / raw)
To: Steven Rostedt; +Cc: Tomas Glozar, John Kacur, linux-trace-kernel, Crystal Wood
Deal with early kthread termination by holding a reference to the
task_struct and always running the cleanup code in stop_kthread(), rather
than with hacks like osnoise_migration_pending().
This avoids a potential deadlock when a later patch switches from xchg
to using a lock to protect osn->kthread updates, and also deals with
any other situations where a kthread might exit early.
Signed-off-by: Crystal Wood <crwood@redhat.com>
---
v2: New patch. I did this in response to Sashiko pointing out a deadlock
in v1 with the use of osn->lock in osnoise_migration_pending. That might
no longer be an issue now that detach_lock is split out, but this is still
the cleaner and more robust approach.
---
kernel/trace/trace_osnoise.c | 17 ++++-------------
1 file changed, 4 insertions(+), 13 deletions(-)
diff --git a/kernel/trace/trace_osnoise.c b/kernel/trace/trace_osnoise.c
index 953936bd5eec..47ede1696c91 100644
--- a/kernel/trace/trace_osnoise.c
+++ b/kernel/trace/trace_osnoise.c
@@ -1691,17 +1691,6 @@ static inline int osnoise_migration_pending(void)
* thus: taint and leave to unblock the blocked thread.
*/
osnoise_taint("migration requested to osnoise threads, leaving.");
-
- /*
- * Unset this thread from the threads managed by the interface.
- * The tracers are responsible for cleaning their env before
- * exiting.
- */
- mutex_lock(&interface_lock);
- this_cpu_osn_var()->kthread = NULL;
- cpumask_clear_cpu(smp_processor_id(), &kthread_cpumask);
- mutex_unlock(&interface_lock);
-
return 1;
}
@@ -2001,8 +1990,8 @@ static void stop_kthread(unsigned int cpu)
* by killing the thread. NOTE: this is a purpose specific file.
*/
kill_pid(kthread->thread_pid, SIGKILL, 1);
- put_task_struct(kthread);
}
+ put_task_struct(kthread);
} else {
/* if no workload, just return */
if (!test_bit(OSN_WORKLOAD, &osnoise_options)) {
@@ -2059,15 +2048,17 @@ static int start_kthread(unsigned int cpu)
snprintf(comm, 24, "osnoise/%d", cpu);
}
- kthread = kthread_run_on_cpu(main, NULL, cpu, comm);
+ kthread = kthread_create_on_cpu(main, NULL, cpu, comm);
if (IS_ERR(kthread)) {
pr_err(BANNER "could not start sampling thread\n");
return -ENOMEM;
}
+ get_task_struct(kthread);
cpu_osn_var(cpu)->kthread = kthread;
cpumask_set_cpu(cpu, &kthread_cpumask);
+ wake_up_process(kthread);
return 0;
}
--
2.54.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH v3 3/9] tracing/osnoise: Per-cpu mutexes and fd detachment
2026-10-14 2:26 [PATCH v3 0/9] tracing/osnoise: Synchronization fixes Crystal Wood
2026-10-14 2:26 ` [PATCH v3 1/9] tracing/osnoise: Variable cleanup Crystal Wood
2026-10-14 2:26 ` [PATCH v3 2/9] tracing/osnoise: Hold a ref on osn->kthread Crystal Wood
@ 2026-10-14 2:26 ` Crystal Wood
2026-10-14 2:26 ` [PATCH v3 4/9] tracing/osnoise: kthreads: Verify CPU after migrate_disable() Crystal Wood
` (5 subsequent siblings)
8 siblings, 0 replies; 12+ messages in thread
From: Crystal Wood @ 2026-10-14 2:26 UTC (permalink / raw)
To: Steven Rostedt; +Cc: Tomas Glozar, John Kacur, linux-trace-kernel, Crystal Wood
Clean up a variety of synchronization issues and related bandaids
by having a per-cpu mutex that guards changes to kthread, and
another than guards fd open/close/revoke.
Replace the SIGKILL hack for userspace timerlat threads (that doesn't
even work, because we don't wait for the process to actually die) with
a mutex-protected detachment mechanism. The mutex should be uncontended
during normal timerlat_fd_read() usage.
This also eliminates the need for the "quick fix" of e6a53481da292
("tracing/timerlat: Only clear timer if a kthread exists").
Signed-off-by: Crystal Wood <crwood@redhat.com>
---
v2:
- Use a separate lock for detachment, so that we can wake the thread that
holds the detach lock, while holding osn->lock to make sure the thread
is valid.
- Return -EINVAL if detached while waiting, and -ERESTARTSYS on a signal
- Init mutex on every possible cpu (as pointed out by Sashiko)
- Update documentation for SIGKILL removal
v3:
- Clear osn_var->sampling in stop_kthread(), as reporteed by Sashiko
for the osnoise non-workload case
---
Documentation/trace/timerlat-tracer.rst | 5 +-
kernel/trace/trace_osnoise.c | 303 ++++++++++++++++--------
2 files changed, 202 insertions(+), 106 deletions(-)
diff --git a/Documentation/trace/timerlat-tracer.rst b/Documentation/trace/timerlat-tracer.rst
index 68d429d454a5..ea94627e821b 100644
--- a/Documentation/trace/timerlat-tracer.rst
+++ b/Documentation/trace/timerlat-tracer.rst
@@ -220,8 +220,9 @@ After reporting the total latency, timerlat will restart the cycle, arm
a timer, and go to sleep for the following activation.
If at any time one of the conditions is broken, e.g., the thread migrates
-while in user space, or the timerlat tracer is disabled, the SIG_KILL
-signal will be sent to the user-space thread.
+while in user space, or the timerlat tracer is disabled, the fd will be
+detached from the tracer, and subsequent usage will fail with EINVAL.
+Note that older kernels may deliver a SIGKILL instead.
Here is an basic example of user-space code for timerlat::
diff --git a/kernel/trace/trace_osnoise.c b/kernel/trace/trace_osnoise.c
index 47ede1696c91..20b0264e5adc 100644
--- a/kernel/trace/trace_osnoise.c
+++ b/kernel/trace/trace_osnoise.c
@@ -222,19 +222,30 @@ struct osn_thread {
u64 delta_start;
};
+struct fd_data {
+ struct task_struct *thread;
+ void (*detach)(struct fd_data *fdd);
+ int cpu;
+};
+
/*
* Runtime information: this structure saves the runtime information used by
* one sampling thread.
*/
struct osnoise_variables {
+ struct mutex lock; /* covers kthread and fdd changes */
struct task_struct *kthread;
- bool sampling;
- pid_t pid;
- struct osn_nmi nmi;
- struct osn_irq irq;
- struct osn_softirq softirq;
- struct osn_thread thread;
- local_t int_counter;
+ struct fd_data *fdd;
+
+ struct_group(zero,
+ bool sampling;
+ pid_t pid;
+ struct osn_nmi nmi;
+ struct osn_irq irq;
+ struct osn_softirq softirq;
+ struct osn_thread thread;
+ local_t int_counter;
+ );
};
/*
@@ -266,11 +277,25 @@ static struct mutex interface_lock;
*/
struct timerlat_variables {
struct task_struct *thread;
- struct hrtimer timer;
- u64 abs_period;
- bool tracing_thread;
- u64 count;
- bool uthread_migrate;
+
+ /*
+ * This is separate from osn->lock in order to allow waking
+ * the waiting thread during forced detach, without having to
+ * wait for the next period (and possibly longer, due to
+ * priority-based lock stealing).
+ *
+ * Both locks must be held during the actual detachment.
+ */
+ struct mutex detach_lock;
+
+ struct_group(zero,
+ struct hrtimer timer;
+ u64 abs_period;
+ bool tracing_thread;
+ u64 count;
+ bool uthread_migrate;
+ bool detach;
+ );
};
static DEFINE_PER_CPU(struct timerlat_variables, per_cpu_timerlat_var);
@@ -301,26 +326,20 @@ static inline void tlat_var_reset(void)
struct timerlat_variables *tlat_var;
int cpu;
- /* Synchronize with the timerlat interfaces */
- mutex_lock(&interface_lock);
-
/*
* So far, all the values are initialized as 0, so
* zeroing the structure is perfect.
*/
for_each_online_cpu(cpu) {
tlat_var = cpu_tmr_var(cpu);
- if (tlat_var->thread)
- hrtimer_cancel(&tlat_var->timer);
- memset(tlat_var, 0, sizeof(*tlat_var));
+ WARN_ON_ONCE(tlat_var->thread);
+ memset(&tlat_var->zero, 0, sizeof(tlat_var->zero));
}
/*
* Reset also align_next, to be filled by a new offset by the first timerlat
* thread that wakes up, if TIMERLAT_ALIGN is set.
*/
atomic64_set(&align_next, 0);
-
- mutex_unlock(&interface_lock);
}
#else /* CONFIG_TIMERLAT_TRACER */
#define tlat_var_reset() do {} while (0)
@@ -340,7 +359,10 @@ static inline void osn_var_reset(void)
*/
for_each_online_cpu(cpu) {
osn_var = cpu_osn_var(cpu);
- memset(osn_var, 0, sizeof(*osn_var));
+
+ WARN_ON(osn_var->kthread);
+ WARN_ON(osn_var->fdd);
+ memset(&osn_var->zero, 0, sizeof(osn_var->zero));
}
}
@@ -1883,10 +1905,18 @@ static int wait_next_period(struct timerlat_variables *tlat)
}
set_current_state(TASK_INTERRUPTIBLE);
+ if (!tlat->detach && !signal_pending(current)) {
+ hrtimer_start(&tlat->timer, next_abs_period, HRTIMER_MODE_ABS_PINNED_HARD);
+ schedule();
+ }
+ __set_current_state(TASK_RUNNING);
- hrtimer_start(&tlat->timer, next_abs_period, HRTIMER_MODE_ABS_PINNED_HARD);
- schedule();
- return 1;
+ if (tlat->detach || signal_pending(current)) {
+ hrtimer_cancel(&tlat->timer);
+ return -1;
+ }
+
+ return 0;
}
/*
@@ -1960,7 +1990,9 @@ static int timerlat_main(void *data)
wait_next_period(tlat);
}
+ WARN_ON_ONCE(hrtimer_active(&tlat->timer));
hrtimer_cancel(&tlat->timer);
+ tlat->thread = NULL;
migrate_enable();
return 0;
}
@@ -1976,32 +2008,24 @@ static int timerlat_main(void *data)
*/
static void stop_kthread(unsigned int cpu)
{
- struct task_struct *kthread;
+ struct osnoise_variables *osn_var = cpu_osn_var(cpu);
- kthread = xchg_relaxed(&cpu_osn_var(cpu)->kthread, NULL);
- if (kthread) {
- if (cpumask_test_and_clear_cpu(cpu, &kthread_cpumask) &&
- !WARN_ON(!test_bit(OSN_WORKLOAD, &osnoise_options))) {
- kthread_stop(kthread);
- } else if (!WARN_ON(test_bit(OSN_WORKLOAD, &osnoise_options))) {
- /*
- * This is a user thread waiting on the timerlat_fd. We need
- * to close all users, and the best way to guarantee this is
- * by killing the thread. NOTE: this is a purpose specific file.
- */
- kill_pid(kthread->thread_pid, SIGKILL, 1);
- }
- put_task_struct(kthread);
- } else {
- /* if no workload, just return */
- if (!test_bit(OSN_WORKLOAD, &osnoise_options)) {
- /*
- * This is set in the osnoise tracer case.
- */
- cpu_osn_var(cpu)->sampling = false;
- barrier();
- }
+ mutex_lock(&osn_var->lock);
+
+ if (osn_var->fdd) {
+ WARN_ON(test_bit(OSN_WORKLOAD, &osnoise_options));
+ WARN_ON(osn_var->kthread);
+ osn_var->fdd->detach(osn_var->fdd);
+ } else if (osn_var->kthread) {
+ WARN_ON(!cpumask_test_and_clear_cpu(cpu, &kthread_cpumask));
+ WARN_ON(!test_bit(OSN_WORKLOAD, &osnoise_options));
+ kthread_stop(osn_var->kthread);
+ put_task_struct(osn_var->kthread);
+ osn_var->kthread = NULL;
}
+
+ osn_var->sampling = false;
+ mutex_unlock(&osn_var->lock);
}
/*
@@ -2027,13 +2051,18 @@ static void stop_per_cpu_kthreads(void)
*/
static int start_kthread(unsigned int cpu)
{
+ struct osnoise_variables *osn = cpu_osn_var(cpu);
struct task_struct *kthread;
void *main = osnoise_main;
char comm[24];
+ int ret = 0;
+
+ lockdep_assert_held(&trace_types_lock);
+ mutex_lock(&osn->lock);
/* Do not start a new thread if it is already running */
- if (cpu_osn_var(cpu)->kthread)
- return 0;
+ if (osn->kthread)
+ goto out;
if (timerlat_enabled()) {
snprintf(comm, 24, "timerlat/%d", cpu);
@@ -2043,7 +2072,7 @@ static int start_kthread(unsigned int cpu)
if (!test_bit(OSN_WORKLOAD, &osnoise_options)) {
cpu_osn_var(cpu)->sampling = true;
barrier();
- return 0;
+ goto out;
}
snprintf(comm, 24, "osnoise/%d", cpu);
}
@@ -2052,15 +2081,18 @@ static int start_kthread(unsigned int cpu)
if (IS_ERR(kthread)) {
pr_err(BANNER "could not start sampling thread\n");
- return -ENOMEM;
+ ret = -ENOMEM;
+ goto out;
}
get_task_struct(kthread);
- cpu_osn_var(cpu)->kthread = kthread;
+ osn->kthread = kthread;
cpumask_set_cpu(cpu, &kthread_cpumask);
wake_up_process(kthread);
- return 0;
+out:
+ mutex_unlock(&osn->lock);
+ return ret;
}
/*
@@ -2086,15 +2118,8 @@ static int start_per_cpu_kthreads(void)
*/
cpumask_and(current_mask, cpu_online_mask, &osnoise_cpumask);
- for_each_possible_cpu(cpu) {
- if (cpumask_test_and_clear_cpu(cpu, &kthread_cpumask)) {
- struct task_struct *kthread;
-
- kthread = xchg_relaxed(&cpu_osn_var(cpu)->kthread, NULL);
- if (!WARN_ON(!kthread))
- kthread_stop(kthread);
- }
- }
+ for_each_possible_cpu(cpu)
+ WARN_ON(cpumask_test_cpu(cpu, &kthread_cpumask));
for_each_cpu(cpu, current_mask) {
retval = start_kthread(cpu);
@@ -2417,11 +2442,48 @@ osnoise_cpus_write(struct file *filp, const char __user *ubuf, size_t count,
}
#ifdef CONFIG_TIMERLAT_TRACER
+static void timerlat_fd_detach(struct fd_data *fdd)
+{
+ struct osnoise_variables *osn;
+ struct timerlat_variables *tlat;
+
+ osn = cpu_osn_var(fdd->cpu);
+ tlat = cpu_tmr_var(fdd->cpu);
+
+ lockdep_assert_held(&osn->lock);
+ if (WARN_ON(fdd != osn->fdd))
+ return;
+
+ WARN_ON(osn->kthread);
+ WARN_ON(fdd->thread != tlat->thread);
+ tlat->detach = true;
+ wake_up_process(tlat->thread);
+
+ mutex_lock(&tlat->detach_lock);
+
+ memset(tlat, 0, sizeof(*tlat));
+
+ osn->sampling = 0;
+ osn->pid = 0;
+
+ WARN_ON_ONCE(hrtimer_active(&tlat->timer));
+ hrtimer_cancel(&tlat->timer);
+ put_task_struct(fdd->thread);
+
+ fdd->thread = NULL;
+ tlat->thread = NULL;
+ osn->fdd = NULL;
+
+ mutex_unlock(&tlat->detach_lock);
+}
+
static int timerlat_fd_open(struct inode *inode, struct file *file)
{
+ struct fd_data *fdd;
struct osnoise_variables *osn_var;
struct timerlat_variables *tlat;
long cpu = (long) inode->i_cdev;
+ int ret = 0;
mutex_lock(&interface_lock);
@@ -2437,16 +2499,15 @@ static int timerlat_fd_open(struct inode *inode, struct file *file)
migrate_disable();
osn_var = this_cpu_osn_var();
+ mutex_lock(&osn_var->lock);
- /*
- * The osn_var->pid holds the single access to this file.
- */
- if (osn_var->pid) {
- mutex_unlock(&interface_lock);
- migrate_enable();
- return -EBUSY;
+ if (osn_var->fdd) {
+ ret = -EBUSY;
+ goto err;
}
+ WARN_ON_ONCE(osn_var->kthread);
+
/*
* timerlat tracer is a per-cpu tracer. Check if the user-space too
* is pinned to a single CPU. The tracer laters monitor if the task
@@ -2455,24 +2516,33 @@ static int timerlat_fd_open(struct inode *inode, struct file *file)
* setup.
*/
if (current->nr_cpus_allowed > 1 || cpu != smp_processor_id()) {
- mutex_unlock(&interface_lock);
- migrate_enable();
- return -EPERM;
+ ret = -EPERM;
+ goto err;
+ }
+
+ fdd = kmalloc(sizeof(*fdd), GFP_KERNEL);
+ if (!fdd) {
+ ret = -ENOMEM;
+ goto err;
}
/*
* From now on, it is good to go.
*/
- file->private_data = inode->i_cdev;
+ fdd->thread = current;
+ fdd->detach = timerlat_fd_detach;
+ fdd->cpu = cpu;
+ file->private_data = fdd;
get_task_struct(current);
- osn_var->kthread = current;
osn_var->pid = current->pid;
+ osn_var->fdd = fdd;
/*
* Setup is done.
*/
+ mutex_unlock(&osn_var->lock);
mutex_unlock(&interface_lock);
tlat = this_cpu_tmr_var();
@@ -2482,6 +2552,12 @@ static int timerlat_fd_open(struct inode *inode, struct file *file)
migrate_enable();
return 0;
+
+err:
+ mutex_unlock(&osn_var->lock);
+ mutex_unlock(&interface_lock);
+ migrate_enable();
+ return ret;
};
/*
@@ -2497,12 +2573,13 @@ static ssize_t
timerlat_fd_read(struct file *file, char __user *ubuf, size_t count,
loff_t *ppos)
{
- long cpu = (long) file->private_data;
+ struct fd_data *fdd = file->private_data;
struct osnoise_variables *osn_var;
struct timerlat_variables *tlat;
struct timerlat_sample s;
s64 diff;
u64 now;
+ int ret = 0;
migrate_disable();
@@ -2513,13 +2590,13 @@ timerlat_fd_read(struct file *file, char __user *ubuf, size_t count,
* we can do about it.
* So, if the thread is running on another CPU, stop the machinery.
*/
- if (cpu == smp_processor_id()) {
+ if (fdd->cpu == smp_processor_id()) {
if (tlat->uthread_migrate) {
migrate_enable();
return -EINVAL;
}
} else {
- cpu_tmr_var(cpu)->uthread_migrate = 1;
+ cpu_tmr_var(fdd->cpu)->uthread_migrate = 1;
osnoise_taint("timerlat user thread migrate\n");
osnoise_stop_tracing();
migrate_enable();
@@ -2528,6 +2605,13 @@ timerlat_fd_read(struct file *file, char __user *ubuf, size_t count,
osn_var = this_cpu_osn_var();
+ /* In normal usage, this should always be uncontended. */
+ mutex_lock(&tlat->detach_lock);
+ if (fdd != osn_var->fdd || current != fdd->thread) {
+ ret = -EINVAL;
+ goto out;
+ }
+
/*
* The timerlat in user-space runs in a different order:
* the read() starts from the execution of the previous occurrence,
@@ -2572,7 +2656,19 @@ timerlat_fd_read(struct file *file, char __user *ubuf, size_t count,
}
/* wait for the next period */
- wait_next_period(tlat);
+ if (wait_next_period(tlat) < 0) {
+ if (signal_pending(current))
+ ret = -ERESTARTSYS;
+ else
+ ret = -EINVAL;
+
+ goto out;
+ }
+
+ if (WARN_ON_ONCE(fdd != osn_var->fdd)) {
+ ret = -EINVAL;
+ goto out;
+ }
/* This is the wakeup from this cycle */
now = ktime_to_ns(hrtimer_cb_get_time(&tlat->timer));
@@ -2599,39 +2695,24 @@ timerlat_fd_read(struct file *file, char __user *ubuf, size_t count,
}
out:
+ mutex_unlock(&tlat->detach_lock);
migrate_enable();
- return 0;
+ return ret;
}
static int timerlat_fd_release(struct inode *inode, struct file *file)
{
+ struct fd_data *fdd = file->private_data;
struct osnoise_variables *osn_var;
- struct timerlat_variables *tlat_var;
- long cpu = (long) file->private_data;
- migrate_disable();
- mutex_lock(&interface_lock);
+ osn_var = cpu_osn_var(fdd->cpu);
- osn_var = cpu_osn_var(cpu);
- tlat_var = cpu_tmr_var(cpu);
-
- if (tlat_var->thread)
- hrtimer_cancel(&tlat_var->timer);
- memset(tlat_var, 0, sizeof(*tlat_var));
-
- osn_var->sampling = 0;
- osn_var->pid = 0;
-
- /*
- * We are leaving, not being stopped... see stop_kthread();
- */
- if (osn_var->kthread) {
- put_task_struct(osn_var->kthread);
- osn_var->kthread = NULL;
- }
+ mutex_lock(&osn_var->lock);
+ if (fdd == osn_var->fdd)
+ timerlat_fd_detach(fdd);
+ mutex_unlock(&osn_var->lock);
- mutex_unlock(&interface_lock);
- migrate_enable();
+ kfree(fdd);
return 0;
}
#endif
@@ -3146,6 +3227,14 @@ static struct tracer timerlat_tracer __read_mostly = {
__init static int init_timerlat_tracer(void)
{
+ int cpu;
+
+ for_each_possible_cpu(cpu) {
+ struct timerlat_variables *tlat = cpu_tmr_var(cpu);
+
+ mutex_init(&tlat->detach_lock);
+ }
+
return register_tracer(&timerlat_tracer);
}
#else /* CONFIG_TIMERLAT_TRACER */
@@ -3157,10 +3246,16 @@ __init static int init_timerlat_tracer(void)
__init static int init_osnoise_tracer(void)
{
- int ret;
+ int ret, cpu;
mutex_init(&interface_lock);
+ for_each_possible_cpu(cpu) {
+ struct osnoise_variables *osn = cpu_osn_var(cpu);
+
+ mutex_init(&osn->lock);
+ }
+
cpumask_copy(&osnoise_cpumask, cpu_all_mask);
ret = register_tracer(&osnoise_tracer);
--
2.54.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH v3 4/9] tracing/osnoise: kthreads: Verify CPU after migrate_disable()
2026-10-14 2:26 [PATCH v3 0/9] tracing/osnoise: Synchronization fixes Crystal Wood
` (2 preceding siblings ...)
2026-10-14 2:26 ` [PATCH v3 3/9] tracing/osnoise: Per-cpu mutexes and fd detachment Crystal Wood
@ 2026-10-14 2:26 ` Crystal Wood
2026-10-14 2:26 ` [PATCH v3 5/9] tracing/osnoise: start_kthread: Always check OSN_WORKLOAD Crystal Wood
` (4 subsequent siblings)
8 siblings, 0 replies; 12+ messages in thread
From: Crystal Wood @ 2026-10-14 2:26 UTC (permalink / raw)
To: Steven Rostedt; +Cc: Tomas Glozar, John Kacur, linux-trace-kernel, Crystal Wood
Pass osn vars as the kthread arg, and make sure it matches where ve're
running in case of (e.g.) hotplug issues.
Signed-off-by: Crystal Wood <crwood@redhat.com>
---
v2:
- Do it for osnoise too, not just timerlat
- I haven't been able to reproduce this in a while, and I'm not seeing the
hole in the hotplug sync that would allow it, but I also don't see what
would have allowed it in v1 (maybe it was in some pre-v1 iteration? or
maybe just a tiny race window).
So we can maybe drop this patch, though it still feels better to explicitly
splat if problems happen rather than rely on this_cpu_osn_var() blowing up
due to the affinity being set to multiple cpus.
---
kernel/trace/trace_osnoise.c | 21 ++++++++++++++++++---
1 file changed, 18 insertions(+), 3 deletions(-)
diff --git a/kernel/trace/trace_osnoise.c b/kernel/trace/trace_osnoise.c
index 20b0264e5adc..202d7b4254bd 100644
--- a/kernel/trace/trace_osnoise.c
+++ b/kernel/trace/trace_osnoise.c
@@ -236,6 +236,7 @@ struct osnoise_variables {
struct mutex lock; /* covers kthread and fdd changes */
struct task_struct *kthread;
struct fd_data *fdd;
+ int cpu;
struct_group(zero,
bool sampling;
@@ -1724,6 +1725,7 @@ static inline int osnoise_migration_pending(void)
*/
static int osnoise_main(void *data)
{
+ struct osnoise_variables *osn = data;
unsigned long flags;
/*
@@ -1734,6 +1736,11 @@ static int osnoise_main(void *data)
* flag.
*/
migrate_disable();
+ if (WARN_ON_ONCE(osn->cpu != smp_processor_id())) {
+ migrate_enable();
+ return 1;
+ }
+
raw_spin_lock_irqsave(¤t->pi_lock, flags);
current->flags &= ~(PF_NO_SETAFFINITY);
raw_spin_unlock_irqrestore(¤t->pi_lock, flags);
@@ -1924,8 +1931,8 @@ static int wait_next_period(struct timerlat_variables *tlat)
*/
static int timerlat_main(void *data)
{
- struct osnoise_variables *osn_var = this_cpu_osn_var();
- struct timerlat_variables *tlat = this_cpu_tmr_var();
+ struct osnoise_variables *osn_var = data;
+ struct timerlat_variables *tlat;
struct timerlat_sample s;
struct sched_param sp;
unsigned long flags;
@@ -1945,6 +1952,13 @@ static int timerlat_main(void *data)
* flag.
*/
migrate_disable();
+ if (WARN_ON_ONCE(osn_var->cpu != smp_processor_id())) {
+ migrate_enable();
+ return 1;
+ }
+
+ tlat = this_cpu_tmr_var();
+
raw_spin_lock_irqsave(¤t->pi_lock, flags);
current->flags &= ~(PF_NO_SETAFFINITY);
raw_spin_unlock_irqrestore(¤t->pi_lock, flags);
@@ -2077,7 +2091,7 @@ static int start_kthread(unsigned int cpu)
snprintf(comm, 24, "osnoise/%d", cpu);
}
- kthread = kthread_create_on_cpu(main, NULL, cpu, comm);
+ kthread = kthread_create_on_cpu(main, cpu_osn_var(cpu), cpu, comm);
if (IS_ERR(kthread)) {
pr_err(BANNER "could not start sampling thread\n");
@@ -3254,6 +3268,7 @@ __init static int init_osnoise_tracer(void)
struct osnoise_variables *osn = cpu_osn_var(cpu);
mutex_init(&osn->lock);
+ osn->cpu = cpu;
}
cpumask_copy(&osnoise_cpumask, cpu_all_mask);
--
2.54.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH v3 5/9] tracing/osnoise: start_kthread: Always check OSN_WORKLOAD
2026-10-14 2:26 [PATCH v3 0/9] tracing/osnoise: Synchronization fixes Crystal Wood
` (3 preceding siblings ...)
2026-10-14 2:26 ` [PATCH v3 4/9] tracing/osnoise: kthreads: Verify CPU after migrate_disable() Crystal Wood
@ 2026-10-14 2:26 ` Crystal Wood
2026-10-14 2:26 ` [PATCH v3 6/9] tracing/osnoise: Take trace_types_lock in timerlat_fd_open Crystal Wood
` (3 subsequent siblings)
8 siblings, 0 replies; 12+ messages in thread
From: Crystal Wood @ 2026-10-14 2:26 UTC (permalink / raw)
To: Steven Rostedt; +Cc: Tomas Glozar, John Kacur, linux-trace-kernel, Crystal Wood
We can get here on timerlat with OSN_WORKLOAD cleared via
osnoise_hotplug_workfn().
While we're moving this code, remove the pointless compiler barrier
immediately before returning from a function. Even if it's inlined,
it's not doing anything useful.
Signed-off-by: Crystal Wood <crwood@redhat.com>
---
v2: no change
---
kernel/trace/trace_osnoise.c | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/kernel/trace/trace_osnoise.c b/kernel/trace/trace_osnoise.c
index 202d7b4254bd..3da4f1f095df 100644
--- a/kernel/trace/trace_osnoise.c
+++ b/kernel/trace/trace_osnoise.c
@@ -2078,16 +2078,17 @@ static int start_kthread(unsigned int cpu)
if (osn->kthread)
goto out;
+ /* Can still happen with timerlat via cpu hotplug */
+ if (!test_bit(OSN_WORKLOAD, &osnoise_options)) {
+ if (!timerlat_enabled())
+ osn->sampling = true;
+ goto out;
+ }
+
if (timerlat_enabled()) {
snprintf(comm, 24, "timerlat/%d", cpu);
main = timerlat_main;
} else {
- /* if no workload, just return */
- if (!test_bit(OSN_WORKLOAD, &osnoise_options)) {
- cpu_osn_var(cpu)->sampling = true;
- barrier();
- goto out;
- }
snprintf(comm, 24, "osnoise/%d", cpu);
}
--
2.54.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH v3 6/9] tracing/osnoise: Take trace_types_lock in timerlat_fd_open
2026-10-14 2:26 [PATCH v3 0/9] tracing/osnoise: Synchronization fixes Crystal Wood
` (4 preceding siblings ...)
2026-10-14 2:26 ` [PATCH v3 5/9] tracing/osnoise: start_kthread: Always check OSN_WORKLOAD Crystal Wood
@ 2026-10-14 2:26 ` Crystal Wood
2026-10-14 2:26 ` [PATCH v3 7/9] tracing/osnoise: Split out per-cpu variable resets Crystal Wood
` (2 subsequent siblings)
8 siblings, 0 replies; 12+ messages in thread
From: Crystal Wood @ 2026-10-14 2:26 UTC (permalink / raw)
To: Steven Rostedt; +Cc: Tomas Glozar, John Kacur, linux-trace-kernel, Crystal Wood
This prevents a user fd from being opened when osnoise_options_write()
has already stopped threads but not yet set OSN_WORKLOAD, and matches what
all callers of start_kthread() do.
The original intent was probably for interface_lock to cover things like
this, but it never covered the full race window, and currently can't
due to lock order issues stemming from using interface_lock in
osnoise_sleep(). There does not appear to be any remaining reason
to take interface_lock in timerlat_fd_open(), so remove it.
Signed-off-by: Crystal Wood <crwood@redhat.com>
---
v2: convert rest of function to guards
Do we really need that interface_lock in osnoise_sleep()? Technically
we should probably be grabbing it every time we access osnoise_data, but
we don't seem to do so anywhere else. Perhaps this was due to concerns
about the atomicity of "period - runtime", but that's just synchronization
theater given that userspace has no way of writing to both atomically.
In any case, we shouldn't use the same lock for protecting osnoise_data
members that we use for major reconfiguration procedures.
---
kernel/trace/trace_osnoise.c | 43 +++++++++---------------------------
1 file changed, 10 insertions(+), 33 deletions(-)
diff --git a/kernel/trace/trace_osnoise.c b/kernel/trace/trace_osnoise.c
index 3da4f1f095df..22263e08b968 100644
--- a/kernel/trace/trace_osnoise.c
+++ b/kernel/trace/trace_osnoise.c
@@ -2498,28 +2498,23 @@ static int timerlat_fd_open(struct inode *inode, struct file *file)
struct osnoise_variables *osn_var;
struct timerlat_variables *tlat;
long cpu = (long) inode->i_cdev;
- int ret = 0;
- mutex_lock(&interface_lock);
+ guard(mutex)(&trace_types_lock);
/*
* This file is accessible only if timerlat is enabled, and
* NO_OSNOISE_WORKLOAD is set.
*/
- if (!timerlat_enabled() || test_bit(OSN_WORKLOAD, &osnoise_options)) {
- mutex_unlock(&interface_lock);
+ if (!timerlat_enabled() || test_bit(OSN_WORKLOAD, &osnoise_options))
return -EINVAL;
- }
- migrate_disable();
+ guard(migrate)();
osn_var = this_cpu_osn_var();
- mutex_lock(&osn_var->lock);
+ guard(mutex)(&osn_var->lock);
- if (osn_var->fdd) {
- ret = -EBUSY;
- goto err;
- }
+ if (osn_var->fdd)
+ return -EBUSY;
WARN_ON_ONCE(osn_var->kthread);
@@ -2530,16 +2525,12 @@ static int timerlat_fd_open(struct inode *inode, struct file *file)
* worth doing this basic acceptance test to avoid obviusly wrong
* setup.
*/
- if (current->nr_cpus_allowed > 1 || cpu != smp_processor_id()) {
- ret = -EPERM;
- goto err;
- }
+ if (current->nr_cpus_allowed > 1 || cpu != smp_processor_id())
+ return -EPERM;
fdd = kmalloc(sizeof(*fdd), GFP_KERNEL);
- if (!fdd) {
- ret = -ENOMEM;
- goto err;
- }
+ if (!fdd)
+ return -ENOMEM;
/*
* From now on, it is good to go.
@@ -2554,25 +2545,11 @@ static int timerlat_fd_open(struct inode *inode, struct file *file)
osn_var->pid = current->pid;
osn_var->fdd = fdd;
- /*
- * Setup is done.
- */
- mutex_unlock(&osn_var->lock);
- mutex_unlock(&interface_lock);
-
tlat = this_cpu_tmr_var();
tlat->count = 0;
hrtimer_setup(&tlat->timer, timerlat_irq, CLOCK_MONOTONIC, HRTIMER_MODE_ABS_PINNED_HARD);
-
- migrate_enable();
return 0;
-
-err:
- mutex_unlock(&osn_var->lock);
- mutex_unlock(&interface_lock);
- migrate_enable();
- return ret;
};
/*
--
2.54.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH v3 7/9] tracing/osnoise: Split out per-cpu variable resets
2026-10-14 2:26 [PATCH v3 0/9] tracing/osnoise: Synchronization fixes Crystal Wood
` (5 preceding siblings ...)
2026-10-14 2:26 ` [PATCH v3 6/9] tracing/osnoise: Take trace_types_lock in timerlat_fd_open Crystal Wood
@ 2026-10-14 2:26 ` Crystal Wood
2026-10-14 2:39 ` sashiko-bot
2026-10-14 2:26 ` [PATCH v3 8/9] tracing/osnoise: Print taint message for timerlat Crystal Wood
2026-10-14 2:26 ` [PATCH v3 9/9] tracing/osnoise: Refuse timerlat fd open if tracer inactive Crystal Wood
8 siblings, 1 reply; 12+ messages in thread
From: Crystal Wood @ 2026-10-14 2:26 UTC (permalink / raw)
To: Steven Rostedt; +Cc: Tomas Glozar, John Kacur, linux-trace-kernel, Crystal Wood
Reset per-cpu state every time we stop a kthread or detach an fd, rather
than assume everything gets cleaned up properly, and avoiding the need
to explicitly zero individual fields.
Also, stop initializing tlat->thread lazily, add some debug checks to
detect an unclean state, and clear osnoise_data.tainted in
osn_var_reset().
Signed-off-by: Crystal Wood <crwood@redhat.com>
---
v2: New patch. Without this, I was seeing sampling = 1 leaking from a
kthread context to an fd context, which confused the latter into
operating with tlat->thread set to the old kernel thread, causing the fd
thread to hang.
v3: Reset the per-cpu context unconditionally in stop_kthread(), so sampling
gets cleared as noted in patch 3/9
---
kernel/trace/trace_osnoise.c | 109 +++++++++++++++++------------------
1 file changed, 52 insertions(+), 57 deletions(-)
diff --git a/kernel/trace/trace_osnoise.c b/kernel/trace/trace_osnoise.c
index 22263e08b968..d799c400ed9b 100644
--- a/kernel/trace/trace_osnoise.c
+++ b/kernel/trace/trace_osnoise.c
@@ -319,23 +319,19 @@ static inline struct timerlat_variables *cpu_tmr_var(int cpu)
return per_cpu_ptr(&per_cpu_timerlat_var, cpu);
}
+static inline void tlat_reset_cpu(int cpu)
+{
+ struct timerlat_variables *tlat = cpu_tmr_var(cpu);
+
+ WARN_ON_ONCE(tlat->thread);
+ memset(&tlat->zero, 0, sizeof(tlat->zero));
+}
+
/*
- * tlat_var_reset - Reset the values of the given timerlat_variables
+ * tlat_var_reset - Reset non-per-cpu state
*/
static inline void tlat_var_reset(void)
{
- struct timerlat_variables *tlat_var;
- int cpu;
-
- /*
- * So far, all the values are initialized as 0, so
- * zeroing the structure is perfect.
- */
- for_each_online_cpu(cpu) {
- tlat_var = cpu_tmr_var(cpu);
- WARN_ON_ONCE(tlat_var->thread);
- memset(&tlat_var->zero, 0, sizeof(tlat_var->zero));
- }
/*
* Reset also align_next, to be filled by a new offset by the first timerlat
* thread that wakes up, if TIMERLAT_ALIGN is set.
@@ -343,38 +339,12 @@ static inline void tlat_var_reset(void)
atomic64_set(&align_next, 0);
}
#else /* CONFIG_TIMERLAT_TRACER */
-#define tlat_var_reset() do {} while (0)
-#endif /* CONFIG_TIMERLAT_TRACER */
-
-/*
- * osn_var_reset - Reset the values of the given osnoise_variables
- */
-static inline void osn_var_reset(void)
+static inline void tlat_reset_cpu(int cpu)
{
- struct osnoise_variables *osn_var;
- int cpu;
-
- /*
- * So far, all the values are initialized as 0, so
- * zeroing the structure is perfect.
- */
- for_each_online_cpu(cpu) {
- osn_var = cpu_osn_var(cpu);
-
- WARN_ON(osn_var->kthread);
- WARN_ON(osn_var->fdd);
- memset(&osn_var->zero, 0, sizeof(osn_var->zero));
- }
}
-/*
- * osn_var_reset_all - Reset the value of all per-cpu osnoise_variables
- */
-static inline void osn_var_reset_all(void)
-{
- osn_var_reset();
- tlat_var_reset();
-}
+#define tlat_var_reset() do {} while (0)
+#endif /* CONFIG_TIMERLAT_TRACER */
/*
* Tells NMIs to call back to the osnoise tracer to record timestamps.
@@ -409,6 +379,36 @@ static struct osnoise_data {
#endif
};
+static inline void osn_reset_cpu(int cpu)
+{
+ struct osnoise_variables *osn = cpu_osn_var(cpu);
+
+ WARN_ON(osn->kthread);
+ WARN_ON(osn->fdd);
+ memset(&osn->zero, 0, sizeof(osn->zero));
+ tlat_reset_cpu(cpu);
+}
+
+/*
+ * osn_var_reset - Reset non-per-cpu state, and check for stale data
+ */
+static inline void osn_var_reset(void)
+{
+ struct osnoise_variables *osn;
+ int cpu;
+
+ for_each_possible_cpu(cpu) {
+ osn = cpu_osn_var(cpu);
+
+ WARN_ON(osn->kthread);
+ WARN_ON(osn->fdd);
+ WARN_ON(osn->sampling);
+ }
+
+ tlat_var_reset();
+ osnoise_data.tainted = false;
+}
+
#ifdef CONFIG_TIMERLAT_TRACER
static inline bool timerlat_enabled(void)
{
@@ -2038,7 +2038,7 @@ static void stop_kthread(unsigned int cpu)
osn_var->kthread = NULL;
}
- osn_var->sampling = false;
+ osn_reset_cpu(cpu);
mutex_unlock(&osn_var->lock);
}
@@ -2469,18 +2469,12 @@ static void timerlat_fd_detach(struct fd_data *fdd)
if (WARN_ON(fdd != osn->fdd))
return;
- WARN_ON(osn->kthread);
WARN_ON(fdd->thread != tlat->thread);
tlat->detach = true;
wake_up_process(tlat->thread);
mutex_lock(&tlat->detach_lock);
- memset(tlat, 0, sizeof(*tlat));
-
- osn->sampling = 0;
- osn->pid = 0;
-
WARN_ON_ONCE(hrtimer_active(&tlat->timer));
hrtimer_cancel(&tlat->timer);
put_task_struct(fdd->thread);
@@ -2490,6 +2484,7 @@ static void timerlat_fd_detach(struct fd_data *fdd)
osn->fdd = NULL;
mutex_unlock(&tlat->detach_lock);
+
}
static int timerlat_fd_open(struct inode *inode, struct file *file)
@@ -2546,7 +2541,11 @@ static int timerlat_fd_open(struct inode *inode, struct file *file)
osn_var->fdd = fdd;
tlat = this_cpu_tmr_var();
- tlat->count = 0;
+ WARN_ON(tlat->thread);
+ WARN_ON(tlat->count != 0);
+ WARN_ON(tlat->tracing_thread);
+ WARN_ON(osn_var->sampling);
+ tlat->thread = current;
hrtimer_setup(&tlat->timer, timerlat_irq, CLOCK_MONOTONIC, HRTIMER_MODE_ABS_PINNED_HARD);
return 0;
@@ -2638,9 +2637,6 @@ timerlat_fd_read(struct file *file, char __user *ubuf, size_t count,
}
}
} else {
- tlat->tracing_thread = false;
- tlat->thread = current;
-
/* Annotate now to drift new period */
tlat->abs_period = hrtimer_cb_get_time(&tlat->timer);
@@ -2695,13 +2691,12 @@ timerlat_fd_read(struct file *file, char __user *ubuf, size_t count,
static int timerlat_fd_release(struct inode *inode, struct file *file)
{
struct fd_data *fdd = file->private_data;
- struct osnoise_variables *osn_var;
-
- osn_var = cpu_osn_var(fdd->cpu);
+ struct osnoise_variables *osn_var = cpu_osn_var(fdd->cpu);
mutex_lock(&osn_var->lock);
if (fdd == osn_var->fdd)
timerlat_fd_detach(fdd);
+ osn_reset_cpu(fdd->cpu);
mutex_unlock(&osn_var->lock);
kfree(fdd);
@@ -3015,7 +3010,7 @@ static int osnoise_workload_start(void)
if (osnoise_has_registered_instances())
return 0;
- osn_var_reset_all();
+ osn_var_reset();
retval = osnoise_hook_events();
if (retval)
--
2.54.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* Re: [PATCH v3 7/9] tracing/osnoise: Split out per-cpu variable resets
2026-10-14 2:26 ` [PATCH v3 7/9] tracing/osnoise: Split out per-cpu variable resets Crystal Wood
@ 2026-10-14 2:39 ` sashiko-bot
0 siblings, 0 replies; 12+ messages in thread
From: sashiko-bot @ 2026-10-14 2:39 UTC (permalink / raw)
To: Crystal Wood; +Cc: linux-trace-kernel
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] tracing/osnoise: unconditional osn_reset_cpu corrupts active tracer state
--
commit e7bd03b79c8b047561cc86479c9cbb434768ea94
Author: Crystal Wood <crwood@redhat.com>
tracing/osnoise: Split out per-cpu variable resets
Reset per-cpu state every time we stop a kthread or detach an fd, rather
than assume everything gets cleaned up properly, and avoiding the need
to explicitly zero individual fields.
> diff --git a/kernel/trace/trace_osnoise.c b/kernel/trace/trace_osnoise.c
> index 22263e08b968b..d799c400ed9b7 100644
> --- a/kernel/trace/trace_osnoise.c
> +++ b/kernel/trace/trace_osnoise.c
[ ... ]
> @@ -2695,13 +2691,12 @@ timerlat_fd_read(struct file *file, char __user *ubuf, size_t count,
> static int timerlat_fd_release(struct inode *inode, struct file *file)
> {
> struct fd_data *fdd = file->private_data;
> - struct osnoise_variables *osn_var;
> -
> - osn_var = cpu_osn_var(fdd->cpu);
> + struct osnoise_variables *osn_var = cpu_osn_var(fdd->cpu);
>
> mutex_lock(&osn_var->lock);
> if (fdd == osn_var->fdd)
> timerlat_fd_detach(fdd);
> + osn_reset_cpu(fdd->cpu);
[Severity: High]
Does this unconditionally call osn_reset_cpu() even when the file descriptor
was already detached?
If tracing is stopped and the active file descriptor is detached, userspace
can start a new tracing session. This sets osn_var->fdd to a new fdd and arms
a new hrtimer on the CPU.
If userspace subsequently closes the old, detached file descriptor,
timerlat_fd_release() executes, skips timerlat_fd_detach(), and
unconditionally calls osn_reset_cpu().
Could this trigger a WARN_ON(osn->fdd) and zero out the tlat->zero and
osn->zero structs of the newly active session, corrupting its queued hrtimer
and potentially causing memory corruption or a kernel panic?
> mutex_unlock(&osn_var->lock);
>
> kfree(fdd);
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261014022640.695786-1-crwood@redhat.com?part=7
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v3 8/9] tracing/osnoise: Print taint message for timerlat
2026-10-14 2:26 [PATCH v3 0/9] tracing/osnoise: Synchronization fixes Crystal Wood
` (6 preceding siblings ...)
2026-10-14 2:26 ` [PATCH v3 7/9] tracing/osnoise: Split out per-cpu variable resets Crystal Wood
@ 2026-10-14 2:26 ` Crystal Wood
2026-10-14 2:26 ` [PATCH v3 9/9] tracing/osnoise: Refuse timerlat fd open if tracer inactive Crystal Wood
8 siblings, 0 replies; 12+ messages in thread
From: Crystal Wood @ 2026-10-14 2:26 UTC (permalink / raw)
To: Steven Rostedt; +Cc: Tomas Glozar, John Kacur, linux-trace-kernel, Crystal Wood
Provide taint information the same as is done in print_osnoise_headers().
Signed-off-by: Crystal Wood <crwood@redhat.com>
---
v2: new patch
---
kernel/trace/trace_osnoise.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/kernel/trace/trace_osnoise.c b/kernel/trace/trace_osnoise.c
index d799c400ed9b..9ca45b0b8cb3 100644
--- a/kernel/trace/trace_osnoise.c
+++ b/kernel/trace/trace_osnoise.c
@@ -577,6 +577,9 @@ static void record_osnoise_sample(struct osnoise_sample *sample)
#ifdef CONFIG_PREEMPT_RT
static void print_timerlat_headers(struct seq_file *s)
{
+ if (osnoise_data.tainted)
+ seq_puts(s, "# timerlat is tainted!\n");
+
seq_puts(s, "# _-------=> irqs-off\n");
seq_puts(s, "# / _------=> need-resched\n");
seq_puts(s, "# | / _-----=> need-resched-lazy\n");
@@ -594,6 +597,9 @@ static void print_timerlat_headers(struct seq_file *s)
#else /* CONFIG_PREEMPT_RT */
static void print_timerlat_headers(struct seq_file *s)
{
+ if (osnoise_data.tainted)
+ seq_puts(s, "# timerlat is tainted!\n");
+
seq_puts(s, "# _-----=> irqs-off\n");
seq_puts(s, "# / _----=> need-resched\n");
seq_puts(s, "# | / _---=> hardirq/softirq\n");
--
2.54.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH v3 9/9] tracing/osnoise: Refuse timerlat fd open if tracer inactive
2026-10-14 2:26 [PATCH v3 0/9] tracing/osnoise: Synchronization fixes Crystal Wood
` (7 preceding siblings ...)
2026-10-14 2:26 ` [PATCH v3 8/9] tracing/osnoise: Print taint message for timerlat Crystal Wood
@ 2026-10-14 2:26 ` Crystal Wood
2026-10-14 2:39 ` sashiko-bot
8 siblings, 1 reply; 12+ messages in thread
From: Crystal Wood @ 2026-10-14 2:26 UTC (permalink / raw)
To: Steven Rostedt; +Cc: Tomas Glozar, John Kacur, linux-trace-kernel, Crystal Wood
Besides being generally bad, allowing an fd open when there are no active
instances means that osnoise_options_write() and osnoise_cpus_write() will
leave it active during reconfiguration, causing at least WARN splats.
Signed-off-by: Crystal Wood <crwood@redhat.com>
---
v2: new patch
---
kernel/trace/trace_osnoise.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/kernel/trace/trace_osnoise.c b/kernel/trace/trace_osnoise.c
index 9ca45b0b8cb3..2511a9b763b9 100644
--- a/kernel/trace/trace_osnoise.c
+++ b/kernel/trace/trace_osnoise.c
@@ -2506,7 +2506,8 @@ static int timerlat_fd_open(struct inode *inode, struct file *file)
* This file is accessible only if timerlat is enabled, and
* NO_OSNOISE_WORKLOAD is set.
*/
- if (!timerlat_enabled() || test_bit(OSN_WORKLOAD, &osnoise_options))
+ if (!osnoise_has_registered_instances() || !timerlat_enabled() ||
+ test_bit(OSN_WORKLOAD, &osnoise_options))
return -EINVAL;
guard(migrate)();
--
2.54.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* Re: [PATCH v3 9/9] tracing/osnoise: Refuse timerlat fd open if tracer inactive
2026-10-14 2:26 ` [PATCH v3 9/9] tracing/osnoise: Refuse timerlat fd open if tracer inactive Crystal Wood
@ 2026-10-14 2:39 ` sashiko-bot
0 siblings, 0 replies; 12+ messages in thread
From: sashiko-bot @ 2026-10-14 2:39 UTC (permalink / raw)
To: Crystal Wood; +Cc: linux-trace-kernel
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] trace: timerlat_fd_release unconditionally resets cpu state
--
commit 3a852123b0f7d3294fb2be0fe904429d8ee1fccc
Author: Crystal Wood <crwood@redhat.com>
tracing/osnoise: Refuse timerlat fd open if tracer inactive
This commit refuses to open a timerlat file descriptor when there are no
active instances. This prevents the file descriptor from remaining active
during reconfiguration, which could cause WARN splats.
> diff --git a/kernel/trace/trace_osnoise.c b/kernel/trace/trace_osnoise.c
> index 9ca45b0b8cb39..2511a9b763b98 100644
> --- a/kernel/trace/trace_osnoise.c
> +++ b/kernel/trace/trace_osnoise.c
> @@ -2506,7 +2506,8 @@ static int timerlat_fd_open(struct inode *inode, struct file *file)
> * This file is accessible only if timerlat is enabled, and
> * NO_OSNOISE_WORKLOAD is set.
> */
> - if (!timerlat_enabled() || test_bit(OSN_WORKLOAD, &osnoise_options))
> + if (!osnoise_has_registered_instances() || !timerlat_enabled() ||
> + test_bit(OSN_WORKLOAD, &osnoise_options))
> return -EINVAL;
>
> guard(migrate)();
[Severity: High]
Does this code still allow state corruption during the fd release path?
Looking at timerlat_fd_release() in kernel/trace/trace_osnoise.c,
osn_reset_cpu() is called unconditionally, bypassing the active session check:
kernel/trace/trace_osnoise.c:timerlat_fd_release() {
...
if (fdd == osn_var->fdd)
timerlat_fd_detach(fdd);
osn_reset_cpu(fdd->cpu);
...
}
Can the following sequence occur?
1. Userspace opens a timerlat fd, creating an active tracing session (fdd1).
2. Tracing is stopped or reconfigured (e.g. via
/sys/kernel/tracing/osnoise/cpus), calling stop_kthread(). This detaches
fdd1 (osn_var->fdd = NULL) but leaves the fd open in userspace.
3. Tracing is restarted, and userspace opens a new timerlat fd on the same
CPU (fdd2). This becomes the active session (osn_var->fdd = fdd2) and
tlat_var->timer is initialized and armed.
4. Userspace closes the first fd (fdd1).
5. timerlat_fd_release() is called. Because fdd1 != osn_var->fdd, it skips
detachment, but unconditionally calls osn_reset_cpu(fdd1->cpu).
Would this unconditional reset wipe the active tracing session's state,
including zero-filling the armed hrtimer of the active fdd2 session,
potentially leading to timer tree corruption and a kernel panic?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261014022640.695786-1-crwood@redhat.com?part=9
^ permalink raw reply [flat|nested] 12+ messages in thread