This patch add the function that call perf function and bt_pages that
can record the data that get from perf.

The interface is in "/sys/kernel/debug/bloodtest/perf".
"on" is the switch.  When it set to 1, access "test" will call perf.
There are "perf_config", "perf_freq", "perf_period", "perf_type" can
set the options of perf.
After record, access "str" will get the record data in string.
Access "cpu0/page" will get the record data in binary that is format is
in "bin_format".

Signed-off-by: Hui Zhu <zhu...@xiaomi.com>
---
 kernel/bloodtest/Makefile   |   4 +-
 kernel/bloodtest/core.c     |  76 +++---
 kernel/bloodtest/internal.h |  43 +++-
 kernel/bloodtest/pages.c    | 266 ++++++++++++++++++++
 kernel/bloodtest/perf.c     | 591 ++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 931 insertions(+), 49 deletions(-)
 create mode 100644 kernel/bloodtest/pages.c
 create mode 100644 kernel/bloodtest/perf.c

diff --git a/kernel/bloodtest/Makefile b/kernel/bloodtest/Makefile
index 7f289af..79b7ea0 100644
--- a/kernel/bloodtest/Makefile
+++ b/kernel/bloodtest/Makefile
@@ -1 +1,3 @@
-obj-y  = core.o kernel_stat.o
+obj-y  = core.o pages.o kernel_stat.o
+
+obj-$(CONFIG_PERF_EVENTS) += perf.o
diff --git a/kernel/bloodtest/core.c b/kernel/bloodtest/core.c
index 7b39cbb..5ba800c 100644
--- a/kernel/bloodtest/core.c
+++ b/kernel/bloodtest/core.c
@@ -6,31 +6,17 @@
 
 #include "internal.h"
 
-enum bt_stat_enum bt_stat;
-DEFINE_SPINLOCK(bt_lock);
+DECLARE_RWSEM(bt_lock);
 
 static DECLARE_WAIT_QUEUE_HEAD(bt_wq);
 static struct hrtimer bt_timer;
 static ktime_t bt_ktime;
-
-static bool is_bt_stat(enum bt_stat_enum stat)
-{
-       unsigned long flags;
-       bool ret = false;
-
-       spin_lock_irqsave(&bt_lock, flags);
-       if (bt_stat == stat)
-               ret = true;
-       spin_unlock_irqrestore(&bt_lock, flags);
-
-       return ret;
-}
+static bool bt_timer_stop;
 
 /* This function must be called under the protection of bt_lock.  */
 static void bt_insert(void)
 {
-       bt_stat = bt_running;
-
+       bt_insert_perf();
        bt_insert_kernel_stat();
 }
 
@@ -38,8 +24,13 @@ static void bt_insert(void)
 static void bt_pullout(void)
 {
        bt_pullout_kernel_stat();
+       bt_pullout_perf();
+}
 
-       bt_stat = bt_done;
+/* This function must be called under the protection of bt_lock.  */
+static void bt_task_pullout(void)
+{
+       bt_task_pullout_perf();
 }
 
 /* This function must be called under the protection of bt_lock.  */
@@ -50,38 +41,33 @@ static void bt_report(struct seq_file *p)
 
 static enum hrtimer_restart bt_timer_fn(struct hrtimer *data)
 {
-       spin_lock(&bt_lock);
        bt_pullout();
-       spin_unlock(&bt_lock);
 
-       wake_up_interruptible_all(&bt_wq);
+       bt_timer_stop = true;
+       wake_up_all(&bt_wq);
 
        return HRTIMER_NORESTART;
 }
 
