Merge branch 'pm-opp'
[linux-2.6-microblaze.git] / arch / s390 / kernel / debug.c
index 09b6c64..4331c7e 100644 (file)
@@ -24,6 +24,7 @@
 #include <linux/export.h>
 #include <linux/init.h>
 #include <linux/fs.h>
+#include <linux/minmax.h>
 #include <linux/debugfs.h>
 
 #include <asm/debug.h>
@@ -92,6 +93,8 @@ static int debug_hex_ascii_format_fn(debug_info_t *id, struct debug_view *view,
                                     char *out_buf, const char *in_buf);
 static int debug_sprintf_format_fn(debug_info_t *id, struct debug_view *view,
                                   char *out_buf, debug_sprintf_entry_t *curr_event);
+static void debug_areas_swap(debug_info_t *a, debug_info_t *b);
+static void debug_events_append(debug_info_t *dest, debug_info_t *src);
 
 /* globals */
 
@@ -311,24 +314,6 @@ static debug_info_t *debug_info_create(const char *name, int pages_per_area,
                goto out;
 
        rc->mode = mode & ~S_IFMT;
-
-       /* create root directory */
-       rc->debugfs_root_entry = debugfs_create_dir(rc->name,
-                                                   debug_debugfs_root_entry);
-
-       /* append new element to linked list */
-       if (!debug_area_first) {
-               /* first element in list */
-               debug_area_first = rc;
-               rc->prev = NULL;
-       } else {
-               /* append element to end of list */
-               debug_area_last->next = rc;
-               rc->prev = debug_area_last;
-       }
-       debug_area_last = rc;
-       rc->next = NULL;
-
        refcount_set(&rc->ref_count, 1);
 out:
        return rc;
@@ -388,27 +373,10 @@ static void debug_info_get(debug_info_t *db_info)
  */
 static void debug_info_put(debug_info_t *db_info)
 {
-       int i;
-
        if (!db_info)
                return;
-       if (refcount_dec_and_test(&db_info->ref_count)) {
-               for (i = 0; i < DEBUG_MAX_VIEWS; i++) {
-                       if (!db_info->views[i])
-                               continue;
-                       debugfs_remove(db_info->debugfs_entries[i]);
-               }
-               debugfs_remove(db_info->debugfs_root_entry);
-               if (db_info == debug_area_first)
-                       debug_area_first = db_info->next;
-               if (db_info == debug_area_last)
-                       debug_area_last = db_info->prev;
-               if (db_info->prev)
-                       db_info->prev->next = db_info->next;
-               if (db_info->next)
-                       db_info->next->prev = db_info->prev;
+       if (refcount_dec_and_test(&db_info->ref_count))
                debug_info_free(db_info);
-       }
 }
 
 /*
@@ -632,6 +600,31 @@ static int debug_close(struct inode *inode, struct file *file)
        return 0; /* success */
 }
 
+/* Create debugfs entries and add to internal list. */
+static void _debug_register(debug_info_t *id)
+{
+       /* create root directory */
+       id->debugfs_root_entry = debugfs_create_dir(id->name,
+                                                   debug_debugfs_root_entry);
+
+       /* append new element to linked list */
+       if (!debug_area_first) {
+               /* first element in list */
+               debug_area_first = id;
+               id->prev = NULL;
+       } else {
+               /* append element to end of list */
+               debug_area_last->next = id;
+               id->prev = debug_area_last;
+       }
+       debug_area_last = id;
+       id->next = NULL;
+
+       debug_register_view(id, &debug_level_view);
+       debug_register_view(id, &debug_flush_view);
+       debug_register_view(id, &debug_pages_view);
+}
+
 /**
  * debug_register_mode() - creates and initializes debug area.
  *
@@ -661,19 +654,16 @@ debug_info_t *debug_register_mode(const char *name, int pages_per_area,
        if ((uid != 0) || (gid != 0))
                pr_warn("Root becomes the owner of all s390dbf files in sysfs\n");
        BUG_ON(!initialized);
-       mutex_lock(&debug_mutex);
 
        /* create new debug_info */
        rc = debug_info_create(name, pages_per_area, nr_areas, buf_size, mode);
