+/*
+ * LTT trace control module over debugfs.
+ *
+ * Copyright 2008 - Zhaolei <zhaolei@cn.fujitsu.com>
+ *
+ * Copyright 2009 - Gui Jianfeng <guijianfeng@cn.fujitsu.com>
+ * Make mark-control work in debugfs
+ *
+ * Dual LGPL v2.1/GPL v2 license.
+ */
+
+/*
+ * Todo:
+ * Impl read operations for control file to read attributes
+ * Create a README file in ltt control dir, for display help info
+ */
+
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/uaccess.h>
+#include <linux/debugfs.h>
+#include <linux/notifier.h>
+#include <linux/jiffies.h>
+#include <linux/marker.h>
+
+#include "ltt-tracer.h"
+
+#define LTT_CONTROL_DIR "control"
+#define MARKERS_CONTROL_DIR "markers"
+#define LTT_SETUP_TRACE_FILE "setup_trace"
+#define LTT_DESTROY_TRACE_FILE "destroy_trace"
+
+#define LTT_WRITE_MAXLEN (128)
+
+struct dentry *ltt_control_dir, *ltt_setup_trace_file, *ltt_destroy_trace_file,
+ *markers_control_dir;
+
+/*
+ * the traces_lock nests inside control_lock.
+ * control_lock protects the consistency of directories presented in ltt
+ * directory.
+ */
+static DEFINE_MUTEX(control_lock);
+
+/*
+ * big note about locking for marker control files :
+ * If a marker control file is added/removed manually racing with module
+ * load/unload, there may be warning messages appearing, but those two
+ * operations should be able to execute concurrently without any lock
+ * synchronizing their operation one wrt another.
+ * Locking the marker mutex, module mutex and also keeping a mutex here
+ * from mkdir/rmdir _and_ from the notifier called from module load/unload makes
+ * life miserable and just asks for deadlocks.
+ */
+
+/*
+ * lookup a file/dir in parent dir.
+ * only designed to work well for debugfs.
+ * (although it maybe ok for other fs)
+ *
+ * return:
+ * file/dir's dentry on success
+ * NULL on failure
+ */
+static struct dentry *dir_lookup(struct dentry *parent, const char *name)
+{
+ struct qstr q;
+ struct dentry *d;
+
+ q.name = name;
+ q.len = strlen(name);
+ q.hash = full_name_hash(q.name, q.len);
+
+ d = d_lookup(parent, &q);
+ if (d)
+ dput(d);
+
+ return d;
+}
+
+
+static ssize_t alloc_write(struct file *file, const char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ int err = 0;
+ int buf_size;
+ char *buf = (char *)__get_free_page(GFP_KERNEL);
+ char *cmd = (char *)__get_free_page(GFP_KERNEL);
+
+ buf_size = min_t(size_t, count, PAGE_SIZE - 1);
+ err = copy_from_user(buf, user_buf, buf_size);
+ if (err)
+ goto err_copy_from_user;
+ buf[buf_size] = 0;
+
+ if (sscanf(buf, "%s", cmd) != 1) {
+ err = -EPERM;
+ goto err_get_cmd;
+ }
+
+ if ((cmd[0] != 'Y' && cmd[0] != 'y' && cmd[0] != '1') || cmd[1]) {
+ err = -EPERM;
+ goto err_bad_cmd;
+ }
+
+ err = ltt_trace_alloc(file->f_dentry->d_parent->d_name.name);
+ if (IS_ERR_VALUE(err)) {
+ printk(KERN_ERR "alloc_write: ltt_trace_alloc failed: %d\n",
+ err);
+ goto err_alloc_trace;
+ }
+
+ free_page((unsigned long)buf);
+ free_page((unsigned long)cmd);
+ return count;
+
+err_alloc_trace:
+err_bad_cmd:
+err_get_cmd:
+err_copy_from_user:
+ free_page((unsigned long)buf);
+ free_page((unsigned long)cmd);
+ return err;
+}
+
+static const struct file_operations ltt_alloc_operations = {
+ .write = alloc_write,
+};
+
+
+static ssize_t enabled_write(struct file *file, const char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ int err = 0;
+ int buf_size;
+ char *buf = (char *)__get_free_page(GFP_KERNEL);
+ char *cmd = (char *)__get_free_page(GFP_KERNEL);
+
+ buf_size = min_t(size_t, count, PAGE_SIZE - 1);
+ err = copy_from_user(buf, user_buf, buf_size);
+ if (err)
+ goto err_copy_from_user;
+ buf[buf_size] = 0;
+
+ if (sscanf(buf, "%s", cmd) != 1) {
+ err = -EPERM;
+ goto err_get_cmd;
+ }
+
+ if (cmd[1]) {
+ err = -EPERM;
+ goto err_bad_cmd;
+ }
+
+ switch (cmd[0]) {
+ case 'Y':
+ case 'y':
+ case '1':
+ err = ltt_trace_start(file->f_dentry->d_parent->d_name.name);
+ if (IS_ERR_VALUE(err)) {
+ printk(KERN_ERR
+ "enabled_write: ltt_trace_start failed: %d\n",
+ err);
+ err = -EPERM;
+ goto err_start_trace;
+ }
+ break;
+ case 'N':
+ case 'n':
+ case '0':
+ err = ltt_trace_stop(file->f_dentry->d_parent->d_name.name);
+ if (IS_ERR_VALUE(err)) {
+ printk(KERN_ERR
+ "enabled_write: ltt_trace_stop failed: %d\n",
+ err);
+ err = -EPERM;
+ goto err_stop_trace;
+ }
+ break;
+ default:
+ err = -EPERM;
+ goto err_bad_cmd;
+ }
+
+ free_page((unsigned long)buf);
+ free_page((unsigned long)cmd);
+ return count;
+
+err_stop_trace:
+err_start_trace:
+err_bad_cmd:
+err_get_cmd:
+err_copy_from_user:
+ free_page((unsigned long)buf);
+ free_page((unsigned long)cmd);
+ return err;
+}
+
+static const struct file_operations ltt_enabled_operations = {
+ .write = enabled_write,
+};
+
+
+static ssize_t trans_write(struct file *file, const char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ char *buf = (char *)__get_free_page(GFP_KERNEL);
+ char *trans_name = (char *)__get_free_page(GFP_KERNEL);
+ int err = 0;
+ int buf_size;
+
+ buf_size = min_t(size_t, count, PAGE_SIZE - 1);
+ err = copy_from_user(buf, user_buf, buf_size);
+ if (err)
+ goto err_copy_from_user;
+ buf[buf_size] = 0;
+
+ if (sscanf(buf, "%s", trans_name) != 1) {
+ err = -EPERM;
+ goto err_get_transname;
+ }
+
+ err = ltt_trace_set_type(file->f_dentry->d_parent->d_name.name,
+ trans_name);
+ if (IS_ERR_VALUE(err)) {
+ printk(KERN_ERR "trans_write: ltt_trace_set_type failed: %d\n",
+ err);
+ goto err_set_trans;
+ }
+
+ free_page((unsigned long)buf);
+ free_page((unsigned long)trans_name);
+ return count;
+
+err_set_trans:
+err_get_transname:
+err_copy_from_user:
+ free_page((unsigned long)buf);
+ free_page((unsigned long)trans_name);
+ return err;
+}
+
+static const struct file_operations ltt_trans_operations = {
+ .write = trans_write,
+};
+
+
+static ssize_t channel_subbuf_num_write(struct file *file,
+ const char __user *user_buf, size_t count, loff_t *ppos)
+{
+ int err = 0;
+ int buf_size;
+ unsigned int num;
+ const char *channel_name;
+ const char *trace_name;
+ char *buf = (char *)__get_free_page(GFP_KERNEL);
+
+ buf_size = min_t(size_t, count, PAGE_SIZE - 1);
+ err = copy_from_user(buf, user_buf, buf_size);
+ if (err)
+ goto err_copy_from_user;
+ buf[buf_size] = 0;
+
+ if (sscanf(buf, "%u", &num) != 1) {
+ err = -EPERM;
+ goto err_get_number;
+ }
+
+ channel_name = file->f_dentry->d_parent->d_name.name;
+ trace_name = file->f_dentry->d_parent->d_parent->d_parent->d_name.name;
+
+ err = ltt_trace_set_channel_subbufcount(trace_name, channel_name, num);
+ if (IS_ERR_VALUE(err)) {
+ printk(KERN_ERR "channel_subbuf_num_write: "
+ "ltt_trace_set_channel_subbufcount failed: %d\n", err);
+ goto err_set_subbufcount;
+ }
+
+ free_page((unsigned long)buf);
+ return count;
+
+err_set_subbufcount:
+err_get_number:
+err_copy_from_user:
+ free_page((unsigned long)buf);
+ return err;
+}
+
+static const struct file_operations ltt_channel_subbuf_num_operations = {
+ .write = channel_subbuf_num_write,
+};
+
+
+static
+ssize_t channel_subbuf_size_write(struct file *file,
+ const char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ int err = 0;
+ int buf_size;
+ unsigned int num;
+ const char *channel_name;
+ const char *trace_name;
+ char *buf = (char *)__get_free_page(GFP_KERNEL);
+
+ buf_size = min_t(size_t, count, PAGE_SIZE - 1);
+ err = copy_from_user(buf, user_buf, buf_size);
+ if (err)
+ goto err_copy_from_user;
+ buf[buf_size] = 0;
+
+ if (sscanf(buf, "%u", &num) != 1) {
+ err = -EPERM;
+ goto err_get_number;
+ }
+
+ channel_name = file->f_dentry->d_parent->d_name.name;
+ trace_name = file->f_dentry->d_parent->d_parent->d_parent->d_name.name;
+
+ err = ltt_trace_set_channel_subbufsize(trace_name, channel_name, num);
+ if (IS_ERR_VALUE(err)) {
+ printk(KERN_ERR "channel_subbuf_size_write: "
+ "ltt_trace_set_channel_subbufsize failed: %d\n", err);
+ goto err_set_subbufsize;
+ }
+
+ free_page((unsigned long)buf);
+ return count;
+
+err_set_subbufsize:
+err_get_number:
+err_copy_from_user:
+ free_page((unsigned long)buf);
+ return err;
+}
+
+static const struct file_operations ltt_channel_subbuf_size_operations = {
+ .write = channel_subbuf_size_write,
+};
+
+static
+ssize_t channel_switch_timer_write(struct file *file,
+ const char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ int err = 0;
+ int buf_size;
+ unsigned long num;
+ const char *channel_name;
+ const char *trace_name;
+ char *buf = (char *)__get_free_page(GFP_KERNEL);
+
+ buf_size = min_t(size_t, count, PAGE_SIZE - 1);
+ err = copy_from_user(buf, user_buf, buf_size);
+ if (err)
+ goto err_copy_from_user;
+ buf[buf_size] = 0;
+
+ if (sscanf(buf, "%lu", &num) != 1) {
+ err = -EPERM;
+ goto err_get_number;
+ }
+
+ channel_name = file->f_dentry->d_parent->d_name.name;
+ trace_name = file->f_dentry->d_parent->d_parent->d_parent->d_name.name;
+
+ /* Convert from ms to us */
+ num *= 1000;
+
+ err = ltt_trace_set_channel_switch_timer(trace_name, channel_name, num);
+ if (IS_ERR_VALUE(err)) {
+ printk(KERN_ERR "channel_switch_timer_write: "
+ "ltt_trace_set_channel_switch_timer failed: %d\n", err);
+ goto err_set_switch_timer;
+ }
+
+ free_page((unsigned long)buf);
+ return count;
+
+err_set_switch_timer:
+err_get_number:
+err_copy_from_user:
+ free_page((unsigned long)buf);
+ return err;
+}
+
+static struct file_operations ltt_channel_switch_timer_operations = {
+ .write = channel_switch_timer_write,
+};
+
+static
+ssize_t channel_overwrite_write(struct file *file,
+ const char __user *user_buf, size_t count,
+ loff_t *ppos)
+{
+ int err = 0;
+ int buf_size;
+ const char *channel_name;
+ const char *trace_name;
+ char *buf = (char *)__get_free_page(GFP_KERNEL);
+ char *cmd = (char *)__get_free_page(GFP_KERNEL);
+
+ buf_size = min_t(size_t, count, PAGE_SIZE - 1);
+ err = copy_from_user(buf, user_buf, buf_size);
+ if (err)
+ goto err_copy_from_user;
+ buf[buf_size] = 0;
+
+ if (sscanf(buf, "%s", cmd) != 1) {
+ err = -EPERM;
+ goto err_get_cmd;
+ }
+
+ if (cmd[1]) {
+ err = -EPERM;
+ goto err_bad_cmd;
+ }
+
+ channel_name = file->f_dentry->d_parent->d_name.name;
+ trace_name = file->f_dentry->d_parent->d_parent->d_parent->d_name.name;
+
+ switch (cmd[0]) {
+ case 'Y':
+ case 'y':
+ case '1':
+ err = ltt_trace_set_channel_overwrite(trace_name, channel_name,
+ 1);
+ if (IS_ERR_VALUE(err)) {
+ printk(KERN_ERR "channel_overwrite_write: "
+ "ltt_trace_set_channel_overwrite failed: %d\n",
+ err);
+ goto err_set_subbufsize;
+ }
+ break;
+ case 'N':
+ case 'n':
+ case '0':
+ err = ltt_trace_set_channel_overwrite(trace_name, channel_name,
+ 0);
+ if (IS_ERR_VALUE(err)) {
+ printk(KERN_ERR "channel_overwrite_write: "
+ "ltt_trace_set_channel_overwrite failed: %d\n",
+ err);
+ goto err_set_subbufsize;
+ }
+ break;
+ default:
+ err = -EPERM;
+ goto err_bad_cmd;
+ }
+
+ free_page((unsigned long)buf);
+ free_page((unsigned long)cmd);
+ return count;
+
+err_set_subbufsize:
+err_bad_cmd:
+err_get_cmd:
+err_copy_from_user:
+ free_page((unsigned long)buf);
+ free_page((unsigned long)cmd);
+ return err;
+}
+
+static const struct file_operations ltt_channel_overwrite_operations = {
+ .write = channel_overwrite_write,
+};
+
+
+static
+ssize_t channel_enable_write(struct file *file,
+ const char __user *user_buf, size_t count,
+ loff_t *ppos)
+{
+ int err = 0;
+ int buf_size;
+ const char *channel_name;
+ const char *trace_name;
+ char *buf = (char *)__get_free_page(GFP_KERNEL);
+ char *cmd = (char *)__get_free_page(GFP_KERNEL);
+
+ buf_size = min_t(size_t, count, PAGE_SIZE - 1);
+ err = copy_from_user(buf, user_buf, buf_size);
+ if (err)
+ goto err_copy_from_user;
+ buf[buf_size] = 0;
+
+ if (sscanf(buf, "%s", cmd) != 1) {
+ err = -EPERM;
+ goto err_get_cmd;
+ }
+
+ if (cmd[1]) {
+ err = -EPERM;
+ goto err_bad_cmd;
+ }
+
+ channel_name = file->f_dentry->d_parent->d_name.name;
+ trace_name = file->f_dentry->d_parent->d_parent->d_parent->d_name.name;
+
+ switch (cmd[0]) {
+ case 'Y':
+ case 'y':
+ case '1':
+ err = ltt_trace_set_channel_enable(trace_name, channel_name,
+ 1);
+ if (IS_ERR_VALUE(err)) {
+ printk(KERN_ERR "channel_enable_write: "
+ "ltt_trace_set_channel_enable failed: %d\n",
+ err);
+ goto err_set_subbufsize;
+ }
+ break;
+ case 'N':
+ case 'n':
+ case '0':
+ err = ltt_trace_set_channel_enable(trace_name, channel_name,
+ 0);
+ if (IS_ERR_VALUE(err)) {
+ printk(KERN_ERR "channel_enable_write: "
+ "ltt_trace_set_channel_enable failed: %d\n",
+ err);
+ goto err_set_subbufsize;
+ }
+ break;
+ default:
+ err = -EPERM;
+ goto err_bad_cmd;
+ }
+
+ free_page((unsigned long)buf);
+ free_page((unsigned long)cmd);
+ return count;
+
+err_set_subbufsize:
+err_bad_cmd:
+err_get_cmd:
+err_copy_from_user:
+ free_page((unsigned long)buf);
+ free_page((unsigned long)cmd);
+ return err;
+}
+
+static const struct file_operations ltt_channel_enable_operations = {
+ .write = channel_enable_write,
+};
+
+
+static int _create_trace_control_dir(const char *trace_name,
+ struct ltt_trace *trace)
+{
+ int err;
+ struct dentry *trace_root, *channel_root;
+ struct dentry *tmp_den;
+ int i;
+
+ /* debugfs/control/trace_name */
+ trace_root = debugfs_create_dir(trace_name, ltt_control_dir);
+ if (IS_ERR(trace_root) || !trace_root) {
+ printk(KERN_ERR "_create_trace_control_dir: "
+ "create control root dir of %s failed\n", trace_name);
+ err = -ENOMEM;
+ goto err_create_trace_root;
+ }
+
+ /* debugfs/control/trace_name/alloc */
+ tmp_den = debugfs_create_file("alloc", S_IWUSR, trace_root, NULL,
+ <t_alloc_operations);
+ if (IS_ERR(tmp_den) || !tmp_den) {
+ printk(KERN_ERR "_create_trace_control_dir: "
+ "create file of alloc failed\n");
+ err = -ENOMEM;
+ goto err_create_subdir;
+ }
+
+ /* debugfs/control/trace_name/trans */
+ tmp_den = debugfs_create_file("trans", S_IWUSR, trace_root, NULL,
+ <t_trans_operations);
+ if (IS_ERR(tmp_den) || !tmp_den) {
+ printk(KERN_ERR "_create_trace_control_dir: "
+ "create file of trans failed\n");
+ err = -ENOMEM;
+ goto err_create_subdir;
+ }
+
+ /* debugfs/control/trace_name/enabled */
+ tmp_den = debugfs_create_file("enabled", S_IWUSR, trace_root, NULL,
+ <t_enabled_operations);
+ if (IS_ERR(tmp_den) || !tmp_den) {
+ printk(KERN_ERR "_create_trace_control_dir: "
+ "create file of enabled failed\n");
+ err = -ENOMEM;
+ goto err_create_subdir;
+ }
+
+ /* debugfs/control/trace_name/channel/ */
+ channel_root = debugfs_create_dir("channel", trace_root);
+ if (IS_ERR(channel_root) || !channel_root) {
+ printk(KERN_ERR "_create_trace_control_dir: "
+ "create dir of channel failed\n");
+ err = -ENOMEM;
+ goto err_create_subdir;
+ }
+
+ /*
+ * Create dir and files in debugfs/ltt/control/trace_name/channel/
+ * Following things(without <>) will be created:
+ * `-- <control>
+ * `-- <trace_name>
+ * `-- <channel>
+ * |-- <channel_name>
+ * | |-- enable
+ * | |-- overwrite
+ * | |-- subbuf_num
+ * | |-- subbuf_size
+ * | `-- switch_timer
+ * `-- ...
+ */
+
+ for (i = 0; i < trace->nr_channels; i++) {
+ struct dentry *channel_den;
+ struct ltt_chan *chan;
+
+ chan = &trace->channels[i];
+ if (!chan->active)
+ continue;
+ channel_den = debugfs_create_dir(chan->a.filename,
+ channel_root);
+ if (IS_ERR(channel_den) || !channel_den) {
+ printk(KERN_ERR "_create_trace_control_dir: "
+ "create channel dir of %s failed\n",
+ chan->a.filename);
+ err = -ENOMEM;
+ goto err_create_subdir;
+ }
+
+ tmp_den = debugfs_create_file("subbuf_num", S_IWUSR,
+ channel_den, NULL,
+ <t_channel_subbuf_num_operations);
+ if (IS_ERR(tmp_den) || !tmp_den) {
+ printk(KERN_ERR "_create_trace_control_dir: "
+ "create subbuf_num in %s failed\n",
+ chan->a.filename);
+ err = -ENOMEM;
+ goto err_create_subdir;
+ }
+
+ tmp_den = debugfs_create_file("subbuf_size", S_IWUSR,
+ channel_den, NULL,
+ <t_channel_subbuf_size_operations);
+ if (IS_ERR(tmp_den) || !tmp_den) {
+ printk(KERN_ERR "_create_trace_control_dir: "
+ "create subbuf_size in %s failed\n",
+ chan->a.filename);
+ err = -ENOMEM;
+ goto err_create_subdir;
+ }
+
+ tmp_den = debugfs_create_file("enable", S_IWUSR, channel_den,
+ NULL,
+ <t_channel_enable_operations);
+ if (IS_ERR(tmp_den) || !tmp_den) {
+ printk(KERN_ERR "_create_trace_control_dir: "
+ "create enable in %s failed\n",
+ chan->a.filename);
+ err = -ENOMEM;
+ goto err_create_subdir;
+ }
+
+ tmp_den = debugfs_create_file("overwrite", S_IWUSR, channel_den,
+ NULL,
+ <t_channel_overwrite_operations);
+ if (IS_ERR(tmp_den) || !tmp_den) {
+ printk(KERN_ERR "_create_trace_control_dir: "
+ "create overwrite in %s failed\n",
+ chan->a.filename);
+ err = -ENOMEM;
+ goto err_create_subdir;
+ }
+
+ tmp_den = debugfs_create_file("switch_timer", S_IWUSR,
+ channel_den, NULL,
+ <t_channel_switch_timer_operations);
+ if (IS_ERR(tmp_den) || !tmp_den) {
+ printk(KERN_ERR "_create_trace_control_dir: "
+ "create switch_timer in %s failed\n",
+ chan->a.filename);
+ err = -ENOMEM;
+ goto err_create_subdir;
+ }
+ }
+
+ return 0;
+
+err_create_subdir:
+ debugfs_remove_recursive(trace_root);
+err_create_trace_root:
+ return err;
+}
+
+static
+ssize_t setup_trace_write(struct file *file, const char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ int err = 0;
+ int buf_size;
+ struct ltt_trace *trace;
+ char *buf = (char *)__get_free_page(GFP_KERNEL);
+ char *trace_name = (char *)__get_free_page(GFP_KERNEL);
+
+ buf_size = min_t(size_t, count, PAGE_SIZE - 1);
+ err = copy_from_user(buf, user_buf, buf_size);
+ if (err)
+ goto err_copy_from_user;
+ buf[buf_size] = 0;
+
+ if (sscanf(buf, "%s", trace_name) != 1) {
+ err = -EPERM;
+ goto err_get_tracename;
+ }
+
+ mutex_lock(&control_lock);
+ ltt_lock_traces();
+
+ err = _ltt_trace_setup(trace_name);
+ if (IS_ERR_VALUE(err)) {
+ printk(KERN_ERR
+ "setup_trace_write: ltt_trace_setup failed: %d\n", err);
+ goto err_setup_trace;
+ }
+ trace = _ltt_trace_find_setup(trace_name);
+ BUG_ON(!trace);
+ err = _create_trace_control_dir(trace_name, trace);
+ if (IS_ERR_VALUE(err)) {
+ printk(KERN_ERR "setup_trace_write: "
+ "_create_trace_control_dir failed: %d\n", err);
+ goto err_create_trace_control_dir;
+ }
+
+ ltt_unlock_traces();
+ mutex_unlock(&control_lock);
+
+ free_page((unsigned long)buf);
+ free_page((unsigned long)trace_name);
+ return count;
+
+err_create_trace_control_dir:
+ ltt_trace_destroy(trace_name);
+err_setup_trace:
+ ltt_unlock_traces();
+ mutex_unlock(&control_lock);
+err_get_tracename:
+err_copy_from_user:
+ free_page((unsigned long)buf);
+ free_page((unsigned long)trace_name);
+ return err;
+}
+
+static const struct file_operations ltt_setup_trace_operations = {
+ .write = setup_trace_write,
+};
+
+static
+ssize_t destroy_trace_write(struct file *file, const char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ struct dentry *trace_den;
+ int buf_size;
+ int err = 0;
+ char *buf = (char *)__get_free_page(GFP_KERNEL);
+ char *trace_name = (char *)__get_free_page(GFP_KERNEL);
+
+ buf_size = min_t(size_t, count, PAGE_SIZE - 1);
+ err = copy_from_user(buf, user_buf, buf_size);
+ if (err)
+ goto err_copy_from_user;
+ buf[buf_size] = 0;
+
+ if (sscanf(buf, "%s", trace_name) != 1) {
+ err = -EPERM;
+ goto err_get_tracename;
+ }
+
+ mutex_lock(&control_lock);
+
+ err = ltt_trace_destroy(trace_name);
+ if (IS_ERR_VALUE(err)) {
+ printk(KERN_ERR
+ "destroy_trace_write: ltt_trace_destroy failed: %d\n",
+ err);
+ err = -EPERM;
+ goto err_destroy_trace;
+ }
+
+ trace_den = dir_lookup(ltt_control_dir, trace_name);
+ if (!trace_den) {
+ printk(KERN_ERR
+ "destroy_trace_write: lookup for %s's dentry failed\n",
+ trace_name);
+ err = -ENOENT;
+ goto err_get_dentry;
+ }
+
+ debugfs_remove_recursive(trace_den);
+
+ mutex_unlock(&control_lock);
+
+ free_page((unsigned long)buf);
+ free_page((unsigned long)trace_name);
+ return count;
+
+err_get_dentry:
+err_destroy_trace:
+ mutex_unlock(&control_lock);
+err_get_tracename:
+err_copy_from_user:
+ free_page((unsigned long)buf);
+ free_page((unsigned long)trace_name);
+ return err;
+}
+
+static const struct file_operations ltt_destroy_trace_operations = {
+ .write = destroy_trace_write,
+};
+
+static void init_marker_dir(struct dentry *dentry,
+ const struct inode_operations *opt)
+{
+ dentry->d_inode->i_op = opt;
+}
+
+static
+ssize_t marker_enable_read(struct file *filp, char __user *ubuf,
+ size_t cnt, loff_t *ppos)
+{
+ char *buf;
+ const char *channel, *marker;
+ int len, enabled, present;
+
+ marker = filp->f_dentry->d_parent->d_name.name;
+ channel = filp->f_dentry->d_parent->d_parent->d_name.name;
+
+ len = 0;
+ buf = (char *)__get_free_page(GFP_KERNEL);
+
+ /*
+ * Note: we cannot take the marker lock to make these two checks
+ * atomic, because the marker mutex nests inside the module mutex, taken
+ * inside the marker present check.
+ */
+ enabled = is_marker_enabled(channel, marker);
+ present = is_marker_present(channel, marker);
+
+ if (enabled && present)
+ len = snprintf(buf, PAGE_SIZE, "%d\n", 1);
+ else if (enabled && !present)
+ len = snprintf(buf, PAGE_SIZE, "%d\n", 2);
+ else
+ len = snprintf(buf, PAGE_SIZE, "%d\n", 0);
+
+
+ if (len >= PAGE_SIZE) {
+ len = PAGE_SIZE;
+ buf[PAGE_SIZE] = '\0';
+ }
+ len = simple_read_from_buffer(ubuf, cnt, ppos, buf, len);
+ free_page((unsigned long)buf);
+
+ return len;
+}
+
+static
+ssize_t marker_enable_write(struct file *filp, const char __user *ubuf,
+ size_t cnt, loff_t *ppos)
+{
+ char *buf = (char *)__get_free_page(GFP_KERNEL);
+ int buf_size;
+ ssize_t ret = 0;
+ const char *channel, *marker;
+
+ marker = filp->f_dentry->d_parent->d_name.name;
+ channel = filp->f_dentry->d_parent->d_parent->d_name.name;
+
+ buf_size = min_t(size_t, cnt, PAGE_SIZE - 1);
+ ret = copy_from_user(buf, ubuf, buf_size);
+ if (ret)
+ goto end;
+
+ buf[buf_size] = 0;
+
+ switch (buf[0]) {
+ case 'Y':
+ case 'y':
+ case '1':
+ ret = ltt_marker_connect(channel, marker, "default");
+ if (ret)
+ goto end;
+ break;
+ case 'N':
+ case 'n':
+ case '0':
+ ret = ltt_marker_disconnect(channel, marker, "default");
+ if (ret)
+ goto end;
+ break;
+ default:
+ ret = -EPERM;
+ goto end;
+ }
+ ret = cnt;
+end:
+ free_page((unsigned long)buf);
+ return ret;
+}
+
+static const struct file_operations enable_fops = {
+ .read = marker_enable_read,
+ .write = marker_enable_write,
+};
+
+/*
+ * In practice, the output size should never be larger than 4096 kB. If it
+ * ever happens, the output will simply be truncated.
+ */
+static
+ssize_t marker_info_read(struct file *filp, char __user *ubuf,
+ size_t cnt, loff_t *ppos)
+{
+ char *buf;
+ const char *channel, *marker;
+ int len;
+ struct marker_iter iter;
+
+ marker = filp->f_dentry->d_parent->d_name.name;
+ channel = filp->f_dentry->d_parent->d_parent->d_name.name;
+
+ len = 0;
+ buf = (char *)__get_free_page(GFP_KERNEL);
+
+ if (is_marker_enabled(channel, marker) &&
+ !is_marker_present(channel, marker)) {
+ len += snprintf(buf + len, PAGE_SIZE - len,
+ "Marker Pre-enabled\n");
+ goto out;
+ }
+
+ marker_iter_reset(&iter);
+ marker_iter_start(&iter);
+ for (; iter.marker != NULL; marker_iter_next(&iter)) {
+ if (!strcmp(iter.marker->channel, channel) &&
+ !strcmp(iter.marker->name, marker))
+ len += snprintf(buf + len, PAGE_SIZE - len,
+ "Location: %s\n"
+ "format: \"%s\"\nstate: %d\n"
+ "event_id: %hu\n"
+ "call: 0x%p\n"
+ "probe %s : 0x%p\n\n",
+#ifdef CONFIG_MODULES
+ iter.module ? iter.module->name :
+#endif
+ "Core Kernel",
+ iter.marker->format,
+ _imv_read(iter.marker->state),
+ iter.marker->event_id,
+ iter.marker->call,
+ iter.marker->ptype ?
+ "multi" : "single", iter.marker->ptype ?
+ (void *)iter.marker->multi :
+ (void *)iter.marker->single.func);
+ if (len >= PAGE_SIZE)
+ break;
+ }
+ marker_iter_stop(&iter);
+
+out:
+ if (len >= PAGE_SIZE) {
+ len = PAGE_SIZE;
+ buf[PAGE_SIZE] = '\0';
+ }
+
+ len = simple_read_from_buffer(ubuf, cnt, ppos, buf, len);
+ free_page((unsigned long)buf);
+
+ return len;
+}
+
+static const struct file_operations info_fops = {
+ .read = marker_info_read,
+};
+
+static int marker_mkdir(struct inode *dir, struct dentry *dentry, int mode)
+{
+ struct dentry *marker_d, *enable_d, *info_d, *channel_d;
+ int ret;
+
+ ret = 0;
+ channel_d = (struct dentry *)dir->i_private;
+ mutex_unlock(&dir->i_mutex);
+
+ marker_d = debugfs_create_dir(dentry->d_name.name,
+ channel_d);
+ if (IS_ERR(marker_d)) {
+ ret = PTR_ERR(marker_d);
+ goto out;
+ }
+
+ enable_d = debugfs_create_file("enable", 0644, marker_d,
+ NULL, &enable_fops);
+ if (IS_ERR(enable_d) || !enable_d) {
+ printk(KERN_ERR
+ "%s: create file of %s failed\n",
+ __func__, "enable");
+ ret = -ENOMEM;
+ goto remove_marker_dir;
+ }
+
+ info_d = debugfs_create_file("info", 0644, marker_d,
+ NULL, &info_fops);
+ if (IS_ERR(info_d) || !info_d) {
+ printk(KERN_ERR
+ "%s: create file of %s failed\n",
+ __func__, "info");
+ ret = -ENOMEM;
+ goto remove_enable_dir;
+ }
+
+ goto out;
+
+remove_enable_dir:
+ debugfs_remove(enable_d);
+remove_marker_dir:
+ debugfs_remove(marker_d);
+out:
+ mutex_lock_nested(&dir->i_mutex, I_MUTEX_PARENT);
+ return ret;
+}
+
+static int marker_rmdir(struct inode *dir, struct dentry *dentry)
+{
+ struct dentry *marker_d, *channel_d;
+ const char *channel, *name;
+ int ret, enabled, present;
+
+ ret = 0;
+
+ channel_d = (struct dentry *)dir->i_private;
+ channel = channel_d->d_name.name;
+
+ marker_d = dir_lookup(channel_d, dentry->d_name.name);
+
+ if (!marker_d) {
+ ret = -ENOENT;
+ goto out;
+ }
+
+ name = marker_d->d_name.name;
+
+ enabled = is_marker_enabled(channel, name);
+ present = is_marker_present(channel, name);
+
+ if (present || (!present && enabled)) {
+ ret = -EPERM;
+ goto out;
+ }
+
+ mutex_unlock(&dir->i_mutex);
+ mutex_unlock(&dentry->d_inode->i_mutex);
+ debugfs_remove_recursive(marker_d);
+ mutex_lock_nested(&dir->i_mutex, I_MUTEX_PARENT);
+ mutex_lock(&dentry->d_inode->i_mutex);
+out:
+ return ret;
+}
+
+const struct inode_operations channel_dir_opt = {
+ .lookup = simple_lookup,
+ .mkdir = marker_mkdir,
+ .rmdir = marker_rmdir,
+};
+
+static int channel_mkdir(struct inode *dir, struct dentry *dentry, int mode)
+{
+ struct dentry *channel_d;
+ int ret;
+
+ ret = 0;
+ mutex_unlock(&dir->i_mutex);
+
+ channel_d = debugfs_create_dir(dentry->d_name.name,
+ markers_control_dir);
+ if (IS_ERR(channel_d)) {
+ ret = PTR_ERR(channel_d);
+ goto out;
+ }
+
+ channel_d->d_inode->i_private = (void *)channel_d;
+ init_marker_dir(channel_d, &channel_dir_opt);
+out:
+ mutex_lock_nested(&dir->i_mutex, I_MUTEX_PARENT);
+ return ret;
+}
+
+static int channel_rmdir(struct inode *dir, struct dentry *dentry)
+{
+ struct dentry *channel_d;
+ int ret;
+
+ ret = 0;
+
+ channel_d = dir_lookup(markers_control_dir, dentry->d_name.name);
+ if (!channel_d) {
+ ret = -ENOENT;
+ goto out;
+ }
+
+ if (list_empty(&channel_d->d_subdirs)) {
+ mutex_unlock(&dir->i_mutex);
+ mutex_unlock(&dentry->d_inode->i_mutex);
+ debugfs_remove(channel_d);
+ mutex_lock_nested(&dir->i_mutex, I_MUTEX_PARENT);
+ mutex_lock(&dentry->d_inode->i_mutex);
+ } else
+ ret = -EPERM;
+
+out:
+ return ret;
+}
+
+const struct inode_operations root_dir_opt = {
+ .lookup = simple_lookup,
+ .mkdir = channel_mkdir,
+ .rmdir = channel_rmdir
+};
+
+static int build_marker_file(struct marker *marker)
+{
+ struct dentry *channel_d, *marker_d, *enable_d, *info_d;
+ int err;
+
+ channel_d = dir_lookup(markers_control_dir, marker->channel);
+ if (!channel_d) {
+ channel_d = debugfs_create_dir(marker->channel,
+ markers_control_dir);
+ if (IS_ERR(channel_d) || !channel_d) {
+ printk(KERN_ERR
+ "%s: build channel dir of %s failed\n",
+ __func__, marker->channel);
+ err = -ENOMEM;
+ goto err_build_fail;
+ }
+ channel_d->d_inode->i_private = (void *)channel_d;
+ init_marker_dir(channel_d, &channel_dir_opt);
+ }
+
+ marker_d = dir_lookup(channel_d, marker->name);
+ if (!marker_d) {
+ marker_d = debugfs_create_dir(marker->name, channel_d);
+ if (IS_ERR(marker_d) || !marker_d) {
+ printk(KERN_ERR
+ "%s: marker dir of %s failed\n",
+ __func__, marker->name);
+ err = -ENOMEM;
+ goto err_build_fail;
+ }
+ }
+
+ enable_d = dir_lookup(marker_d, "enable");
+ if (!enable_d) {
+ enable_d = debugfs_create_file("enable", 0644, marker_d,
+ NULL, &enable_fops);
+ if (IS_ERR(enable_d) || !enable_d) {
+ printk(KERN_ERR
+ "%s: create file of %s failed\n",
+ __func__, "enable");
+ err = -ENOMEM;
+ goto err_build_fail;
+ }
+ }
+
+ info_d = dir_lookup(marker_d, "info");
+ if (!info_d) {
+ info_d = debugfs_create_file("info", 0444, marker_d,
+ NULL, &info_fops);
+ if (IS_ERR(info_d) || !info_d) {
+ printk(KERN_ERR
+ "%s: create file of %s failed\n",
+ __func__, "enable");
+ err = -ENOMEM;
+ goto err_build_fail;
+ }
+ }
+
+ return 0;
+
+err_build_fail:
+ return err;
+}
+
+static int build_marker_control_files(void)
+{
+ struct marker_iter iter;
+ int err;
+
+ err = 0;
+ if (!markers_control_dir)
+ return -EEXIST;
+
+ marker_iter_reset(&iter);
+ marker_iter_start(&iter);
+ for (; iter.marker != NULL; marker_iter_next(&iter)) {
+ err = build_marker_file(iter.marker);
+ if (err)
+ goto out;
+ }
+ marker_iter_stop(&iter);
+
+out:
+ return err;
+}
+
+#ifdef CONFIG_MODULES
+static int remove_marker_control_dir(struct module *mod, struct marker *marker)
+{
+ struct dentry *channel_d, *marker_d;
+ const char *channel, *name;
+ int count;
+ struct marker_iter iter;
+
+ count = 0;
+
+ channel_d = dir_lookup(markers_control_dir, marker->channel);
+ if (!channel_d)
+ return -ENOENT;
+ channel = channel_d->d_name.name;
+
+ marker_d = dir_lookup(channel_d, marker->name);
+ if (!marker_d)
+ return -ENOENT;
+ name = marker_d->d_name.name;
+
+ marker_iter_reset(&iter);
+ marker_iter_start(&iter);
+ for (; iter.marker != NULL; marker_iter_next(&iter)) {
+ if (!strcmp(iter.marker->channel, channel) &&
+ !strcmp(iter.marker->name, name) && mod != iter.module)
+ count++;
+ }
+
+ if (count > 0)
+ goto end;
+
+ debugfs_remove_recursive(marker_d);
+ if (list_empty(&channel_d->d_subdirs))
+ debugfs_remove(channel_d);
+
+end:
+ marker_iter_stop(&iter);
+ return 0;
+}
+
+static void cleanup_control_dir(struct module *mod, struct marker *begin,
+ struct marker *end)
+{
+ struct marker *iter;
+
+ if (!markers_control_dir)
+ return;
+
+ for (iter = begin; iter < end; iter++)
+ remove_marker_control_dir(mod, iter);
+
+ return;
+}
+
+static void build_control_dir(struct module *mod, struct marker *begin,
+ struct marker *end)
+{
+ struct marker *iter;
+ int err;
+
+ err = 0;
+ if (!markers_control_dir)
+ return;
+
+ for (iter = begin; iter < end; iter++) {
+ err = build_marker_file(iter);
+ if (err)
+ goto err_build_fail;
+ }
+
+ return;
+err_build_fail:
+ cleanup_control_dir(mod, begin, end);
+}
+
+static int module_notify(struct notifier_block *self,
+ unsigned long val, void *data)
+{
+ struct module *mod = data;
+
+ switch (val) {
+ case MODULE_STATE_COMING:
+ build_control_dir(mod, mod->markers,
+ mod->markers + mod->num_markers);
+ break;
+ case MODULE_STATE_GOING:
+ cleanup_control_dir(mod, mod->markers,
+ mod->markers + mod->num_markers);
+ break;
+ }
+ return NOTIFY_DONE;
+}
+#else
+static inline int module_notify(struct notifier_block *self,
+ unsigned long val, void *data)
+{
+ return 0;
+}
+#endif
+
+static struct notifier_block module_nb = {
+ .notifier_call = module_notify,
+};
+
+static int __init ltt_trace_control_init(void)
+{
+ int err = 0;
+ struct dentry *ltt_root_dentry;
+
+ ltt_root_dentry = get_ltt_root();
+ if (!ltt_root_dentry) {
+ err = -ENOENT;
+ goto err_no_root;
+ }
+
+ ltt_control_dir = debugfs_create_dir(LTT_CONTROL_DIR, ltt_root_dentry);
+ if (IS_ERR(ltt_control_dir) || !ltt_control_dir) {
+ printk(KERN_ERR
+ "ltt_channel_control_init: create dir of %s failed\n",
+ LTT_CONTROL_DIR);
+ err = -ENOMEM;
+ goto err_create_control_dir;
+ }
+
+ ltt_setup_trace_file = debugfs_create_file(LTT_SETUP_TRACE_FILE,
+ S_IWUSR, ltt_root_dentry,
+ NULL,
+ <t_setup_trace_operations);
+ if (IS_ERR(ltt_setup_trace_file) || !ltt_setup_trace_file) {
+ printk(KERN_ERR
+ "ltt_channel_control_init: create file of %s failed\n",
+ LTT_SETUP_TRACE_FILE);
+ err = -ENOMEM;
+ goto err_create_setup_trace_file;
+ }
+
+ ltt_destroy_trace_file = debugfs_create_file(LTT_DESTROY_TRACE_FILE,
+ S_IWUSR, ltt_root_dentry,
+ NULL,
+ <t_destroy_trace_operations);
+ if (IS_ERR(ltt_destroy_trace_file) || !ltt_destroy_trace_file) {
+ printk(KERN_ERR
+ "ltt_channel_control_init: create file of %s failed\n",
+ LTT_DESTROY_TRACE_FILE);
+ err = -ENOMEM;
+ goto err_create_destroy_trace_file;
+ }
+
+ markers_control_dir = debugfs_create_dir(MARKERS_CONTROL_DIR,
+ ltt_root_dentry);
+ if (IS_ERR(markers_control_dir) || !markers_control_dir) {
+ printk(KERN_ERR
+ "ltt_channel_control_init: create dir of %s failed\n",
+ MARKERS_CONTROL_DIR);
+ err = -ENOMEM;
+ goto err_create_marker_control_dir;
+ }
+
+ init_marker_dir(markers_control_dir, &root_dir_opt);
+
+ if (build_marker_control_files())
+ goto err_build_fail;
+
+ if (!register_module_notifier(&module_nb))
+ return 0;
+
+err_build_fail:
+ debugfs_remove_recursive(markers_control_dir);
+ markers_control_dir = NULL;
+err_create_marker_control_dir:
+ debugfs_remove(ltt_destroy_trace_file);
+err_create_destroy_trace_file:
+ debugfs_remove(ltt_setup_trace_file);
+err_create_setup_trace_file:
+ debugfs_remove(ltt_control_dir);
+err_create_control_dir:
+err_no_root:
+ return err;
+}
+
+static void __exit ltt_trace_control_exit(void)
+{
+ struct dentry *trace_dir;
+
+ /* destory all traces */
+ list_for_each_entry(trace_dir, <t_control_dir->d_subdirs,
+ d_u.d_child) {
+ ltt_trace_stop(trace_dir->d_name.name);
+ ltt_trace_destroy(trace_dir->d_name.name);
+ }
+
+ /* clean dirs in debugfs */
+ debugfs_remove(ltt_setup_trace_file);
+ debugfs_remove(ltt_destroy_trace_file);
+ debugfs_remove_recursive(ltt_control_dir);
+ debugfs_remove_recursive(markers_control_dir);
+ unregister_module_notifier(&module_nb);
+ put_ltt_root();
+}
+
+module_init(ltt_trace_control_init);
+module_exit(ltt_trace_control_exit);
+
+MODULE_LICENSE("GPL and additional rights");
+MODULE_AUTHOR("Zhao Lei <zhaolei@cn.fujitsu.com>");
+MODULE_DESCRIPTION("Linux Trace Toolkit Trace Controller");