-static int test_show(struct seq_file *p, void *v)
+static int test_show(struct seq_file *p, void *unused)
 {
-       int ret = 0;
+       down_write(&bt_lock);
 
-       spin_lock(&bt_lock);
-       if (bt_stat == bt_running)
-               goto wait;
+       bt_timer_stop = false;
 
-       hrtimer_start(&bt_timer, bt_ktime, HRTIMER_MODE_REL);
        bt_insert();
+       hrtimer_start(&bt_timer, bt_ktime, HRTIMER_MODE_REL);
 
-wait:
-       spin_unlock(&bt_lock);
-       ret = wait_event_interruptible(bt_wq, is_bt_stat(bt_done));
-       if (ret)
-               goto out;
+       wait_event(bt_wq, bt_timer_stop);
 
-       spin_lock(&bt_lock);
-       bt_report(p);
-       spin_unlock(&bt_lock);
+       bt_task_pullout();
+       up_write(&bt_lock);
 
-out:
-       return ret;
+       down_read(&bt_lock);
+       bt_report(p);
+       up_read(&bt_lock);
+       
+       return 0;
 }
 
 static int test_open(struct inode *inode, struct file *file)
@@ -98,20 +84,28 @@ static int test_open(struct inode *inode, struct file *file)
 
 static int __init bt_init(void)
 {
-       struct dentry *d, *t;
+       int ret = -ENOMEM;
+       struct dentry *d = NULL, *t = NULL;
 
        d = debugfs_create_dir("bloodtest", NULL);
        if (!d)
-               return -ENOMEM;
+               goto out;
        t = debugfs_create_file("test", S_IRUSR, d, NULL, &test_fops);
        if (!t)
-               return -ENOMEM;
+               goto out;
 
        hrtimer_init(&bt_timer, CLOCK_REALTIME, HRTIMER_MODE_REL);
        bt_timer.function = bt_timer_fn;
        bt_ktime = ktime_set(1, 0);
 
-       return 0;
+       ret = bt_perf_init(d);
+
+out:
+       if (ret != 0) {
+               debugfs_remove(t);
+               debugfs_remove(d);
+       }
+       return ret;
 }
 
 core_initcall(bt_init);
diff --git a/kernel/bloodtest/internal.h b/kernel/bloodtest/internal.h
index 48faf4d..f6befc4 100644
--- a/kernel/bloodtest/internal.h
+++ b/kernel/bloodtest/internal.h
@@ -3,17 +3,46 @@
 
 #include <linux/seq_file.h>
 
-enum bt_stat_enum {
-       bt_empty = 0,
-       bt_done,
-       bt_running,
-};
+extern struct rw_semaphore bt_lock;
+
+struct bt_pages {
+       struct page **pages;
+       unsigned int pages_num;
+
+       int node;
 
-extern enum bt_stat_enum bt_stat;
-extern spinlock_t bt_lock;
+       unsigned int entry_size;
+       unsigned int entry_max_per_page;
+
+       void *entry_next;
+       unsigned int entry_count_in_page;
+       unsigned int index;
+};
+extern const struct file_operations bt_pages_bin_fops;
+extern int bt_pages_entry_num_get(void *data, u64 *val);
+extern int bt_pages_page_num_get(void *data, u64 *val);
+extern void bt_pages_clear(struct bt_pages *pages);
+extern int bt_pages_setup(struct bt_pages *pages, unsigned int entry_size,
+                         unsigned int entry_max, int cpu);
+extern void bt_pages_release(struct bt_pages *pages);
+extern void *bt_pages_alloc_entry(struct bt_pages *pages);
+extern void *bt_pages_get_entry(struct bt_pages *pages, unsigned int *index,
+                               void *prev_entry);
 
 extern void bt_insert_kernel_stat(void);
 extern void bt_pullout_kernel_stat(void);
 extern void bt_report_kernel_stat(struct seq_file *p);
 
+#ifdef CONFIG_PERF_EVENTS
+extern void bt_insert_perf(void);
+extern void bt_pullout_perf(void);
+extern void bt_task_pullout_perf(void);
+extern int bt_perf_init(struct dentry *d);
+#else
+static inline void bt_insert_perf(void)                        { }
+static inline void bt_pullout_perf(void)               { }
+static inline void bt_task_pullout_perf(void)          { }
+static inline int bt_perf_init(struct dentry *d)       { return 0; }
+#endif
+
 #endif /* _KERNEL_BLOODTEST_INTERNAL_H */
