From: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Add a self-destractive test for the persistent ring buffer. This
will invalidate some sub-buffer pages in the persistent ring buffer
when kernel gets panic, and check whether the number of detected
invalid pages and the total entry_bytes are the same as record
after reboot.
This can ensure the kernel correctly recover partially corrupted
persistent ring buffer when boot.
The test only runs on the persistent ring buffer whose name is
"ptracingtest". And user has to fill it up with events before
kernel panics.
To run the test, enable CONFIG_RING_BUFFER_PERSISTENT_INJECT
and you have to setup the kernel cmdline;
reserve_mem=20M:2M:trace trace_instance=ptracingtest^traceoff@trace
panic=1
And run following commands after the 1st boot;
cd /sys/kernel/tracing/instances/ptracingtest
echo 1 > tracing_on
echo 1 > events/enable
sleep 3
echo c > /proc/sysrq-trigger
After panic message, the kernel will reboot and run the verification
on the persistent ring buffer, e.g.
Ring buffer meta [2] invalid buffer page detected
Ring buffer meta [2] is from previous boot! (318 pages discarded)
Ring buffer testing [2] invalid pages: PASSED (318/318)
Ring buffer testing [2] entry_bytes: PASSED (1300476/1300476)
Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
---
Changes in v14:
- Rename config to CONFIG_RING_BUFFER_PERSISTENT_INJECT.
- Clear meta->nr_invalid/entry_bytes after testing.
- Add test commands in config comment.
Changes in v10:
- Add entry_bytes test.
- Do not compile test code if CONFIG_RING_BUFFER_PERSISTENT_SELFTEST=n.
Changes in v9:
- Test also reader pages.
---
include/linux/ring_buffer.h | 1 +
kernel/trace/Kconfig | 31 +++++++++++++++++++
kernel/trace/ring_buffer.c | 71 +++++++++++++++++++++++++++++++++++++++++++
kernel/trace/trace.c | 4 ++
4 files changed, 107 insertions(+)
diff --git a/include/linux/ring_buffer.h b/include/linux/ring_buffer.h
index 994f52b34344..0670742b2d60 100644
--- a/include/linux/ring_buffer.h
+++ b/include/linux/ring_buffer.h
@@ -238,6 +238,7 @@ int ring_buffer_subbuf_size_get(struct trace_buffer *buffer);
enum ring_buffer_flags {
RB_FL_OVERWRITE = 1 << 0,
+ RB_FL_TESTING = 1 << 1,
};
#ifdef CONFIG_RING_BUFFER
diff --git a/kernel/trace/Kconfig b/kernel/trace/Kconfig
index e130da35808f..07305ed6d745 100644
--- a/kernel/trace/Kconfig
+++ b/kernel/trace/Kconfig
@@ -1202,6 +1202,37 @@ config RING_BUFFER_VALIDATE_TIME_DELTAS
Only say Y if you understand what this does, and you
still want it enabled. Otherwise say N
+config RING_BUFFER_PERSISTENT_INJECT
+ bool "Enable persistent ring buffer error injection test"
+ depends on RING_BUFFER
+ help
+ Run a selftest on the persistent ring buffer which names
+ "ptracingtest" (and its backup) when panic_on_reboot by
+ invalidating ring buffer pages.
+ To use this, boot kernel with "ptracingtest" persistent
+ ring buffer, e.g.
+
+ reserve_mem=20M:2M:trace trace_instance=ptracingtest@trace panic=1
+
+ And after the 1st boot, run test command, like;
+
+ cd /sys/kernel/tracing/instances/ptracingtest
+ echo 1 > events/enable
+ echo 1 > tracing_on
+ sleep 3
+ echo c > /proc/sysrq-trigger
+
+ After panic message, the kernel reboots and show test results
+ on the boot log.
+
+ Note that user has to enable events on the persistent ring
+ buffer manually to fill up ring buffers before rebooting.
+ Since this invalidates the data on test target ring buffer,
+ "ptracingtest" persistent ring buffer must not be used for
+ actual tracing, but only for testing.
+
+ If unsure, say N
+
config MMIOTRACE_TEST
tristate "Test module for mmiotrace"
depends on MMIOTRACE && m
diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c
index 5049cf13021e..7f8140c54fce 100644
--- a/kernel/trace/ring_buffer.c
+++ b/kernel/trace/ring_buffer.c
@@ -64,6 +64,10 @@ struct ring_buffer_cpu_meta {
unsigned long commit_buffer;
__u32 subbuf_size;
__u32 nr_subbufs;
+#ifdef CONFIG_RING_BUFFER_PERSISTENT_INJECT
+ __u32 nr_invalid;
+ __u32 entry_bytes;
+#endif
int buffers[];
};
@@ -2078,6 +2082,21 @@ static void rb_meta_validate_events(struct ring_buffer_per_cpu *cpu_buffer)
pr_info("Ring buffer meta [%d] is from previous boot! (%d pages discarded)\n",
cpu_buffer->cpu, discarded);
+
+#ifdef CONFIG_RING_BUFFER_PERSISTENT_INJECT
+ if (meta->nr_invalid)
+ pr_info("Ring buffer testing [%d] invalid pages: %s (%d/%d)\n",
+ cpu_buffer->cpu,
+ (discarded == meta->nr_invalid) ? "PASSED" : "FAILED",
+ discarded, meta->nr_invalid);
+ if (meta->entry_bytes)
+ pr_info("Ring buffer testing [%d] entry_bytes: %s (%ld/%ld)\n",
+ cpu_buffer->cpu,
+ (entry_bytes == meta->entry_bytes) ? "PASSED" : "FAILED",
+ (long)entry_bytes, (long)meta->entry_bytes);
+ meta->nr_invalid = 0;
+ meta->entry_bytes = 0;
+#endif
return;
invalid:
@@ -2558,12 +2577,64 @@ static void rb_free_cpu_buffer(struct ring_buffer_per_cpu *cpu_buffer)
kfree(cpu_buffer);
}
+#ifdef CONFIG_RING_BUFFER_PERSISTENT_INJECT
+static void rb_test_inject_invalid_pages(struct trace_buffer *buffer)
+{
+ struct ring_buffer_per_cpu *cpu_buffer;
+ struct ring_buffer_cpu_meta *meta;
+ struct buffer_data_page *dpage;
+ u32 entry_bytes = 0;
+ unsigned long ptr;
+ int subbuf_size;
+ int invalid = 0;
+ int cpu;
+ int i;
+
+ if (!(buffer->flags & RB_FL_TESTING))
+ return;
+
+ guard(preempt)();
+ cpu = smp_processor_id();
+
+ cpu_buffer = buffer->buffers[cpu];
+ meta = cpu_buffer->ring_meta;
+ ptr = (unsigned long)rb_subbufs_from_meta(meta);
+ subbuf_size = meta->subbuf_size;
+
+ for (i = 0; i < meta->nr_subbufs; i++) {
+ int idx = meta->buffers[i];
+
+ dpage = (void *)(ptr + idx * subbuf_size);
+ /* Skip unused pages */
+ if (!local_read(&dpage->commit))
+ continue;
+
+ /* Invalidate even pages. */
+ if (!(i & 0x1)) {
+ local_add(subbuf_size + 1, &dpage->commit);
+ invalid++;
+ } else {
+ /* Count total commit bytes. */
+ entry_bytes += local_read(&dpage->commit);
+ }
+ }
+
+ pr_info("Inject invalidated %d pages on CPU%d, total size: %ld\n",
+ invalid, cpu, (long)entry_bytes);
+ meta->nr_invalid = invalid;
+ meta->entry_bytes = entry_bytes;
+}
+#else /* !CONFIG_RING_BUFFER_PERSISTENT_INJECT */
+#define rb_test_inject_invalid_pages(buffer) do { } while (0)
+#endif
+
/* Stop recording on a persistent buffer and flush cache if needed. */
static int rb_flush_buffer_cb(struct notifier_block *nb, unsigned long event, void *data)
{
struct trace_buffer *buffer = container_of(nb, struct trace_buffer, flush_nb);
ring_buffer_record_off(buffer);
+ rb_test_inject_invalid_pages(buffer);
arch_ring_buffer_flush_range(buffer->range_addr_start, buffer->range_addr_end);
return NOTIFY_DONE;
}
diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c
index 4189ec9df6a5..108b0d16badf 100644
--- a/kernel/trace/trace.c
+++ b/kernel/trace/trace.c
@@ -9366,6 +9366,8 @@ static void setup_trace_scratch(struct trace_array *tr,
memset(tscratch, 0, size);
}
+#define TRACE_TEST_PTRACING_NAME "ptracingtest"
+
static int
allocate_trace_buffer(struct trace_array *tr, struct array_buffer *buf, unsigned long size)
{
@@ -9378,6 +9380,8 @@ allocate_trace_buffer(struct trace_array *tr, struct array_buffer *buf, unsigned
buf->tr = tr;
if (tr->range_addr_start && tr->range_addr_size) {
+ if (!strcmp(tr->name, TRACE_TEST_PTRACING_NAME))
+ rb_flags |= RB_FL_TESTING;
/* Add scratch buffer to handle 128 modules */
buf->buffer = ring_buffer_alloc_range(size, rb_flags, 0,
tr->range_addr_start,
On Mon, 30 Mar 2026 21:50:27 +0900
"Masami Hiramatsu (Google)" <mhiramat@kernel.org> wrote:
> @@ -2558,12 +2577,64 @@ static void rb_free_cpu_buffer(struct ring_buffer_per_cpu *cpu_buffer)
> kfree(cpu_buffer);
> }
>
> +#ifdef CONFIG_RING_BUFFER_PERSISTENT_INJECT
> +static void rb_test_inject_invalid_pages(struct trace_buffer *buffer)
> +{
> + struct ring_buffer_per_cpu *cpu_buffer;
> + struct ring_buffer_cpu_meta *meta;
> + struct buffer_data_page *dpage;
> + u32 entry_bytes = 0;
> + unsigned long ptr;
> + int subbuf_size;
> + int invalid = 0;
> + int cpu;
> + int i;
> +
> + if (!(buffer->flags & RB_FL_TESTING))
> + return;
> +
> + guard(preempt)();
> + cpu = smp_processor_id();
> +
> + cpu_buffer = buffer->buffers[cpu];
> + meta = cpu_buffer->ring_meta;
> + ptr = (unsigned long)rb_subbufs_from_meta(meta);
> + subbuf_size = meta->subbuf_size;
> +
> + for (i = 0; i < meta->nr_subbufs; i++) {
> + int idx = meta->buffers[i];
> +
> + dpage = (void *)(ptr + idx * subbuf_size);
> + /* Skip unused pages */
> + if (!local_read(&dpage->commit))
> + continue;
> +
> + /* Invalidate even pages. */
> + if (!(i & 0x1)) {
> + local_add(subbuf_size + 1, &dpage->commit);
> + invalid++;
> + } else {
> + /* Count total commit bytes. */
> + entry_bytes += local_read(&dpage->commit);
> + }
> + }
> +
> + pr_info("Inject invalidated %d pages on CPU%d, total size: %ld\n",
> + invalid, cpu, (long)entry_bytes);
This is only enabled when testing. Let's make that a pr_warn() as we really
do want to be able to see it. And it should warn that it is invalidating pages!
(warn as in pr_warn, it doesn't need a warn as in WARN()).
-- Steve
> + meta->nr_invalid = invalid;
> + meta->entry_bytes = entry_bytes;
> +}
> +#else /* !CONFIG_RING_BUFFER_PERSISTENT_INJECT */
> +#define rb_test_inject_invalid_pages(buffer) do { } while (0)
> +#endif
> +
> /* Stop recording on a persistent buffer and flush cache if needed. */
> static int rb_flush_buffer_cb(struct notifier_block *nb, unsigned long event, void *data)
> {
> struct trace_buffer *buffer = container_of(nb, struct trace_buffer, flush_nb);
>
> ring_buffer_record_off(buffer);
> + rb_test_inject_invalid_pages(buffer);
> arch_ring_buffer_flush_range(buffer->range_addr_start, buffer->range_addr_end);
> return NOTIFY_DONE;
> }
On Mon, 30 Mar 2026 16:24:19 -0400
Steven Rostedt <rostedt@goodmis.org> wrote:
> On Mon, 30 Mar 2026 21:50:27 +0900
> "Masami Hiramatsu (Google)" <mhiramat@kernel.org> wrote:
>
> > @@ -2558,12 +2577,64 @@ static void rb_free_cpu_buffer(struct ring_buffer_per_cpu *cpu_buffer)
> > kfree(cpu_buffer);
> > }
> >
> > +#ifdef CONFIG_RING_BUFFER_PERSISTENT_INJECT
> > +static void rb_test_inject_invalid_pages(struct trace_buffer *buffer)
> > +{
> > + struct ring_buffer_per_cpu *cpu_buffer;
> > + struct ring_buffer_cpu_meta *meta;
> > + struct buffer_data_page *dpage;
> > + u32 entry_bytes = 0;
> > + unsigned long ptr;
> > + int subbuf_size;
> > + int invalid = 0;
> > + int cpu;
> > + int i;
> > +
> > + if (!(buffer->flags & RB_FL_TESTING))
> > + return;
> > +
> > + guard(preempt)();
> > + cpu = smp_processor_id();
> > +
> > + cpu_buffer = buffer->buffers[cpu];
> > + meta = cpu_buffer->ring_meta;
> > + ptr = (unsigned long)rb_subbufs_from_meta(meta);
> > + subbuf_size = meta->subbuf_size;
> > +
> > + for (i = 0; i < meta->nr_subbufs; i++) {
> > + int idx = meta->buffers[i];
> > +
> > + dpage = (void *)(ptr + idx * subbuf_size);
> > + /* Skip unused pages */
> > + if (!local_read(&dpage->commit))
> > + continue;
> > +
> > + /* Invalidate even pages. */
> > + if (!(i & 0x1)) {
> > + local_add(subbuf_size + 1, &dpage->commit);
> > + invalid++;
> > + } else {
> > + /* Count total commit bytes. */
> > + entry_bytes += local_read(&dpage->commit);
> > + }
> > + }
> > +
> > + pr_info("Inject invalidated %d pages on CPU%d, total size: %ld\n",
> > + invalid, cpu, (long)entry_bytes);
>
> This is only enabled when testing. Let's make that a pr_warn() as we really
> do want to be able to see it. And it should warn that it is invalidating pages!
> (warn as in pr_warn, it doesn't need a warn as in WARN()).
OK. Let me update it.
Thanks!
>
> -- Steve
>
>
> > + meta->nr_invalid = invalid;
> > + meta->entry_bytes = entry_bytes;
> > +}
> > +#else /* !CONFIG_RING_BUFFER_PERSISTENT_INJECT */
> > +#define rb_test_inject_invalid_pages(buffer) do { } while (0)
> > +#endif
> > +
> > /* Stop recording on a persistent buffer and flush cache if needed. */
> > static int rb_flush_buffer_cb(struct notifier_block *nb, unsigned long event, void *data)
> > {
> > struct trace_buffer *buffer = container_of(nb, struct trace_buffer, flush_nb);
> >
> > ring_buffer_record_off(buffer);
> > + rb_test_inject_invalid_pages(buffer);
> > arch_ring_buffer_flush_range(buffer->range_addr_start, buffer->range_addr_end);
> > return NOTIFY_DONE;
> > }
--
Masami Hiramatsu (Google) <mhiramat@kernel.org>
© 2016 - 2026 Red Hat, Inc.