tracing: Add enum_map file to show enums that have been mapped
[deliverable/linux.git] / kernel / trace / trace.c
index 62c6506d663fa11e79128759bd03103d12ed7c0f..39e69568302e0d8095412e78f50f5346fca67dd3 100644 (file)
@@ -123,6 +123,42 @@ enum ftrace_dump_mode ftrace_dump_on_oops;
 /* When set, tracing will stop when a WARN*() is hit */
 int __disable_trace_on_warning;
 
+#ifdef CONFIG_TRACE_ENUM_MAP_FILE
+/* Map of enums to their values, for "enum_map" file */
+struct trace_enum_map_head {
+       struct module                   *mod;
+       unsigned long                   length;
+};
+
+union trace_enum_map_item;
+
+struct trace_enum_map_tail {
+       /*
+        * "end" is first and points to NULL as it must be different
+        * than "mod" or "enum_string"
+        */
+       union trace_enum_map_item       *next;
+       const char                      *end;   /* points to NULL */
+};
+
+static DEFINE_MUTEX(trace_enum_mutex);
+
+/*
+ * The trace_enum_maps are saved in an array with two extra elements,
+ * one at the beginning, and one at the end. The beginning item contains
+ * the count of the saved maps (head.length), and the module they
+ * belong to if not built in (head.mod). The ending item contains a
+ * pointer to the next array of saved enum_map items.
+ */
+union trace_enum_map_item {
+       struct trace_enum_map           map;
+       struct trace_enum_map_head      head;
+       struct trace_enum_map_tail      tail;
+};
+
+static union trace_enum_map_item *trace_enum_maps;
+#endif /* CONFIG_TRACE_ENUM_MAP_FILE */
+
 static int tracing_set_tracer(struct trace_array *tr, const char *buf);
 
 #define MAX_TRACER_SIZE                100
@@ -3908,6 +3944,182 @@ static const struct file_operations tracing_saved_cmdlines_size_fops = {
        .write          = tracing_saved_cmdlines_size_write,
 };
 
+#ifdef CONFIG_TRACE_ENUM_MAP_FILE
+static union trace_enum_map_item *
+update_enum_map(union trace_enum_map_item *ptr)
+{
+       if (!ptr->map.enum_string) {
+               if (ptr->tail.next) {
+                       ptr = ptr->tail.next;
+                       /* Set ptr to the next real item (skip head) */
+                       ptr++;
+               } else
+                       return NULL;
+       }
+       return ptr;
+}
+
+static void *enum_map_next(struct seq_file *m, void *v, loff_t *pos)
+{
+       union trace_enum_map_item *ptr = v;
+
+       /*
+        * Paranoid! If ptr points to end, we don't want to increment past it.
+        * This really should never happen.
+        */
+       ptr = update_enum_map(ptr);
+       if (WARN_ON_ONCE(!ptr))
+               return NULL;
+
+       ptr++;
+
+       (*pos)++;
+
+       ptr = update_enum_map(ptr);
+
+       return ptr;
+}
+
+static void *enum_map_start(struct seq_file *m, loff_t *pos)
+{
+       union trace_enum_map_item *v;
+       loff_t l = 0;
+
+       mutex_lock(&trace_enum_mutex);
+
+       v = trace_enum_maps;
+       if (v)
+               v++;
+
+       while (v && l < *pos) {
+               v = enum_map_next(m, v, &l);
+       }
+
+       return v;
+}
+
+static void enum_map_stop(struct seq_file *m, void *v)
+{
+       mutex_unlock(&trace_enum_mutex);
+}
+
+static int enum_map_show(struct seq_file *m, void *v)
+{
+       union trace_enum_map_item *ptr = v;
+
+       seq_printf(m, "%s %ld (%s)\n",
+                  ptr->map.enum_string, ptr->map.enum_value,
+                  ptr->map.system);
+
+       return 0;
+}
+
+static const struct seq_operations tracing_enum_map_seq_ops = {
+       .start          = enum_map_start,
+       .next           = enum_map_next,
+       .stop           = enum_map_stop,
+       .show           = enum_map_show,
+};
+
+static int tracing_enum_map_open(struct inode *inode, struct file *filp)
+{
+       if (tracing_disabled)
+               return -ENODEV;
+
+       return seq_open(filp, &tracing_enum_map_seq_ops);
+}
+
+static const struct file_operations tracing_enum_map_fops = {
+       .open           = tracing_enum_map_open,
+       .read           = seq_read,
+       .llseek         = seq_lseek,
+       .release        = seq_release,
+};
+
+static inline union trace_enum_map_item *
+trace_enum_jmp_to_tail(union trace_enum_map_item *ptr)
+{
+       /* Return tail of array given the head */
+       return ptr + ptr->head.length + 1;
+}
+
+static void
+trace_insert_enum_map_file(struct module *mod, struct trace_enum_map **start,
+                          int len)
+{
+       struct trace_enum_map **stop;
+       struct trace_enum_map **map;
+       union trace_enum_map_item *map_array;
+       union trace_enum_map_item *ptr;
+
+       stop = start + len;
+
+       /*
+        * The trace_enum_maps contains the map plus a head and tail item,
+        * where the head holds the module and length of array, and the
+        * tail holds a pointer to the next list.
+        */
+       map_array = kmalloc(sizeof(*map_array) * (len + 2), GFP_KERNEL);
+       if (!map_array) {
+               pr_warning("Unable to allocate trace enum mapping\n");
+               return;
+       }
+
+       mutex_lock(&trace_enum_mutex);
+
+       if (!trace_enum_maps)
+               trace_enum_maps = map_array;
+       else {
+               ptr = trace_enum_maps;
+               for (;;) {
+                       ptr = trace_enum_jmp_to_tail(ptr);
+                       if (!ptr->tail.next)
+                               break;
+                       ptr = ptr->tail.next;
+
+               }
+               ptr->tail.next = map_array;
+       }
+       map_array->head.mod = mod;
+       map_array->head.length = len;
+       map_array++;
+
+       for (map = start; (unsigned long)map < (unsigned long)stop; map++) {
+               map_array->map = **map;
+               map_array++;
+       }
+       memset(map_array, 0, sizeof(*map_array));
+
+       mutex_unlock(&trace_enum_mutex);
+}
+
+static void trace_create_enum_file(struct dentry *d_tracer)
+{
+       trace_create_file("enum_map", 0444, d_tracer,
+                         NULL, &tracing_enum_map_fops);
+}
+
+#else /* CONFIG_TRACE_ENUM_MAP_FILE */
+static inline void trace_create_enum_file(struct dentry *d_tracer) { }
+static inline void trace_insert_enum_map_file(struct module *mod,
+                             struct trace_enum_map **start, int len) { }
+#endif /* !CONFIG_TRACE_ENUM_MAP_FILE */
+
+static void trace_insert_enum_map(struct module *mod,
+                                 struct trace_enum_map **start, int len)
+{
+       struct trace_enum_map **map;
+
+       if (len <= 0)
+               return;
+
+       map = start;
+
+       trace_event_enum_update(map, len);
+
+       trace_insert_enum_map_file(mod, start, len);
+}
+
 static ssize_t
 tracing_set_trace_read(struct file *filp, char __user *ubuf,
                       size_t cnt, loff_t *ppos)
