diff options
author | Igor Nabirushkin <inabirushkin@nvidia.com> | 2014-02-20 20:48:48 +0400 |
---|---|---|
committer | Riham Haidar <rhaidar@nvidia.com> | 2014-03-06 12:31:21 -0800 |
commit | ccde0304ffcf4a1cb75bad78507597edc1d69290 (patch) | |
tree | d5f6da2e278c7c35059062ebd9a519ad267e683a /drivers | |
parent | cd663c69739cd88af046f3a31f961956589d3f17 (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>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/misc/tegra-profiler/Makefile | 3 | ||||
-rw-r--r-- | drivers/misc/tegra-profiler/backtrace.c | 104 | ||||
-rw-r--r-- | drivers/misc/tegra-profiler/backtrace.h | 25 | ||||
-rw-r--r-- | drivers/misc/tegra-profiler/comm.c | 75 | ||||
-rw-r--r-- | drivers/misc/tegra-profiler/comm.h | 5 | ||||
-rw-r--r-- | drivers/misc/tegra-profiler/eh_unwind.c | 924 | ||||
-rw-r--r-- | drivers/misc/tegra-profiler/eh_unwind.h | 38 | ||||
-rw-r--r-- | drivers/misc/tegra-profiler/hrt.c | 22 | ||||
-rw-r--r-- | drivers/misc/tegra-profiler/hrt.h | 4 | ||||
-rw-r--r-- | drivers/misc/tegra-profiler/main.c | 30 | ||||
-rw-r--r-- | drivers/misc/tegra-profiler/version.h | 2 |
11 files changed, 1139 insertions, 93 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 */ |