diff options
Diffstat (limited to 'runtime/transport/utt.c')
-rw-r--r-- | runtime/transport/utt.c | 253 |
1 files changed, 253 insertions, 0 deletions
diff --git a/runtime/transport/utt.c b/runtime/transport/utt.c new file mode 100644 index 00000000..b23260d7 --- /dev/null +++ b/runtime/transport/utt.c @@ -0,0 +1,253 @@ +/* -*- linux-c -*- + * + * This is a modified version of the proposed utt interface. If that + * interface makes it into the kernel, this file can go away. + * + * Copyright (C) 2006 Jens Axboe <axboe@suse.de> + * + * Moved to utt.c by Tom Zanussi, 2006 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/percpu.h> +#include <linux/init.h> +#include <linux/debugfs.h> +#include <linux/relay.h> +#include "utt.h" + +static void utt_remove_root(struct utt_trace *utt) +{ + if (utt->utt_tree_root && simple_empty(utt->utt_tree_root)) { + debugfs_remove(utt->utt_tree_root); + utt->utt_tree_root = NULL; + } +} + +static void utt_remove_tree(struct utt_trace *utt) +{ + utt_remove_root(utt); +} + +static struct dentry *utt_create_tree(struct utt_trace *utt, const char *root) +{ + if (root == NULL) + return NULL; + + if (!utt->utt_tree_root) + utt->utt_tree_root = debugfs_create_dir(root, NULL); + return utt->utt_tree_root; +} + + +void utt_trace_cleanup(struct utt_trace *utt) +{ + relay_close(utt->rchan); + debugfs_remove(utt->dropped_file); + utt_remove_tree(utt); + free_percpu(utt->sequence); + kfree(utt); +} + +int utt_trace_remove(struct utt_trace *utt) +{ + if (utt->trace_state == Utt_trace_setup || + utt->trace_state == Utt_trace_stopped) + utt_trace_cleanup(utt); + + return 0; +} + +static int utt_dropped_open(struct inode *inode, struct file *filp) +{ + filp->private_data = inode->i_private; + + return 0; +} + +static ssize_t utt_dropped_read(struct file *filp, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct utt_trace *utt = filp->private_data; + char buf[16]; + + snprintf(buf, sizeof(buf), "%u\n", atomic_read(&utt->dropped)); + + return simple_read_from_buffer(buffer, count, ppos, buf, strlen(buf)); +} + +static struct file_operations utt_dropped_fops = { + .owner = THIS_MODULE, + .open = utt_dropped_open, + .read = utt_dropped_read, +}; + +/* + * Keep track of how many times we encountered a full subbuffer, to aid + * the user space app in telling how many lost events there were. + */ +static int utt_subbuf_start_callback(struct rchan_buf *buf, void *subbuf, + void *prev_subbuf, size_t prev_padding) +{ + struct utt_trace *utt; + + if (!relay_buf_full(buf)) + return 1; + + utt = buf->chan->private_data; + atomic_inc(&utt->dropped); + return 0; +} + +static int utt_remove_buf_file_callback(struct dentry *dentry) +{ + debugfs_remove(dentry); + return 0; +} + +static struct dentry *utt_create_buf_file_callback(const char *filename, + struct dentry *parent, + int mode, + struct rchan_buf *buf, + int *is_global) +{ + return debugfs_create_file(filename, mode, parent, buf, + &relay_file_operations); +} + +static struct dentry *utt_create_global_buf_file_callback(const char *filename, + struct dentry *parent, + int mode, + struct rchan_buf *buf, + int *is_global) +{ + *is_global = 1; + return debugfs_create_file(filename, mode, parent, buf, + &relay_file_operations); +} + +static struct rchan_callbacks utt_relay_callbacks = { + .subbuf_start = utt_subbuf_start_callback, + .create_buf_file = utt_create_buf_file_callback, + .remove_buf_file = utt_remove_buf_file_callback, +}; + +static struct rchan_callbacks utt_relay_callbacks_global = { + .subbuf_start = utt_subbuf_start_callback, + .create_buf_file = utt_create_global_buf_file_callback, + .remove_buf_file = utt_remove_buf_file_callback, +}; + +/* + * Setup everything required to start tracing + */ +struct utt_trace *utt_trace_setup(struct utt_trace_setup *utts) +{ + struct utt_trace *utt = NULL; + struct dentry *dir = NULL; + int ret = -EINVAL; + + if (!utts->buf_size || !utts->buf_nr) + goto err; + + ret = -ENOMEM; + utt = kzalloc(sizeof(*utt), GFP_KERNEL); + if (!utt) + goto err; + + utt->sequence = alloc_percpu(unsigned long); + if (!utt->sequence) + goto err; + + ret = -ENOENT; + dir = utt_create_tree(utt, utts->root); + if (!dir) + goto err; + + atomic_set(&utt->dropped, 0); + + ret = -EIO; + utt->dropped_file = debugfs_create_file("dropped", 0444, dir, utt, &utt_dropped_fops); + if (!utt->dropped_file) + goto err; + +#if (RELAYFS_CHANNEL_VERSION >= 7) + if (utts->is_global) + utt->rchan = relay_open("trace", dir, utts->buf_size, utts->buf_nr, + &utt_relay_callbacks_global, NULL); + else + utt->rchan = relay_open("trace", dir, utts->buf_size, utts->buf_nr, + &utt_relay_callbacks, NULL); +#else + if (utts->is_global) + utt->rchan = relay_open("trace", dir, utts->buf_size, utts->buf_nr, &utt_relay_callbacks_global); + else + utt->rchan = relay_open("trace", dir, utts->buf_size, utts->buf_nr, &utt_relay_callbacks); +#endif + + if (!utt->rchan) + goto err; + utt->rchan->private_data = utt; + + utt->trace_state = Utt_trace_setup; + + utts->err = 0; + return utt; +err: + if (utt) { + if (utt->dropped_file) + debugfs_remove(utt->dropped_file); + if (utt->sequence) + free_percpu(utt->sequence); + if (utt->rchan) + relay_close(utt->rchan); + kfree(utt); + } + if (dir) + utt_remove_tree(utt); + utts->err = ret; + return NULL; +} + +int utt_trace_startstop(struct utt_trace *utt, int start, + unsigned int *trace_seq) +{ + int ret; + + /* + * For starting a trace, we can transition from a setup or stopped + * trace. For stopping a trace, the state must be running + */ + ret = -EINVAL; + if (start) { + if (utt->trace_state == Utt_trace_setup || + utt->trace_state == Utt_trace_stopped) { + if (trace_seq) + (*trace_seq)++; + smp_mb(); + utt->trace_state = Utt_trace_running; + ret = 0; + } + } else { + if (utt->trace_state == Utt_trace_running) { + utt->trace_state = Utt_trace_stopped; + relay_flush(utt->rchan); + ret = 0; + } + } + + return ret; +} |