Linux Trace Kernel
 help / color / mirror / Atom feed
* [PATCH 1/5] tracing: Fix checking of freed trace_event_file for hist files
From: Petr Pavlu @ 2026-02-10 11:28 UTC (permalink / raw)
  To: Steven Rostedt, Masami Hiramatsu
  Cc: Mathieu Desnoyers, Tom Zanussi, linux-kernel, linux-trace-kernel,
	Petr Pavlu
In-Reply-To: <20260210113427.1068932-1-petr.pavlu@suse.com>

The event_hist_open() and event_hist_poll() functions currently retrieve
a trace_event_file pointer from a file struct by invoking
event_file_data(), which simply returns file->f_inode->i_private. The
functions then check if the pointer is NULL to determine whether the event
is still valid. This approach is flawed because i_private is assigned when
an eventfs inode is allocated and remains set throughout its lifetime.
Instead, the code should call event_file_file(), which checks for
EVENT_FILE_FL_FREED. Using the incorrect access function may result in the
code potentially opening a hist file for an event that is being removed or
becoming stuck while polling on this file.

A related issue is that although event_hist_poll() attempts to verify
whether an event file is being removed, this check may not occur or could
be unnecessarily delayed. This happens because hist_poll_wakeup() is
currently invoked only from event_hist_trigger() when a hist command is
triggered. If the event file is being removed, no associated hist command
will be triggered and a waiter will be woken up only after an unrelated
hist command is triggered.

Fix these issues by changing the access method to event_file_file() and
adding a call to hist_poll_wakeup() in remove_event_file_dir() after
setting the EVENT_FILE_FL_FREED flag. This ensures that a task polling on
a hist file is woken up and receives EPOLLERR.

Fixes: 1bd13edbbed6 ("tracing/hist: Add poll(POLLIN) support on hist file")
Signed-off-by: Petr Pavlu <petr.pavlu@suse.com>
---
 kernel/trace/trace_events.c      | 3 +++
 kernel/trace/trace_events_hist.c | 4 ++--
 2 files changed, 5 insertions(+), 2 deletions(-)

diff --git a/kernel/trace/trace_events.c b/kernel/trace/trace_events.c
index 137b4d9bb116..e8ed6ba155cf 100644
--- a/kernel/trace/trace_events.c
+++ b/kernel/trace/trace_events.c
@@ -1295,6 +1295,9 @@ static void remove_event_file_dir(struct trace_event_file *file)
 	free_event_filter(file->filter);
 	file->flags |= EVENT_FILE_FL_FREED;
 	event_file_put(file);
+
+	/* Wake up hist poll waiters to notice the EVENT_FILE_FL_FREED flag. */
+	hist_poll_wakeup();
 }
 
 /*
diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c
index c97bb2fda5c0..744c2aa3d668 100644
--- a/kernel/trace/trace_events_hist.c
+++ b/kernel/trace/trace_events_hist.c
@@ -5778,7 +5778,7 @@ static __poll_t event_hist_poll(struct file *file, struct poll_table_struct *wai
 
 	guard(mutex)(&event_mutex);
 
-	event_file = event_file_data(file);
+	event_file = event_file_file(file);
 	if (!event_file)
 		return EPOLLERR;
 
@@ -5816,7 +5816,7 @@ static int event_hist_open(struct inode *inode, struct file *file)
 
 	guard(mutex)(&event_mutex);
 
-	event_file = event_file_data(file);
+	event_file = event_file_file(file);
 	if (!event_file) {
 		ret = -ENODEV;
 		goto err;
-- 
2.52.0


^ permalink raw reply related

* [PATCH 0/5] Clean up access to trace_event_file from a file struct
From: Petr Pavlu @ 2026-02-10 11:28 UTC (permalink / raw)
  To: Steven Rostedt, Masami Hiramatsu
  Cc: Mathieu Desnoyers, Tom Zanussi, linux-kernel, linux-trace-kernel,
	Petr Pavlu

This series includes several patches related to accessing
trace_event_file from a file struct. The first two patches are fixes for
edge cases, the remaining patches are minor cleanups.

Petr Pavlu (5):
  tracing: Fix checking of freed trace_event_file for hist files
  tracing: Fix checking of freed trace_event_file for id files
  tracing: Remove unnecessary check for EVENT_FILE_FL_FREED
  tracing: Clean up access to trace_event_file from a file pointer
  tracing: Free up file->private_data for use by individual events

 kernel/trace/trace.c             |  2 --
 kernel/trace/trace.h             | 17 +++++++++++------
 kernel/trace/trace_events.c      | 25 ++++++++++++++++---------
 kernel/trace/trace_events_hist.c |  8 ++------
 4 files changed, 29 insertions(+), 23 deletions(-)


base-commit: 05f7e89ab9731565d8a62e3b5d1ec206485eeb0b
-- 
2.52.0


^ permalink raw reply

* [PATCH 2/2] selftests/tracing: Fix to check awk supports non POSIX strtonum()
From: Masami Hiramatsu (Google) @ 2026-02-10  9:54 UTC (permalink / raw)
  To: Steven Rostedt, Shuah Khan, Gabriele Monaco
  Cc: Mathieu Desnoyers, Masami Hiramatsu, linux-kernel,
	linux-trace-kernel, linux-kselftest
In-Reply-To: <177071725191.2369897.14781037901532893911.stgit@mhiramat.tok.corp.google.com>

From: Masami Hiramatsu (Google) <mhiramat@kernel.org>

Check the awk command supports non POSIX strtonum() function in
the trace_marker_raw test case.

Fixes: 37f46601383a ("selftests/tracing: Add basic test for trace_marker_raw file")
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
 .../ftrace/test.d/00basic/trace_marker_raw.tc      |    2 ++
 tools/testing/selftests/ftrace/test.d/functions    |    4 ++++
 2 files changed, 6 insertions(+)

diff --git a/tools/testing/selftests/ftrace/test.d/00basic/trace_marker_raw.tc b/tools/testing/selftests/ftrace/test.d/00basic/trace_marker_raw.tc
index a2c42e13f614..8e905d4fe6dd 100644
--- a/tools/testing/selftests/ftrace/test.d/00basic/trace_marker_raw.tc
+++ b/tools/testing/selftests/ftrace/test.d/00basic/trace_marker_raw.tc
@@ -4,6 +4,8 @@
 # requires: trace_marker_raw
 # flags: instance
 
+check_awk_strtonum || exit_unresolved
+
 is_little_endian() {
 	if lscpu | grep -q 'Little Endian'; then
 		echo 1;
diff --git a/tools/testing/selftests/ftrace/test.d/functions b/tools/testing/selftests/ftrace/test.d/functions
index e8e718139294..41325f387ee7 100644
--- a/tools/testing/selftests/ftrace/test.d/functions
+++ b/tools/testing/selftests/ftrace/test.d/functions
@@ -173,6 +173,10 @@ check_requires() { # Check required files and tracers
     done
 }
 
+check_awk_strtonum() { # strtonum is GNU awk extension
+    awk 'BEGIN{strtonum("0x1")}'
+}
+
 LOCALHOST=127.0.0.1
 
 yield() {


^ permalink raw reply related

* [PATCH 1/2] selftests/tracing: Fix to make --logdir option work again
From: Masami Hiramatsu (Google) @ 2026-02-10  9:54 UTC (permalink / raw)
  To: Steven Rostedt, Shuah Khan, Gabriele Monaco
  Cc: Mathieu Desnoyers, Masami Hiramatsu, linux-kernel,
	linux-trace-kernel, linux-kselftest

From: Masami Hiramatsu (Google) <mhiramat@kernel.org>

Since commit a0aa283c53a7 ("selftest/ftrace: Generalise ftracetest to
use with RV") moved the default LOG_DIR setting after --logdir option
parser, it overwrites the user given LOG_DIR.
This fixes it to check the --logdir option parameter when setting new
default LOG_DIR with a new TOP_DIR.

Fixes: a0aa283c53a7 ("selftest/ftrace: Generalise ftracetest to use with RV")
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
 tools/testing/selftests/ftrace/ftracetest |   18 ++++++++++++------
 1 file changed, 12 insertions(+), 6 deletions(-)

diff --git a/tools/testing/selftests/ftrace/ftracetest b/tools/testing/selftests/ftrace/ftracetest
index 3230bd54dba8..0a56bf209f6c 100755
--- a/tools/testing/selftests/ftrace/ftracetest
+++ b/tools/testing/selftests/ftrace/ftracetest
@@ -130,8 +130,7 @@ parse_opts() { # opts
       shift 1
     ;;
     --logdir|-l)
-      LOG_DIR=$2
-      LINK_PTR=
+      USER_LOG_DIR=$2
       shift 2
     ;;
     --rv)
@@ -199,6 +198,7 @@ fi
 TOP_DIR=`absdir $0`
 TEST_DIR=$TOP_DIR/test.d
 TEST_CASES=`find_testcases $TEST_DIR`
+USER_LOG_DIR=
 KEEP_LOG=0
 KTAP=0
 DEBUG=0
@@ -210,12 +210,18 @@ RV_TEST=0
 # Parse command-line options
 parse_opts $*
 
+[ $DEBUG -ne 0 ] && set -x
+
+# TOP_DIR can be changed for rv. Setting log directory.
 LOG_TOP_DIR=$TOP_DIR/logs
 LOG_DATE=`date +%Y%m%d-%H%M%S`
-LOG_DIR=$LOG_TOP_DIR/$LOG_DATE/
-LINK_PTR=$LOG_TOP_DIR/latest
-
-[ $DEBUG -ne 0 ] && set -x
+if [ -n "$USER_LOG_DIR" ]; then
+  LOG_DIR=$USER_LOG_DIR
+  LINK_PTR=
+else
+  LOG_DIR=$LOG_TOP_DIR/$LOG_DATE/
+  LINK_PTR=$LOG_TOP_DIR/latest
+fi
 
 if [ $RV_TEST -ne 0 ]; then
 	TRACING_DIR=$TRACING_DIR/rv


^ permalink raw reply related

* [PATCH v8 6/6] tracing/Documentation: Add a section about backup instance
From: Masami Hiramatsu (Google) @ 2026-02-10  8:44 UTC (permalink / raw)
  To: Steven Rostedt
  Cc: Masami Hiramatsu, Mathieu Desnoyers, linux-kernel,
	linux-trace-kernel
In-Reply-To: <177071300558.2293046.12057922262682243630.stgit@mhiramat.tok.corp.google.com>

From: Masami Hiramatsu (Google) <mhiramat@kernel.org>

Add a section about backup instance to the debugging.rst.

Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
  Changes in v6:
   - Fix typos.
---
 Documentation/trace/debugging.rst |   19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/Documentation/trace/debugging.rst b/Documentation/trace/debugging.rst
index 4d88c346fc38..15857951b506 100644
--- a/Documentation/trace/debugging.rst
+++ b/Documentation/trace/debugging.rst
@@ -159,3 +159,22 @@ If setting it from the kernel command line, it is recommended to also
 disable tracing with the "traceoff" flag, and enable tracing after boot up.
 Otherwise the trace from the most recent boot will be mixed with the trace
 from the previous boot, and may make it confusing to read.
+
+Using a backup instance for keeping previous boot data
+------------------------------------------------------
+
+It is also possible to record trace data at system boot time by specifying
+events with the persistent ring buffer, but in this case the data before the
+reboot will be lost before it can be read. This problem can be solved by a
+backup instance. From the kernel command line::
+
+  reserve_mem=12M:4096:trace trace_instance=boot_map@trace,sched,irq trace_instance=backup=boot_map
+
+On boot up, the previous data in the "boot_map" is copied to the "backup"
+instance, and the "sched:*" and "irq:*" events for the current boot are traced
+in the "boot_map". Thus the user can read the previous boot data from the "backup"
+instance without stopping the trace.
+
+Note that this "backup" instance is readonly, and will be removed automatically
+if you clear the trace data or read out all trace data from the "trace_pipe"
+or the "trace_pipe_raw" files.
\ No newline at end of file


^ permalink raw reply related

* [PATCH v8 5/6] tracing: Remove the backup instance automatically after read
From: Masami Hiramatsu (Google) @ 2026-02-10  8:44 UTC (permalink / raw)
  To: Steven Rostedt
  Cc: Masami Hiramatsu, Mathieu Desnoyers, linux-kernel,
	linux-trace-kernel
In-Reply-To: <177071300558.2293046.12057922262682243630.stgit@mhiramat.tok.corp.google.com>

From: Masami Hiramatsu (Google) <mhiramat@kernel.org>

Since the backup instance is readonly, after reading all data
via pipe, no data is left on the instance. Thus it can be
removed safely after closing all files.
This also removes it if user resets the ring buffer manually
via 'trace' file.

Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
 Changes in v6:
   - Fix typo in comment.
   - Only when there is a readonly trace array, initialize autoremove_wq.
   - Fix to exit loop in trace_array_get() if tr is found in the list.
 Changes in v4:
   - Update description.
---
 kernel/trace/trace.c |   61 +++++++++++++++++++++++++++++++++++++++++++++++++-
 kernel/trace/trace.h |    6 +++++
 2 files changed, 66 insertions(+), 1 deletion(-)

diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c
index 566d1e824360..c746cb4c6e38 100644
--- a/kernel/trace/trace.c
+++ b/kernel/trace/trace.c
@@ -578,6 +578,51 @@ void trace_set_ring_buffer_expanded(struct trace_array *tr)
 	tr->ring_buffer_expanded = true;
 }
 
+static int __remove_instance(struct trace_array *tr);
+
+static void trace_array_autoremove(struct work_struct *work)
+{
+	struct trace_array *tr = container_of(work, struct trace_array, autoremove_work);
+
+	guard(mutex)(&event_mutex);
+	guard(mutex)(&trace_types_lock);
+
+	/*
+	 * This can be fail if someone gets @tr before starting this
+	 * function, but in that case, this will be kicked again when
+	 * putting it. So we don't care about the result.
+	 */
+	__remove_instance(tr);
+}
+
+static struct workqueue_struct *autoremove_wq;
+
+static void trace_array_kick_autoremove(struct trace_array *tr)
+{
+	if (autoremove_wq && !work_pending(&tr->autoremove_work))
+		queue_work(autoremove_wq, &tr->autoremove_work);
+}
+
+static void trace_array_cancel_autoremove(struct trace_array *tr)
+{
+	if (autoremove_wq && work_pending(&tr->autoremove_work))
+		cancel_work(&tr->autoremove_work);
+}
+
+static void trace_array_init_autoremove(struct trace_array *tr)
+{
+	INIT_WORK(&tr->autoremove_work, trace_array_autoremove);
+
+	/* Only readonly trace_array can kick the autoremove. */
+	if (!trace_array_is_readonly(tr) || autoremove_wq)
+		return;
+
+	autoremove_wq = alloc_workqueue("tr_autoremove_wq",
+					WQ_UNBOUND | WQ_HIGHPRI, 0);
+	if (!autoremove_wq)
+		pr_warn("Unable to allocate tr_autoremove_wq. autoremove disabled.\n");
+}
+
 LIST_HEAD(ftrace_trace_arrays);
 
 int trace_array_get(struct trace_array *this_tr)
