x86/ptrace: Fix regs_get_argument_nth() to add correct offset
[pandora-kernel.git] / arch / x86 / kernel / ds_selftest.c
index cccc19a..6bc7c19 100644 (file)
 #include <linux/kernel.h>
 #include <linux/string.h>
 #include <linux/smp.h>
+#include <linux/cpu.h>
 
 #include <asm/ds.h>
 
 
-#define BUFFER_SIZE 1021 /* Intentionally chose an odd size. */
+#define BUFFER_SIZE            521     /* Intentionally chose an odd size. */
+#define SMALL_BUFFER_SIZE      24      /* A single bts entry. */
 
+struct ds_selftest_bts_conf {
+       struct bts_tracer *tracer;
+       int error;
+       int (*suspend)(struct bts_tracer *);
+       int (*resume)(struct bts_tracer *);
+};
 
 static int ds_selftest_bts_consistency(const struct bts_trace *trace)
 {
@@ -125,36 +133,32 @@ static int ds_selftest_bts_read(struct bts_tracer *tracer,
        return 0;
 }
 
-int ds_selftest_bts(void)
+static void ds_selftest_bts_cpu(void *arg)
 {
+       struct ds_selftest_bts_conf *conf = arg;
        const struct bts_trace *trace;
-       struct bts_tracer *tracer;
-       int error = 0;
        void *top;
-       unsigned char buffer[BUFFER_SIZE];
 
-       printk(KERN_INFO "[ds] bts selftest...");
-
-       tracer = ds_request_bts_cpu(smp_processor_id(), buffer, BUFFER_SIZE,
-                                   NULL, (size_t)-1, BTS_KERNEL);
-       if (IS_ERR(tracer)) {
-               error = PTR_ERR(tracer);
-               tracer = NULL;
+       if (IS_ERR(conf->tracer)) {
+               conf->error = PTR_ERR(conf->tracer);
+               conf->tracer = NULL;
 
                printk(KERN_CONT
-                      "initialization failed (err: %d)...", error);
-               goto out;
+                      "initialization failed (err: %d)...", conf->error);
+               return;
        }
 
-       /* The return should already give us enough trace. */
-       ds_suspend_bts(tracer);
+       /* We should meanwhile have enough trace. */
+       conf->error = conf->suspend(conf->tracer);
+       if (conf->error < 0)
+               return;
 
        /* Let's see if we can access the trace. */
-       trace = ds_read_bts(tracer);
+       trace = ds_read_bts(conf->tracer);
 
-       error = ds_selftest_bts_consistency(trace);
-       if (error < 0)
-               goto out;
+       conf->error = ds_selftest_bts_consistency(trace);
+       if (conf->error < 0)
+               return;
 
        /* If everything went well, we should have a few trace entries. */
        if (trace->ds.top == trace->ds.begin) {
@@ -168,10 +172,11 @@ int ds_selftest_bts(void)
        }
 
        /* Let's try to read the trace we collected. */
-       error = ds_selftest_bts_read(tracer, trace,
+       conf->error =
+               ds_selftest_bts_read(conf->tracer, trace,
                                     trace->ds.begin, trace->ds.top);
-       if (error < 0)
-               goto out;
+       if (conf->error < 0)
+               return;
 
        /*
         * Let's read the trace again.
@@ -179,26 +184,31 @@ int ds_selftest_bts(void)
         */
        top = trace->ds.top;
 
-       trace = ds_read_bts(tracer);
-       error = ds_selftest_bts_consistency(trace);
-       if (error < 0)
-               goto out;
+       trace = ds_read_bts(conf->tracer);
+       conf->error = ds_selftest_bts_consistency(trace);
+       if (conf->error < 0)
+               return;
 
        if (top != trace->ds.top) {
                printk(KERN_CONT "suspend not working...");
-               error = -1;
-               goto out;
+               conf->error = -1;
+               return;
        }
 
        /* Let's collect some more trace - see if resume is working. */
-       ds_resume_bts(tracer);
-       ds_suspend_bts(tracer);
+       conf->error = conf->resume(conf->tracer);
+       if (conf->error < 0)
+               return;
 
-       trace = ds_read_bts(tracer);
+       conf->error = conf->suspend(conf->tracer);
+       if (conf->error < 0)
+               return;
 
-       error = ds_selftest_bts_consistency(trace);
-       if (error < 0)
-               goto out;
+       trace = ds_read_bts(conf->tracer);
+
+       conf->error = ds_selftest_bts_consistency(trace);
+       if (conf->error < 0)
+               return;
 
        if (trace->ds.top == top) {
                /*
@@ -210,35 +220,186 @@ int ds_selftest_bts(void)
                printk(KERN_CONT
                       "no resume progress/overflow...");
 
-               error = ds_selftest_bts_read(tracer, trace,
+               conf->error =
+                       ds_selftest_bts_read(conf->tracer, trace,
                                             trace->ds.begin, trace->ds.end);
        } else if (trace->ds.top < top) {
                /*
                 * We had a buffer overflow - the entire buffer should
                 * contain trace records.
                 */
-               error = ds_selftest_bts_read(tracer, trace,
+               conf->error =
+                       ds_selftest_bts_read(conf->tracer, trace,
                                             trace->ds.begin, trace->ds.end);
        } else {
                /*
                 * It is quite likely that the buffer did not overflow.
                 * Let's just check the delta trace.
                 */
-               error = ds_selftest_bts_read(tracer, trace,
-                                            top, trace->ds.top);
+               conf->error =
+                       ds_selftest_bts_read(conf->tracer, trace, top,
+                                            trace->ds.top);
+       }
+       if (conf->error < 0)
+               return;
+
+       conf->error = 0;
+}
+
+static int ds_suspend_bts_wrap(struct bts_tracer *tracer)
+{
+       ds_suspend_bts(tracer);
+       return 0;
+}
+
+static int ds_resume_bts_wrap(struct bts_tracer *tracer)
+{
+       ds_resume_bts(tracer);
+       return 0;
+}
+
+static void ds_release_bts_noirq_wrap(void *tracer)
+{
+       (void)ds_release_bts_noirq(tracer);
+}
+
+static int ds_selftest_bts_bad_release_noirq(int cpu,
+                                            struct bts_tracer *tracer)
+{
+       int error = -EPERM;
+
+       /* Try to release the tracer on the wrong cpu. */
+       get_cpu();
+       if (cpu != smp_processor_id()) {
+               error = ds_release_bts_noirq(tracer);
+               if (error != -EPERM)
+                       printk(KERN_CONT "release on wrong cpu...");
+       }
+       put_cpu();
+
+       return error ? 0 : -1;
+}
+
+static int ds_selftest_bts_bad_request_cpu(int cpu, void *buffer)
+{
+       struct bts_tracer *tracer;
+       int error;
+
+       /* Try to request cpu tracing while task tracing is active. */
+       tracer = ds_request_bts_cpu(cpu, buffer, BUFFER_SIZE, NULL,
+                                   (size_t)-1, BTS_KERNEL);
+       error = PTR_ERR(tracer);
+       if (!IS_ERR(tracer)) {
+               ds_release_bts(tracer);
+               error = 0;
+       }
+
+       if (error != -EPERM)
+               printk(KERN_CONT "cpu/task tracing overlap...");
+
+       return error ? 0 : -1;
+}
+
+static int ds_selftest_bts_bad_request_task(void *buffer)
+{
+       struct bts_tracer *tracer;
+       int error;
+
+       /* Try to request cpu tracing while task tracing is active. */
+       tracer = ds_request_bts_task(current, buffer, BUFFER_SIZE, NULL,
+                                   (size_t)-1, BTS_KERNEL);
+       error = PTR_ERR(tracer);
+       if (!IS_ERR(tracer)) {
+               error = 0;
+               ds_release_bts(tracer);
+       }
+
+       if (error != -EPERM)
+               printk(KERN_CONT "task/cpu tracing overlap...");
+
+       return error ? 0 : -1;
+}
+
+int ds_selftest_bts(void)
+{
+       struct ds_selftest_bts_conf conf;
+       unsigned char buffer[BUFFER_SIZE], *small_buffer;
+       unsigned long irq;
+       int cpu;
+
+       printk(KERN_INFO "[ds] bts selftest...");
+       conf.error = 0;
+
+       small_buffer = (unsigned char *)ALIGN((unsigned long)buffer, 8) + 8;
+
+       get_online_cpus();
+       for_each_online_cpu(cpu) {
+               conf.suspend = ds_suspend_bts_wrap;
+               conf.resume = ds_resume_bts_wrap;
+               conf.tracer =
+                       ds_request_bts_cpu(cpu, buffer, BUFFER_SIZE,
+                                          NULL, (size_t)-1, BTS_KERNEL);
+               ds_selftest_bts_cpu(&conf);
+               if (conf.error >= 0)
+                       conf.error = ds_selftest_bts_bad_request_task(buffer);
+               ds_release_bts(conf.tracer);
+               if (conf.error < 0)
+                       goto out;
+
+               conf.suspend = ds_suspend_bts_noirq;
+               conf.resume = ds_resume_bts_noirq;
+               conf.tracer =
+                       ds_request_bts_cpu(cpu, buffer, BUFFER_SIZE,
+                                          NULL, (size_t)-1, BTS_KERNEL);
+               smp_call_function_single(cpu, ds_selftest_bts_cpu, &conf, 1);
+               if (conf.error >= 0) {
+                       conf.error =
+                               ds_selftest_bts_bad_release_noirq(cpu,
+                                                                 conf.tracer);
+                       /* We must not release the tracer twice. */
+                       if (conf.error < 0)
+                               conf.tracer = NULL;
+               }
+               if (conf.error >= 0)
+                       conf.error = ds_selftest_bts_bad_request_task(buffer);
+               smp_call_function_single(cpu, ds_release_bts_noirq_wrap,
+                                        conf.tracer, 1);
+               if (conf.error < 0)
+                       goto out;
        }
-       if (error < 0)
+
+       conf.suspend = ds_suspend_bts_wrap;
+       conf.resume = ds_resume_bts_wrap;
+       conf.tracer =
+               ds_request_bts_task(current, buffer, BUFFER_SIZE,
+                                   NULL, (size_t)-1, BTS_KERNEL);
+       ds_selftest_bts_cpu(&conf);
+       if (conf.error >= 0)
+               conf.error = ds_selftest_bts_bad_request_cpu(0, buffer);
+       ds_release_bts(conf.tracer);
+       if (conf.error < 0)
                goto out;
 
-       error = 0;
+       conf.suspend = ds_suspend_bts_noirq;
+       conf.resume = ds_resume_bts_noirq;
+       conf.tracer =
+               ds_request_bts_task(current, small_buffer, SMALL_BUFFER_SIZE,
+                                  NULL, (size_t)-1, BTS_KERNEL);
+       local_irq_save(irq);
+       ds_selftest_bts_cpu(&conf);
+       if (conf.error >= 0)
+               conf.error = ds_selftest_bts_bad_request_cpu(0, buffer);
+       ds_release_bts_noirq(conf.tracer);
+       local_irq_restore(irq);
+       if (conf.error < 0)
+               goto out;
 
-       /* The final test: release the tracer while tracing is suspended. */
+       conf.error = 0;
  out:
-       ds_release_bts(tracer);
+       put_online_cpus();
+       printk(KERN_CONT "%s.\n", (conf.error ? "failed" : "passed"));
 
-       printk(KERN_CONT "%s.\n", (error ? "failed" : "passed"));
-
-       return error;
+       return conf.error;
 }
 
 int ds_selftest_pebs(void)