summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIgor Nabirushkin <inabirushkin@nvidia.com>2014-02-20 20:48:48 +0400
committerRiham Haidar <rhaidar@nvidia.com>2014-03-06 12:31:21 -0800
commitccde0304ffcf4a1cb75bad78507597edc1d69290 (patch)
treed5f6da2e278c7c35059062ebd9a519ad267e683a
parentcd663c69739cd88af046f3a31f961956589d3f17 (diff)
misc: tegra_profiler: add unwinding
Tegra Profiler: unwinding based on exception-handling tables Bug 1465331 Change-Id: I9e8bb2eb342c5dadf82af05bb87040c0925cca1b Signed-off-by: Igor Nabirushkin <inabirushkin@nvidia.com> Reviewed-on: http://git-master/r/377109 Reviewed-by: Automatic_Commit_Validation_User GVS: Gerrit_Virtual_Submit Tested-by: Andrey Trachenko <atrachenko@nvidia.com> Tested-by: Maxim Morin <mmorin@nvidia.com> Reviewed-by: Raymond Poudrier <rapoudrier@nvidia.com> Reviewed-by: Bharat Nihalani <bnihalani@nvidia.com>
-rw-r--r--drivers/misc/tegra-profiler/Makefile3
-rw-r--r--drivers/misc/tegra-profiler/backtrace.c104
-rw-r--r--drivers/misc/tegra-profiler/backtrace.h25
-rw-r--r--drivers/misc/tegra-profiler/comm.c75
-rw-r--r--drivers/misc/tegra-profiler/comm.h5
-rw-r--r--drivers/misc/tegra-profiler/eh_unwind.c924
-rw-r--r--drivers/misc/tegra-profiler/eh_unwind.h38
-rw-r--r--drivers/misc/tegra-profiler/hrt.c22
-rw-r--r--drivers/misc/tegra-profiler/hrt.h4
-rw-r--r--drivers/misc/tegra-profiler/main.c30
-rw-r--r--drivers/misc/tegra-profiler/version.h2
-rw-r--r--include/linux/tegra_profiler.h58
12 files changed, 1192 insertions, 98 deletions
diff --git a/drivers/misc/tegra-profiler/Makefile b/drivers/misc/tegra-profiler/Makefile
index c5d81af2112c..89f5e946b30c 100644
--- a/drivers/misc/tegra-profiler/Makefile
+++ b/drivers/misc/tegra-profiler/Makefile
@@ -26,7 +26,8 @@ tegra-profiler-objs := \
ma.o \
power_clk.o \
auth.o \
- quadd_proc.o
+ quadd_proc.o \
+ eh_unwind.o
ifdef CONFIG_CACHE_L2X0
tegra-profiler-objs += pl310.o
diff --git a/drivers/misc/tegra-profiler/backtrace.c b/drivers/misc/tegra-profiler/backtrace.c
index ce02f82d17e1..c8cc5f7cc0d0 100644
--- a/drivers/misc/tegra-profiler/backtrace.c
+++ b/drivers/misc/tegra-profiler/backtrace.c
@@ -16,72 +16,57 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
-#include <linux/module.h>
#include <linux/uaccess.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <linux/tegra_profiler.h>
+#include "quadd.h"
#include "backtrace.h"
+#include "eh_unwind.h"
#define QUADD_USER_SPACE_MIN_ADDR 0x8000
-static inline void
-quadd_callchain_store(struct quadd_callchain *callchain_data,
+void
+quadd_callchain_store(struct quadd_callchain *cc,
quadd_bt_addr_t ip)
{
- if (callchain_data->nr < QUADD_MAX_STACK_DEPTH)
- callchain_data->callchain[callchain_data->nr++] = ip;
-}
-
-static int
-check_vma_address(unsigned long addr, struct vm_area_struct *vma)
-{
- unsigned long start, end, length;
-
- if (vma) {
- start = vma->vm_start;
- end = vma->vm_end;
- length = end - start;
- if (length > sizeof(unsigned long) &&
- addr >= start && addr <= end - sizeof(unsigned long))
- return 0;
- }
- return -EINVAL;
+ if (ip && cc->nr < QUADD_MAX_STACK_DEPTH)
+ cc->ip[cc->nr++] = ip;
}
static unsigned long __user *
user_backtrace(unsigned long __user *tail,
- struct quadd_callchain *callchain_data,
+ struct quadd_callchain *cc,
struct vm_area_struct *stack_vma)
{
unsigned long value, value_lr = 0, value_fp = 0;
unsigned long __user *fp_prev = NULL;
- if (check_vma_address((unsigned long)tail, stack_vma))
+ if (!is_vma_addr((unsigned long)tail, stack_vma))
return NULL;
if (__copy_from_user_inatomic(&value, tail, sizeof(unsigned long)))
return NULL;
- if (!check_vma_address(value, stack_vma)) {
+ if (is_vma_addr(value, stack_vma)) {
/* gcc thumb/clang frame */
value_fp = value;
- if (check_vma_address((unsigned long)(tail + 1), stack_vma))
+ if (!is_vma_addr((unsigned long)(tail + 1), stack_vma))
return NULL;
if (__copy_from_user_inatomic(&value_lr, tail + 1,
- sizeof(unsigned long)))
+ sizeof(value_lr)))
return NULL;
} else {
/* gcc arm frame */
if (__copy_from_user_inatomic(&value_fp, tail - 1,
- sizeof(unsigned long)))
+ sizeof(value_fp)))
return NULL;
- if (check_vma_address(value_fp, stack_vma))
+ if (!is_vma_addr(value_fp, stack_vma))
return NULL;
value_lr = value;
@@ -92,7 +77,7 @@ user_backtrace(unsigned long __user *tail,
if (value_lr < QUADD_USER_SPACE_MIN_ADDR)
return NULL;
- quadd_callchain_store(callchain_data, value_lr);
+ quadd_callchain_store(cc, value_lr);
if (fp_prev <= tail)
return NULL;
@@ -100,16 +85,16 @@ user_backtrace(unsigned long __user *tail,
return fp_prev;
}
-unsigned int
-quadd_get_user_callchain(struct pt_regs *regs,
- struct quadd_callchain *callchain_data)
+static unsigned int
+get_user_callchain_fp(struct pt_regs *regs,
+ struct quadd_callchain *cc)
{
unsigned long fp, sp, pc, reg;
struct vm_area_struct *vma, *vma_pc;
unsigned long __user *tail = NULL;
struct mm_struct *mm = current->mm;
- callchain_data->nr = 0;
+ cc->nr = 0;
if (!regs || !mm)
return 0;
@@ -120,7 +105,7 @@ quadd_get_user_callchain(struct pt_regs *regs,
fp = regs->ARM_fp;
sp = regs->ARM_sp;
- pc = regs->ARM_pc;
+ pc = instruction_pointer(regs);
if (fp == 0 || fp < sp || fp & 0x3)
return 0;
@@ -129,26 +114,25 @@ quadd_get_user_callchain(struct pt_regs *regs,
if (!vma)
return 0;
- if (check_vma_address(fp, vma))
+ if (!is_vma_addr(fp, vma))
return 0;
if (probe_kernel_address(fp, reg)) {
pr_warn_once("frame error: sp/fp: %#lx/%#lx, pc/lr: %#lx/%#lx, vma: %#lx-%#lx\n",
- sp, fp, regs->ARM_pc, regs->ARM_lr,
+ sp, fp, pc, regs->ARM_lr,
vma->vm_start, vma->vm_end);
return 0;
}
if (thumb_mode(regs)) {
- if (reg <= fp || check_vma_address(reg, vma))
+ if (reg <= fp || !is_vma_addr(reg, vma))
return 0;
- } else if (reg > fp &&
- !check_vma_address(reg, vma)) {
+ } else if (reg > fp && is_vma_addr(reg, vma)) {
/* fp --> fp prev */
unsigned long value;
int read_lr = 0;
- if (!check_vma_address(fp + sizeof(unsigned long), vma)) {
+ if (is_vma_addr(fp + sizeof(unsigned long), vma)) {
if (__copy_from_user_inatomic(
&value,
(unsigned long __user *)fp + 1,
@@ -159,13 +143,13 @@ quadd_get_user_callchain(struct pt_regs *regs,
read_lr = 1;
}
- if (!read_lr || check_vma_address(value, vma_pc)) {
+ if (!read_lr || !is_vma_addr(value, vma_pc)) {
/* gcc: fp --> short frame tail (fp) */
if (regs->ARM_lr < QUADD_USER_SPACE_MIN_ADDR)
return 0;
- quadd_callchain_store(callchain_data, regs->ARM_lr);
+ quadd_callchain_store(cc, regs->ARM_lr);
tail = (unsigned long __user *)reg;
}
}
@@ -174,7 +158,39 @@ quadd_get_user_callchain(struct pt_regs *regs,
tail = (unsigned long __user *)fp;
while (tail && !((unsigned long)tail & 0x3))
- tail = user_backtrace(tail, callchain_data, vma);
+ tail = user_backtrace(tail, cc, vma);
+
+ return cc->nr;
+}
+
+unsigned int
+quadd_get_user_callchain(struct pt_regs *regs,
+ struct quadd_callchain *cc,
+ struct quadd_ctx *ctx)
+{
+ int unw_fp, unw_eht;
+ unsigned int extra;
+ struct quadd_parameters *param = &ctx->param;
+
+ cc->nr = 0;
+
+ if (!regs)
+ return 0;
+
+ extra = param->reserved[QUADD_PARAM_IDX_EXTRA];
+
+ unw_fp = extra & QUADD_PARAM_EXTRA_BT_FP;
+ unw_eht = extra & QUADD_PARAM_EXTRA_BT_UNWIND_TABLES;
+
+ cc->unw_rc = 0;
+
+ if (unw_fp) {
+ cc->unw_method = QUADD_UNW_METHOD_FP;
+ get_user_callchain_fp(regs, cc);
+ } else if (unw_eht) {
+ cc->unw_method = QUADD_UNW_METHOD_EHT;
+ quadd_get_user_callchain_ut(regs, cc);
+ }
- return callchain_data->nr;
+ return cc->nr;
}
diff --git a/drivers/misc/tegra-profiler/backtrace.h b/drivers/misc/tegra-profiler/backtrace.h
index ce7608217629..1b56b3a9ef8d 100644
--- a/drivers/misc/tegra-profiler/backtrace.h
+++ b/drivers/misc/tegra-profiler/backtrace.h
@@ -17,18 +17,37 @@
#ifndef __QUADD_BACKTRACE_H
#define __QUADD_BACKTRACE_H
-#include <linux/types.h>
+#include <linux/mm.h>
+#include <linux/tegra_profiler.h>
#define QUADD_MAX_STACK_DEPTH 64
struct quadd_callchain {
int nr;
- quadd_bt_addr_t callchain[QUADD_MAX_STACK_DEPTH];
+ quadd_bt_addr_t ip[QUADD_MAX_STACK_DEPTH];
+
+ unsigned int unw_method;
+ unsigned int unw_rc;
};
+struct quadd_ctx;
+struct pt_regs;
+
unsigned int
quadd_get_user_callchain(struct pt_regs *regs,
- struct quadd_callchain *callchain_data);
+ struct quadd_callchain *cc_data,
+ struct quadd_ctx *ctx);
+
+void quadd_callchain_store(struct quadd_callchain *cc,
+ quadd_bt_addr_t ip);
+
+static inline int
+is_vma_addr(unsigned long addr, struct vm_area_struct *vma)
+{
+ return vma &&
+ addr >= vma->vm_start &&
+ addr <= vma->vm_end - sizeof(addr);
+}
#endif /* __QUADD_BACKTRACE_H */
diff --git a/drivers/misc/tegra-profiler/comm.c b/drivers/misc/tegra-profiler/comm.c
index e94bcbf1c831..d8c297c83bc0 100644
--- a/drivers/misc/tegra-profiler/comm.c
+++ b/drivers/misc/tegra-profiler/comm.c
@@ -24,7 +24,6 @@
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/bitops.h>
-#include <linux/interrupt.h>
#include <linux/err.h>
#include <asm/uaccess.h>
@@ -495,11 +494,12 @@ device_ioctl(struct file *file,
unsigned int ioctl_num,
unsigned long ioctl_param)
{
- int err;
+ int err = 0;
struct quadd_parameters *user_params;
struct quadd_comm_cap cap;
struct quadd_module_state state;
struct quadd_module_version versions;
+ struct quadd_extables extabs;
unsigned long flags;
struct quadd_ring_buffer *rb = &comm_ctx.rb;
@@ -518,20 +518,22 @@ device_ioctl(struct file *file,
case IOCTL_SETUP:
if (atomic_read(&comm_ctx.active)) {
pr_err("error: tegra profiler is active\n");
- mutex_unlock(&comm_ctx.io_mutex);
- return -EBUSY;
+ err = -EBUSY;
+ goto error_out;
}
user_params = vmalloc(sizeof(*user_params));
- if (!user_params)
- return -ENOMEM;
+ if (!user_params) {
+ err = -ENOMEM;
+ goto error_out;
+ }
if (copy_from_user(user_params, (void __user *)ioctl_param,
sizeof(struct quadd_parameters))) {
pr_err("setup failed\n");
vfree(user_params);
- mutex_unlock(&comm_ctx.io_mutex);
- return -EFAULT;
+ err = -EFAULT;
+ goto error_out;
}
err = comm_ctx.control->set_parameters(user_params,
@@ -539,8 +541,7 @@ device_ioctl(struct file *file,
if (err) {
pr_err("error: setup failed\n");
vfree(user_params);
- mutex_unlock(&comm_ctx.io_mutex);
- return err;
+ goto error_out;
}
comm_ctx.params_ok = 1;
comm_ctx.process_pid = user_params->pids[0];
@@ -548,8 +549,8 @@ device_ioctl(struct file *file,
if (user_params->reserved[QUADD_PARAM_IDX_SIZE_OF_RB] == 0) {
pr_err("error: too old version of daemon\n");
vfree(user_params);
- mutex_unlock(&comm_ctx.io_mutex);
- return -EINVAL;
+ err = -EINVAL;
+ goto error_out;
}
comm_ctx.rb_size = user_params->reserved[0];
@@ -567,8 +568,8 @@ device_ioctl(struct file *file,
if (copy_to_user((void __user *)ioctl_param, &cap,
sizeof(struct quadd_comm_cap))) {
pr_err("error: get_capabilities failed\n");
- mutex_unlock(&comm_ctx.io_mutex);
- return -EFAULT;
+ err = -EFAULT;
+ goto error_out;
}
break;
@@ -582,8 +583,8 @@ device_ioctl(struct file *file,
if (copy_to_user((void __user *)ioctl_param, &versions,
sizeof(struct quadd_module_version))) {
pr_err("error: get version failed\n");
- mutex_unlock(&comm_ctx.io_mutex);
- return -EFAULT;
+ err = -EFAULT;
+ goto error_out;
}
break;
@@ -602,8 +603,8 @@ device_ioctl(struct file *file,
if (copy_to_user((void __user *)ioctl_param, &state,
sizeof(struct quadd_module_state))) {
pr_err("error: get_state failed\n");
- mutex_unlock(&comm_ctx.io_mutex);
- return -EFAULT;
+ err = -EFAULT;
+ goto error_out;
}
break;
@@ -612,23 +613,22 @@ device_ioctl(struct file *file,
if (!comm_ctx.params_ok) {
pr_err("error: params failed\n");
atomic_set(&comm_ctx.active, 0);
- mutex_unlock(&comm_ctx.io_mutex);
- return -EFAULT;
+ err = -EFAULT;
+ goto error_out;
}
err = rb_init(rb, comm_ctx.rb_size);
if (err) {
pr_err("error: rb_init failed\n");
atomic_set(&comm_ctx.active, 0);
- mutex_unlock(&comm_ctx.io_mutex);
- return err;
+ goto error_out;
}
- if (comm_ctx.control->start()) {
+ err = comm_ctx.control->start();
+ if (err) {
pr_err("error: start failed\n");
atomic_set(&comm_ctx.active, 0);
- mutex_unlock(&comm_ctx.io_mutex);
- return -EFAULT;
+ goto error_out;
}
pr_info("Start profiling success\n");
}
@@ -643,15 +643,30 @@ device_ioctl(struct file *file,
}
break;
+ case IOCTL_SET_EXTAB:
+ if (copy_from_user(&extabs, (void __user *)ioctl_param,
+ sizeof(extabs))) {
+ pr_err("error: set_extab failed\n");
+ err = -EFAULT;
+ goto error_out;
+ }
+
+ err = comm_ctx.control->set_extab(&extabs);
+ if (err) {
+ pr_err("error: set_extab\n");
+ goto error_out;
+ }
+ break;
+
default:
pr_err("error: ioctl %u is unsupported in this version of module\n",
ioctl_num);
- mutex_unlock(&comm_ctx.io_mutex);
- return -EFAULT;
+ err = -EFAULT;
}
- mutex_unlock(&comm_ctx.io_mutex);
- return 0;
+error_out:
+ mutex_unlock(&comm_ctx.io_mutex);
+ return err;
}
static void unregister(void)
@@ -690,7 +705,7 @@ static int comm_init(void)
res = misc_register(misc_dev);
if (res < 0) {
- pr_err("Error: misc_register %d\n", res);
+ pr_err("Error: misc_register: %d\n", res);
return res;
}
comm_ctx.misc_dev = misc_dev;
diff --git a/drivers/misc/tegra-profiler/comm.h b/drivers/misc/tegra-profiler/comm.h
index 1bed2d98d7ce..a72b1d1d37dc 100644
--- a/drivers/misc/tegra-profiler/comm.h
+++ b/drivers/misc/tegra-profiler/comm.h
@@ -23,6 +23,8 @@ struct quadd_record_data;
struct quadd_comm_cap;
struct quadd_module_state;
struct miscdevice;
+struct quadd_parameters;
+struct quadd_extables;
struct quadd_ring_buffer {
char *buf;
@@ -40,8 +42,6 @@ struct quadd_iovec {
size_t len;
};
-struct quadd_parameters;
-
struct quadd_comm_control_interface {
int (*start)(void);
void (*stop)(void);
@@ -49,6 +49,7 @@ struct quadd_comm_control_interface {
uid_t *debug_app_uid);
void (*get_capabilities)(struct quadd_comm_cap *cap);
void (*get_state)(struct quadd_module_state *state);
+ int (*set_extab)(struct quadd_extables *extabs);
};
struct quadd_comm_data_interface {
diff --git a/drivers/misc/tegra-profiler/eh_unwind.c b/drivers/misc/tegra-profiler/eh_unwind.c
new file mode 100644
index 000000000000..3f8789022466
--- /dev/null
+++ b/drivers/misc/tegra-profiler/eh_unwind.c
@@ -0,0 +1,924 @@
+/*
+ * drivers/misc/tegra-profiler/exh_tables.c
+ *
+ * Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/mm.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/err.h>
+
+#include "eh_unwind.h"
+#include "backtrace.h"
+
+#define QUADD_EXTABS_SIZE 0x100
+
+#define GET_NR_PAGES(a, l) \
+ ((PAGE_ALIGN((a) + (l)) - ((a) & PAGE_MASK)) / PAGE_SIZE)
+
+enum regs {
+ FP_THUMB = 7,
+ FP_ARM = 11,
+
+ SP = 13,
+ LR = 14,
+ PC = 15
+};
+
+struct extab_info {
+ unsigned long addr;
+ unsigned long length;
+};
+
+struct extables {
+ struct extab_info exidx;
+ struct extab_info extab;
+};
+
+struct ex_region_info {
+ unsigned long vm_start;
+ unsigned long vm_end;
+
+ struct extables tabs;
+};
+
+struct quadd_unwind_ctx {
+ struct ex_region_info *regions;
+ unsigned long ri_nr;
+ unsigned long ri_size;
+
+ struct task_struct *task;
+
+ unsigned long pinned_pages;
+ unsigned long pinned_size;
+
+ spinlock_t lock;
+};
+
+struct unwind_idx {
+ unsigned long addr_offset;
+ unsigned long insn;
+};
+
+struct stackframe {
+ unsigned long fp_thumb;
+ unsigned long fp_arm;
+
+ unsigned long sp;
+ unsigned long lr;
+ unsigned long pc;
+};
+
+struct unwind_ctrl_block {
+ unsigned long vrs[16]; /* virtual register set */
+ const unsigned long *insn; /* pointer to the current instr word */
+ int entries; /* number of entries left */
+ int byte; /* current byte in the instr word */
+};
+
+struct pin_pages_work {
+ struct work_struct work;
+ unsigned long vm_start;
+};
+
+struct quadd_unwind_ctx ctx;
+
+#define read_user_data(addr, retval) \
+({ \
+ long ret; \
+ ret = probe_kernel_address(addr, retval); \
+ if (ret) \
+ ret = -QUADD_URC_EACCESS; \
+ ret; \
+})
+
+static int
+add_ex_region(struct ex_region_info *new_entry)
+{
+ unsigned int i_min, i_max, mid;
+ struct ex_region_info *array = ctx.regions;
+ unsigned long size = ctx.ri_nr;
+
+ if (!array)
+ return 0;
+
+ if (size == 0) {
+ memcpy(&array[0], new_entry, sizeof(*new_entry));
+ return 1;
+ } else if (size == 1 && array[0].vm_start == new_entry->vm_start) {
+ return 0;
+ }
+
+ i_min = 0;
+ i_max = size;
+
+ if (array[0].vm_start > new_entry->vm_start) {
+ memmove(array + 1, array,
+ size * sizeof(*array));
+ memcpy(&array[0], new_entry, sizeof(*new_entry));
+ return 1;
+ } else if (array[size - 1].vm_start < new_entry->vm_start) {
+ memcpy(&array[size], new_entry, sizeof(*new_entry));
+ return 1;
+ }
+
+ while (i_min < i_max) {
+ mid = i_min + (i_max - i_min) / 2;
+
+ if (new_entry->vm_start <= array[mid].vm_start)
+ i_max = mid;
+ else
+ i_min = mid + 1;
+ }
+
+ if (array[i_max].vm_start == new_entry->vm_start) {
+ return 0;
+ } else {
+ memmove(array + i_max + 1,
+ array + i_max,
+ (size - i_max) * sizeof(*array));
+ memcpy(&array[i_max], new_entry, sizeof(*new_entry));
+ return 1;
+ }
+}
+
+static struct ex_region_info *
+search_ex_region(unsigned long key, struct extables *tabs)
+{
+ unsigned int i_min, i_max, mid;
+ struct ex_region_info *array = ctx.regions;
+ unsigned long size = ctx.ri_nr;
+
+ if (size == 0)
+ return NULL;
+
+ i_min = 0;
+ i_max = size;
+
+ while (i_min < i_max) {
+ mid = i_min + (i_max - i_min) / 2;
+
+ if (key <= array[mid].vm_start)
+ i_max = mid;
+ else
+ i_min = mid + 1;
+ }
+
+ if (array[i_max].vm_start == key) {
+ memcpy(tabs, &array[i_max].tabs, sizeof(*tabs));
+ return &array[i_max];
+ }
+
+ return NULL;
+}
+
+static void pin_user_pages(struct extables *tabs)
+{
+ long ret;
+ struct extab_info *ti;
+ unsigned long nr_pages, addr;
+ struct mm_struct *mm = ctx.task->mm;
+
+ if (!mm)
+ return;
+
+ down_write(&mm->mmap_sem);
+
+ ti = &tabs->exidx;
+ addr = ti->addr & PAGE_MASK;
+ nr_pages = GET_NR_PAGES(ti->addr, ti->length);
+
+ ret = get_user_pages(ctx.task, mm, addr, nr_pages, 0, 0,
+ NULL, NULL);
+ if (ret < 0) {
+ pr_debug("%s: warning: addr/nr_pages: %#lx/%lu\n",
+ __func__, ti->addr, nr_pages);
+ goto error_out;
+ }
+
+ ctx.pinned_pages += ret;
+ ctx.pinned_size += ti->length;
+
+ pr_debug("%s: pin exidx: addr/nr_pages: %#lx/%lu\n",
+ __func__, ti->addr, nr_pages);
+
+ ti = &tabs->extab;
+ addr = ti->addr & PAGE_MASK;
+ nr_pages = GET_NR_PAGES(ti->addr, ti->length);
+
+ ret = get_user_pages(ctx.task, mm, addr, nr_pages, 0, 0,
+ NULL, NULL);
+ if (ret < 0) {
+ pr_debug("%s: warning: addr/nr_pages: %#lx/%lu\n",
+ __func__, ti->addr, nr_pages);
+ goto error_out;
+ }
+
+ ctx.pinned_pages += ret;
+ ctx.pinned_size += ti->length;
+
+ pr_debug("%s: pin extab: addr/nr_pages: %#lx/%lu\n",
+ __func__, ti->addr, nr_pages);
+
+error_out:
+ up_write(&mm->mmap_sem);
+}
+
+static void
+pin_user_pages_work(struct work_struct *w)
+{
+ struct extables tabs;
+ struct ex_region_info *ri;
+ struct pin_pages_work *work;
+
+ work = container_of(w, struct pin_pages_work, work);
+
+ spin_lock(&ctx.lock);
+ ri = search_ex_region(work->vm_start, &tabs);
+ spin_unlock(&ctx.lock);
+ if (ri)
+ pin_user_pages(&tabs);
+
+ kfree(w);
+}
+
+static int
+__pin_user_pages(unsigned long vm_start)
+{
+ struct pin_pages_work *work;
+
+ work = kmalloc(sizeof(*work), GFP_ATOMIC);
+ if (!work)
+ return -ENOMEM;
+
+ INIT_WORK(&work->work, pin_user_pages_work);
+ work->vm_start = vm_start;
+
+ schedule_work(&work->work);
+
+ return 0;
+}
+
+int quadd_unwind_set_extab(struct quadd_extables *extabs)
+{
+ int err = 0;
+ struct ex_region_info ri_entry;
+ struct extab_info *ti;
+
+ spin_lock(&ctx.lock);
+
+ if (!ctx.regions) {
+ err = -ENOMEM;
+ goto error_out;
+ }
+
+ if (ctx.ri_nr >= ctx.ri_size) {
+ struct ex_region_info *new_regions;
+ unsigned long newlen = ctx.ri_size + (ctx.ri_size >> 1);
+
+ new_regions = krealloc(ctx.regions, newlen, GFP_KERNEL);
+ if (!new_regions) {
+ err = -ENOMEM;
+ goto error_out;
+ }
+ ctx.regions = new_regions;
+ ctx.ri_size = newlen;
+ }
+
+ ri_entry.vm_start = extabs->vm_start;
+ ri_entry.vm_end = extabs->vm_end;
+
+ ti = &ri_entry.tabs.exidx;
+ ti->addr = extabs->exidx.addr;
+ ti->length = extabs->exidx.length;
+
+ ti = &ri_entry.tabs.extab;
+ ti->addr = extabs->extab.addr;
+ ti->length = extabs->extab.length;
+
+ ctx.ri_nr += add_ex_region(&ri_entry);
+
+ spin_unlock(&ctx.lock);
+
+ __pin_user_pages(ri_entry.vm_start);
+
+ return 0;
+
+error_out:
+ spin_unlock(&ctx.lock);
+ return err;
+}
+
+static unsigned long
+prel31_to_addr(const unsigned long *ptr)
+{
+ unsigned long value;
+ long offset;
+
+ if (read_user_data(ptr, value))
+ return 0;
+
+ /* sign-extend to 32 bits */
+ offset = (((long)value) << 1) >> 1;
+ return (unsigned long)ptr + offset;
+}
+
+static const struct unwind_idx *
+unwind_find_origin(const struct unwind_idx *start,
+ const struct unwind_idx *stop)
+{
+ while (start < stop) {
+ unsigned long addr_offset;
+ const struct unwind_idx *mid = start + ((stop - start) >> 1);
+
+ if (read_user_data(&mid->addr_offset, addr_offset))
+ return ERR_PTR(-EFAULT);
+
+ if (addr_offset >= 0x40000000)
+ /* negative offset */
+ start = mid + 1;
+ else
+ /* positive offset */
+ stop = mid;
+ }
+
+ return stop;
+}
+
+/*
+ * Binary search in the unwind index. The entries are
+ * guaranteed to be sorted in ascending order by the linker.
+ *
+ * start = first entry
+ * origin = first entry with positive offset (or stop if there is no such entry)
+ * stop - 1 = last entry
+ */
+static const struct unwind_idx *
+search_index(unsigned long addr,
+ const struct unwind_idx *start,
+ const struct unwind_idx *origin,
+ const struct unwind_idx *stop)
+{
+ unsigned long addr_prel31;
+
+ pr_debug("%08lx, %p, %p, %p\n", addr, start, origin, stop);
+
+ /*
+ * only search in the section with the matching sign. This way the
+ * prel31 numbers can be compared as unsigned longs.
+ */
+ if (addr < (unsigned long)start)
+ /* negative offsets: [start; origin) */
+ stop = origin;
+ else
+ /* positive offsets: [origin; stop) */
+ start = origin;
+
+ /* prel31 for address relavive to start */
+ addr_prel31 = (addr - (unsigned long)start) & 0x7fffffff;
+
+ while (start < stop - 1) {
+ unsigned long addr_offset;
+
+ const struct unwind_idx *mid = start + ((stop - start) >> 1);
+
+ /*
+ * As addr_prel31 is relative to start an offset is needed to
+ * make it relative to mid.
+ */
+ if (read_user_data(&mid->addr_offset, addr_offset))
+ return ERR_PTR(-EFAULT);
+
+ if (addr_prel31 - ((unsigned long)mid - (unsigned long)start) <
+ addr_offset) {
+ stop = mid;
+ } else {
+ /* keep addr_prel31 relative to start */
+ addr_prel31 -= ((unsigned long)mid -
+ (unsigned long)start);
+ start = mid;
+ }
+ }
+
+ if (likely(start->addr_offset <= addr_prel31))
+ return start;
+
+ pr_debug("Unknown address %08lx\n", addr);
+ return NULL;
+}
+
+static const struct unwind_idx *
+unwind_find_idx(struct extab_info *exidx, unsigned long addr)
+{
+ const struct unwind_idx *start;
+ const struct unwind_idx *origin;
+ const struct unwind_idx *stop;
+ const struct unwind_idx *idx = NULL;
+
+ start = (const struct unwind_idx *)exidx->addr;
+ stop = start + exidx->length / sizeof(*start);
+
+ origin = unwind_find_origin(start, stop);
+ if (IS_ERR(origin))
+ return origin;
+
+ idx = search_index(addr, start, origin, stop);
+
+ pr_debug("addr: %#lx, start: %p, origin: %p, stop: %p, idx: %p\n",
+ addr, start, origin, stop, idx);
+
+ return idx;
+}
+
+static unsigned long
+unwind_get_byte(struct unwind_ctrl_block *ctrl, int *err)
+{
+ unsigned long ret, insn_word;
+
+ *err = 0;
+
+ if (ctrl->entries <= 0) {
+ pr_debug("error: corrupt unwind table\n");
+ *err = -QUADD_URC_TBL_IS_CORRUPT;
+ return 0;
+ }
+
+ *err = read_user_data(ctrl->insn, insn_word);
+ if (*err < 0)
+ return 0;
+
+ ret = (insn_word >> (ctrl->byte * 8)) & 0xff;
+
+ if (ctrl->byte == 0) {
+ ctrl->insn++;
+ ctrl->entries--;
+ ctrl->byte = 3;
+ } else
+ ctrl->byte--;
+
+ return ret;
+}
+
+/*
+ * Execute the current unwind instruction.
+ */
+static int unwind_exec_insn(struct unwind_ctrl_block *ctrl)
+{
+ int i, err;
+ unsigned long insn = unwind_get_byte(ctrl, &err);
+
+ if (err < 0)
+ return err;
+
+ pr_debug("%s: insn = %08lx\n", __func__, insn);
+
+ if ((insn & 0xc0) == 0x00) {
+ ctrl->vrs[SP] += ((insn & 0x3f) << 2) + 4;
+
+ pr_debug("CMD_DATA_POP: vsp = vsp + %lu (new: %#lx)\n",
+ ((insn & 0x3f) << 2) + 4, ctrl->vrs[SP]);
+ } else if ((insn & 0xc0) == 0x40) {
+ ctrl->vrs[SP] -= ((insn & 0x3f) << 2) + 4;
+
+ pr_debug("CMD_DATA_PUSH: vsp = vsp – %lu (new: %#lx)\n",
+ ((insn & 0x3f) << 2) + 4, ctrl->vrs[SP]);
+ } else if ((insn & 0xf0) == 0x80) {
+ unsigned long mask;
+ unsigned long *vsp = (unsigned long *)ctrl->vrs[SP];
+ int load_sp, reg = 4;
+
+ insn = (insn << 8) | unwind_get_byte(ctrl, &err);
+ if (err < 0)
+ return err;
+
+ mask = insn & 0x0fff;
+ if (mask == 0) {
+ pr_debug("CMD_REFUSED: unwind: 'Refuse to unwind' instruction %04lx\n",
+ insn);
+ return -QUADD_URC_REFUSE_TO_UNWIND;
+ }
+
+ /* pop R4-R15 according to mask */
+ load_sp = mask & (1 << (13 - 4));
+ while (mask) {
+ if (mask & 1) {
+ err = read_user_data(vsp++, ctrl->vrs[reg]);
+ if (err < 0)
+ return err;
+
+ pr_debug("CMD_REG_POP: pop {r%d}\n", reg);
+ }
+ mask >>= 1;
+ reg++;
+ }
+ if (!load_sp)
+ ctrl->vrs[SP] = (unsigned long)vsp;
+
+ pr_debug("new vsp: %#lx\n", ctrl->vrs[SP]);
+ } else if ((insn & 0xf0) == 0x90 &&
+ (insn & 0x0d) != 0x0d) {
+ ctrl->vrs[SP] = ctrl->vrs[insn & 0x0f];
+ pr_debug("CMD_REG_TO_SP: vsp = {r%lu}\n", insn & 0x0f);
+ } else if ((insn & 0xf0) == 0xa0) {
+ unsigned long *vsp = (unsigned long *)ctrl->vrs[SP];
+ int reg;
+
+ /* pop R4-R[4+bbb] */
+ for (reg = 4; reg <= 4 + (insn & 7); reg++) {
+ err = read_user_data(vsp++, ctrl->vrs[reg]);
+ if (err < 0)
+ return err;
+
+ pr_debug("CMD_REG_POP: pop {r%d}\n", reg);
+ }
+
+ if (insn & 0x08) {
+ err = read_user_data(vsp++, ctrl->vrs[14]);
+ if (err < 0)
+ return err;
+
+ pr_debug("CMD_REG_POP: pop {r14}\n");
+ }
+ ctrl->vrs[SP] = (unsigned long)vsp;
+ pr_debug("new vsp: %#lx\n", ctrl->vrs[SP]);
+ } else if (insn == 0xb0) {
+ if (ctrl->vrs[PC] == 0)
+ ctrl->vrs[PC] = ctrl->vrs[LR];
+ /* no further processing */
+ ctrl->entries = 0;
+
+ pr_debug("CMD_FINISH\n");
+ } else if (insn == 0xb1) {
+ unsigned long mask = unwind_get_byte(ctrl, &err);
+ unsigned long *vsp = (unsigned long *)ctrl->vrs[SP];
+ int reg = 0;
+
+ if (err < 0)
+ return err;
+
+ if (mask == 0 || mask & 0xf0) {
+ pr_debug("unwind: Spare encoding %04lx\n",
+ (insn << 8) | mask);
+ return -QUADD_URC_SPARE_ENCODING;
+ }
+
+ /* pop R0-R3 according to mask */
+ while (mask) {
+ if (mask & 1) {
+ err = read_user_data(vsp++, ctrl->vrs[reg]);
+ if (err < 0)
+ return err;
+
+ pr_debug("CMD_REG_POP: pop {r%d}\n", reg);
+ }
+ mask >>= 1;
+ reg++;
+ }
+ ctrl->vrs[SP] = (unsigned long)vsp;
+ pr_debug("new vsp: %#lx\n", ctrl->vrs[SP]);
+ } else if (insn == 0xb2) {
+ unsigned long uleb128 = unwind_get_byte(ctrl, &err);
+ if (err < 0)
+ return err;
+
+ ctrl->vrs[SP] += 0x204 + (uleb128 << 2);
+
+ pr_debug("CMD_DATA_POP: vsp = vsp + %lu, new vsp: %#lx\n",
+ 0x204 + (uleb128 << 2), ctrl->vrs[SP]);
+ } else if (insn == 0xb3 || insn == 0xc8 || insn == 0xc9) {
+ unsigned long data, reg_from, reg_to;
+ unsigned long *vsp = (unsigned long *)ctrl->vrs[SP];
+
+ data = unwind_get_byte(ctrl, &err);
+ if (err < 0)
+ return err;
+
+ reg_from = (data & 0xf0) >> 4;
+ reg_to = reg_from + (data & 0x0f);
+
+ if (insn == 0xc8) {
+ reg_from += 16;
+ reg_to += 16;
+ }
+
+ for (i = reg_from; i <= reg_to; i++)
+ vsp += 2;
+
+ if (insn == 0xb3)
+ vsp++;
+
+ ctrl->vrs[SP] = (unsigned long)vsp;
+
+ pr_debug("CMD_VFP_POP (%#lx %#lx): pop {D%lu-D%lu}\n",
+ insn, data, reg_from, reg_to);
+ pr_debug("new vsp: %#lx\n", ctrl->vrs[SP]);
+ } else if ((insn & 0xf8) == 0xb8 || (insn & 0xf8) == 0xd0) {
+ unsigned long reg_to;
+ unsigned long data = insn & 0x07;
+ unsigned long *vsp = (unsigned long *)ctrl->vrs[SP];
+
+ reg_to = 8 + data;
+
+ for (i = 8; i <= reg_to; i++)
+ vsp += 2;
+
+ if ((insn & 0xf8) == 0xb8)
+ vsp++;
+
+ ctrl->vrs[SP] = (unsigned long)vsp;
+
+ pr_debug("CMD_VFP_POP (%#lx): pop {D8-D%lu}\n",
+ insn, reg_to);
+ pr_debug("new vsp: %#lx\n", ctrl->vrs[SP]);
+ } else {
+ pr_debug("error: unhandled instruction %02lx\n", insn);
+ return -QUADD_URC_UNHANDLED_INSTRUCTION;
+ }
+
+ pr_debug("%s: fp_arm: %#lx, fp_thumb: %#lx, sp: %#lx, lr = %#lx, pc: %#lx\n",
+ __func__,
+ ctrl->vrs[FP_ARM], ctrl->vrs[FP_THUMB], ctrl->vrs[SP],
+ ctrl->vrs[LR], ctrl->vrs[PC]);
+
+ return 0;
+}
+
+/*
+ * Unwind a single frame starting with *sp for the symbol at *pc. It
+ * updates the *pc and *sp with the new values.
+ */
+static int
+unwind_frame(struct extab_info *exidx,
+ struct stackframe *frame,
+ struct vm_area_struct *vma_sp)
+{
+ unsigned long high, low;
+ const struct unwind_idx *idx;
+ struct unwind_ctrl_block ctrl;
+ unsigned long val, err;
+
+ /* only go to a higher address on the stack */
+ low = frame->sp;
+ high = vma_sp->vm_end;
+
+ pr_debug("pc: %#lx, lr: %#lx, sp:%#lx, low/high: %#lx/%#lx\n",
+ frame->pc, frame->lr, frame->sp, low, high);
+
+ idx = unwind_find_idx(exidx, frame->pc);
+ if (IS_ERR_OR_NULL(idx))
+ return -QUADD_URC_IDX_NOT_FOUND;
+
+ pr_debug("index was found by pc (%#lx): %p\n", frame->pc, idx);
+
+ ctrl.vrs[FP_THUMB] = frame->fp_thumb;
+ ctrl.vrs[FP_ARM] = frame->fp_arm;
+
+ ctrl.vrs[SP] = frame->sp;
+ ctrl.vrs[LR] = frame->lr;
+ ctrl.vrs[PC] = 0;
+
+ err = read_user_data(&idx->insn, val);
+ if (err < 0)
+ return err;
+
+ if (val == 1) {
+ /* can't unwind */
+ return -QUADD_URC_CANTUNWIND;
+ } else if ((val & 0x80000000) == 0) {
+ /* prel31 to the unwind table */
+ ctrl.insn = (unsigned long *)prel31_to_addr(&idx->insn);
+ if (!ctrl.insn)
+ return -QUADD_URC_EACCESS;
+ } else if ((val & 0xff000000) == 0x80000000) {
+ /* only personality routine 0 supported in the index */
+ ctrl.insn = &idx->insn;
+ } else {
+ pr_debug("unsupported personality routine %08lx in the index at %p\n",
+ val, idx);
+ return -QUADD_URC_UNSUPPORTED_PR;
+ }
+
+ err = read_user_data(ctrl.insn, val);
+ if (err < 0)
+ return err;
+
+ /* check the personality routine */
+ if ((val & 0xff000000) == 0x80000000) {
+ ctrl.byte = 2;
+ ctrl.entries = 1;
+ } else if ((val & 0xff000000) == 0x81000000) {
+ ctrl.byte = 1;
+ ctrl.entries = 1 + ((val & 0x00ff0000) >> 16);
+ } else {
+ pr_debug("unsupported personality routine %08lx at %p\n",
+ val, ctrl.insn);
+ return -QUADD_URC_UNSUPPORTED_PR;
+ }
+
+ while (ctrl.entries > 0) {
+ int err = unwind_exec_insn(&ctrl);
+ if (err < 0)
+ return err;
+
+ if (ctrl.vrs[SP] < low || ctrl.vrs[SP] >= high)
+ return -QUADD_URC_SP_INCORRECT;
+ }
+
+ if (ctrl.vrs[PC] == 0)
+ ctrl.vrs[PC] = ctrl.vrs[LR];
+
+ /* check for infinite loop */
+ if (frame->pc == ctrl.vrs[PC])
+ return -QUADD_URC_FAILURE;
+
+ frame->fp_thumb = ctrl.vrs[FP_THUMB];
+ frame->fp_arm = ctrl.vrs[FP_ARM];
+
+ frame->sp = ctrl.vrs[SP];
+ frame->lr = ctrl.vrs[LR];
+ frame->pc = ctrl.vrs[PC];
+
+ return 0;
+}
+
+static void
+unwind_backtrace(struct quadd_callchain *cc,
+ struct extab_info *exidx,
+ struct pt_regs *regs,
+ struct vm_area_struct *vma_sp)
+{
+ struct extables tabs;
+ struct stackframe frame;
+
+ frame.fp_thumb = regs->ARM_r7;
+ frame.fp_arm = regs->ARM_fp;
+
+ frame.pc = instruction_pointer(regs);
+ frame.sp = regs->ARM_sp;
+
+ frame.lr = regs->ARM_lr;
+
+ cc->unw_rc = QUADD_URC_FAILURE;
+
+ pr_debug("fp_arm: %#lx, fp_thumb: %#lx, sp: %#lx, lr: %#lx, pc: %#lx\n",
+ frame.fp_arm, frame.fp_thumb, frame.sp, frame.lr, frame.pc);
+ pr_debug("vma_sp: %#lx - %#lx, length: %#lx\n",
+ vma_sp->vm_start, vma_sp->vm_end,
+ vma_sp->vm_start - vma_sp->vm_end);
+
+ while (1) {
+ int err;
+ unsigned long where = frame.pc;
+ struct vm_area_struct *vma_pc;
+ struct mm_struct *mm = current->mm;
+
+ if (!mm)
+ break;
+
+ vma_pc = find_vma(mm, frame.pc);
+ if (!vma_pc)
+ break;
+
+ if (!is_vma_addr(exidx->addr, vma_pc)) {
+ struct ex_region_info *ri;
+
+ spin_lock(&ctx.lock);
+ ri = search_ex_region(vma_pc->vm_start, &tabs);
+ spin_unlock(&ctx.lock);
+ if (!ri) {
+ cc->unw_rc = QUADD_URC_TBL_NOT_EXIST;
+ break;
+ }
+
+ exidx = &tabs.exidx;
+ }
+
+ err = unwind_frame(exidx, &frame, vma_sp);
+ if (err < 0) {
+ pr_debug("end unwind, urc: %d\n", err);
+ cc->unw_rc = -err;
+ break;
+ }
+
+ pr_debug("function at [<%08lx>] from [<%08lx>]\n",
+ where, frame.pc);
+
+ quadd_callchain_store(cc, frame.pc);
+ }
+}
+
+unsigned int
+quadd_get_user_callchain_ut(struct pt_regs *regs,
+ struct quadd_callchain *cc)
+{
+ unsigned long ip, sp;
+ struct vm_area_struct *vma, *vma_sp;
+ struct mm_struct *mm = current->mm;
+ struct ex_region_info *ri;
+ struct extables tabs;
+
+ cc->unw_rc = QUADD_URC_FAILURE;
+
+ if (!regs || !mm)
+ return 0;
+
+ ip = instruction_pointer(regs);
+
+ vma = find_vma(mm, ip);
+ if (!vma)
+ return 0;
+
+ sp = regs->ARM_sp;
+
+ vma_sp = find_vma(mm, sp);
+ if (!vma_sp)
+ return 0;
+
+ spin_lock(&ctx.lock);
+ ri = search_ex_region(vma->vm_start, &tabs);
+ spin_unlock(&ctx.lock);
+ if (!ri) {
+ cc->unw_rc = QUADD_URC_TBL_NOT_EXIST;
+ return 0;
+ }
+
+ unwind_backtrace(cc, &tabs.exidx, regs, vma_sp);
+
+ return cc->nr;
+}
+
+int quadd_unwind_start(struct task_struct *task)
+{
+ spin_lock(&ctx.lock);
+
+ kfree(ctx.regions);
+
+ ctx.ri_nr = 0;
+ ctx.ri_size = 0;
+
+ ctx.pinned_pages = 0;
+ ctx.pinned_size = 0;
+
+ ctx.regions = kzalloc(QUADD_EXTABS_SIZE * sizeof(*ctx.regions),
+ GFP_KERNEL);
+ if (!ctx.regions) {
+ spin_unlock(&ctx.lock);
+ return -ENOMEM;
+ }
+ ctx.ri_size = QUADD_EXTABS_SIZE;
+
+ ctx.task = task;
+
+ spin_unlock(&ctx.lock);
+
+ return 0;
+}
+
+void quadd_unwind_stop(void)
+{
+ spin_lock(&ctx.lock);
+
+ kfree(ctx.regions);
+ ctx.regions = NULL;
+
+ ctx.ri_size = 0;
+ ctx.ri_nr = 0;
+
+ ctx.task = NULL;
+
+ spin_unlock(&ctx.lock);
+
+ pr_info("exception tables size: %lu bytes\n", ctx.pinned_size);
+ pr_info("pinned pages: %lu (%lu bytes)\n", ctx.pinned_pages,
+ ctx.pinned_pages * PAGE_SIZE);
+}
+
+int quadd_unwind_init(void)
+{
+ ctx.regions = NULL;
+ ctx.ri_size = 0;
+ ctx.ri_nr = 0;
+
+ spin_lock_init(&ctx.lock);
+
+ return 0;
+}
+
+void quadd_unwind_deinit(void)
+{
+ quadd_unwind_stop();
+}
diff --git a/drivers/misc/tegra-profiler/eh_unwind.h b/drivers/misc/tegra-profiler/eh_unwind.h
new file mode 100644
index 000000000000..61eaa5f92f90
--- /dev/null
+++ b/drivers/misc/tegra-profiler/eh_unwind.h
@@ -0,0 +1,38 @@
+/*
+ * drivers/misc/tegra-profiler/eh_unwind.h
+ *
+ * Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ */
+
+#ifndef __QUADD_EH_UNWIND_H__
+#define __QUADD_EH_UNWIND_H__
+
+struct pt_regs;
+struct quadd_callchain;
+struct quadd_ctx;
+struct quadd_extables;
+struct task_struct;
+
+unsigned int
+quadd_get_user_callchain_ut(struct pt_regs *regs,
+ struct quadd_callchain *cc);
+
+int quadd_unwind_init(void);
+void quadd_unwind_deinit(void);
+
+int quadd_unwind_start(struct task_struct *task);
+void quadd_unwind_stop(void);
+
+int quadd_unwind_set_extab(struct quadd_extables *extabs);
+
+#endif /* __QUADD_EH_UNWIND_H__ */
diff --git a/drivers/misc/tegra-profiler/hrt.c b/drivers/misc/tegra-profiler/hrt.c
index ff631a68d698..72267feccbc7 100644
--- a/drivers/misc/tegra-profiler/hrt.c
+++ b/drivers/misc/tegra-profiler/hrt.c
@@ -134,7 +134,7 @@ static void put_header(void)
hdr->power_rate_freq = param->power_rate_freq;
hdr->power_rate = hdr->power_rate_freq > 0 ? 1 : 0;
- hdr->get_mmap = (extra & QUADD_PARAM_IDX_EXTRA_GET_MMAP) ? 1 : 0;
+ hdr->get_mmap = (extra & QUADD_PARAM_EXTRA_GET_MMAP) ? 1 : 0;
hdr->reserved = 0;
hdr->extra_length = 0;
@@ -223,7 +223,7 @@ static int read_source(struct quadd_event_source_interface *source,
if (s->event_source == QUADD_EVENT_SOURCE_PL310) {
int nr_active = atomic_read(&hrt.nr_active_all_core);
if (nr_active > 1)
- res_val = res_val / nr_active;
+ res_val /= nr_active;
}
events_vals[i].event_id = s->event_id;
@@ -249,7 +249,7 @@ read_all_sources(struct pt_regs *regs, struct task_struct *task)
struct quadd_ctx *ctx = hrt.quadd_ctx;
struct quadd_cpu_context *cpu_ctx = this_cpu_ptr(hrt.cpu_ctx);
- struct quadd_callchain *cc_data = &cpu_ctx->callchain_data;
+ struct quadd_callchain *cc = &cpu_ctx->cc;
if (!regs)
return;
@@ -294,14 +294,21 @@ read_all_sources(struct pt_regs *regs, struct task_struct *task)
if (get_sample_data(s, regs, task))
return;
+ s->reserved = 0;
+
if (ctx->param.backtrace) {
- bt_size = quadd_get_user_callchain(user_regs, cc_data);
+ bt_size = quadd_get_user_callchain(user_regs, cc, ctx);
if (bt_size > 0) {
- vec[vec_idx].base = cc_data->callchain;
+ vec[vec_idx].base = cc->ip;
vec[vec_idx].len =
- bt_size * sizeof(cc_data->callchain[0]);
+ bt_size * sizeof(cc->ip[0]);
vec_idx++;
}
+
+ s->reserved |= cc->unw_method << QUADD_SAMPLE_UNW_METHOD_SHIFT;
+
+ if (cc->unw_method == QUADD_UNW_METHOD_EHT)
+ s->reserved |= cc->unw_rc << QUADD_SAMPLE_URC_SHIFT;
}
s->callchain_nr = bt_size;
@@ -474,7 +481,6 @@ void __quadd_event_mmap(struct vm_area_struct *vma)
param = &hrt.quadd_ctx->param;
quadd_process_mmap(vma, param->pids[0]);
}
-EXPORT_SYMBOL(__quadd_event_mmap);
static void reset_cpu_ctx(void)
{
@@ -520,7 +526,7 @@ int quadd_hrt_start(void)
extra = param->reserved[QUADD_PARAM_IDX_EXTRA];
- if (extra & QUADD_PARAM_IDX_EXTRA_GET_MMAP) {
+ if (extra & QUADD_PARAM_EXTRA_GET_MMAP) {
err = quadd_get_current_mmap(param->pids[0]);
if (err) {
pr_err("error: quadd_get_current_mmap\n");
diff --git a/drivers/misc/tegra-profiler/hrt.h b/drivers/misc/tegra-profiler/hrt.h
index ed8c99a391c7..f209dc116119 100644
--- a/drivers/misc/tegra-profiler/hrt.h
+++ b/drivers/misc/tegra-profiler/hrt.h
@@ -17,8 +17,6 @@
#ifndef __QUADD_HRT_H
#define __QUADD_HRT_H
-#define QUADD_MAX_STACK_DEPTH 64
-
#ifdef __KERNEL__
#include <linux/hrtimer.h>
@@ -34,7 +32,7 @@ struct quadd_thread_data {
struct quadd_cpu_context {
struct hrtimer hrtimer;
- struct quadd_callchain callchain_data;
+ struct quadd_callchain cc;
char mmap_filename[PATH_MAX];
struct quadd_thread_data active_thread;
diff --git a/drivers/misc/tegra-profiler/main.c b/drivers/misc/tegra-profiler/main.c
index c28748b01a6b..4a83663fe184 100644
--- a/drivers/misc/tegra-profiler/main.c
+++ b/drivers/misc/tegra-profiler/main.c
@@ -34,6 +34,7 @@
#include "auth.h"
#include "version.h"
#include "quadd_proc.h"
+#include "eh_unwind.h"
#ifdef CONFIG_CACHE_L2X0
#include "pl310.h"
@@ -126,6 +127,7 @@ static void stop(void)
ctx.comm->reset();
quadd_power_clk_stop();
+ quadd_unwind_stop();
if (ctx.pmu)
ctx.pmu->disable();
@@ -158,6 +160,7 @@ static int set_parameters(struct quadd_parameters *param, uid_t *debug_app_uid)
int nr_pmu = 0, nr_pl310 = 0;
int uid = 0;
struct task_struct *task;
+ unsigned int extra;
if (ctx.param.freq != 100 && ctx.param.freq != 1000 &&
ctx.param.freq != 10000)
@@ -171,7 +174,7 @@ static int set_parameters(struct quadd_parameters *param, uid_t *debug_app_uid)
ctx.param.power_rate_freq = param->power_rate_freq;
ctx.param.debug_samples = param->debug_samples;
- for (i = 0; i < QM_ARRAY_SIZE(param->reserved); i++)
+ for (i = 0; i < ARRAY_SIZE(param->reserved); i++)
ctx.param.reserved[i] = param->reserved[i];
/* Currently only one process */
@@ -266,6 +269,17 @@ static int set_parameters(struct quadd_parameters *param, uid_t *debug_app_uid)
ctx.pl310->set_events(NULL, 0);
}
}
+
+ extra = param->reserved[QUADD_PARAM_IDX_EXTRA];
+
+ if (extra & QUADD_PARAM_EXTRA_BT_UNWIND_TABLES)
+ pr_info("unwinding: exception-handling tables\n");
+
+ if (extra & QUADD_PARAM_EXTRA_BT_FP)
+ pr_info("unwinding: frame pointers\n");
+
+ quadd_unwind_start(task);
+
pr_info("New parameters have been applied\n");
return 0;
@@ -411,12 +425,19 @@ void quadd_get_state(struct quadd_module_state *state)
state->reserved[QUADD_MOD_STATE_IDX_STATUS] = status;
}
+static int
+set_extab(struct quadd_extables *extabs)
+{
+ return quadd_unwind_set_extab(extabs);
+}
+
static struct quadd_comm_control_interface control = {
.start = start,
.stop = stop,
.set_parameters = set_parameters,
.get_capabilities = get_capabilities,
.get_state = quadd_get_state,
+ .set_extab = set_extab,
};
static int __init quadd_module_init(void)
@@ -503,6 +524,12 @@ static int __init quadd_module_init(void)
return err;
}
+ err = quadd_unwind_init();
+ if (err < 0) {
+ pr_err("error: EH unwinding init failed\n");
+ return err;
+ }
+
get_capabilities(&ctx.cap);
quadd_proc_init(&ctx);
@@ -519,6 +546,7 @@ static void __exit quadd_module_exit(void)
quadd_auth_deinit();
quadd_proc_deinit();
quadd_armv7_pmu_deinit();
+ quadd_unwind_deinit();
}
module_init(quadd_module_init);
diff --git a/drivers/misc/tegra-profiler/version.h b/drivers/misc/tegra-profiler/version.h
index 97d0a51f90d5..1ecb006c7f0f 100644
--- a/drivers/misc/tegra-profiler/version.h
+++ b/drivers/misc/tegra-profiler/version.h
@@ -18,7 +18,7 @@
#ifndef __QUADD_VERSION_H
#define __QUADD_VERSION_H
-#define QUADD_MODULE_VERSION "1.51"
+#define QUADD_MODULE_VERSION "1.55"
#define QUADD_MODULE_BRANCH "Dev"
#endif /* __QUADD_VERSION_H */
diff --git a/include/linux/tegra_profiler.h b/include/linux/tegra_profiler.h
index 9a7299e457ed..9f849a46fd3a 100644
--- a/include/linux/tegra_profiler.h
+++ b/include/linux/tegra_profiler.h
@@ -19,18 +19,20 @@
#include <linux/ioctl.h>
-#define QUADD_SAMPLES_VERSION 21
-#define QUADD_IO_VERSION 9
+#define QUADD_SAMPLES_VERSION 22
+#define QUADD_IO_VERSION 10
#define QUADD_IO_VERSION_DYNAMIC_RB 5
#define QUADD_IO_VERSION_RB_MAX_FILL_COUNT 6
#define QUADD_IO_VERSION_MOD_STATE_STATUS_FIELD 7
#define QUADD_IO_VERSION_BT_KERNEL_CTX 8
#define QUADD_IO_VERSION_GET_MMAP 9
+#define QUADD_IO_VERSION_BT_UNWIND_TABLES 10
#define QUADD_SAMPLE_VERSION_THUMB_MODE_FLAG 17
#define QUADD_SAMPLE_VERSION_GROUP_SAMPLES 18
#define QUADD_SAMPLE_VERSION_THREAD_STATE_FLD 19
+#define QUADD_SAMPLE_VERSION_BT_UNWIND_TABLES 22
#define QUADD_MAX_COUNTERS 32
#define QUADD_MAX_PROCESS 64
@@ -73,8 +75,10 @@
*/
#define IOCTL_GET_VERSION _IOR(QUADD_IOCTL, 5, struct quadd_module_version)
-
-#define QUADD_HRT_SCHED_IN_FUNC "finish_task_switch"
+/*
+ * Send exception-handling tables info
+ */
+#define IOCTL_SET_EXTAB _IOW(QUADD_IOCTL, 6, struct quadd_extables)
#define QUADD_CPUMODE_TEGRA_POWER_CLUSTER_LP (1 << 29) /* LP CPU */
#define QUADD_CPUMODE_THUMB (1 << 30) /* thumb mode */
@@ -132,6 +136,32 @@ typedef u32 quadd_bt_addr_t;
#pragma pack(push, 1)
+#define QUADD_SAMPLE_UNW_METHOD_SHIFT 0
+#define QUADD_SAMPLE_UNW_METHOD_MASK (1 << QUADD_SAMPLE_UNW_METHOD_SHIFT)
+
+enum {
+ QUADD_UNW_METHOD_FP = 0,
+ QUADD_UNW_METHOD_EHT,
+};
+
+#define QUADD_SAMPLE_URC_SHIFT 1
+#define QUADD_SAMPLE_URC_MASK (0x0f << QUADD_SAMPLE_URC_SHIFT)
+
+enum {
+ QUADD_URC_SUCCESS = 0,
+ QUADD_URC_FAILURE,
+ QUADD_URC_IDX_NOT_FOUND,
+ QUADD_URC_TBL_NOT_EXIST,
+ QUADD_URC_EACCESS,
+ QUADD_URC_TBL_IS_CORRUPT,
+ QUADD_URC_CANTUNWIND,
+ QUADD_URC_UNHANDLED_INSTRUCTION,
+ QUADD_URC_REFUSE_TO_UNWIND,
+ QUADD_URC_SP_INCORRECT,
+ QUADD_URC_SPARE_ENCODING,
+ QUADD_URC_UNSUPPORTED_PR,
+};
+
struct quadd_sample_data {
u64 ip;
u32 pid;
@@ -260,7 +290,9 @@ enum {
QUADD_PARAM_IDX_EXTRA = 1,
};
-#define QUADD_PARAM_IDX_EXTRA_GET_MMAP (1 << 0)
+#define QUADD_PARAM_EXTRA_GET_MMAP (1 << 0)
+#define QUADD_PARAM_EXTRA_BT_FP (1 << 1)
+#define QUADD_PARAM_EXTRA_BT_UNWIND_TABLES (1 << 2)
struct quadd_parameters {
u32 freq;
@@ -306,6 +338,7 @@ enum {
#define QUADD_COMM_CAP_EXTRA_BT_KERNEL_CTX (1 << 0)
#define QUADD_COMM_CAP_EXTRA_GET_MMAP (1 << 1)
#define QUADD_COMM_CAP_EXTRA_GROUP_SAMPLES (1 << 2)
+#define QUADD_COMM_CAP_EXTRA_BT_UNWIND_TABLES (1 << 3)
struct quadd_comm_cap {
u32 pmu:1,
@@ -348,6 +381,21 @@ struct quadd_module_version {
u32 reserved[4]; /* reserved fields for future extensions */
};
+struct quadd_sec_info {
+ u64 addr;
+ u64 length;
+};
+
+struct quadd_extables {
+ u64 vm_start;
+ u64 vm_end;
+
+ struct quadd_sec_info extab;
+ struct quadd_sec_info exidx;
+
+ u32 reserved[4]; /* reserved fields for future extensions */
+};
+
#pragma pack(pop)
#ifdef __KERNEL__