-       if (!rc)
-               goto out;
-       debug_register_view(rc, &debug_level_view);
-       debug_register_view(rc, &debug_flush_view);
-       debug_register_view(rc, &debug_pages_view);
-out:
-       if (!rc)
+       if (rc) {
+               mutex_lock(&debug_mutex);
+               _debug_register(rc);
+               mutex_unlock(&debug_mutex);
+       } else {
                pr_err("Registering debug feature %s failed\n", name);
-       mutex_unlock(&debug_mutex);
+       }
        return rc;
 }
 EXPORT_SYMBOL(debug_register_mode);
@@ -702,6 +692,82 @@ debug_info_t *debug_register(const char *name, int pages_per_area,
 }
 EXPORT_SYMBOL(debug_register);
 
+/**
+ * debug_register_static() - registers a static debug area
+ *
+ * @id: Handle for static debug area
+ * @pages_per_area: Number of pages per area
+ * @nr_areas: Number of debug areas
+ *
+ * Register debug_info_t defined using DEFINE_STATIC_DEBUG_INFO.
+ *
+ * Note: This function is called automatically via an initcall generated by
+ *      DEFINE_STATIC_DEBUG_INFO.
+ */
+void debug_register_static(debug_info_t *id, int pages_per_area, int nr_areas)
+{
+       unsigned long flags;
+       debug_info_t *copy;
+
+       if (!initialized) {
+               pr_err("Tried to register debug feature %s too early\n",
+                      id->name);
+               return;
+       }
+
+       copy = debug_info_alloc("", pages_per_area, nr_areas, id->buf_size,
+                               id->level, ALL_AREAS);
+       if (!copy) {
+               pr_err("Registering debug feature %s failed\n", id->name);
+
+               /* Clear pointers to prevent tracing into released initdata. */
+               spin_lock_irqsave(&id->lock, flags);
+               id->areas = NULL;
+               id->active_pages = NULL;
+               id->active_entries = NULL;
+               spin_unlock_irqrestore(&id->lock, flags);
+
+               return;
+       }
+
+       /* Replace static trace area with dynamic copy. */
+       spin_lock_irqsave(&id->lock, flags);
+       debug_events_append(copy, id);
+       debug_areas_swap(id, copy);
+       spin_unlock_irqrestore(&id->lock, flags);
+
+       /* Clear pointers to initdata and discard copy. */
+       copy->areas = NULL;
+       copy->active_pages = NULL;
+       copy->active_entries = NULL;
+       debug_info_free(copy);
+
+       mutex_lock(&debug_mutex);
+       _debug_register(id);
+       mutex_unlock(&debug_mutex);
+}
+
+/* Remove debugfs entries and remove from internal list. */
+static void _debug_unregister(debug_info_t *id)
+{
+       int i;
+
+       for (i = 0; i < DEBUG_MAX_VIEWS; i++) {
+               if (!id->views[i])
+                       continue;
+               debugfs_remove(id->debugfs_entries[i]);
+       }
+       debugfs_remove(id->debugfs_root_entry);
+       if (id == debug_area_first)
+               debug_area_first = id->next;
+       if (id == debug_area_last)
+               debug_area_last = id->prev;
+       if (id->prev)
+               id->prev->next = id->next;
+       if (id->next)
+               id->next->prev = id->prev;
+}
+
 /**
  * debug_unregister() - give back debug area.
  *
@@ -715,8 +781,10 @@ void debug_unregister(debug_info_t *id)
        if (!id)
                return;
        mutex_lock(&debug_mutex);
-       debug_info_put(id);
+       _debug_unregister(id);
        mutex_unlock(&debug_mutex);
+
+       debug_info_put(id);
 }
 EXPORT_SYMBOL(debug_unregister);
 
@@ -726,35 +794,28 @@ EXPORT_SYMBOL(debug_unregister);
  */
 static int debug_set_size(debug_info_t *id, int nr_areas, int pages_per_area)
 {
-       debug_entry_t ***new_areas;
+       debug_info_t *new_id;
        unsigned long flags;
-       int rc = 0;
 
        if (!id || (nr_areas <= 0) || (pages_per_area < 0))
                return -EINVAL;
-       if (pages_per_area > 0) {
-               new_areas = debug_areas_alloc(pages_per_area, nr_areas);
-               if (!new_areas) {
-                       pr_info("Allocating memory for %i pages failed\n",
-                               pages_per_area);
-                       rc = -ENOMEM;
-                       goto out;
-               }
-       } else {
-               new_areas = NULL;
+
+       new_id = debug_info_alloc("", pages_per_area, nr_areas, id->buf_size,
+                                 id->level, ALL_AREAS);
+       if (!new_id) {
+               pr_info("Allocating memory for %i pages failed\n",
+                       pages_per_area);
+               return -ENOMEM;
        }
+
        spin_lock_irqsave(&id->lock, flags);
-       debug_areas_free(id);
-       id->areas = new_areas;
-       id->nr_areas = nr_areas;
-       id->pages_per_area = pages_per_area;
-       id->active_area = 0;
-       memset(id->active_entries, 0, sizeof(int)*id->nr_areas);
-       memset(id->active_pages, 0, sizeof(int)*id->nr_areas);
+       debug_events_append(new_id, id);
+       debug_areas_swap(new_id, id);
+       debug_info_free(new_id);
        spin_unlock_irqrestore(&id->lock, flags);
        pr_info("%s: set new size (%i pages)\n", id->name, pages_per_area);
-out:
-       return rc;
+
+       return 0;
 }
 
 /**
@@ -772,16 +833,17 @@ void debug_set_level(debug_info_t *id, int new_level)
 
        if (!id)
                return;
-       spin_lock_irqsave(&id->lock, flags);
+
        if (new_level == DEBUG_OFF_LEVEL) {
-               id->level = DEBUG_OFF_LEVEL;
                pr_info("%s: switched off\n", id->name);
        } else if ((new_level > DEBUG_MAX_LEVEL) || (new_level < 0)) {
                pr_info("%s: level %i is out of range (%i - %i)\n",
                        id->name, new_level, 0, DEBUG_MAX_LEVEL);
-       } else {
-               id->level = new_level;
+               return;
        }
+
+       spin_lock_irqsave(&id->lock, flags);
+       id->level = new_level;
        spin_unlock_irqrestore(&id->lock, flags);
 }
 EXPORT_SYMBOL(debug_set_level);
@@ -821,6 +883,42 @@ static inline debug_entry_t *get_active_entry(debug_info_t *id)
                                  id->active_entries[id->active_area]);
 }
 
+/* Swap debug areas of a and b. */
+static void debug_areas_swap(debug_info_t *a, debug_info_t *b)
+{
+       swap(a->nr_areas, b->nr_areas);
+       swap(a->pages_per_area, b->pages_per_area);
+       swap(a->areas, b->areas);
+       swap(a->active_area, b->active_area);
+       swap(a->active_pages, b->active_pages);
+       swap(a->active_entries, b->active_entries);
+}
+
+/* Append all debug events in active area from source to destination log. */
+static void debug_events_append(debug_info_t *dest, debug_info_t *src)
+{
+       debug_entry_t *from, *to, *last;
+
+       if (!src->areas || !dest->areas)
+               return;
+
+       /* Loop over all entries in src, starting with oldest. */
+       from = get_active_entry(src);
+       last = from;
+       do {
+               if (from->clock != 0LL) {
+                       to = get_active_entry(dest);
+                       memset(to, 0, dest->entry_size);
+                       memcpy(to, from, min(src->entry_size,
+                                            dest->entry_size));
+                       proceed_active_entry(dest);
+               }
+
+               proceed_active_entry(src);
+               from = get_active_entry(src);
+       } while (from != last);
+}
+
 /*
  * debug_finish_entry:
  * - set timestamp, caller address, cpu number etc.
@@ -1111,16 +1209,17 @@ int debug_register_view(debug_info_t *id, struct debug_view *view)
                        break;
        }
        if (i == DEBUG_MAX_VIEWS) {
-               pr_err("Registering view %s/%s would exceed the maximum "
-                      "number of views %i\n", id->name, view->name, i);
                rc = -1;
        } else {
                id->views[i] = view;
                id->debugfs_entries[i] = pde;
        }
        spin_unlock_irqrestore(&id->lock, flags);
-       if (rc)
+       if (rc) {
+               pr_err("Registering view %s/%s would exceed the maximum "
+                      "number of views %i\n", id->name, view->name, i);
                debugfs_remove(pde);
+       }
 out:
        return rc;
 }