summaryrefslogtreecommitdiff
path: root/kernel/trace/trace_events_trigger.c
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/trace/trace_events_trigger.c')
-rw-r--r--kernel/trace/trace_events_trigger.c186
1 files changed, 122 insertions, 64 deletions
diff --git a/kernel/trace/trace_events_trigger.c b/kernel/trace/trace_events_trigger.c
index 06b75bcfc7b8..4d2fde996c0f 100644
--- a/kernel/trace/trace_events_trigger.c
+++ b/kernel/trace/trace_events_trigger.c
@@ -22,6 +22,46 @@ static struct task_struct *trigger_kthread;
static struct llist_head trigger_data_free_list;
static DEFINE_MUTEX(trigger_data_kthread_mutex);
+static int trigger_kthread_fn(void *ignore);
+
+static void trigger_create_kthread_locked(void)
+{
+ lockdep_assert_held(&trigger_data_kthread_mutex);
+
+ if (!trigger_kthread) {
+ struct task_struct *kthread;
+
+ kthread = kthread_create(trigger_kthread_fn, NULL,
+ "trigger_data_free");
+ if (!IS_ERR(kthread))
+ WRITE_ONCE(trigger_kthread, kthread);
+ }
+}
+
+static void trigger_data_free_one(struct event_trigger_data *data)
+{
+ if (data->private_data_free)
+ data->private_data_free(data);
+ kfree(data);
+}
+
+static void trigger_data_free_queued_locked(void)
+{
+ struct event_trigger_data *data, *tmp;
+ struct llist_node *llnodes;
+
+ lockdep_assert_held(&trigger_data_kthread_mutex);
+
+ llnodes = llist_del_all(&trigger_data_free_list);
+ if (!llnodes)
+ return;
+
+ tracepoint_synchronize_unregister();
+
+ llist_for_each_entry_safe(data, tmp, llnodes, llist)
+ trigger_data_free_one(data);
+}
+
/* Bulk garbage collection of event_trigger_data elements */
static int trigger_kthread_fn(void *ignore)
{
@@ -42,7 +82,7 @@ static int trigger_kthread_fn(void *ignore)
tracepoint_synchronize_unregister();
llist_for_each_entry_safe(data, tmp, llnodes, llist)
- kfree(data);
+ trigger_data_free_one(data);
}
return 0;
@@ -50,33 +90,56 @@ static int trigger_kthread_fn(void *ignore)
void trigger_data_free(struct event_trigger_data *data)
{
+ if (!data)
+ return;
+
if (data->cmd_ops->set_filter)
data->cmd_ops->set_filter(NULL, data, NULL);
+ /*
+ * Boot-time trigger registration can fail before kthread creation
+ * works. Keep the deferred-free semantics during boot and let late
+ * init start the kthread to drain the list.
+ */
+ if (system_state == SYSTEM_BOOTING && !trigger_kthread) {
+ llist_add(&data->llist, &trigger_data_free_list);
+ return;
+ }
+
if (unlikely(!trigger_kthread)) {
guard(mutex)(&trigger_data_kthread_mutex);
+
+ trigger_create_kthread_locked();
/* Check again after taking mutex */
if (!trigger_kthread) {
- struct task_struct *kthread;
-
- kthread = kthread_create(trigger_kthread_fn, NULL,
- "trigger_data_free");
- if (!IS_ERR(kthread))
- WRITE_ONCE(trigger_kthread, kthread);
+ llist_add(&data->llist, &trigger_data_free_list);
+ /* Drain the queued frees synchronously if creation failed. */
+ trigger_data_free_queued_locked();
+ return;
}
}
- if (!trigger_kthread) {
- /* Do it the slow way */
- tracepoint_synchronize_unregister();
- kfree(data);
- return;
- }
-
llist_add(&data->llist, &trigger_data_free_list);
wake_up_process(trigger_kthread);
}
+static int __init trigger_data_free_init(void)
+{
+ guard(mutex)(&trigger_data_kthread_mutex);
+
+ if (llist_empty(&trigger_data_free_list))
+ return 0;
+
+ trigger_create_kthread_locked();
+ if (trigger_kthread)
+ wake_up_process(trigger_kthread);
+ else
+ trigger_data_free_queued_locked();
+
+ return 0;
+}
+late_initcall(trigger_data_free_init);
+
static inline void data_ops_trigger(struct event_trigger_data *data,
struct trace_buffer *buffer, void *rec,
struct ring_buffer_event *event)
@@ -914,7 +977,7 @@ struct event_trigger_data *trigger_data_alloc(struct event_command *cmd_ops,
{
struct event_trigger_data *trigger_data;
- trigger_data = kzalloc(sizeof(*trigger_data), GFP_KERNEL);
+ trigger_data = kzalloc_obj(*trigger_data);
if (!trigger_data)
return NULL;
@@ -1347,18 +1410,13 @@ traceon_trigger(struct event_trigger_data *data,
{
struct trace_event_file *file = data->private_data;
- if (file) {
- if (tracer_tracing_is_on(file->tr))
- return;
-
- tracer_tracing_on(file->tr);
+ if (WARN_ON_ONCE(!file))
return;
- }
- if (tracing_is_on())
+ if (tracer_tracing_is_on(file->tr))
return;
- tracing_on();
+ tracer_tracing_on(file->tr);
}
static bool
@@ -1368,13 +1426,11 @@ traceon_count_func(struct event_trigger_data *data,
{
struct trace_event_file *file = data->private_data;
- if (file) {
- if (tracer_tracing_is_on(file->tr))
- return false;
- } else {
- if (tracing_is_on())
- return false;
- }
+ if (WARN_ON_ONCE(!file))
+ return false;
+
+ if (tracer_tracing_is_on(file->tr))
+ return false;
if (!data->count)
return false;
@@ -1392,18 +1448,13 @@ traceoff_trigger(struct event_trigger_data *data,
{
struct trace_event_file *file = data->private_data;
- if (file) {
- if (!tracer_tracing_is_on(file->tr))
- return;
-
- tracer_tracing_off(file->tr);
+ if (WARN_ON_ONCE(!file))
return;
- }
- if (!tracing_is_on())
+ if (!tracer_tracing_is_on(file->tr))
return;
- tracing_off();
+ tracer_tracing_off(file->tr);
}
static bool
@@ -1413,13 +1464,11 @@ traceoff_count_func(struct event_trigger_data *data,
{
struct trace_event_file *file = data->private_data;
- if (file) {
- if (!tracer_tracing_is_on(file->tr))
- return false;
- } else {
- if (!tracing_is_on())
- return false;
- }
+ if (WARN_ON_ONCE(!file))
+ return false;
+
+ if (!tracer_tracing_is_on(file->tr))
+ return false;
if (!data->count)
return false;
@@ -1481,10 +1530,10 @@ snapshot_trigger(struct event_trigger_data *data,
{
struct trace_event_file *file = data->private_data;
- if (file)
- tracing_snapshot_instance(file->tr);
- else
- tracing_snapshot();
+ if (WARN_ON_ONCE(!file))
+ return;
+
+ tracing_snapshot_instance(file->tr);
}
static int
@@ -1570,10 +1619,10 @@ stacktrace_trigger(struct event_trigger_data *data,
{
struct trace_event_file *file = data->private_data;
- if (file)
- __trace_stack(file->tr, tracing_gen_ctx_dec(), STACK_SKIP);
- else
- trace_dump_stack(STACK_SKIP);
+ if (WARN_ON_ONCE(!file))
+ return;
+
+ __trace_stack(file->tr, tracing_gen_ctx_dec(), STACK_SKIP);
}
static int
@@ -1675,6 +1724,15 @@ int event_enable_trigger_print(struct seq_file *m,
return 0;
}
+static void enable_trigger_private_data_free(struct event_trigger_data *data)
+{
+ struct enable_trigger_data *enable_data = data->private_data;
+
+ /* The file may already be freed here, only the call is kept alive */
+ trace_event_put_ref(enable_data->call);
+ kfree(enable_data);
+}
+
void event_enable_trigger_free(struct event_trigger_data *data)
{
struct enable_trigger_data *enable_data = data->private_data;
@@ -1686,9 +1744,8 @@ void event_enable_trigger_free(struct event_trigger_data *data)
if (!data->ref) {
/* Remove the SOFT_MODE flag */
trace_event_enable_disable(enable_data->file, 0, 1);
- trace_event_put_ref(enable_data->file->event_call);
+ data->private_data_free = enable_trigger_private_data_free;
trigger_data_free(data);
- kfree(enable_data);
}
}
@@ -1697,7 +1754,7 @@ int event_enable_trigger_parse(struct event_command *cmd_ops,
char *glob, char *cmd, char *param_and_filter)
{
struct trace_event_file *event_enable_file;
- struct enable_trigger_data *enable_data;
+ struct enable_trigger_data *enable_data __free(kfree) = NULL;
struct event_trigger_data *trigger_data;
struct trace_array *tr = file->tr;
char *param, *filter;
@@ -1738,26 +1795,23 @@ int event_enable_trigger_parse(struct event_command *cmd_ops,
#endif
ret = -ENOMEM;
- enable_data = kzalloc(sizeof(*enable_data), GFP_KERNEL);
+ enable_data = kzalloc_obj(*enable_data);
if (!enable_data)
return ret;
enable_data->hist = hist;
enable_data->enable = enable;
enable_data->file = event_enable_file;
+ enable_data->call = event_enable_file->event_call;
trigger_data = trigger_data_alloc(cmd_ops, cmd, param, enable_data);
- if (!trigger_data) {
- kfree(enable_data);
+ if (!trigger_data)
return ret;
- }
if (remove) {
event_trigger_unregister(cmd_ops, file, glob+1, trigger_data);
kfree(trigger_data);
- kfree(enable_data);
- ret = 0;
- return ret;
+ return 0;
}
/* Up the trigger_data count to make sure nothing frees it on failure */
@@ -1786,7 +1840,12 @@ int event_enable_trigger_parse(struct event_command *cmd_ops,
if (ret)
goto out_disable;
+ /* It's now safe to free the reference taken earlier */
event_trigger_free(trigger_data);
+
+ /* The enabled_data is assigned to trigger_data->private_data */
+ retain_and_null_ptr(enable_data);
+
return ret;
out_disable:
trace_event_enable_disable(event_enable_file, 0, 1);
@@ -1795,7 +1854,6 @@ int event_enable_trigger_parse(struct event_command *cmd_ops,
out_free:
event_trigger_reset_filter(cmd_ops, trigger_data);
event_trigger_free(trigger_data);
- kfree(enable_data);
return ret;
}