aboutsummaryrefslogtreecommitdiff
path: root/driver/gator_main.c
diff options
context:
space:
mode:
Diffstat (limited to 'driver/gator_main.c')
-rw-r--r--driver/gator_main.c1071
1 files changed, 1071 insertions, 0 deletions
diff --git a/driver/gator_main.c b/driver/gator_main.c
new file mode 100644
index 0000000..60b09cb
--- /dev/null
+++ b/driver/gator_main.c
@@ -0,0 +1,1071 @@
+/**
+ * Copyright (C) ARM Limited 2010-2011. All rights reserved.
+ *
+ * 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.
+ *
+ */
+
+static unsigned long gator_protocol_version = 3;
+
+#include "gator.h"
+#include <linux/slab.h>
+#include <linux/cpu.h>
+#include <linux/sched.h>
+#include <linux/irq.h>
+#include <linux/vmalloc.h>
+#include <asm/uaccess.h>
+
+#ifndef CONFIG_GENERIC_TRACER
+#ifndef CONFIG_TRACING
+#warning gator requires the kernel to have CONFIG_GENERIC_TRACER or CONFIG_TRACING defined
+#endif
+#endif
+
+#ifndef CONFIG_PROFILING
+#warning gator requires the kernel to have CONFIG_PROFILING defined
+#endif
+
+#ifndef CONFIG_HIGH_RES_TIMERS
+#warning gator requires the kernel to have CONFIG_HIGH_RES_TIMERS defined
+#endif
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 32)
+#error kernels prior to 2.6.32 are not supported
+#endif
+
+/******************************************************************************
+ * DEFINES
+ ******************************************************************************/
+#define BUFFER_SIZE_DEFAULT 262144
+#define SYNC_FREQ_DEFAULT 1000
+
+#define NO_COOKIE 0UL
+#define INVALID_COOKIE ~0UL
+
+#define PROTOCOL_FRAME ~0
+#define PROTOCOL_START_TICK 1
+#define PROTOCOL_END_TICK 3
+#define PROTOCOL_START_BACKTRACE 5
+#define PROTOCOL_END_BACKTRACE 7
+#define PROTOCOL_COOKIE 9
+#define PROTOCOL_SCHEDULER_TRACE 11
+#define PROTOCOL_COUNTERS 13
+#define PROTOCOL_ANNOTATE 15
+#define PROTOCOL_CPU_SYNC 17
+
+#if defined(__arm__)
+#define PC_REG regs->ARM_pc
+#else
+#define PC_REG regs->ip
+#endif
+
+/******************************************************************************
+ * PER CPU
+ ******************************************************************************/
+static unsigned long gator_cpu_cores;
+static unsigned long gator_buffer_size;
+static unsigned long gator_backtrace_depth;
+
+static unsigned long gator_started;
+static unsigned long gator_buffer_opened;
+static unsigned long gator_timer_count;
+static unsigned long gator_sync_freq;
+static int gator_master_tick;
+static DEFINE_MUTEX(start_mutex);
+static DEFINE_MUTEX(gator_buffer_mutex);
+
+unsigned long gator_net_traffic;
+
+#define COMMIT_SIZE 128
+#define COMMIT_MASK (COMMIT_SIZE-1)
+static DEFINE_SPINLOCK(gator_commit_lock);
+static int *gator_commit;
+static int gator_commit_read;
+static int gator_commit_write;
+
+static DECLARE_WAIT_QUEUE_HEAD(gator_buffer_wait);
+static DEFINE_PER_CPU(int, gator_cpu_sync);
+static DEFINE_PER_CPU(int, gator_cpu_tick);
+static DEFINE_PER_CPU(int, gator_first_time);
+
+/******************************************************************************
+ * Prototypes
+ ******************************************************************************/
+static void gator_buffer_write_packed_int(int cpu, unsigned int x);
+static void gator_buffer_write_string(int cpu, char *x);
+static void gator_add_trace(int cpu, unsigned int address);
+static uint64_t gator_get_time(void);
+
+/******************************************************************************
+ * Application Includes
+ ******************************************************************************/
+#include "gator_cookies.c"
+#include "gator_trace_sched.c"
+#include "gator_backtrace.c"
+#include "gator_annotate.c"
+#include "gator_events.c"
+#include "gator_fs.c"
+
+/******************************************************************************
+ * Misc
+ ******************************************************************************/
+#if defined(__arm__)
+u32 gator_cpuid(void)
+{
+ u32 val;
+ asm volatile("mrc p15, 0, %0, c0, c0, 0" : "=r" (val));
+ return (val >> 4) & 0xfff;
+}
+#endif
+
+/******************************************************************************
+ * Commit interface
+ ******************************************************************************/
+static int buffer_commit_ready(void)
+{
+ return (gator_commit_read != gator_commit_write);
+}
+
+static void buffer_commit_read(int *cpu, int *readval, int *writeval)
+{
+ int read = gator_commit_read;
+ *cpu = gator_commit[read+0];
+ *readval = gator_commit[read+1];
+ *writeval = gator_commit[read+2];
+ gator_commit_read = (read + 4) & COMMIT_MASK;
+}
+
+static void buffer_commit_write(int cpu, int readval, int writeval) {
+ int write = gator_commit_write;
+ gator_commit[write+0] = cpu;
+ gator_commit[write+1] = readval;
+ gator_commit[write+2] = writeval;
+ gator_commit_write = (write + 4) & COMMIT_MASK;
+}
+
+/******************************************************************************
+ * Buffer management
+ ******************************************************************************/
+static uint32_t use_buffer_size;
+static uint32_t use_buffer_mask;
+static DEFINE_PER_CPU(int, use_buffer_seq);
+static DEFINE_PER_CPU(int, use_buffer_read);
+static DEFINE_PER_CPU(int, use_buffer_write);
+static DEFINE_PER_CPU(char *, use_buffer);
+
+static void gator_buffer_write_packed_int(int cpu, unsigned int x)
+{
+ uint32_t write = per_cpu(use_buffer_write, cpu);
+ uint32_t mask = use_buffer_mask;
+ char *buffer = per_cpu(use_buffer, cpu);
+ int write0 = (write + 0) & mask;
+ int write1 = (write + 1) & mask;
+ int write2 = (write + 2) & mask;
+ int write3 = (write + 3) & mask;
+ int write4 = (write + 4) & mask;
+ int write5 = (write + 5) & mask;
+
+ if ((x & 0xffffff80) == 0) {
+ buffer[write0] = x & 0x7f;
+ per_cpu(use_buffer_write, cpu) = write1;
+ } else if ((x & 0xffffc000) == 0) {
+ buffer[write0] = x | 0x80;
+ buffer[write1] = (x>>7) & 0x7f;
+ per_cpu(use_buffer_write, cpu) = write2;
+ } else if ((x & 0xffe00000) == 0) {
+ buffer[write0] = x | 0x80;
+ buffer[write1] = (x>>7) | 0x80;
+ buffer[write2] = (x>>14) & 0x7f;
+ per_cpu(use_buffer_write, cpu) = write3;
+ } else if ((x & 0xf0000000) == 0) {
+ buffer[write0] = x | 0x80;
+ buffer[write1] = (x>>7) | 0x80;
+ buffer[write2] = (x>>14) | 0x80;
+ buffer[write3] = (x>>21) & 0x7f;
+ per_cpu(use_buffer_write, cpu) = write4;
+ } else {
+ buffer[write0] = x | 0x80;
+ buffer[write1] = (x>>7) | 0x80;
+ buffer[write2] = (x>>14) | 0x80;
+ buffer[write3] = (x>>21) | 0x80;
+ buffer[write4] = (x>>28) & 0x0f;
+ per_cpu(use_buffer_write, cpu) = write5;
+ }
+}
+
+static void gator_buffer_write_bytes(int cpu, char *x, int len)
+{
+ uint32_t write = per_cpu(use_buffer_write, cpu);
+ uint32_t mask = use_buffer_mask;
+ char *buffer = per_cpu(use_buffer, cpu);
+ int i;
+
+ for (i = 0; i < len; i++) {
+ buffer[write] = x[i];
+ write = (write + 1) & mask;
+ }
+
+ per_cpu(use_buffer_write, cpu) = write;
+}
+
+static void gator_buffer_write_string(int cpu, char *x)
+{
+ int len = strlen(x);
+ gator_buffer_write_packed_int(cpu, len);
+ gator_buffer_write_bytes(cpu, x, len);
+}
+
+static void gator_buffer_header(int cpu)
+{
+ gator_buffer_write_packed_int(cpu, PROTOCOL_FRAME);
+ gator_buffer_write_packed_int(cpu, cpu);
+ gator_buffer_write_packed_int(cpu, per_cpu(use_buffer_seq, cpu));
+ per_cpu(use_buffer_seq, cpu)++;
+}
+
+static void gator_buffer_commit(int cpu)
+{
+ buffer_commit_write(cpu, per_cpu(use_buffer_read, cpu), per_cpu(use_buffer_write, cpu));
+ per_cpu(use_buffer_read, cpu) = per_cpu(use_buffer_write, cpu);
+ gator_buffer_header(cpu);
+ wake_up(&gator_buffer_wait);
+}
+
+static void gator_buffer_check(int cpu, int tick)
+{
+ if (gator_sync_freq && !(tick % gator_sync_freq)) {
+ int c, sync;
+ spin_lock(&gator_commit_lock);
+ // synchronize, if all online cpus have the same tick waypoint
+ sync = per_cpu(gator_cpu_sync, cpu) = per_cpu(gator_cpu_tick, cpu);
+ for_each_online_cpu(c) {
+ if (sync != per_cpu(gator_cpu_sync, c)) {
+ sync = 0;
+ break;
+ }
+ }
+ if (sync) {
+ gator_buffer_write_packed_int(cpu, PROTOCOL_CPU_SYNC);
+ }
+ // commit the buffer
+ gator_buffer_commit(cpu);
+ spin_unlock(&gator_commit_lock);
+ } else {
+ int available = per_cpu(use_buffer_write, cpu) - per_cpu(use_buffer_read, cpu);
+ if (available < 0) {
+ available += use_buffer_size;
+ }
+ if (available >= ((use_buffer_size * 3) / 4)) {
+ spin_lock(&gator_commit_lock);
+ gator_buffer_commit(cpu);
+ spin_unlock(&gator_commit_lock);
+ }
+ }
+}
+
+static void gator_add_trace(int cpu, unsigned int address)
+{
+ off_t offset = 0;
+ unsigned long cookie = get_address_cookie(cpu, current, address & ~1, &offset);
+
+ if (cookie == NO_COOKIE || cookie == INVALID_COOKIE) {
+ offset = address;
+ }
+
+ gator_buffer_write_packed_int(cpu, offset & ~1);
+ gator_buffer_write_packed_int(cpu, cookie);
+}
+
+static void gator_add_sample(int cpu, struct pt_regs * const regs)
+{
+ int inKernel = regs ? !user_mode(regs) : 1;
+ unsigned long cookie = !inKernel ? get_exec_cookie(cpu, current) : NO_COOKIE;
+
+ gator_buffer_write_packed_int(cpu, PROTOCOL_START_BACKTRACE);
+
+ // TGID::PID::inKernel
+ gator_buffer_write_packed_int(cpu, cookie);
+ gator_buffer_write_packed_int(cpu, (unsigned int)current->tgid);
+ gator_buffer_write_packed_int(cpu, (unsigned int)current->pid);
+ gator_buffer_write_packed_int(cpu, inKernel);
+
+ // get_irq_regs() will return NULL outside of IRQ context (e.g. nested IRQ)
+ if (regs) {
+ if (inKernel) {
+ gator_buffer_write_packed_int(cpu, PC_REG & ~1);
+ gator_buffer_write_packed_int(cpu, 0); // cookie
+ } else {
+ // Cookie+PC
+ gator_add_trace(cpu, PC_REG);
+
+ // Backtrace
+ if (gator_backtrace_depth)
+ arm_backtrace_eabi(cpu, regs, gator_backtrace_depth);
+ }
+ }
+
+ gator_buffer_write_packed_int(cpu, PROTOCOL_END_BACKTRACE);
+}
+
+static void gator_write_packet(int cpu, int type, int len, int *buffer)
+{
+ int i;
+ gator_buffer_write_packed_int(cpu, type);
+ gator_buffer_write_packed_int(cpu, len);
+ for (i = 0; i < len; i++) {
+ gator_buffer_write_packed_int(cpu, buffer[i]);
+ }
+}
+
+static void gator_write_annotate(int cpu, int len, int *buffer)
+{
+ int pos = 0;
+
+ while (pos < len) {
+ unsigned int tid = buffer[pos++];
+ unsigned int bytes = buffer[pos++];
+ unsigned int words = (bytes + 3) / 4;
+ char *ptr = (char *)&buffer[pos];
+ pos += words;
+
+ gator_buffer_write_packed_int(cpu, PROTOCOL_ANNOTATE);
+ gator_buffer_write_packed_int(cpu, tid);
+ gator_buffer_write_packed_int(cpu, bytes);
+ gator_buffer_write_bytes(cpu, ptr, bytes);
+ }
+}
+
+/******************************************************************************
+ * Interrupt Processing
+ ******************************************************************************/
+static gator_interface *gi = NULL;
+
+static void gator_timer_interrupt(void)
+{
+ struct pt_regs * const regs = get_irq_regs();
+ int cpu = raw_smp_processor_id();
+ int *buffer, len, tick;
+ gator_interface *i;
+
+ // check full backtrace has enough space, otherwise may
+ // have breaks between samples in the same callstack
+ if (per_cpu(gator_first_time, cpu)) {
+ per_cpu(gator_first_time, cpu) = 0;
+
+ for (i = gi; i != NULL; i = i->next) {
+ if (i->read) {
+ i->read(NULL);
+ }
+ }
+ return;
+ }
+
+ // Header
+ gator_buffer_write_packed_int(cpu, PROTOCOL_START_TICK); // Escape
+
+ // Output scheduler
+ len = gator_trace_sched_read(&buffer);
+ if (len > 0) {
+ gator_write_packet(cpu, PROTOCOL_SCHEDULER_TRACE, len, buffer);
+ }
+
+ // Output annotate
+ len = gator_annotate_read(&buffer);
+ if (len > 0)
+ gator_write_annotate(cpu, len, buffer);
+
+ // Output counters
+ for (i = gi; i != NULL; i = i->next) {
+ if (i->read) {
+ len = i->read(&buffer);
+ if (len > 0) {
+ gator_write_packet(cpu, PROTOCOL_COUNTERS, len, buffer);
+ }
+ }
+ }
+
+ // Output backtrace
+ gator_add_sample(cpu, regs);
+
+ // Timer Tick
+ tick = per_cpu(gator_cpu_tick, cpu);
+ if (tick == gator_master_tick) {
+ tick++;
+ per_cpu(gator_cpu_tick, cpu) = gator_master_tick = tick;
+ } else {
+ per_cpu(gator_cpu_tick, cpu) = tick = gator_master_tick;
+ }
+ gator_write_packet(cpu, PROTOCOL_END_TICK, 1, &tick);
+
+ // Check and commit; generally, commit is set to occur once per second
+ gator_buffer_check(cpu, tick);
+}
+
+/******************************************************************************
+ * hrtimer
+ ******************************************************************************/
+DEFINE_PER_CPU(struct hrtimer, percpu_hrtimer);
+DEFINE_PER_CPU(int, hrtimer_is_active);
+static int hrtimer_running;
+static ktime_t profiling_interval;
+
+static enum hrtimer_restart gator_hrtimer_notify(struct hrtimer *hrtimer)
+{
+ hrtimer_forward_now(hrtimer, profiling_interval);
+ gator_timer_interrupt();
+ return HRTIMER_RESTART;
+}
+
+static int gator_timer_init(void)
+{
+ return 0;
+}
+
+static void __gator_timer_offline(void *unused)
+{
+ int cpu = smp_processor_id();
+ if (per_cpu(hrtimer_is_active, cpu)) {
+ gator_interface *i;
+ struct hrtimer *hrtimer = &per_cpu(percpu_hrtimer, cpu);
+ hrtimer_cancel(hrtimer);
+ per_cpu(hrtimer_is_active, cpu) = 0;
+ gator_buffer_commit(cpu);
+
+ // offline any events
+ for (i = gi; i != NULL; i = i->next) {
+ if (i->offline) {
+ i->offline();
+ }
+ }
+ }
+}
+
+static void gator_timer_offline(void)
+{
+ if (hrtimer_running) {
+ hrtimer_running = 0;
+
+ on_each_cpu(__gator_timer_offline, NULL, 1);
+
+ // output a final sync point
+ gator_buffer_write_packed_int(0, PROTOCOL_CPU_SYNC);
+ gator_buffer_commit(0);
+ }
+}
+
+static void __gator_timer_online(void *unused)
+{
+ int cpu = smp_processor_id();
+ if (!per_cpu(hrtimer_is_active, cpu)) {
+ gator_interface *i;
+ struct hrtimer *hrtimer = &per_cpu(percpu_hrtimer, cpu);
+ hrtimer_init(hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ hrtimer->function = gator_hrtimer_notify;
+ hrtimer_start(hrtimer, profiling_interval, HRTIMER_MODE_REL_PINNED);
+ per_cpu(gator_cpu_tick, cpu) = 0;
+ per_cpu(gator_first_time, cpu) = 1;
+ per_cpu(hrtimer_is_active, cpu) = 1;
+
+ // online any events
+ for (i = gi; i != NULL; i = i->next) {
+ if (i->online) {
+ i->online();
+ }
+ }
+ }
+}
+
+int gator_timer_online(unsigned long setup)
+{
+ if (!setup) {
+ pr_err("gator: cannot start due to a system tick value of zero");
+ return -1;
+ } else if (hrtimer_running) {
+ pr_notice("gator: high res timer already running");
+ return 0;
+ }
+
+ hrtimer_running = 1;
+
+ // calculate profiling interval
+ profiling_interval = ns_to_ktime(1000000000UL / setup);
+
+ // timer interrupt
+ gator_master_tick = 0;
+ on_each_cpu(__gator_timer_online, NULL, 1);
+
+ return 0;
+}
+
+static uint64_t gator_get_time(void)
+{
+ struct timespec ts;
+ uint64_t timestamp;
+
+ ktime_get_ts(&ts);
+ timestamp = timespec_to_ns(&ts);
+
+ return timestamp;
+}
+
+/******************************************************************************
+ * cpu online notifier
+ ******************************************************************************/
+static int __cpuinit gator_cpu_notify(struct notifier_block *self,
+ unsigned long action, void *hcpu)
+{
+ long cpu = (long)hcpu;
+
+ switch (action) {
+ case CPU_ONLINE:
+ case CPU_ONLINE_FROZEN:
+ smp_call_function_single(cpu, __gator_timer_online, NULL, 1);
+ break;
+ case CPU_DOWN_PREPARE:
+ case CPU_DOWN_PREPARE_FROZEN:
+ smp_call_function_single(cpu, __gator_timer_offline, NULL, 1);
+ break;
+ }
+
+ return NOTIFY_OK;
+}
+
+static struct notifier_block __refdata gator_cpu_notifier = {
+ .notifier_call = gator_cpu_notify,
+};
+
+static int gator_notifier_start(void)
+{
+ return register_hotcpu_notifier(&gator_cpu_notifier);
+}
+
+static void gator_notifier_stop(void)
+{
+ unregister_hotcpu_notifier(&gator_cpu_notifier);
+}
+
+/******************************************************************************
+ * Main
+ ******************************************************************************/
+int gator_event_install(int (*event_install)(gator_interface *))
+{
+ gator_interface *ni = (gator_interface*)kmalloc(sizeof(gator_interface), GFP_KERNEL);
+ if (ni == NULL) {
+ return -1;
+ }
+
+ ni->create_files = NULL;
+ ni->init = NULL;
+ ni->start = NULL;
+ ni->stop = NULL;
+ ni->online = NULL;
+ ni->offline = NULL;
+ ni->read = NULL;
+ ni->next = NULL;
+
+ // Initialize ni gator interface
+ if (!event_install(ni)) {
+ if (gi == NULL) {
+ // Set gi to point to the first gator interface
+ gi = ni;
+ } else {
+ // Link the gator interfaces
+ gator_interface *i = gi;
+ while (i->next) {
+ i = i->next;
+ }
+ i->next = ni;
+ }
+ } else {
+ kfree(ni);
+ }
+
+ return 0;
+}
+
+static int gator_init(void)
+{
+ gator_interface *i;
+ int key = 0;
+
+ if (gator_timer_init())
+ return -1;
+ if (gator_trace_sched_init())
+ return -1;
+ if (gator_annotate_init())
+ return -1;
+
+ // set up gator interface linked list structure
+ if (gator_events_install())
+ return -1;
+
+ // initialize all events
+ for (i = gi; i != NULL; i = i->next) {
+ if (i->init) {
+ if (i->init(&key)) {
+ return -1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int gator_start(void)
+{
+ gator_interface *i, *f;
+
+ // start all events
+ for (i = gi; i != NULL; i = i->next) {
+ if (i->start) {
+ if (i->start()) {
+ goto events_failure;
+ }
+ }
+ }
+
+ if (gator_annotate_start())
+ goto annotate_failure;
+ if (gator_trace_sched_start())
+ goto sched_failure;
+ if (gator_timer_online(gator_timer_count))
+ goto timer_failure;
+ if (gator_notifier_start())
+ goto notifier_failure;
+
+ return 0;
+
+notifier_failure:
+ gator_timer_offline();
+timer_failure:
+ gator_trace_sched_stop();
+sched_failure:
+ gator_annotate_stop();
+annotate_failure:
+events_failure:
+ for (f = gi; f != i; f = f->next) {
+ f->stop();
+ }
+
+ return -1;
+}
+
+static void gator_stop(void)
+{
+ gator_interface *i;
+
+ // stop all events
+ for (i = gi; i != NULL; i = i->next) {
+ if (i->stop) {
+ i->stop();
+ }
+ }
+
+ gator_annotate_stop();
+ gator_trace_sched_stop();
+
+ // stop all interrupt callback reads before tearing down other interfaces
+ gator_timer_offline();
+ gator_notifier_stop();
+}
+
+static void gator_exit(void)
+{
+ gator_interface *i = gi;
+
+ while (i) {
+ gator_interface *p = i;
+ i = i->next;
+ kfree(p);
+ }
+}
+
+/******************************************************************************
+ * Filesystem
+ ******************************************************************************/
+/* fopen("buffer") */
+static int gator_op_setup(void)
+{
+ int err = 0;
+ int cpu;
+
+ mutex_lock(&start_mutex);
+
+ use_buffer_size = gator_buffer_size;
+ use_buffer_mask = use_buffer_size - 1;
+
+ // must be a power of 2
+ if (use_buffer_size & (use_buffer_size - 1)) {
+ err = -ENOEXEC;
+ goto setup_error;
+ }
+
+ gator_net_traffic = 0;
+
+ gator_commit_read = gator_commit_write = 0;
+ gator_commit = vmalloc(COMMIT_SIZE * sizeof(int));
+ if (!gator_commit) {
+ err = -ENOMEM;
+ goto setup_error;
+ }
+
+ for_each_present_cpu(cpu) {
+ per_cpu(use_buffer, cpu) = vmalloc(use_buffer_size);
+ if (!per_cpu(use_buffer, cpu)) {
+ err = -ENOMEM;
+ goto setup_error;
+ }
+
+ per_cpu(gator_cpu_sync, cpu) = 0;
+ per_cpu(gator_cpu_tick, cpu) = 0;
+
+ per_cpu(use_buffer_seq, cpu) = 0;
+ per_cpu(use_buffer_read, cpu) = 0;
+ per_cpu(use_buffer_write, cpu) = 0;
+ gator_buffer_header(cpu);
+ }
+
+setup_error:
+ mutex_unlock(&start_mutex);
+ return err;
+}
+
+/* Actually start profiling (echo 1>/dev/gator/enable) */
+static int gator_op_start(void)
+{
+ int err = 0;
+
+ mutex_lock(&start_mutex);
+
+ if (gator_started || gator_start())
+ err = -EINVAL;
+ else
+ gator_started = 1;
+
+ cookies_initialize();
+
+ mutex_unlock(&start_mutex);
+
+ return err;
+}
+
+/* echo 0>/dev/gator/enable */
+static void gator_op_stop(void)
+{
+ mutex_lock(&start_mutex);
+
+ if (gator_started) {
+ gator_stop();
+
+ mutex_lock(&gator_buffer_mutex);
+
+ gator_started = 0;
+ cookies_release();
+ wake_up(&gator_buffer_wait);
+
+ mutex_unlock(&gator_buffer_mutex);
+ }
+
+ mutex_unlock(&start_mutex);
+}
+
+static void gator_shutdown(void)
+{
+ int cpu;
+
+ mutex_lock(&start_mutex);
+
+ vfree(gator_commit);
+ gator_commit = NULL;
+
+ for_each_present_cpu(cpu) {
+ mutex_lock(&gator_buffer_mutex);
+ vfree(per_cpu(use_buffer, cpu));
+ per_cpu(use_buffer, cpu) = NULL;
+ per_cpu(use_buffer_seq, cpu) = 0;
+ per_cpu(use_buffer_read, cpu) = 0;
+ per_cpu(use_buffer_write, cpu) = 0;
+ mutex_unlock(&gator_buffer_mutex);
+ }
+
+ mutex_unlock(&start_mutex);
+}
+
+static int gator_set_backtrace(unsigned long val)
+{
+ int err = 0;
+
+ mutex_lock(&start_mutex);
+
+ if (gator_started)
+ err = -EBUSY;
+ else
+ gator_backtrace_depth = val;
+
+ mutex_unlock(&start_mutex);
+
+ return err;
+}
+
+static ssize_t enable_read(struct file *file, char __user *buf, size_t count, loff_t *offset)
+{
+ return gatorfs_ulong_to_user(gator_started, buf, count, offset);
+}
+
+static ssize_t enable_write(struct file *file, char const __user *buf, size_t count, loff_t *offset)
+{
+ unsigned long val;
+ int retval;
+
+ if (*offset)
+ return -EINVAL;
+
+ retval = gatorfs_ulong_from_user(&val, buf, count);
+ if (retval)
+ return retval;
+
+ if (val)
+ retval = gator_op_start();
+ else
+ gator_op_stop();
+
+ if (retval)
+ return retval;
+ return count;
+}
+
+static const struct file_operations enable_fops = {
+ .read = enable_read,
+ .write = enable_write,
+};
+
+static int event_buffer_open(struct inode *inode, struct file *file)
+{
+ int err = -EPERM;
+
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ if (test_and_set_bit_lock(0, &gator_buffer_opened))
+ return -EBUSY;
+
+ if ((err = gator_op_setup()))
+ goto fail;
+
+ /* NB: the actual start happens from userspace
+ * echo 1 >/dev/gator/enable
+ */
+
+ return 0;
+
+fail:
+ __clear_bit_unlock(0, &gator_buffer_opened);
+ return err;
+}
+
+static int event_buffer_release(struct inode *inode, struct file *file)
+{
+ gator_op_stop();
+ gator_shutdown();
+ __clear_bit_unlock(0, &gator_buffer_opened);
+ return 0;
+}
+
+static ssize_t event_buffer_read(struct file *file, char __user *buf,
+ size_t count, loff_t *offset)
+{
+ int retval = -EINVAL;
+ int commit, length1, length2, read;
+ char *buffer1, *buffer2;
+ int cpu;
+
+ /* do not handle partial reads */
+ if (count != use_buffer_size || *offset)
+ return -EINVAL;
+
+ // sleep until the condition is true or a signal is received
+ // the condition is checked each time gator_buffer_wait is woken up
+ wait_event_interruptible(gator_buffer_wait, buffer_commit_ready() || !gator_started);
+
+ if (signal_pending(current))
+ return -EINTR;
+
+ if (!buffer_commit_ready())
+ return 0;
+
+ buffer_commit_read(&cpu, &read, &commit);
+
+ mutex_lock(&gator_buffer_mutex);
+
+ retval = -EFAULT;
+
+ /* May happen if the buffer is freed during pending reads. */
+ if (!per_cpu(use_buffer, cpu)) {
+ retval = -EFAULT;
+ goto out;
+ }
+
+ /* determine the size of two halves */
+ length1 = commit - read;
+ length2 = 0;
+ buffer1 = &(per_cpu(use_buffer, cpu)[read]);
+ buffer2 = &(per_cpu(use_buffer, cpu)[0]);
+ if (length1 < 0) {
+ length1 = use_buffer_size - read;
+ length2 = commit;
+ }
+
+ /* start, middle or end */
+ if (length1 > 0) {
+ if (copy_to_user(&buf[0], buffer1, length1)) {
+ goto out;
+ }
+ }
+
+ /* possible wrap around */
+ if (length2 > 0) {
+ if (copy_to_user(&buf[length1], buffer2, length2)) {
+ goto out;
+ }
+ }
+
+ retval = length1 + length2;
+
+ /* kick just in case we've lost an SMP event */
+ wake_up(&gator_buffer_wait);
+
+out:
+ // do not adjust network stats if in non-streaming buffer mode
+ if (gator_sync_freq)
+ gator_net_traffic += retval;
+ mutex_unlock(&gator_buffer_mutex);
+ return retval;
+}
+
+const struct file_operations gator_event_buffer_fops = {
+ .open = event_buffer_open,
+ .release = event_buffer_release,
+ .read = event_buffer_read,
+};
+
+static ssize_t depth_read(struct file *file, char __user *buf, size_t count, loff_t *offset)
+{
+ return gatorfs_ulong_to_user(gator_backtrace_depth, buf, count,
+ offset);
+}
+
+static ssize_t depth_write(struct file *file, char const __user *buf, size_t count, loff_t *offset)
+{
+ unsigned long val;
+ int retval;
+
+ if (*offset)
+ return -EINVAL;
+
+ retval = gatorfs_ulong_from_user(&val, buf, count);
+ if (retval)
+ return retval;
+
+ retval = gator_set_backtrace(val);
+
+ if (retval)
+ return retval;
+ return count;
+}
+
+static const struct file_operations depth_fops = {
+ .read = depth_read,
+ .write = depth_write
+};
+
+static const char gator_cpu_type[] = "gator";
+
+static ssize_t cpu_type_read(struct file *file, char __user *buf, size_t count, loff_t *offset)
+{
+ return gatorfs_str_to_user(gator_cpu_type, buf, count, offset);
+}
+
+static const struct file_operations cpu_type_fops = {
+ .read = cpu_type_read,
+};
+
+void gator_op_create_files(struct super_block *sb, struct dentry *root)
+{
+ struct dentry *dir;
+ gator_interface *i;
+ int cpu;
+
+ /* reinitialize default values */
+ gator_cpu_cores = 0;
+ for_each_present_cpu(cpu) {
+ gator_cpu_cores++;
+ }
+ gator_buffer_size = BUFFER_SIZE_DEFAULT;
+ gator_sync_freq = SYNC_FREQ_DEFAULT;
+
+ gatorfs_create_file(sb, root, "enable", &enable_fops);
+ gatorfs_create_file(sb, root, "buffer", &gator_event_buffer_fops);
+ gatorfs_create_file(sb, root, "backtrace_depth", &depth_fops);
+ gatorfs_create_file(sb, root, "cpu_type", &cpu_type_fops);
+ gatorfs_create_ulong(sb, root, "cpu_cores", &gator_cpu_cores);
+ gatorfs_create_ulong(sb, root, "buffer_size", &gator_buffer_size);
+ gatorfs_create_ulong(sb, root, "tick", &gator_timer_count);
+ gatorfs_create_ulong(sb, root, "sync_freq", &gator_sync_freq);
+ gatorfs_create_ro_ulong(sb, root, "version", &gator_protocol_version);
+
+ // Annotate interface
+ gator_annotate_create_files(sb, root);
+
+ // Linux Events
+ dir = gatorfs_mkdir(sb, root, "events");
+ for (i = gi; i != NULL; i = i->next) {
+ if (i->create_files) {
+ i->create_files(sb, dir);
+ }
+ }
+}
+
+/******************************************************************************
+ * Module
+ ******************************************************************************/
+static int gator_initialized;
+
+static int __init gator_module_init(void)
+{
+ if (gatorfs_register()) {
+ return -1;
+ }
+
+ if (gator_init()) {
+ gatorfs_unregister();
+ return -1;
+ }
+
+ gator_initialized = 1;
+#ifdef GATOR_DEBUG
+ pr_err("gator_module_init");
+#endif
+ return 0;
+}
+
+static void __exit gator_module_exit(void)
+{
+#ifdef GATOR_DEBUG
+ pr_err("gator_module_exit");
+#endif
+ tracepoint_synchronize_unregister();
+ gatorfs_unregister();
+ if (gator_initialized) {
+ gator_initialized = 0;
+ gator_exit();
+ }
+}
+
+module_init(gator_module_init);
+module_exit(gator_module_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("ARM Ltd");
+MODULE_DESCRIPTION("Gator system profiler");