| /* |
| * 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; |
| } |