blob: 60a6a6d66dc0d46c9abc2a826820e7d2fa77df35 [file] [log] [blame]
/*
* 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;
}