@@ -587,7 +632,8 @@ int trace_array_get(struct trace_array *this_tr)
 	guard(mutex)(&trace_types_lock);
 	list_for_each_entry(tr, &ftrace_trace_arrays, list) {
 		if (tr == this_tr) {
-			tr->ref++;
+			if (!tr->free_on_close)
+				tr->ref++;
 			return 0;
 		}
 	}
@@ -599,6 +645,12 @@ static void __trace_array_put(struct trace_array *this_tr)
 {
 	WARN_ON(!this_tr->ref);
 	this_tr->ref--;
+	/*
+	 * When free_on_close is set, prepare removing the array
+	 * when the last reference is released.
+	 */
+	if (this_tr->ref == 1 && this_tr->free_on_close)
+		trace_array_kick_autoremove(this_tr);
 }
 
 /**
@@ -5463,6 +5515,10 @@ static void update_last_data(struct trace_array *tr)
 	/* Only if the buffer has previous boot data clear and update it. */
 	tr->flags &= ~TRACE_ARRAY_FL_LAST_BOOT;
 
+	/* If this is a backup instance, mark it for autoremove. */
+	if (tr->flags & TRACE_ARRAY_FL_VMALLOC)
+		tr->free_on_close = true;
+
 	/* Reset the module list and reload them */
 	if (tr->scratch) {
 		struct trace_scratch *tscratch = tr->scratch;
@@ -9596,6 +9652,8 @@ trace_array_create_systems(const char *name, const char *systems,
 	if (ftrace_allocate_ftrace_ops(tr) < 0)
 		goto out_free_tr;
 
+	trace_array_init_autoremove(tr);
+
 	ftrace_init_trace_array(tr);
 
 	init_trace_flags_index(tr);
@@ -9744,6 +9802,7 @@ static int __remove_instance(struct trace_array *tr)
 	if (update_marker_trace(tr, 0))
 		synchronize_rcu();
 
+	trace_array_cancel_autoremove(tr);
 	tracing_set_nop(tr);
 	clear_ftrace_function_probes(tr);
 	event_trace_del_tracer(tr);
diff --git a/kernel/trace/trace.h b/kernel/trace/trace.h
index 393be92768f1..48b94759ba1c 100644
--- a/kernel/trace/trace.h
+++ b/kernel/trace/trace.h
@@ -450,6 +450,12 @@ struct trace_array {
 	 * we do not waste memory on systems that are not using tracing.
 	 */
 	bool ring_buffer_expanded;
+	/*
+	 * If the ring buffer is a read only backup instance, it will be
+	 * removed after dumping all data via pipe, because no readable data.
+	 */
+	bool free_on_close;
+	struct work_struct	autoremove_work;
 };
 
 enum {


^ permalink raw reply related

* [PATCH v8 4/6] tracing: Make the backup instance non-reusable
From: Masami Hiramatsu (Google) @ 2026-02-10  8:43 UTC (permalink / raw)
  To: Steven Rostedt
  Cc: Masami Hiramatsu, Mathieu Desnoyers, linux-kernel,
	linux-trace-kernel
In-Reply-To: <177071300558.2293046.12057922262682243630.stgit@mhiramat.tok.corp.google.com>

From: Masami Hiramatsu (Google) <mhiramat@kernel.org>

Since there is no reason to reuse the backup instance, make it
readonly (but erasable).
Note that only backup instances are readonly, because
other trace instances will be empty unless it is writable.
Only backup instances have copy entries from the original.

With this change, most of the trace control files are removed
from the backup instance, including eventfs enable/filter etc.

 # find /sys/kernel/tracing/instances/backup/events/ | wc -l
 4093
 # find /sys/kernel/tracing/instances/boot_map/events/ | wc -l
 9573

Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
 Changes in v8:
   - Remove read-only checks in read() operation.
 Changes in v7:
   - Return -EACCES instead of -EPERM.
 Changes in v6:
   - Remove tracing_on file from readonly instances.
   - Remove unused writable_mode from tracing_init_tracefs_percpu().
   - Cleanup init_tracer_tracefs() and create_event_toplevel_files().
   - Remove TRACE_MODE_WRITE_MASK.
   - Add TRACE_ARRAY_FL_RDONLY.
 Changes in v5:
   - Rebased on the latest for-next (and hide show_event_filters/triggers
     if the instance is readonly.
 Changes in v4:
  - Make trace data erasable. (not reusable)
 Changes in v3:
  - Resuse the beginning part of event_entries for readonly files.
  - Remove readonly file_operations and checking readonly flag in
    each write operation.
 Changes in v2:
  - Use readonly file_operations to prohibit writing instead of
    checking flags in write() callbacks.
  - Remove writable files from eventfs.
---
 kernel/trace/trace.c        |   71 +++++++++++++++++++++++-----------------
 kernel/trace/trace.h        |    7 ++++
 kernel/trace/trace_boot.c   |    5 ++-
 kernel/trace/trace_events.c |   76 +++++++++++++++++++++++++------------------
 4 files changed, 94 insertions(+), 65 deletions(-)

diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c
index e884d32b7895..566d1e824360 100644
--- a/kernel/trace/trace.c
+++ b/kernel/trace/trace.c
@@ -9836,17 +9836,22 @@ static __init void create_trace_instances(struct dentry *d_tracer)
 static void
 init_tracer_tracefs(struct trace_array *tr, struct dentry *d_tracer)
 {
+	umode_t writable_mode = TRACE_MODE_WRITE;
 	int cpu;
 
+	if (trace_array_is_readonly(tr))
+		writable_mode = TRACE_MODE_READ;
+
 	trace_create_file("available_tracers", TRACE_MODE_READ, d_tracer,
-			tr, &show_traces_fops);
+			  tr, &show_traces_fops);
 
-	trace_create_file("current_tracer", TRACE_MODE_WRITE, d_tracer,
-			tr, &set_tracer_fops);
+	trace_create_file("current_tracer", writable_mode, d_tracer,
+			  tr, &set_tracer_fops);
 
-	trace_create_file("tracing_cpumask", TRACE_MODE_WRITE, d_tracer,
+	trace_create_file("tracing_cpumask", writable_mode, d_tracer,
 			  tr, &tracing_cpumask_fops);
 
+	/* Options are used for changing print-format even for readonly instance. */
 	trace_create_file("trace_options", TRACE_MODE_WRITE, d_tracer,
 			  tr, &tracing_iter_fops);
 
@@ -9856,12 +9861,36 @@ init_tracer_tracefs(struct trace_array *tr, struct dentry *d_tracer)
 	trace_create_file("trace_pipe", TRACE_MODE_READ, d_tracer,
 			  tr, &tracing_pipe_fops);
 
-	trace_create_file("buffer_size_kb", TRACE_MODE_WRITE, d_tracer,
+	trace_create_file("buffer_size_kb", writable_mode, d_tracer,
 			  tr, &tracing_entries_fops);
 
 	trace_create_file("buffer_total_size_kb", TRACE_MODE_READ, d_tracer,
 			  tr, &tracing_total_entries_fops);
 
+	trace_create_file("trace_clock", writable_mode, d_tracer, tr,
+			  &trace_clock_fops);
+
+	trace_create_file("timestamp_mode", TRACE_MODE_READ, d_tracer, tr,
+			  &trace_time_stamp_mode_fops);
+
+	tr->buffer_percent = 50;
+
+	trace_create_file("buffer_subbuf_size_kb", writable_mode, d_tracer,
+			  tr, &buffer_subbuf_size_fops);
+
+	create_trace_options_dir(tr);
+
+	if (tr->range_addr_start)
+		trace_create_file("last_boot_info", TRACE_MODE_READ, d_tracer,
+				  tr, &last_boot_fops);
+
+	for_each_tracing_cpu(cpu)
+		tracing_init_tracefs_percpu(tr, cpu);
+
+	/* Read-only instance has above files only. */
+	if (trace_array_is_readonly(tr))
+		return;
+
 	trace_create_file("free_buffer", 0200, d_tracer,
 			  tr, &tracing_free_buffer_fops);
 
@@ -9873,49 +9902,29 @@ init_tracer_tracefs(struct trace_array *tr, struct dentry *d_tracer)
 	trace_create_file("trace_marker_raw", 0220, d_tracer,
 			  tr, &tracing_mark_raw_fops);
 
-	trace_create_file("trace_clock", TRACE_MODE_WRITE, d_tracer, tr,
-			  &trace_clock_fops);
-
-	trace_create_file("tracing_on", TRACE_MODE_WRITE, d_tracer,
-			  tr, &rb_simple_fops);
-
-	trace_create_file("timestamp_mode", TRACE_MODE_READ, d_tracer, tr,
-			  &trace_time_stamp_mode_fops);
-
-	tr->buffer_percent = 50;
-
 	trace_create_file("buffer_percent", TRACE_MODE_WRITE, d_tracer,
-			tr, &buffer_percent_fops);
-
-	trace_create_file("buffer_subbuf_size_kb", TRACE_MODE_WRITE, d_tracer,
-			  tr, &buffer_subbuf_size_fops);
+			  tr, &buffer_percent_fops);
 
 	trace_create_file("syscall_user_buf_size", TRACE_MODE_WRITE, d_tracer,
-			 tr, &tracing_syscall_buf_fops);
+			  tr, &tracing_syscall_buf_fops);
 
-	create_trace_options_dir(tr);
+	trace_create_file("tracing_on", TRACE_MODE_WRITE, d_tracer,
+			  tr, &rb_simple_fops);
 
 	trace_create_maxlat_file(tr, d_tracer);
 
 	if (ftrace_create_function_files(tr, d_tracer))
 		MEM_FAIL(1, "Could not allocate function filter files");
 
-	if (tr->range_addr_start) {
-		trace_create_file("last_boot_info", TRACE_MODE_READ, d_tracer,
-				  tr, &last_boot_fops);
 #ifdef CONFIG_TRACER_SNAPSHOT
-	} else {
+	if (!tr->range_addr_start)
 		trace_create_file("snapshot", TRACE_MODE_WRITE, d_tracer,
 				  tr, &snapshot_fops);
 #endif
-	}
 
 	trace_create_file("error_log", TRACE_MODE_WRITE, d_tracer,
 			  tr, &tracing_err_log_fops);
 
-	for_each_tracing_cpu(cpu)
-		tracing_init_tracefs_percpu(tr, cpu);
-
 	ftrace_init_tracefs(tr, d_tracer);
 }
 
@@ -10742,7 +10751,7 @@ __init static void enable_instances(void)
 		 * Backup buffers can be freed but need vfree().
 		 */
 		if (backup)
-			tr->flags |= TRACE_ARRAY_FL_VMALLOC;
+			tr->flags |= TRACE_ARRAY_FL_VMALLOC | TRACE_ARRAY_FL_RDONLY;
 
 		if (start || backup) {
 			tr->flags |= TRACE_ARRAY_FL_BOOT | TRACE_ARRAY_FL_LAST_BOOT;
diff --git a/kernel/trace/trace.h b/kernel/trace/trace.h
index 649fdd20fc91..393be92768f1 100644
--- a/kernel/trace/trace.h
+++ b/kernel/trace/trace.h
@@ -459,6 +459,7 @@ enum {
 	TRACE_ARRAY_FL_MOD_INIT		= BIT(3),
 	TRACE_ARRAY_FL_MEMMAP		= BIT(4),
 	TRACE_ARRAY_FL_VMALLOC		= BIT(5),
+	TRACE_ARRAY_FL_RDONLY		= BIT(6),
 };
 
 #ifdef CONFIG_MODULES
@@ -488,6 +489,12 @@ extern unsigned long trace_adjust_address(struct trace_array *tr, unsigned long
 
 extern struct trace_array *printk_trace;
 
+static inline bool trace_array_is_readonly(struct trace_array *tr)
+{
+	/* backup instance is read only. */
+	return tr->flags & TRACE_ARRAY_FL_RDONLY;
+}
+
 /*
  * The global tracer (top) should be the first trace array added,
  * but we check the flag anyway.
diff --git a/kernel/trace/trace_boot.c b/kernel/trace/trace_boot.c
index dbe29b4c6a7a..2ca2541c8a58 100644
--- a/kernel/trace/trace_boot.c
+++ b/kernel/trace/trace_boot.c
@@ -61,7 +61,8 @@ trace_boot_set_instance_options(struct trace_array *tr, struct xbc_node *node)
 		v = memparse(p, NULL);
 		if (v < PAGE_SIZE)
 			pr_err("Buffer size is too small: %s\n", p);
-		if (tracing_resize_ring_buffer(tr, v, RING_BUFFER_ALL_CPUS) < 0)
+		if (trace_array_is_readonly(tr) ||
+		    tracing_resize_ring_buffer(tr, v, RING_BUFFER_ALL_CPUS) < 0)
 			pr_err("Failed to resize trace buffer to %s\n", p);
 	}
 
@@ -597,7 +598,7 @@ trace_boot_enable_tracer(struct trace_array *tr, struct xbc_node *node)
 
 	p = xbc_node_find_value(node, "tracer", NULL);
 	if (p && *p != '\0') {
-		if (tracing_set_tracer(tr, p) < 0)
+		if (trace_array_is_readonly(tr) || tracing_set_tracer(tr, p) < 0)
 			pr_err("Failed to set given tracer: %s\n", p);
 	}
 
diff --git a/kernel/trace/trace_events.c b/kernel/trace/trace_events.c
index 61fe01dce7a6..b493cbdf0ea0 100644
--- a/kernel/trace/trace_events.c
+++ b/kernel/trace/trace_events.c
@@ -1396,6 +1396,9 @@ static int __ftrace_set_clr_event(struct trace_array *tr, const char *match,
 {
 	int ret;
 
+	if (trace_array_is_readonly(tr))
+		return -EACCES;
+
 	mutex_lock(&event_mutex);
 	ret = __ftrace_set_clr_event_nolock(tr, match, sub, event, set, mod);
 	mutex_unlock(&event_mutex);
@@ -2968,8 +2971,8 @@ event_subsystem_dir(struct trace_array *tr, const char *name,
 	} else
 		__get_system(system);
 
-	/* ftrace only has directories no files */
-	if (strcmp(name, "ftrace") == 0)
+	/* ftrace only has directories no files, readonly instance too. */
+	if (strcmp(name, "ftrace") == 0 || trace_array_is_readonly(tr))
 		nr_entries = 0;
 	else
 		nr_entries = ARRAY_SIZE(system_entries);
@@ -3134,28 +3137,30 @@ event_create_dir(struct eventfs_inode *parent, struct trace_event_file *file)
 	int ret;
 	static struct eventfs_entry event_entries[] = {
 		{
-			.name		= "enable",
+			.name		= "format",
 			.callback	= event_callback,
-			.release	= event_release,
 		},
+#ifdef CONFIG_PERF_EVENTS
 		{
-			.name		= "filter",
+			.name		= "id",
 			.callback	= event_callback,
 		},
+#endif
+#define NR_RO_EVENT_ENTRIES	(1 + IS_ENABLED(CONFIG_PERF_EVENTS))
+/* Readonly files must be above this line and counted by NR_RO_EVENT_ENTRIES. */
 		{
-			.name		= "trigger",
+			.name		= "enable",
 			.callback	= event_callback,
+			.release	= event_release,
 		},
 		{
-			.name		= "format",
+			.name		= "filter",
 			.callback	= event_callback,
 		},
-#ifdef CONFIG_PERF_EVENTS
 		{
-			.name		= "id",
+			.name		= "trigger",
 			.callback	= event_callback,
 		},
-#endif
 #ifdef CONFIG_HIST_TRIGGERS
 		{
 			.name		= "hist",
@@ -3188,7 +3193,10 @@ event_create_dir(struct eventfs_inode *parent, struct trace_event_file *file)
 	if (!e_events)
 		return -ENOMEM;
 
-	nr_entries = ARRAY_SIZE(event_entries);
+	if (trace_array_is_readonly(tr))
+		nr_entries = NR_RO_EVENT_ENTRIES;
+	else
+		nr_entries = ARRAY_SIZE(event_entries);
 
 	name = trace_event_name(call);
 	ei = eventfs_create_dir(name, e_events, event_entries, nr_entries, file);
@@ -4527,31 +4535,44 @@ create_event_toplevel_files(struct dentry *parent, struct trace_array *tr)
 	int nr_entries;
 	static struct eventfs_entry events_entries[] = {
 		{
-			.name		= "enable",
+			.name		= "header_page",
 			.callback	= events_callback,
 		},
 		{
-			.name		= "header_page",
+			.name		= "header_event",
 			.callback	= events_callback,
 		},
+#define NR_RO_TOP_ENTRIES	2
+/* Readonly files must be above this line and counted by NR_RO_TOP_ENTRIES. */
 		{
-			.name		= "header_event",
+			.name		= "enable",
 			.callback	= events_callback,
 		},
 	};
 
-	entry = trace_create_file("set_event", TRACE_MODE_WRITE, parent,
-				  tr, &ftrace_set_event_fops);
-	if (!entry)
-		return -ENOMEM;
+	if (!trace_array_is_readonly(tr)) {
+		entry = trace_create_file("set_event", TRACE_MODE_WRITE, parent,
+					tr, &ftrace_set_event_fops);
+		if (!entry)
+			return -ENOMEM;
 
-	trace_create_file("show_event_filters", TRACE_MODE_READ, parent, tr,
-			  &ftrace_show_event_filters_fops);
+		/* There are not as crucial, just warn if they are not created */
+		trace_create_file("show_event_filters", TRACE_MODE_READ, parent, tr,
+				&ftrace_show_event_filters_fops);
 
-	trace_create_file("show_event_triggers", TRACE_MODE_READ, parent, tr,
-			  &ftrace_show_event_triggers_fops);
+		trace_create_file("show_event_triggers", TRACE_MODE_READ, parent, tr,
+				&ftrace_show_event_triggers_fops);
 
-	nr_entries = ARRAY_SIZE(events_entries);
+		trace_create_file("set_event_pid", TRACE_MODE_WRITE, parent,
+				tr, &ftrace_set_event_pid_fops);
+
+		trace_create_file("set_event_notrace_pid",
+				TRACE_MODE_WRITE, parent, tr,
+				&ftrace_set_event_notrace_pid_fops);
+		nr_entries = ARRAY_SIZE(events_entries);
+	} else {
+		nr_entries = NR_RO_TOP_ENTRIES;
+	}
 
 	e_events = eventfs_create_events_dir("events", parent, events_entries,
 					     nr_entries, tr);
@@ -4560,15 +4581,6 @@ create_event_toplevel_files(struct dentry *parent, struct trace_array *tr)
 		return -ENOMEM;
 	}
 
-	/* There are not as crucial, just warn if they are not created */
-
-	trace_create_file("set_event_pid", TRACE_MODE_WRITE, parent,
-			  tr, &ftrace_set_event_pid_fops);
-
-	trace_create_file("set_event_notrace_pid",
-			  TRACE_MODE_WRITE, parent, tr,
-			  &ftrace_set_event_notrace_pid_fops);
-
 	tr->event_dir = e_events;
 
 	return 0;


^ permalink raw reply related

* [PATCH v8 3/6] tracefs: Check file permission even if user has CAP_DAC_OVERRIDE
From: Masami Hiramatsu (Google) @ 2026-02-10  8:43 UTC (permalink / raw)
  To: Steven Rostedt
  Cc: Masami Hiramatsu, Mathieu Desnoyers, linux-kernel,
	linux-trace-kernel
In-Reply-To: <177071300558.2293046.12057922262682243630.stgit@mhiramat.tok.corp.google.com>

From: Masami Hiramatsu (Google) <mhiramat@kernel.org>

Strictly checking the file read/write permission even if the owner has
CAP_DAC_OVERRIDE on tracefs as same as sysfs.
Tracefs is a pseudo filesystem, just like sysfs, so any file that the
system defines as unwritable should actually be unwritable by anyone.

Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
 fs/tracefs/event_inode.c |    2 ++
 fs/tracefs/inode.c       |   36 +++++++++++++++++++++++++++++++++---
 fs/tracefs/internal.h    |    3 +++
 3 files changed, 38 insertions(+), 3 deletions(-)

diff --git a/fs/tracefs/event_inode.c b/fs/tracefs/event_inode.c
index 61cbdafa2411..65e8be761e79 100644
--- a/fs/tracefs/event_inode.c
+++ b/fs/tracefs/event_inode.c
@@ -233,10 +233,12 @@ static int eventfs_set_attr(struct mnt_idmap *idmap, struct dentry *dentry,
 static const struct inode_operations eventfs_dir_inode_operations = {
 	.lookup		= eventfs_root_lookup,
 	.setattr	= eventfs_set_attr,
+	.permission	= tracefs_permission,
 };
 
 static const struct inode_operations eventfs_file_inode_operations = {
 	.setattr	= eventfs_set_attr,
+	.permission	= tracefs_permission,
 };
 
 static const struct file_operations eventfs_file_operations = {
diff --git a/fs/tracefs/inode.c b/fs/tracefs/inode.c
index d9d8932a7b9c..eb1ddc0cc13a 100644
--- a/fs/tracefs/inode.c
+++ b/fs/tracefs/inode.c
@@ -212,10 +212,40 @@ static void set_tracefs_inode_owner(struct inode *inode)
 		inode->i_gid = gid;
 }
 
-static int tracefs_permission(struct mnt_idmap *idmap,
-			      struct inode *inode, int mask)
+int tracefs_permission(struct mnt_idmap *idmap,
+		       struct inode *inode, int mask)
 {
-	set_tracefs_inode_owner(inode);
+	struct tracefs_inode *ti = get_tracefs(inode);
+	const struct file_operations *fops;
+
+	if (!(ti->flags & TRACEFS_EVENT_INODE))
+		set_tracefs_inode_owner(inode);
+
+	/*
+	 * Like sysfs, file permission checks are performed even for superuser
+	 * with CAP_DAC_OVERRIDE. See the KERNFS_ROOT_EXTRA_OPEN_PERM_CHECK
+	 * definition in linux/kernfs.h.
+	 */
+	if (mask & MAY_OPEN) {
+		fops = inode->i_fop;
+
+		if (mask & MAY_WRITE) {
+			if (!(inode->i_mode & 0222))
+				return -EACCES;
+			if (!fops || (!fops->write && !fops->write_iter &&
+				      !fops->mmap))
+				return -EACCES;
+		}
+
+		if (mask & MAY_READ) {
+			if (!(inode->i_mode & 0444))
+				return -EACCES;
+			if (!fops || (!fops->read && !fops->read_iter &&
+				      !fops->mmap && !fops->splice_read))
+				return -EACCES;
+		}
+	}
+
 	return generic_permission(idmap, inode, mask);
 }
 
diff --git a/fs/tracefs/internal.h b/fs/tracefs/internal.h
index d83c2a25f288..1e49ba445ba3 100644
--- a/fs/tracefs/internal.h
+++ b/fs/tracefs/internal.h
@@ -76,4 +76,7 @@ struct inode *tracefs_get_inode(struct super_block *sb);
 void eventfs_remount(struct tracefs_inode *ti, bool update_uid, bool update_gid);
 void eventfs_d_release(struct dentry *dentry);
 
+int tracefs_permission(struct mnt_idmap *idmap,
+		       struct inode *inode, int mask);
+
 #endif /* _TRACEFS_INTERNAL_H */


^ permalink raw reply related

* [PATCH v8 2/6] tracing: Reset last_boot_info if ring buffer is reset
From: Masami Hiramatsu (Google) @ 2026-02-10  8:43 UTC (permalink / raw)
  To: Steven Rostedt
  Cc: Masami Hiramatsu, Mathieu Desnoyers, linux-kernel,
	linux-trace-kernel
In-Reply-To: <177071300558.2293046.12057922262682243630.stgit@mhiramat.tok.corp.google.com>

From: Masami Hiramatsu (Google) <mhiramat@kernel.org>

Commit 32dc0042528d ("tracing: Reset last-boot buffers when reading
out all cpu buffers") resets the last_boot_info when user read out
all data via trace_pipe* files. But it is not reset when user
resets the buffer from other files. (e.g. write `trace` file)

Reset it when the corresponding ring buffer is reset too.

Fixes: 32dc0042528d ("tracing: Reset last-boot buffers when reading out all cpu buffers")
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
Changes in v7:
 - Remove unneeded update_last_data_if_empty() call from
   tracing_snapshot_write() because snapshot is disabled on
   persistent instances.
---
 kernel/trace/trace.c |    6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c
index fd470675809b..e884d32b7895 100644
--- a/kernel/trace/trace.c
+++ b/kernel/trace/trace.c
@@ -4127,6 +4127,8 @@ static int tracing_single_release_tr(struct inode *inode, struct file *file)
 	return single_release(inode, file);
 }
 
+static bool update_last_data_if_empty(struct trace_array *tr);
+
 static int tracing_open(struct inode *inode, struct file *file)
 {
 	struct trace_array *tr = inode->i_private;
@@ -4151,6 +4153,8 @@ static int tracing_open(struct inode *inode, struct file *file)
 			tracing_reset_online_cpus(trace_buf);
 		else
 			tracing_reset_cpu(trace_buf, cpu);
+
+		update_last_data_if_empty(tr);
 	}
 
 	if (file->f_mode & FMODE_READ) {
@@ -5215,6 +5219,7 @@ tracing_set_trace_read(struct file *filp, char __user *ubuf,
 int tracer_init(struct tracer *t, struct trace_array *tr)
 {
 	tracing_reset_online_cpus(&tr->array_buffer);
+	update_last_data_if_empty(tr);
 	return t->init(tr);
 }
 
@@ -7028,6 +7033,7 @@ int tracing_set_clock(struct trace_array *tr, const char *clockstr)
 		ring_buffer_set_clock(tr->snapshot_buffer.buffer, trace_clocks[i].func);
 	tracing_reset_online_cpus(&tr->snapshot_buffer);
 #endif
+	update_last_data_if_empty(tr);
 
 	if (tr->scratch && !(tr->flags & TRACE_ARRAY_FL_LAST_BOOT)) {
 		struct trace_scratch *tscratch = tr->scratch;


^ permalink raw reply related

* [PATCH v8 1/6] tracing: Fix to set write permission to per-cpu buffer_size_kb
From: Masami Hiramatsu (Google) @ 2026-02-10  8:43 UTC (permalink / raw)
  To: Steven Rostedt
  Cc: Masami Hiramatsu, Mathieu Desnoyers, linux-kernel,
	linux-trace-kernel
In-Reply-To: <177071300558.2293046.12057922262682243630.stgit@mhiramat.tok.corp.google.com>

From: Masami Hiramatsu (Google) <mhiramat@kernel.org>

Since the per-cpu buffer_size_kb file is writable for changing
per-cpu ring buffer size, the file should have the write access
permission.

Fixes: 21ccc9cd7211 ("tracing: Disable "other" permission bits in the tracefs files")
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
 Changes in v8: Newly added.
---
 kernel/trace/trace.c |    2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c
index 845b8a165daf..fd470675809b 100644
--- a/kernel/trace/trace.c
+++ b/kernel/trace/trace.c
@@ -8613,7 +8613,7 @@ tracing_init_tracefs_percpu(struct trace_array *tr, long cpu)
 	trace_create_cpu_file("stats", TRACE_MODE_READ, d_cpu,
 				tr, cpu, &tracing_stats_fops);
 
-	trace_create_cpu_file("buffer_size_kb", TRACE_MODE_READ, d_cpu,
+	trace_create_cpu_file("buffer_size_kb", TRACE_MODE_WRITE, d_cpu,
 				tr, cpu, &tracing_entries_fops);
 
 	if (tr->range_addr_start)


^ permalink raw reply related

* [PATCH v8 0/6] tracing: Remove backup instance after read all
From: Masami Hiramatsu (Google) @ 2026-02-10  8:43 UTC (permalink / raw)
  To: Steven Rostedt
  Cc: Masami Hiramatsu, Mathieu Desnoyers, linux-kernel,
	linux-trace-kernel

Hi,

Here is the v8 of the series to improve backup instances of
the persistent ring buffer. The previous version is here:

https://lore.kernel.org/all/177062912135.1230888.17419570791737357433.stgit@mhiramat.tok.corp.google.com/

In this version, I modified the tracefs to check the file permission
even if the user has CAP_DAC_OVERRIDE as same as sysfs[3/6] and remove
read-only check from each read() operations[4/6]. Also add a bugfix [2/6]
for per-cpu buffer_size_kb permission.

Series Description
------------------
Since backup instances are a kind of snapshot of the persistent
ring buffer, it should be readonly. And if it is readonly
there is no reason to keep it after reading all data via trace_pipe
because the data has been consumed. But user should be able to remove
the readonly instance by rmdir or truncating `trace` file.

Thus, [3/5] makes backup instances readonly (not able to write any
events, cleanup trace, change buffer size). Also, [4/5] removes the
backup instance after consuming all data via trace_pipe.
With this improvements, even if we makes a backup instance (using
the same amount of memory of the persistent ring buffer), it will
be removed after reading the data automatically.

---

Masami Hiramatsu (Google) (6):
      tracing: Fix to set write permission to per-cpu buffer_size_kb
      tracing: Reset last_boot_info if ring buffer is reset
      tracefs: Check file permission even if user has CAP_DAC_OVERRIDE
      tracing: Make the backup instance non-reusable
      tracing: Remove the backup instance automatically after read
      tracing/Documentation: Add a section about backup instance


 Documentation/trace/debugging.rst |   19 +++++
 fs/tracefs/event_inode.c          |    2 +
 fs/tracefs/inode.c                |   36 +++++++++-
 fs/tracefs/internal.h             |    3 +
 kernel/trace/trace.c              |  140 ++++++++++++++++++++++++++++---------
 kernel/trace/trace.h              |   13 +++
 kernel/trace/trace_boot.c         |    5 +
 kernel/trace/trace_events.c       |   76 ++++++++++++--------
 8 files changed, 224 insertions(+), 70 deletions(-)

--
Masami Hiramatsu (Google) <mhiramat@kernel.org>

^ permalink raw reply

* [PATCH v3] tracing: Fix false sharing in hwlat get_sample()
From: Colin Lord @ 2026-02-10  7:48 UTC (permalink / raw)
  To: linux-kernel, linux-trace-kernel
  Cc: Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers, Colin Lord

The get_sample() function in the hwlat tracer assumes the caller holds
hwlat_data.lock, but this is not actually happening. The result is
unprotected data access to hwlat_data, and in per-cpu mode can result in
false sharing which may show up as false positive latency events.

The specific case of false sharing observed was primarily between
hwlat_data.sample_width and hwlat_data.count. These are separated by
just 8B and are therefore likely to share a cache line. When one thread
modifies count, the cache line is in a modified state so when other
threads read sample_width in the main latency detection loop, they fetch
the modified cache line. On some systems, the fetch itself may be slow
enough to count as a latency event, which could set up a self
reinforcing cycle of latency events as each event increments count which
then causes more latency events, continuing the cycle.

The other result of the unprotected data access is that hwlat_data.count
can end up with duplicate or missed values, which was observed on some
systems in testing.

Convert hwlat_data.count to atomic64_t so it can be safely modified
without locking, and prevent false sharing by pulling sample_width into
a local variable.

One system this was tested on was a dual socket server with 32 CPUs on
each numa node. With settings of 1us threshold, 1000us width, and
2000us window, this change reduced the number of latency events from
500 per second down to approximately 1 event per minute. Some machines
tested did not exhibit measurable latency from the false sharing.

Signed-off-by: Colin Lord <clord@mykolab.com>
---
Changes in v3:
- include additional details on the false sharing in the commit message

Changes in v2:
- convert hwlat_data.count to atomic64_t
- leave irqs_disabled block where it originally was, outside of
  get_sample()

thanks,
Colin

 kernel/trace/trace_hwlat.c | 15 +++++++--------
 1 file changed, 7 insertions(+), 8 deletions(-)

diff --git a/kernel/trace/trace_hwlat.c b/kernel/trace/trace_hwlat.c
index 2f7b94e98317..3fe274b84f1c 100644
--- a/kernel/trace/trace_hwlat.c
+++ b/kernel/trace/trace_hwlat.c
@@ -102,9 +102,9 @@ struct hwlat_sample {
 /* keep the global state somewhere. */
 static struct hwlat_data {
 
-	struct mutex lock;		/* protect changes */
+	struct mutex	lock;		/* protect changes */
 
-	u64	count;			/* total since reset */
+	atomic64_t	count;		/* total since reset */
 
 	u64	sample_window;		/* total sampling window (on+off) */
 	u64	sample_width;		/* active sampling portion of window */
@@ -193,8 +193,7 @@ void trace_hwlat_callback(bool enter)
  * get_sample - sample the CPU TSC and look for likely hardware latencies
  *
  * Used to repeatedly capture the CPU TSC (or similar), looking for potential
- * hardware-induced latency. Called with interrupts disabled and with
- * hwlat_data.lock held.
+ * hardware-induced latency. Called with interrupts disabled.
  */
 static int get_sample(void)
 {
@@ -204,6 +203,7 @@ static int get_sample(void)
 	time_type start, t1, t2, last_t2;
 	s64 diff, outer_diff, total, last_total = 0;
 	u64 sample = 0;
+	u64 sample_width = READ_ONCE(hwlat_data.sample_width);
 	u64 thresh = tracing_thresh;
 	u64 outer_sample = 0;
 	int ret = -1;
@@ -267,7 +267,7 @@ static int get_sample(void)
 		if (diff > sample)
 			sample = diff; /* only want highest value */
 
-	} while (total <= hwlat_data.sample_width);
+	} while (total <= sample_width);
 
 	barrier(); /* finish the above in the view for NMIs */
 	trace_hwlat_callback_enabled = false;
@@ -285,8 +285,7 @@ static int get_sample(void)
 		if (kdata->nmi_total_ts)
 			do_div(kdata->nmi_total_ts, NSEC_PER_USEC);
 
-		hwlat_data.count++;
-		s.seqnum = hwlat_data.count;
+		s.seqnum = atomic64_inc_return(&hwlat_data.count);
 		s.duration = sample;
 		s.outer_duration = outer_sample;
 		s.nmi_total_ts = kdata->nmi_total_ts;
@@ -832,7 +831,7 @@ static int hwlat_tracer_init(struct trace_array *tr)
 
 	hwlat_trace = tr;
 
-	hwlat_data.count = 0;
+	atomic64_set(&hwlat_data.count, 0);
 	tr->max_latency = 0;
 	save_tracing_thresh = tracing_thresh;
 

base-commit: 24d479d26b25bce5faea3ddd9fa8f3a6c3129ea7
-- 
2.51.2


^ permalink raw reply related

* Re: [PATCH v6 2/4] tracing: Make the backup instance non-reusable
From: Masami Hiramatsu @ 2026-02-10  5:14 UTC (permalink / raw)
  To: Steven Rostedt; +Cc: Mathieu Desnoyers, linux-kernel, linux-trace-kernel
In-Reply-To: <20260209184247.4c6daccd@fedora>

On Mon, 9 Feb 2026 18:42:47 -0500
Steven Rostedt <rostedt@goodmis.org> wrote:

> On Mon, 9 Feb 2026 18:08:44 +0900
> Masami Hiramatsu (Google) <mhiramat@kernel.org> wrote:
> > > > @@ -5152,6 +5157,9 @@ tracing_cpumask_write(struct file *filp, const char __user *ubuf,
> > > >  	cpumask_var_t tracing_cpumask_new;
> > > >  	int err;
> > > >  
> > > > +	if (trace_array_is_readonly(tr))
> > > > +		return -EPERM;
> > > > +  
> > > 
> > > Shouldn't these checks be done in the open function? Doing it now is
> > > too late, as -EPERM on a write is confusing when the open for write
> > > succeeds.  
> > 
> > I've made a small program and straced. Surprisingly, for the super user,
> > open(2) does not return error on opening a readonly file with O_RDWR.
> 
> *blink*
> 
> So if on open, the trace_array_is_read_only(tr) returns true and you
> return -EPREM, it still succeeds? That sounds like a bug!

Hmm, OK. Now I found how sysfs handles it.

	/*
	 * For regular files, if the opener has CAP_DAC_OVERRIDE, open(2)
	 * succeeds regardless of the RW permissions.  sysfs had an extra
	 * layer of enforcement where open(2) fails with -EACCES regardless
	 * of CAP_DAC_OVERRIDE if the permission doesn't have the
	 * respective read or write access at all (none of S_IRUGO or
	 * S_IWUGO) or the respective operation isn't implemented.  The
	 * following flag enables that behavior.
	 */
	KERNFS_ROOT_EXTRA_OPEN_PERM_CHECK	= 0x0002,

So for the similar reason, I will make tracefs to check the permission
even if CAP_DAC_OVERRIDE is set. (But this check should be done in general,
instead of each open() operation)

Thank you,


> -- Steve


-- 
Masami Hiramatsu (Google) <mhiramat@kernel.org>

^ permalink raw reply

* [PATCH v2] tracing: Move d_max_latency out of CONFIG_FSNOTIFY protection
From: Steven Rostedt @ 2026-02-10  0:46 UTC (permalink / raw)
  To: LKML, Linux trace kernel
  Cc: Masami Hiramatsu, Mathieu Desnoyers, kernel test robot

From: Steven Rostedt <rostedt@goodmis.org>

The tracing_max_latency shouldn't be limited if CONFIG_FSNOTIFY is defined
or not and it was moved out of that protection to be always available with
CONFIG_TRACER_MAX_TRACE. All was moved out except the dentry descriptor
for it (d_max_latency) and it failed to build on some configs.

Move that out of the CONFIG_FSNOTIFY protection too.

Fixes: ba73713da50e ("tracing: Clean up use of trace_create_maxlat_file()")
Reported-by: kernel test robot <lkp@intel.com>
Closes: https://lore.kernel.org/oe-kbuild-all/202602092133.fTdojd95-lkp@intel.com/
Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
---
Changes since v1: https://lore.kernel.org/linux-trace-kernel/20260209192746.70529899@fedora/

- Rebased on my for-next branch and not the offending commit

 kernel/trace/trace.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/kernel/trace/trace.h b/kernel/trace/trace.h
index 649fdd20fc91..7894bf55743c 100644
--- a/kernel/trace/trace.h
+++ b/kernel/trace/trace.h
@@ -348,8 +348,8 @@ struct trace_array {
 	unsigned int		snapshot;
 #ifdef CONFIG_TRACER_MAX_TRACE
 	unsigned long		max_latency;
-#ifdef CONFIG_FSNOTIFY
 	struct dentry		*d_max_latency;
+#ifdef CONFIG_FSNOTIFY
 	struct work_struct	fsnotify_work;
 	struct irq_work		fsnotify_irqwork;
 #endif /* CONFIG_FSNOTIFY */
-- 
2.51.0


^ permalink raw reply related

* [PATCH] tracing: Move d_max_latency out of CONFIG_FSNOTIFY protection
From: Steven Rostedt @ 2026-02-10  0:27 UTC (permalink / raw)
  To: LKML, Linux trace kernel
  Cc: Masami Hiramatsu, Mathieu Desnoyers, kernel test robot

From: Steven Rostedt <rostedt@goodmis.org>

The tracing_max_latency shouldn't be limited if CONFIG_FSNOTIFY is defined
or not and it was moved out of that protection to be always available with
CONFIG_TRACER_MAX_TRACE. All was moved out except the dentry descriptor
for it (d_max_latency) and it failed to build on some configs.

Move that out of the CONFIG_FSNOTIFY protection too.

Fixes: ba73713da50e ("tracing: Clean up use of trace_create_maxlat_file()")
Reported-by: kernel test robot <lkp@intel.com>
Closes: https://lore.kernel.org/oe-kbuild-all/202602092133.fTdojd95-lkp@intel.com/
Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
---
 kernel/trace/trace.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/kernel/trace/trace.h b/kernel/trace/trace.h
index 31fb137e1c66..c08edaa7d179 100644
--- a/kernel/trace/trace.h
+++ b/kernel/trace/trace.h
@@ -346,8 +346,8 @@ struct trace_array {
 	spinlock_t		snapshot_trigger_lock;
 	unsigned int		snapshot;
 	unsigned long		max_latency;
-#ifdef CONFIG_FSNOTIFY
 	struct dentry		*d_max_latency;
+#ifdef CONFIG_FSNOTIFY
 	struct work_struct	fsnotify_work;
 	struct irq_work		fsnotify_irqwork;
 #endif
-- 
2.51.0


^ permalink raw reply related

* Re: [PATCH v6 2/4] tracing: Make the backup instance non-reusable
From: Steven Rostedt @ 2026-02-09 23:42 UTC (permalink / raw)
  To: Masami Hiramatsu (Google)
  Cc: Mathieu Desnoyers, linux-kernel, linux-trace-kernel
In-Reply-To: <20260209180844.c582bdbb6a4a5b737db7a0a7@kernel.org>

On Mon, 9 Feb 2026 18:08:44 +0900
Masami Hiramatsu (Google) <mhiramat@kernel.org> wrote:
> > > @@ -5152,6 +5157,9 @@ tracing_cpumask_write(struct file *filp, const char __user *ubuf,
> > >  	cpumask_var_t tracing_cpumask_new;
> > >  	int err;
> > >  
> > > +	if (trace_array_is_readonly(tr))
> > > +		return -EPERM;
> > > +  
> > 
> > Shouldn't these checks be done in the open function? Doing it now is
> > too late, as -EPERM on a write is confusing when the open for write
> > succeeds.  
> 
> I've made a small program and straced. Surprisingly, for the super user,
> open(2) does not return error on opening a readonly file with O_RDWR.

*blink*

So if on open, the trace_array_is_read_only(tr) returns true and you
return -EPREM, it still succeeds? That sounds like a bug!

-- Steve

^ permalink raw reply

* Re: [PATCH v8 6/6] x86/vdso: Enable sframe generation in VDSO
From: H. Peter Anvin @ 2026-02-09 19:13 UTC (permalink / raw)
  To: Jens Remus, linux-kernel, linux-trace-kernel, bpf, x86, linux-mm,
	Josh Poimboeuf, Steven Rostedt, Indu Bhagat
  Cc: Masami Hiramatsu, Mathieu Desnoyers, Peter Zijlstra, Ingo Molnar,
	Jiri Olsa, Arnaldo Carvalho de Melo, Namhyung Kim,
	Thomas Gleixner, Andrii Nakryiko, Jose E. Marchesi, Beau Belgrave,
	Linus Torvalds, Andrew Morton, Florian Weimer, Kees Cook,
	Carlos O'Donell, Sam James, Dylan Hatch, Borislav Petkov,
	Dave Hansen, David Hildenbrand, Liam R. Howlett, Lorenzo Stoakes,
	Michal Hocko, Mike Rapoport, Suren Baghdasaryan, Vlastimil Babka,
	Heiko Carstens, Vasily Gorbik, Steven Rostedt (Google)
In-Reply-To: <22bc8f74-1943-4ceb-bc6b-ea404ba013d9@linux.ibm.com>

On 2026-02-09 08:45, Jens Remus wrote:
> On 2/7/2026 12:08 AM, H. Peter Anvin wrote:
>> On 2026-02-06 11:36, Jens Remus wrote:
>>> From: Josh Poimboeuf <jpoimboe@kernel.org>
>>>
>>> Enable sframe generation in the VDSO library so kernel and user space
>>> can unwind through it.
>>>
>>> SFrame isn't supported for x32 or x86-32.  Discard .sframe sections for
>>> those VDSOs.
>>>
>>> [ Jens Remus: Add support for SFrame V3.  Prevent GNU_SFRAME program
>>> table entry to empty .sframe section. ]
>>>
>>
>> This will not break the x86-32 build if the assembler encounters .sframe?
> 
> I cannot follow.  Assembler option --gsframe-3 is only specified in
> vdso64/Makefile if CONFIG_AS_SFRAME3, which affects the x86-64 and x32
> VDSOs.  The latter as the x32 VDSO is built from x86-64 objects
> converted to x86-32 objects using the X32 build step.  Assembler
> directive ".cfi_sections .sframe" is no longer used in dwarf2.h, which
> affected the x86-32 VDSO if cross build on x86-64 (so that
> CONFIG_AS_SFRAME3=y).
> 
> The reason to discard .sframe in the common VDSO linker script if
> !KEEP_SFRAME is to remove it from x32 VDSO (built from x86-64 objects
> having .sframe).  It should also prevent linker errors from linkers that
> do not support R_X86_64_PC64 in x32 mode, such as the meanwhile fixed
> GNU linker:
> https://www.sourceware.org/bugzilla/show_bug.cgi?id=33807
> 

OK, the linker not handing R_X86_64_PC64 is probably a good enough reason to
discard .sframe.

The x32 ABI and object format is explicitly intended to be as close to the
x86-64 as possible; it even uses the same architectural identifier. That's
pretty much the reason we don't even bother recompiling the objects in the
first place; the 64-bit objects can simply be downconverted from ELF64 to
ELF32. This also means that the compiler doesn't need to support x32, although
objcopy and ld does.

This also means the DWARF data is the one generated for 64 bits.

This works because the following is true for all vdso entrypoints:
	- No vdso entry point access pointers or explicit "long"s *in memory*.
	  (The latter formally violates POSIX for struct timespec, but that
	   was a deliberate decision imposed by Linus at the time the x32 API
	   was defined: "no 32-bit time_t in a new ABI.")
	- No vdso entry point calls system calls that do, either.
	- No vdso entry point takes a *signed* long as an argument.
	- No vdso entry point calls system calls that differ in behavior
	  between x86-64 and x32, making it OK to call the 64-bit system call
	  from an x32 process.

"unsigned long" and pointers *can* be safely passed in argument registers
(because the architecture will zero-extend those registers), and in addition
"signed long" *can* be safely passed as returns.

If ANY of the constraints above were ever violated, OR the binary format would
start diverging to the point that objcopy can't properly convert it, then we
would need to start compiling the x32 binaries separately.


> The following works and indeed looks nicer with #if KEEP_SFRAME.  Will
> wait for further feedback on whether or not to discard .sframe in x32
> VDSO before sending a v9.

I think ld tripping over a bug justifies dropping .sframe at this point. *Make
sure* to document that in the checkin, and add a comment to vdso32.lds.S that
that is the motivation, so that if x32 gains support for .sframe in the future
(which should be simple enough, since the formats are so similar) we don't get
stuck in some kind of cargo cult programming problem.

It also will avoid issues on the off chance .sframe ends up being defined
differently for x32 for some reason, perhaps for consistency with other 32-bit
platforms.


Include the version of binutils which fixed the problem in your comments.


	-hpa



> diff --git a/arch/x86/entry/vdso/common/vdso-layout.lds.S b/arch/x86/entry/vdso/common/vdso-layout.lds.S
> @@ -60,7 +60,7 @@ SECTIONS
>  		*(.eh_frame.*)
>  	}					:text
>  
> -#ifdef KEEP_SFRAME
> +#if KEEP_SFRAME
>  	.sframe		: {
>  		KEEP (*(.sframe))
>  		*(.sframe.*)
> @@ -87,7 +87,7 @@ SECTIONS
>  		*(.discard)
>  		*(.discard.*)
>  		*(__bug_table)
> -#ifndef KEEP_SFRAME
> +#if !KEEP_SFRAME
>  		*(.sframe)
>  		*(.sframe.*)
>  #endif
> @@ -116,7 +116,7 @@ PHDRS
>  	dynamic		PT_DYNAMIC	PF_R;
>  	note		PT_NOTE		PF_R;
>  	eh_frame_hdr	PT_GNU_EH_FRAME PF_R;
> -#ifdef KEEP_SFRAME
> +#if KEEP_SFRAME
>  	sframe		PT_GNU_SFRAME	PF_R;
>  #endif
>  	gnu_stack	PT_GNU_STACK	PF_RW;
> diff --git a/arch/x86/entry/vdso/vdso32/vdso32.lds.S b/arch/x86/entry/vdso/vdso32/vdso32.lds.S
> @@ -10,6 +10,7 @@
>  #include <asm/page.h>
>  
>  #define BUILD_VDSO32
> +#define KEEP_SFRAME	false
>  
>  #include "common/vdso-layout.lds.S"
>  
> diff --git a/arch/x86/entry/vdso/vdso64/vdso64.lds.S b/arch/x86/entry/vdso/vdso64/vdso64.lds.S
> @@ -8,10 +8,7 @@
>   */
>  
>  #define BUILD_VDSO64
> -
> -#ifdef CONFIG_AS_SFRAME
> -# define KEEP_SFRAME
> -#endif
> +#define KEEP_SFRAME	(CONFIG_AS_SFRAME)

This needs to be IS_ENABLED(CONFIG_AS_SFRAME). Otherwise you get either "y" or
"CONFIG_AS_SFRAME", neither of which are what you want.

>  #include "common/vdso-layout.lds.S"
>  
> diff --git a/arch/x86/entry/vdso/vdso64/vdsox32.lds.S b/arch/x86/entry/vdso/vdso64/vdsox32.lds.S
> @@ -8,6 +8,7 @@
>   */
>  
>  #define BUILD_VDSOX32
> +#define KEEP_SFRAME	false

Is "false" supported by all appropriate cpp versions? "0" is probably a safer
choice, and matches the IS_ENABLED() output anyway.

	-hpa


^ permalink raw reply

* Re: [PATCH v13 00/18] unwind_deferred: Implement sframe handling
From: Jens Remus @ 2026-02-09 17:07 UTC (permalink / raw)
  To: Steven Rostedt, Namhyung Kim
  Cc: linux-kernel, linux-trace-kernel, bpf, x86, linux-mm,
	Josh Poimboeuf, Masami Hiramatsu, Mathieu Desnoyers,
	Peter Zijlstra, Ingo Molnar, Jiri Olsa, Arnaldo Carvalho de Melo,
	Thomas Gleixner, Andrii Nakryiko, Indu Bhagat, Jose E. Marchesi,
	Beau Belgrave, Linus Torvalds, Andrew Morton, Florian Weimer,
	Kees Cook, Carlos O'Donell, Sam James, Dylan Hatch,
	Borislav Petkov, Dave Hansen, David Hildenbrand, H. Peter Anvin,
	Liam R. Howlett, Lorenzo Stoakes, Michal Hocko, Mike Rapoport,
	Suren Baghdasaryan, Vlastimil Babka, Heiko Carstens,
	Vasily Gorbik
In-Reply-To: <20260205135411.5fb22df2@gandalf.local.home>

On 2/5/2026 7:54 PM, Steven Rostedt wrote:
> On Thu, 5 Feb 2026 10:26:10 -0800
> Namhyung Kim <namhyung@kernel.org> wrote:
> 
>>> Namhyung Kim's related perf tools deferred callchain support can be used
>>> for testing ("perf record --call-graph fp,defer" and "perf report/script").  
>>
>> Is it possible for users to choose the unwinder - frame pointer or
>> SFrame at runtime?  I feel like the option should be
>> "--call-graph sframe,defer" or just "--call-graph sframe" if it always
>> uses deferred unwinding.
> 
> Currently no, and I'm not sure we want that do we? The idea is to use the
> best option that is available. Why use frame pointers if sframe is
> available and it's being called with defer?
> 
> If there's no defer, then sframes are not available, so it defaults to the
> best option available (which will likely be frame pointers).

Maybe it would make sense not to "overload" the perf record option
"--call-graph fp,defer" and use it for all deferred unwinding methods.

What about "--call-graph defer", "--call-graph any,defer", or
"--call-graph *,defer"?

Regards,
Jens
-- 
Jens Remus
Linux on Z Development (D3303)
jremus@de.ibm.com / jremus@linux.ibm.com

IBM Deutschland Research & Development GmbH; Vorsitzender des Aufsichtsrats: Wolfgang Wendt; Geschäftsführung: David Faller; Sitz der Gesellschaft: Ehningen; Registergericht: Amtsgericht Stuttgart, HRB 243294
IBM Data Privacy Statement: https://www.ibm.com/privacy/


^ permalink raw reply

* Re: [PATCH v8 6/6] x86/vdso: Enable sframe generation in VDSO
From: Jens Remus @ 2026-02-09 16:45 UTC (permalink / raw)
  To: H. Peter Anvin, linux-kernel, linux-trace-kernel, bpf, x86,
	linux-mm, Josh Poimboeuf, Steven Rostedt, Indu Bhagat
  Cc: Masami Hiramatsu, Mathieu Desnoyers, Peter Zijlstra, Ingo Molnar,
	Jiri Olsa, Arnaldo Carvalho de Melo, Namhyung Kim,
	Thomas Gleixner, Andrii Nakryiko, Jose E. Marchesi, Beau Belgrave,
	Linus Torvalds, Andrew Morton, Florian Weimer, Kees Cook,
	Carlos O'Donell, Sam James, Dylan Hatch, Borislav Petkov,
	Dave Hansen, David Hildenbrand, Liam R. Howlett, Lorenzo Stoakes,
	Michal Hocko, Mike Rapoport, Suren Baghdasaryan, Vlastimil Babka,
	Heiko Carstens, Vasily Gorbik, Steven Rostedt (Google)
In-Reply-To: <4304d18a-f647-4709-9f29-43d9995cc24e@zytor.com>

On 2/7/2026 12:08 AM, H. Peter Anvin wrote:
> On 2026-02-06 11:36, Jens Remus wrote:
>> From: Josh Poimboeuf <jpoimboe@kernel.org>
>>
>> Enable sframe generation in the VDSO library so kernel and user space
>> can unwind through it.
>>
>> SFrame isn't supported for x32 or x86-32.  Discard .sframe sections for
>> those VDSOs.
>>
>> [ Jens Remus: Add support for SFrame V3.  Prevent GNU_SFRAME program
>> table entry to empty .sframe section. ]
>>
> 
> This will not break the x86-32 build if the assembler encounters .sframe?

I cannot follow.  Assembler option --gsframe-3 is only specified in
vdso64/Makefile if CONFIG_AS_SFRAME3, which affects the x86-64 and x32
VDSOs.  The latter as the x32 VDSO is built from x86-64 objects
converted to x86-32 objects using the X32 build step.  Assembler
directive ".cfi_sections .sframe" is no longer used in dwarf2.h, which
affected the x86-32 VDSO if cross build on x86-64 (so that
CONFIG_AS_SFRAME3=y).

The reason to discard .sframe in the common VDSO linker script if
!KEEP_SFRAME is to remove it from x32 VDSO (built from x86-64 objects
having .sframe).  It should also prevent linker errors from linkers that
do not support R_X86_64_PC64 in x32 mode, such as the meanwhile fixed
GNU linker:
https://www.sourceware.org/bugzilla/show_bug.cgi?id=33807

> 
>> Notes (jremus):
>>     Changes in v8:
>>     - Discard .sframe for x32 and x86-32 VDSOs. (Josh/Indu)
>>       Note that the use of KEEP_SFRAME enables to define it for x86-64
>>       VDSO only.  Unlike CONFIG_AS_SFRAME, which may also be defined
>>       for x32 and x86-32 VDSO.  In x32 VDSO it would result in superfluous
>>       .sframe (copied from the x86-64 build - could be removed in X32
>>       build step).  In x86-32 VDSO it would cause a bogus GNU_SFRAME
>>       program table entry.
> 
> For x32, this would be a "valid" sframe, right, even if the tools currently
> don't know how to consume it (and potentially never will)? If so, is there
> really any reason to explicitly remove it?

I am not an x86 expert.  IIUC the x32 ABI uses 4-byte pointers.  But GCC
with option -mx32 emits DWARF that suggests that at least the return
address (RA) and frame pointer (FP; rbp) are still pushed as 8-byte
values on the stack, which would be relevant for SFrame:

00000000 <foo>:
   0:   55                      push   %rbp
   1:   89 e5                   mov    %esp,%ebp
...

   LOC           CFA      rbp   ra
0000000000000000 rsp+8    u     c-8   <-- suggests RA is 8-bytes on stack
0000000000000001 rsp+16   c-16  c-8   <-- suggests FP is 8-bytes on stack
...

That could mean that technically the .sframe would be mostly valid.  The
fixed RA offset of -8 would be correct, the variable FP offset would be
tracked, and the implicit SP rule SP=CFA should be correct as well.  But
the SFrame header would incorrectly specify AMD64 as ABI/arch ID instead
of ILP32 (if I got the ELF x86-64-ABI psABI [1] correct).

AFAIK SFrame does not officially support ILP32.  At least GNU assembler
does not:

$ printf ".cfi_startproc\n.cfi_endproc\n" | as --gsframe-3 --x32 ; echo $?
Assembler messages:
{standard input}: Error: .sframe not supported for target
1

My take would be that it would be better to build x32 VDSO without
.sframe (or discard .sframe from x32 VDSO), unless it is officially
supported.

@Indu: What are your thoughts as SFrame maintainer?

[1]: ELF x86-64-ABI psABI,
     https://gitlab.com/x86-psABIs/x86-64-ABI

>>  	/*
>>  	 * Text is well-separated from actual data: there's plenty of
>>  	 * stuff that isn't used at runtime in between.
>> @@ -80,6 +87,10 @@ SECTIONS
>>  		*(.discard)
>>  		*(.discard.*)
>>  		*(__bug_table)
>> +#ifndef KEEP_SFRAME
>> +		*(.sframe)
>> +		*(.sframe.*)
>> +#endif
> 
> This #ifndef is actually not necessary: if we have already "consumed" the
> .sframe* sections they will not be encountered here.

It is necessary to remove .sframe from x86-64 objects (created by the
x86-64 VDSO build) converted to x86-32 objects in the X32 build step for
x32 VDSO, provided SFrame is not supported for x32.  The x86-64 VDSO has
.sframe, as the x86-64 VDSO linker script defines KEEP_SFRAME.  The x32
VDSO has .sframe removed, as the x32 linker script does not define
KEEP_SFRAME.

An alternative to the #ifndef (or #if !KEEP_SFRAME) would be to remove
the .sframe in the X32 build step:

diff --git a/arch/x86/entry/vdso/vdso64/Makefile b/arch/x86/entry/vdso/vdso64/Makefile
@@ -23,14 +24,14 @@ include $(src)/../common/Makefile.include
 #
 # Build x32 vDSO image:
 # 1. Compile x32 vDSO as 64bit.
-# 2. Convert object files to x32.
+# 2. Convert object files to x32 and remove .sframe.
 # 3. Build x32 VDSO image with x32 objects, which contains 64bit codes
 # so that it can reach 64bit address space with 64bit pointers.
 #

 # Convert 64bit object file to x32 for x32 vDSO.
 quiet_cmd_x32 = X32     $@
-      cmd_x32 = $(OBJCOPY) -O elf32-x86-64 $< $@
+      cmd_x32 = $(OBJCOPY) -O elf32-x86-64 -R .sframe $< $@

 $(obj)/%-x32.o: $(obj)/%.o FORCE
	$(call if_changed,x32)

KEEP_SFRAME (or then maybe better HAVE_SFRAME) would then still be
required to only emit a program table entry, if .sframe was generated.

Note that AS_SFRAME only indicates whether the assembler supports to
generate .sframe.  Not whether if it should actually be done.  That is
selected by adding the --gsframe-3 assembler option and defining
KEEP_SFRAME to true, which is done in the respective VDSO Makefile and
linker script.

> I would prefer to have KEEP_SFRAME always defined (as true or false, and using
> #if) instead of using #ifdef. I believe that also means you can do:
> 
> #define KEEP_SFRAME IS_ENABLED(CONFIG_AS_SFRAME)
> 
> ... instead of #ifdef.

The following works and indeed looks nicer with #if KEEP_SFRAME.  Will
wait for further feedback on whether or not to discard .sframe in x32
VDSO before sending a v9.

diff --git a/arch/x86/entry/vdso/common/vdso-layout.lds.S b/arch/x86/entry/vdso/common/vdso-layout.lds.S
@@ -60,7 +60,7 @@ SECTIONS
 		*(.eh_frame.*)
 	}					:text
 
-#ifdef KEEP_SFRAME
+#if KEEP_SFRAME
 	.sframe		: {
 		KEEP (*(.sframe))
 		*(.sframe.*)
@@ -87,7 +87,7 @@ SECTIONS
 		*(.discard)
 		*(.discard.*)
 		*(__bug_table)
-#ifndef KEEP_SFRAME
+#if !KEEP_SFRAME
 		*(.sframe)
 		*(.sframe.*)
 #endif
@@ -116,7 +116,7 @@ PHDRS
 	dynamic		PT_DYNAMIC	PF_R;
 	note		PT_NOTE		PF_R;
 	eh_frame_hdr	PT_GNU_EH_FRAME PF_R;
-#ifdef KEEP_SFRAME
+#if KEEP_SFRAME
 	sframe		PT_GNU_SFRAME	PF_R;
 #endif
 	gnu_stack	PT_GNU_STACK	PF_RW;
diff --git a/arch/x86/entry/vdso/vdso32/vdso32.lds.S b/arch/x86/entry/vdso/vdso32/vdso32.lds.S
@@ -10,6 +10,7 @@
 #include <asm/page.h>
 
 #define BUILD_VDSO32
+#define KEEP_SFRAME	false
 
 #include "common/vdso-layout.lds.S"
 
diff --git a/arch/x86/entry/vdso/vdso64/vdso64.lds.S b/arch/x86/entry/vdso/vdso64/vdso64.lds.S
@@ -8,10 +8,7 @@
  */
 
 #define BUILD_VDSO64
-
-#ifdef CONFIG_AS_SFRAME
-# define KEEP_SFRAME
-#endif
+#define KEEP_SFRAME	(CONFIG_AS_SFRAME)
 
 #include "common/vdso-layout.lds.S"
 
diff --git a/arch/x86/entry/vdso/vdso64/vdsox32.lds.S b/arch/x86/entry/vdso/vdso64/vdsox32.lds.S
@@ -8,6 +8,7 @@
  */
 
 #define BUILD_VDSOX32
+#define KEEP_SFRAME	false
 
 #include "common/vdso-layout.lds.S"
 
Thanks and regards,
Jens
-- 
Jens Remus
Linux on Z Development (D3303)
jremus@de.ibm.com / jremus@linux.ibm.com

IBM Deutschland Research & Development GmbH; Vorsitzender des Aufsichtsrats: Wolfgang Wendt; Geschäftsführung: David Faller; Sitz der Gesellschaft: Ehningen; Registergericht: Amtsgericht Stuttgart, HRB 243294
IBM Data Privacy Statement: https://www.ibm.com/privacy/


^ permalink raw reply

* Re: [PATCH] tracing: Fix soft lockup when lseeking trace file
From: Steven Rostedt @ 2026-02-09 16:10 UTC (permalink / raw)
  To: Tengda Wu, Peter Zijlstra
  Cc: Masami Hiramatsu, Mathieu Desnoyers, Ingo Molnar, Thomas Gleixner,
	Arnaldo Carvalho de Melo, linux-trace-kernel, linux-kernel
In-Reply-To: <20260209093101.1623454-1-wutengda@huaweicloud.com>


Peter,

When is PREEMPT_NONE going to be removed? If that's going into 7.0, I want
to stop accepting these scattered cond_resched() additions.

-- Steve


On Mon,  9 Feb 2026 17:31:01 +0800
Tengda Wu <wutengda@huaweicloud.com> wrote:

> A soft lockup may occur when accessing trace file via lseek while
> tracing is active and a large offset is provided. The call trace
> is shown below:
> 
> watchdog: BUG: soft lockup - CPU#3 stuck for 26s! [poc:141]
> CPU: 3 UID: 0 PID: 141 Comm: poc Not tainted 6.19.0 #1 PREEMPT(none)
> Call Trace:
>   ring_buffer_iter_peek
>   peek_next_entry
>   __find_next_entry
>   trace_find_next_entry_inc
>   s_next
>   traverse.part.0
>   seq_lseek
>   tracing_lseek
>   __x64_sys_lseek
>   do_syscall_64
>   entry_SYSCALL_64_after_hwframe
> 
> The root cause is that the lseek implementation for trace files
> is based on seq_lseek, which contains a loop that repeatedly calls
> show() and next() functions until the position reaches the target
> offset. Since no scheduling point is set within this loop, a large
> offset can cause the CPU to be stuck in the loop for an extended
> period, triggering the soft lockup detector.
> 
> Fixed by adding cond_resched() in s_next().
> 
> Fixes: bc0c38d139ec ("ftrace: latency tracer infrastructure")
> Signed-off-by: Tengda Wu <wutengda@huaweicloud.com>
> ---
>  kernel/trace/trace.c | 2 ++
>  1 file changed, 2 insertions(+)
> 
> diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c
> index 8bd4ec08fb36..3afe148ef683 100644
> --- a/kernel/trace/trace.c
> +++ b/kernel/trace/trace.c
> @@ -3928,6 +3928,8 @@ static void *s_next(struct seq_file *m, void *v, loff_t *pos)
>  
>  	iter->pos = *pos;
>  
> +	cond_resched();
> +
>  	return ent;
>  }
>  


^ permalink raw reply

* [PATCH] tracing: Fix soft lockup when lseeking trace file
From: Tengda Wu @ 2026-02-09  9:31 UTC (permalink / raw)
  To: Steven Rostedt, Masami Hiramatsu
  Cc: Mathieu Desnoyers, Ingo Molnar, Thomas Gleixner,
	Arnaldo Carvalho de Melo, linux-trace-kernel, linux-kernel,
	Tengda Wu

A soft lockup may occur when accessing trace file via lseek while
tracing is active and a large offset is provided. The call trace
is shown below:

watchdog: BUG: soft lockup - CPU#3 stuck for 26s! [poc:141]
CPU: 3 UID: 0 PID: 141 Comm: poc Not tainted 6.19.0 #1 PREEMPT(none)
Call Trace:
  ring_buffer_iter_peek
  peek_next_entry
  __find_next_entry
  trace_find_next_entry_inc
  s_next
  traverse.part.0
  seq_lseek
  tracing_lseek
  __x64_sys_lseek
  do_syscall_64
  entry_SYSCALL_64_after_hwframe

The root cause is that the lseek implementation for trace files
is based on seq_lseek, which contains a loop that repeatedly calls
show() and next() functions until the position reaches the target
offset. Since no scheduling point is set within this loop, a large
offset can cause the CPU to be stuck in the loop for an extended
period, triggering the soft lockup detector.

Fixed by adding cond_resched() in s_next().

Fixes: bc0c38d139ec ("ftrace: latency tracer infrastructure")
Signed-off-by: Tengda Wu <wutengda@huaweicloud.com>
---
 kernel/trace/trace.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c
index 8bd4ec08fb36..3afe148ef683 100644
--- a/kernel/trace/trace.c
+++ b/kernel/trace/trace.c
@@ -3928,6 +3928,8 @@ static void *s_next(struct seq_file *m, void *v, loff_t *pos)
 
 	iter->pos = *pos;
 
+	cond_resched();
+
 	return ent;
 }
 
-- 
2.34.1


^ permalink raw reply related

* [PATCH v7 4/4] tracing/Documentation: Add a section about backup instance
From: Masami Hiramatsu (Google) @ 2026-02-09  9:25 UTC (permalink / raw)
  To: Steven Rostedt
  Cc: Masami Hiramatsu, Mathieu Desnoyers, linux-kernel,
	linux-trace-kernel
In-Reply-To: <177062912135.1230888.17419570791737357433.stgit@mhiramat.tok.corp.google.com>

From: Masami Hiramatsu (Google) <mhiramat@kernel.org>

Add a section about backup instance to the debugging.rst.

Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
  Changes in v6:
   - Fix typos.
---
 Documentation/trace/debugging.rst |   19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/Documentation/trace/debugging.rst b/Documentation/trace/debugging.rst
index 4d88c346fc38..15857951b506 100644
--- a/Documentation/trace/debugging.rst
+++ b/Documentation/trace/debugging.rst
@@ -159,3 +159,22 @@ If setting it from the kernel command line, it is recommended to also
 disable tracing with the "traceoff" flag, and enable tracing after boot up.
 Otherwise the trace from the most recent boot will be mixed with the trace
 from the previous boot, and may make it confusing to read.
+
+Using a backup instance for keeping previous boot data
+------------------------------------------------------
+
+It is also possible to record trace data at system boot time by specifying
+events with the persistent ring buffer, but in this case the data before the
+reboot will be lost before it can be read. This problem can be solved by a
+backup instance. From the kernel command line::
+
+  reserve_mem=12M:4096:trace trace_instance=boot_map@trace,sched,irq trace_instance=backup=boot_map
+
+On boot up, the previous data in the "boot_map" is copied to the "backup"
+instance, and the "sched:*" and "irq:*" events for the current boot are traced
+in the "boot_map". Thus the user can read the previous boot data from the "backup"
+instance without stopping the trace.
+
+Note that this "backup" instance is readonly, and will be removed automatically
+if you clear the trace data or read out all trace data from the "trace_pipe"
+or the "trace_pipe_raw" files.
\ No newline at end of file


^ permalink raw reply related

* [PATCH v7 3/4] tracing: Remove the backup instance automatically after read
From: Masami Hiramatsu (Google) @ 2026-02-09  9:25 UTC (permalink / raw)
  To: Steven Rostedt
  Cc: Masami Hiramatsu, Mathieu Desnoyers, linux-kernel,
	linux-trace-kernel
In-Reply-To: <177062912135.1230888.17419570791737357433.stgit@mhiramat.tok.corp.google.com>

From: Masami Hiramatsu (Google) <mhiramat@kernel.org>

Since the backup instance is readonly, after reading all data
via pipe, no data is left on the instance. Thus it can be
removed safely after closing all files.
This also removes it if user resets the ring buffer manually
via 'trace' file.

Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
 Changes in v6:
   - Fix typo in comment.
   - Only when there is a readonly trace array, initialize autoremove_wq.
   - Fix to exit loop in trace_array_get() if tr is found in the list.
 Changes in v4:
   - Update description.
---
 kernel/trace/trace.c |   61 +++++++++++++++++++++++++++++++++++++++++++++++++-
 kernel/trace/trace.h |    6 +++++
 2 files changed, 66 insertions(+), 1 deletion(-)

diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c
index eee1f283c5f4..3dd9896612d5 100644
--- a/kernel/trace/trace.c
+++ b/kernel/trace/trace.c
@@ -578,6 +578,51 @@ void trace_set_ring_buffer_expanded(struct trace_array *tr)
 	tr->ring_buffer_expanded = true;
 }
 
+static int __remove_instance(struct trace_array *tr);
+
+static void trace_array_autoremove(struct work_struct *work)
+{
+	struct trace_array *tr = container_of(work, struct trace_array, autoremove_work);
+
+	guard(mutex)(&event_mutex);
+	guard(mutex)(&trace_types_lock);
+
+	/*
+	 * This can be fail if someone gets @tr before starting this
+	 * function, but in that case, this will be kicked again when
+	 * putting it. So we don't care about the result.
+	 */
+	__remove_instance(tr);
+}
+
+static struct workqueue_struct *autoremove_wq;
+
+static void trace_array_kick_autoremove(struct trace_array *tr)
+{
+	if (autoremove_wq && !work_pending(&tr->autoremove_work))
+		queue_work(autoremove_wq, &tr->autoremove_work);
+}
+
+static void trace_array_cancel_autoremove(struct trace_array *tr)
+{
+	if (autoremove_wq && work_pending(&tr->autoremove_work))
+		cancel_work(&tr->autoremove_work);
+}
+
+static void trace_array_init_autoremove(struct trace_array *tr)
+{
+	INIT_WORK(&tr->autoremove_work, trace_array_autoremove);
+
+	/* Only readonly trace_array can kick the autoremove. */
+	if (!trace_array_is_readonly(tr) || autoremove_wq)
+		return;
+
+	autoremove_wq = alloc_workqueue("tr_autoremove_wq",
+					WQ_UNBOUND | WQ_HIGHPRI, 0);
+	if (!autoremove_wq)
+		pr_warn("Unable to allocate tr_autoremove_wq. autoremove disabled.\n");
+}
+
 LIST_HEAD(ftrace_trace_arrays);
 
 int trace_array_get(struct trace_array *this_tr)
@@ -587,7 +632,8 @@ int trace_array_get(struct trace_array *this_tr)
 	guard(mutex)(&trace_types_lock);
 	list_for_each_entry(tr, &ftrace_trace_arrays, list) {
 		if (tr == this_tr) {
-			tr->ref++;
+			if (!tr->free_on_close)
+				tr->ref++;
 			return 0;
 		}
 	}
@@ -599,6 +645,12 @@ static void __trace_array_put(struct trace_array *this_tr)
 {
 	WARN_ON(!this_tr->ref);
 	this_tr->ref--;
+	/*
+	 * When free_on_close is set, prepare removing the array
+	 * when the last reference is released.
+	 */
+	if (this_tr->ref == 1 && this_tr->free_on_close)
+		trace_array_kick_autoremove(this_tr);
 }
 
 /**
@@ -5471,6 +5523,10 @@ static void update_last_data(struct trace_array *tr)
 	/* Only if the buffer has previous boot data clear and update it. */
 	tr->flags &= ~TRACE_ARRAY_FL_LAST_BOOT;
 
+	/* If this is a backup instance, mark it for autoremove. */
+	if (tr->flags & TRACE_ARRAY_FL_VMALLOC)
+		tr->free_on_close = true;
+
 	/* Reset the module list and reload them */
 	if (tr->scratch) {
 		struct trace_scratch *tscratch = tr->scratch;
@@ -9619,6 +9675,8 @@ trace_array_create_systems(const char *name, const char *systems,
 	if (ftrace_allocate_ftrace_ops(tr) < 0)
 		goto out_free_tr;
 
+	trace_array_init_autoremove(tr);
+
 	ftrace_init_trace_array(tr);
 
 	init_trace_flags_index(tr);
@@ -9767,6 +9825,7 @@ static int __remove_instance(struct trace_array *tr)
 	if (update_marker_trace(tr, 0))
 		synchronize_rcu();
 
+	trace_array_cancel_autoremove(tr);
 	tracing_set_nop(tr);
 	clear_ftrace_function_probes(tr);
 	event_trace_del_tracer(tr);
diff --git a/kernel/trace/trace.h b/kernel/trace/trace.h
index 393be92768f1..48b94759ba1c 100644
--- a/kernel/trace/trace.h
+++ b/kernel/trace/trace.h
@@ -450,6 +450,12 @@ struct trace_array {
 	 * we do not waste memory on systems that are not using tracing.
 	 */
 	bool ring_buffer_expanded;
+	/*
+	 * If the ring buffer is a read only backup instance, it will be
+	 * removed after dumping all data via pipe, because no readable data.
+	 */
+	bool free_on_close;
+	struct work_struct	autoremove_work;
 };
 
 enum {


^ permalink raw reply related

* [PATCH v7 2/4] tracing: Make the backup instance non-reusable
From: Masami Hiramatsu (Google) @ 2026-02-09  9:25 UTC (permalink / raw)
  To: Steven Rostedt
  Cc: Masami Hiramatsu, Mathieu Desnoyers, linux-kernel,
	linux-trace-kernel
In-Reply-To: <177062912135.1230888.17419570791737357433.stgit@mhiramat.tok.corp.google.com>

From: Masami Hiramatsu (Google) <mhiramat@kernel.org>

Since there is no reason to reuse the backup instance, make it
readonly (but erasable).
Note that only backup instances are readonly, because
other trace instances will be empty unless it is writable.
Only backup instances have copy entries from the original.

With this change, most of the trace control files are removed
from the backup instance, including eventfs enable/filter etc.

 # find /sys/kernel/tracing/instances/backup/events/ | wc -l
 4093
 # find /sys/kernel/tracing/instances/boot_map/events/ | wc -l
 9573

Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
 Changes in v7:
   - Return -EACCES instead of -EPERM.
 Changes in v6:
   - Remove tracing_on file from readonly instances.
   - Remove unused writable_mode from tracing_init_tracefs_percpu().
   - Cleanup init_tracer_tracefs() and create_event_toplevel_files().
   - Remove TRACE_MODE_WRITE_MASK.
   - Add TRACE_ARRAY_FL_RDONLY.
 Changes in v5:
   - Rebased on the latest for-next (and hide show_event_filters/triggers
     if the instance is readonly.
 Changes in v4:
  - Make trace data erasable. (not reusable)
 Changes in v3:
  - Resuse the beginning part of event_entries for readonly files.
  - Remove readonly file_operations and checking readonly flag in
    each write operation.
 Changes in v2:
  - Use readonly file_operations to prohibit writing instead of
    checking flags in write() callbacks.
  - Remove writable files from eventfs.
---
 kernel/trace/trace.c        |   94 +++++++++++++++++++++++++++++--------------
 kernel/trace/trace.h        |    7 +++
 kernel/trace/trace_boot.c   |    5 +-
 kernel/trace/trace_events.c |   76 ++++++++++++++++++++---------------
 4 files changed, 117 insertions(+), 65 deletions(-)

diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c
index 9e1cff78a1ca..eee1f283c5f4 100644
--- a/kernel/trace/trace.c
+++ b/kernel/trace/trace.c
@@ -4286,6 +4286,11 @@ static ssize_t
 tracing_write_stub(struct file *filp, const char __user *ubuf,
 		   size_t count, loff_t *ppos)
 {
+	struct trace_array *tr = file_inode(filp)->i_private;
+
+	if (trace_array_is_readonly(tr))
+		return -EACCES;
+
 	return count;
 }
 
@@ -4386,6 +4391,9 @@ tracing_cpumask_write(struct file *filp, const char __user *ubuf,
 	cpumask_var_t tracing_cpumask_new;
 	int err;
 
+	if (trace_array_is_readonly(tr))
+		return -EACCES;
+
 	if (count == 0 || count > KMALLOC_MAX_SIZE)
 		return -EINVAL;
 
@@ -5665,6 +5673,9 @@ tracing_set_trace_write(struct file *filp, const char __user *ubuf,
 	size_t ret;
 	int err;
 
+	if (trace_array_is_readonly(tr))
+		return -EACCES;
+
 	ret = cnt;
 
 	if (cnt > MAX_TRACER_SIZE)
@@ -6299,6 +6310,9 @@ tracing_entries_write(struct file *filp, const char __user *ubuf,
 	unsigned long val;
 	int ret;
 
+	if (trace_array_is_readonly(tr))
+		return -EACCES;
+
 	ret = kstrtoul_from_user(ubuf, cnt, 10, &val);
 	if (ret)
 		return ret;
@@ -7053,6 +7067,9 @@ static ssize_t tracing_clock_write(struct file *filp, const char __user *ubuf,
 	const char *clockstr;
 	int ret;
 
+	if (trace_array_is_readonly(tr))
+		return -EACCES;
+
 	if (cnt >= sizeof(buf))
 		return -EINVAL;
 
@@ -9050,6 +9067,9 @@ rb_simple_write(struct file *filp, const char __user *ubuf,
 	unsigned long val;
 	int ret;
 
+	if (trace_array_is_readonly(tr))
+		return -EACCES;
+
 	ret = kstrtoul_from_user(ubuf, cnt, 10, &val);
 	if (ret)
 		return ret;
@@ -9156,6 +9176,9 @@ buffer_subbuf_size_write(struct file *filp, const char __user *ubuf,
 	int pages;
 	int ret;
 
+	if (trace_array_is_readonly(tr))
+		return -EACCES;
+
 	ret = kstrtoul_from_user(ubuf, cnt, 10, &val);
 	if (ret)
 		return ret;
@@ -9836,17 +9859,22 @@ static __init void create_trace_instances(struct dentry *d_tracer)
 static void
 init_tracer_tracefs(struct trace_array *tr, struct dentry *d_tracer)
 {
+	umode_t writable_mode = TRACE_MODE_WRITE;
 	int cpu;
 
+	if (trace_array_is_readonly(tr))
+		writable_mode = TRACE_MODE_READ;
+
 	trace_create_file("available_tracers", TRACE_MODE_READ, d_tracer,
-			tr, &show_traces_fops);
+			  tr, &show_traces_fops);
 
-	trace_create_file("current_tracer", TRACE_MODE_WRITE, d_tracer,
-			tr, &set_tracer_fops);
+	trace_create_file("current_tracer", writable_mode, d_tracer,
+			  tr, &set_tracer_fops);
 
-	trace_create_file("tracing_cpumask", TRACE_MODE_WRITE, d_tracer,
+	trace_create_file("tracing_cpumask", writable_mode, d_tracer,
 			  tr, &tracing_cpumask_fops);
 
+	/* Options are used for changing print-format even for readonly instance. */
 	trace_create_file("trace_options", TRACE_MODE_WRITE, d_tracer,
 			  tr, &tracing_iter_fops);
 
@@ -9856,12 +9884,36 @@ init_tracer_tracefs(struct trace_array *tr, struct dentry *d_tracer)
 	trace_create_file("trace_pipe", TRACE_MODE_READ, d_tracer,
 			  tr, &tracing_pipe_fops);
 
-	trace_create_file("buffer_size_kb", TRACE_MODE_WRITE, d_tracer,
+	trace_create_file("buffer_size_kb", writable_mode, d_tracer,
 			  tr, &tracing_entries_fops);
 
 	trace_create_file("buffer_total_size_kb", TRACE_MODE_READ, d_tracer,
 			  tr, &tracing_total_entries_fops);
 
+	trace_create_file("trace_clock", writable_mode, d_tracer, tr,
+			  &trace_clock_fops);
+
+	trace_create_file("timestamp_mode", TRACE_MODE_READ, d_tracer, tr,
+			  &trace_time_stamp_mode_fops);
+
+	tr->buffer_percent = 50;
+
+	trace_create_file("buffer_subbuf_size_kb", writable_mode, d_tracer,
+			  tr, &buffer_subbuf_size_fops);
+
+	create_trace_options_dir(tr);
+
+	if (tr->range_addr_start)
+		trace_create_file("last_boot_info", TRACE_MODE_READ, d_tracer,
+				  tr, &last_boot_fops);
+
+	for_each_tracing_cpu(cpu)
+		tracing_init_tracefs_percpu(tr, cpu);
+
+	/* Read-only instance has above files only. */
+	if (trace_array_is_readonly(tr))
+		return;
+
 	trace_create_file("free_buffer", 0200, d_tracer,
 			  tr, &tracing_free_buffer_fops);
 
@@ -9873,49 +9925,29 @@ init_tracer_tracefs(struct trace_array *tr, struct dentry *d_tracer)
 	trace_create_file("trace_marker_raw", 0220, d_tracer,
 			  tr, &tracing_mark_raw_fops);
 
-	trace_create_file("trace_clock", TRACE_MODE_WRITE, d_tracer, tr,
-			  &trace_clock_fops);
-
-	trace_create_file("tracing_on", TRACE_MODE_WRITE, d_tracer,
-			  tr, &rb_simple_fops);
-
-	trace_create_file("timestamp_mode", TRACE_MODE_READ, d_tracer, tr,
-			  &trace_time_stamp_mode_fops);
-
-	tr->buffer_percent = 50;
-
 	trace_create_file("buffer_percent", TRACE_MODE_WRITE, d_tracer,
-			tr, &buffer_percent_fops);
-
-	trace_create_file("buffer_subbuf_size_kb", TRACE_MODE_WRITE, d_tracer,
-			  tr, &buffer_subbuf_size_fops);
+			  tr, &buffer_percent_fops);
 
 	trace_create_file("syscall_user_buf_size", TRACE_MODE_WRITE, d_tracer,
-			 tr, &tracing_syscall_buf_fops);
+			  tr, &tracing_syscall_buf_fops);
 
-	create_trace_options_dir(tr);
+	trace_create_file("tracing_on", TRACE_MODE_WRITE, d_tracer,
+			  tr, &rb_simple_fops);
 
 	trace_create_maxlat_file(tr, d_tracer);
 
 	if (ftrace_create_function_files(tr, d_tracer))
 		MEM_FAIL(1, "Could not allocate function filter files");
 
-	if (tr->range_addr_start) {
-		trace_create_file("last_boot_info", TRACE_MODE_READ, d_tracer,
-				  tr, &last_boot_fops);
 #ifdef CONFIG_TRACER_SNAPSHOT
-	} else {
+	if (!tr->range_addr_start)
 		trace_create_file("snapshot", TRACE_MODE_WRITE, d_tracer,
 				  tr, &snapshot_fops);
 #endif
-	}
 
 	trace_create_file("error_log", TRACE_MODE_WRITE, d_tracer,
 			  tr, &tracing_err_log_fops);
 
-	for_each_tracing_cpu(cpu)
-		tracing_init_tracefs_percpu(tr, cpu);
-
 	ftrace_init_tracefs(tr, d_tracer);
 }
 
@@ -10742,7 +10774,7 @@ __init static void enable_instances(void)
 		 * Backup buffers can be freed but need vfree().
 		 */
 		if (backup)
-			tr->flags |= TRACE_ARRAY_FL_VMALLOC;
+			tr->flags |= TRACE_ARRAY_FL_VMALLOC | TRACE_ARRAY_FL_RDONLY;
 
 		if (start || backup) {
 			tr->flags |= TRACE_ARRAY_FL_BOOT | TRACE_ARRAY_FL_LAST_BOOT;
diff --git a/kernel/trace/trace.h b/kernel/trace/trace.h
index 649fdd20fc91..393be92768f1 100644
--- a/kernel/trace/trace.h
+++ b/kernel/trace/trace.h
@@ -459,6 +459,7 @@ enum {
 	TRACE_ARRAY_FL_MOD_INIT		= BIT(3),
 	TRACE_ARRAY_FL_MEMMAP		= BIT(4),
 	TRACE_ARRAY_FL_VMALLOC		= BIT(5),
+	TRACE_ARRAY_FL_RDONLY		= BIT(6),
 };
 
 #ifdef CONFIG_MODULES
@@ -488,6 +489,12 @@ extern unsigned long trace_adjust_address(struct trace_array *tr, unsigned long
 
 extern struct trace_array *printk_trace;
 
+static inline bool trace_array_is_readonly(struct trace_array *tr)
+{
+	/* backup instance is read only. */
+	return tr->flags & TRACE_ARRAY_FL_RDONLY;
+}
+
 /*
  * The global tracer (top) should be the first trace array added,
  * but we check the flag anyway.
diff --git a/kernel/trace/trace_boot.c b/kernel/trace/trace_boot.c
index dbe29b4c6a7a..2ca2541c8a58 100644
--- a/kernel/trace/trace_boot.c
+++ b/kernel/trace/trace_boot.c
@@ -61,7 +61,8 @@ trace_boot_set_instance_options(struct trace_array *tr, struct xbc_node *node)
 		v = memparse(p, NULL);
 		if (v < PAGE_SIZE)
 			pr_err("Buffer size is too small: %s\n", p);
-		if (tracing_resize_ring_buffer(tr, v, RING_BUFFER_ALL_CPUS) < 0)
+		if (trace_array_is_readonly(tr) ||
+		    tracing_resize_ring_buffer(tr, v, RING_BUFFER_ALL_CPUS) < 0)
 			pr_err("Failed to resize trace buffer to %s\n", p);
 	}
 
@@ -597,7 +598,7 @@ trace_boot_enable_tracer(struct trace_array *tr, struct xbc_node *node)
 
 	p = xbc_node_find_value(node, "tracer", NULL);
 	if (p && *p != '\0') {
-		if (tracing_set_tracer(tr, p) < 0)
+		if (trace_array_is_readonly(tr) || tracing_set_tracer(tr, p) < 0)
 			pr_err("Failed to set given tracer: %s\n", p);
 	}
 
diff --git a/kernel/trace/trace_events.c b/kernel/trace/trace_events.c
index 61fe01dce7a6..b493cbdf0ea0 100644
--- a/kernel/trace/trace_events.c
+++ b/kernel/trace/trace_events.c
@@ -1396,6 +1396,9 @@ static int __ftrace_set_clr_event(struct trace_array *tr, const char *match,
 {
 	int ret;
 
+	if (trace_array_is_readonly(tr))
+		return -EACCES;
+
 	mutex_lock(&event_mutex);
 	ret = __ftrace_set_clr_event_nolock(tr, match, sub, event, set, mod);
 	mutex_unlock(&event_mutex);
@@ -2968,8 +2971,8 @@ event_subsystem_dir(struct trace_array *tr, const char *name,
 	} else
 		__get_system(system);
 
-	/* ftrace only has directories no files */
-	if (strcmp(name, "ftrace") == 0)
+	/* ftrace only has directories no files, readonly instance too. */
+	if (strcmp(name, "ftrace") == 0 || trace_array_is_readonly(tr))
 		nr_entries = 0;
 	else
 		nr_entries = ARRAY_SIZE(system_entries);
@@ -3134,28 +3137,30 @@ event_create_dir(struct eventfs_inode *parent, struct trace_event_file *file)
 	int ret;
 	static struct eventfs_entry event_entries[] = {
 		{
-			.name		= "enable",
+			.name		= "format",
 			.callback	= event_callback,
-			.release	= event_release,
 		},
+#ifdef CONFIG_PERF_EVENTS
 		{
-			.name		= "filter",
+			.name		= "id",
 			.callback	= event_callback,
 		},
+#endif
+#define NR_RO_EVENT_ENTRIES	(1 + IS_ENABLED(CONFIG_PERF_EVENTS))
+/* Readonly files must be above this line and counted by NR_RO_EVENT_ENTRIES. */
 		{
-			.name		= "trigger",
+			.name		= "enable",
 			.callback	= event_callback,
+			.release	= event_release,
 		},
 		{
-			.name		= "format",
+			.name		= "filter",
 			.callback	= event_callback,
 		},
-#ifdef CONFIG_PERF_EVENTS
 		{
-			.name		= "id",
+			.name		= "trigger",
 			.callback	= event_callback,
 		},
-#endif
 #ifdef CONFIG_HIST_TRIGGERS
 		{
 			.name		= "hist",
@@ -3188,7 +3193,10 @@ event_create_dir(struct eventfs_inode *parent, struct trace_event_file *file)
 	if (!e_events)
 		return -ENOMEM;
 
-	nr_entries = ARRAY_SIZE(event_entries);
+	if (trace_array_is_readonly(tr))
+		nr_entries = NR_RO_EVENT_ENTRIES;
+	else
+		nr_entries = ARRAY_SIZE(event_entries);
 
 	name = trace_event_name(call);
 	ei = eventfs_create_dir(name, e_events, event_entries, nr_entries, file);
@@ -4527,31 +4535,44 @@ create_event_toplevel_files(struct dentry *parent, struct trace_array *tr)
 	int nr_entries;
 	static struct eventfs_entry events_entries[] = {
 		{
-			.name		= "enable",
+			.name		= "header_page",
 			.callback	= events_callback,
 		},
 		{
-			.name		= "header_page",
+			.name		= "header_event",
 			.callback	= events_callback,
 		},
+#define NR_RO_TOP_ENTRIES	2
+/* Readonly files must be above this line and counted by NR_RO_TOP_ENTRIES. */
 		{
-			.name		= "header_event",
+			.name		= "enable",
 			.callback	= events_callback,
 		},
 	};
 
-	entry = trace_create_file("set_event", TRACE_MODE_WRITE, parent,
-				  tr, &ftrace_set_event_fops);
-	if (!entry)
-		return -ENOMEM;
+	if (!trace_array_is_readonly(tr)) {
+		entry = trace_create_file("set_event", TRACE_MODE_WRITE, parent,
+					tr, &ftrace_set_event_fops);
+		if (!entry)
+			return -ENOMEM;
 
-	trace_create_file("show_event_filters", TRACE_MODE_READ, parent, tr,
-			  &ftrace_show_event_filters_fops);
+		/* There are not as crucial, just warn if they are not created */
+		trace_create_file("show_event_filters", TRACE_MODE_READ, parent, tr,
+				&ftrace_show_event_filters_fops);
 
-	trace_create_file("show_event_triggers", TRACE_MODE_READ, parent, tr,
-			  &ftrace_show_event_triggers_fops);
+		trace_create_file("show_event_triggers", TRACE_MODE_READ, parent, tr,
+				&ftrace_show_event_triggers_fops);
 
-	nr_entries = ARRAY_SIZE(events_entries);
+		trace_create_file("set_event_pid", TRACE_MODE_WRITE, parent,
+				tr, &ftrace_set_event_pid_fops);
+
+		trace_create_file("set_event_notrace_pid",
+				TRACE_MODE_WRITE, parent, tr,
+				&ftrace_set_event_notrace_pid_fops);
+		nr_entries = ARRAY_SIZE(events_entries);
+	} else {
+		nr_entries = NR_RO_TOP_ENTRIES;
+	}
 
 	e_events = eventfs_create_events_dir("events", parent, events_entries,
 					     nr_entries, tr);
@@ -4560,15 +4581,6 @@ create_event_toplevel_files(struct dentry *parent, struct trace_array *tr)
 		return -ENOMEM;
 	}
 
-	/* There are not as crucial, just warn if they are not created */
-
-	trace_create_file("set_event_pid", TRACE_MODE_WRITE, parent,
-			  tr, &ftrace_set_event_pid_fops);
-
-	trace_create_file("set_event_notrace_pid",
-			  TRACE_MODE_WRITE, parent, tr,
-			  &ftrace_set_event_notrace_pid_fops);
-
 	tr->event_dir = e_events;
 
 	return 0;


^ permalink raw reply related

* [PATCH v7 1/4] tracing: Reset last_boot_info if ring buffer is reset
From: Masami Hiramatsu (Google) @ 2026-02-09  9:25 UTC (permalink / raw)
  To: Steven Rostedt
  Cc: Masami Hiramatsu, Mathieu Desnoyers, linux-kernel,
	linux-trace-kernel
In-Reply-To: <177062912135.1230888.17419570791737357433.stgit@mhiramat.tok.corp.google.com>

From: Masami Hiramatsu (Google) <mhiramat@kernel.org>

Commit 32dc0042528d ("tracing: Reset last-boot buffers when reading
out all cpu buffers") resets the last_boot_info when user read out
all data via trace_pipe* files. But it is not reset when user
resets the buffer from other files. (e.g. write `trace` file)

Reset it when the corresponding ring buffer is reset too.

Fixes: 32dc0042528d ("tracing: Reset last-boot buffers when reading out all cpu buffers")
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
Changes in v7:
 - Remove unneeded update_last_data_if_empty() call from
   tracing_snapshot_write() because snapshot is disabled on
   persistent instances.
---
 kernel/trace/trace.c |    6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c
index 845b8a165daf..9e1cff78a1ca 100644
--- a/kernel/trace/trace.c
+++ b/kernel/trace/trace.c
@@ -4127,6 +4127,8 @@ static int tracing_single_release_tr(struct inode *inode, struct file *file)
 	return single_release(inode, file);
 }
 
+static bool update_last_data_if_empty(struct trace_array *tr);
+
 static int tracing_open(struct inode *inode, struct file *file)
 {
 	struct trace_array *tr = inode->i_private;
@@ -4151,6 +4153,8 @@ static int tracing_open(struct inode *inode, struct file *file)
 			tracing_reset_online_cpus(trace_buf);
 		else
 			tracing_reset_cpu(trace_buf, cpu);
+
+		update_last_data_if_empty(tr);
 	}
 
 	if (file->f_mode & FMODE_READ) {
@@ -5215,6 +5219,7 @@ tracing_set_trace_read(struct file *filp, char __user *ubuf,
 int tracer_init(struct tracer *t, struct trace_array *tr)
 {
 	tracing_reset_online_cpus(&tr->array_buffer);
+	update_last_data_if_empty(tr);
 	return t->init(tr);
 }
 
@@ -7028,6 +7033,7 @@ int tracing_set_clock(struct trace_array *tr, const char *clockstr)
 		ring_buffer_set_clock(tr->snapshot_buffer.buffer, trace_clocks[i].func);
 	tracing_reset_online_cpus(&tr->snapshot_buffer);
 #endif
+	update_last_data_if_empty(tr);
 
 	if (tr->scratch && !(tr->flags & TRACE_ARRAY_FL_LAST_BOOT)) {
 		struct trace_scratch *tscratch = tr->scratch;


^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox