Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
File diff suppressed because it is too large Load Diff
+166
View File
@@ -0,0 +1,166 @@
# SPDX-License-Identifier: GPL-2.0
# Do not instrument the tracer itself:
ccflags-remove-$(CONFIG_FUNCTION_TRACER) += $(CC_FLAGS_FTRACE)
ifdef CONFIG_FUNCTION_TRACER
# Avoid recursion due to instrumentation.
KCSAN_SANITIZE := n
ifdef CONFIG_FTRACE_SELFTEST
# selftest needs instrumentation
CFLAGS_trace_selftest_dynamic.o = $(CC_FLAGS_FTRACE)
obj-y += trace_selftest_dynamic.o
endif
endif
# Allow some files to be function traced
ifdef CONFIG_FUNCTION_SELF_TRACING
CFLAGS_trace_output.o = $(CC_FLAGS_FTRACE)
CFLAGS_trace_seq.o = $(CC_FLAGS_FTRACE)
CFLAGS_trace_stat.o = $(CC_FLAGS_FTRACE)
CFLAGS_tracing_map.o = $(CC_FLAGS_FTRACE)
CFLAGS_synth_event_gen_test.o = $(CC_FLAGS_FTRACE)
CFLAGS_trace_events.o = $(CC_FLAGS_FTRACE)
CFLAGS_trace_syscalls.o = $(CC_FLAGS_FTRACE)
CFLAGS_trace_events_filter.o = $(CC_FLAGS_FTRACE)
CFLAGS_trace_events_trigger.o = $(CC_FLAGS_FTRACE)
CFLAGS_trace_events_synth.o = $(CC_FLAGS_FTRACE)
CFLAGS_trace_events_hist.o = $(CC_FLAGS_FTRACE)
CFLAGS_trace_events_user.o = $(CC_FLAGS_FTRACE)
CFLAGS_trace_dynevent.o = $(CC_FLAGS_FTRACE)
endif
ifdef CONFIG_FTRACE_STARTUP_TEST
CFLAGS_trace_kprobe_selftest.o = $(CC_FLAGS_FTRACE)
obj-$(CONFIG_KPROBE_EVENTS) += trace_kprobe_selftest.o
endif
# If unlikely tracing is enabled, do not trace these files
ifdef CONFIG_TRACING_BRANCHES
KBUILD_CFLAGS += -DDISABLE_BRANCH_PROFILING
endif
# for GCOV coverage profiling
ifdef CONFIG_GCOV_PROFILE_FTRACE
GCOV_PROFILE := y
endif
# Functions in these files can run from IRQ entry before hardirq context
# is visible to KCOV, and produce coverage unrelated to syscall inputs.
KCOV_INSTRUMENT_trace_preemptirq.o := n
KCOV_INSTRUMENT_trace_irqsoff.o := n
CFLAGS_bpf_trace.o := -I$(src)
CFLAGS_trace_benchmark.o := -I$(src)
CFLAGS_trace_events_filter.o := -I$(src)
obj-$(CONFIG_TRACE_CLOCK) += trace_clock.o
obj-$(CONFIG_FUNCTION_TRACER) += libftrace.o
obj-$(CONFIG_RING_BUFFER) += ring_buffer.o
obj-$(CONFIG_RING_BUFFER_BENCHMARK) += ring_buffer_benchmark.o
obj-$(CONFIG_TRACING) += trace.o
obj-$(CONFIG_TRACING) += trace_output.o
obj-$(CONFIG_TRACING) += trace_seq.o
obj-$(CONFIG_TRACING) += trace_stat.o
obj-$(CONFIG_TRACING) += trace_printk.o
obj-$(CONFIG_TRACING) += trace_pid.o
obj-$(CONFIG_TRACER_SNAPSHOT) += trace_snapshot.o
obj-$(CONFIG_TRACING) += pid_list.o
obj-$(CONFIG_TRACING_MAP) += tracing_map.o
obj-$(CONFIG_PREEMPTIRQ_DELAY_TEST) += preemptirq_delay_test.o
obj-$(CONFIG_SYNTH_EVENT_GEN_TEST) += synth_event_gen_test.o
obj-$(CONFIG_KPROBE_EVENT_GEN_TEST) += kprobe_event_gen_test.o
obj-$(CONFIG_CONTEXT_SWITCH_TRACER) += trace_sched_switch.o
obj-$(CONFIG_FUNCTION_TRACER) += trace_functions.o
obj-$(CONFIG_PREEMPTIRQ_TRACEPOINTS) += trace_preemptirq.o
obj-$(CONFIG_IRQSOFF_TRACER) += trace_irqsoff.o
obj-$(CONFIG_PREEMPT_TRACER) += trace_irqsoff.o
obj-$(CONFIG_SCHED_TRACER) += trace_sched_wakeup.o
obj-$(CONFIG_HWLAT_TRACER) += trace_hwlat.o
obj-$(CONFIG_OSNOISE_TRACER) += trace_osnoise.o
obj-$(CONFIG_NOP_TRACER) += trace_nop.o
obj-$(CONFIG_STACK_TRACER) += trace_stack.o
obj-$(CONFIG_MMIOTRACE) += trace_mmiotrace.o
obj-$(CONFIG_FUNCTION_GRAPH_TRACER) += trace_functions_graph.o
obj-$(CONFIG_TRACE_BRANCH_PROFILING) += trace_branch.o
obj-$(CONFIG_BLK_DEV_IO_TRACE) += blktrace.o
obj-$(CONFIG_FUNCTION_GRAPH_TRACER) += fgraph.o
ifeq ($(CONFIG_BLOCK),y)
obj-$(CONFIG_EVENT_TRACING) += blktrace.o
endif
obj-$(CONFIG_EVENT_TRACING) += trace_events.o
obj-$(CONFIG_EVENT_TRACING) += trace_export.o
obj-$(CONFIG_FTRACE_SYSCALLS) += trace_syscalls.o
ifeq ($(CONFIG_PERF_EVENTS),y)
obj-$(CONFIG_EVENT_TRACING) += trace_event_perf.o
endif
obj-$(CONFIG_EVENT_TRACING) += trace_events_filter.o
obj-$(CONFIG_EVENT_TRACING) += trace_events_trigger.o
obj-$(CONFIG_EPROBE_EVENTS) += trace_eprobe.o
obj-$(CONFIG_TRACE_EVENT_INJECT) += trace_events_inject.o
obj-$(CONFIG_SYNTH_EVENTS) += trace_events_synth.o
obj-$(CONFIG_HIST_TRIGGERS) += trace_events_hist.o
obj-$(CONFIG_USER_EVENTS) += trace_events_user.o
obj-$(CONFIG_BPF_EVENTS) += bpf_trace.o
obj-$(CONFIG_KPROBE_EVENTS) += trace_kprobe.o
obj-$(CONFIG_TRACEPOINTS) += error_report-traces.o
obj-$(CONFIG_TRACEPOINTS) += power-traces.o
ifeq ($(CONFIG_PM),y)
obj-$(CONFIG_TRACEPOINTS) += rpm-traces.o
endif
ifeq ($(CONFIG_TRACING),y)
obj-$(CONFIG_KGDB_KDB) += trace_kdb.o
endif
obj-$(CONFIG_DYNAMIC_EVENTS) += trace_dynevent.o
obj-$(CONFIG_PROBE_EVENTS) += trace_probe.o
obj-$(CONFIG_PROBE_EVENTS_BTF_ARGS) += trace_btf.o
obj-$(CONFIG_UPROBE_EVENTS) += trace_uprobe.o
obj-$(CONFIG_BOOTTIME_TRACING) += trace_boot.o
obj-$(CONFIG_FTRACE_RECORD_RECURSION) += trace_recursion_record.o
obj-$(CONFIG_FPROBE) += fprobe.o
obj-$(CONFIG_RETHOOK) += rethook.o
obj-$(CONFIG_FPROBE_EVENTS) += trace_fprobe.o
obj-$(CONFIG_TRACEPOINT_BENCHMARK) += trace_benchmark.o
obj-$(CONFIG_RV) += rv/
obj-$(CONFIG_TRACE_REMOTE) += trace_remote.o
obj-$(CONFIG_SIMPLE_RING_BUFFER) += simple_ring_buffer.o
obj-$(CONFIG_TRACE_REMOTE_TEST) += remote_test.o
# simple_ring_buffer is used by the pKVM hypervisor which does not have access
# to all kernel symbols. Fail the build if forbidden symbols are found.
# Basic compiler and tooling-generated symbols that can safely be left
# undefined. Ensure KASAN is enabled to avoid logic that may disable
# FORTIFY_SOURCE when KASAN is not enabled. undefsyms_base.o does not
# automatically get KASAN flags because it is not linked into vmlinux.
targets += undefsyms_base.o
KASAN_SANITIZE_undefsyms_base.o := y
UNDEFINED_ALLOWLIST = __asan __gcov __kasan __kcsan __hwasan __sancov __sanitizer __tsan __ubsan __msan \
__aeabi_unwind_cpp __s390_indirect_jump __x86_indirect_thunk simple_ring_buffer \
$(shell $(NM) -u $(obj)/undefsyms_base.o 2>/dev/null | awk '{print $$2}')
quiet_cmd_check_undefined = NM $<
cmd_check_undefined = \
undefsyms=$$($(NM) -u $< | grep -v $(addprefix -e , $(UNDEFINED_ALLOWLIST)) || true); \
if [ -n "$$undefsyms" ]; then \
echo "Unexpected symbols in $<:" >&2; \
echo "$$undefsyms" >&2; \
false; \
fi; \
touch $@
$(obj)/%.o.checked: $(obj)/%.o $(obj)/undefsyms_base.o FORCE
$(call if_changed,check_undefined)
always-$(CONFIG_SIMPLE_RING_BUFFER) += simple_ring_buffer.o.checked
libftrace-y := ftrace.o
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+34
View File
@@ -0,0 +1,34 @@
/* SPDX-License-Identifier: GPL-2.0 */
#undef TRACE_SYSTEM
#define TRACE_SYSTEM bpf_trace
#if !defined(_TRACE_BPF_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
#define _TRACE_BPF_TRACE_H
#include <linux/tracepoint.h>
TRACE_EVENT(bpf_trace_printk,
TP_PROTO(const char *bpf_string),
TP_ARGS(bpf_string),
TP_STRUCT__entry(
__string(bpf_string, bpf_string)
),
TP_fast_assign(
__assign_str(bpf_string);
),
TP_printk("%s", __get_str(bpf_string))
);
#endif /* _TRACE_BPF_TRACE_H */
#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH .
#define TRACE_INCLUDE_FILE bpf_trace
#include <trace/define_trace.h>
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Error reporting trace points.
*
* Copyright (C) 2021, Google LLC.
*/
#define CREATE_TRACE_POINTS
#include <trace/events/error_report.h>
EXPORT_TRACEPOINT_SYMBOL_GPL(error_report_end);
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,68 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _LINUX_KERNEL_FTRACE_INTERNAL_H
#define _LINUX_KERNEL_FTRACE_INTERNAL_H
int __register_ftrace_function(struct ftrace_ops *ops);
int __unregister_ftrace_function(struct ftrace_ops *ops);
#ifdef CONFIG_FUNCTION_TRACER
extern struct mutex ftrace_lock;
extern struct ftrace_ops global_ops;
#ifdef CONFIG_DYNAMIC_FTRACE
int ftrace_startup(struct ftrace_ops *ops, int command);
int ftrace_shutdown(struct ftrace_ops *ops, int command);
int ftrace_ops_test(struct ftrace_ops *ops, unsigned long ip, void *regs);
int ftrace_startup_subops(struct ftrace_ops *ops, struct ftrace_ops *subops, int command);
int ftrace_shutdown_subops(struct ftrace_ops *ops, struct ftrace_ops *subops, int command);
#else /* !CONFIG_DYNAMIC_FTRACE */
/* Keep as macros so we do not need to define the commands */
# define ftrace_startup(ops, command) \
({ \
int ___ret = __register_ftrace_function(ops); \
if (!___ret) \
(ops)->flags |= FTRACE_OPS_FL_ENABLED; \
___ret; \
})
# define ftrace_shutdown(ops, command) \
({ \
int ___ret = __unregister_ftrace_function(ops); \
if (!___ret) \
(ops)->flags &= ~FTRACE_OPS_FL_ENABLED; \
___ret; \
})
static inline int
ftrace_ops_test(struct ftrace_ops *ops, unsigned long ip, void *regs)
{
return 1;
}
static inline int ftrace_startup_subops(struct ftrace_ops *ops, struct ftrace_ops *subops, int command)
{
return -EINVAL;
}
static inline int ftrace_shutdown_subops(struct ftrace_ops *ops, struct ftrace_ops *subops, int command)
{
return -EINVAL;
}
#endif /* CONFIG_DYNAMIC_FTRACE */
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
extern int ftrace_graph_active;
# ifdef CONFIG_DYNAMIC_FTRACE
extern void fgraph_update_pid_func(void);
# else
static inline void fgraph_update_pid_func(void) {}
# endif
#else /* !CONFIG_FUNCTION_GRAPH_TRACER */
# define ftrace_graph_active 0
static inline void fgraph_update_pid_func(void) {}
#endif /* CONFIG_FUNCTION_GRAPH_TRACER */
#else /* !CONFIG_FUNCTION_TRACER */
#endif /* CONFIG_FUNCTION_TRACER */
#endif
@@ -0,0 +1,276 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Test module for in-kernel kprobe event creation and generation.
*
* Copyright (C) 2019 Tom Zanussi <zanussi@kernel.org>
*/
#include <linux/module.h>
#include <linux/trace_events.h>
/*
* This module is a simple test of basic functionality for in-kernel
* kprobe/kretprobe event creation. The first test uses
* kprobe_event_gen_cmd_start(), kprobe_event_add_fields() and
* kprobe_event_gen_cmd_end() to create a kprobe event, which is then
* enabled in order to generate trace output. The second creates a
* kretprobe event using kretprobe_event_gen_cmd_start() and
* kretprobe_event_gen_cmd_end(), and is also then enabled.
*
* To test, select CONFIG_KPROBE_EVENT_GEN_TEST and build the module.
* Then:
*
* # insmod kernel/trace/kprobe_event_gen_test.ko
* # cat /sys/kernel/tracing/trace
*
* You should see many instances of the "gen_kprobe_test" and
* "gen_kretprobe_test" events in the trace buffer.
*
* To remove the events, remove the module:
*
* # rmmod kprobe_event_gen_test
*
*/
static struct trace_event_file *gen_kprobe_test;
static struct trace_event_file *gen_kretprobe_test;
#define KPROBE_GEN_TEST_FUNC "do_sys_open"
/* X86 */
#if defined(CONFIG_X86_64) || defined(CONFIG_X86_32)
#define KPROBE_GEN_TEST_ARG0 "dfd=%ax"
#define KPROBE_GEN_TEST_ARG1 "filename=%dx"
#define KPROBE_GEN_TEST_ARG2 "flags=%cx"
#define KPROBE_GEN_TEST_ARG3 "mode=+4($stack)"
/* ARM64 */
#elif defined(CONFIG_ARM64)
#define KPROBE_GEN_TEST_ARG0 "dfd=%x0"
#define KPROBE_GEN_TEST_ARG1 "filename=%x1"
#define KPROBE_GEN_TEST_ARG2 "flags=%x2"
#define KPROBE_GEN_TEST_ARG3 "mode=%x3"
/* ARM */
#elif defined(CONFIG_ARM)
#define KPROBE_GEN_TEST_ARG0 "dfd=%r0"
#define KPROBE_GEN_TEST_ARG1 "filename=%r1"
#define KPROBE_GEN_TEST_ARG2 "flags=%r2"
#define KPROBE_GEN_TEST_ARG3 "mode=%r3"
/* RISCV */
#elif defined(CONFIG_RISCV)
#define KPROBE_GEN_TEST_ARG0 "dfd=%a0"
#define KPROBE_GEN_TEST_ARG1 "filename=%a1"
#define KPROBE_GEN_TEST_ARG2 "flags=%a2"
#define KPROBE_GEN_TEST_ARG3 "mode=%a3"
/* others */
#else
#define KPROBE_GEN_TEST_ARG0 NULL
#define KPROBE_GEN_TEST_ARG1 NULL
#define KPROBE_GEN_TEST_ARG2 NULL
#define KPROBE_GEN_TEST_ARG3 NULL
#endif
static bool trace_event_file_is_valid(struct trace_event_file *input)
{
return input && !IS_ERR(input);
}
/*
* Test to make sure we can create a kprobe event, then add more
* fields.
*/
static int __init test_gen_kprobe_cmd(void)
{
struct dynevent_cmd cmd;
char *buf;
int ret;
/* Create a buffer to hold the generated command */
buf = kzalloc(MAX_DYNEVENT_CMD_LEN, GFP_KERNEL);
if (!buf)
return -ENOMEM;
/* Before generating the command, initialize the cmd object */
kprobe_event_cmd_init(&cmd, buf, MAX_DYNEVENT_CMD_LEN);
/*
* Define the gen_kprobe_test event with the first 2 kprobe
* fields.
*/
ret = kprobe_event_gen_cmd_start(&cmd, "gen_kprobe_test",
KPROBE_GEN_TEST_FUNC,
KPROBE_GEN_TEST_ARG0, KPROBE_GEN_TEST_ARG1);
if (ret)
goto out;
/* Use kprobe_event_add_fields to add the rest of the fields */
ret = kprobe_event_add_fields(&cmd, KPROBE_GEN_TEST_ARG2, KPROBE_GEN_TEST_ARG3);
if (ret)
goto out;
/*
* This actually creates the event.
*/
ret = kprobe_event_gen_cmd_end(&cmd);
if (ret)
goto out;
/*
* Now get the gen_kprobe_test event file. We need to prevent
* the instance and event from disappearing from underneath
* us, which trace_get_event_file() does (though in this case
* we're using the top-level instance which never goes away).
*/
gen_kprobe_test = trace_get_event_file(NULL, "kprobes",
"gen_kprobe_test");
if (IS_ERR(gen_kprobe_test)) {
ret = PTR_ERR(gen_kprobe_test);
goto delete;
}
/* Enable the event or you won't see anything */
ret = trace_array_set_clr_event(gen_kprobe_test->tr,
"kprobes", "gen_kprobe_test", true);
if (ret) {
trace_put_event_file(gen_kprobe_test);
goto delete;
}
out:
kfree(buf);
return ret;
delete:
if (trace_event_file_is_valid(gen_kprobe_test))
gen_kprobe_test = NULL;
/* We got an error after creating the event, delete it */
kprobe_event_delete("gen_kprobe_test");
goto out;
}
/*
* Test to make sure we can create a kretprobe event.
*/
static int __init test_gen_kretprobe_cmd(void)
{
struct dynevent_cmd cmd;
char *buf;
int ret;
/* Create a buffer to hold the generated command */
buf = kzalloc(MAX_DYNEVENT_CMD_LEN, GFP_KERNEL);
if (!buf)
return -ENOMEM;
/* Before generating the command, initialize the cmd object */
kprobe_event_cmd_init(&cmd, buf, MAX_DYNEVENT_CMD_LEN);
/*
* Define the kretprobe event.
*/
ret = kretprobe_event_gen_cmd_start(&cmd, "gen_kretprobe_test",
KPROBE_GEN_TEST_FUNC,
"$retval");
if (ret)
goto out;
/*
* This actually creates the event.
*/
ret = kretprobe_event_gen_cmd_end(&cmd);
if (ret)
goto out;
/*
* Now get the gen_kretprobe_test event file. We need to
* prevent the instance and event from disappearing from
* underneath us, which trace_get_event_file() does (though in
* this case we're using the top-level instance which never
* goes away).
*/
gen_kretprobe_test = trace_get_event_file(NULL, "kprobes",
"gen_kretprobe_test");
if (IS_ERR(gen_kretprobe_test)) {
ret = PTR_ERR(gen_kretprobe_test);
goto delete;
}
/* Enable the event or you won't see anything */
ret = trace_array_set_clr_event(gen_kretprobe_test->tr,
"kprobes", "gen_kretprobe_test", true);
if (ret) {
trace_put_event_file(gen_kretprobe_test);
goto delete;
}
out:
kfree(buf);
return ret;
delete:
if (trace_event_file_is_valid(gen_kretprobe_test))
gen_kretprobe_test = NULL;
/* We got an error after creating the event, delete it */
kprobe_event_delete("gen_kretprobe_test");
goto out;
}
static int __init kprobe_event_gen_test_init(void)
{
int ret;
ret = test_gen_kprobe_cmd();
if (ret)
return ret;
ret = test_gen_kretprobe_cmd();
if (ret) {
if (trace_event_file_is_valid(gen_kretprobe_test)) {
WARN_ON(trace_array_set_clr_event(gen_kretprobe_test->tr,
"kprobes",
"gen_kretprobe_test", false));
trace_put_event_file(gen_kretprobe_test);
}
WARN_ON(kprobe_event_delete("gen_kretprobe_test"));
}
return ret;
}
static void __exit kprobe_event_gen_test_exit(void)
{
if (trace_event_file_is_valid(gen_kprobe_test)) {
/* Disable the event or you can't remove it */
WARN_ON(trace_array_set_clr_event(gen_kprobe_test->tr,
"kprobes",
"gen_kprobe_test", false));
/* Now give the file and instance back */
trace_put_event_file(gen_kprobe_test);
}
/* Now unregister and free the event */
WARN_ON(kprobe_event_delete("gen_kprobe_test"));
if (trace_event_file_is_valid(gen_kretprobe_test)) {
/* Disable the event or you can't remove it */
WARN_ON(trace_array_set_clr_event(gen_kretprobe_test->tr,
"kprobes",
"gen_kretprobe_test", false));
/* Now give the file and instance back */
trace_put_event_file(gen_kretprobe_test);
}
/* Now unregister and free the event */
WARN_ON(kprobe_event_delete("gen_kretprobe_test"));
}
module_init(kprobe_event_gen_test_init)
module_exit(kprobe_event_gen_test_exit)
MODULE_AUTHOR("Tom Zanussi");
MODULE_DESCRIPTION("kprobe event generation test");
MODULE_LICENSE("GPL v2");
+504
View File
@@ -0,0 +1,504 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2021 VMware Inc, Steven Rostedt <rostedt@goodmis.org>
*/
#include <linux/spinlock.h>
#include <linux/seqlock.h>
#include <linux/irq_work.h>
#include <linux/slab.h>
#include "trace.h"
/* See pid_list.h for details */
static inline union lower_chunk *get_lower_chunk(struct trace_pid_list *pid_list)
{
union lower_chunk *chunk;
lockdep_assert_held(&pid_list->lock);
if (!pid_list->lower_list)
return NULL;
chunk = pid_list->lower_list;
pid_list->lower_list = chunk->next;
pid_list->free_lower_chunks--;
WARN_ON_ONCE(pid_list->free_lower_chunks < 0);
chunk->next = NULL;
/*
* If a refill needs to happen, it can not happen here
* as the scheduler run queue locks are held.
*/
if (pid_list->free_lower_chunks <= CHUNK_REALLOC)
irq_work_queue(&pid_list->refill_irqwork);
return chunk;
}
static inline union upper_chunk *get_upper_chunk(struct trace_pid_list *pid_list)
{
union upper_chunk *chunk;
lockdep_assert_held(&pid_list->lock);
if (!pid_list->upper_list)
return NULL;
chunk = pid_list->upper_list;
pid_list->upper_list = chunk->next;
pid_list->free_upper_chunks--;
WARN_ON_ONCE(pid_list->free_upper_chunks < 0);
chunk->next = NULL;
/*
* If a refill needs to happen, it can not happen here
* as the scheduler run queue locks are held.
*/
if (pid_list->free_upper_chunks <= CHUNK_REALLOC)
irq_work_queue(&pid_list->refill_irqwork);
return chunk;
}
static inline void put_lower_chunk(struct trace_pid_list *pid_list,
union lower_chunk *chunk)
{
lockdep_assert_held(&pid_list->lock);
chunk->next = pid_list->lower_list;
pid_list->lower_list = chunk;
pid_list->free_lower_chunks++;
}
static inline void put_upper_chunk(struct trace_pid_list *pid_list,
union upper_chunk *chunk)
{
lockdep_assert_held(&pid_list->lock);
chunk->next = pid_list->upper_list;
pid_list->upper_list = chunk;
pid_list->free_upper_chunks++;
}
static inline bool upper_empty(union upper_chunk *chunk)
{
/*
* If chunk->data has no lower chunks, it will be the same
* as a zeroed bitmask.
*/
return bitmap_empty((unsigned long *)chunk->data, BITS_PER_TYPE(chunk->data));
}
static inline int pid_split(unsigned int pid, unsigned int *upper1,
unsigned int *upper2, unsigned int *lower)
{
/* MAX_PID should cover all pids */
BUILD_BUG_ON(MAX_PID < PID_MAX_LIMIT);
/* In case a bad pid is passed in, then fail */
if (unlikely(pid >= MAX_PID))
return -1;
*upper1 = (pid >> UPPER1_SHIFT) & UPPER_MASK;
*upper2 = (pid >> UPPER2_SHIFT) & UPPER_MASK;
*lower = pid & LOWER_MASK;
return 0;
}
static inline unsigned int pid_join(unsigned int upper1,
unsigned int upper2, unsigned int lower)
{
return ((upper1 & UPPER_MASK) << UPPER1_SHIFT) |
((upper2 & UPPER_MASK) << UPPER2_SHIFT) |
(lower & LOWER_MASK);
}
/**
* trace_pid_list_is_set - test if the pid is set in the list
* @pid_list: The pid list to test
* @pid: The pid to see if set in the list.
*
* Tests if @pid is set in the @pid_list. This is usually called
* from the scheduler when a task is scheduled. Its pid is checked
* if it should be traced or not.
*
* Return true if the pid is in the list, false otherwise.
*/
bool trace_pid_list_is_set(struct trace_pid_list *pid_list, unsigned int pid)
{
union upper_chunk *upper_chunk;
union lower_chunk *lower_chunk;
unsigned int seq;
unsigned int upper1;
unsigned int upper2;
unsigned int lower;
bool ret = false;
if (!pid_list)
return false;
if (pid_split(pid, &upper1, &upper2, &lower) < 0)
return false;
do {
seq = read_seqcount_begin(&pid_list->seqcount);
ret = false;
upper_chunk = pid_list->upper[upper1];
if (upper_chunk) {
lower_chunk = upper_chunk->data[upper2];
if (lower_chunk)
ret = test_bit(lower, lower_chunk->data);
}
} while (read_seqcount_retry(&pid_list->seqcount, seq));
return ret;
}
/**
* trace_pid_list_set - add a pid to the list
* @pid_list: The pid list to add the @pid to.
* @pid: The pid to add.
*
* Adds @pid to @pid_list. This is usually done explicitly by a user
* adding a task to be traced, or indirectly by the fork function
* when children should be traced and a task's pid is in the list.
*
* Return 0 on success, negative otherwise.
*/
int trace_pid_list_set(struct trace_pid_list *pid_list, unsigned int pid)
{
union upper_chunk *upper_chunk;
union lower_chunk *lower_chunk;
unsigned long flags;
unsigned int upper1;
unsigned int upper2;
unsigned int lower;
int ret;
if (!pid_list)
return -ENODEV;
if (pid_split(pid, &upper1, &upper2, &lower) < 0)
return -EINVAL;
raw_spin_lock_irqsave(&pid_list->lock, flags);
write_seqcount_begin(&pid_list->seqcount);
upper_chunk = pid_list->upper[upper1];
if (!upper_chunk) {
upper_chunk = get_upper_chunk(pid_list);
if (!upper_chunk) {
ret = -ENOMEM;
goto out;
}
pid_list->upper[upper1] = upper_chunk;
}
lower_chunk = upper_chunk->data[upper2];
if (!lower_chunk) {
lower_chunk = get_lower_chunk(pid_list);
if (!lower_chunk) {
ret = -ENOMEM;
goto out;
}
upper_chunk->data[upper2] = lower_chunk;
}
set_bit(lower, lower_chunk->data);
ret = 0;
out:
write_seqcount_end(&pid_list->seqcount);
raw_spin_unlock_irqrestore(&pid_list->lock, flags);
return ret;
}
/**
* trace_pid_list_clear - remove a pid from the list
* @pid_list: The pid list to remove the @pid from.
* @pid: The pid to remove.
*
* Removes @pid from @pid_list. This is usually done explicitly by a user
* removing tasks from tracing, or indirectly by the exit function
* when a task that is set to be traced exits.
*
* Return 0 on success, negative otherwise.
*/
int trace_pid_list_clear(struct trace_pid_list *pid_list, unsigned int pid)
{
union upper_chunk *upper_chunk;
union lower_chunk *lower_chunk;
unsigned long flags;
unsigned int upper1;
unsigned int upper2;
unsigned int lower;
if (!pid_list)
return -ENODEV;
if (pid_split(pid, &upper1, &upper2, &lower) < 0)
return -EINVAL;
raw_spin_lock_irqsave(&pid_list->lock, flags);
write_seqcount_begin(&pid_list->seqcount);
upper_chunk = pid_list->upper[upper1];
if (!upper_chunk)
goto out;
lower_chunk = upper_chunk->data[upper2];
if (!lower_chunk)
goto out;
clear_bit(lower, lower_chunk->data);
/* if there's no more bits set, add it to the free list */
if (find_first_bit(lower_chunk->data, LOWER_MAX) >= LOWER_MAX) {
put_lower_chunk(pid_list, lower_chunk);
upper_chunk->data[upper2] = NULL;
if (upper_empty(upper_chunk)) {
put_upper_chunk(pid_list, upper_chunk);
pid_list->upper[upper1] = NULL;
}
}
out:
write_seqcount_end(&pid_list->seqcount);
raw_spin_unlock_irqrestore(&pid_list->lock, flags);
return 0;
}
/**
* trace_pid_list_next - return the next pid in the list
* @pid_list: The pid list to examine.
* @pid: The pid to start from
* @next: The pointer to place the pid that is set starting from @pid.
*
* Looks for the next consecutive pid that is in @pid_list starting
* at the pid specified by @pid. If one is set (including @pid), then
* that pid is placed into @next.
*
* Return 0 when a pid is found, -1 if there are no more pids included.
*/
int trace_pid_list_next(struct trace_pid_list *pid_list, unsigned int pid,
unsigned int *next)
{
union upper_chunk *upper_chunk;
union lower_chunk *lower_chunk;
unsigned long flags;
unsigned int upper1;
unsigned int upper2;
unsigned int lower;
if (!pid_list)
return -ENODEV;
if (pid_split(pid, &upper1, &upper2, &lower) < 0)
return -EINVAL;
raw_spin_lock_irqsave(&pid_list->lock, flags);
for (; upper1 <= UPPER_MASK; upper1++, upper2 = 0) {
upper_chunk = pid_list->upper[upper1];
if (!upper_chunk)
continue;
for (; upper2 <= UPPER_MASK; upper2++, lower = 0) {
lower_chunk = upper_chunk->data[upper2];
if (!lower_chunk)
continue;
lower = find_next_bit(lower_chunk->data, LOWER_MAX,
lower);
if (lower < LOWER_MAX)
goto found;
}
}
found:
raw_spin_unlock_irqrestore(&pid_list->lock, flags);
if (upper1 > UPPER_MASK)
return -1;
*next = pid_join(upper1, upper2, lower);
return 0;
}
/**
* trace_pid_list_first - return the first pid in the list
* @pid_list: The pid list to examine.
* @pid: The pointer to place the pid first found pid that is set.
*
* Looks for the first pid that is set in @pid_list, and places it
* into @pid if found.
*
* Return 0 when a pid is found, -1 if there are no pids set.
*/
int trace_pid_list_first(struct trace_pid_list *pid_list, unsigned int *pid)
{
return trace_pid_list_next(pid_list, 0, pid);
}
static void pid_list_refill_irq(struct irq_work *iwork)
{
struct trace_pid_list *pid_list = container_of(iwork, struct trace_pid_list,
refill_irqwork);
union upper_chunk *upper = NULL;
union lower_chunk *lower = NULL;
union upper_chunk **upper_next = &upper;
union lower_chunk **lower_next = &lower;
int upper_count;
int lower_count;
int ucnt = 0;
int lcnt = 0;
again:
raw_spin_lock(&pid_list->lock);
write_seqcount_begin(&pid_list->seqcount);
upper_count = CHUNK_ALLOC - pid_list->free_upper_chunks;
lower_count = CHUNK_ALLOC - pid_list->free_lower_chunks;
write_seqcount_end(&pid_list->seqcount);
raw_spin_unlock(&pid_list->lock);
if (upper_count <= 0 && lower_count <= 0)
return;
while (upper_count-- > 0) {
union upper_chunk *chunk;
chunk = kzalloc_obj(*chunk, GFP_NOWAIT);
if (!chunk)
break;
*upper_next = chunk;
upper_next = &chunk->next;
ucnt++;
}
while (lower_count-- > 0) {
union lower_chunk *chunk;
chunk = kzalloc_obj(*chunk, GFP_NOWAIT);
if (!chunk)
break;
*lower_next = chunk;
lower_next = &chunk->next;
lcnt++;
}
raw_spin_lock(&pid_list->lock);
write_seqcount_begin(&pid_list->seqcount);
if (upper) {
*upper_next = pid_list->upper_list;
pid_list->upper_list = upper;
pid_list->free_upper_chunks += ucnt;
}
if (lower) {
*lower_next = pid_list->lower_list;
pid_list->lower_list = lower;
pid_list->free_lower_chunks += lcnt;
}
write_seqcount_end(&pid_list->seqcount);
raw_spin_unlock(&pid_list->lock);
/*
* On success of allocating all the chunks, both counters
* will be less than zero. If they are not, then an allocation
* failed, and we should not try again.
*/
if (upper_count >= 0 || lower_count >= 0)
return;
/*
* When the locks were released, free chunks could have
* been used and allocation needs to be done again. Might as
* well allocate it now.
*/
goto again;
}
/**
* trace_pid_list_alloc - create a new pid_list
*
* Allocates a new pid_list to store pids into.
*
* Returns the pid_list on success, NULL otherwise.
*/
struct trace_pid_list *trace_pid_list_alloc(void)
{
struct trace_pid_list *pid_list;
int i;
/* According to linux/thread.h, pids can be no bigger that 30 bits */
WARN_ON_ONCE(init_pid_ns.pid_max > (1 << 30));
pid_list = kzalloc_obj(*pid_list);
if (!pid_list)
return NULL;
init_irq_work(&pid_list->refill_irqwork, pid_list_refill_irq);
raw_spin_lock_init(&pid_list->lock);
seqcount_raw_spinlock_init(&pid_list->seqcount, &pid_list->lock);
for (i = 0; i < CHUNK_ALLOC; i++) {
union upper_chunk *chunk;
chunk = kzalloc_obj(*chunk);
if (!chunk)
break;
chunk->next = pid_list->upper_list;
pid_list->upper_list = chunk;
pid_list->free_upper_chunks++;
}
for (i = 0; i < CHUNK_ALLOC; i++) {
union lower_chunk *chunk;
chunk = kzalloc_obj(*chunk);
if (!chunk)
break;
chunk->next = pid_list->lower_list;
pid_list->lower_list = chunk;
pid_list->free_lower_chunks++;
}
return pid_list;
}
/**
* trace_pid_list_free - Frees an allocated pid_list.
* @pid_list: The pid list to free.
*
* Frees the memory for a pid_list that was allocated.
*/
void trace_pid_list_free(struct trace_pid_list *pid_list)
{
union upper_chunk *upper;
union lower_chunk *lower;
int i, j;
if (!pid_list)
return;
irq_work_sync(&pid_list->refill_irqwork);
while (pid_list->lower_list) {
union lower_chunk *chunk;
chunk = pid_list->lower_list;
pid_list->lower_list = pid_list->lower_list->next;
kfree(chunk);
}
while (pid_list->upper_list) {
union upper_chunk *chunk;
chunk = pid_list->upper_list;
pid_list->upper_list = pid_list->upper_list->next;
kfree(chunk);
}
for (i = 0; i < UPPER1_SIZE; i++) {
upper = pid_list->upper[i];
if (upper) {
for (j = 0; j < UPPER2_SIZE; j++) {
lower = upper->data[j];
kfree(lower);
}
kfree(upper);
}
}
kfree(pid_list);
}
+89
View File
@@ -0,0 +1,89 @@
// SPDX-License-Identifier: GPL-2.0
/* Do not include this file directly. */
#ifndef _TRACE_INTERNAL_PID_LIST_H
#define _TRACE_INTERNAL_PID_LIST_H
/*
* In order to keep track of what pids to trace, a tree is created much
* like page tables are used. This creates a sparse bit map, where
* the tree is filled in when needed. A PID is at most 30 bits (see
* linux/thread.h), and is broken up into 3 sections based on the bit map
* of the bits. The 8 MSB is the "upper1" section. The next 8 MSB is the
* "upper2" section and the 14 LSB is the "lower" section.
*
* A trace_pid_list structure holds the "upper1" section, in an
* array of 256 pointers (1 or 2K in size) to "upper_chunk" unions, where
* each has an array of 256 pointers (1 or 2K in size) to the "lower_chunk"
* structures, where each has an array of size 2K bytes representing a bitmask
* of the 14 LSB of the PID (256 * 8 = 2048)
*
* When a trace_pid_list is allocated, it includes the 256 pointer array
* of the upper1 unions. Then a "cache" of upper and lower is allocated
* where these will be assigned as needed.
*
* When a bit is set in the pid_list bitmask, the pid to use has
* the 8 MSB masked, and this is used to index the array in the
* pid_list to find the next upper union. If the element is NULL,
* then one is retrieved from the upper_list cache. If none is
* available, then -ENOMEM is returned.
*
* The next 8 MSB is used to index into the "upper2" section. If this
* element is NULL, then it is retrieved from the lower_list cache.
* Again, if one is not available -ENOMEM is returned.
*
* Finally the 14 LSB of the PID is used to set the bit in the 16384
* bitmask (made up of 2K bytes).
*
* When the second upper section or the lower section has their last
* bit cleared, they are added back to the free list to be reused
* when needed.
*/
#define UPPER_BITS 8
#define UPPER_MAX (1 << UPPER_BITS)
#define UPPER1_SIZE (1 << UPPER_BITS)
#define UPPER2_SIZE (1 << UPPER_BITS)
#define LOWER_BITS 14
#define LOWER_MAX (1 << LOWER_BITS)
#define LOWER_SIZE (LOWER_MAX / BITS_PER_LONG)
#define UPPER1_SHIFT (LOWER_BITS + UPPER_BITS)
#define UPPER2_SHIFT LOWER_BITS
#define LOWER_MASK (LOWER_MAX - 1)
#define UPPER_MASK (UPPER_MAX - 1)
/* According to linux/thread.h pids can not be bigger than or equal to 1 << 30 */
#define MAX_PID (1 << 30)
/* Just keep 6 chunks of both upper and lower in the cache on alloc */
#define CHUNK_ALLOC 6
/* Have 2 chunks free, trigger a refill of the cache */
#define CHUNK_REALLOC 2
union lower_chunk {
union lower_chunk *next;
unsigned long data[LOWER_SIZE]; // 2K in size
};
union upper_chunk {
union upper_chunk *next;
union lower_chunk *data[UPPER2_SIZE]; // 1 or 2K in size
};
struct trace_pid_list {
seqcount_raw_spinlock_t seqcount;
raw_spinlock_t lock;
struct irq_work refill_irqwork;
union upper_chunk *upper[UPPER1_SIZE]; // 1 or 2K in size
union upper_chunk *upper_list;
union lower_chunk *lower_list;
int free_upper_chunks;
int free_lower_chunks;
};
#endif /* _TRACE_INTERNAL_PID_LIST_H */
+20
View File
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Power trace points
*
* Copyright (C) 2009 Arjan van de Ven <arjan@linux.intel.com>
*/
#include <linux/string.h>
#include <linux/types.h>
#include <linux/workqueue.h>
#include <linux/sched.h>
#include <linux/module.h>
#define CREATE_TRACE_POINTS
#include <trace/events/power.h>
EXPORT_TRACEPOINT_SYMBOL_GPL(suspend_resume);
EXPORT_TRACEPOINT_SYMBOL_GPL(cpu_idle);
EXPORT_TRACEPOINT_SYMBOL_GPL(cpu_frequency);
@@ -0,0 +1,222 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Preempt / IRQ disable delay thread to test latency tracers
*
* Copyright (C) 2018 Joel Fernandes (Google) <joel@joelfernandes.org>
*/
#include <linux/trace_clock.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/kthread.h>
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/completion.h>
static ulong delay = 100;
static char test_mode[12] = "irq";
static uint burst_size = 1;
static int cpu_affinity = -1;
module_param_named(delay, delay, ulong, 0444);
module_param_string(test_mode, test_mode, 12, 0444);
module_param_named(burst_size, burst_size, uint, 0444);
module_param_named(cpu_affinity, cpu_affinity, int, 0444);
MODULE_PARM_DESC(delay, "Period in microseconds (100 us default)");
MODULE_PARM_DESC(test_mode, "Mode of the test such as preempt, irq, or alternate (default irq)");
MODULE_PARM_DESC(burst_size, "The size of a burst (default 1)");
MODULE_PARM_DESC(cpu_affinity, "Cpu num test is running on");
static struct completion done;
static void busy_wait(ulong time)
{
u64 start, end;
start = trace_clock_local();
do {
end = trace_clock_local();
if (kthread_should_stop())
break;
} while ((end - start) < (time * 1000));
}
static __always_inline void irqoff_test(void)
{
unsigned long flags;
local_irq_save(flags);
busy_wait(delay);
local_irq_restore(flags);
}
static __always_inline void preemptoff_test(void)
{
preempt_disable();
busy_wait(delay);
preempt_enable();
}
static void execute_preemptirqtest(int idx)
{
if (!strcmp(test_mode, "irq"))
irqoff_test();
else if (!strcmp(test_mode, "preempt"))
preemptoff_test();
else if (!strcmp(test_mode, "alternate")) {
if (idx % 2 == 0)
irqoff_test();
else
preemptoff_test();
}
}
#define DECLARE_TESTFN(POSTFIX) \
static void preemptirqtest_##POSTFIX(int idx) \
{ \
execute_preemptirqtest(idx); \
} \
/*
* We create 10 different functions, so that we can get 10 different
* backtraces.
*/
DECLARE_TESTFN(0)
DECLARE_TESTFN(1)
DECLARE_TESTFN(2)
DECLARE_TESTFN(3)
DECLARE_TESTFN(4)
DECLARE_TESTFN(5)
DECLARE_TESTFN(6)
DECLARE_TESTFN(7)
DECLARE_TESTFN(8)
DECLARE_TESTFN(9)
static void (*testfuncs[])(int) = {
preemptirqtest_0,
preemptirqtest_1,
preemptirqtest_2,
preemptirqtest_3,
preemptirqtest_4,
preemptirqtest_5,
preemptirqtest_6,
preemptirqtest_7,
preemptirqtest_8,
preemptirqtest_9,
};
#define NR_TEST_FUNCS ARRAY_SIZE(testfuncs)
static int preemptirq_delay_run(void *data)
{
int i;
int s = MIN(burst_size, NR_TEST_FUNCS);
cpumask_var_t cpu_mask;
if (!alloc_cpumask_var(&cpu_mask, GFP_KERNEL))
return -ENOMEM;
if (cpu_affinity > -1) {
cpumask_clear(cpu_mask);
cpumask_set_cpu(cpu_affinity, cpu_mask);
if (set_cpus_allowed_ptr(current, cpu_mask))
pr_err("cpu_affinity:%d, failed\n", cpu_affinity);
}
for (i = 0; i < s; i++)
(testfuncs[i])(i);
complete(&done);
set_current_state(TASK_INTERRUPTIBLE);
while (!kthread_should_stop()) {
schedule();
set_current_state(TASK_INTERRUPTIBLE);
}
__set_current_state(TASK_RUNNING);
free_cpumask_var(cpu_mask);
return 0;
}
static int preemptirq_run_test(void)
{
struct task_struct *task;
char task_name[50];
init_completion(&done);
snprintf(task_name, sizeof(task_name), "%s_test", test_mode);
task = kthread_run(preemptirq_delay_run, NULL, task_name);
if (IS_ERR(task))
return PTR_ERR(task);
if (task) {
wait_for_completion(&done);
kthread_stop(task);
}
return 0;
}
static ssize_t trigger_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count)
{
ssize_t ret;
ret = preemptirq_run_test();
if (ret)
return ret;
return count;
}
static struct kobj_attribute trigger_attribute =
__ATTR(trigger, 0200, NULL, trigger_store);
static struct attribute *attrs[] = {
&trigger_attribute.attr,
NULL,
};
static struct attribute_group attr_group = {
.attrs = attrs,
};
static struct kobject *preemptirq_delay_kobj;
static int __init preemptirq_delay_init(void)
{
int retval;
retval = preemptirq_run_test();
if (retval != 0)
return retval;
preemptirq_delay_kobj = kobject_create_and_add("preemptirq_delay_test",
kernel_kobj);
if (!preemptirq_delay_kobj)
return -ENOMEM;
retval = sysfs_create_group(preemptirq_delay_kobj, &attr_group);
if (retval)
kobject_put(preemptirq_delay_kobj);
return retval;
}
static void __exit preemptirq_delay_exit(void)
{
kobject_put(preemptirq_delay_kobj);
}
module_init(preemptirq_delay_init)
module_exit(preemptirq_delay_exit)
MODULE_DESCRIPTION("Preempt / IRQ disable delay thread to test latency tracers");
MODULE_LICENSE("GPL v2");
+261
View File
@@ -0,0 +1,261 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2025 - Google LLC
* Author: Vincent Donnefort <vdonnefort@google.com>
*/
#include <linux/module.h>
#include <linux/simple_ring_buffer.h>
#include <linux/trace_remote.h>
#include <linux/tracefs.h>
#include <linux/types.h>
#define REMOTE_EVENT_INCLUDE_FILE kernel/trace/remote_test_events.h
#include <trace/define_remote_events.h>
static DEFINE_PER_CPU(struct simple_rb_per_cpu *, simple_rbs);
static struct trace_buffer_desc *remote_test_buffer_desc;
/*
* The trace_remote lock already serializes accesses from the trace_remote_callbacks.
* However write_event can still race with load/unload.
*/
static DEFINE_MUTEX(simple_rbs_lock);
static int remote_test_load_simple_rb(int cpu, struct ring_buffer_desc *rb_desc)
{
struct simple_rb_per_cpu *cpu_buffer;
struct simple_buffer_page *bpages;
int ret = -ENOMEM;
cpu_buffer = kmalloc_obj(*cpu_buffer);
if (!cpu_buffer)
return ret;
bpages = kmalloc_objs(*bpages, rb_desc->nr_page_va);
if (!bpages)
goto err_free_cpu_buffer;
ret = simple_ring_buffer_init(cpu_buffer, bpages, rb_desc);
if (ret)
goto err_free_bpages;
scoped_guard(mutex, &simple_rbs_lock) {
WARN_ON(*per_cpu_ptr(&simple_rbs, cpu));
*per_cpu_ptr(&simple_rbs, cpu) = cpu_buffer;
}
return 0;
err_free_bpages:
kfree(bpages);
err_free_cpu_buffer:
kfree(cpu_buffer);
return ret;
}
static void remote_test_unload_simple_rb(int cpu)
{
struct simple_rb_per_cpu *cpu_buffer = *per_cpu_ptr(&simple_rbs, cpu);
struct simple_buffer_page *bpages;
if (!cpu_buffer)
return;
guard(mutex)(&simple_rbs_lock);
bpages = cpu_buffer->bpages;
simple_ring_buffer_unload(cpu_buffer);
kfree(bpages);
kfree(cpu_buffer);
*per_cpu_ptr(&simple_rbs, cpu) = NULL;
}
static struct trace_buffer_desc *remote_test_load(unsigned long size, void *unused)
{
struct ring_buffer_desc *rb_desc;
struct trace_buffer_desc *desc;
size_t desc_size;
int cpu, ret;
if (WARN_ON(remote_test_buffer_desc))
return ERR_PTR(-EINVAL);
desc_size = trace_buffer_desc_size(size, num_possible_cpus());
if (desc_size == SIZE_MAX) {
ret = -E2BIG;
goto err;
}
desc = kmalloc(desc_size, GFP_KERNEL);
if (!desc) {
ret = -ENOMEM;
goto err;
}
ret = trace_remote_alloc_buffer(desc, desc_size, size, cpu_possible_mask);
if (ret)
goto err_free_desc;
for_each_ring_buffer_desc(rb_desc, cpu, desc) {
ret = remote_test_load_simple_rb(rb_desc->cpu, rb_desc);
if (ret)
goto err_unload;
}
remote_test_buffer_desc = desc;
return remote_test_buffer_desc;
err_unload:
for_each_ring_buffer_desc(rb_desc, cpu, desc)
remote_test_unload_simple_rb(rb_desc->cpu);
trace_remote_free_buffer(desc);
err_free_desc:
kfree(desc);
err:
return ERR_PTR(ret);
}
static void remote_test_unload(struct trace_buffer_desc *desc, void *unused)
{
struct ring_buffer_desc *rb_desc;
int cpu;
if (WARN_ON(desc != remote_test_buffer_desc))
return;
for_each_ring_buffer_desc(rb_desc, cpu, desc)
remote_test_unload_simple_rb(rb_desc->cpu);
remote_test_buffer_desc = NULL;
trace_remote_free_buffer(desc);
kfree(desc);
}
static int remote_test_enable_tracing(bool enable, void *unused)
{
struct ring_buffer_desc *rb_desc;
int cpu;
if (!remote_test_buffer_desc)
return -ENODEV;
for_each_ring_buffer_desc(rb_desc, cpu, remote_test_buffer_desc)
WARN_ON(simple_ring_buffer_enable_tracing(*per_cpu_ptr(&simple_rbs, rb_desc->cpu),
enable));
return 0;
}
static int remote_test_swap_reader_page(unsigned int cpu, void *unused)
{
struct simple_rb_per_cpu *cpu_buffer;
if (cpu >= NR_CPUS)
return -EINVAL;
cpu_buffer = *per_cpu_ptr(&simple_rbs, cpu);
if (!cpu_buffer)
return -EINVAL;
return simple_ring_buffer_swap_reader_page(cpu_buffer);
}
static int remote_test_reset(unsigned int cpu, void *unused)
{
struct simple_rb_per_cpu *cpu_buffer;
if (cpu >= NR_CPUS)
return -EINVAL;
cpu_buffer = *per_cpu_ptr(&simple_rbs, cpu);
if (!cpu_buffer)
return -EINVAL;
return simple_ring_buffer_reset(cpu_buffer);
}
static int remote_test_enable_event(unsigned short id, bool enable, void *unused)
{
if (id != REMOTE_TEST_EVENT_ID)
return -EINVAL;
/*
* Let's just use the struct remote_event enabled field that is turned on and off by
* trace_remote. This is a bit racy but good enough for a simple test module.
*/
return 0;
}
static ssize_t
write_event_write(struct file *filp, const char __user *ubuf, size_t cnt, loff_t *pos)
{
struct remote_event_format_selftest *evt_test;
struct simple_rb_per_cpu *cpu_buffer;
unsigned long val;
int ret;
ret = kstrtoul_from_user(ubuf, cnt, 10, &val);
if (ret)
return ret;
guard(mutex)(&simple_rbs_lock);
if (!remote_event_selftest.enabled)
return -ENODEV;
guard(preempt)();
cpu_buffer = *this_cpu_ptr(&simple_rbs);
if (!cpu_buffer)
return -ENODEV;
evt_test = simple_ring_buffer_reserve(cpu_buffer,
sizeof(struct remote_event_format_selftest),
trace_clock_global());
if (!evt_test)
return -ENODEV;
evt_test->hdr.id = REMOTE_TEST_EVENT_ID;
evt_test->id = val;
simple_ring_buffer_commit(cpu_buffer);
return cnt;
}
static const struct file_operations write_event_fops = {
.write = write_event_write,
};
static int remote_test_init_tracefs(struct dentry *d, void *unused)
{
return tracefs_create_file("write_event", 0200, d, NULL, &write_event_fops) ?
0 : -ENOMEM;
}
static struct trace_remote_callbacks trace_remote_callbacks = {
.init = remote_test_init_tracefs,
.load_trace_buffer = remote_test_load,
.unload_trace_buffer = remote_test_unload,
.enable_tracing = remote_test_enable_tracing,
.swap_reader_page = remote_test_swap_reader_page,
.reset = remote_test_reset,
.enable_event = remote_test_enable_event,
};
static int __init remote_test_init(void)
{
return trace_remote_register("test", &trace_remote_callbacks, NULL,
&remote_event_selftest, 1);
}
module_init(remote_test_init);
MODULE_DESCRIPTION("Test module for the trace remote interface");
MODULE_AUTHOR("Vincent Donnefort");
MODULE_LICENSE("GPL");
@@ -0,0 +1,10 @@
/* SPDX-License-Identifier: GPL-2.0 */
#define REMOTE_TEST_EVENT_ID 1
REMOTE_EVENT(selftest, REMOTE_TEST_EVENT_ID,
RE_STRUCT(
re_field(u64, id)
),
RE_PRINTK("id=%llu", __entry->id)
);
+337
View File
@@ -0,0 +1,337 @@
// SPDX-License-Identifier: GPL-2.0
#define pr_fmt(fmt) "rethook: " fmt
#include <linux/bug.h>
#include <linux/kallsyms.h>
#include <linux/kprobes.h>
#include <linux/preempt.h>
#include <linux/rethook.h>
#include <linux/slab.h>
/* Return hook list (shadow stack by list) */
/*
* This function is called from delayed_put_task_struct() when a task is
* dead and cleaned up to recycle any kretprobe instances associated with
* this task. These left over instances represent probed functions that
* have been called but will never return.
*/
void rethook_flush_task(struct task_struct *tk)
{
struct rethook_node *rhn;
struct llist_node *node;
node = __llist_del_all(&tk->rethooks);
while (node) {
rhn = container_of(node, struct rethook_node, llist);
node = node->next;
preempt_disable();
rethook_recycle(rhn);
preempt_enable();
}
}
static void rethook_free_rcu(struct rcu_head *head)
{
struct rethook *rh = container_of(head, struct rethook, rcu);
objpool_fini(&rh->pool);
}
/**
* rethook_stop() - Stop using a rethook.
* @rh: the struct rethook to stop.
*
* Stop using a rethook to prepare for freeing it. If you want to wait for
* all running rethook handler before calling rethook_free(), you need to
* call this first and wait RCU, and call rethook_free().
*/
void rethook_stop(struct rethook *rh)
{
rcu_assign_pointer(rh->handler, NULL);
}
/**
* rethook_free() - Free struct rethook.
* @rh: the struct rethook to be freed.
*
* Free the rethook. Before calling this function, user must ensure the
* @rh::data is cleaned if needed (or, the handler can access it after
* calling this function.) This function will set the @rh to be freed
* after all rethook_node are freed (not soon). And the caller must
* not touch @rh after calling this.
*/
void rethook_free(struct rethook *rh)
{
rethook_stop(rh);
call_rcu(&rh->rcu, rethook_free_rcu);
}
static int rethook_init_node(void *nod, void *context)
{
struct rethook_node *node = nod;
node->rethook = context;
return 0;
}
static int rethook_fini_pool(struct objpool_head *head, void *context)
{
kfree(context);
return 0;
}
static inline rethook_handler_t rethook_get_handler(struct rethook *rh)
{
return (rethook_handler_t)rcu_dereference_check(rh->handler,
rcu_read_lock_any_held());
}
/**
* rethook_alloc() - Allocate struct rethook.
* @data: a data to pass the @handler when hooking the return.
* @handler: the return hook callback function, must NOT be NULL
* @size: node size: rethook node and additional data
* @num: number of rethook nodes to be preallocated
*
* Allocate and initialize a new rethook with @data and @handler.
* Return pointer of new rethook, or error codes for failures.
*
* Note that @handler == NULL means this rethook is going to be freed.
*/
struct rethook *rethook_alloc(void *data, rethook_handler_t handler,
int size, int num)
{
struct rethook *rh;
if (!handler || num <= 0 || size < sizeof(struct rethook_node))
return ERR_PTR(-EINVAL);
rh = kzalloc_obj(struct rethook);
if (!rh)
return ERR_PTR(-ENOMEM);
rh->data = data;
rcu_assign_pointer(rh->handler, handler);
/* initialize the objpool for rethook nodes */
if (objpool_init(&rh->pool, num, size, GFP_KERNEL, rh,
rethook_init_node, rethook_fini_pool)) {
kfree(rh);
return ERR_PTR(-ENOMEM);
}
return rh;
}
static void free_rethook_node_rcu(struct rcu_head *head)
{
struct rethook_node *node = container_of(head, struct rethook_node, rcu);
struct rethook *rh = node->rethook;
objpool_drop(node, &rh->pool);
}
/**
* rethook_recycle() - return the node to rethook.
* @node: The struct rethook_node to be returned.
*
* Return back the @node to @node::rethook. If the @node::rethook is already
* marked as freed, this will free the @node.
*/
void rethook_recycle(struct rethook_node *node)
{
rethook_handler_t handler;
handler = rethook_get_handler(node->rethook);
if (likely(handler))
objpool_push(node, &node->rethook->pool);
else
call_rcu(&node->rcu, free_rethook_node_rcu);
}
NOKPROBE_SYMBOL(rethook_recycle);
/**
* rethook_try_get() - get an unused rethook node.
* @rh: The struct rethook which pools the nodes.
*
* Get an unused rethook node from @rh. If the node pool is empty, this
* will return NULL. Caller must disable preemption.
*/
struct rethook_node *rethook_try_get(struct rethook *rh)
{
rethook_handler_t handler = rethook_get_handler(rh);
/* Check whether @rh is going to be freed. */
if (unlikely(!handler))
return NULL;
#if defined(CONFIG_FTRACE_VALIDATE_RCU_IS_WATCHING) || defined(CONFIG_KPROBE_EVENTS_ON_NOTRACE)
/*
* This expects the caller will set up a rethook on a function entry.
* When the function returns, the rethook will eventually be reclaimed
* or released in the rethook_recycle() with call_rcu().
* This means the caller must be run in the RCU-availabe context.
*/
if (unlikely(!rcu_is_watching()))
return NULL;
#endif
return (struct rethook_node *)objpool_pop(&rh->pool);
}
NOKPROBE_SYMBOL(rethook_try_get);
/**
* rethook_hook() - Hook the current function return.
* @node: The struct rethook node to hook the function return.
* @regs: The struct pt_regs for the function entry.
* @mcount: True if this is called from mcount(ftrace) context.
*
* Hook the current running function return. This must be called when the
* function entry (or at least @regs must be the registers of the function
* entry.) @mcount is used for identifying the context. If this is called
* from ftrace (mcount) callback, @mcount must be set true. If this is called
* from the real function entry (e.g. kprobes) @mcount must be set false.
* This is because the way to hook the function return depends on the context.
*/
void rethook_hook(struct rethook_node *node, struct pt_regs *regs, bool mcount)
{
arch_rethook_prepare(node, regs, mcount);
__llist_add(&node->llist, &current->rethooks);
}
NOKPROBE_SYMBOL(rethook_hook);
/* This assumes the 'tsk' is the current task or is not running. */
static unsigned long __rethook_find_ret_addr(struct task_struct *tsk,
struct llist_node **cur)
{
struct rethook_node *rh = NULL;
struct llist_node *node = *cur;
if (!node)
node = tsk->rethooks.first;
else
node = node->next;
while (node) {
rh = container_of(node, struct rethook_node, llist);
if (rh->ret_addr != (unsigned long)arch_rethook_trampoline) {
*cur = node;
return rh->ret_addr;
}
node = node->next;
}
return 0;
}
NOKPROBE_SYMBOL(__rethook_find_ret_addr);
/**
* rethook_find_ret_addr -- Find correct return address modified by rethook
* @tsk: Target task
* @frame: A frame pointer
* @cur: a storage of the loop cursor llist_node pointer for next call
*
* Find the correct return address modified by a rethook on @tsk in unsigned
* long type.
* The @tsk must be 'current' or a task which is not running. @frame is a hint
* to get the currect return address - which is compared with the
* rethook::frame field. The @cur is a loop cursor for searching the
* kretprobe return addresses on the @tsk. The '*@cur' should be NULL at the
* first call, but '@cur' itself must NOT NULL.
*
* Returns found address value or zero if not found.
*/
unsigned long rethook_find_ret_addr(struct task_struct *tsk, unsigned long frame,
struct llist_node **cur)
{
struct rethook_node *rhn = NULL;
unsigned long ret;
if (WARN_ON_ONCE(!cur))
return 0;
if (tsk != current && task_is_running(tsk))
return 0;
do {
ret = __rethook_find_ret_addr(tsk, cur);
if (!ret)
break;
rhn = container_of(*cur, struct rethook_node, llist);
} while (rhn->frame != frame);
return ret;
}
NOKPROBE_SYMBOL(rethook_find_ret_addr);
void __weak arch_rethook_fixup_return(struct pt_regs *regs,
unsigned long correct_ret_addr)
{
/*
* Do nothing by default. If the architecture which uses a
* frame pointer to record real return address on the stack,
* it should fill this function to fixup the return address
* so that stacktrace works from the rethook handler.
*/
}
/* This function will be called from each arch-defined trampoline. */
unsigned long rethook_trampoline_handler(struct pt_regs *regs,
unsigned long frame)
{
struct llist_node *first, *node = NULL;
unsigned long correct_ret_addr;
rethook_handler_t handler;
struct rethook_node *rhn;
correct_ret_addr = __rethook_find_ret_addr(current, &node);
if (!correct_ret_addr) {
pr_err("rethook: Return address not found! Maybe there is a bug in the kernel\n");
BUG_ON(1);
}
instruction_pointer_set(regs, correct_ret_addr);
/*
* These loops must be protected from rethook_free_rcu() because those
* are accessing 'rhn->rethook'.
*/
preempt_disable_notrace();
/*
* Run the handler on the shadow stack. Do not unlink the list here because
* stackdump inside the handlers needs to decode it.
*/
first = current->rethooks.first;
while (first) {
rhn = container_of(first, struct rethook_node, llist);
if (WARN_ON_ONCE(rhn->frame != frame))
break;
handler = rethook_get_handler(rhn->rethook);
if (handler)
handler(rhn, rhn->rethook->data,
correct_ret_addr, regs);
if (first == node)
break;
first = first->next;
}
/* Fixup registers for returning to correct address. */
arch_rethook_fixup_return(regs, correct_ret_addr);
/* Unlink used shadow stack */
first = current->rethooks.first;
current->rethooks.first = node->next;
node->next = NULL;
while (first) {
rhn = container_of(first, struct rethook_node, llist);
first = first->next;
rethook_recycle(rhn);
}
preempt_enable_notrace();
return correct_ret_addr;
}
NOKPROBE_SYMBOL(rethook_trampoline_handler);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,499 @@
// SPDX-License-Identifier: GPL-2.0
/*
* ring buffer tester and benchmark
*
* Copyright (C) 2009 Steven Rostedt <srostedt@redhat.com>
*/
#include <linux/ring_buffer.h>
#include <linux/completion.h>
#include <linux/kthread.h>
#include <uapi/linux/sched/types.h>
#include <linux/module.h>
#include <linux/ktime.h>
#include <asm/local.h>
struct rb_page {
u64 ts;
local_t commit;
char data[4080];
};
/* run time and sleep time in seconds */
#define RUN_TIME 10ULL
#define SLEEP_TIME 10
/* number of events for writer to wake up the reader */
static int wakeup_interval = 100;
static int reader_finish;
static DECLARE_COMPLETION(read_start);
static DECLARE_COMPLETION(read_done);
static struct trace_buffer *buffer;
static struct task_struct *producer;
static struct task_struct *consumer;
static unsigned long read;
static unsigned int disable_reader;
module_param(disable_reader, uint, 0644);
MODULE_PARM_DESC(disable_reader, "only run producer");
static unsigned int write_iteration = 50;
module_param(write_iteration, uint, 0644);
MODULE_PARM_DESC(write_iteration, "# of writes between timestamp readings");
static int producer_nice = MAX_NICE;
static int consumer_nice = MAX_NICE;
static int producer_fifo;
static int consumer_fifo;
module_param(producer_nice, int, 0644);
MODULE_PARM_DESC(producer_nice, "nice prio for producer");
module_param(consumer_nice, int, 0644);
MODULE_PARM_DESC(consumer_nice, "nice prio for consumer");
module_param(producer_fifo, int, 0644);
MODULE_PARM_DESC(producer_fifo, "use fifo for producer: 0 - disabled, 1 - low prio, 2 - fifo");
module_param(consumer_fifo, int, 0644);
MODULE_PARM_DESC(consumer_fifo, "use fifo for consumer: 0 - disabled, 1 - low prio, 2 - fifo");
static int read_events;
static int test_error;
#define TEST_ERROR() \
do { \
if (!test_error) { \
test_error = 1; \
WARN_ON(1); \
} \
} while (0)
enum event_status {
EVENT_FOUND,
EVENT_DROPPED,
};
static bool break_test(void)
{
return test_error || kthread_should_stop();
}
static enum event_status read_event(int cpu)
{
struct ring_buffer_event *event;
int *entry;
u64 ts;
event = ring_buffer_consume(buffer, cpu, &ts, NULL);
if (!event)
return EVENT_DROPPED;
entry = ring_buffer_event_data(event);
if (*entry != cpu) {
TEST_ERROR();
return EVENT_DROPPED;
}
read++;
return EVENT_FOUND;
}
static enum event_status read_page(int cpu)
{
struct buffer_data_read_page *bpage;
struct ring_buffer_event *event;
struct rb_page *rpage;
unsigned long commit;
int page_size;
int *entry;
int ret;
int inc;
int i;
bpage = ring_buffer_alloc_read_page(buffer, cpu);
if (IS_ERR(bpage))
return EVENT_DROPPED;
page_size = ring_buffer_subbuf_size_get(buffer);
ret = ring_buffer_read_page(buffer, bpage, page_size, cpu, 1);
if (ret >= 0) {
rpage = ring_buffer_read_page_data(bpage);
/* The commit may have missed event flags set, clear them */
commit = local_read(&rpage->commit) & 0xfffff;
for (i = 0; i < commit && !test_error ; i += inc) {
if (i >= (page_size - offsetof(struct rb_page, data))) {
TEST_ERROR();
break;
}
inc = -1;
event = (void *)&rpage->data[i];
switch (event->type_len) {
case RINGBUF_TYPE_PADDING:
/* failed writes may be discarded events */
if (!event->time_delta)
TEST_ERROR();
inc = event->array[0] + 4;
break;
case RINGBUF_TYPE_TIME_EXTEND:
inc = 8;
break;
case 0:
entry = ring_buffer_event_data(event);
if (*entry != cpu) {
TEST_ERROR();
break;
}
read++;
if (!event->array[0]) {
TEST_ERROR();
break;
}
inc = event->array[0] + 4;
break;
default:
entry = ring_buffer_event_data(event);
if (*entry != cpu) {
TEST_ERROR();
break;
}
read++;
inc = ((event->type_len + 1) * 4);
}
if (test_error)
break;
if (inc <= 0) {
TEST_ERROR();
break;
}
}
}
ring_buffer_free_read_page(buffer, cpu, bpage);
if (ret < 0)
return EVENT_DROPPED;
return EVENT_FOUND;
}
static void ring_buffer_consumer(void)
{
/* toggle between reading pages and events */
read_events ^= 1;
read = 0;
/*
* Continue running until the producer specifically asks to stop
* and is ready for the completion.
*/
while (!READ_ONCE(reader_finish)) {
int found = 1;
while (found && !test_error) {
int cpu;
found = 0;
for_each_online_cpu(cpu) {
enum event_status stat;
if (read_events)
stat = read_event(cpu);
else
stat = read_page(cpu);
if (test_error)
break;
if (stat == EVENT_FOUND)
found = 1;
}
}
/* Wait till the producer wakes us up when there is more data
* available or when the producer wants us to finish reading.
*/
set_current_state(TASK_INTERRUPTIBLE);
if (reader_finish)
break;
schedule();
}
__set_current_state(TASK_RUNNING);
reader_finish = 0;
complete(&read_done);
}
static void ring_buffer_producer(void)
{
ktime_t start_time, end_time, timeout;
unsigned long long time;
unsigned long long entries;
unsigned long long overruns;
unsigned long missed = 0;
unsigned long hit = 0;
unsigned long avg;
int cnt = 0;
/*
* Hammer the buffer for 10 secs (this may
* make the system stall)
*/
trace_printk("Starting ring buffer hammer\n");
start_time = ktime_get();
timeout = ktime_add_ns(start_time, RUN_TIME * NSEC_PER_SEC);
do {
struct ring_buffer_event *event;
int *entry;
int i;
for (i = 0; i < write_iteration; i++) {
event = ring_buffer_lock_reserve(buffer, 10);
if (!event) {
missed++;
} else {
hit++;
entry = ring_buffer_event_data(event);
*entry = smp_processor_id();
ring_buffer_unlock_commit(buffer);
}
}
end_time = ktime_get();
cnt++;
if (consumer && !(cnt % wakeup_interval))
wake_up_process(consumer);
#ifndef CONFIG_PREEMPTION
/*
* If we are a non preempt kernel, the 10 seconds run will
* stop everything while it runs. Instead, we will call
* cond_resched and also add any time that was lost by a
* reschedule.
*
* Do a cond resched at the same frequency we would wake up
* the reader.
*/
if (cnt % wakeup_interval)
cond_resched();
#endif
} while (ktime_before(end_time, timeout) && !break_test());
trace_printk("End ring buffer hammer\n");
if (consumer) {
/* Init both completions here to avoid races */
init_completion(&read_start);
init_completion(&read_done);
/* the completions must be visible before the finish var */
smp_wmb();
reader_finish = 1;
wake_up_process(consumer);
wait_for_completion(&read_done);
}
time = ktime_us_delta(end_time, start_time);
entries = ring_buffer_entries(buffer);
overruns = ring_buffer_overruns(buffer);
if (test_error)
trace_printk("ERROR!\n");
if (!disable_reader) {
if (consumer_fifo)
trace_printk("Running Consumer at SCHED_FIFO %s\n",
str_low_high(consumer_fifo == 1));
else
trace_printk("Running Consumer at nice: %d\n",
consumer_nice);
}
if (producer_fifo)
trace_printk("Running Producer at SCHED_FIFO %s\n",
str_low_high(producer_fifo == 1));
else
trace_printk("Running Producer at nice: %d\n",
producer_nice);
/* Let the user know that the test is running at low priority */
if (!producer_fifo && !consumer_fifo &&
producer_nice == MAX_NICE && consumer_nice == MAX_NICE)
trace_printk("WARNING!!! This test is running at lowest priority.\n");
trace_printk("Time: %lld (usecs)\n", time);
trace_printk("Overruns: %lld\n", overruns);
if (disable_reader)
trace_printk("Read: (reader disabled)\n");
else
trace_printk("Read: %ld (by %s)\n", read,
read_events ? "events" : "pages");
trace_printk("Entries: %lld\n", entries);
trace_printk("Total: %lld\n", entries + overruns + read);
trace_printk("Missed: %ld\n", missed);
trace_printk("Hit: %ld\n", hit);
/* Convert time from usecs to millisecs */
do_div(time, USEC_PER_MSEC);
if (time)
hit /= (long)time;
else
trace_printk("TIME IS ZERO??\n");
trace_printk("Entries per millisec: %ld\n", hit);
if (hit) {
/* Calculate the average time in nanosecs */
avg = NSEC_PER_MSEC / hit;
trace_printk("%ld ns per entry\n", avg);
}
if (missed) {
if (time)
missed /= (long)time;
trace_printk("Total iterations per millisec: %ld\n",
hit + missed);
/* it is possible that hit + missed will overflow and be zero */
if (!(hit + missed)) {
trace_printk("hit + missed overflowed and totalled zero!\n");
hit--; /* make it non zero */
}
/* Calculate the average time in nanosecs */
avg = NSEC_PER_MSEC / (hit + missed);
trace_printk("%ld ns per entry\n", avg);
}
}
static void wait_to_die(void)
{
set_current_state(TASK_INTERRUPTIBLE);
while (!kthread_should_stop()) {
schedule();
set_current_state(TASK_INTERRUPTIBLE);
}
__set_current_state(TASK_RUNNING);
}
static int ring_buffer_consumer_thread(void *arg)
{
while (!break_test()) {
complete(&read_start);
ring_buffer_consumer();
set_current_state(TASK_INTERRUPTIBLE);
if (break_test())
break;
schedule();
}
__set_current_state(TASK_RUNNING);
if (!kthread_should_stop())
wait_to_die();
return 0;
}
static int ring_buffer_producer_thread(void *arg)
{
while (!break_test()) {
ring_buffer_reset(buffer);
if (consumer) {
wake_up_process(consumer);
wait_for_completion(&read_start);
}
ring_buffer_producer();
if (break_test())
goto out_kill;
trace_printk("Sleeping for 10 secs\n");
set_current_state(TASK_INTERRUPTIBLE);
if (break_test())
goto out_kill;
schedule_timeout(HZ * SLEEP_TIME);
}
out_kill:
__set_current_state(TASK_RUNNING);
if (!kthread_should_stop())
wait_to_die();
return 0;
}
static int __init ring_buffer_benchmark_init(void)
{
int ret;
/* make a one meg buffer in overwrite mode */
buffer = ring_buffer_alloc(1000000, RB_FL_OVERWRITE);
if (!buffer)
return -ENOMEM;
if (!disable_reader) {
consumer = kthread_create(ring_buffer_consumer_thread,
NULL, "rb_consumer");
ret = PTR_ERR(consumer);
if (IS_ERR(consumer))
goto out_fail;
}
producer = kthread_run(ring_buffer_producer_thread,
NULL, "rb_producer");
ret = PTR_ERR(producer);
if (IS_ERR(producer))
goto out_kill;
/*
* Run them as low-prio background tasks by default:
*/
if (!disable_reader) {
if (consumer_fifo >= 2)
sched_set_fifo(consumer);
else if (consumer_fifo == 1)
sched_set_fifo_low(consumer);
else
set_user_nice(consumer, consumer_nice);
}
if (producer_fifo >= 2)
sched_set_fifo(producer);
else if (producer_fifo == 1)
sched_set_fifo_low(producer);
else
set_user_nice(producer, producer_nice);
return 0;
out_kill:
if (consumer)
kthread_stop(consumer);
out_fail:
ring_buffer_free(buffer);
return ret;
}
static void __exit ring_buffer_benchmark_exit(void)
{
kthread_stop(producer);
if (consumer)
kthread_stop(consumer);
ring_buffer_free(buffer);
}
module_init(ring_buffer_benchmark_init);
module_exit(ring_buffer_benchmark_exit);
MODULE_AUTHOR("Steven Rostedt");
MODULE_DESCRIPTION("ring_buffer_benchmark");
MODULE_LICENSE("GPL");
+21
View File
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Power trace points
*
* Copyright (C) 2009 Ming Lei <ming.lei@canonical.com>
*/
#include <linux/string.h>
#include <linux/types.h>
#include <linux/workqueue.h>
#include <linux/sched.h>
#include <linux/module.h>
#include <linux/usb.h>
#define CREATE_TRACE_POINTS
#include <trace/events/rpm.h>
EXPORT_TRACEPOINT_SYMBOL_GPL(rpm_return_int);
EXPORT_TRACEPOINT_SYMBOL_GPL(rpm_idle);
EXPORT_TRACEPOINT_SYMBOL_GPL(rpm_suspend);
EXPORT_TRACEPOINT_SYMBOL_GPL(rpm_resume);
+113
View File
@@ -0,0 +1,113 @@
# SPDX-License-Identifier: GPL-2.0-only
#
config RV_MON_EVENTS
bool
config RV_MON_MAINTENANCE_EVENTS
bool
config DA_MON_EVENTS_IMPLICIT
select RV_MON_EVENTS
select RV_MON_MAINTENANCE_EVENTS
bool
config DA_MON_EVENTS_ID
select RV_MON_EVENTS
select RV_MON_MAINTENANCE_EVENTS
bool
config LTL_MON_EVENTS_ID
select RV_MON_EVENTS
bool
config RV_LTL_MONITOR
bool
config RV_HA_MONITOR
bool
config HA_MON_EVENTS_IMPLICIT
select DA_MON_EVENTS_IMPLICIT
select RV_HA_MONITOR
bool
config HA_MON_EVENTS_ID
select DA_MON_EVENTS_ID
select RV_HA_MONITOR
bool
menuconfig RV
bool "Runtime Verification"
select TRACING
help
Enable the kernel runtime verification infrastructure. RV is a
lightweight (yet rigorous) method that complements classical
exhaustive verification techniques (such as model checking and
theorem proving). RV works by analyzing the trace of the system's
actual execution, comparing it against a formal specification of
the system behavior.
For further information, see:
Documentation/trace/rv/runtime-verification.rst
config RV_PER_TASK_MONITORS
int "Maximum number of per-task monitor"
depends on RV
range 1 8
default 2
help
This option configures the maximum number of per-task RV monitors that can run
simultaneously.
source "kernel/trace/rv/monitors/wip/Kconfig"
source "kernel/trace/rv/monitors/wwnr/Kconfig"
source "kernel/trace/rv/monitors/sched/Kconfig"
source "kernel/trace/rv/monitors/sco/Kconfig"
source "kernel/trace/rv/monitors/snroc/Kconfig"
source "kernel/trace/rv/monitors/scpd/Kconfig"
source "kernel/trace/rv/monitors/snep/Kconfig"
source "kernel/trace/rv/monitors/sts/Kconfig"
source "kernel/trace/rv/monitors/nrp/Kconfig"
source "kernel/trace/rv/monitors/sssw/Kconfig"
source "kernel/trace/rv/monitors/opid/Kconfig"
# Add new sched monitors here
source "kernel/trace/rv/monitors/rtapp/Kconfig"
source "kernel/trace/rv/monitors/pagefault/Kconfig"
source "kernel/trace/rv/monitors/sleep/Kconfig"
# Add new rtapp monitors here
source "kernel/trace/rv/monitors/stall/Kconfig"
source "kernel/trace/rv/monitors/deadline/Kconfig"
source "kernel/trace/rv/monitors/nomiss/Kconfig"
# Add new deadline monitors here
# Add new monitors here
config RV_REACTORS
bool "Runtime verification reactors"
default y
depends on RV
help
Enables the online runtime verification reactors. A runtime
monitor can cause a reaction to the detection of an exception
on the model's execution. By default, the monitors have
tracing reactions, printing the monitor output via tracepoints,
but other reactions can be added (on-demand) via this interface.
config RV_REACT_PRINTK
bool "Printk reactor"
depends on RV_REACTORS
default y
help
Enables the printk reactor. The printk reactor emits a printk()
message if an exception is found.
config RV_REACT_PANIC
bool "Panic reactor"
depends on RV_REACTORS
default y
help
Enables the panic reactor. The panic reactor emits a printk()
message if an exception is found and panic()s the system.
+26
View File
@@ -0,0 +1,26 @@
# SPDX-License-Identifier: GPL-2.0
ccflags-y += -I $(src) # needed for trace events
obj-$(CONFIG_RV) += rv.o
obj-$(CONFIG_RV_MON_WIP) += monitors/wip/wip.o
obj-$(CONFIG_RV_MON_WWNR) += monitors/wwnr/wwnr.o
obj-$(CONFIG_RV_MON_SCHED) += monitors/sched/sched.o
obj-$(CONFIG_RV_MON_SCO) += monitors/sco/sco.o
obj-$(CONFIG_RV_MON_SNROC) += monitors/snroc/snroc.o
obj-$(CONFIG_RV_MON_SCPD) += monitors/scpd/scpd.o
obj-$(CONFIG_RV_MON_SNEP) += monitors/snep/snep.o
obj-$(CONFIG_RV_MON_RTAPP) += monitors/rtapp/rtapp.o
obj-$(CONFIG_RV_MON_PAGEFAULT) += monitors/pagefault/pagefault.o
obj-$(CONFIG_RV_MON_SLEEP) += monitors/sleep/sleep.o
obj-$(CONFIG_RV_MON_STS) += monitors/sts/sts.o
obj-$(CONFIG_RV_MON_NRP) += monitors/nrp/nrp.o
obj-$(CONFIG_RV_MON_SSSW) += monitors/sssw/sssw.o
obj-$(CONFIG_RV_MON_OPID) += monitors/opid/opid.o
obj-$(CONFIG_RV_MON_STALL) += monitors/stall/stall.o
obj-$(CONFIG_RV_MON_DEADLINE) += monitors/deadline/deadline.o
obj-$(CONFIG_RV_MON_NOMISS) += monitors/nomiss/nomiss.o
# Add new monitors here
obj-$(CONFIG_RV_REACTORS) += rv_reactors.o
obj-$(CONFIG_RV_REACT_PRINTK) += reactor_printk.o
obj-$(CONFIG_RV_REACT_PANIC) += reactor_panic.o
@@ -0,0 +1,10 @@
config RV_MON_DEADLINE
depends on RV
bool "deadline monitor"
help
Collection of monitors to check the deadline scheduler and server
behave according to specifications. Enable this to enable all
scheduler specification supported by the current kernel.
For further information, see:
Documentation/trace/rv/monitor_deadline.rst
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/rv.h>
#include <linux/kallsyms.h>
#define MODULE_NAME "deadline"
#include "deadline.h"
struct rv_monitor rv_deadline = {
.name = "deadline",
.description = "container for several deadline scheduler specifications.",
.enable = NULL,
.disable = NULL,
.reset = NULL,
.enabled = 0,
};
/* Used by other monitors */
struct sched_class *rv_ext_sched_class;
static int __init register_deadline(void)
{
if (IS_ENABLED(CONFIG_SCHED_CLASS_EXT)) {
rv_ext_sched_class = (void *)kallsyms_lookup_name("ext_sched_class");
if (!rv_ext_sched_class)
pr_warn("rv: Missing ext_sched_class, monitors may not work.\n");
}
return rv_register_monitor(&rv_deadline, NULL);
}
static void __exit unregister_deadline(void)
{
rv_unregister_monitor(&rv_deadline);
}
module_init(register_deadline);
module_exit(unregister_deadline);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Gabriele Monaco <gmonaco@redhat.com>");
MODULE_DESCRIPTION("deadline: container for several deadline scheduler specifications.");
@@ -0,0 +1,203 @@
/* SPDX-License-Identifier: GPL-2.0 */
#include <linux/kernel.h>
#include <linux/uaccess.h>
#include <linux/sched/deadline.h>
#include <asm/syscall.h>
#include <uapi/linux/sched/types.h>
#include <trace/events/sched.h>
/*
* Dummy values if not available
*/
#ifndef __NR_sched_setscheduler
#define __NR_sched_setscheduler -__COUNTER__
#endif
#ifndef __NR_sched_setattr
#define __NR_sched_setattr -__COUNTER__
#endif
extern struct rv_monitor rv_deadline;
/* Initialised when registering the deadline container */
extern struct sched_class *rv_ext_sched_class;
/*
* If both have dummy values, the syscalls are not supported and we don't even
* need to register the handler.
*/
static inline bool should_skip_syscall_handle(void)
{
return __NR_sched_setattr < 0 && __NR_sched_setscheduler < 0;
}
/*
* is_supported_type - return true if @type is supported by the deadline monitors
*/
static inline bool is_supported_type(u8 type)
{
return type == DL_TASK || type == DL_SERVER_FAIR || type == DL_SERVER_EXT;
}
/*
* is_server_type - return true if @type is a supported server
*/
static inline bool is_server_type(u8 type)
{
return is_supported_type(type) && type != DL_TASK;
}
/*
* Use negative numbers for the server.
* Currently only one fair server per CPU, may change in the future.
*/
#define fair_server_id(cpu) (-cpu)
#define ext_server_id(cpu) (-cpu - num_possible_cpus())
#define NO_SERVER_ID (-2 * num_possible_cpus())
/*
* Get a unique id used for dl entities
*
* The cpu is not required for tasks as the pid is used there, if this function
* is called on a dl_se that for sure corresponds to a task, DL_TASK can be
* used in place of cpu.
* We need the cpu for servers as it is provided in the tracepoint and we
* cannot easily retrieve it from the dl_se (requires the struct rq definition).
*/
static inline int get_entity_id(struct sched_dl_entity *dl_se, int cpu, u8 type)
{
if (dl_server(dl_se) && type != DL_TASK) {
if (type == DL_SERVER_FAIR)
return fair_server_id(cpu);
if (type == DL_SERVER_EXT)
return ext_server_id(cpu);
return NO_SERVER_ID;
}
return dl_task_of(dl_se)->pid;
}
static inline bool task_is_scx_enabled(struct task_struct *tsk)
{
return IS_ENABLED(CONFIG_SCHED_CLASS_EXT) &&
tsk->sched_class == rv_ext_sched_class;
}
/* Expand id and target as arguments for da functions */
#define EXPAND_ID(dl_se, cpu, type) get_entity_id(dl_se, cpu, type), dl_se
#define EXPAND_ID_TASK(tsk) get_entity_id(&tsk->dl, task_cpu(tsk), DL_TASK), &tsk->dl
static inline u8 get_server_type(struct task_struct *tsk)
{
if (tsk->policy == SCHED_NORMAL || tsk->policy == SCHED_EXT ||
tsk->policy == SCHED_BATCH || tsk->policy == SCHED_IDLE)
return task_is_scx_enabled(tsk) ? DL_SERVER_EXT : DL_SERVER_FAIR;
return DL_OTHER;
}
static inline int extract_params(struct pt_regs *regs, long id, pid_t *pid_out)
{
size_t size = offsetofend(struct sched_attr, sched_flags);
struct sched_attr __user *uattr;
struct sched_attr attr;
int new_policy = -1, ret;
unsigned long args[6];
switch (id) {
case __NR_sched_setscheduler:
syscall_get_arguments(current, regs, args);
*pid_out = args[0];
new_policy = args[1];
break;
case __NR_sched_setattr:
syscall_get_arguments(current, regs, args);
*pid_out = args[0];
uattr = (struct sched_attr __user *)args[1];
/*
* Just copy up to sched_flags, we are not interested after that
*/
ret = copy_struct_from_user(&attr, size, uattr, size);
if (ret)
return ret;
if (attr.sched_flags & SCHED_FLAG_KEEP_POLICY)
return -EINVAL;
new_policy = attr.sched_policy;
break;
default:
return -EINVAL;
}
return new_policy & ~SCHED_RESET_ON_FORK;
}
/* Helper functions requiring DA/HA utilities */
#ifdef RV_MON_TYPE
/*
* get_fair_server - get the fair server associated to a task
*
* If the task is a boosted task, the server is available in the task_struct,
* otherwise grab the dl entity saved for the CPU where the task is enqueued.
* This function assumes the task is enqueued somewhere.
*/
static inline struct sched_dl_entity *get_server(struct task_struct *tsk, u8 type)
{
if (tsk->dl_server && get_server_type(tsk) == type)
return tsk->dl_server;
if (type == DL_SERVER_FAIR)
return da_get_target_by_id(fair_server_id(task_cpu(tsk)));
if (type == DL_SERVER_EXT)
return da_get_target_by_id(ext_server_id(task_cpu(tsk)));
return NULL;
}
/*
* Initialise monitors for all tasks and pre-allocate the storage for servers.
* This is necessary since we don't have access to the servers here and
* allocation can cause deadlocks from their tracepoints. We can only fill
* pre-initialised storage from there.
*/
static inline int init_storage(bool skip_tasks)
{
struct task_struct *g, *p;
int cpu;
for_each_possible_cpu(cpu) {
if (!da_create_empty_storage(fair_server_id(cpu)))
goto fail;
if (IS_ENABLED(CONFIG_SCHED_CLASS_EXT) &&
!da_create_empty_storage(ext_server_id(cpu)))
goto fail;
}
if (skip_tasks)
return 0;
read_lock(&tasklist_lock);
for_each_process_thread(g, p) {
if (p->policy == SCHED_DEADLINE) {
if (!da_create_storage(EXPAND_ID_TASK(p), NULL)) {
read_unlock(&tasklist_lock);
goto fail;
}
}
}
read_unlock(&tasklist_lock);
return 0;
fail:
da_monitor_destroy();
return -ENOMEM;
}
static void __maybe_unused handle_newtask(void *data, struct task_struct *task, u64 flags)
{
/* Might be superfluous as tasks are not started with this policy.. */
if (task->policy == SCHED_DEADLINE)
da_create_storage(EXPAND_ID_TASK(task), NULL);
}
static void __maybe_unused handle_exit(void *data, struct task_struct *p, bool group_dead)
{
if (p->policy == SCHED_DEADLINE)
da_destroy_storage(get_entity_id(&p->dl, DL_TASK, DL_TASK));
}
#endif
@@ -0,0 +1,15 @@
# SPDX-License-Identifier: GPL-2.0-only
#
config RV_MON_NOMISS
depends on RV
depends on HAVE_SYSCALL_TRACEPOINTS
depends on RV_MON_DEADLINE
default y
select HA_MON_EVENTS_ID
bool "nomiss monitor"
help
Monitor to ensure dl entities run to completion before their deadiline.
This monitor is part of the deadline monitors collection.
For further information, see:
Documentation/trace/rv/monitor_deadline.rst
@@ -0,0 +1,293 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/ftrace.h>
#include <linux/tracepoint.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/rv.h>
#include <rv/instrumentation.h>
#define MODULE_NAME "nomiss"
#include <uapi/linux/sched/types.h>
#include <trace/events/syscalls.h>
#include <trace/events/sched.h>
#include <trace/events/task.h>
#include <rv_trace.h>
#define RV_MON_TYPE RV_MON_PER_OBJ
#define HA_TIMER_TYPE HA_TIMER_WHEEL
/* The start condition is on sched_switch, it's dangerous to allocate there */
#define DA_SKIP_AUTO_ALLOC
typedef struct sched_dl_entity *monitor_target;
#include "nomiss.h"
#include <rv/ha_monitor.h>
#include <monitors/deadline/deadline.h>
/*
* User configurable deadline threshold. If the total utilisation of deadline
* tasks is larger than 1, they are only guaranteed bounded tardiness. See
* Documentation/scheduler/sched-deadline.rst for more details.
* The minimum tardiness without sched_feat(HRTICK_DL) is 1 tick to accommodate
* for throttle enforced on the next tick.
*/
static u64 deadline_thresh = TICK_NSEC;
module_param(deadline_thresh, ullong, 0644);
#define DEADLINE_NS(ha_mon) (ha_get_target(ha_mon)->dl_deadline + deadline_thresh)
static u64 ha_get_env(struct ha_monitor *ha_mon, enum envs_nomiss env, u64 time_ns)
{
if (env == clk_nomiss)
return ha_get_clk_ns(ha_mon, env, time_ns);
else if (env == is_constr_dl_nomiss)
return !dl_is_implicit(ha_get_target(ha_mon));
else if (env == is_defer_nomiss)
return ha_get_target(ha_mon)->dl_defer;
return ENV_INVALID_VALUE;
}
static void ha_reset_env(struct ha_monitor *ha_mon, enum envs_nomiss env, u64 time_ns)
{
if (env == clk_nomiss)
ha_reset_clk_ns(ha_mon, env, time_ns);
}
static inline bool ha_verify_invariants(struct ha_monitor *ha_mon,
enum states curr_state, enum events event,
enum states next_state, u64 time_ns)
{
if (curr_state == ready_nomiss)
return ha_check_invariant_ns(ha_mon, clk_nomiss, time_ns);
else if (curr_state == running_nomiss)
return ha_check_invariant_ns(ha_mon, clk_nomiss, time_ns);
return true;
}
static inline void ha_convert_inv_guard(struct ha_monitor *ha_mon,
enum states curr_state, enum events event,
enum states next_state, u64 time_ns)
{
if (curr_state == next_state)
return;
if (curr_state == ready_nomiss)
ha_inv_to_guard(ha_mon, clk_nomiss, DEADLINE_NS(ha_mon), time_ns);
else if (curr_state == running_nomiss)
ha_inv_to_guard(ha_mon, clk_nomiss, DEADLINE_NS(ha_mon), time_ns);
}
static inline bool ha_verify_guards(struct ha_monitor *ha_mon,
enum states curr_state, enum events event,
enum states next_state, u64 time_ns)
{
bool res = true;
if (curr_state == ready_nomiss && event == dl_replenish_nomiss)
ha_reset_env(ha_mon, clk_nomiss, time_ns);
else if (curr_state == ready_nomiss && event == dl_throttle_nomiss)
res = ha_get_env(ha_mon, is_defer_nomiss, time_ns) == 1ull;
else if (curr_state == idle_nomiss && event == dl_replenish_nomiss)
ha_reset_env(ha_mon, clk_nomiss, time_ns);
else if (curr_state == running_nomiss && event == dl_replenish_nomiss)
ha_reset_env(ha_mon, clk_nomiss, time_ns);
else if (curr_state == sleeping_nomiss && event == dl_replenish_nomiss)
ha_reset_env(ha_mon, clk_nomiss, time_ns);
else if (curr_state == sleeping_nomiss && event == dl_throttle_nomiss)
res = ha_get_env(ha_mon, is_constr_dl_nomiss, time_ns) == 1ull ||
ha_get_env(ha_mon, is_defer_nomiss, time_ns) == 1ull;
else if (curr_state == throttled_nomiss && event == dl_replenish_nomiss)
ha_reset_env(ha_mon, clk_nomiss, time_ns);
return res;
}
static inline void ha_setup_invariants(struct ha_monitor *ha_mon,
enum states curr_state, enum events event,
enum states next_state, u64 time_ns)
{
if (next_state == curr_state && event != dl_replenish_nomiss)
return;
if (next_state == ready_nomiss)
ha_start_timer_ns(ha_mon, clk_nomiss, DEADLINE_NS(ha_mon), time_ns);
else if (next_state == running_nomiss)
ha_start_timer_ns(ha_mon, clk_nomiss, DEADLINE_NS(ha_mon), time_ns);
else if (curr_state == ready_nomiss)
ha_cancel_timer(ha_mon);
else if (curr_state == running_nomiss)
ha_cancel_timer(ha_mon);
}
static bool ha_verify_constraint(struct ha_monitor *ha_mon,
enum states curr_state, enum events event,
enum states next_state, u64 time_ns)
{
if (!ha_verify_invariants(ha_mon, curr_state, event, next_state, time_ns))
return false;
ha_convert_inv_guard(ha_mon, curr_state, event, next_state, time_ns);
if (!ha_verify_guards(ha_mon, curr_state, event, next_state, time_ns))
return false;
ha_setup_invariants(ha_mon, curr_state, event, next_state, time_ns);
return true;
}
static void handle_dl_replenish(void *data, struct sched_dl_entity *dl_se,
int cpu, u8 type)
{
if (is_supported_type(type))
da_handle_event(EXPAND_ID(dl_se, cpu, type), dl_replenish_nomiss);
}
static void handle_dl_throttle(void *data, struct sched_dl_entity *dl_se,
int cpu, u8 type)
{
if (is_supported_type(type))
da_handle_event(EXPAND_ID(dl_se, cpu, type), dl_throttle_nomiss);
}
static void handle_dl_server_stop(void *data, struct sched_dl_entity *dl_se,
int cpu, u8 type)
{
/*
* This isn't the standard use of da_handle_start_run_event since this
* event cannot only occur from the initial state.
* It is fine to use here because it always brings to a known state and
* the fact we "pretend" the transition starts from the initial state
* has no side effect.
*/
if (is_supported_type(type))
da_handle_start_run_event(EXPAND_ID(dl_se, cpu, type), dl_server_stop_nomiss);
}
static inline void handle_server_switch(struct task_struct *next, int cpu, u8 type)
{
struct sched_dl_entity *dl_se = get_server(next, type);
if (dl_se && is_idle_task(next))
da_handle_event(EXPAND_ID(dl_se, cpu, type), dl_server_idle_nomiss);
}
static void handle_sched_switch(void *data, bool preempt,
struct task_struct *prev,
struct task_struct *next,
unsigned int prev_state)
{
int cpu = task_cpu(next);
if (prev_state != TASK_RUNNING && !preempt && prev->policy == SCHED_DEADLINE)
da_handle_event(EXPAND_ID_TASK(prev), sched_switch_suspend_nomiss);
if (next->policy == SCHED_DEADLINE)
da_handle_start_run_event(EXPAND_ID_TASK(next), sched_switch_in_nomiss);
/*
* The server is available in next only if the next task is boosted,
* otherwise we need to retrieve it.
* Here the server continues in the state running/armed until actually
* stopped, this works since we continue expecting a throttle.
*/
if (next->dl_server)
da_handle_start_event(EXPAND_ID(next->dl_server, cpu,
get_server_type(next)),
sched_switch_in_nomiss);
else {
handle_server_switch(next, cpu, DL_SERVER_FAIR);
if (IS_ENABLED(CONFIG_SCHED_CLASS_EXT))
handle_server_switch(next, cpu, DL_SERVER_EXT);
}
}
static void handle_sys_enter(void *data, struct pt_regs *regs, long id)
{
struct task_struct *p;
int new_policy = -1;
pid_t pid = 0;
new_policy = extract_params(regs, id, &pid);
if (new_policy < 0)
return;
guard(rcu)();
p = pid ? find_task_by_vpid(pid) : current;
if (unlikely(!p) || new_policy == p->policy)
return;
if (p->policy == SCHED_DEADLINE)
da_reset(EXPAND_ID_TASK(p));
else if (new_policy == SCHED_DEADLINE)
da_create_or_get(EXPAND_ID_TASK(p));
}
static void handle_sched_wakeup(void *data, struct task_struct *tsk)
{
if (tsk->policy == SCHED_DEADLINE)
da_handle_event(EXPAND_ID_TASK(tsk), sched_wakeup_nomiss);
}
static int enable_nomiss(void)
{
int retval;
retval = ha_monitor_init();
if (retval)
return retval;
retval = init_storage(false);
if (retval)
return retval;
rv_attach_trace_probe("nomiss", sched_dl_replenish_tp, handle_dl_replenish);
rv_attach_trace_probe("nomiss", sched_dl_throttle_tp, handle_dl_throttle);
rv_attach_trace_probe("nomiss", sched_dl_server_stop_tp, handle_dl_server_stop);
rv_attach_trace_probe("nomiss", sched_switch, handle_sched_switch);
rv_attach_trace_probe("nomiss", sched_wakeup, handle_sched_wakeup);
if (!should_skip_syscall_handle())
rv_attach_trace_probe("nomiss", sys_enter, handle_sys_enter);
rv_attach_trace_probe("nomiss", task_newtask, handle_newtask);
rv_attach_trace_probe("nomiss", sched_process_exit, handle_exit);
return 0;
}
static void disable_nomiss(void)
{
rv_this.enabled = 0;
/* Those are RCU writers, detach earlier hoping to close a bit faster */
rv_detach_trace_probe("nomiss", task_newtask, handle_newtask);
rv_detach_trace_probe("nomiss", sched_process_exit, handle_exit);
if (!should_skip_syscall_handle())
rv_detach_trace_probe("nomiss", sys_enter, handle_sys_enter);
rv_detach_trace_probe("nomiss", sched_dl_replenish_tp, handle_dl_replenish);
rv_detach_trace_probe("nomiss", sched_dl_throttle_tp, handle_dl_throttle);
rv_detach_trace_probe("nomiss", sched_dl_server_stop_tp, handle_dl_server_stop);
rv_detach_trace_probe("nomiss", sched_switch, handle_sched_switch);
rv_detach_trace_probe("nomiss", sched_wakeup, handle_sched_wakeup);
ha_monitor_destroy();
}
static struct rv_monitor rv_this = {
.name = "nomiss",
.description = "dl entities run to completion before their deadline.",
.enable = enable_nomiss,
.disable = disable_nomiss,
.reset = da_monitor_reset_all,
.enabled = 0,
};
static int __init register_nomiss(void)
{
return rv_register_monitor(&rv_this, &rv_deadline);
}
static void __exit unregister_nomiss(void)
{
rv_unregister_monitor(&rv_this);
}
module_init(register_nomiss);
module_exit(unregister_nomiss);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Gabriele Monaco <gmonaco@redhat.com>");
MODULE_DESCRIPTION("nomiss: dl entities run to completion before their deadline.");
@@ -0,0 +1,123 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Automatically generated C representation of nomiss automaton
* For further information about this format, see kernel documentation:
* Documentation/trace/rv/deterministic_automata.rst
*/
#define MONITOR_NAME nomiss
enum states_nomiss {
ready_nomiss,
idle_nomiss,
running_nomiss,
sleeping_nomiss,
throttled_nomiss,
state_max_nomiss,
};
#define INVALID_STATE state_max_nomiss
enum events_nomiss {
dl_replenish_nomiss,
dl_server_idle_nomiss,
dl_server_stop_nomiss,
dl_throttle_nomiss,
sched_switch_in_nomiss,
sched_switch_suspend_nomiss,
sched_wakeup_nomiss,
event_max_nomiss,
};
enum envs_nomiss {
clk_nomiss,
is_constr_dl_nomiss,
is_defer_nomiss,
env_max_nomiss,
env_max_stored_nomiss = is_constr_dl_nomiss,
};
_Static_assert(env_max_stored_nomiss <= MAX_HA_ENV_LEN, "Not enough slots");
#define HA_CLK_NS
struct automaton_nomiss {
char *state_names[state_max_nomiss];
char *event_names[event_max_nomiss];
char *env_names[env_max_nomiss];
unsigned char function[state_max_nomiss][event_max_nomiss];
unsigned char initial_state;
bool final_states[state_max_nomiss];
};
static const struct automaton_nomiss automaton_nomiss = {
.state_names = {
"ready",
"idle",
"running",
"sleeping",
"throttled",
},
.event_names = {
"dl_replenish",
"dl_server_idle",
"dl_server_stop",
"dl_throttle",
"sched_switch_in",
"sched_switch_suspend",
"sched_wakeup",
},
.env_names = {
"clk",
"is_constr_dl",
"is_defer",
},
.function = {
{
ready_nomiss,
idle_nomiss,
sleeping_nomiss,
throttled_nomiss,
running_nomiss,
INVALID_STATE,
ready_nomiss,
},
{
ready_nomiss,
idle_nomiss,
sleeping_nomiss,
throttled_nomiss,
running_nomiss,
INVALID_STATE,
INVALID_STATE,
},
{
running_nomiss,
idle_nomiss,
sleeping_nomiss,
throttled_nomiss,
running_nomiss,
sleeping_nomiss,
running_nomiss,
},
{
ready_nomiss,
sleeping_nomiss,
sleeping_nomiss,
throttled_nomiss,
running_nomiss,
INVALID_STATE,
ready_nomiss,
},
{
ready_nomiss,
throttled_nomiss,
INVALID_STATE,
throttled_nomiss,
INVALID_STATE,
throttled_nomiss,
throttled_nomiss,
},
},
.initial_state = ready_nomiss,
.final_states = { 1, 0, 0, 0, 0 },
};
@@ -0,0 +1,19 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Snippet to be included in rv_trace.h
*/
#ifdef CONFIG_RV_MON_NOMISS
DEFINE_EVENT(event_da_monitor_id, event_nomiss,
TP_PROTO(int id, char *state, char *event, char *next_state, bool final_state),
TP_ARGS(id, state, event, next_state, final_state));
DEFINE_EVENT(error_da_monitor_id, error_nomiss,
TP_PROTO(int id, char *state, char *event),
TP_ARGS(id, state, event));
DEFINE_EVENT(error_env_da_monitor_id, error_env_nomiss,
TP_PROTO(int id, char *state, char *event, char *env),
TP_ARGS(id, state, event, env));
#endif /* CONFIG_RV_MON_NOMISS */
@@ -0,0 +1,16 @@
# SPDX-License-Identifier: GPL-2.0-only
#
config RV_MON_NRP
depends on RV
depends on RV_MON_SCHED
default y if !ARM64
select DA_MON_EVENTS_ID
bool "nrp monitor"
help
Monitor to ensure preemption requires need resched.
This monitor is part of the sched monitors collection.
This monitor is unstable on arm64, say N unless you are testing it.
For further information, see:
Documentation/trace/rv/monitor_sched.rst
@@ -0,0 +1,136 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/ftrace.h>
#include <linux/tracepoint.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/rv.h>
#include <rv/instrumentation.h>
#define MODULE_NAME "nrp"
#include <trace/events/irq.h>
#include <trace/events/sched.h>
#include <rv_trace.h>
#include <monitors/sched/sched.h>
#define RV_MON_TYPE RV_MON_PER_TASK
#include "nrp.h"
#include <rv/da_monitor.h>
#ifdef CONFIG_X86_LOCAL_APIC
#include <asm/trace/irq_vectors.h>
static void handle_vector_irq_entry(void *data, int vector)
{
da_handle_event(current, irq_entry_nrp);
}
static void attach_vector_irq(void)
{
rv_attach_trace_probe("nrp", local_timer_entry, handle_vector_irq_entry);
if (IS_ENABLED(CONFIG_IRQ_WORK))
rv_attach_trace_probe("nrp", irq_work_entry, handle_vector_irq_entry);
if (IS_ENABLED(CONFIG_SMP)) {
rv_attach_trace_probe("nrp", reschedule_entry, handle_vector_irq_entry);
rv_attach_trace_probe("nrp", call_function_entry, handle_vector_irq_entry);
rv_attach_trace_probe("nrp", call_function_single_entry, handle_vector_irq_entry);
}
}
static void detach_vector_irq(void)
{
rv_detach_trace_probe("nrp", local_timer_entry, handle_vector_irq_entry);
if (IS_ENABLED(CONFIG_IRQ_WORK))
rv_detach_trace_probe("nrp", irq_work_entry, handle_vector_irq_entry);
if (IS_ENABLED(CONFIG_SMP)) {
rv_detach_trace_probe("nrp", reschedule_entry, handle_vector_irq_entry);
rv_detach_trace_probe("nrp", call_function_entry, handle_vector_irq_entry);
rv_detach_trace_probe("nrp", call_function_single_entry, handle_vector_irq_entry);
}
}
#else
/* We assume irq_entry tracepoints are sufficient on other architectures */
static void attach_vector_irq(void) { }
static void detach_vector_irq(void) { }
#endif
static void handle_irq_entry(void *data, int irq, struct irqaction *action)
{
da_handle_event(current, irq_entry_nrp);
}
static void handle_sched_need_resched(void *data, struct task_struct *tsk,
int cpu, int tif)
{
/*
* Although need_resched leads to both the rescheduling and preempt_irq
* states, it is safer to start the monitor always in preempt_irq,
* which may not mirror the system state but makes the monitor simpler,
*/
if (tif == TIF_NEED_RESCHED)
da_handle_start_event(tsk, sched_need_resched_nrp);
}
static void handle_schedule_entry(void *data, bool preempt)
{
if (preempt)
da_handle_event(current, schedule_entry_preempt_nrp);
else
da_handle_event(current, schedule_entry_nrp);
}
static int enable_nrp(void)
{
int retval;
retval = da_monitor_init();
if (retval)
return retval;
rv_attach_trace_probe("nrp", irq_handler_entry, handle_irq_entry);
rv_attach_trace_probe("nrp", sched_set_need_resched_tp, handle_sched_need_resched);
rv_attach_trace_probe("nrp", sched_entry_tp, handle_schedule_entry);
attach_vector_irq();
return 0;
}
static void disable_nrp(void)
{
rv_this.enabled = 0;
rv_detach_trace_probe("nrp", irq_handler_entry, handle_irq_entry);
rv_detach_trace_probe("nrp", sched_set_need_resched_tp, handle_sched_need_resched);
rv_detach_trace_probe("nrp", sched_entry_tp, handle_schedule_entry);
detach_vector_irq();
da_monitor_destroy();
}
static struct rv_monitor rv_this = {
.name = "nrp",
.description = "need resched preempts.",
.enable = enable_nrp,
.disable = disable_nrp,
.reset = da_monitor_reset_all,
.enabled = 0,
};
static int __init register_nrp(void)
{
return rv_register_monitor(&rv_this, &rv_sched);
}
static void __exit unregister_nrp(void)
{
rv_unregister_monitor(&rv_this);
}
module_init(register_nrp);
module_exit(unregister_nrp);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Gabriele Monaco <gmonaco@redhat.com>");
MODULE_DESCRIPTION("nrp: need resched preempts.");
@@ -0,0 +1,77 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Automatically generated C representation of nrp automaton
* For further information about this format, see kernel documentation:
* Documentation/trace/rv/deterministic_automata.rst
*/
#define MONITOR_NAME nrp
enum states_nrp {
preempt_irq_nrp,
any_thread_running_nrp,
nested_preempt_nrp,
rescheduling_nrp,
state_max_nrp,
};
#define INVALID_STATE state_max_nrp
enum events_nrp {
irq_entry_nrp,
sched_need_resched_nrp,
schedule_entry_nrp,
schedule_entry_preempt_nrp,
event_max_nrp,
};
struct automaton_nrp {
char *state_names[state_max_nrp];
char *event_names[event_max_nrp];
unsigned char function[state_max_nrp][event_max_nrp];
unsigned char initial_state;
bool final_states[state_max_nrp];
};
static const struct automaton_nrp automaton_nrp = {
.state_names = {
"preempt_irq",
"any_thread_running",
"nested_preempt",
"rescheduling",
},
.event_names = {
"irq_entry",
"sched_need_resched",
"schedule_entry",
"schedule_entry_preempt",
},
.function = {
{
preempt_irq_nrp,
preempt_irq_nrp,
nested_preempt_nrp,
nested_preempt_nrp,
},
{
any_thread_running_nrp,
rescheduling_nrp,
any_thread_running_nrp,
INVALID_STATE,
},
{
nested_preempt_nrp,
preempt_irq_nrp,
any_thread_running_nrp,
any_thread_running_nrp,
},
{
preempt_irq_nrp,
rescheduling_nrp,
any_thread_running_nrp,
any_thread_running_nrp,
},
},
.initial_state = preempt_irq_nrp,
.final_states = { 0, 1, 0, 0 },
};
@@ -0,0 +1,15 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Snippet to be included in rv_trace.h
*/
#ifdef CONFIG_RV_MON_NRP
DEFINE_EVENT(event_da_monitor_id, event_nrp,
TP_PROTO(int id, char *state, char *event, char *next_state, bool final_state),
TP_ARGS(id, state, event, next_state, final_state));
DEFINE_EVENT(error_da_monitor_id, error_nrp,
TP_PROTO(int id, char *state, char *event),
TP_ARGS(id, state, event));
#endif /* CONFIG_RV_MON_NRP */
@@ -0,0 +1,14 @@
# SPDX-License-Identifier: GPL-2.0-only
#
config RV_MON_OPID
depends on RV
depends on RV_MON_SCHED
default y
select HA_MON_EVENTS_IMPLICIT
bool "opid monitor"
help
Monitor to ensure operations like wakeup and need resched occur with
interrupts and preemption disabled.
For further information, see:
Documentation/trace/rv/monitor_sched.rst
@@ -0,0 +1,117 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/ftrace.h>
#include <linux/tracepoint.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/rv.h>
#include <rv/instrumentation.h>
#define MODULE_NAME "opid"
#include <trace/events/sched.h>
#include <rv_trace.h>
#include <monitors/sched/sched.h>
#define RV_MON_TYPE RV_MON_PER_CPU
#include "opid.h"
#include <rv/ha_monitor.h>
static u64 ha_get_env(struct ha_monitor *ha_mon, enum envs_opid env, u64 time_ns)
{
if (env == irq_off_opid)
return irqs_disabled();
else if (env == preempt_off_opid) {
if (IS_ENABLED(CONFIG_PREEMPTION))
return (preempt_count() & PREEMPT_MASK) > 0;
return true;
}
return ENV_INVALID_VALUE;
}
static inline bool ha_verify_guards(struct ha_monitor *ha_mon,
enum states curr_state, enum events event,
enum states next_state, u64 time_ns)
{
bool res = true;
if (curr_state == any_opid && event == sched_need_resched_opid)
res = ha_get_env(ha_mon, irq_off_opid, time_ns) == 1ull;
else if (curr_state == any_opid && event == sched_waking_opid)
res = ha_get_env(ha_mon, irq_off_opid, time_ns) == 1ull &&
ha_get_env(ha_mon, preempt_off_opid, time_ns) == 1ull;
return res;
}
static bool ha_verify_constraint(struct ha_monitor *ha_mon,
enum states curr_state, enum events event,
enum states next_state, u64 time_ns)
{
if (!ha_verify_guards(ha_mon, curr_state, event, next_state, time_ns))
return false;
return true;
}
static void handle_sched_need_resched(void *data, struct task_struct *tsk, int cpu, int tif)
{
da_handle_start_run_event(sched_need_resched_opid);
}
static void handle_sched_waking(void *data, struct task_struct *p)
{
da_handle_start_run_event(sched_waking_opid);
}
static int enable_opid(void)
{
int retval;
retval = ha_monitor_init();
if (retval)
return retval;
rv_attach_trace_probe("opid", sched_set_need_resched_tp, handle_sched_need_resched);
rv_attach_trace_probe("opid", sched_waking, handle_sched_waking);
return 0;
}
static void disable_opid(void)
{
rv_this.enabled = 0;
rv_detach_trace_probe("opid", sched_set_need_resched_tp, handle_sched_need_resched);
rv_detach_trace_probe("opid", sched_waking, handle_sched_waking);
ha_monitor_destroy();
}
/*
* This is the monitor register section.
*/
static struct rv_monitor rv_this = {
.name = "opid",
.description = "operations with preemption and irq disabled.",
.enable = enable_opid,
.disable = disable_opid,
.reset = da_monitor_reset_all,
.enabled = 0,
};
static int __init register_opid(void)
{
return rv_register_monitor(&rv_this, &rv_sched);
}
static void __exit unregister_opid(void)
{
rv_unregister_monitor(&rv_this);
}
module_init(register_opid);
module_exit(unregister_opid);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Gabriele Monaco <gmonaco@redhat.com>");
MODULE_DESCRIPTION("opid: operations with preemption and irq disabled.");
@@ -0,0 +1,58 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Automatically generated C representation of opid automaton
* For further information about this format, see kernel documentation:
* Documentation/trace/rv/deterministic_automata.rst
*/
#define MONITOR_NAME opid
enum states_opid {
any_opid,
state_max_opid,
};
#define INVALID_STATE state_max_opid
enum events_opid {
sched_need_resched_opid,
sched_waking_opid,
event_max_opid,
};
enum envs_opid {
irq_off_opid,
preempt_off_opid,
env_max_opid,
env_max_stored_opid = irq_off_opid,
};
_Static_assert(env_max_stored_opid <= MAX_HA_ENV_LEN, "Not enough slots");
struct automaton_opid {
char *state_names[state_max_opid];
char *event_names[event_max_opid];
char *env_names[env_max_opid];
unsigned char function[state_max_opid][event_max_opid];
unsigned char initial_state;
bool final_states[state_max_opid];
};
static const struct automaton_opid automaton_opid = {
.state_names = {
"any",
},
.event_names = {
"sched_need_resched",
"sched_waking",
},
.env_names = {
"irq_off",
"preempt_off",
},
.function = {
{ any_opid, any_opid },
},
.initial_state = any_opid,
.final_states = { 1 },
};
@@ -0,0 +1,19 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Snippet to be included in rv_trace.h
*/
#ifdef CONFIG_RV_MON_OPID
DEFINE_EVENT(event_da_monitor, event_opid,
TP_PROTO(char *state, char *event, char *next_state, bool final_state),
TP_ARGS(state, event, next_state, final_state));
DEFINE_EVENT(error_da_monitor, error_opid,
TP_PROTO(char *state, char *event),
TP_ARGS(state, event));
DEFINE_EVENT(error_env_da_monitor, error_env_opid,
TP_PROTO(char *state, char *event, char *env),
TP_ARGS(state, event, env));
#endif /* CONFIG_RV_MON_OPID */
@@ -0,0 +1,21 @@
# SPDX-License-Identifier: GPL-2.0-only
#
config RV_MON_PAGEFAULT
depends on RV
select RV_LTL_MONITOR
depends on RV_MON_RTAPP
depends on X86 || RISCV
depends on MMU
default y
select LTL_MON_EVENTS_ID
bool "pagefault monitor"
help
Monitor that real-time tasks do not raise page faults, causing
undesirable latency.
If you are developing a real-time system and not entirely sure whether
the applications are designed correctly for real-time, you want to say
Y here.
This monitor does not affect execution speed while it is not running,
therefore it is safe to enable this in production kernel.
@@ -0,0 +1,88 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/ftrace.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/rv.h>
#include <linux/sched/deadline.h>
#include <linux/sched/rt.h>
#include <linux/tracepoint.h>
#include <rv/instrumentation.h>
#define MODULE_NAME "pagefault"
#include <rv_trace.h>
#include <trace/events/exceptions.h>
#include <monitors/rtapp/rtapp.h>
#include "pagefault.h"
#include <rv/ltl_monitor.h>
static void ltl_atoms_fetch(struct task_struct *task, struct ltl_monitor *mon)
{
/*
* This includes "actual" real-time tasks and also PI-boosted
* tasks. A task being PI-boosted means it is blocking an "actual"
* real-task, therefore it should also obey the monitor's rule,
* otherwise the "actual" real-task may be delayed.
*/
ltl_atom_set(mon, LTL_RT, rt_or_dl_task(task));
}
static void ltl_atoms_init(struct task_struct *task, struct ltl_monitor *mon, bool task_creation)
{
if (task_creation)
ltl_atom_set(mon, LTL_PAGEFAULT, false);
}
static void handle_page_fault(void *data, unsigned long address, struct pt_regs *regs,
unsigned long error_code)
{
ltl_atom_pulse(current, LTL_PAGEFAULT, true);
}
static int enable_pagefault(void)
{
int retval;
retval = ltl_monitor_init();
if (retval)
return retval;
rv_attach_trace_probe("rtapp_pagefault", page_fault_kernel, handle_page_fault);
rv_attach_trace_probe("rtapp_pagefault", page_fault_user, handle_page_fault);
return 0;
}
static void disable_pagefault(void)
{
rv_detach_trace_probe("rtapp_pagefault", page_fault_kernel, handle_page_fault);
rv_detach_trace_probe("rtapp_pagefault", page_fault_user, handle_page_fault);
ltl_monitor_destroy();
}
static struct rv_monitor rv_pagefault = {
.name = "pagefault",
.description = "Monitor that RT tasks do not raise page faults",
.enable = enable_pagefault,
.disable = disable_pagefault,
};
static int __init register_pagefault(void)
{
return rv_register_monitor(&rv_pagefault, &rv_rtapp);
}
static void __exit unregister_pagefault(void)
{
rv_unregister_monitor(&rv_pagefault);
}
module_init(register_pagefault);
module_exit(unregister_pagefault);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Nam Cao <namcao@linutronix.de>");
MODULE_DESCRIPTION("pagefault: Monitor that RT tasks do not raise page faults");
@@ -0,0 +1,64 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* C implementation of Buchi automaton, automatically generated by
* tools/verification/rvgen from the linear temporal logic specification.
* For further information, see kernel documentation:
* Documentation/trace/rv/linear_temporal_logic.rst
*/
#include <linux/rv.h>
#define MONITOR_NAME pagefault
enum ltl_atom {
LTL_PAGEFAULT,
LTL_RT,
LTL_NUM_ATOM
};
static_assert(LTL_NUM_ATOM <= RV_MAX_LTL_ATOM);
static const char *ltl_atom_str(enum ltl_atom atom)
{
static const char *const names[] = {
"pa",
"rt",
};
return names[atom];
}
enum ltl_buchi_state {
S0,
RV_NUM_BA_STATES
};
static_assert(RV_NUM_BA_STATES <= RV_MAX_BA_STATES);
static void ltl_start(struct task_struct *task, struct ltl_monitor *mon)
{
bool pagefault = test_bit(LTL_PAGEFAULT, mon->atoms);
bool val3 = !pagefault;
bool rt = test_bit(LTL_RT, mon->atoms);
bool val1 = !rt;
bool val4 = val1 || val3;
if (val4)
__set_bit(S0, mon->states);
}
static void
ltl_possible_next_states(struct ltl_monitor *mon, unsigned int state, unsigned long *next)
{
bool pagefault = test_bit(LTL_PAGEFAULT, mon->atoms);
bool val3 = !pagefault;
bool rt = test_bit(LTL_RT, mon->atoms);
bool val1 = !rt;
bool val4 = val1 || val3;
switch (state) {
case S0:
if (val4)
__set_bit(S0, next);
break;
}
}
@@ -0,0 +1,14 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Snippet to be included in rv_trace.h
*/
#ifdef CONFIG_RV_MON_PAGEFAULT
DEFINE_EVENT(event_ltl_monitor_id, event_pagefault,
TP_PROTO(struct task_struct *task, char *states, char *atoms, char *next),
TP_ARGS(task, states, atoms, next));
DEFINE_EVENT(error_ltl_monitor_id, error_pagefault,
TP_PROTO(struct task_struct *task),
TP_ARGS(task));
#endif /* CONFIG_RV_MON_PAGEFAULT */
@@ -0,0 +1,11 @@
config RV_MON_RTAPP
depends on RV
depends on RV_PER_TASK_MONITORS >= 2
bool "rtapp monitor"
help
Collection of monitors to check for common problems with real-time
application that may cause unexpected latency.
If you are developing a real-time system and not entirely sure whether
the applications are designed correctly for real-time, you want to say
Y here.
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/rv.h>
#define MODULE_NAME "rtapp"
#include "rtapp.h"
struct rv_monitor rv_rtapp = {
.name = "rtapp",
.description = "Collection of monitors for detecting problems with real-time applications",
};
static int __init register_rtapp(void)
{
return rv_register_monitor(&rv_rtapp, NULL);
}
static void __exit unregister_rtapp(void)
{
rv_unregister_monitor(&rv_rtapp);
}
module_init(register_rtapp);
module_exit(unregister_rtapp);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Nam Cao <namcao@linutronix.de>");
MODULE_DESCRIPTION("Collection of monitors for detecting problems with real-time applications");
@@ -0,0 +1,3 @@
/* SPDX-License-Identifier: GPL-2.0 */
extern struct rv_monitor rv_rtapp;
@@ -0,0 +1,12 @@
# SPDX-License-Identifier: GPL-2.0-only
#
config RV_MON_SCHED
depends on RV
depends on RV_PER_TASK_MONITORS >= 3
bool "sched monitor"
help
Collection of monitors to check the scheduler behaves according to specifications.
Enable this to enable all scheduler specification supported by the current kernel.
For further information, see:
Documentation/trace/rv/monitor_sched.rst
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/rv.h>
#define MODULE_NAME "sched"
#include "sched.h"
struct rv_monitor rv_sched = {
.name = "sched",
.description = "container for several scheduler monitor specifications.",
.enable = NULL,
.disable = NULL,
.reset = NULL,
.enabled = 0,
};
static int __init register_sched(void)
{
return rv_register_monitor(&rv_sched, NULL);
}
static void __exit unregister_sched(void)
{
rv_unregister_monitor(&rv_sched);
}
module_init(register_sched);
module_exit(unregister_sched);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Gabriele Monaco <gmonaco@redhat.com>");
MODULE_DESCRIPTION("sched: container for several scheduler monitor specifications.");
@@ -0,0 +1,3 @@
/* SPDX-License-Identifier: GPL-2.0 */
extern struct rv_monitor rv_sched;
@@ -0,0 +1,14 @@
# SPDX-License-Identifier: GPL-2.0-only
#
config RV_MON_SCO
depends on RV
depends on RV_MON_SCHED
default y
select DA_MON_EVENTS_IMPLICIT
bool "sco monitor"
help
Monitor to ensure sched_set_state happens only in thread context.
This monitor is part of the sched monitors collection.
For further information, see:
Documentation/trace/rv/monitor_sched.rst
@@ -0,0 +1,85 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/ftrace.h>
#include <linux/tracepoint.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/rv.h>
#include <rv/instrumentation.h>
#define MODULE_NAME "sco"
#include <trace/events/sched.h>
#include <rv_trace.h>
#include <monitors/sched/sched.h>
#define RV_MON_TYPE RV_MON_PER_CPU
#include "sco.h"
#include <rv/da_monitor.h>
static void handle_sched_set_state(void *data, struct task_struct *tsk, int state)
{
da_handle_start_event(sched_set_state_sco);
}
static void handle_schedule_entry(void *data, bool preempt)
{
da_handle_event(schedule_entry_sco);
}
static void handle_schedule_exit(void *data, bool is_switch)
{
da_handle_start_event(schedule_exit_sco);
}
static int enable_sco(void)
{
int retval;
retval = da_monitor_init();
if (retval)
return retval;
rv_attach_trace_probe("sco", sched_set_state_tp, handle_sched_set_state);
rv_attach_trace_probe("sco", sched_entry_tp, handle_schedule_entry);
rv_attach_trace_probe("sco", sched_exit_tp, handle_schedule_exit);
return 0;
}
static void disable_sco(void)
{
rv_this.enabled = 0;
rv_detach_trace_probe("sco", sched_set_state_tp, handle_sched_set_state);
rv_detach_trace_probe("sco", sched_entry_tp, handle_schedule_entry);
rv_detach_trace_probe("sco", sched_exit_tp, handle_schedule_exit);
da_monitor_destroy();
}
static struct rv_monitor rv_this = {
.name = "sco",
.description = "scheduling context operations.",
.enable = enable_sco,
.disable = disable_sco,
.reset = da_monitor_reset_all,
.enabled = 0,
};
static int __init register_sco(void)
{
return rv_register_monitor(&rv_this, &rv_sched);
}
static void __exit unregister_sco(void)
{
rv_unregister_monitor(&rv_this);
}
module_init(register_sco);
module_exit(unregister_sco);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Gabriele Monaco <gmonaco@redhat.com>");
MODULE_DESCRIPTION("sco: scheduling context operations.");
@@ -0,0 +1,49 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Automatically generated C representation of sco automaton
* For further information about this format, see kernel documentation:
* Documentation/trace/rv/deterministic_automata.rst
*/
#define MONITOR_NAME sco
enum states_sco {
thread_context_sco,
scheduling_context_sco,
state_max_sco,
};
#define INVALID_STATE state_max_sco
enum events_sco {
sched_set_state_sco,
schedule_entry_sco,
schedule_exit_sco,
event_max_sco,
};
struct automaton_sco {
char *state_names[state_max_sco];
char *event_names[event_max_sco];
unsigned char function[state_max_sco][event_max_sco];
unsigned char initial_state;
bool final_states[state_max_sco];
};
static const struct automaton_sco automaton_sco = {
.state_names = {
"thread_context",
"scheduling_context",
},
.event_names = {
"sched_set_state",
"schedule_entry",
"schedule_exit",
},
.function = {
{ thread_context_sco, scheduling_context_sco, INVALID_STATE },
{ INVALID_STATE, INVALID_STATE, thread_context_sco },
},
.initial_state = thread_context_sco,
.final_states = { 1, 0 },
};
@@ -0,0 +1,15 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Snippet to be included in rv_trace.h
*/
#ifdef CONFIG_RV_MON_SCO
DEFINE_EVENT(event_da_monitor, event_sco,
TP_PROTO(char *state, char *event, char *next_state, bool final_state),
TP_ARGS(state, event, next_state, final_state));
DEFINE_EVENT(error_da_monitor, error_sco,
TP_PROTO(char *state, char *event),
TP_ARGS(state, event));
#endif /* CONFIG_RV_MON_SCO */
@@ -0,0 +1,15 @@
# SPDX-License-Identifier: GPL-2.0-only
#
config RV_MON_SCPD
depends on RV
depends on TRACE_PREEMPT_TOGGLE
depends on RV_MON_SCHED
default y
select DA_MON_EVENTS_IMPLICIT
bool "scpd monitor"
help
Monitor to ensure schedule is called with preemption disabled.
This monitor is part of the sched monitors collection.
For further information, see:
Documentation/trace/rv/monitor_sched.rst
@@ -0,0 +1,93 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/ftrace.h>
#include <linux/tracepoint.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/rv.h>
#include <rv/instrumentation.h>
#define MODULE_NAME "scpd"
#include <trace/events/sched.h>
#include <trace/events/preemptirq.h>
#include <rv_trace.h>
#include <monitors/sched/sched.h>
#define RV_MON_TYPE RV_MON_PER_CPU
#include "scpd.h"
#include <rv/da_monitor.h>
static void handle_preempt_disable(void *data, unsigned long ip, unsigned long parent_ip)
{
da_handle_event(preempt_disable_scpd);
}
static void handle_preempt_enable(void *data, unsigned long ip, unsigned long parent_ip)
{
da_handle_start_event(preempt_enable_scpd);
}
static void handle_schedule_entry(void *data, bool preempt)
{
da_handle_event(schedule_entry_scpd);
}
static void handle_schedule_exit(void *data, bool is_switch)
{
da_handle_event(schedule_exit_scpd);
}
static int enable_scpd(void)
{
int retval;
retval = da_monitor_init();
if (retval)
return retval;
rv_attach_trace_probe("scpd", preempt_disable, handle_preempt_disable);
rv_attach_trace_probe("scpd", preempt_enable, handle_preempt_enable);
rv_attach_trace_probe("scpd", sched_entry_tp, handle_schedule_entry);
rv_attach_trace_probe("scpd", sched_exit_tp, handle_schedule_exit);
return 0;
}
static void disable_scpd(void)
{
rv_this.enabled = 0;
rv_detach_trace_probe("scpd", preempt_disable, handle_preempt_disable);
rv_detach_trace_probe("scpd", preempt_enable, handle_preempt_enable);
rv_detach_trace_probe("scpd", sched_entry_tp, handle_schedule_entry);
rv_detach_trace_probe("scpd", sched_exit_tp, handle_schedule_exit);
da_monitor_destroy();
}
static struct rv_monitor rv_this = {
.name = "scpd",
.description = "schedule called with preemption disabled.",
.enable = enable_scpd,
.disable = disable_scpd,
.reset = da_monitor_reset_all,
.enabled = 0,
};
static int __init register_scpd(void)
{
return rv_register_monitor(&rv_this, &rv_sched);
}
static void __exit unregister_scpd(void)
{
rv_unregister_monitor(&rv_this);
}
module_init(register_scpd);
module_exit(unregister_scpd);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Gabriele Monaco <gmonaco@redhat.com>");
MODULE_DESCRIPTION("scpd: schedule called with preemption disabled.");
@@ -0,0 +1,51 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Automatically generated C representation of scpd automaton
* For further information about this format, see kernel documentation:
* Documentation/trace/rv/deterministic_automata.rst
*/
#define MONITOR_NAME scpd
enum states_scpd {
cant_sched_scpd,
can_sched_scpd,
state_max_scpd,
};
#define INVALID_STATE state_max_scpd
enum events_scpd {
preempt_disable_scpd,
preempt_enable_scpd,
schedule_entry_scpd,
schedule_exit_scpd,
event_max_scpd,
};
struct automaton_scpd {
char *state_names[state_max_scpd];
char *event_names[event_max_scpd];
unsigned char function[state_max_scpd][event_max_scpd];
unsigned char initial_state;
bool final_states[state_max_scpd];
};
static const struct automaton_scpd automaton_scpd = {
.state_names = {
"cant_sched",
"can_sched",
},
.event_names = {
"preempt_disable",
"preempt_enable",
"schedule_entry",
"schedule_exit",
},
.function = {
{ can_sched_scpd, INVALID_STATE, INVALID_STATE, INVALID_STATE },
{ INVALID_STATE, cant_sched_scpd, can_sched_scpd, can_sched_scpd },
},
.initial_state = cant_sched_scpd,
.final_states = { 1, 0 },
};
@@ -0,0 +1,15 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Snippet to be included in rv_trace.h
*/
#ifdef CONFIG_RV_MON_SCPD
DEFINE_EVENT(event_da_monitor, event_scpd,
TP_PROTO(char *state, char *event, char *next_state, bool final_state),
TP_ARGS(state, event, next_state, final_state));
DEFINE_EVENT(error_da_monitor, error_scpd,
TP_PROTO(char *state, char *event),
TP_ARGS(state, event));
#endif /* CONFIG_RV_MON_SCPD */
@@ -0,0 +1,22 @@
# SPDX-License-Identifier: GPL-2.0-only
#
config RV_MON_SLEEP
depends on RV
select RV_LTL_MONITOR
depends on HAVE_SYSCALL_TRACEPOINTS
depends on RV_MON_RTAPP
select TRACE_IRQFLAGS
default y
select LTL_MON_EVENTS_ID
bool "sleep monitor"
help
Monitor that real-time tasks do not sleep in a manner that may
cause undesirable latency.
If you are developing a real-time system and not entirely sure whether
the applications are designed correctly for real-time, you want to say
Y here.
Enabling this monitor may have performance impact (due to select
TRACE_IRQFLAGS). Therefore, you probably should say N for
production kernel.
@@ -0,0 +1,249 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/ftrace.h>
#include <linux/tracepoint.h>
#include <linux/init.h>
#include <linux/irqflags.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/rv.h>
#include <linux/sched/deadline.h>
#include <linux/sched/rt.h>
#include <rv/instrumentation.h>
#define MODULE_NAME "sleep"
#include <trace/events/syscalls.h>
#include <trace/events/sched.h>
#include <trace/events/lock.h>
#include <uapi/linux/futex.h>
#include <rv_trace.h>
#include <monitors/rtapp/rtapp.h>
#include "sleep.h"
#include <rv/ltl_monitor.h>
static void ltl_atoms_fetch(struct task_struct *task, struct ltl_monitor *mon)
{
/*
* This includes "actual" real-time tasks and also PI-boosted
* tasks. A task being PI-boosted means it is blocking an "actual"
* real-task, therefore it should also obey the monitor's rule,
* otherwise the "actual" real-task may be delayed.
*/
ltl_atom_set(mon, LTL_RT, rt_or_dl_task(task));
}
static void ltl_atoms_init(struct task_struct *task, struct ltl_monitor *mon, bool task_creation)
{
ltl_atom_set(mon, LTL_SLEEP, false);
ltl_atom_set(mon, LTL_WAKE, false);
ltl_atom_set(mon, LTL_ABORT_SLEEP, false);
ltl_atom_set(mon, LTL_WOKEN_BY_HARDIRQ, false);
ltl_atom_set(mon, LTL_WOKEN_BY_NMI, false);
ltl_atom_set(mon, LTL_WOKEN_BY_EQUAL_OR_HIGHER_PRIO, false);
if (task_creation) {
ltl_atom_set(mon, LTL_KTHREAD_SHOULD_STOP, false);
ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_MONOTONIC, false);
ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_TAI, false);
ltl_atom_set(mon, LTL_NANOSLEEP_TIMER_ABSTIME, false);
ltl_atom_set(mon, LTL_CLOCK_NANOSLEEP, false);
ltl_atom_set(mon, LTL_FUTEX_WAIT, false);
ltl_atom_set(mon, LTL_EPOLL_WAIT, false);
ltl_atom_set(mon, LTL_FUTEX_LOCK_PI, false);
ltl_atom_set(mon, LTL_BLOCK_ON_RT_MUTEX, false);
}
if (task->flags & PF_KTHREAD) {
ltl_atom_set(mon, LTL_KERNEL_THREAD, true);
/* kernel tasks do not do syscall */
ltl_atom_set(mon, LTL_FUTEX_WAIT, false);
ltl_atom_set(mon, LTL_FUTEX_LOCK_PI, false);
ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_MONOTONIC, false);
ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_TAI, false);
ltl_atom_set(mon, LTL_NANOSLEEP_TIMER_ABSTIME, false);
ltl_atom_set(mon, LTL_CLOCK_NANOSLEEP, false);
ltl_atom_set(mon, LTL_EPOLL_WAIT, false);
if (strstarts(task->comm, "migration/"))
ltl_atom_set(mon, LTL_TASK_IS_MIGRATION, true);
else
ltl_atom_set(mon, LTL_TASK_IS_MIGRATION, false);
if (strstarts(task->comm, "rcu"))
ltl_atom_set(mon, LTL_TASK_IS_RCU, true);
else
ltl_atom_set(mon, LTL_TASK_IS_RCU, false);
} else {
ltl_atom_set(mon, LTL_KTHREAD_SHOULD_STOP, false);
ltl_atom_set(mon, LTL_KERNEL_THREAD, false);
ltl_atom_set(mon, LTL_TASK_IS_RCU, false);
ltl_atom_set(mon, LTL_TASK_IS_MIGRATION, false);
}
}
static void handle_sched_set_state(void *data, struct task_struct *task, int state)
{
if (state & TASK_INTERRUPTIBLE)
ltl_atom_pulse(task, LTL_SLEEP, true);
else if (state == TASK_RUNNING)
ltl_atom_pulse(task, LTL_ABORT_SLEEP, true);
}
static void handle_sched_wakeup(void *data, struct task_struct *task)
{
ltl_atom_pulse(task, LTL_WAKE, true);
}
static void handle_sched_waking(void *data, struct task_struct *task)
{
if (this_cpu_read(hardirq_context)) {
ltl_atom_pulse(task, LTL_WOKEN_BY_HARDIRQ, true);
} else if (in_task()) {
if (current->prio <= task->prio)
ltl_atom_pulse(task, LTL_WOKEN_BY_EQUAL_OR_HIGHER_PRIO, true);
} else if (in_nmi()) {
ltl_atom_pulse(task, LTL_WOKEN_BY_NMI, true);
}
}
static void handle_contention_begin(void *data, void *lock, unsigned int flags)
{
if (flags & LCB_F_RT)
ltl_atom_update(current, LTL_BLOCK_ON_RT_MUTEX, true);
}
static void handle_contention_end(void *data, void *lock, int ret)
{
ltl_atom_update(current, LTL_BLOCK_ON_RT_MUTEX, false);
}
static void handle_sys_enter(void *data, struct pt_regs *regs, long id)
{
struct ltl_monitor *mon;
unsigned long args[6];
int op, cmd;
mon = ltl_get_monitor(current);
switch (id) {
#ifdef __NR_clock_nanosleep
case __NR_clock_nanosleep:
#endif
#ifdef __NR_clock_nanosleep_time64
case __NR_clock_nanosleep_time64:
#endif
syscall_get_arguments(current, regs, args);
ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_MONOTONIC, args[0] == CLOCK_MONOTONIC);
ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_TAI, args[0] == CLOCK_TAI);
ltl_atom_set(mon, LTL_NANOSLEEP_TIMER_ABSTIME, args[1] == TIMER_ABSTIME);
ltl_atom_update(current, LTL_CLOCK_NANOSLEEP, true);
break;
#ifdef __NR_futex
case __NR_futex:
#endif
#ifdef __NR_futex_time64
case __NR_futex_time64:
#endif
syscall_get_arguments(current, regs, args);
op = args[1];
cmd = op & FUTEX_CMD_MASK;
switch (cmd) {
case FUTEX_LOCK_PI:
case FUTEX_LOCK_PI2:
ltl_atom_update(current, LTL_FUTEX_LOCK_PI, true);
break;
case FUTEX_WAIT:
case FUTEX_WAIT_BITSET:
case FUTEX_WAIT_REQUEUE_PI:
ltl_atom_update(current, LTL_FUTEX_WAIT, true);
break;
}
break;
#ifdef __NR_epoll_wait
case __NR_epoll_wait:
ltl_atom_update(current, LTL_EPOLL_WAIT, true);
break;
#endif
}
}
static void handle_sys_exit(void *data, struct pt_regs *regs, long ret)
{
struct ltl_monitor *mon = ltl_get_monitor(current);
ltl_atom_set(mon, LTL_FUTEX_LOCK_PI, false);
ltl_atom_set(mon, LTL_FUTEX_WAIT, false);
ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_MONOTONIC, false);
ltl_atom_set(mon, LTL_NANOSLEEP_CLOCK_TAI, false);
ltl_atom_set(mon, LTL_NANOSLEEP_TIMER_ABSTIME, false);
ltl_atom_set(mon, LTL_EPOLL_WAIT, false);
ltl_atom_update(current, LTL_CLOCK_NANOSLEEP, false);
}
static void handle_kthread_stop(void *data, struct task_struct *task)
{
/* FIXME: this could race with other tracepoint handlers */
ltl_atom_update(task, LTL_KTHREAD_SHOULD_STOP, true);
}
static int enable_sleep(void)
{
int retval;
retval = ltl_monitor_init();
if (retval)
return retval;
rv_attach_trace_probe("rtapp_sleep", sched_waking, handle_sched_waking);
rv_attach_trace_probe("rtapp_sleep", sched_wakeup, handle_sched_wakeup);
rv_attach_trace_probe("rtapp_sleep", sched_set_state_tp, handle_sched_set_state);
rv_attach_trace_probe("rtapp_sleep", contention_begin, handle_contention_begin);
rv_attach_trace_probe("rtapp_sleep", contention_end, handle_contention_end);
rv_attach_trace_probe("rtapp_sleep", sched_kthread_stop, handle_kthread_stop);
rv_attach_trace_probe("rtapp_sleep", sys_enter, handle_sys_enter);
rv_attach_trace_probe("rtapp_sleep", sys_exit, handle_sys_exit);
return 0;
}
static void disable_sleep(void)
{
rv_detach_trace_probe("rtapp_sleep", sched_waking, handle_sched_waking);
rv_detach_trace_probe("rtapp_sleep", sched_wakeup, handle_sched_wakeup);
rv_detach_trace_probe("rtapp_sleep", sched_set_state_tp, handle_sched_set_state);
rv_detach_trace_probe("rtapp_sleep", contention_begin, handle_contention_begin);
rv_detach_trace_probe("rtapp_sleep", contention_end, handle_contention_end);
rv_detach_trace_probe("rtapp_sleep", sched_kthread_stop, handle_kthread_stop);
rv_detach_trace_probe("rtapp_sleep", sys_enter, handle_sys_enter);
rv_detach_trace_probe("rtapp_sleep", sys_exit, handle_sys_exit);
ltl_monitor_destroy();
}
static struct rv_monitor rv_sleep = {
.name = "sleep",
.description = "Monitor that RT tasks do not undesirably sleep",
.enable = enable_sleep,
.disable = disable_sleep,
};
static int __init register_sleep(void)
{
return rv_register_monitor(&rv_sleep, &rv_rtapp);
}
static void __exit unregister_sleep(void)
{
rv_unregister_monitor(&rv_sleep);
}
module_init(register_sleep);
module_exit(unregister_sleep);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Nam Cao <namcao@linutronix.de>");
MODULE_DESCRIPTION("sleep: Monitor that RT tasks do not undesirably sleep");
@@ -0,0 +1,263 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* C implementation of Buchi automaton, automatically generated by
* tools/verification/rvgen from the linear temporal logic specification.
* For further information, see kernel documentation:
* Documentation/trace/rv/linear_temporal_logic.rst
*/
#include <linux/rv.h>
#define MONITOR_NAME sleep
enum ltl_atom {
LTL_ABORT_SLEEP,
LTL_BLOCK_ON_RT_MUTEX,
LTL_CLOCK_NANOSLEEP,
LTL_EPOLL_WAIT,
LTL_FUTEX_LOCK_PI,
LTL_FUTEX_WAIT,
LTL_KERNEL_THREAD,
LTL_KTHREAD_SHOULD_STOP,
LTL_NANOSLEEP_CLOCK_MONOTONIC,
LTL_NANOSLEEP_CLOCK_TAI,
LTL_NANOSLEEP_TIMER_ABSTIME,
LTL_RT,
LTL_SLEEP,
LTL_TASK_IS_MIGRATION,
LTL_TASK_IS_RCU,
LTL_WAKE,
LTL_WOKEN_BY_EQUAL_OR_HIGHER_PRIO,
LTL_WOKEN_BY_HARDIRQ,
LTL_WOKEN_BY_NMI,
LTL_NUM_ATOM
};
static_assert(LTL_NUM_ATOM <= RV_MAX_LTL_ATOM);
static const char *ltl_atom_str(enum ltl_atom atom)
{
static const char *const names[] = {
"ab_sl",
"bl_on_rt_mu",
"cl_na",
"ep_wa",
"fu_lo_pi",
"fu_wa",
"ker_th",
"kth_sh_st",
"na_cl_mo",
"na_cl_ta",
"na_ti_ab",
"rt",
"sl",
"ta_mi",
"ta_rc",
"wak",
"wo_eq_hi_pr",
"wo_ha",
"wo_nm",
};
return names[atom];
}
enum ltl_buchi_state {
S0,
S1,
S2,
S3,
S4,
S5,
S6,
S7,
RV_NUM_BA_STATES
};
static_assert(RV_NUM_BA_STATES <= RV_MAX_BA_STATES);
static void ltl_start(struct task_struct *task, struct ltl_monitor *mon)
{
bool woken_by_nmi = test_bit(LTL_WOKEN_BY_NMI, mon->atoms);
bool woken_by_hardirq = test_bit(LTL_WOKEN_BY_HARDIRQ, mon->atoms);
bool woken_by_equal_or_higher_prio = test_bit(LTL_WOKEN_BY_EQUAL_OR_HIGHER_PRIO,
mon->atoms);
bool wake = test_bit(LTL_WAKE, mon->atoms);
bool task_is_rcu = test_bit(LTL_TASK_IS_RCU, mon->atoms);
bool task_is_migration = test_bit(LTL_TASK_IS_MIGRATION, mon->atoms);
bool sleep = test_bit(LTL_SLEEP, mon->atoms);
bool rt = test_bit(LTL_RT, mon->atoms);
bool nanosleep_timer_abstime = test_bit(LTL_NANOSLEEP_TIMER_ABSTIME, mon->atoms);
bool nanosleep_clock_tai = test_bit(LTL_NANOSLEEP_CLOCK_TAI, mon->atoms);
bool nanosleep_clock_monotonic = test_bit(LTL_NANOSLEEP_CLOCK_MONOTONIC, mon->atoms);
bool kthread_should_stop = test_bit(LTL_KTHREAD_SHOULD_STOP, mon->atoms);
bool kernel_thread = test_bit(LTL_KERNEL_THREAD, mon->atoms);
bool futex_wait = test_bit(LTL_FUTEX_WAIT, mon->atoms);
bool futex_lock_pi = test_bit(LTL_FUTEX_LOCK_PI, mon->atoms);
bool epoll_wait = test_bit(LTL_EPOLL_WAIT, mon->atoms);
bool clock_nanosleep = test_bit(LTL_CLOCK_NANOSLEEP, mon->atoms);
bool block_on_rt_mutex = test_bit(LTL_BLOCK_ON_RT_MUTEX, mon->atoms);
bool abort_sleep = test_bit(LTL_ABORT_SLEEP, mon->atoms);
bool val42 = task_is_rcu || task_is_migration;
bool val43 = futex_lock_pi || val42;
bool val5 = block_on_rt_mutex || val43;
bool val34 = abort_sleep || kthread_should_stop;
bool val35 = woken_by_nmi || val34;
bool val36 = woken_by_hardirq || val35;
bool val14 = woken_by_equal_or_higher_prio || val36;
bool val13 = !wake;
bool val26 = nanosleep_clock_monotonic || nanosleep_clock_tai;
bool val27 = nanosleep_timer_abstime && val26;
bool val18 = clock_nanosleep && val27;
bool val20 = val18 || epoll_wait;
bool val9 = futex_wait || val20;
bool val11 = val9 || kernel_thread;
bool val2 = !sleep;
bool val1 = !rt;
bool val3 = val1 || val2;
if (val3)
__set_bit(S0, mon->states);
if (val11 && val13)
__set_bit(S1, mon->states);
if (val11 && val14)
__set_bit(S4, mon->states);
if (val5)
__set_bit(S5, mon->states);
}
static void
ltl_possible_next_states(struct ltl_monitor *mon, unsigned int state, unsigned long *next)
{
bool woken_by_nmi = test_bit(LTL_WOKEN_BY_NMI, mon->atoms);
bool woken_by_hardirq = test_bit(LTL_WOKEN_BY_HARDIRQ, mon->atoms);
bool woken_by_equal_or_higher_prio = test_bit(LTL_WOKEN_BY_EQUAL_OR_HIGHER_PRIO,
mon->atoms);
bool wake = test_bit(LTL_WAKE, mon->atoms);
bool task_is_rcu = test_bit(LTL_TASK_IS_RCU, mon->atoms);
bool task_is_migration = test_bit(LTL_TASK_IS_MIGRATION, mon->atoms);
bool sleep = test_bit(LTL_SLEEP, mon->atoms);
bool rt = test_bit(LTL_RT, mon->atoms);
bool nanosleep_timer_abstime = test_bit(LTL_NANOSLEEP_TIMER_ABSTIME, mon->atoms);
bool nanosleep_clock_tai = test_bit(LTL_NANOSLEEP_CLOCK_TAI, mon->atoms);
bool nanosleep_clock_monotonic = test_bit(LTL_NANOSLEEP_CLOCK_MONOTONIC, mon->atoms);
bool kthread_should_stop = test_bit(LTL_KTHREAD_SHOULD_STOP, mon->atoms);
bool kernel_thread = test_bit(LTL_KERNEL_THREAD, mon->atoms);
bool futex_wait = test_bit(LTL_FUTEX_WAIT, mon->atoms);
bool futex_lock_pi = test_bit(LTL_FUTEX_LOCK_PI, mon->atoms);
bool epoll_wait = test_bit(LTL_EPOLL_WAIT, mon->atoms);
bool clock_nanosleep = test_bit(LTL_CLOCK_NANOSLEEP, mon->atoms);
bool block_on_rt_mutex = test_bit(LTL_BLOCK_ON_RT_MUTEX, mon->atoms);
bool abort_sleep = test_bit(LTL_ABORT_SLEEP, mon->atoms);
bool val42 = task_is_rcu || task_is_migration;
bool val43 = futex_lock_pi || val42;
bool val5 = block_on_rt_mutex || val43;
bool val34 = abort_sleep || kthread_should_stop;
bool val35 = woken_by_nmi || val34;
bool val36 = woken_by_hardirq || val35;
bool val14 = woken_by_equal_or_higher_prio || val36;
bool val13 = !wake;
bool val26 = nanosleep_clock_monotonic || nanosleep_clock_tai;
bool val27 = nanosleep_timer_abstime && val26;
bool val18 = clock_nanosleep && val27;
bool val20 = val18 || epoll_wait;
bool val9 = futex_wait || val20;
bool val11 = val9 || kernel_thread;
bool val2 = !sleep;
bool val1 = !rt;
bool val3 = val1 || val2;
switch (state) {
case S0:
if (val3)
__set_bit(S0, next);
if (val11 && val13)
__set_bit(S1, next);
if (val11 && val14)
__set_bit(S4, next);
if (val5)
__set_bit(S5, next);
break;
case S1:
if (val11 && val13)
__set_bit(S1, next);
if (val13 && val3)
__set_bit(S2, next);
if (val14 && val3)
__set_bit(S3, next);
if (val11 && val14)
__set_bit(S4, next);
if (val13 && val5)
__set_bit(S6, next);
if (val14 && val5)
__set_bit(S7, next);
break;
case S2:
if (val11 && val13)
__set_bit(S1, next);
if (val13 && val3)
__set_bit(S2, next);
if (val14 && val3)
__set_bit(S3, next);
if (val11 && val14)
__set_bit(S4, next);
if (val13 && val5)
__set_bit(S6, next);
if (val14 && val5)
__set_bit(S7, next);
break;
case S3:
if (val3)
__set_bit(S0, next);
if (val11 && val13)
__set_bit(S1, next);
if (val11 && val14)
__set_bit(S4, next);
if (val5)
__set_bit(S5, next);
break;
case S4:
if (val3)
__set_bit(S0, next);
if (val11 && val13)
__set_bit(S1, next);
if (val11 && val14)
__set_bit(S4, next);
if (val5)
__set_bit(S5, next);
break;
case S5:
if (val3)
__set_bit(S0, next);
if (val11 && val13)
__set_bit(S1, next);
if (val11 && val14)
__set_bit(S4, next);
if (val5)
__set_bit(S5, next);
break;
case S6:
if (val11 && val13)
__set_bit(S1, next);
if (val13 && val3)
__set_bit(S2, next);
if (val14 && val3)
__set_bit(S3, next);
if (val11 && val14)
__set_bit(S4, next);
if (val13 && val5)
__set_bit(S6, next);
if (val14 && val5)
__set_bit(S7, next);
break;
case S7:
if (val3)
__set_bit(S0, next);
if (val11 && val13)
__set_bit(S1, next);
if (val11 && val14)
__set_bit(S4, next);
if (val5)
__set_bit(S5, next);
break;
}
}
@@ -0,0 +1,14 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Snippet to be included in rv_trace.h
*/
#ifdef CONFIG_RV_MON_SLEEP
DEFINE_EVENT(event_ltl_monitor_id, event_sleep,
TP_PROTO(struct task_struct *task, char *states, char *atoms, char *next),
TP_ARGS(task, states, atoms, next));
DEFINE_EVENT(error_ltl_monitor_id, error_sleep,
TP_PROTO(struct task_struct *task),
TP_ARGS(task));
#endif /* CONFIG_RV_MON_SLEEP */
@@ -0,0 +1,15 @@
# SPDX-License-Identifier: GPL-2.0-only
#
config RV_MON_SNEP
depends on RV
depends on TRACE_PREEMPT_TOGGLE
depends on RV_MON_SCHED
default y
select DA_MON_EVENTS_IMPLICIT
bool "snep monitor"
help
Monitor to ensure schedule does not enable preempt.
This monitor is part of the sched monitors collection.
For further information, see:
Documentation/trace/rv/monitor_sched.rst
@@ -0,0 +1,93 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/ftrace.h>
#include <linux/tracepoint.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/rv.h>
#include <rv/instrumentation.h>
#define MODULE_NAME "snep"
#include <trace/events/sched.h>
#include <trace/events/preemptirq.h>
#include <rv_trace.h>
#include <monitors/sched/sched.h>
#define RV_MON_TYPE RV_MON_PER_CPU
#include "snep.h"
#include <rv/da_monitor.h>
static void handle_preempt_disable(void *data, unsigned long ip, unsigned long parent_ip)
{
da_handle_start_event(preempt_disable_snep);
}
static void handle_preempt_enable(void *data, unsigned long ip, unsigned long parent_ip)
{
da_handle_start_event(preempt_enable_snep);
}
static void handle_schedule_entry(void *data, bool preempt)
{
da_handle_event(schedule_entry_snep);
}
static void handle_schedule_exit(void *data, bool is_switch)
{
da_handle_start_event(schedule_exit_snep);
}
static int enable_snep(void)
{
int retval;
retval = da_monitor_init();
if (retval)
return retval;
rv_attach_trace_probe("snep", preempt_disable, handle_preempt_disable);
rv_attach_trace_probe("snep", preempt_enable, handle_preempt_enable);
rv_attach_trace_probe("snep", sched_entry_tp, handle_schedule_entry);
rv_attach_trace_probe("snep", sched_exit_tp, handle_schedule_exit);
return 0;
}
static void disable_snep(void)
{
rv_this.enabled = 0;
rv_detach_trace_probe("snep", preempt_disable, handle_preempt_disable);
rv_detach_trace_probe("snep", preempt_enable, handle_preempt_enable);
rv_detach_trace_probe("snep", sched_entry_tp, handle_schedule_entry);
rv_detach_trace_probe("snep", sched_exit_tp, handle_schedule_exit);
da_monitor_destroy();
}
static struct rv_monitor rv_this = {
.name = "snep",
.description = "schedule does not enable preempt.",
.enable = enable_snep,
.disable = disable_snep,
.reset = da_monitor_reset_all,
.enabled = 0,
};
static int __init register_snep(void)
{
return rv_register_monitor(&rv_this, &rv_sched);
}
static void __exit unregister_snep(void)
{
rv_unregister_monitor(&rv_this);
}
module_init(register_snep);
module_exit(unregister_snep);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Gabriele Monaco <gmonaco@redhat.com>");
MODULE_DESCRIPTION("snep: schedule does not enable preempt.");
@@ -0,0 +1,61 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Automatically generated C representation of snep automaton
* For further information about this format, see kernel documentation:
* Documentation/trace/rv/deterministic_automata.rst
*/
#define MONITOR_NAME snep
enum states_snep {
non_scheduling_context_snep,
scheduling_contex_snep,
state_max_snep,
};
#define INVALID_STATE state_max_snep
enum events_snep {
preempt_disable_snep,
preempt_enable_snep,
schedule_entry_snep,
schedule_exit_snep,
event_max_snep,
};
struct automaton_snep {
char *state_names[state_max_snep];
char *event_names[event_max_snep];
unsigned char function[state_max_snep][event_max_snep];
unsigned char initial_state;
bool final_states[state_max_snep];
};
static const struct automaton_snep automaton_snep = {
.state_names = {
"non_scheduling_context",
"scheduling_contex",
},
.event_names = {
"preempt_disable",
"preempt_enable",
"schedule_entry",
"schedule_exit",
},
.function = {
{
non_scheduling_context_snep,
non_scheduling_context_snep,
scheduling_contex_snep,
INVALID_STATE,
},
{
INVALID_STATE,
INVALID_STATE,
INVALID_STATE,
non_scheduling_context_snep,
},
},
.initial_state = non_scheduling_context_snep,
.final_states = { 1, 0 },
};
@@ -0,0 +1,15 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Snippet to be included in rv_trace.h
*/
#ifdef CONFIG_RV_MON_SNEP
DEFINE_EVENT(event_da_monitor, event_snep,
TP_PROTO(char *state, char *event, char *next_state, bool final_state),
TP_ARGS(state, event, next_state, final_state));
DEFINE_EVENT(error_da_monitor, error_snep,
TP_PROTO(char *state, char *event),
TP_ARGS(state, event));
#endif /* CONFIG_RV_MON_SNEP */
@@ -0,0 +1,14 @@
# SPDX-License-Identifier: GPL-2.0-only
#
config RV_MON_SNROC
depends on RV
depends on RV_MON_SCHED
default y
select DA_MON_EVENTS_ID
bool "snroc monitor"
help
Monitor to ensure sched_set_state happens only in the respective task's context.
This monitor is part of the sched monitors collection.
For further information, see:
Documentation/trace/rv/monitor_sched.rst
@@ -0,0 +1,82 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/ftrace.h>
#include <linux/tracepoint.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/rv.h>
#include <rv/instrumentation.h>
#define MODULE_NAME "snroc"
#include <trace/events/sched.h>
#include <rv_trace.h>
#include <monitors/sched/sched.h>
#define RV_MON_TYPE RV_MON_PER_TASK
#include "snroc.h"
#include <rv/da_monitor.h>
static void handle_sched_set_state(void *data, struct task_struct *tsk, int state)
{
da_handle_event(tsk, sched_set_state_snroc);
}
static void handle_sched_switch(void *data, bool preempt,
struct task_struct *prev,
struct task_struct *next,
unsigned int prev_state)
{
da_handle_start_event(prev, sched_switch_out_snroc);
da_handle_event(next, sched_switch_in_snroc);
}
static int enable_snroc(void)
{
int retval;
retval = da_monitor_init();
if (retval)
return retval;
rv_attach_trace_probe("snroc", sched_set_state_tp, handle_sched_set_state);
rv_attach_trace_probe("snroc", sched_switch, handle_sched_switch);
return 0;
}
static void disable_snroc(void)
{
rv_this.enabled = 0;
rv_detach_trace_probe("snroc", sched_set_state_tp, handle_sched_set_state);
rv_detach_trace_probe("snroc", sched_switch, handle_sched_switch);
da_monitor_destroy();
}
static struct rv_monitor rv_this = {
.name = "snroc",
.description = "set non runnable on its own context.",
.enable = enable_snroc,
.disable = disable_snroc,
.reset = da_monitor_reset_all,
.enabled = 0,
};
static int __init register_snroc(void)
{
return rv_register_monitor(&rv_this, &rv_sched);
}
static void __exit unregister_snroc(void)
{
rv_unregister_monitor(&rv_this);
}
module_init(register_snroc);
module_exit(unregister_snroc);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Gabriele Monaco <gmonaco@redhat.com>");
MODULE_DESCRIPTION("snroc: set non runnable on its own context.");
@@ -0,0 +1,49 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Automatically generated C representation of snroc automaton
* For further information about this format, see kernel documentation:
* Documentation/trace/rv/deterministic_automata.rst
*/
#define MONITOR_NAME snroc
enum states_snroc {
other_context_snroc,
own_context_snroc,
state_max_snroc,
};
#define INVALID_STATE state_max_snroc
enum events_snroc {
sched_set_state_snroc,
sched_switch_in_snroc,
sched_switch_out_snroc,
event_max_snroc,
};
struct automaton_snroc {
char *state_names[state_max_snroc];
char *event_names[event_max_snroc];
unsigned char function[state_max_snroc][event_max_snroc];
unsigned char initial_state;
bool final_states[state_max_snroc];
};
static const struct automaton_snroc automaton_snroc = {
.state_names = {
"other_context",
"own_context",
},
.event_names = {
"sched_set_state",
"sched_switch_in",
"sched_switch_out",
},
.function = {
{ INVALID_STATE, own_context_snroc, INVALID_STATE },
{ own_context_snroc, INVALID_STATE, other_context_snroc },
},
.initial_state = other_context_snroc,
.final_states = { 1, 0 },
};
@@ -0,0 +1,15 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Snippet to be included in rv_trace.h
*/
#ifdef CONFIG_RV_MON_SNROC
DEFINE_EVENT(event_da_monitor_id, event_snroc,
TP_PROTO(int id, char *state, char *event, char *next_state, bool final_state),
TP_ARGS(id, state, event, next_state, final_state));
DEFINE_EVENT(error_da_monitor_id, error_snroc,
TP_PROTO(int id, char *state, char *event),
TP_ARGS(id, state, event));
#endif /* CONFIG_RV_MON_SNROC */
@@ -0,0 +1,15 @@
# SPDX-License-Identifier: GPL-2.0-only
#
config RV_MON_SSSW
depends on RV
depends on RV_MON_SCHED
default y
select DA_MON_EVENTS_ID
bool "sssw monitor"
help
Monitor to ensure sched_set_state to sleepable leads to sleeping and
sleeping tasks require wakeup.
This monitor is part of the sched monitors collection.
For further information, see:
Documentation/trace/rv/monitor_sched.rst
@@ -0,0 +1,114 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/ftrace.h>
#include <linux/tracepoint.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/rv.h>
#include <rv/instrumentation.h>
#define MODULE_NAME "sssw"
#include <trace/events/sched.h>
#include <trace/events/signal.h>
#include <rv_trace.h>
#include <monitors/sched/sched.h>
#define RV_MON_TYPE RV_MON_PER_TASK
#include "sssw.h"
#include <rv/da_monitor.h>
static void handle_sched_set_state(void *data, struct task_struct *tsk, int state)
{
if (state == TASK_RUNNING)
da_handle_start_event(tsk, sched_set_state_runnable_sssw);
else
da_handle_event(tsk, sched_set_state_sleepable_sssw);
}
static void handle_sched_switch(void *data, bool preempt,
struct task_struct *prev,
struct task_struct *next,
unsigned int prev_state)
{
if (preempt)
da_handle_event(prev, sched_switch_preempt_sssw);
else if (prev_state == TASK_RUNNING)
da_handle_event(prev, sched_switch_yield_sssw);
else if (prev_state == TASK_RTLOCK_WAIT)
/* special case of sleeping task with racy conditions */
da_handle_event(prev, sched_switch_blocking_sssw);
else
da_handle_event(prev, sched_switch_suspend_sssw);
da_handle_event(next, sched_switch_in_sssw);
}
static void handle_sched_wakeup(void *data, struct task_struct *p)
{
/*
* Wakeup can also lead to signal_wakeup although the system is
* actually runnable. The monitor can safely start with this event.
*/
da_handle_start_event(p, sched_wakeup_sssw);
}
static void handle_signal_deliver(void *data, int sig,
struct kernel_siginfo *info,
struct k_sigaction *ka)
{
da_handle_event(current, signal_deliver_sssw);
}
static int enable_sssw(void)
{
int retval;
retval = da_monitor_init();
if (retval)
return retval;
rv_attach_trace_probe("sssw", sched_set_state_tp, handle_sched_set_state);
rv_attach_trace_probe("sssw", sched_switch, handle_sched_switch);
rv_attach_trace_probe("sssw", sched_wakeup, handle_sched_wakeup);
rv_attach_trace_probe("sssw", signal_deliver, handle_signal_deliver);
return 0;
}
static void disable_sssw(void)
{
rv_this.enabled = 0;
rv_detach_trace_probe("sssw", sched_set_state_tp, handle_sched_set_state);
rv_detach_trace_probe("sssw", sched_switch, handle_sched_switch);
rv_detach_trace_probe("sssw", sched_wakeup, handle_sched_wakeup);
rv_detach_trace_probe("sssw", signal_deliver, handle_signal_deliver);
da_monitor_destroy();
}
static struct rv_monitor rv_this = {
.name = "sssw",
.description = "set state sleep and wakeup.",
.enable = enable_sssw,
.disable = disable_sssw,
.reset = da_monitor_reset_all,
.enabled = 0,
};
static int __init register_sssw(void)
{
return rv_register_monitor(&rv_this, &rv_sched);
}
static void __exit unregister_sssw(void)
{
rv_unregister_monitor(&rv_this);
}
module_init(register_sssw);
module_exit(unregister_sssw);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Gabriele Monaco <gmonaco@redhat.com>");
MODULE_DESCRIPTION("sssw: set state sleep and wakeup.");
@@ -0,0 +1,107 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Automatically generated C representation of sssw automaton
* For further information about this format, see kernel documentation:
* Documentation/trace/rv/deterministic_automata.rst
*/
#define MONITOR_NAME sssw
enum states_sssw {
runnable_sssw,
signal_wakeup_sssw,
sleepable_sssw,
sleeping_sssw,
state_max_sssw,
};
#define INVALID_STATE state_max_sssw
enum events_sssw {
sched_set_state_runnable_sssw,
sched_set_state_sleepable_sssw,
sched_switch_blocking_sssw,
sched_switch_in_sssw,
sched_switch_preempt_sssw,
sched_switch_suspend_sssw,
sched_switch_yield_sssw,
sched_wakeup_sssw,
signal_deliver_sssw,
event_max_sssw,
};
struct automaton_sssw {
char *state_names[state_max_sssw];
char *event_names[event_max_sssw];
unsigned char function[state_max_sssw][event_max_sssw];
unsigned char initial_state;
bool final_states[state_max_sssw];
};
static const struct automaton_sssw automaton_sssw = {
.state_names = {
"runnable",
"signal_wakeup",
"sleepable",
"sleeping",
},
.event_names = {
"sched_set_state_runnable",
"sched_set_state_sleepable",
"sched_switch_blocking",
"sched_switch_in",
"sched_switch_preempt",
"sched_switch_suspend",
"sched_switch_yield",
"sched_wakeup",
"signal_deliver",
},
.function = {
{
runnable_sssw,
sleepable_sssw,
sleeping_sssw,
runnable_sssw,
runnable_sssw,
INVALID_STATE,
runnable_sssw,
runnable_sssw,
runnable_sssw,
},
{
INVALID_STATE,
sleepable_sssw,
INVALID_STATE,
signal_wakeup_sssw,
signal_wakeup_sssw,
INVALID_STATE,
signal_wakeup_sssw,
signal_wakeup_sssw,
runnable_sssw,
},
{
runnable_sssw,
sleepable_sssw,
sleeping_sssw,
sleepable_sssw,
sleepable_sssw,
sleeping_sssw,
signal_wakeup_sssw,
runnable_sssw,
sleepable_sssw,
},
{
INVALID_STATE,
INVALID_STATE,
INVALID_STATE,
INVALID_STATE,
INVALID_STATE,
INVALID_STATE,
INVALID_STATE,
runnable_sssw,
INVALID_STATE,
},
},
.initial_state = runnable_sssw,
.final_states = { 1, 0, 0, 0 },
};
@@ -0,0 +1,15 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Snippet to be included in rv_trace.h
*/
#ifdef CONFIG_RV_MON_SSSW
DEFINE_EVENT(event_da_monitor_id, event_sssw,
TP_PROTO(int id, char *state, char *event, char *next_state, bool final_state),
TP_ARGS(id, state, event, next_state, final_state));
DEFINE_EVENT(error_da_monitor_id, error_sssw,
TP_PROTO(int id, char *state, char *event),
TP_ARGS(id, state, event));
#endif /* CONFIG_RV_MON_SSSW */
@@ -0,0 +1,13 @@
# SPDX-License-Identifier: GPL-2.0-only
#
config RV_MON_STALL
depends on RV
select HA_MON_EVENTS_ID
bool "stall monitor"
help
Enable the stall sample monitor that illustrates the usage of hybrid
automata monitors. It can be used to identify tasks stalled for
longer than a threshold.
For further information, see:
Documentation/trace/rv/monitor_stall.rst
@@ -0,0 +1,150 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/ftrace.h>
#include <linux/tracepoint.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/rv.h>
#include <rv/instrumentation.h>
#define MODULE_NAME "stall"
#include <trace/events/sched.h>
#include <rv_trace.h>
#define RV_MON_TYPE RV_MON_PER_TASK
#define HA_TIMER_TYPE HA_TIMER_WHEEL
#include "stall.h"
#include <rv/ha_monitor.h>
static u64 threshold_jiffies = 1000;
module_param(threshold_jiffies, ullong, 0644);
static u64 ha_get_env(struct ha_monitor *ha_mon, enum envs_stall env, u64 time_ns)
{
if (env == clk_stall)
return ha_get_clk_jiffy(ha_mon, env);
return ENV_INVALID_VALUE;
}
static void ha_reset_env(struct ha_monitor *ha_mon, enum envs_stall env, u64 time_ns)
{
if (env == clk_stall)
ha_reset_clk_jiffy(ha_mon, env);
}
static inline bool ha_verify_invariants(struct ha_monitor *ha_mon,
enum states curr_state, enum events event,
enum states next_state, u64 time_ns)
{
if (curr_state == enqueued_stall)
return ha_check_invariant_jiffy(ha_mon, clk_stall, time_ns);
return true;
}
static inline bool ha_verify_guards(struct ha_monitor *ha_mon,
enum states curr_state, enum events event,
enum states next_state, u64 time_ns)
{
bool res = true;
if (curr_state == dequeued_stall && event == sched_wakeup_stall)
ha_reset_env(ha_mon, clk_stall, time_ns);
else if (curr_state == running_stall && event == sched_switch_preempt_stall)
ha_reset_env(ha_mon, clk_stall, time_ns);
return res;
}
static inline void ha_setup_invariants(struct ha_monitor *ha_mon,
enum states curr_state, enum events event,
enum states next_state, u64 time_ns)
{
if (next_state == curr_state)
return;
if (next_state == enqueued_stall)
ha_start_timer_jiffy(ha_mon, clk_stall, threshold_jiffies, time_ns);
else if (curr_state == enqueued_stall)
ha_cancel_timer(ha_mon);
}
static bool ha_verify_constraint(struct ha_monitor *ha_mon,
enum states curr_state, enum events event,
enum states next_state, u64 time_ns)
{
if (!ha_verify_invariants(ha_mon, curr_state, event, next_state, time_ns))
return false;
if (!ha_verify_guards(ha_mon, curr_state, event, next_state, time_ns))
return false;
ha_setup_invariants(ha_mon, curr_state, event, next_state, time_ns);
return true;
}
static void handle_sched_switch(void *data, bool preempt,
struct task_struct *prev,
struct task_struct *next,
unsigned int prev_state)
{
if (!preempt && prev_state != TASK_RUNNING)
da_handle_start_event(prev, sched_switch_wait_stall);
else
da_handle_event(prev, sched_switch_preempt_stall);
da_handle_event(next, sched_switch_in_stall);
}
static void handle_sched_wakeup(void *data, struct task_struct *p)
{
da_handle_event(p, sched_wakeup_stall);
}
static int enable_stall(void)
{
int retval;
retval = ha_monitor_init();
if (retval)
return retval;
rv_attach_trace_probe("stall", sched_switch, handle_sched_switch);
rv_attach_trace_probe("stall", sched_wakeup, handle_sched_wakeup);
return 0;
}
static void disable_stall(void)
{
rv_this.enabled = 0;
rv_detach_trace_probe("stall", sched_switch, handle_sched_switch);
rv_detach_trace_probe("stall", sched_wakeup, handle_sched_wakeup);
ha_monitor_destroy();
}
static struct rv_monitor rv_this = {
.name = "stall",
.description = "identify tasks stalled for longer than a threshold.",
.enable = enable_stall,
.disable = disable_stall,
.reset = da_monitor_reset_all,
.enabled = 0,
};
static int __init register_stall(void)
{
return rv_register_monitor(&rv_this, NULL);
}
static void __exit unregister_stall(void)
{
rv_unregister_monitor(&rv_this);
}
module_init(register_stall);
module_exit(unregister_stall);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Gabriele Monaco <gmonaco@redhat.com>");
MODULE_DESCRIPTION("stall: identify tasks stalled for longer than a threshold.");
@@ -0,0 +1,81 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Automatically generated C representation of stall automaton
* For further information about this format, see kernel documentation:
* Documentation/trace/rv/deterministic_automata.rst
*/
#define MONITOR_NAME stall
enum states_stall {
dequeued_stall,
enqueued_stall,
running_stall,
state_max_stall,
};
#define INVALID_STATE state_max_stall
enum events_stall {
sched_switch_in_stall,
sched_switch_preempt_stall,
sched_switch_wait_stall,
sched_wakeup_stall,
event_max_stall,
};
enum envs_stall {
clk_stall,
env_max_stall,
env_max_stored_stall = env_max_stall,
};
_Static_assert(env_max_stored_stall <= MAX_HA_ENV_LEN, "Not enough slots");
struct automaton_stall {
char *state_names[state_max_stall];
char *event_names[event_max_stall];
char *env_names[env_max_stall];
unsigned char function[state_max_stall][event_max_stall];
unsigned char initial_state;
bool final_states[state_max_stall];
};
static const struct automaton_stall automaton_stall = {
.state_names = {
"dequeued",
"enqueued",
"running",
},
.event_names = {
"sched_switch_in",
"sched_switch_preempt",
"sched_switch_wait",
"sched_wakeup",
},
.env_names = {
"clk",
},
.function = {
{
INVALID_STATE,
INVALID_STATE,
INVALID_STATE,
enqueued_stall,
},
{
running_stall,
INVALID_STATE,
INVALID_STATE,
enqueued_stall,
},
{
running_stall,
enqueued_stall,
dequeued_stall,
running_stall,
},
},
.initial_state = dequeued_stall,
.final_states = { 1, 0, 0 },
};
@@ -0,0 +1,19 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Snippet to be included in rv_trace.h
*/
#ifdef CONFIG_RV_MON_STALL
DEFINE_EVENT(event_da_monitor_id, event_stall,
TP_PROTO(int id, char *state, char *event, char *next_state, bool final_state),
TP_ARGS(id, state, event, next_state, final_state));
DEFINE_EVENT(error_da_monitor_id, error_stall,
TP_PROTO(int id, char *state, char *event),
TP_ARGS(id, state, event));
DEFINE_EVENT(error_env_da_monitor_id, error_env_stall,
TP_PROTO(int id, char *state, char *event, char *env),
TP_ARGS(id, state, event, env));
#endif /* CONFIG_RV_MON_STALL */
@@ -0,0 +1,19 @@
# SPDX-License-Identifier: GPL-2.0-only
#
config RV_MON_STS
depends on RV
depends on TRACE_IRQFLAGS
depends on RV_MON_SCHED
default y
select DA_MON_EVENTS_IMPLICIT
bool "sts monitor"
help
Monitor to ensure relationships between scheduler and task switches
* the scheduler is called and returns with interrupts disabled
* each call to the scheduler has up to one switch
* switches only happen inside the scheduler
* each call to the scheduler disables interrupts to switch
This monitor is part of the sched monitors collection.
For further information, see:
Documentation/trace/rv/monitor_sched.rst
@@ -0,0 +1,154 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/ftrace.h>
#include <linux/tracepoint.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/rv.h>
#include <rv/instrumentation.h>
#define MODULE_NAME "sts"
#include <trace/events/sched.h>
#include <trace/events/irq.h>
#include <trace/events/preemptirq.h>
#include <rv_trace.h>
#include <monitors/sched/sched.h>
#define RV_MON_TYPE RV_MON_PER_CPU
#include "sts.h"
#include <rv/da_monitor.h>
#ifdef CONFIG_X86_LOCAL_APIC
#include <asm/trace/irq_vectors.h>
static void handle_vector_irq_entry(void *data, int vector)
{
da_handle_event(irq_entry_sts);
}
static void attach_vector_irq(void)
{
rv_attach_trace_probe("sts", local_timer_entry, handle_vector_irq_entry);
if (IS_ENABLED(CONFIG_IRQ_WORK))
rv_attach_trace_probe("sts", irq_work_entry, handle_vector_irq_entry);
if (IS_ENABLED(CONFIG_SMP)) {
rv_attach_trace_probe("sts", reschedule_entry, handle_vector_irq_entry);
rv_attach_trace_probe("sts", call_function_entry, handle_vector_irq_entry);
rv_attach_trace_probe("sts", call_function_single_entry, handle_vector_irq_entry);
}
}
static void detach_vector_irq(void)
{
rv_detach_trace_probe("sts", local_timer_entry, handle_vector_irq_entry);
if (IS_ENABLED(CONFIG_IRQ_WORK))
rv_detach_trace_probe("sts", irq_work_entry, handle_vector_irq_entry);
if (IS_ENABLED(CONFIG_SMP)) {
rv_detach_trace_probe("sts", reschedule_entry, handle_vector_irq_entry);
rv_detach_trace_probe("sts", call_function_entry, handle_vector_irq_entry);
rv_detach_trace_probe("sts", call_function_single_entry, handle_vector_irq_entry);
}
}
#else
/* We assume irq_entry tracepoints are sufficient on other architectures */
static void attach_vector_irq(void) { }
static void detach_vector_irq(void) { }
#endif
static void handle_irq_disable(void *data, unsigned long ip, unsigned long parent_ip)
{
da_handle_event(irq_disable_sts);
}
static void handle_irq_enable(void *data, unsigned long ip, unsigned long parent_ip)
{
da_handle_event(irq_enable_sts);
}
static void handle_irq_entry(void *data, int irq, struct irqaction *action)
{
da_handle_event(irq_entry_sts);
}
static void handle_sched_switch(void *data, bool preempt,
struct task_struct *prev,
struct task_struct *next,
unsigned int prev_state)
{
da_handle_event(sched_switch_sts);
}
static void handle_schedule_entry(void *data, bool preempt)
{
da_handle_event(schedule_entry_sts);
}
static void handle_schedule_exit(void *data, bool is_switch)
{
da_handle_start_event(schedule_exit_sts);
}
static int enable_sts(void)
{
int retval;
retval = da_monitor_init();
if (retval)
return retval;
rv_attach_trace_probe("sts", irq_disable, handle_irq_disable);
rv_attach_trace_probe("sts", irq_enable, handle_irq_enable);
rv_attach_trace_probe("sts", irq_handler_entry, handle_irq_entry);
rv_attach_trace_probe("sts", sched_switch, handle_sched_switch);
rv_attach_trace_probe("sts", sched_entry_tp, handle_schedule_entry);
rv_attach_trace_probe("sts", sched_exit_tp, handle_schedule_exit);
attach_vector_irq();
return 0;
}
static void disable_sts(void)
{
rv_this.enabled = 0;
rv_detach_trace_probe("sts", irq_disable, handle_irq_disable);
rv_detach_trace_probe("sts", irq_enable, handle_irq_enable);
rv_detach_trace_probe("sts", irq_handler_entry, handle_irq_entry);
rv_detach_trace_probe("sts", sched_switch, handle_sched_switch);
rv_detach_trace_probe("sts", sched_entry_tp, handle_schedule_entry);
rv_detach_trace_probe("sts", sched_exit_tp, handle_schedule_exit);
detach_vector_irq();
da_monitor_destroy();
}
/*
* This is the monitor register section.
*/
static struct rv_monitor rv_this = {
.name = "sts",
.description = "schedule implies task switch.",
.enable = enable_sts,
.disable = disable_sts,
.reset = da_monitor_reset_all,
.enabled = 0,
};
static int __init register_sts(void)
{
return rv_register_monitor(&rv_this, &rv_sched);
}
static void __exit unregister_sts(void)
{
rv_unregister_monitor(&rv_this);
}
module_init(register_sts);
module_exit(unregister_sts);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Gabriele Monaco <gmonaco@redhat.com>");
MODULE_DESCRIPTION("sts: schedule implies task switch.");
@@ -0,0 +1,119 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Automatically generated C representation of sts automaton
* For further information about this format, see kernel documentation:
* Documentation/trace/rv/deterministic_automata.rst
*/
#define MONITOR_NAME sts
enum states_sts {
can_sched_sts,
cant_sched_sts,
disable_to_switch_sts,
enable_to_exit_sts,
in_irq_sts,
scheduling_sts,
switching_sts,
state_max_sts,
};
#define INVALID_STATE state_max_sts
enum events_sts {
irq_disable_sts,
irq_enable_sts,
irq_entry_sts,
sched_switch_sts,
schedule_entry_sts,
schedule_exit_sts,
event_max_sts,
};
struct automaton_sts {
char *state_names[state_max_sts];
char *event_names[event_max_sts];
unsigned char function[state_max_sts][event_max_sts];
unsigned char initial_state;
bool final_states[state_max_sts];
};
static const struct automaton_sts automaton_sts = {
.state_names = {
"can_sched",
"cant_sched",
"disable_to_switch",
"enable_to_exit",
"in_irq",
"scheduling",
"switching",
},
.event_names = {
"irq_disable",
"irq_enable",
"irq_entry",
"sched_switch",
"schedule_entry",
"schedule_exit",
},
.function = {
{
cant_sched_sts,
INVALID_STATE,
INVALID_STATE,
INVALID_STATE,
scheduling_sts,
INVALID_STATE,
},
{
INVALID_STATE,
can_sched_sts,
cant_sched_sts,
INVALID_STATE,
INVALID_STATE,
INVALID_STATE,
},
{
INVALID_STATE,
enable_to_exit_sts,
in_irq_sts,
switching_sts,
INVALID_STATE,
INVALID_STATE,
},
{
enable_to_exit_sts,
enable_to_exit_sts,
enable_to_exit_sts,
INVALID_STATE,
INVALID_STATE,
can_sched_sts,
},
{
INVALID_STATE,
scheduling_sts,
in_irq_sts,
INVALID_STATE,
INVALID_STATE,
INVALID_STATE,
},
{
disable_to_switch_sts,
INVALID_STATE,
INVALID_STATE,
INVALID_STATE,
INVALID_STATE,
INVALID_STATE,
},
{
INVALID_STATE,
enable_to_exit_sts,
INVALID_STATE,
INVALID_STATE,
INVALID_STATE,
INVALID_STATE,
},
},
.initial_state = can_sched_sts,
.final_states = { 1, 0, 0, 0, 0, 0, 0 },
};
@@ -0,0 +1,15 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Snippet to be included in rv_trace.h
*/
#ifdef CONFIG_RV_MON_STS
DEFINE_EVENT(event_da_monitor, event_sts,
TP_PROTO(char *state, char *event, char *next_state, bool final_state),
TP_ARGS(state, event, next_state, final_state));
DEFINE_EVENT(error_da_monitor, error_sts,
TP_PROTO(char *state, char *event),
TP_ARGS(state, event));
#endif /* CONFIG_RV_MON_STS */
@@ -0,0 +1,14 @@
# SPDX-License-Identifier: GPL-2.0-only
#
config RV_MON_WIP
depends on RV
depends on TRACE_PREEMPT_TOGGLE
select DA_MON_EVENTS_IMPLICIT
bool "wip monitor"
help
Enable wip (wakeup in preemptive) sample monitor that illustrates
the usage of per-cpu monitors, and one limitation of the
preempt_disable/enable events.
For further information, see:
Documentation/trace/rv/monitor_wip.rst
@@ -0,0 +1,85 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/ftrace.h>
#include <linux/tracepoint.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/rv.h>
#include <rv/instrumentation.h>
#define MODULE_NAME "wip"
#include <rv_trace.h>
#include <trace/events/sched.h>
#include <trace/events/preemptirq.h>
#define RV_MON_TYPE RV_MON_PER_CPU
#include "wip.h"
#include <rv/da_monitor.h>
static void handle_preempt_disable(void *data, unsigned long ip, unsigned long parent_ip)
{
da_handle_event(preempt_disable_wip);
}
static void handle_preempt_enable(void *data, unsigned long ip, unsigned long parent_ip)
{
da_handle_start_event(preempt_enable_wip);
}
static void handle_sched_waking(void *data, struct task_struct *task)
{
da_handle_event(sched_waking_wip);
}
static int enable_wip(void)
{
int retval;
retval = da_monitor_init();
if (retval)
return retval;
rv_attach_trace_probe("wip", preempt_enable, handle_preempt_enable);
rv_attach_trace_probe("wip", sched_waking, handle_sched_waking);
rv_attach_trace_probe("wip", preempt_disable, handle_preempt_disable);
return 0;
}
static void disable_wip(void)
{
rv_this.enabled = 0;
rv_detach_trace_probe("wip", preempt_disable, handle_preempt_disable);
rv_detach_trace_probe("wip", preempt_enable, handle_preempt_enable);
rv_detach_trace_probe("wip", sched_waking, handle_sched_waking);
da_monitor_destroy();
}
static struct rv_monitor rv_this = {
.name = "wip",
.description = "wakeup in preemptive per-cpu testing monitor.",
.enable = enable_wip,
.disable = disable_wip,
.reset = da_monitor_reset_all,
.enabled = 0,
};
static int __init register_wip(void)
{
return rv_register_monitor(&rv_this, NULL);
}
static void __exit unregister_wip(void)
{
rv_unregister_monitor(&rv_this);
}
module_init(register_wip);
module_exit(unregister_wip);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Daniel Bristot de Oliveira <bristot@kernel.org>");
MODULE_DESCRIPTION("wip: wakeup in preemptive - per-cpu sample monitor.");
@@ -0,0 +1,49 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Automatically generated C representation of wip automaton
* For further information about this format, see kernel documentation:
* Documentation/trace/rv/deterministic_automata.rst
*/
#define MONITOR_NAME wip
enum states_wip {
preemptive_wip,
non_preemptive_wip,
state_max_wip,
};
#define INVALID_STATE state_max_wip
enum events_wip {
preempt_disable_wip,
preempt_enable_wip,
sched_waking_wip,
event_max_wip,
};
struct automaton_wip {
char *state_names[state_max_wip];
char *event_names[event_max_wip];
unsigned char function[state_max_wip][event_max_wip];
unsigned char initial_state;
bool final_states[state_max_wip];
};
static const struct automaton_wip automaton_wip = {
.state_names = {
"preemptive",
"non_preemptive",
},
.event_names = {
"preempt_disable",
"preempt_enable",
"sched_waking",
},
.function = {
{ non_preemptive_wip, INVALID_STATE, INVALID_STATE },
{ INVALID_STATE, preemptive_wip, non_preemptive_wip },
},
.initial_state = preemptive_wip,
.final_states = { 1, 0 },
};
@@ -0,0 +1,15 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Snippet to be included in rv_trace.h
*/
#ifdef CONFIG_RV_MON_WIP
DEFINE_EVENT(event_da_monitor, event_wip,
TP_PROTO(char *state, char *event, char *next_state, bool final_state),
TP_ARGS(state, event, next_state, final_state));
DEFINE_EVENT(error_da_monitor, error_wip,
TP_PROTO(char *state, char *event),
TP_ARGS(state, event));
#endif /* CONFIG_RV_MON_WIP */
@@ -0,0 +1,13 @@
# SPDX-License-Identifier: GPL-2.0-only
#
config RV_MON_WWNR
depends on RV
select DA_MON_EVENTS_ID
bool "wwnr monitor"
help
Enable wwnr (wakeup while not running) sample monitor, this is a
sample monitor that illustrates the usage of per-task monitor.
The model is borken on purpose: it serves to test reactors.
For further information, see:
Documentation/trace/rv/monitor_wwnr.rst
@@ -0,0 +1,84 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/ftrace.h>
#include <linux/tracepoint.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/rv.h>
#include <rv/instrumentation.h>
#define MODULE_NAME "wwnr"
#include <rv_trace.h>
#include <trace/events/sched.h>
#define RV_MON_TYPE RV_MON_PER_TASK
#include "wwnr.h"
#include <rv/da_monitor.h>
static void handle_switch(void *data, bool preempt, struct task_struct *p,
struct task_struct *n, unsigned int prev_state)
{
/* start monitoring only after the first suspension */
if (prev_state == TASK_INTERRUPTIBLE)
da_handle_start_event(p, switch_out_wwnr);
else
da_handle_event(p, switch_out_wwnr);
da_handle_event(n, switch_in_wwnr);
}
static void handle_wakeup(void *data, struct task_struct *p)
{
da_handle_event(p, wakeup_wwnr);
}
static int enable_wwnr(void)
{
int retval;
retval = da_monitor_init();
if (retval)
return retval;
rv_attach_trace_probe("wwnr", sched_switch, handle_switch);
rv_attach_trace_probe("wwnr", sched_wakeup, handle_wakeup);
return 0;
}
static void disable_wwnr(void)
{
rv_this.enabled = 0;
rv_detach_trace_probe("wwnr", sched_switch, handle_switch);
rv_detach_trace_probe("wwnr", sched_wakeup, handle_wakeup);
da_monitor_destroy();
}
static struct rv_monitor rv_this = {
.name = "wwnr",
.description = "wakeup while not running per-task testing model.",
.enable = enable_wwnr,
.disable = disable_wwnr,
.reset = da_monitor_reset_all,
.enabled = 0,
};
static int __init register_wwnr(void)
{
return rv_register_monitor(&rv_this, NULL);
}
static void __exit unregister_wwnr(void)
{
rv_unregister_monitor(&rv_this);
}
module_init(register_wwnr);
module_exit(unregister_wwnr);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Daniel Bristot de Oliveira <bristot@kernel.org>");
MODULE_DESCRIPTION("wwnr: wakeup while not running monitor");
@@ -0,0 +1,49 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Automatically generated C representation of wwnr automaton
* For further information about this format, see kernel documentation:
* Documentation/trace/rv/deterministic_automata.rst
*/
#define MONITOR_NAME wwnr
enum states_wwnr {
not_running_wwnr,
running_wwnr,
state_max_wwnr,
};
#define INVALID_STATE state_max_wwnr
enum events_wwnr {
switch_in_wwnr,
switch_out_wwnr,
wakeup_wwnr,
event_max_wwnr,
};
struct automaton_wwnr {
char *state_names[state_max_wwnr];
char *event_names[event_max_wwnr];
unsigned char function[state_max_wwnr][event_max_wwnr];
unsigned char initial_state;
bool final_states[state_max_wwnr];
};
static const struct automaton_wwnr automaton_wwnr = {
.state_names = {
"not_running",
"running",
},
.event_names = {
"switch_in",
"switch_out",
"wakeup",
},
.function = {
{ running_wwnr, INVALID_STATE, not_running_wwnr },
{ INVALID_STATE, not_running_wwnr, INVALID_STATE },
},
.initial_state = not_running_wwnr,
.final_states = { 1, 0 },
};
@@ -0,0 +1,16 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Snippet to be included in rv_trace.h
*/
#ifdef CONFIG_RV_MON_WWNR
/* id is the pid of the task */
DEFINE_EVENT(event_da_monitor_id, event_wwnr,
TP_PROTO(int id, char *state, char *event, char *next_state, bool final_state),
TP_ARGS(id, state, event, next_state, final_state));
DEFINE_EVENT(error_da_monitor_id, error_wwnr,
TP_PROTO(int id, char *state, char *event),
TP_ARGS(id, state, event));
#endif /* CONFIG_RV_MON_WWNR */
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2019-2022 Red Hat, Inc. Daniel Bristot de Oliveira <bristot@kernel.org>
*
* Panic RV reactor:
* Prints the exception msg to the kernel message log and panic().
*/
#include <linux/ftrace.h>
#include <linux/tracepoint.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/rv.h>
__printf(1, 0) static void rv_panic_reaction(const char *msg, va_list args)
{
vpanic(msg, args);
}
static struct rv_reactor rv_panic = {
.name = "panic",
.description = "panic the system if an exception is found.",
.react = rv_panic_reaction
};
static int __init register_react_panic(void)
{
rv_register_reactor(&rv_panic);
return 0;
}
static void __exit unregister_react_panic(void)
{
rv_unregister_reactor(&rv_panic);
}
module_init(register_react_panic);
module_exit(unregister_react_panic);
MODULE_AUTHOR("Daniel Bristot de Oliveira");
MODULE_DESCRIPTION("panic rv reactor: panic if an exception is found.");
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2019-2022 Red Hat, Inc. Daniel Bristot de Oliveira <bristot@kernel.org>
*
* Printk RV reactor:
* Prints the exception msg to the kernel message log.
*/
#include <linux/ftrace.h>
#include <linux/tracepoint.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/rv.h>
__printf(1, 0) static void rv_printk_reaction(const char *msg, va_list args)
{
vprintk_deferred(msg, args);
}
static struct rv_reactor rv_printk = {
.name = "printk",
.description = "prints the exception msg to the kernel message log.",
.react = rv_printk_reaction
};
static int __init register_react_printk(void)
{
rv_register_reactor(&rv_printk);
return 0;
}
static void __exit unregister_react_printk(void)
{
rv_unregister_reactor(&rv_printk);
}
module_init(register_react_printk);
module_exit(unregister_react_printk);
MODULE_AUTHOR("Daniel Bristot de Oliveira");
MODULE_DESCRIPTION("printk rv reactor: printk if an exception is hit.");
+854
View File
@@ -0,0 +1,854 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2019-2022 Red Hat, Inc. Daniel Bristot de Oliveira <bristot@kernel.org>
*
* This is the online Runtime Verification (RV) interface.
*
* RV is a lightweight (yet rigorous) method that complements classical
* exhaustive verification techniques (such as model checking and
* theorem proving) with a more practical approach to complex systems.
*
* RV works by analyzing the trace of the system's actual execution,
* comparing it against a formal specification of the system behavior.
* RV can give precise information on the runtime behavior of the
* monitored system while enabling the reaction for unexpected
* events, avoiding, for example, the propagation of a failure on
* safety-critical systems.
*
* The development of this interface roots in the development of the
* paper:
*
* De Oliveira, Daniel Bristot; Cucinotta, Tommaso; De Oliveira, Romulo
* Silva. Efficient formal verification for the Linux kernel. In:
* International Conference on Software Engineering and Formal Methods.
* Springer, Cham, 2019. p. 315-332.
*
* And:
*
* De Oliveira, Daniel Bristot, et al. Automata-based formal analysis
* and verification of the real-time Linux kernel. PhD Thesis, 2020.
*
* == Runtime monitor interface ==
*
* A monitor is the central part of the runtime verification of a system.
*
* The monitor stands in between the formal specification of the desired
* (or undesired) behavior, and the trace of the actual system.
*
* In Linux terms, the runtime verification monitors are encapsulated
* inside the "RV monitor" abstraction. A RV monitor includes a reference
* model of the system, a set of instances of the monitor (per-cpu monitor,
* per-task monitor, and so on), and the helper functions that glue the
* monitor to the system via trace. Generally, a monitor includes some form
* of trace output as a reaction for event parsing and exceptions,
* as depicted below:
*
* Linux +----- RV Monitor ----------------------------------+ Formal
* Realm | | Realm
* +-------------------+ +----------------+ +-----------------+
* | Linux kernel | | Monitor | | Reference |
* | Tracing | -> | Instance(s) | <- | Model |
* | (instrumentation) | | (verification) | | (specification) |
* +-------------------+ +----------------+ +-----------------+
* | | |
* | V |
* | +----------+ |
* | | Reaction | |
* | +--+--+--+-+ |
* | | | | |
* | | | +-> trace output ? |
* +------------------------|--|----------------------+
* | +----> panic ?
* +-------> <user-specified>
*
* This file implements the interface for loading RV monitors, and
* to control the verification session.
*
* == Registering monitors ==
*
* The struct rv_monitor defines a set of callback functions to control
* a verification session. For instance, when a given monitor is enabled,
* the "enable" callback function is called to hook the instrumentation
* functions to the kernel trace events. The "disable" function is called
* when disabling the verification session.
*
* A RV monitor is registered via:
* int rv_register_monitor(struct rv_monitor *monitor);
* And unregistered via:
* int rv_unregister_monitor(struct rv_monitor *monitor);
*
* == User interface ==
*
* The user interface resembles kernel tracing interface. It presents
* these files:
*
* "available_monitors"
* - List the available monitors, one per line.
*
* For example:
* # cat available_monitors
* wip
* wwnr
*
* "enabled_monitors"
* - Lists the enabled monitors, one per line;
* - Writing to it enables a given monitor;
* - Writing a monitor name with a '!' prefix disables it;
* - Truncating the file disables all enabled monitors.
*
* For example:
* # cat enabled_monitors
* # echo wip > enabled_monitors
* # echo wwnr >> enabled_monitors
* # cat enabled_monitors
* wip
* wwnr
* # echo '!wip' >> enabled_monitors
* # cat enabled_monitors
* wwnr
* # echo > enabled_monitors
* # cat enabled_monitors
* #
*
* Note that more than one monitor can be enabled concurrently.
*
* "monitoring_on"
* - It is an on/off general switcher for monitoring. Note
* that it does not disable enabled monitors or detach events,
* but stops the per-entity monitors from monitoring the events
* received from the instrumentation. It resembles the "tracing_on"
* switcher.
*
* "monitors/"
* Each monitor will have its own directory inside "monitors/". There
* the monitor specific files will be presented.
* The "monitors/" directory resembles the "events" directory on
* tracefs.
*
* For example:
* # cd monitors/wip/
* # ls
* desc enable
* # cat desc
* auto-generated wakeup in preemptive monitor.
* # cat enable
* 0
*
* For further information, see:
* Documentation/trace/rv/runtime-verification.rst
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#ifdef CONFIG_RV_MON_EVENTS
#define CREATE_TRACE_POINTS
#include <rv_trace.h>
#endif
#include "rv.h"
DEFINE_MUTEX(rv_interface_lock);
static struct rv_interface rv_root;
struct dentry *get_monitors_root(void)
{
return rv_root.monitors_dir;
}
/*
* Interface for the monitor register.
*/
LIST_HEAD(rv_monitors_list);
static int task_monitor_count;
static bool task_monitor_slots[CONFIG_RV_PER_TASK_MONITORS];
int rv_get_task_monitor_slot(void)
{
int i;
lockdep_assert_held(&rv_interface_lock);
if (task_monitor_count == CONFIG_RV_PER_TASK_MONITORS)
return -EBUSY;
task_monitor_count++;
for (i = 0; i < CONFIG_RV_PER_TASK_MONITORS; i++) {
if (task_monitor_slots[i] == false) {
task_monitor_slots[i] = true;
return i;
}
}
WARN_ONCE(1, "RV task_monitor_count and slots are out of sync\n");
return -EINVAL;
}
void rv_put_task_monitor_slot(int slot)
{
lockdep_assert_held(&rv_interface_lock);
if (slot < 0 || slot >= CONFIG_RV_PER_TASK_MONITORS) {
WARN_ONCE(1, "RV releasing an invalid slot!: %d\n", slot);
return;
}
WARN_ONCE(!task_monitor_slots[slot], "RV releasing unused task_monitor_slots: %d\n",
slot);
task_monitor_count--;
task_monitor_slots[slot] = false;
}
/*
* Monitors with a parent are nested,
* Monitors without a parent could be standalone or containers.
*/
bool rv_is_nested_monitor(struct rv_monitor *mon)
{
return mon->parent != NULL;
}
/*
* We set our list to have nested monitors listed after their parent
* if a monitor has a child element its a container.
* Containers can be also identified based on their function pointers:
* as they are not real monitors they do not need function definitions
* for enable()/disable(). Use this condition to find empty containers.
* Keep both conditions in case we have some non-compliant containers.
*/
bool rv_is_container_monitor(struct rv_monitor *mon)
{
struct rv_monitor *next;
if (list_is_last(&mon->list, &rv_monitors_list))
return false;
next = list_next_entry(mon, list);
return next->parent == mon || !mon->enable;
}
/*
* This section collects the monitor/ files and folders.
*/
static ssize_t monitor_enable_read_data(struct file *filp, char __user *user_buf, size_t count,
loff_t *ppos)
{
struct rv_monitor *mon = filp->private_data;
const char *buff;
buff = mon->enabled ? "1\n" : "0\n";
return simple_read_from_buffer(user_buf, count, ppos, buff, strlen(buff)+1);
}
/*
* __rv_disable_monitor - disabled an enabled monitor
*/
static int __rv_disable_monitor(struct rv_monitor *mon, bool sync)
{
lockdep_assert_held(&rv_interface_lock);
if (mon->enabled) {
mon->enabled = 0;
if (mon->disable)
mon->disable();
/*
* Wait for the execution of all events to finish.
* Otherwise, the data used by the monitor could
* be inconsistent. i.e., if the monitor is re-enabled.
*/
if (sync)
tracepoint_synchronize_unregister();
return 1;
}
return 0;
}
static void rv_disable_single(struct rv_monitor *mon)
{
__rv_disable_monitor(mon, true);
}
static int rv_enable_single(struct rv_monitor *mon)
{
int retval;
lockdep_assert_held(&rv_interface_lock);
if (mon->enabled)
return 0;
retval = mon->enable();
if (!retval)
mon->enabled = 1;
return retval;
}
static void rv_disable_container(struct rv_monitor *mon)
{
struct rv_monitor *p = mon;
int enabled = 0;
list_for_each_entry_continue(p, &rv_monitors_list, list) {
if (p->parent != mon)
break;
enabled += __rv_disable_monitor(p, false);
}
if (enabled)
tracepoint_synchronize_unregister();
mon->enabled = 0;
}
static int rv_enable_container(struct rv_monitor *mon)
{
struct rv_monitor *p = mon;
int retval = 0;
list_for_each_entry_continue(p, &rv_monitors_list, list) {
if (retval || p->parent != mon)
break;
retval = rv_enable_single(p);
}
if (retval)
rv_disable_container(mon);
else
mon->enabled = 1;
return retval;
}
/**
* rv_disable_monitor - disable a given runtime monitor
* @mon: Pointer to the monitor definition structure.
*
* Returns 0 on success.
*/
int rv_disable_monitor(struct rv_monitor *mon)
{
if (rv_is_container_monitor(mon))
rv_disable_container(mon);
else
rv_disable_single(mon);
return 0;
}
/**
* rv_enable_monitor - enable a given runtime monitor
* @mon: Pointer to the monitor definition structure.
*
* Returns 0 on success, error otherwise.
*/
int rv_enable_monitor(struct rv_monitor *mon)
{
int retval;
if (rv_is_container_monitor(mon))
retval = rv_enable_container(mon);
else
retval = rv_enable_single(mon);
return retval;
}
/*
* interface for enabling/disabling a monitor.
*/
static ssize_t monitor_enable_write_data(struct file *filp, const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct rv_monitor *mon = filp->private_data;
int retval;
bool val;
retval = kstrtobool_from_user(user_buf, count, &val);
if (retval)
return retval;
guard(mutex)(&rv_interface_lock);
if (val)
retval = rv_enable_monitor(mon);
else
retval = rv_disable_monitor(mon);
return retval ? : count;
}
static const struct file_operations interface_enable_fops = {
.open = simple_open,
.write = monitor_enable_write_data,
.read = monitor_enable_read_data,
};
/*
* Interface to read monitors description.
*/
static ssize_t monitor_desc_read_data(struct file *filp, char __user *user_buf, size_t count,
loff_t *ppos)
{
struct rv_monitor *mon = filp->private_data;
char buff[256];
memset(buff, 0, sizeof(buff));
snprintf(buff, sizeof(buff), "%s\n", mon->description);
return simple_read_from_buffer(user_buf, count, ppos, buff, strlen(buff) + 1);
}
static const struct file_operations interface_desc_fops = {
.open = simple_open,
.read = monitor_desc_read_data,
};
/*
* During the registration of a monitor, this function creates
* the monitor dir, where the specific options of the monitor
* are exposed.
*/
static int create_monitor_dir(struct rv_monitor *mon, struct rv_monitor *parent)
{
struct dentry *root = parent ? parent->root_d : get_monitors_root();
struct dentry *dir __free(rv_remove) = rv_create_dir(mon->name, root);
struct dentry *tmp;
int retval;
if (!dir)
return -ENOMEM;
tmp = rv_create_file("enable", RV_MODE_WRITE, dir, mon, &interface_enable_fops);
if (!tmp)
return -ENOMEM;
tmp = rv_create_file("desc", RV_MODE_READ, dir, mon, &interface_desc_fops);
if (!tmp)
return -ENOMEM;
retval = reactor_populate_monitor(mon, dir);
if (retval)
return retval;
mon->root_d = no_free_ptr(dir);
return 0;
}
/*
* Available/Enable monitor shared seq functions.
*/
static int monitors_show(struct seq_file *m, void *p)
{
struct rv_monitor *mon = container_of(p, struct rv_monitor, list);
if (mon->parent)
seq_printf(m, "%s:%s\n", mon->parent->name, mon->name);
else
seq_printf(m, "%s\n", mon->name);
return 0;
}
/*
* Used by the seq file operations at the end of a read
* operation.
*/
static void monitors_stop(struct seq_file *m, void *p)
{
mutex_unlock(&rv_interface_lock);
}
/*
* Available monitor seq functions.
*/
static void *available_monitors_start(struct seq_file *m, loff_t *pos)
{
mutex_lock(&rv_interface_lock);
return seq_list_start(&rv_monitors_list, *pos);
}
static void *available_monitors_next(struct seq_file *m, void *p, loff_t *pos)
{
return seq_list_next(p, &rv_monitors_list, pos);
}
/*
* Enable monitor seq functions.
*/
static void *enabled_monitors_next(struct seq_file *m, void *p, loff_t *pos)
{
struct rv_monitor *mon = container_of(p, struct rv_monitor, list);
(*pos)++;
list_for_each_entry_continue(mon, &rv_monitors_list, list) {
if (mon->enabled)
return &mon->list;
}
return NULL;
}
static void *enabled_monitors_start(struct seq_file *m, loff_t *pos)
{
struct list_head *head;
loff_t l;
mutex_lock(&rv_interface_lock);
if (list_empty(&rv_monitors_list))
return NULL;
head = &rv_monitors_list;
for (l = 0; l <= *pos; ) {
head = enabled_monitors_next(m, head, &l);
if (!head)
break;
}
return head;
}
/*
* available/enabled monitors seq definition.
*/
static const struct seq_operations available_monitors_seq_ops = {
.start = available_monitors_start,
.next = available_monitors_next,
.stop = monitors_stop,
.show = monitors_show
};
static const struct seq_operations enabled_monitors_seq_ops = {
.start = enabled_monitors_start,
.next = enabled_monitors_next,
.stop = monitors_stop,
.show = monitors_show
};
/*
* available_monitors interface.
*/
static int available_monitors_open(struct inode *inode, struct file *file)
{
return seq_open(file, &available_monitors_seq_ops);
};
static const struct file_operations available_monitors_ops = {
.open = available_monitors_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release
};
/*
* enabled_monitors interface.
*/
static void disable_all_monitors(void)
{
struct rv_monitor *mon;
int enabled = 0;
guard(mutex)(&rv_interface_lock);
list_for_each_entry(mon, &rv_monitors_list, list)
enabled += __rv_disable_monitor(mon, false);
if (enabled) {
/*
* Wait for the execution of all events to finish.
* Otherwise, the data used by the monitor could
* be inconsistent. i.e., if the monitor is re-enabled.
*/
tracepoint_synchronize_unregister();
}
}
static int enabled_monitors_open(struct inode *inode, struct file *file)
{
if ((file->f_mode & FMODE_WRITE) && (file->f_flags & O_TRUNC))
disable_all_monitors();
return seq_open(file, &enabled_monitors_seq_ops);
};
static ssize_t enabled_monitors_write(struct file *filp, const char __user *user_buf,
size_t count, loff_t *ppos)
{
char buff[MAX_RV_MONITOR_NAME_SIZE + 2];
struct rv_monitor *mon;
int retval = -EINVAL;
bool enable = true;
char *ptr, *tmp;
int len;
if (count < 1 || count > MAX_RV_MONITOR_NAME_SIZE + 1)
return -EINVAL;
memset(buff, 0, sizeof(buff));
retval = simple_write_to_buffer(buff, sizeof(buff) - 1, ppos, user_buf, count);
if (retval < 0)
return -EFAULT;
ptr = strim(buff);
if (ptr[0] == '!') {
enable = false;
ptr++;
}
len = strlen(ptr);
if (!len)
return count;
guard(mutex)(&rv_interface_lock);
retval = -EINVAL;
/* we support 1 nesting level, trim the parent */
tmp = strstr(ptr, ":");
if (tmp)
ptr = tmp+1;
list_for_each_entry(mon, &rv_monitors_list, list) {
if (strcmp(ptr, mon->name) != 0)
continue;
/*
* Monitor found!
*/
if (enable)
retval = rv_enable_monitor(mon);
else
retval = rv_disable_monitor(mon);
if (retval)
return retval;
return count;
}
return retval;
}
static const struct file_operations enabled_monitors_ops = {
.open = enabled_monitors_open,
.read = seq_read,
.write = enabled_monitors_write,
.llseek = seq_lseek,
.release = seq_release,
};
/*
* Monitoring on global switcher!
*/
static bool __read_mostly monitoring_on;
/**
* rv_monitoring_on - checks if monitoring is on
*
* Returns 1 if on, 0 otherwise.
*/
bool rv_monitoring_on(void)
{
return READ_ONCE(monitoring_on);
}
/*
* monitoring_on general switcher.
*/
static ssize_t monitoring_on_read_data(struct file *filp, char __user *user_buf,
size_t count, loff_t *ppos)
{
const char *buff;
buff = rv_monitoring_on() ? "1\n" : "0\n";
return simple_read_from_buffer(user_buf, count, ppos, buff, strlen(buff) + 1);
}
static void turn_monitoring_off(void)
{
WRITE_ONCE(monitoring_on, false);
}
static void reset_all_monitors(void)
{
struct rv_monitor *mon;
list_for_each_entry(mon, &rv_monitors_list, list) {
if (mon->enabled && mon->reset)
mon->reset();
}
}
static void turn_monitoring_on(void)
{
WRITE_ONCE(monitoring_on, true);
}
static void turn_monitoring_on_with_reset(void)
{
lockdep_assert_held(&rv_interface_lock);
if (rv_monitoring_on())
return;
/*
* Monitors might be out of sync with the system if events were not
* processed because of !rv_monitoring_on().
*
* Reset all monitors, forcing a re-sync.
*/
reset_all_monitors();
turn_monitoring_on();
}
static ssize_t monitoring_on_write_data(struct file *filp, const char __user *user_buf,
size_t count, loff_t *ppos)
{
int retval;
bool val;
retval = kstrtobool_from_user(user_buf, count, &val);
if (retval)
return retval;
guard(mutex)(&rv_interface_lock);
if (val)
turn_monitoring_on_with_reset();
else
turn_monitoring_off();
/*
* Wait for the execution of all events to finish
* before returning to user-space.
*/
tracepoint_synchronize_unregister();
return count;
}
static const struct file_operations monitoring_on_fops = {
.open = simple_open,
.write = monitoring_on_write_data,
.read = monitoring_on_read_data,
};
static void destroy_monitor_dir(struct rv_monitor *mon)
{
rv_remove(mon->root_d);
}
/**
* rv_register_monitor - register a rv monitor.
* @monitor: The rv_monitor to be registered.
* @parent: The parent of the monitor to be registered, NULL if not nested.
*
* Returns 0 if successful, error otherwise.
*/
int rv_register_monitor(struct rv_monitor *monitor, struct rv_monitor *parent)
{
struct rv_monitor *r;
int retval = 0;
if (strlen(monitor->name) >= MAX_RV_MONITOR_NAME_SIZE) {
pr_info("Monitor %s has a name longer than %d\n", monitor->name,
MAX_RV_MONITOR_NAME_SIZE);
return -EINVAL;
}
guard(mutex)(&rv_interface_lock);
list_for_each_entry(r, &rv_monitors_list, list) {
if (strcmp(monitor->name, r->name) == 0) {
pr_info("Monitor %s is already registered\n", monitor->name);
return -EEXIST;
}
}
if (parent && rv_is_nested_monitor(parent)) {
pr_info("Parent monitor %s is already nested, cannot nest further\n",
parent->name);
return -EINVAL;
}
monitor->parent = parent;
retval = create_monitor_dir(monitor, parent);
if (retval)
return retval;
/* keep children close to the parent for easier visualisation */
if (parent)
list_add(&monitor->list, &parent->list);
else
list_add_tail(&monitor->list, &rv_monitors_list);
return 0;
}
/**
* rv_unregister_monitor - unregister a rv monitor.
* @monitor: The rv_monitor to be unregistered.
*
* Returns 0 if successful, error otherwise.
*/
int rv_unregister_monitor(struct rv_monitor *monitor)
{
guard(mutex)(&rv_interface_lock);
rv_disable_monitor(monitor);
list_del(&monitor->list);
destroy_monitor_dir(monitor);
return 0;
}
int __init rv_init_interface(void)
{
struct dentry *tmp;
int retval;
struct dentry *root_dir __free(rv_remove) = rv_create_dir("rv", NULL);
if (!root_dir)
return 1;
rv_root.monitors_dir = rv_create_dir("monitors", root_dir);
if (!rv_root.monitors_dir)
return 1;
tmp = rv_create_file("available_monitors", RV_MODE_READ, root_dir, NULL,
&available_monitors_ops);
if (!tmp)
return 1;
tmp = rv_create_file("enabled_monitors", RV_MODE_WRITE, root_dir, NULL,
&enabled_monitors_ops);
if (!tmp)
return 1;
tmp = rv_create_file("monitoring_on", RV_MODE_WRITE, root_dir, NULL,
&monitoring_on_fops);
if (!tmp)
return 1;
retval = init_rv_reactors(root_dir);
if (retval)
return 1;
turn_monitoring_on();
rv_root.root_dir = no_free_ptr(root_dir);
return 0;
}
+47
View File
@@ -0,0 +1,47 @@
/* SPDX-License-Identifier: GPL-2.0 */
#include <linux/mutex.h>
struct rv_interface {
struct dentry *root_dir;
struct dentry *monitors_dir;
};
#include "../trace.h"
#include <linux/tracefs.h>
#include <linux/rv.h>
#define RV_MODE_WRITE TRACE_MODE_WRITE
#define RV_MODE_READ TRACE_MODE_READ
#define rv_create_dir tracefs_create_dir
#define rv_create_file tracefs_create_file
#define rv_remove tracefs_remove
DEFINE_FREE(rv_remove, struct dentry *, if (_T) rv_remove(_T));
#define MAX_RV_MONITOR_NAME_SIZE 32
#define MAX_RV_REACTOR_NAME_SIZE 32
extern struct mutex rv_interface_lock;
extern struct list_head rv_monitors_list;
struct dentry *get_monitors_root(void);
int rv_disable_monitor(struct rv_monitor *mon);
int rv_enable_monitor(struct rv_monitor *mon);
bool rv_is_container_monitor(struct rv_monitor *mon);
bool rv_is_nested_monitor(struct rv_monitor *mon);
#ifdef CONFIG_RV_REACTORS
int reactor_populate_monitor(struct rv_monitor *mon, struct dentry *root);
int init_rv_reactors(struct dentry *root_dir);
#else
static inline int reactor_populate_monitor(struct rv_monitor *mon, struct dentry *root)
{
return 0;
}
static inline int init_rv_reactors(struct dentry *root_dir)
{
return 0;
}
#endif
+481
View File
@@ -0,0 +1,481 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2019-2022 Red Hat, Inc. Daniel Bristot de Oliveira <bristot@kernel.org>
*
* Runtime reactor interface.
*
* A runtime monitor can cause a reaction to the detection of an
* exception on the model's execution. By default, the monitors have
* tracing reactions, printing the monitor output via tracepoints.
* But other reactions can be added (on-demand) via this interface.
*
* == Registering reactors ==
*
* The struct rv_reactor defines a callback function to be executed
* in case of a model exception happens. The callback function
* receives a message to be (optionally) printed before executing
* the reaction.
*
* A RV reactor is registered via:
* int rv_register_reactor(struct rv_reactor *reactor)
* And unregistered via:
* int rv_unregister_reactor(struct rv_reactor *reactor)
*
* These functions are exported to modules, enabling reactors to be
* dynamically loaded.
*
* == User interface ==
*
* The user interface resembles the kernel tracing interface and
* presents these files:
*
* "available_reactors"
* - List the available reactors, one per line.
*
* For example:
* # cat available_reactors
* nop
* panic
* printk
*
* "reacting_on"
* - It is an on/off general switch for reactors, disabling
* all reactions.
*
* "monitors/MONITOR/reactors"
* - List available reactors, with the select reaction for the given
* MONITOR inside []. The default one is the nop (no operation)
* reactor.
* - Writing the name of an reactor enables it to the given
* MONITOR.
*
* For example:
* # cat monitors/wip/reactors
* [nop]
* panic
* printk
* # echo panic > monitors/wip/reactors
* # cat monitors/wip/reactors
* nop
* [panic]
* printk
*/
#include <linux/lockdep.h>
#include <linux/slab.h>
#include "rv.h"
/*
* Interface for the reactor register.
*/
static LIST_HEAD(rv_reactors_list);
static struct rv_reactor *get_reactor_rdef_by_name(char *name)
{
struct rv_reactor *r;
list_for_each_entry(r, &rv_reactors_list, list) {
if (strcmp(name, r->name) == 0)
return r;
}
return NULL;
}
/*
* Available reactors seq functions.
*/
static int reactors_show(struct seq_file *m, void *p)
{
struct rv_reactor *reactor = container_of(p, struct rv_reactor, list);
seq_printf(m, "%s\n", reactor->name);
return 0;
}
static void reactors_stop(struct seq_file *m, void *p)
{
mutex_unlock(&rv_interface_lock);
}
static void *reactors_start(struct seq_file *m, loff_t *pos)
{
mutex_lock(&rv_interface_lock);
return seq_list_start(&rv_reactors_list, *pos);
}
static void *reactors_next(struct seq_file *m, void *p, loff_t *pos)
{
return seq_list_next(p, &rv_reactors_list, pos);
}
/*
* available_reactors seq definition.
*/
static const struct seq_operations available_reactors_seq_ops = {
.start = reactors_start,
.next = reactors_next,
.stop = reactors_stop,
.show = reactors_show
};
/*
* available_reactors interface.
*/
static int available_reactors_open(struct inode *inode, struct file *file)
{
return seq_open(file, &available_reactors_seq_ops);
};
static const struct file_operations available_reactors_ops = {
.open = available_reactors_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release
};
/*
* Monitor's reactor file.
*/
static int monitor_reactor_show(struct seq_file *m, void *p)
{
struct rv_monitor *mon = m->private;
struct rv_reactor *reactor = container_of(p, struct rv_reactor, list);
if (mon->reactor == reactor)
seq_printf(m, "[%s]\n", reactor->name);
else
seq_printf(m, "%s\n", reactor->name);
return 0;
}
/*
* available_reactors seq definition.
*/
static const struct seq_operations monitor_reactors_seq_ops = {
.start = reactors_start,
.next = reactors_next,
.stop = reactors_stop,
.show = monitor_reactor_show
};
static void monitor_swap_reactors_single(struct rv_monitor *mon,
struct rv_reactor *reactor,
bool nested)
{
bool monitor_enabled;
/* nothing to do */
if (mon->reactor == reactor)
return;
monitor_enabled = mon->enabled;
if (monitor_enabled)
rv_disable_monitor(mon);
mon->reactor = reactor;
mon->react = reactor->react;
/* enable only once if iterating through a container */
if (monitor_enabled && !nested)
rv_enable_monitor(mon);
}
static void monitor_swap_reactors(struct rv_monitor *mon, struct rv_reactor *reactor)
{
struct rv_monitor *p = mon;
if (rv_is_container_monitor(mon))
list_for_each_entry_continue(p, &rv_monitors_list, list) {
if (p->parent != mon)
break;
monitor_swap_reactors_single(p, reactor, true);
}
/*
* This call enables and disables the monitor if they were active.
* In case of a container, we already disabled all and will enable all.
* All nested monitors are enabled also if they were off, we may refine
* this logic in the future.
*/
monitor_swap_reactors_single(mon, reactor, false);
}
static ssize_t
monitor_reactors_write(struct file *file, const char __user *user_buf,
size_t count, loff_t *ppos)
{
char buff[MAX_RV_REACTOR_NAME_SIZE + 2];
struct rv_monitor *mon;
struct rv_reactor *reactor;
struct seq_file *seq_f;
int retval = -EINVAL;
char *ptr;
int len;
if (count < 1 || count > MAX_RV_REACTOR_NAME_SIZE + 1)
return -EINVAL;
memset(buff, 0, sizeof(buff));
retval = simple_write_to_buffer(buff, sizeof(buff) - 1, ppos, user_buf, count);
if (retval < 0)
return -EFAULT;
ptr = strim(buff);
len = strlen(ptr);
if (!len)
return count;
/*
* See monitor_reactors_open()
*/
seq_f = file->private_data;
mon = seq_f->private;
guard(mutex)(&rv_interface_lock);
list_for_each_entry(reactor, &rv_reactors_list, list) {
if (strcmp(ptr, reactor->name) != 0)
continue;
monitor_swap_reactors(mon, reactor);
return count;
}
return -EINVAL;
}
/*
* available_reactors interface.
*/
static int monitor_reactors_open(struct inode *inode, struct file *file)
{
struct rv_monitor *mon = inode->i_private;
struct seq_file *seq_f;
int ret;
ret = seq_open(file, &monitor_reactors_seq_ops);
if (ret < 0)
return ret;
/*
* seq_open stores the seq_file on the file->private data.
*/
seq_f = file->private_data;
/*
* Copy the create file "private" data to the seq_file private data.
*/
seq_f->private = mon;
return 0;
};
static const struct file_operations monitor_reactors_ops = {
.open = monitor_reactors_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release,
.write = monitor_reactors_write
};
static int __rv_register_reactor(struct rv_reactor *reactor)
{
struct rv_reactor *r;
list_for_each_entry(r, &rv_reactors_list, list) {
if (strcmp(reactor->name, r->name) == 0) {
pr_info("Reactor %s is already registered\n", reactor->name);
return -EINVAL;
}
}
list_add_tail(&reactor->list, &rv_reactors_list);
return 0;
}
/**
* rv_register_reactor - register a rv reactor.
* @reactor: The rv_reactor to be registered.
*
* Returns 0 if successful, error otherwise.
*/
int rv_register_reactor(struct rv_reactor *reactor)
{
if (strlen(reactor->name) >= MAX_RV_REACTOR_NAME_SIZE) {
pr_info("Reactor %s has a name longer than %d\n",
reactor->name, MAX_RV_MONITOR_NAME_SIZE);
return -EINVAL;
}
guard(mutex)(&rv_interface_lock);
return __rv_register_reactor(reactor);
}
/**
* rv_unregister_reactor - unregister a rv reactor.
* @reactor: The rv_reactor to be unregistered.
*
* Returns 0 if successful, error otherwise.
*/
int rv_unregister_reactor(struct rv_reactor *reactor)
{
guard(mutex)(&rv_interface_lock);
list_del(&reactor->list);
return 0;
}
/*
* reacting_on interface.
*/
static bool __read_mostly reacting_on;
/**
* rv_reacting_on - checks if reacting is on
*
* Returns 1 if on, 0 otherwise.
*/
static bool rv_reacting_on(void)
{
/* Ensures that concurrent monitors read consistent reacting_on */
smp_rmb();
return READ_ONCE(reacting_on);
}
static ssize_t reacting_on_read_data(struct file *filp,
char __user *user_buf,
size_t count, loff_t *ppos)
{
char *buff;
buff = rv_reacting_on() ? "1\n" : "0\n";
return simple_read_from_buffer(user_buf, count, ppos, buff, strlen(buff)+1);
}
static void turn_reacting_off(void)
{
WRITE_ONCE(reacting_on, false);
/* Ensures that concurrent monitors read consistent reacting_on */
smp_wmb();
}
static void turn_reacting_on(void)
{
WRITE_ONCE(reacting_on, true);
/* Ensures that concurrent monitors read consistent reacting_on */
smp_wmb();
}
static ssize_t reacting_on_write_data(struct file *filp, const char __user *user_buf,
size_t count, loff_t *ppos)
{
int retval;
bool val;
retval = kstrtobool_from_user(user_buf, count, &val);
if (retval)
return retval;
guard(mutex)(&rv_interface_lock);
if (val)
turn_reacting_on();
else
turn_reacting_off();
/*
* Wait for the execution of all events to finish
* before returning to user-space.
*/
tracepoint_synchronize_unregister();
return count;
}
static const struct file_operations reacting_on_fops = {
.open = simple_open,
.write = reacting_on_write_data,
.read = reacting_on_read_data,
};
/**
* reactor_populate_monitor - creates per monitor reactors file
* @mon: The monitor.
* @root: The directory of the monitor.
*
* Returns 0 if successful, error otherwise.
*/
int reactor_populate_monitor(struct rv_monitor *mon, struct dentry *root)
{
struct dentry *tmp;
tmp = rv_create_file("reactors", RV_MODE_WRITE, root, mon, &monitor_reactors_ops);
if (!tmp)
return -ENOMEM;
/*
* Configure as the rv_nop reactor.
*/
mon->reactor = get_reactor_rdef_by_name("nop");
return 0;
}
/*
* Nop reactor register
*/
__printf(1, 0) static void rv_nop_reaction(const char *msg, va_list args)
{
}
static struct rv_reactor rv_nop = {
.name = "nop",
.description = "no-operation reactor: do nothing.",
.react = rv_nop_reaction
};
int init_rv_reactors(struct dentry *root_dir)
{
int retval;
struct dentry *available __free(rv_remove) =
rv_create_file("available_reactors", RV_MODE_READ, root_dir,
NULL, &available_reactors_ops);
struct dentry *reacting __free(rv_remove) =
rv_create_file("reacting_on", RV_MODE_WRITE, root_dir, NULL, &reacting_on_fops);
if (!reacting || !available)
return -ENOMEM;
retval = __rv_register_reactor(&rv_nop);
if (retval)
return retval;
turn_reacting_on();
retain_and_null_ptr(available);
retain_and_null_ptr(reacting);
return 0;
}
void rv_react(struct rv_monitor *monitor, const char *msg, ...)
{
static DEFINE_WAIT_OVERRIDE_MAP(rv_react_map, LD_WAIT_FREE);
va_list args;
if (!rv_reacting_on() || !monitor->react)
return;
va_start(args, msg);
lock_map_acquire_try(&rv_react_map);
monitor->react(msg, args);
lock_map_release(&rv_react_map);
va_end(args);
}
+277
View File
@@ -0,0 +1,277 @@
/* SPDX-License-Identifier: GPL-2.0 */
#undef TRACE_SYSTEM
#define TRACE_SYSTEM rv
#if !defined(_TRACE_RV_H) || defined(TRACE_HEADER_MULTI_READ)
#define _TRACE_RV_H
#include <linux/rv.h>
#include <linux/tracepoint.h>
#ifdef CONFIG_DA_MON_EVENTS_IMPLICIT
DECLARE_EVENT_CLASS(event_da_monitor,
TP_PROTO(char *state, char *event, char *next_state, bool final_state),
TP_ARGS(state, event, next_state, final_state),
TP_STRUCT__entry(
__string( state, state )
__string( event, event )
__string( next_state, next_state )
__field( bool, final_state )
),
TP_fast_assign(
__assign_str(state);
__assign_str(event);
__assign_str(next_state);
__entry->final_state = final_state;
),
TP_printk("%s x %s -> %s%s",
__get_str(state),
__get_str(event),
__get_str(next_state),
__entry->final_state ? " (final)" : "")
);
DECLARE_EVENT_CLASS(error_da_monitor,
TP_PROTO(char *state, char *event),
TP_ARGS(state, event),
TP_STRUCT__entry(
__string( state, state )
__string( event, event )
),
TP_fast_assign(
__assign_str(state);
__assign_str(event);
),
TP_printk("event %s not expected in the state %s",
__get_str(event),
__get_str(state))
);
#include <monitors/wip/wip_trace.h>
#include <monitors/sco/sco_trace.h>
#include <monitors/scpd/scpd_trace.h>
#include <monitors/snep/snep_trace.h>
#include <monitors/sts/sts_trace.h>
// Add new monitors based on CONFIG_DA_MON_EVENTS_IMPLICIT here
#ifdef CONFIG_HA_MON_EVENTS_IMPLICIT
/* For simplicity this class is marked as DA although relevant only for HA */
DECLARE_EVENT_CLASS(error_env_da_monitor,
TP_PROTO(char *state, char *event, char *env),
TP_ARGS(state, event, env),
TP_STRUCT__entry(
__string( state, state )
__string( event, event )
__string( env, env )
),
TP_fast_assign(
__assign_str(state);
__assign_str(event);
__assign_str(env);
),
TP_printk("event %s not expected in the state %s with env %s",
__get_str(event),
__get_str(state),
__get_str(env))
);
#include <monitors/opid/opid_trace.h>
// Add new monitors based on CONFIG_HA_MON_EVENTS_IMPLICIT here
#endif
#endif /* CONFIG_DA_MON_EVENTS_IMPLICIT */
#ifdef CONFIG_DA_MON_EVENTS_ID
DECLARE_EVENT_CLASS(event_da_monitor_id,
TP_PROTO(int id, char *state, char *event, char *next_state, bool final_state),
TP_ARGS(id, state, event, next_state, final_state),
TP_STRUCT__entry(
__field( int, id )
__string( state, state )
__string( event, event )
__string( next_state, next_state )
__field( bool, final_state )
),
TP_fast_assign(
__assign_str(state);
__assign_str(event);
__assign_str(next_state);
__entry->id = id;
__entry->final_state = final_state;
),
TP_printk("%d: %s x %s -> %s%s",
__entry->id,
__get_str(state),
__get_str(event),
__get_str(next_state),
__entry->final_state ? " (final)" : "")
);
DECLARE_EVENT_CLASS(error_da_monitor_id,
TP_PROTO(int id, char *state, char *event),
TP_ARGS(id, state, event),
TP_STRUCT__entry(
__field( int, id )
__string( state, state )
__string( event, event )
),
TP_fast_assign(
__assign_str(state);
__assign_str(event);
__entry->id = id;
),
TP_printk("%d: event %s not expected in the state %s",
__entry->id,
__get_str(event),
__get_str(state))
);
#include <monitors/wwnr/wwnr_trace.h>
#include <monitors/snroc/snroc_trace.h>
#include <monitors/nrp/nrp_trace.h>
#include <monitors/sssw/sssw_trace.h>
// Add new monitors based on CONFIG_DA_MON_EVENTS_ID here
#ifdef CONFIG_HA_MON_EVENTS_ID
/* For simplicity this class is marked as DA although relevant only for HA */
DECLARE_EVENT_CLASS(error_env_da_monitor_id,
TP_PROTO(int id, char *state, char *event, char *env),
TP_ARGS(id, state, event, env),
TP_STRUCT__entry(
__field( int, id )
__string( state, state )
__string( event, event )
__string( env, env )
),
TP_fast_assign(
__assign_str(state);
__assign_str(event);
__assign_str(env);
__entry->id = id;
),
TP_printk("%d: event %s not expected in the state %s with env %s",
__entry->id,
__get_str(event),
__get_str(state),
__get_str(env))
);
#include <monitors/stall/stall_trace.h>
#include <monitors/nomiss/nomiss_trace.h>
// Add new monitors based on CONFIG_HA_MON_EVENTS_ID here
#endif
#endif /* CONFIG_DA_MON_EVENTS_ID */
#ifdef CONFIG_LTL_MON_EVENTS_ID
DECLARE_EVENT_CLASS(event_ltl_monitor_id,
TP_PROTO(struct task_struct *task, char *states, char *atoms, char *next),
TP_ARGS(task, states, atoms, next),
TP_STRUCT__entry(
__string(comm, task->comm)
__field(pid_t, pid)
__string(states, states)
__string(atoms, atoms)
__string(next, next)
),
TP_fast_assign(
__assign_str(comm);
__entry->pid = task->pid;
__assign_str(states);
__assign_str(atoms);
__assign_str(next);
),
TP_printk("%s[%d]: (%s) x (%s) -> (%s)", __get_str(comm), __entry->pid,
__get_str(states), __get_str(atoms), __get_str(next))
);
DECLARE_EVENT_CLASS(error_ltl_monitor_id,
TP_PROTO(struct task_struct *task),
TP_ARGS(task),
TP_STRUCT__entry(
__string(comm, task->comm)
__field(pid_t, pid)
),
TP_fast_assign(
__assign_str(comm);
__entry->pid = task->pid;
),
TP_printk("%s[%d]: violation detected", __get_str(comm), __entry->pid)
);
#include <monitors/pagefault/pagefault_trace.h>
#include <monitors/sleep/sleep_trace.h>
// Add new monitors based on CONFIG_LTL_MON_EVENTS_ID here
#endif /* CONFIG_LTL_MON_EVENTS_ID */
#ifdef CONFIG_RV_MON_MAINTENANCE_EVENTS
/* Tracepoint useful for monitors development, currenly only used in DA */
TRACE_EVENT(rv_retries_error,
TP_PROTO(char *name, char *event),
TP_ARGS(name, event),
TP_STRUCT__entry(
__string( name, name )
__string( event, event )
),
TP_fast_assign(
__assign_str(name);
__assign_str(event);
),
TP_printk(__stringify(MAX_DA_RETRY_RACING_EVENTS)
" retries reached for event %s, resetting monitor %s",
__get_str(event), __get_str(name))
);
#endif /* CONFIG_RV_MON_MAINTENANCE_EVENTS */
#endif /* _TRACE_RV_H */
/* This part must be outside protection */
#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH .
#undef TRACE_INCLUDE_FILE
#define TRACE_INCLUDE_FILE rv_trace
#include <trace/define_trace.h>
@@ -0,0 +1,517 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2025 - Google LLC
* Author: Vincent Donnefort <vdonnefort@google.com>
*/
#include <linux/atomic.h>
#include <linux/simple_ring_buffer.h>
#include <asm/barrier.h>
#include <asm/local.h>
enum simple_rb_link_type {
SIMPLE_RB_LINK_NORMAL = 0,
SIMPLE_RB_LINK_HEAD = 1,
SIMPLE_RB_LINK_HEAD_MOVING
};
#define SIMPLE_RB_LINK_MASK ~(SIMPLE_RB_LINK_HEAD | SIMPLE_RB_LINK_HEAD_MOVING)
static void simple_bpage_set_head_link(struct simple_buffer_page *bpage)
{
unsigned long link = (unsigned long)bpage->link.next;
link &= SIMPLE_RB_LINK_MASK;
link |= SIMPLE_RB_LINK_HEAD;
/*
* Paired with simple_rb_find_head() to order access between the head
* link and overrun. It ensures we always report an up-to-date value
* after swapping the reader page.
*/
smp_store_release(&bpage->link.next, (struct list_head *)link);
}
static bool simple_bpage_unset_head_link(struct simple_buffer_page *bpage,
struct simple_buffer_page *dst,
enum simple_rb_link_type new_type)
{
unsigned long *link = (unsigned long *)(&bpage->link.next);
unsigned long old = (*link & SIMPLE_RB_LINK_MASK) | SIMPLE_RB_LINK_HEAD;
unsigned long new = (unsigned long)(&dst->link) | new_type;
return try_cmpxchg(link, &old, new);
}
static void simple_bpage_set_normal_link(struct simple_buffer_page *bpage)
{
unsigned long link = (unsigned long)bpage->link.next;
WRITE_ONCE(bpage->link.next, (struct list_head *)(link & SIMPLE_RB_LINK_MASK));
}
static struct simple_buffer_page *simple_bpage_from_link(struct list_head *link)
{
unsigned long ptr = (unsigned long)link & SIMPLE_RB_LINK_MASK;
return container_of((struct list_head *)ptr, struct simple_buffer_page, link);
}
static struct simple_buffer_page *simple_bpage_next_page(struct simple_buffer_page *bpage)
{
return simple_bpage_from_link(bpage->link.next);
}
static void simple_bpage_reset(struct simple_buffer_page *bpage)
{
bpage->write = 0;
bpage->entries = 0;
local_set(&bpage->page->commit, 0);
}
static void simple_bpage_init(struct simple_buffer_page *bpage, void *page)
{
INIT_LIST_HEAD(&bpage->link);
bpage->page = (struct buffer_data_page *)page;
simple_bpage_reset(bpage);
}
#define simple_rb_meta_inc(__meta, __inc) \
WRITE_ONCE((__meta), (__meta + __inc))
static bool simple_rb_loaded(struct simple_rb_per_cpu *cpu_buffer)
{
return !!cpu_buffer->bpages;
}
static int simple_rb_find_head(struct simple_rb_per_cpu *cpu_buffer)
{
int retry = cpu_buffer->nr_pages * 2;
struct simple_buffer_page *head;
head = cpu_buffer->head_page;
while (retry--) {
unsigned long link;
spin:
/* See smp_store_release in simple_bpage_set_head_link() */
link = (unsigned long)smp_load_acquire(&head->link.prev->next);
switch (link & ~SIMPLE_RB_LINK_MASK) {
/* Found the head */
case SIMPLE_RB_LINK_HEAD:
cpu_buffer->head_page = head;
return 0;
/* The writer caught the head, we can spin, that won't be long */
case SIMPLE_RB_LINK_HEAD_MOVING:
goto spin;
}
head = simple_bpage_next_page(head);
}
return -EBUSY;
}
/**
* simple_ring_buffer_swap_reader_page - Swap ring-buffer head with the reader
* @cpu_buffer: A simple_rb_per_cpu
*
* This function enables consuming reading. It ensures the current head page will not be overwritten
* and can be safely read.
*
* Returns 0 on success, -ENODEV if @cpu_buffer was unloaded or -EBUSY if we failed to catch the
* head page.
*/
int simple_ring_buffer_swap_reader_page(struct simple_rb_per_cpu *cpu_buffer)
{
struct simple_buffer_page *last, *head, *reader;
unsigned long overrun;
int retry = 8;
int ret;
if (!simple_rb_loaded(cpu_buffer))
return -ENODEV;
reader = cpu_buffer->reader_page;
do {
/* Run after the writer to find the head */
ret = simple_rb_find_head(cpu_buffer);
if (ret)
return ret;
head = cpu_buffer->head_page;
/* Connect the reader page around the header page */
reader->link.next = head->link.next;
reader->link.prev = head->link.prev;
/* The last page before the head */
last = simple_bpage_from_link(head->link.prev);
/* The reader page points to the new header page */
simple_bpage_set_head_link(reader);
overrun = cpu_buffer->meta->overrun;
} while (!simple_bpage_unset_head_link(last, reader, SIMPLE_RB_LINK_NORMAL) && retry--);
if (!retry)
return -EINVAL;
cpu_buffer->head_page = simple_bpage_from_link(reader->link.next);
cpu_buffer->head_page->link.prev = &reader->link;
cpu_buffer->reader_page = head;
cpu_buffer->meta->reader.lost_events = overrun - cpu_buffer->last_overrun;
cpu_buffer->meta->reader.id = cpu_buffer->reader_page->id;
cpu_buffer->last_overrun = overrun;
return 0;
}
EXPORT_SYMBOL_GPL(simple_ring_buffer_swap_reader_page);
static struct simple_buffer_page *simple_rb_move_tail(struct simple_rb_per_cpu *cpu_buffer)
{
struct simple_buffer_page *tail, *new_tail;
tail = cpu_buffer->tail_page;
new_tail = simple_bpage_next_page(tail);
if (simple_bpage_unset_head_link(tail, new_tail, SIMPLE_RB_LINK_HEAD_MOVING)) {
/*
* Oh no! we've caught the head. There is none anymore and
* swap_reader will spin until we set the new one. Overrun must
* be written first, to make sure we report the correct number
* of lost events.
*/
simple_rb_meta_inc(cpu_buffer->meta->overrun, new_tail->entries);
simple_rb_meta_inc(cpu_buffer->meta->pages_lost, 1);
simple_bpage_set_head_link(new_tail);
simple_bpage_set_normal_link(tail);
}
simple_bpage_reset(new_tail);
cpu_buffer->tail_page = new_tail;
simple_rb_meta_inc(cpu_buffer->meta->pages_touched, 1);
return new_tail;
}
static unsigned long rb_event_size(unsigned long length)
{
struct ring_buffer_event *event;
return length + RB_EVNT_HDR_SIZE + sizeof(event->array[0]);
}
static struct ring_buffer_event *
rb_event_add_ts_extend(struct ring_buffer_event *event, u64 delta)
{
event->type_len = RINGBUF_TYPE_TIME_EXTEND;
event->time_delta = delta & TS_MASK;
event->array[0] = delta >> TS_SHIFT;
return (struct ring_buffer_event *)((unsigned long)event + 8);
}
static struct ring_buffer_event *
simple_rb_reserve_next(struct simple_rb_per_cpu *cpu_buffer, unsigned long length, u64 timestamp)
{
unsigned long ts_ext_size = 0, event_size = rb_event_size(length);
struct simple_buffer_page *tail = cpu_buffer->tail_page;
struct ring_buffer_event *event;
u32 write, prev_write;
u64 time_delta;
time_delta = timestamp - cpu_buffer->write_stamp;
if (test_time_stamp(time_delta))
ts_ext_size = 8;
prev_write = tail->write;
write = prev_write + event_size + ts_ext_size;
if (unlikely(write > (PAGE_SIZE - BUF_PAGE_HDR_SIZE)))
tail = simple_rb_move_tail(cpu_buffer);
if (!tail->entries) {
tail->page->time_stamp = timestamp;
time_delta = 0;
ts_ext_size = 0;
write = event_size;
prev_write = 0;
}
tail->write = write;
tail->entries++;
cpu_buffer->write_stamp = timestamp;
event = (struct ring_buffer_event *)(tail->page->data + prev_write);
if (ts_ext_size) {
event = rb_event_add_ts_extend(event, time_delta);
time_delta = 0;
}
event->type_len = 0;
event->time_delta = time_delta;
event->array[0] = event_size - RB_EVNT_HDR_SIZE;
return event;
}
/**
* simple_ring_buffer_reserve - Reserve an entry in @cpu_buffer
* @cpu_buffer: A simple_rb_per_cpu
* @length: Size of the entry in bytes
* @timestamp: Timestamp of the entry
*
* Returns the address of the entry where to write data or NULL
*/
void *simple_ring_buffer_reserve(struct simple_rb_per_cpu *cpu_buffer, unsigned long length,
u64 timestamp)
{
struct ring_buffer_event *rb_event;
if (cmpxchg(&cpu_buffer->status, SIMPLE_RB_READY, SIMPLE_RB_WRITING) != SIMPLE_RB_READY)
return NULL;
rb_event = simple_rb_reserve_next(cpu_buffer, length, timestamp);
return &rb_event->array[1];
}
EXPORT_SYMBOL_GPL(simple_ring_buffer_reserve);
/**
* simple_ring_buffer_commit - Commit the entry reserved with simple_ring_buffer_reserve()
* @cpu_buffer: The simple_rb_per_cpu where the entry has been reserved
*/
void simple_ring_buffer_commit(struct simple_rb_per_cpu *cpu_buffer)
{
local_set(&cpu_buffer->tail_page->page->commit,
cpu_buffer->tail_page->write);
simple_rb_meta_inc(cpu_buffer->meta->entries, 1);
/*
* Paired with simple_rb_enable_tracing() to ensure data is
* written to the ring-buffer before teardown.
*/
smp_store_release(&cpu_buffer->status, SIMPLE_RB_READY);
}
EXPORT_SYMBOL_GPL(simple_ring_buffer_commit);
static u32 simple_rb_enable_tracing(struct simple_rb_per_cpu *cpu_buffer, bool enable)
{
u32 prev_status;
if (enable)
return cmpxchg(&cpu_buffer->status, SIMPLE_RB_UNAVAILABLE, SIMPLE_RB_READY);
/* Wait for the buffer to be released */
do {
prev_status = cmpxchg_acquire(&cpu_buffer->status,
SIMPLE_RB_READY,
SIMPLE_RB_UNAVAILABLE);
} while (prev_status == SIMPLE_RB_WRITING);
return prev_status;
}
/**
* simple_ring_buffer_reset - Reset @cpu_buffer
* @cpu_buffer: A simple_rb_per_cpu
*
* This will not clear the content of the data, only reset counters and pointers
*
* Returns 0 on success or -ENODEV if @cpu_buffer was unloaded.
*/
int simple_ring_buffer_reset(struct simple_rb_per_cpu *cpu_buffer)
{
struct simple_buffer_page *bpage;
u32 prev_status;
int ret;
if (!simple_rb_loaded(cpu_buffer))
return -ENODEV;
prev_status = simple_rb_enable_tracing(cpu_buffer, false);
ret = simple_rb_find_head(cpu_buffer);
if (ret)
return ret;
bpage = cpu_buffer->tail_page = cpu_buffer->head_page;
do {
simple_bpage_reset(bpage);
bpage = simple_bpage_next_page(bpage);
} while (bpage != cpu_buffer->head_page);
simple_bpage_reset(cpu_buffer->reader_page);
cpu_buffer->last_overrun = 0;
cpu_buffer->write_stamp = 0;
cpu_buffer->meta->reader.read = 0;
cpu_buffer->meta->reader.lost_events = 0;
cpu_buffer->meta->entries = 0;
cpu_buffer->meta->overrun = 0;
cpu_buffer->meta->read = 0;
cpu_buffer->meta->pages_lost = 0;
cpu_buffer->meta->pages_touched = 0;
if (prev_status == SIMPLE_RB_READY)
simple_rb_enable_tracing(cpu_buffer, true);
return 0;
}
EXPORT_SYMBOL_GPL(simple_ring_buffer_reset);
int simple_ring_buffer_init_mm(struct simple_rb_per_cpu *cpu_buffer,
struct simple_buffer_page *bpages,
const struct ring_buffer_desc *desc,
void *(*load_page)(unsigned long va),
void (*unload_page)(void *va))
{
struct simple_buffer_page *bpage = bpages;
int ret = 0;
void *page;
int i;
/* At least 1 reader page and two pages in the ring-buffer */
if (desc->nr_page_va < 3)
return -EINVAL;
memset(cpu_buffer, 0, sizeof(*cpu_buffer));
cpu_buffer->meta = load_page(desc->meta_va);
if (!cpu_buffer->meta)
return -EINVAL;
memset(cpu_buffer->meta, 0, sizeof(*cpu_buffer->meta));
cpu_buffer->meta->meta_page_size = PAGE_SIZE;
/* The reader page is not part of the ring initially */
page = load_page(desc->page_va[0]);
if (!page) {
unload_page(cpu_buffer->meta);
return -EINVAL;
}
simple_bpage_init(bpage, page);
bpage->id = 0;
cpu_buffer->nr_pages = 1;
cpu_buffer->reader_page = bpage;
cpu_buffer->tail_page = bpage + 1;
cpu_buffer->head_page = bpage + 1;
for (i = 1; i < desc->nr_page_va; i++) {
page = load_page(desc->page_va[i]);
if (!page) {
ret = -EINVAL;
break;
}
simple_bpage_init(++bpage, page);
bpage->link.next = &(bpage + 1)->link;
bpage->link.prev = &(bpage - 1)->link;
bpage->id = i;
cpu_buffer->nr_pages = i + 1;
}
if (ret) {
for (i--; i >= 0; i--)
unload_page(bpages[i].page);
unload_page(cpu_buffer->meta);
return ret;
}
cpu_buffer->meta->nr_subbufs = cpu_buffer->nr_pages;
/* Close the ring */
bpage->link.next = &cpu_buffer->tail_page->link;
cpu_buffer->tail_page->link.prev = &bpage->link;
/* The last init'ed page points to the head page */
simple_bpage_set_head_link(bpage);
cpu_buffer->bpages = bpages;
return 0;
}
static void *__load_page(unsigned long page)
{
return (void *)page;
}
static void __unload_page(void *page) { }
/**
* simple_ring_buffer_init - Init @cpu_buffer based on @desc
* @cpu_buffer: A simple_rb_per_cpu buffer to init, allocated by the caller.
* @bpages: Array of simple_buffer_pages, with as many elements as @desc->nr_page_va
* @desc: A ring_buffer_desc
*
* Returns 0 on success or -EINVAL if the content of @desc is invalid
*/
int simple_ring_buffer_init(struct simple_rb_per_cpu *cpu_buffer, struct simple_buffer_page *bpages,
const struct ring_buffer_desc *desc)
{
return simple_ring_buffer_init_mm(cpu_buffer, bpages, desc, __load_page, __unload_page);
}
EXPORT_SYMBOL_GPL(simple_ring_buffer_init);
void simple_ring_buffer_unload_mm(struct simple_rb_per_cpu *cpu_buffer,
void (*unload_page)(void *))
{
int p;
if (!simple_rb_loaded(cpu_buffer))
return;
simple_rb_enable_tracing(cpu_buffer, false);
unload_page(cpu_buffer->meta);
for (p = 0; p < cpu_buffer->nr_pages; p++)
unload_page(cpu_buffer->bpages[p].page);
cpu_buffer->bpages = NULL;
}
/**
* simple_ring_buffer_unload - Prepare @cpu_buffer for deletion
* @cpu_buffer: A simple_rb_per_cpu that will be deleted.
*/
void simple_ring_buffer_unload(struct simple_rb_per_cpu *cpu_buffer)
{
return simple_ring_buffer_unload_mm(cpu_buffer, __unload_page);
}
EXPORT_SYMBOL_GPL(simple_ring_buffer_unload);
/**
* simple_ring_buffer_enable_tracing - Enable or disable writing to @cpu_buffer
* @cpu_buffer: A simple_rb_per_cpu
* @enable: True to enable tracing, False to disable it
*
* Returns 0 on success or -ENODEV if @cpu_buffer was unloaded
*/
int simple_ring_buffer_enable_tracing(struct simple_rb_per_cpu *cpu_buffer, bool enable)
{
if (!simple_rb_loaded(cpu_buffer))
return -ENODEV;
simple_rb_enable_tracing(cpu_buffer, enable);
return 0;
}
EXPORT_SYMBOL_GPL(simple_ring_buffer_enable_tracing);
@@ -0,0 +1,536 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Test module for in-kernel synthetic event creation and generation.
*
* Copyright (C) 2019 Tom Zanussi <zanussi@kernel.org>
*/
#include <linux/module.h>
#include <linux/trace_events.h>
/*
* This module is a simple test of basic functionality for in-kernel
* synthetic event creation and generation, the first and second tests
* using synth_event_gen_cmd_start() and synth_event_add_field(), the
* third uses synth_event_create() to do it all at once with a static
* field array.
*
* Following that are a few examples using the created events to test
* various ways of tracing a synthetic event.
*
* To test, select CONFIG_SYNTH_EVENT_GEN_TEST and build the module.
* Then:
*
* # insmod kernel/trace/synth_event_gen_test.ko
* # cat /sys/kernel/tracing/trace
*
* You should see several events in the trace buffer -
* "create_synth_test", "empty_synth_test", and several instances of
* "gen_synth_test".
*
* To remove the events, remove the module:
*
* # rmmod synth_event_gen_test
*
*/
static struct trace_event_file *create_synth_test;
static struct trace_event_file *empty_synth_test;
static struct trace_event_file *gen_synth_test;
/*
* Test to make sure we can create a synthetic event, then add more
* fields.
*/
static int __init test_gen_synth_cmd(void)
{
struct dynevent_cmd cmd;
u64 vals[7];
char *buf;
int ret;
/* Create a buffer to hold the generated command */
buf = kzalloc(MAX_DYNEVENT_CMD_LEN, GFP_KERNEL);
if (!buf)
return -ENOMEM;
/* Before generating the command, initialize the cmd object */
synth_event_cmd_init(&cmd, buf, MAX_DYNEVENT_CMD_LEN);
/*
* Create the empty gen_synth_test synthetic event with the
* first 4 fields.
*/
ret = synth_event_gen_cmd_start(&cmd, "gen_synth_test", THIS_MODULE,
"pid_t", "next_pid_field",
"char[16]", "next_comm_field",
"u64", "ts_ns",
"u64", "ts_ms");
if (ret)
goto free;
/* Use synth_event_add_field to add the rest of the fields */
ret = synth_event_add_field(&cmd, "unsigned int", "cpu");
if (ret)
goto free;
ret = synth_event_add_field(&cmd, "char[64]", "my_string_field");
if (ret)
goto free;
ret = synth_event_add_field(&cmd, "int", "my_int_field");
if (ret)
goto free;
ret = synth_event_gen_cmd_end(&cmd);
if (ret)
goto free;
/*
* Now get the gen_synth_test event file. We need to prevent
* the instance and event from disappearing from underneath
* us, which trace_get_event_file() does (though in this case
* we're using the top-level instance which never goes away).
*/
gen_synth_test = trace_get_event_file(NULL, "synthetic",
"gen_synth_test");
if (IS_ERR(gen_synth_test)) {
ret = PTR_ERR(gen_synth_test);
goto delete;
}
/* Enable the event or you won't see anything */
ret = trace_array_set_clr_event(gen_synth_test->tr,
"synthetic", "gen_synth_test", true);
if (ret) {
trace_put_event_file(gen_synth_test);
goto delete;
}
/* Create some bogus values just for testing */
vals[0] = 777; /* next_pid_field */
vals[1] = (u64)(long)"hula hoops"; /* next_comm_field */
vals[2] = 1000000; /* ts_ns */
vals[3] = 1000; /* ts_ms */
vals[4] = raw_smp_processor_id(); /* cpu */
vals[5] = (u64)(long)"thneed"; /* my_string_field */
vals[6] = 598; /* my_int_field */
/* Now generate a gen_synth_test event */
ret = synth_event_trace_array(gen_synth_test, vals, ARRAY_SIZE(vals));
free:
kfree(buf);
return ret;
delete:
/* We got an error after creating the event, delete it */
synth_event_delete("gen_synth_test");
goto free;
}
/*
* Test to make sure we can create an initially empty synthetic event,
* then add all the fields.
*/
static int __init test_empty_synth_event(void)
{
struct dynevent_cmd cmd;
u64 vals[7];
char *buf;
int ret;
/* Create a buffer to hold the generated command */
buf = kzalloc(MAX_DYNEVENT_CMD_LEN, GFP_KERNEL);
if (!buf)
return -ENOMEM;
/* Before generating the command, initialize the cmd object */
synth_event_cmd_init(&cmd, buf, MAX_DYNEVENT_CMD_LEN);
/*
* Create the empty_synth_test synthetic event with no fields.
*/
ret = synth_event_gen_cmd_start(&cmd, "empty_synth_test", THIS_MODULE);
if (ret)
goto free;
/* Use synth_event_add_field to add all of the fields */
ret = synth_event_add_field(&cmd, "pid_t", "next_pid_field");
if (ret)
goto free;
ret = synth_event_add_field(&cmd, "char[16]", "next_comm_field");
if (ret)
goto free;
ret = synth_event_add_field(&cmd, "u64", "ts_ns");
if (ret)
goto free;
ret = synth_event_add_field(&cmd, "u64", "ts_ms");
if (ret)
goto free;
ret = synth_event_add_field(&cmd, "unsigned int", "cpu");
if (ret)
goto free;
ret = synth_event_add_field(&cmd, "char[64]", "my_string_field");
if (ret)
goto free;
ret = synth_event_add_field(&cmd, "int", "my_int_field");
if (ret)
goto free;
/* All fields have been added, close and register the synth event */
ret = synth_event_gen_cmd_end(&cmd);
if (ret)
goto free;
/*
* Now get the empty_synth_test event file. We need to
* prevent the instance and event from disappearing from
* underneath us, which trace_get_event_file() does (though in
* this case we're using the top-level instance which never
* goes away).
*/
empty_synth_test = trace_get_event_file(NULL, "synthetic",
"empty_synth_test");
if (IS_ERR(empty_synth_test)) {
ret = PTR_ERR(empty_synth_test);
goto delete;
}
/* Enable the event or you won't see anything */
ret = trace_array_set_clr_event(empty_synth_test->tr,
"synthetic", "empty_synth_test", true);
if (ret) {
trace_put_event_file(empty_synth_test);
goto delete;
}
/* Create some bogus values just for testing */
vals[0] = 777; /* next_pid_field */
vals[1] = (u64)(long)"tiddlywinks"; /* next_comm_field */
vals[2] = 1000000; /* ts_ns */
vals[3] = 1000; /* ts_ms */
vals[4] = raw_smp_processor_id(); /* cpu */
vals[5] = (u64)(long)"thneed_2.0"; /* my_string_field */
vals[6] = 399; /* my_int_field */
/* Now trace an empty_synth_test event */
ret = synth_event_trace_array(empty_synth_test, vals, ARRAY_SIZE(vals));
free:
kfree(buf);
return ret;
delete:
/* We got an error after creating the event, delete it */
synth_event_delete("empty_synth_test");
goto free;
}
static struct synth_field_desc create_synth_test_fields[] = {
{ .type = "pid_t", .name = "next_pid_field" },
{ .type = "char[16]", .name = "next_comm_field" },
{ .type = "u64", .name = "ts_ns" },
{ .type = "char[]", .name = "dynstring_field_1" },
{ .type = "u64", .name = "ts_ms" },
{ .type = "unsigned int", .name = "cpu" },
{ .type = "char[64]", .name = "my_string_field" },
{ .type = "char[]", .name = "dynstring_field_2" },
{ .type = "int", .name = "my_int_field" },
};
/*
* Test synthetic event creation all at once from array of field
* descriptors.
*/
static int __init test_create_synth_event(void)
{
u64 vals[9];
int ret;
/* Create the create_synth_test event with the fields above */
ret = synth_event_create("create_synth_test",
create_synth_test_fields,
ARRAY_SIZE(create_synth_test_fields),
THIS_MODULE);
if (ret)
goto out;
/*
* Now get the create_synth_test event file. We need to
* prevent the instance and event from disappearing from
* underneath us, which trace_get_event_file() does (though in
* this case we're using the top-level instance which never
* goes away).
*/
create_synth_test = trace_get_event_file(NULL, "synthetic",
"create_synth_test");
if (IS_ERR(create_synth_test)) {
ret = PTR_ERR(create_synth_test);
goto delete;
}
/* Enable the event or you won't see anything */
ret = trace_array_set_clr_event(create_synth_test->tr,
"synthetic", "create_synth_test", true);
if (ret) {
trace_put_event_file(create_synth_test);
goto delete;
}
/* Create some bogus values just for testing */
vals[0] = 777; /* next_pid_field */
vals[1] = (u64)(long)"tiddlywinks"; /* next_comm_field */
vals[2] = 1000000; /* ts_ns */
vals[3] = (u64)(long)"xrayspecs"; /* dynstring_field_1 */
vals[4] = 1000; /* ts_ms */
vals[5] = raw_smp_processor_id(); /* cpu */
vals[6] = (u64)(long)"thneed"; /* my_string_field */
vals[7] = (u64)(long)"kerplunk"; /* dynstring_field_2 */
vals[8] = 398; /* my_int_field */
/* Now generate a create_synth_test event */
ret = synth_event_trace_array(create_synth_test, vals, ARRAY_SIZE(vals));
out:
return ret;
delete:
/* We got an error after creating the event, delete it */
synth_event_delete("create_synth_test");
goto out;
}
/*
* Test tracing a synthetic event by reserving trace buffer space,
* then filling in fields one after another.
*/
static int __init test_add_next_synth_val(void)
{
struct synth_event_trace_state trace_state;
int ret;
/* Start by reserving space in the trace buffer */
ret = synth_event_trace_start(gen_synth_test, &trace_state);
if (ret)
return ret;
/* Write some bogus values into the trace buffer, one after another */
/* next_pid_field */
ret = synth_event_add_next_val(777, &trace_state);
if (ret)
goto out;
/* next_comm_field */
ret = synth_event_add_next_val((u64)(long)"slinky", &trace_state);
if (ret)
goto out;
/* ts_ns */
ret = synth_event_add_next_val(1000000, &trace_state);
if (ret)
goto out;
/* ts_ms */
ret = synth_event_add_next_val(1000, &trace_state);
if (ret)
goto out;
/* cpu */
ret = synth_event_add_next_val(raw_smp_processor_id(), &trace_state);
if (ret)
goto out;
/* my_string_field */
ret = synth_event_add_next_val((u64)(long)"thneed_2.01", &trace_state);
if (ret)
goto out;
/* my_int_field */
ret = synth_event_add_next_val(395, &trace_state);
out:
/* Finally, commit the event */
ret = synth_event_trace_end(&trace_state);
return ret;
}
/*
* Test tracing a synthetic event by reserving trace buffer space,
* then filling in fields using field names, which can be done in any
* order.
*/
static int __init test_add_synth_val(void)
{
struct synth_event_trace_state trace_state;
int ret;
/* Start by reserving space in the trace buffer */
ret = synth_event_trace_start(gen_synth_test, &trace_state);
if (ret)
return ret;
/* Write some bogus values into the trace buffer, using field names */
ret = synth_event_add_val("ts_ns", 1000000, &trace_state);
if (ret)
goto out;
ret = synth_event_add_val("ts_ms", 1000, &trace_state);
if (ret)
goto out;
ret = synth_event_add_val("cpu", raw_smp_processor_id(), &trace_state);
if (ret)
goto out;
ret = synth_event_add_val("next_pid_field", 777, &trace_state);
if (ret)
goto out;
ret = synth_event_add_val("next_comm_field", (u64)(long)"silly putty",
&trace_state);
if (ret)
goto out;
ret = synth_event_add_val("my_string_field", (u64)(long)"thneed_9",
&trace_state);
if (ret)
goto out;
ret = synth_event_add_val("my_int_field", 3999, &trace_state);
out:
/* Finally, commit the event */
ret = synth_event_trace_end(&trace_state);
return ret;
}
/*
* Test tracing a synthetic event all at once from array of values.
*/
static int __init test_trace_synth_event(void)
{
int ret;
/* Trace some bogus values just for testing */
ret = synth_event_trace(create_synth_test, 9, /* number of values */
(u64)444, /* next_pid_field */
(u64)(long)"clackers", /* next_comm_field */
(u64)1000000, /* ts_ns */
(u64)(long)"viewmaster",/* dynstring_field_1 */
(u64)1000, /* ts_ms */
(u64)raw_smp_processor_id(), /* cpu */
(u64)(long)"Thneed", /* my_string_field */
(u64)(long)"yoyos", /* dynstring_field_2 */
(u64)999); /* my_int_field */
return ret;
}
static int __init synth_event_gen_test_init(void)
{
int ret;
ret = test_gen_synth_cmd();
if (ret)
return ret;
ret = test_empty_synth_event();
if (ret) {
WARN_ON(trace_array_set_clr_event(gen_synth_test->tr,
"synthetic",
"gen_synth_test", false));
trace_put_event_file(gen_synth_test);
WARN_ON(synth_event_delete("gen_synth_test"));
goto out;
}
ret = test_create_synth_event();
if (ret) {
WARN_ON(trace_array_set_clr_event(gen_synth_test->tr,
"synthetic",
"gen_synth_test", false));
trace_put_event_file(gen_synth_test);
WARN_ON(synth_event_delete("gen_synth_test"));
WARN_ON(trace_array_set_clr_event(empty_synth_test->tr,
"synthetic",
"empty_synth_test", false));
trace_put_event_file(empty_synth_test);
WARN_ON(synth_event_delete("empty_synth_test"));
goto out;
}
ret = test_add_next_synth_val();
WARN_ON(ret);
ret = test_add_synth_val();
WARN_ON(ret);
ret = test_trace_synth_event();
WARN_ON(ret);
/* Disable when done */
trace_array_set_clr_event(gen_synth_test->tr,
"synthetic",
"gen_synth_test", false);
trace_array_set_clr_event(empty_synth_test->tr,
"synthetic",
"empty_synth_test", false);
trace_array_set_clr_event(create_synth_test->tr,
"synthetic",
"create_synth_test", false);
out:
return ret;
}
static void __exit synth_event_gen_test_exit(void)
{
/* Disable the event or you can't remove it */
WARN_ON(trace_array_set_clr_event(gen_synth_test->tr,
"synthetic",
"gen_synth_test", false));
/* Now give the file and instance back */
trace_put_event_file(gen_synth_test);
/* Now unregister and free the synthetic event */
WARN_ON(synth_event_delete("gen_synth_test"));
/* Disable the event or you can't remove it */
WARN_ON(trace_array_set_clr_event(empty_synth_test->tr,
"synthetic",
"empty_synth_test", false));
/* Now give the file and instance back */
trace_put_event_file(empty_synth_test);
/* Now unregister and free the synthetic event */
WARN_ON(synth_event_delete("empty_synth_test"));
/* Disable the event or you can't remove it */
WARN_ON(trace_array_set_clr_event(create_synth_test->tr,
"synthetic",
"create_synth_test", false));
/* Now give the file and instance back */
trace_put_event_file(create_synth_test);
/* Now unregister and free the synthetic event */
WARN_ON(synth_event_delete("create_synth_test"));
}
module_init(synth_event_gen_test_init)
module_exit(synth_event_gen_test_exit)
MODULE_AUTHOR("Tom Zanussi");
MODULE_DESCRIPTION("synthetic event generation test");
MODULE_LICENSE("GPL v2");
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+228
View File
@@ -0,0 +1,228 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/kthread.h>
#include <linux/trace_clock.h>
#define CREATE_TRACE_POINTS
#include "trace_benchmark.h"
static struct task_struct *bm_event_thread;
static char bm_str[BENCHMARK_EVENT_STRLEN] = "START";
static u64 bm_total;
static u64 bm_totalsq;
static u64 bm_last;
static u64 bm_max;
static u64 bm_min;
static u64 bm_first;
static u64 bm_cnt;
static u64 bm_stddev;
static unsigned int bm_avg;
static unsigned int bm_std;
static bool ok_to_run;
/*
* This gets called in a loop recording the time it took to write
* the tracepoint. What it writes is the time statistics of the last
* tracepoint write. As there is nothing to write the first time
* it simply writes "START". As the first write is cold cache and
* the rest is hot, we save off that time in bm_first and it is
* reported as "first", which is shown in the second write to the
* tracepoint. The "first" field is written within the statics from
* then on but never changes.
*/
static void trace_do_benchmark(void)
{
u64 start;
u64 stop;
u64 delta;
u64 stddev;
u64 seed;
u64 last_seed;
unsigned int avg;
unsigned int std = 0;
/* Only run if the tracepoint is actually active */
if (!trace_benchmark_event_enabled() || !tracing_is_on())
return;
local_irq_disable();
start = trace_clock_local();
trace_benchmark_event(bm_str, bm_last);
stop = trace_clock_local();
local_irq_enable();
bm_cnt++;
delta = stop - start;
/*
* The first read is cold cached, keep it separate from the
* other calculations.
*/
if (bm_cnt == 1) {
bm_first = delta;
scnprintf(bm_str, BENCHMARK_EVENT_STRLEN,
"first=%llu [COLD CACHED]", bm_first);
return;
}
bm_last = delta;
if (delta > bm_max)
bm_max = delta;
if (!bm_min || delta < bm_min)
bm_min = delta;
/*
* When bm_cnt is greater than UINT_MAX, it breaks the statistics
* accounting. Freeze the statistics when that happens.
* We should have enough data for the avg and stddev anyway.
*/
if (bm_cnt > UINT_MAX) {
scnprintf(bm_str, BENCHMARK_EVENT_STRLEN,
"last=%llu first=%llu max=%llu min=%llu ** avg=%u std=%d std^2=%lld",
bm_last, bm_first, bm_max, bm_min, bm_avg, bm_std, bm_stddev);
return;
}
bm_total += delta;
bm_totalsq += delta * delta;
if (bm_cnt > 1) {
/*
* Apply Welford's method to calculate standard deviation:
* s^2 = 1 / (n * (n-1)) * (n * \Sum (x_i)^2 - (\Sum x_i)^2)
*/
stddev = (u64)bm_cnt * bm_totalsq - bm_total * bm_total;
do_div(stddev, (u32)bm_cnt);
do_div(stddev, (u32)bm_cnt - 1);
} else
stddev = 0;
delta = bm_total;
do_div(delta, (u32)bm_cnt);
avg = delta;
if (stddev > 0) {
int i = 0;
/*
* stddev is the square of standard deviation but
* we want the actually number. Use the average
* as our seed to find the std.
*
* The next try is:
* x = (x + N/x) / 2
*
* Where N is the squared number to find the square
* root of.
*/
seed = avg;
do {
last_seed = seed;
seed = stddev;
if (!last_seed)
break;
seed = div64_u64(seed, last_seed);
seed += last_seed;
do_div(seed, 2);
} while (i++ < 10 && last_seed != seed);
std = seed;
}
scnprintf(bm_str, BENCHMARK_EVENT_STRLEN,
"last=%llu first=%llu max=%llu min=%llu avg=%u std=%d std^2=%lld",
bm_last, bm_first, bm_max, bm_min, avg, std, stddev);
bm_std = std;
bm_avg = avg;
bm_stddev = stddev;
}
static int benchmark_event_kthread(void *arg)
{
/* sleep a bit to make sure the tracepoint gets activated */
msleep(100);
while (!kthread_should_stop()) {
trace_do_benchmark();
/*
* We don't go to sleep, but let others run as well.
* This is basically a "yield()" to let any task that
* wants to run, schedule in, but if the CPU is idle,
* we'll keep burning cycles.
*
* Note the tasks_rcu_qs() version of cond_resched() will
* notify synchronize_rcu_tasks() that this thread has
* passed a quiescent state for rcu_tasks. Otherwise
* this thread will never voluntarily schedule which would
* block synchronize_rcu_tasks() indefinitely.
*/
cond_resched_tasks_rcu_qs();
}
return 0;
}
/*
* When the benchmark tracepoint is enabled, it calls this
* function and the thread that calls the tracepoint is created.
*/
int trace_benchmark_reg(void)
{
if (!ok_to_run) {
pr_warn("trace benchmark cannot be started via kernel command line\n");
return -EBUSY;
}
bm_event_thread = kthread_run(benchmark_event_kthread,
NULL, "event_benchmark");
if (IS_ERR(bm_event_thread)) {
pr_warn("trace benchmark failed to create kernel thread\n");
return PTR_ERR(bm_event_thread);
}
return 0;
}
/*
* When the benchmark tracepoint is disabled, it calls this
* function and the thread that calls the tracepoint is deleted
* and all the numbers are reset.
*/
void trace_benchmark_unreg(void)
{
if (!bm_event_thread)
return;
kthread_stop(bm_event_thread);
bm_event_thread = NULL;
strcpy(bm_str, "START");
bm_total = 0;
bm_totalsq = 0;
bm_last = 0;
bm_max = 0;
bm_min = 0;
bm_cnt = 0;
/* These don't need to be reset but reset them anyway */
bm_first = 0;
bm_std = 0;
bm_avg = 0;
bm_stddev = 0;
}
static __init int ok_to_run_trace_benchmark(void)
{
ok_to_run = true;
return 0;
}
early_initcall(ok_to_run_trace_benchmark);
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: GPL-2.0
#undef TRACE_SYSTEM
#define TRACE_SYSTEM benchmark
#if !defined(_TRACE_BENCHMARK_H) || defined(TRACE_HEADER_MULTI_READ)
#define _TRACE_BENCHMARK_H
#include <linux/tracepoint.h>
extern int trace_benchmark_reg(void);
extern void trace_benchmark_unreg(void);
#define BENCHMARK_EVENT_STRLEN 128
TRACE_EVENT_FN(benchmark_event,
TP_PROTO(const char *str, u64 delta),
TP_ARGS(str, delta),
TP_STRUCT__entry(
__array( char, str, BENCHMARK_EVENT_STRLEN )
__field( u64, delta)
),
TP_fast_assign(
memcpy(__entry->str, str, BENCHMARK_EVENT_STRLEN);
__entry->delta = delta;
),
TP_printk("%s delta=%llu", __entry->str, __entry->delta),
trace_benchmark_reg, trace_benchmark_unreg
);
#endif /* _TRACE_BENCHMARK_H */
#undef TRACE_INCLUDE_FILE
#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH .
#define TRACE_INCLUDE_FILE trace_benchmark
/* This part must be outside protection */
#include <trace/define_trace.h>

Some files were not shown because too many files have changed in this diff Show More