diff --git a/kernel/bloodtest/pages.c b/kernel/bloodtest/pages.c
new file mode 100644
index 0000000..077b7b8
--- /dev/null
+++ b/kernel/bloodtest/pages.c
@@ -0,0 +1,266 @@
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/fs.h>
+
+#include "internal.h"
+
+static unsigned int
+bt_pages_entry_num(struct bt_pages *pages)
+{
+       return pages->index * pages->entry_max_per_page
+               + pages->entry_count_in_page;
+}
+
+static inline unsigned int
+bt_pages_page_num(struct bt_pages *pages)
+{
+       return pages->index + (pages->entry_count_in_page ? 1 : 0);
+}
+
+int bt_pages_entry_num_get(void *data, u64 *val)
+{
+       struct bt_pages *pages = data;
+
+       down_read(&bt_lock);
+
+       *val = (u64)bt_pages_entry_num(pages);
+
+       up_read(&bt_lock);
+
+       return 0;
+}
+
+int bt_pages_page_num_get(void *data, u64 *val)
+{
+       struct bt_pages *pages = data;
+
+       down_read(&bt_lock);
+
+       *val = (u64)bt_pages_page_num(pages);
+
+       up_read(&bt_lock);
+
+       return 0;
+}
+
+void bt_pages_clear(struct bt_pages *pages)
+{
+       pages->entry_count_in_page = 0;
+       pages->index = 0;
+       pages->entry_next = page_address(pages->pages[0]);
+}
+
+/* pages must be memset to zero before call this function.  */
+
+int bt_pages_setup(struct bt_pages *pages,
+                  unsigned int entry_size, unsigned int entry_max,
+                  int cpu)
+{
+       int i;
+
+       pages->entry_size = entry_size;
+       pages->entry_max_per_page = PAGE_SIZE / entry_size;
+
+       if (cpu >= 0)
+               pages->node = cpu_to_node(cpu);
+       else
+               pages->node = NUMA_NO_NODE;
+
+       pages->pages_num = entry_max / pages->entry_max_per_page;
+       if (entry_max % pages->entry_max_per_page)
+               pages->pages_num++;
+       pages->pages = kmalloc_node(sizeof(struct page *) * pages->pages_num,
+                                   GFP_KERNEL | __GFP_ZERO,
+                                   pages->node);
+       if (!pages->pages)
+               return -ENOMEM;
+
+       for (i = 0; i < pages->pages_num; i++) {
+               pages->pages[i] = alloc_pages_node(pages->node,
+                                                  GFP_KERNEL | __GFP_ZERO,
+                                                  0);
+               if (!pages->pages[i])
+                       return -ENOMEM;
+               clear_page(page_address(pages->pages[i]));
+       }
+
+       bt_pages_clear(pages);
+
+       return 0;
+}
+
+void bt_pages_release(struct bt_pages *pages)
+{
+       int i;
+
+       if (!pages->pages)
+               return;
+
+       for (i = 0; i < pages->pages_num; i++) {
+               if (pages->pages[i])
+                       __free_page(pages->pages[i]);
+       }
+
+       kfree(pages->pages);
+
+       memset(pages, 0, sizeof(struct bt_pages));
+}
+
+void *
+bt_pages_alloc_entry(struct bt_pages *pages)
+{
+       void *ret = pages->entry_next;
+
+       if (!ret)
+               goto out;
+
+       pages->entry_count_in_page ++;
+
+       if (pages->entry_count_in_page >= pages->entry_max_per_page) {
+               /* Goto next page.  */
+               pages->index ++;
+               pages->entry_count_in_page = 0;
+               if (pages->index >= pages->pages_num) {
+                       /* Pages is full.  */
+                       pages->entry_next = NULL;
+               } else
+                       pages->entry_next
+                               = page_address(pages->pages[pages->index]);
+       } else
+               pages->entry_next += pages->entry_size;
+
+out:
+       return ret;
+}
+
+void *
+bt_pages_get_entry(struct bt_pages *pages, unsigned int *index, void 
*prev_entry)
+{
+       unsigned int max_size;
+       void *last_entry;
+
+get_entry:
+       if (*index > pages->index)
+               return NULL;
+
+       if (*index == pages->index)
+               max_size = pages->entry_count_in_page * pages->entry_size;
+       else
+               max_size = pages->entry_max_per_page * pages->entry_size;
+
+       if (max_size == 0)
+               return NULL;
+
+       if (!prev_entry)
+               return page_address(pages->pages[*index]);
+
+       last_entry = (void *)(((unsigned long)prev_entry & PAGE_MASK) +
+                             max_size - pages->entry_size);
+
+       if (prev_entry >= last_entry) {
+               /* Goto use next page.  */
+               (*index)++;
+               prev_entry = NULL;
+               goto get_entry;
+       }
+
+       return prev_entry + pages->entry_size;
+}
+
+static int bt_pages_bin_open(struct inode *inode, struct file *file)
+{
+       down_read(&bt_lock);
+
+       file->private_data = inode->i_private;
+
+       return 0;
+}
+
+static int bt_pages_bin_release(struct inode *inode, struct file *file)
+{
+       up_read(&bt_lock);
+
+       return 0;
+}
+
+ssize_t bt_pages_bin_read(struct file *file, char __user *buf,
+                         size_t len, loff_t *ppos)
+{
+       pgoff_t index;
+       loff_t offset;
+       size_t copied = 0;
+       struct bt_pages *pages;
+
+       if (!access_ok(VERIFY_WRITE, buf, len))
+               return -EFAULT;
+
+       pages = file->private_data;
+       offset = *ppos;
+       index = offset >> PAGE_SHIFT;
+       offset &= ~PAGE_MASK;
+
+       for (; index <= pages->index; index++) {
+               size_t copy_size;
+
+               if (len == 0)
+                       break;
+
+               /* Get copy_size.  */
+               if (index == pages->index) {
+                       copy_size = pages->entry_count_in_page *
+                                   pages->entry_size;
+                       if (copy_size == 0 || copy_size <= offset)
+                               break;
+                       copy_size -= offset;
+               } else
+                       copy_size = PAGE_SIZE - offset;
+               if (copy_size > len)
+                       copy_size = len;
+
+               if (__copy_to_user(buf,
+                                  page_address(pages->pages[index]) + offset,
+                                  copy_size))
+                       return -EFAULT;
+
+               buf += copy_size;
+               len -= copy_size;
+               copied += copy_size;
+               offset = 0;
+       }
+
+       *ppos += copied;
+       return copied;
+}
+
+static int bt_pages_bin_fault(struct vm_fault *vmf)
+{
+       struct file *file = vmf->vma->vm_file;
+       struct bt_pages *pages = file->private_data;
+
+       if (vmf->pgoff >= bt_pages_page_num(pages))
+               return VM_FAULT_SIGBUS;
+
+       vmf->page = pages->pages[vmf->pgoff];
+       return 0;
+}
+
+static const struct vm_operations_struct bt_pages_bin_vm_ops = {
+       .fault          = bt_pages_bin_fault,
+};
+
+int bt_pages_bin_mmap(struct file *file, struct vm_area_struct *vma)
+{
+       if ((vma->vm_flags & VM_SHARED) && (vma->vm_flags & VM_MAYWRITE))
+               return -EINVAL;
+
+       vma->vm_ops = &bt_pages_bin_vm_ops;
+       return 0;
+}
+
+const struct file_operations bt_pages_bin_fops = {
+       .open           = bt_pages_bin_open,
+       .release        = bt_pages_bin_release,
+       .read           = bt_pages_bin_read,
+       .mmap           = bt_pages_bin_mmap,
+};
diff --git a/kernel/bloodtest/perf.c b/kernel/bloodtest/perf.c
new file mode 100644
index 0000000..cf23844
--- /dev/null
+++ b/kernel/bloodtest/perf.c
@@ -0,0 +1,591 @@
+#include <linux/perf_event.h>
+#include <linux/debugfs.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/stddef.h>
+
+#include "internal.h"
+
+struct perf_entry {
+       u64 pc;
+       u8 is_user;
+       s16 oom_score_adj;
+       char comm[TASK_COMM_LEN];
+};
+
+static bool perf_on;
+static bool perf_use_freq = true;
+static unsigned int perf_type = PERF_TYPE_SOFTWARE;
+static unsigned int perf_config = PERF_COUNT_SW_CPU_CLOCK;
+static unsigned int perf_period = 1000;
+static unsigned int perf_freq = 200;
+static unsigned int rec_max = 210;
+struct perf_rec {
+       struct perf_event *event;
+
+       atomic_t is_running;
+
+       /* Record entry.  */
+       struct bt_pages pages;
+       unsigned int drop;
+
+       struct dentry *dir;
+       struct dentry *number_fs;
+       struct dentry *page_fs;
+       struct dentry *drop_fs;
+       struct dentry *bin_fs;
+} __percpu *percpu_rec;
+
+struct dentry *perf_dir;
+struct dentry *perf_str_dir;
+
+static int perf_number_get(void *data, u64 *val)
+{
+       unsigned int *number_point = data;
+
+       down_read(&bt_lock);
+
+       *val = (u64)*number_point;
+
+       up_read(&bt_lock);
+
+       return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(perf_number_fops, perf_number_get, NULL, "%llu\n");
+
+static void perf_overflow_handler(struct perf_event *event,
+               struct perf_sample_data *data,
+               struct pt_regs *regs)
+{
+       struct perf_rec *rec = this_cpu_ptr(percpu_rec);
+       struct perf_entry *entry;
+
+       if (!atomic_read(&rec->is_running))
+               return;
+
+       entry = bt_pages_alloc_entry(&rec->pages);
+       if (entry) {
+               struct task_struct *p;
+
+               entry->pc = instruction_pointer(regs);
+               entry->is_user = user_mode(regs);
+
+               rcu_read_lock();
+               if (thread_group_leader(current))
+                       p = current;
+               else
+                       p = current->group_leader;
+               strncpy(entry->comm, p->comm, sizeof(p->comm));
+               entry->oom_score_adj = p->signal->oom_score_adj;
+               rcu_read_unlock();
+       } else
+               rec->drop++;
+}
+
+static int perf_event_creat(void)
+{
+       int ret = 0;
+       struct perf_event_attr attr;
+       int cpu;
+
+       /* Setup attr.  */
+       memset(&attr, 0, sizeof(attr));
+       attr.type = perf_type;
+       attr.config = perf_config;
+       attr.disabled = true;
+       attr.pinned = true;
+       attr.freq = perf_use_freq;
+       if (attr.freq) {
+               attr.sample_freq = perf_freq;
+               pr_info("bloodtest: perf freq %llu\n",
+                       (unsigned long long) attr.sample_freq);
+       } else {
+               attr.sample_period = perf_period;
+               pr_info("bloodtest: perf period %llu\n",
+                       (unsigned long long) attr.sample_period);
+       }
+       attr.size = sizeof(attr);
+
+       for_each_possible_cpu(cpu) {
+               struct perf_rec *rec = per_cpu_ptr(percpu_rec, cpu);
+
+               /* Set event.  */
+               rec->event = perf_event_create_kernel_counter(&attr, cpu, NULL,
+                                                       perf_overflow_handler,
+                                                       NULL);
+               if (IS_ERR(rec->event)) {
+                       ret = PTR_ERR(rec->event);
+                       rec->event = NULL;
+                       break;
+               }
+       }
+
+       return ret;
+}
+
+static void perf_event_release(void)
+{
+       int cpu;
+
+       for_each_possible_cpu(cpu) {
+               struct perf_rec *rec = per_cpu_ptr(percpu_rec, cpu);
+
+               if (rec->event) {
+                       perf_event_release_kernel(rec->event);
+                       rec->event = NULL;
+               }
+       }
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(perf_entry_num_fops, bt_pages_entry_num_get, NULL,
+                       "%llu\n");
+
+DEFINE_SIMPLE_ATTRIBUTE(perf_page_num_fops, bt_pages_page_num_get, NULL,
+                       "%llu\n");
+
+static int perf_str_show(struct seq_file *p, void *unused)
+{
+       int cpu;
+
+       for_each_possible_cpu(cpu) {
+               struct perf_rec *rec = per_cpu_ptr(percpu_rec, cpu);
+               struct perf_entry *entry = NULL;
+               unsigned int index = 0;
+
+               seq_printf(p, "cpu%d\n", cpu);
+               while (1) {
+                       char buffer[KSYM_SYMBOL_LEN];
+
+                       entry = bt_pages_get_entry(&rec->pages, &index,
+                                                  entry);
+                       if (!entry)
+                               break;
+
+                       if (entry->is_user)
+                               buffer[0] = '\0';
+                       else
+                               sprint_symbol(buffer, (unsigned long)entry->pc);
+
+                       seq_printf(p, "%c %s\t\t%d\t%s[0x%llx]\n",
+                                  entry->is_user ? 'u' : 'k',
+                                  entry->comm,
+                                  (int)entry->oom_score_adj,
+                                  buffer,
+                                  (unsigned long long)entry->pc);
+               }
+               seq_puts(p, "\n");
+       }
+
+       return 0;
+}
+
+static int perf_str_open(struct inode *inode, struct file *file)
+{
+       down_read(&bt_lock);
+
+       return single_open(file, perf_str_show, NULL);
+}
+
+static int perf_str_release(struct inode *inode, struct file *file)
+{
+       int ret = single_release(inode, file);
+
+       up_read(&bt_lock);
+       return ret;
+}
+
+static const struct file_operations perf_str_fops = {
+       .open           = perf_str_open,
+       .read           = seq_read,
+       .llseek         = seq_lseek,
+       .release        = perf_str_release,
+};
+
+static int
+perf_pages_alloc(void)
+{
+       int cpu;
+
+       for_each_possible_cpu(cpu) {
+               int ret;
+               struct perf_rec *rec = per_cpu_ptr(percpu_rec, cpu);
+
+               ret = bt_pages_setup(&rec->pages,
+                                    sizeof(struct perf_entry),
+                                    rec_max, cpu);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
+
+static void
+perf_pages_release(void)
+{
+       int cpu;
+
+       for_each_possible_cpu(cpu) {
+               struct perf_rec *rec = per_cpu_ptr(percpu_rec, cpu);
+
+               bt_pages_release(&rec->pages);
+       }
+}
+
+static int perf_alloc(void)
+{
+       int cpu, ret;
+
+       percpu_rec = alloc_percpu(struct perf_rec);
+       if (!percpu_rec)
+               return -ENOMEM;
+
+       /* Init rec.  */
+       for_each_possible_cpu(cpu) {
+               struct perf_rec *rec = per_cpu_ptr(percpu_rec, cpu);
+
+               memset(rec, 0, sizeof(struct perf_rec));
+       }
+
+       ret = perf_pages_alloc();
+       if (ret)
+               return ret;
+
+       for_each_possible_cpu(cpu) {
+               struct perf_rec *rec = per_cpu_ptr(percpu_rec, cpu);
+               char name[10];
+
+               snprintf(name, 10, "cpu%d", cpu);
+               rec->dir = debugfs_create_dir(name, perf_dir);
+               if (!rec->dir)
+                       return -ENOMEM;
+
+               rec->number_fs = debugfs_create_file("number",
+                                                    S_IRUSR,
+                                                    rec->dir,
+                                                    &rec->pages,
+                                                    &perf_entry_num_fops);
+               if (!rec->number_fs)
+                       return -ENOMEM;
+
+               rec->page_fs = debugfs_create_file("page",
+                                                  S_IRUSR,
+                                                  rec->dir,
+                                                  &rec->pages,
+                                                  &perf_page_num_fops);
+               if (!rec->page_fs)
+                       return -ENOMEM;
+
+               rec->drop_fs = debugfs_create_file("drop",
+                                                  S_IRUSR,
+                                                  rec->dir,
+                                                  &rec->drop,
+                                                  &perf_number_fops);
+               if (!rec->drop_fs)
+                       return -ENOMEM;
+
+               rec->bin_fs = debugfs_create_file("bin",
+                                                 S_IRUSR,
+                                                 rec->dir,
+                                                 &rec->pages,
+                                                 &bt_pages_bin_fops);
+               if (!rec->bin_fs)
+                       return -ENOMEM;
+       }
+
+       perf_str_dir = debugfs_create_file("str",
+                                          S_IRUSR,
+                                          perf_dir,
+                                          NULL,
+                                          &perf_str_fops);
+
+       return perf_event_creat();
+}
+
+static void perf_release(void)
+{
+       int cpu;
+
+       if (!percpu_rec)
+               return;
+
+       debugfs_remove(perf_str_dir);
+       perf_str_dir = NULL;
+
+       for_each_possible_cpu(cpu) {
+               struct perf_rec *rec = per_cpu_ptr(percpu_rec, cpu);
+
+               debugfs_remove(rec->number_fs);
+               debugfs_remove(rec->page_fs);
+               debugfs_remove(rec->drop_fs);
+               debugfs_remove(rec->bin_fs);
+               debugfs_remove(rec->dir);
+       }
+
+       perf_pages_release();
+
+       perf_event_release();
+
+       free_percpu(percpu_rec);
+       percpu_rec = NULL;
+}
+
+static int perf_on_set(void *data, u64 val)
+{
+       int ret = 0;
+
+       down_write(&bt_lock);
+
+       if (!perf_on && val) {
+               ret = perf_alloc();
+               if (ret) {
+                       perf_release();
+                       goto out;
+               }
+
+               perf_on = true;
+       } else if (perf_on && !val) {
+               perf_release();
+
+               perf_on = false;
+       }
+
+out:
+       up_write(&bt_lock);
+       return ret;
+}
+
+static int perf_on_get(void *data, u64 *val)
+{
+       down_read(&bt_lock);
+
+       if (perf_on)
+               *val = 1;
+       else
+               *val = 0;
+
+       up_read(&bt_lock);
+
+       return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(perf_on_fops, perf_on_get, perf_on_set, "%llu\n");
+
+static int perf_event_set(void *data, u64 val)
+{
+       unsigned int *number_point = data;
+       int ret = 0;
+
+       down_write(&bt_lock);
+
+       *number_point = (unsigned int)val;
+
+       if (number_point == &perf_freq)
+               perf_use_freq = true;
+       else if (number_point == &perf_period)
+               perf_use_freq = false;
+
+       if (perf_on) {
+               perf_event_release();
+               ret = perf_event_creat();
+               if (ret) {
+                       pr_err("bloodtest: alloc perf get error %d\n", ret);
+                       perf_release();
+                       perf_on = false;
+               }
+       }
+
+       up_write(&bt_lock);
+       return ret;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(perf_event_fops,
+                       perf_number_get,
+                       perf_event_set, "%llu\n");
+
+static int perf_bin_format_show(struct seq_file *p, void *unused)
+{
+#ifdef CONFIG_CPU_BIG_ENDIAN
+       seq_puts(p, "big-endian\n");
+#else
+       seq_puts(p, "little-endian\n");
+#endif
+       seq_printf(p, "size:%lu\n", sizeof(struct perf_entry));
+
+       seq_printf(p, "pc format:u64 unsigned offset:%lu size:%lu\n",
+                  offsetof(struct perf_entry, pc), sizeof(u64));
+       seq_printf(p, "is_user format:u8 unsigned offset:%lu size:%lu\n",
+                  offsetof(struct perf_entry, is_user), sizeof(u8));
+       seq_printf(p, "oom_score_adj format:s16 signed offset:%lu size:%lu\n",
+                  offsetof(struct perf_entry, oom_score_adj), sizeof(s16));
+       seq_printf(p, "comm format:char[] signed offset:%lu size:%d\n",
+                  offsetof(struct perf_entry, comm), TASK_COMM_LEN);
+
+       return 0;
+}
+
+static int perf_bin_format_open(struct inode *inode, struct file *file)
+{
+       return single_open(file, perf_bin_format_show, NULL);
+}
+
+static const struct file_operations perf_bin_format_fops = {
+       .open           = perf_bin_format_open,
+       .read           = seq_read,
+       .llseek         = seq_lseek,
+       .release        = single_release,
+};
+
+static int rec_max_set(void *data, u64 val)
+{
+       int ret = 0;
+
+       down_write(&bt_lock);
+       if (rec_max == val)
+               goto out;
+
+       rec_max = val;
+
+       if (!perf_on)
+               goto out;
+
+       perf_pages_release();
+       ret = perf_pages_alloc();
+       if (ret) {
+               perf_release();
+               perf_on = false;
+               goto out;
+       }
+
+out:
+       up_write(&bt_lock);
+       return ret;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(rec_max_fops,
+                       perf_number_get,
+                       rec_max_set, "%llu\n");
+
+void
+bt_insert_perf(void)
+{
+       int cpu;
+
+       if (!perf_on)
+               return;
+
+       for_each_possible_cpu(cpu) {
+               struct perf_rec *rec = per_cpu_ptr(percpu_rec, cpu);
+
+               /* The perf is not running.  So doesn't lock.  */
+               bt_pages_clear(&rec->pages);
+               rec->drop = 0;
+               atomic_set(&rec->is_running, 1);
+               perf_event_enable(rec->event);
+       }
+}
+
+void
+bt_pullout_perf(void)
+{
+       int cpu;
+
+       if (!perf_on)
+               return;
+
+       for_each_possible_cpu(cpu) {
+               struct perf_rec *rec = per_cpu_ptr(percpu_rec, cpu);
+
+               /* This function is called by hrtimer that irq is disabled.
+                  Function perf_event_disable will call
+                  smp_call_function_single that should not run when irq is
+                  disabled.
+                  So call perf_event_disable later.  */
+               atomic_set(&rec->is_running, 0);
+       }
+}
+
+void
+bt_task_pullout_perf(void)
+{
+       int cpu;
+
+       if (!perf_on)
+               return;
+
+       for_each_possible_cpu(cpu) {
+               struct perf_rec *rec = per_cpu_ptr(percpu_rec, cpu);
+
+               perf_event_disable(rec->event);
+       }
+}
+
+
+
+int __init bt_perf_init(struct dentry *f)
+{
+       int ret = -ENOMEM;
+       struct dentry *on = NULL, *format = NULL, *period = NULL,
+                     *freq = NULL, *max = NULL, *type = NULL, *config = NULL;
+
+       perf_dir = debugfs_create_dir("perf", f);
+       if (!perf_dir)
+               goto out;
+
+       on = debugfs_create_file("on", S_IRUSR | S_IWUSR, perf_dir, NULL,
+                                &perf_on_fops);
+       if (!on)
+               goto out;
+
+       format = debugfs_create_file("bin_format", S_IRUSR,
+                                    perf_dir, NULL,
+                                    &perf_bin_format_fops);
+       if (!format)
+               goto out;
+
+       period = debugfs_create_file("perf_period", S_IRUSR | S_IWUSR, perf_dir,
+                                    &perf_period,
+                                    &perf_event_fops);
+       if (!period)
+               goto out;
+
+       freq = debugfs_create_file("perf_freq", S_IRUSR | S_IWUSR, perf_dir,
+                                  &perf_freq,
+                                  &perf_event_fops);
+       if (!freq)
+               goto out;
+
+       type = debugfs_create_file("perf_type", S_IRUSR | S_IWUSR, perf_dir,
+                                  &perf_type,
+                                  &perf_event_fops);
+       if (!type)
+               goto out;
+
+       config = debugfs_create_file("perf_config", S_IRUSR | S_IWUSR, perf_dir,
+                                    &perf_config,
+                                    &perf_event_fops);
+       if (!config)
+               goto out;
+
+       max = debugfs_create_file("rec_max", S_IRUSR | S_IWUSR, perf_dir,
+                                  &rec_max,
+                                  &rec_max_fops);
+       if (!max)
+               goto out;
+
+       ret = 0;
+out:
+       if (ret) {
+               debugfs_remove(on);
+               debugfs_remove(format);
+               debugfs_remove(period);
+               debugfs_remove(freq);
+               debugfs_remove(max);
+               debugfs_remove(type);
+               debugfs_remove(config);
+               debugfs_remove(perf_dir);
+       }
+       return ret;
+}
-- 
1.9.1

Reply via email to