diff options
Diffstat (limited to 'kernel/arch/blackfin/kernel/ptrace.c')
-rw-r--r-- | kernel/arch/blackfin/kernel/ptrace.c | 411 |
1 files changed, 411 insertions, 0 deletions
diff --git a/kernel/arch/blackfin/kernel/ptrace.c b/kernel/arch/blackfin/kernel/ptrace.c new file mode 100644 index 000000000..8b8fe671b --- /dev/null +++ b/kernel/arch/blackfin/kernel/ptrace.c @@ -0,0 +1,411 @@ +/* + * linux/kernel/ptrace.c is by Ross Biro 1/23/92, edited by Linus Torvalds + * these modifications are Copyright 2004-2010 Analog Devices Inc. + * + * Licensed under the GPL-2 + */ + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/smp.h> +#include <linux/elf.h> +#include <linux/errno.h> +#include <linux/ptrace.h> +#include <linux/user.h> +#include <linux/regset.h> +#include <linux/signal.h> +#include <linux/tracehook.h> +#include <linux/uaccess.h> + +#include <asm/page.h> +#include <asm/pgtable.h> +#include <asm/processor.h> +#include <asm/asm-offsets.h> +#include <asm/dma.h> +#include <asm/fixed_code.h> +#include <asm/cacheflush.h> +#include <asm/mem_map.h> +#include <asm/mmu_context.h> + +/* + * does not yet catch signals sent when the child dies. + * in exit.c or in signal.c. + */ + +/* + * Get contents of register REGNO in task TASK. + */ +static inline long +get_reg(struct task_struct *task, unsigned long regno, + unsigned long __user *datap) +{ + long tmp; + struct pt_regs *regs = task_pt_regs(task); + + if (regno & 3 || regno > PT_LAST_PSEUDO) + return -EIO; + + switch (regno) { + case PT_TEXT_ADDR: + tmp = task->mm->start_code; + break; + case PT_TEXT_END_ADDR: + tmp = task->mm->end_code; + break; + case PT_DATA_ADDR: + tmp = task->mm->start_data; + break; + case PT_USP: + tmp = task->thread.usp; + break; + default: + if (regno < sizeof(*regs)) { + void *reg_ptr = regs; + tmp = *(long *)(reg_ptr + regno); + } else + return -EIO; + } + + return put_user(tmp, datap); +} + +/* + * Write contents of register REGNO in task TASK. + */ +static inline int +put_reg(struct task_struct *task, unsigned long regno, unsigned long data) +{ + struct pt_regs *regs = task_pt_regs(task); + + if (regno & 3 || regno > PT_LAST_PSEUDO) + return -EIO; + + switch (regno) { + case PT_PC: + /*********************************************************************/ + /* At this point the kernel is most likely in exception. */ + /* The RETX register will be used to populate the pc of the process. */ + /*********************************************************************/ + regs->retx = data; + regs->pc = data; + break; + case PT_RETX: + break; /* regs->retx = data; break; */ + case PT_USP: + regs->usp = data; + task->thread.usp = data; + break; + case PT_SYSCFG: /* don't let userspace screw with this */ + if ((data & ~1) != 0x6) + pr_warning("ptrace: ignore syscfg write of %#lx\n", data); + break; /* regs->syscfg = data; break; */ + default: + if (regno < sizeof(*regs)) { + void *reg_offset = regs; + *(long *)(reg_offset + regno) = data; + } + /* Ignore writes to pseudo registers */ + } + + return 0; +} + +/* + * check that an address falls within the bounds of the target process's memory mappings + */ +int +is_user_addr_valid(struct task_struct *child, unsigned long start, unsigned long len) +{ + bool valid; + struct vm_area_struct *vma; + struct sram_list_struct *sraml; + + /* overflow */ + if (start + len < start) + return -EIO; + + down_read(&child->mm->mmap_sem); + vma = find_vma(child->mm, start); + valid = vma && start >= vma->vm_start && start + len <= vma->vm_end; + up_read(&child->mm->mmap_sem); + if (valid) + return 0; + + for (sraml = child->mm->context.sram_list; sraml; sraml = sraml->next) + if (start >= (unsigned long)sraml->addr + && start + len < (unsigned long)sraml->addr + sraml->length) + return 0; + + if (start >= FIXED_CODE_START && start + len < FIXED_CODE_END) + return 0; + +#ifdef CONFIG_APP_STACK_L1 + if (child->mm->context.l1_stack_save) + if (start >= (unsigned long)l1_stack_base && + start + len < (unsigned long)l1_stack_base + l1_stack_len) + return 0; +#endif + + return -EIO; +} + +/* + * retrieve the contents of Blackfin userspace general registers + */ +static int genregs_get(struct task_struct *target, + const struct user_regset *regset, + unsigned int pos, unsigned int count, + void *kbuf, void __user *ubuf) +{ + struct pt_regs *regs = task_pt_regs(target); + int ret; + + /* This sucks ... */ + regs->usp = target->thread.usp; + + ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf, + regs, 0, sizeof(*regs)); + if (ret < 0) + return ret; + + return user_regset_copyout_zero(&pos, &count, &kbuf, &ubuf, + sizeof(*regs), -1); +} + +/* + * update the contents of the Blackfin userspace general registers + */ +static int genregs_set(struct task_struct *target, + const struct user_regset *regset, + unsigned int pos, unsigned int count, + const void *kbuf, const void __user *ubuf) +{ + struct pt_regs *regs = task_pt_regs(target); + int ret; + + /* Don't let people set SYSCFG (it's at the end of pt_regs) */ + ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, + regs, 0, PT_SYSCFG); + if (ret < 0) + return ret; + + /* This sucks ... */ + target->thread.usp = regs->usp; + /* regs->retx = regs->pc; */ + + return user_regset_copyin_ignore(&pos, &count, &kbuf, &ubuf, + PT_SYSCFG, -1); +} + +/* + * Define the register sets available on the Blackfin under Linux + */ +enum bfin_regset { + REGSET_GENERAL, +}; + +static const struct user_regset bfin_regsets[] = { + [REGSET_GENERAL] = { + .core_note_type = NT_PRSTATUS, + .n = sizeof(struct pt_regs) / sizeof(long), + .size = sizeof(long), + .align = sizeof(long), + .get = genregs_get, + .set = genregs_set, + }, +}; + +static const struct user_regset_view user_bfin_native_view = { + .name = "Blackfin", + .e_machine = EM_BLACKFIN, + .regsets = bfin_regsets, + .n = ARRAY_SIZE(bfin_regsets), +}; + +const struct user_regset_view *task_user_regset_view(struct task_struct *task) +{ + return &user_bfin_native_view; +} + +void user_enable_single_step(struct task_struct *child) +{ + struct pt_regs *regs = task_pt_regs(child); + regs->syscfg |= SYSCFG_SSSTEP; + + set_tsk_thread_flag(child, TIF_SINGLESTEP); +} + +void user_disable_single_step(struct task_struct *child) +{ + struct pt_regs *regs = task_pt_regs(child); + regs->syscfg &= ~SYSCFG_SSSTEP; + + clear_tsk_thread_flag(child, TIF_SINGLESTEP); +} + +long arch_ptrace(struct task_struct *child, long request, + unsigned long addr, unsigned long data) +{ + int ret; + unsigned long __user *datap = (unsigned long __user *)data; + void *paddr = (void *)addr; + + switch (request) { + /* when I and D space are separate, these will need to be fixed. */ + case PTRACE_PEEKDATA: + pr_debug("ptrace: PEEKDATA\n"); + /* fall through */ + case PTRACE_PEEKTEXT: /* read word at location addr. */ + { + unsigned long tmp = 0; + int copied = 0, to_copy = sizeof(tmp); + + ret = -EIO; + pr_debug("ptrace: PEEKTEXT at addr 0x%08lx + %i\n", addr, to_copy); + if (is_user_addr_valid(child, addr, to_copy) < 0) + break; + pr_debug("ptrace: user address is valid\n"); + + switch (bfin_mem_access_type(addr, to_copy)) { + case BFIN_MEM_ACCESS_CORE: + case BFIN_MEM_ACCESS_CORE_ONLY: + copied = access_process_vm(child, addr, &tmp, + to_copy, 0); + if (copied) + break; + + /* hrm, why didn't that work ... maybe no mapping */ + if (addr >= FIXED_CODE_START && + addr + to_copy <= FIXED_CODE_END) { + copy_from_user_page(0, 0, 0, &tmp, paddr, to_copy); + copied = to_copy; + } else if (addr >= BOOT_ROM_START) { + memcpy(&tmp, paddr, to_copy); + copied = to_copy; + } + + break; + case BFIN_MEM_ACCESS_DMA: + if (safe_dma_memcpy(&tmp, paddr, to_copy)) + copied = to_copy; + break; + case BFIN_MEM_ACCESS_ITEST: + if (isram_memcpy(&tmp, paddr, to_copy)) + copied = to_copy; + break; + default: + copied = 0; + break; + } + + pr_debug("ptrace: copied size %d [0x%08lx]\n", copied, tmp); + if (copied == to_copy) + ret = put_user(tmp, datap); + break; + } + + /* when I and D space are separate, this will have to be fixed. */ + case PTRACE_POKEDATA: + pr_debug("ptrace: PTRACE_PEEKDATA\n"); + /* fall through */ + case PTRACE_POKETEXT: /* write the word at location addr. */ + { + int copied = 0, to_copy = sizeof(data); + + ret = -EIO; + pr_debug("ptrace: POKETEXT at addr 0x%08lx + %i bytes %lx\n", + addr, to_copy, data); + if (is_user_addr_valid(child, addr, to_copy) < 0) + break; + pr_debug("ptrace: user address is valid\n"); + + switch (bfin_mem_access_type(addr, to_copy)) { + case BFIN_MEM_ACCESS_CORE: + case BFIN_MEM_ACCESS_CORE_ONLY: + copied = access_process_vm(child, addr, &data, + to_copy, 1); + break; + case BFIN_MEM_ACCESS_DMA: + if (safe_dma_memcpy(paddr, &data, to_copy)) + copied = to_copy; + break; + case BFIN_MEM_ACCESS_ITEST: + if (isram_memcpy(paddr, &data, to_copy)) + copied = to_copy; + break; + default: + copied = 0; + break; + } + + pr_debug("ptrace: copied size %d\n", copied); + if (copied == to_copy) + ret = 0; + break; + } + + case PTRACE_PEEKUSR: + switch (addr) { +#ifdef CONFIG_BINFMT_ELF_FDPIC /* backwards compat */ + case PT_FDPIC_EXEC: + request = PTRACE_GETFDPIC; + addr = PTRACE_GETFDPIC_EXEC; + goto case_default; + case PT_FDPIC_INTERP: + request = PTRACE_GETFDPIC; + addr = PTRACE_GETFDPIC_INTERP; + goto case_default; +#endif + default: + ret = get_reg(child, addr, datap); + } + pr_debug("ptrace: PEEKUSR reg %li with %#lx = %i\n", addr, data, ret); + break; + + case PTRACE_POKEUSR: + ret = put_reg(child, addr, data); + pr_debug("ptrace: POKEUSR reg %li with %li = %i\n", addr, data, ret); + break; + + case PTRACE_GETREGS: + pr_debug("ptrace: PTRACE_GETREGS\n"); + return copy_regset_to_user(child, &user_bfin_native_view, + REGSET_GENERAL, + 0, sizeof(struct pt_regs), + datap); + + case PTRACE_SETREGS: + pr_debug("ptrace: PTRACE_SETREGS\n"); + return copy_regset_from_user(child, &user_bfin_native_view, + REGSET_GENERAL, + 0, sizeof(struct pt_regs), + datap); + + case_default: + default: + ret = ptrace_request(child, request, addr, data); + break; + } + + return ret; +} + +asmlinkage int syscall_trace_enter(struct pt_regs *regs) +{ + int ret = 0; + + if (test_thread_flag(TIF_SYSCALL_TRACE)) + ret = tracehook_report_syscall_entry(regs); + + return ret; +} + +asmlinkage void syscall_trace_leave(struct pt_regs *regs) +{ + int step; + + step = test_thread_flag(TIF_SINGLESTEP); + if (step || test_thread_flag(TIF_SYSCALL_TRACE)) + tracehook_report_syscall_exit(regs, step); +} |