@@ -6542,6 +6754,88 @@ struct dentry *tracing_init_dentry(void)
        return tr->dir;
 }
 
+extern struct trace_enum_map *__start_ftrace_enum_maps[];
+extern struct trace_enum_map *__stop_ftrace_enum_maps[];
+
+static void __init trace_enum_init(void)
+{
+       int len;
+
+       len = __stop_ftrace_enum_maps - __start_ftrace_enum_maps;
+       trace_insert_enum_map(NULL, __start_ftrace_enum_maps, len);
+}
+
+#ifdef CONFIG_MODULES
+static void trace_module_add_enums(struct module *mod)
+{
+       if (!mod->num_trace_enums)
+               return;
+
+       /*
+        * Modules with bad taint do not have events created, do
+        * not bother with enums either.
+        */
+       if (trace_module_has_bad_taint(mod))
+               return;
+
+       trace_insert_enum_map(mod, mod->trace_enums, mod->num_trace_enums);
+}
+
+#ifdef CONFIG_TRACE_ENUM_MAP_FILE
+static void trace_module_remove_enums(struct module *mod)
+{
+       union trace_enum_map_item *map;
+       union trace_enum_map_item **last = &trace_enum_maps;
+
+       if (!mod->num_trace_enums)
+               return;
+
+       mutex_lock(&trace_enum_mutex);
+
+       map = trace_enum_maps;
+
+       while (map) {
+               if (map->head.mod == mod)
+                       break;
+               map = trace_enum_jmp_to_tail(map);
+               last = &map->tail.next;
+               map = map->tail.next;
+       }
+       if (!map)
+               goto out;
+
+       *last = trace_enum_jmp_to_tail(map)->tail.next;
+       kfree(map);
+ out:
+       mutex_unlock(&trace_enum_mutex);
+}
+#else
+static inline void trace_module_remove_enums(struct module *mod) { }
+#endif /* CONFIG_TRACE_ENUM_MAP_FILE */
+
+static int trace_module_notify(struct notifier_block *self,
+                              unsigned long val, void *data)
+{
+       struct module *mod = data;
+
+       switch (val) {
+       case MODULE_STATE_COMING:
+               trace_module_add_enums(mod);
+               break;
+       case MODULE_STATE_GOING:
+               trace_module_remove_enums(mod);
+               break;
+       }
+
+       return 0;
+}
+
+static struct notifier_block trace_module_nb = {
+       .notifier_call = trace_module_notify,
+       .priority = 0,
+};
+#endif /* CONFIG_MODULES */
+
 static __init int tracer_init_debugfs(void)
 {
        struct dentry *d_tracer;
@@ -6566,6 +6860,14 @@ static __init int tracer_init_debugfs(void)
        trace_create_file("saved_cmdlines_size", 0644, d_tracer,
                          NULL, &tracing_saved_cmdlines_size_fops);
 
+       trace_enum_init();
+
+       trace_create_enum_file(d_tracer);
+
+#ifdef CONFIG_MODULES
+       register_module_notifier(&trace_module_nb);
+#endif
+
 #ifdef CONFIG_DYNAMIC_FTRACE
        trace_create_file("dyn_ftrace_total_info", 0444, d_tracer,
                        &ftrace_update_tot_cnt, &tracing_dyn_info_fops);
@@ -6888,7 +7190,7 @@ void __init trace_init(void)
                        tracepoint_printk = 0;
        }
        tracer_alloc_buffers();
-       trace_event_init();     
+       trace_event_init();
 }
 
 __init static int clear_boot_tracer(void)
This page took 0.02903 seconds and 5 git commands to generate.