279 lines
6.6 KiB
C
279 lines
6.6 KiB
C
/*
|
|
* trace_events_trigger - trace event triggers
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
*
|
|
* Copyright (C) 2013 Tom Zanussi <tom.zanussi@linux.intel.com>
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/ctype.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include "trace.h"
|
|
|
|
static LIST_HEAD(trigger_commands);
|
|
static DEFINE_MUTEX(trigger_cmd_mutex);
|
|
|
|
/**
|
|
* event_triggers_call - Call triggers associated with a trace event
|
|
* @file: The ftrace_event_file associated with the event
|
|
*
|
|
* For each trigger associated with an event, invoke the trigger
|
|
* function registered with the associated trigger command.
|
|
*
|
|
* Called from tracepoint handlers (with rcu_read_lock_sched() held).
|
|
*
|
|
* Return: an enum event_trigger_type value containing a set bit for
|
|
* any trigger that should be deferred, ETT_NONE if nothing to defer.
|
|
*/
|
|
void event_triggers_call(struct ftrace_event_file *file)
|
|
{
|
|
struct event_trigger_data *data;
|
|
|
|
if (list_empty(&file->triggers))
|
|
return;
|
|
|
|
list_for_each_entry_rcu(data, &file->triggers, list)
|
|
data->ops->func(data);
|
|
}
|
|
EXPORT_SYMBOL_GPL(event_triggers_call);
|
|
|
|
static void *trigger_next(struct seq_file *m, void *t, loff_t *pos)
|
|
{
|
|
struct ftrace_event_file *event_file = event_file_data(m->private);
|
|
|
|
return seq_list_next(t, &event_file->triggers, pos);
|
|
}
|
|
|
|
static void *trigger_start(struct seq_file *m, loff_t *pos)
|
|
{
|
|
struct ftrace_event_file *event_file;
|
|
|
|
/* ->stop() is called even if ->start() fails */
|
|
mutex_lock(&event_mutex);
|
|
event_file = event_file_data(m->private);
|
|
if (unlikely(!event_file))
|
|
return ERR_PTR(-ENODEV);
|
|
|
|
return seq_list_start(&event_file->triggers, *pos);
|
|
}
|
|
|
|
static void trigger_stop(struct seq_file *m, void *t)
|
|
{
|
|
mutex_unlock(&event_mutex);
|
|
}
|
|
|
|
static int trigger_show(struct seq_file *m, void *v)
|
|
{
|
|
struct event_trigger_data *data;
|
|
|
|
data = list_entry(v, struct event_trigger_data, list);
|
|
data->ops->print(m, data->ops, data);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct seq_operations event_triggers_seq_ops = {
|
|
.start = trigger_start,
|
|
.next = trigger_next,
|
|
.stop = trigger_stop,
|
|
.show = trigger_show,
|
|
};
|
|
|
|
static int event_trigger_regex_open(struct inode *inode, struct file *file)
|
|
{
|
|
int ret = 0;
|
|
|
|
mutex_lock(&event_mutex);
|
|
|
|
if (unlikely(!event_file_data(file))) {
|
|
mutex_unlock(&event_mutex);
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (file->f_mode & FMODE_READ) {
|
|
ret = seq_open(file, &event_triggers_seq_ops);
|
|
if (!ret) {
|
|
struct seq_file *m = file->private_data;
|
|
m->private = file;
|
|
}
|
|
}
|
|
|
|
mutex_unlock(&event_mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int trigger_process_regex(struct ftrace_event_file *file, char *buff)
|
|
{
|
|
char *command, *next = buff;
|
|
struct event_command *p;
|
|
int ret = -EINVAL;
|
|
|
|
command = strsep(&next, ": \t");
|
|
command = (command[0] != '!') ? command : command + 1;
|
|
|
|
mutex_lock(&trigger_cmd_mutex);
|
|
list_for_each_entry(p, &trigger_commands, list) {
|
|
if (strcmp(p->name, command) == 0) {
|
|
ret = p->func(p, file, buff, command, next);
|
|
goto out_unlock;
|
|
}
|
|
}
|
|
out_unlock:
|
|
mutex_unlock(&trigger_cmd_mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t event_trigger_regex_write(struct file *file,
|
|
const char __user *ubuf,
|
|
size_t cnt, loff_t *ppos)
|
|
{
|
|
struct ftrace_event_file *event_file;
|
|
ssize_t ret;
|
|
char *buf;
|
|
|
|
if (!cnt)
|
|
return 0;
|
|
|
|
if (cnt >= PAGE_SIZE)
|
|
return -EINVAL;
|
|
|
|
buf = (char *)__get_free_page(GFP_TEMPORARY);
|
|
if (!buf)
|
|
return -ENOMEM;
|
|
|
|
if (copy_from_user(buf, ubuf, cnt)) {
|
|
free_page((unsigned long)buf);
|
|
return -EFAULT;
|
|
}
|
|
buf[cnt] = '\0';
|
|
strim(buf);
|
|
|
|
mutex_lock(&event_mutex);
|
|
event_file = event_file_data(file);
|
|
if (unlikely(!event_file)) {
|
|
mutex_unlock(&event_mutex);
|
|
free_page((unsigned long)buf);
|
|
return -ENODEV;
|
|
}
|
|
ret = trigger_process_regex(event_file, buf);
|
|
mutex_unlock(&event_mutex);
|
|
|
|
free_page((unsigned long)buf);
|
|
if (ret < 0)
|
|
goto out;
|
|
|
|
*ppos += cnt;
|
|
ret = cnt;
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static int event_trigger_regex_release(struct inode *inode, struct file *file)
|
|
{
|
|
mutex_lock(&event_mutex);
|
|
|
|
if (file->f_mode & FMODE_READ)
|
|
seq_release(inode, file);
|
|
|
|
mutex_unlock(&event_mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t
|
|
event_trigger_write(struct file *filp, const char __user *ubuf,
|
|
size_t cnt, loff_t *ppos)
|
|
{
|
|
return event_trigger_regex_write(filp, ubuf, cnt, ppos);
|
|
}
|
|
|
|
static int
|
|
event_trigger_open(struct inode *inode, struct file *filp)
|
|
{
|
|
return event_trigger_regex_open(inode, filp);
|
|
}
|
|
|
|
static int
|
|
event_trigger_release(struct inode *inode, struct file *file)
|
|
{
|
|
return event_trigger_regex_release(inode, file);
|
|
}
|
|
|
|
const struct file_operations event_trigger_fops = {
|
|
.open = event_trigger_open,
|
|
.read = seq_read,
|
|
.write = event_trigger_write,
|
|
.llseek = ftrace_filter_lseek,
|
|
.release = event_trigger_release,
|
|
};
|
|
|
|
static int trace_event_trigger_enable_disable(struct ftrace_event_file *file,
|
|
int trigger_enable)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (trigger_enable) {
|
|
if (atomic_inc_return(&file->tm_ref) > 1)
|
|
return ret;
|
|
set_bit(FTRACE_EVENT_FL_TRIGGER_MODE_BIT, &file->flags);
|
|
ret = trace_event_enable_disable(file, 1, 1);
|
|
} else {
|
|
if (atomic_dec_return(&file->tm_ref) > 0)
|
|
return ret;
|
|
clear_bit(FTRACE_EVENT_FL_TRIGGER_MODE_BIT, &file->flags);
|
|
ret = trace_event_enable_disable(file, 0, 1);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* clear_event_triggers - Clear all triggers associated with a trace array
|
|
* @tr: The trace array to clear
|
|
*
|
|
* For each trigger, the triggering event has its tm_ref decremented
|
|
* via trace_event_trigger_enable_disable(), and any associated event
|
|
* (in the case of enable/disable_event triggers) will have its sm_ref
|
|
* decremented via free()->trace_event_enable_disable(). That
|
|
* combination effectively reverses the soft-mode/trigger state added
|
|
* by trigger registration.
|
|
*
|
|
* Must be called with event_mutex held.
|
|
*/
|
|
void
|
|
clear_event_triggers(struct trace_array *tr)
|
|
{
|
|
struct ftrace_event_file *file;
|
|
|
|
list_for_each_entry(file, &tr->events, list) {
|
|
struct event_trigger_data *data;
|
|
list_for_each_entry_rcu(data, &file->triggers, list) {
|
|
trace_event_trigger_enable_disable(file, 0);
|
|
if (data->ops->free)
|
|
data->ops->free(data->ops, data);
|
|
}
|
|
}
|
|
}
|
|
|
|
__init int register_trigger_cmds(void)
|
|
{
|
|
return 0;
|
|
}
|