linux/arch/avr32/kernel/ptrace.c
Haavard Skinnemoen 5f97f7f940 [PATCH] avr32 architecture
This adds support for the Atmel AVR32 architecture as well as the AT32AP7000
CPU and the AT32STK1000 development board.

AVR32 is a new high-performance 32-bit RISC microprocessor core, designed for
cost-sensitive embedded applications, with particular emphasis on low power
consumption and high code density.  The AVR32 architecture is not binary
compatible with earlier 8-bit AVR architectures.

The AVR32 architecture, including the instruction set, is described by the
AVR32 Architecture Manual, available from

http://www.atmel.com/dyn/resources/prod_documents/doc32000.pdf

The Atmel AT32AP7000 is the first CPU implementing the AVR32 architecture.  It
features a 7-stage pipeline, 16KB instruction and data caches and a full
Memory Management Unit.  It also comes with a large set of integrated
peripherals, many of which are shared with the AT91 ARM-based controllers from
Atmel.

Full data sheet is available from

http://www.atmel.com/dyn/resources/prod_documents/doc32003.pdf

while the CPU core implementation including caches and MMU is documented by
the AVR32 AP Technical Reference, available from

http://www.atmel.com/dyn/resources/prod_documents/doc32001.pdf

Information about the AT32STK1000 development board can be found at

http://www.atmel.com/dyn/products/tools_card.asp?tool_id=3918

including a BSP CD image with an earlier version of this patch, development
tools (binaries and source/patches) and a root filesystem image suitable for
booting from SD card.

Alternatively, there's a preliminary "getting started" guide available at
http://avr32linux.org/twiki/bin/view/Main/GettingStarted which provides links
to the sources and patches you will need in order to set up a cross-compiling
environment for avr32-linux.

This patch, as well as the other patches included with the BSP and the
toolchain patches, is actively supported by Atmel Corporation.

[dmccr@us.ibm.com: Fix more pxx_page macro locations]
[bunk@stusta.de: fix `make defconfig']
Signed-off-by: Haavard Skinnemoen <hskinnemoen@atmel.com>
Signed-off-by: Adrian Bunk <bunk@stusta.de>
Signed-off-by: Dave McCracken <dmccr@us.ibm.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2006-09-26 08:48:54 -07:00

372 lines
8.9 KiB
C

/*
* Copyright (C) 2004-2006 Atmel Corporation
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#undef DEBUG
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <linux/smp_lock.h>
#include <linux/ptrace.h>
#include <linux/errno.h>
#include <linux/user.h>
#include <linux/security.h>
#include <linux/unistd.h>
#include <linux/notifier.h>
#include <asm/traps.h>
#include <asm/uaccess.h>
#include <asm/ocd.h>
#include <asm/mmu_context.h>
#include <asm/kdebug.h>
static struct pt_regs *get_user_regs(struct task_struct *tsk)
{
return (struct pt_regs *)((unsigned long) tsk->thread_info +
THREAD_SIZE - sizeof(struct pt_regs));
}
static void ptrace_single_step(struct task_struct *tsk)
{
pr_debug("ptrace_single_step: pid=%u, SR=0x%08lx\n",
tsk->pid, tsk->thread.cpu_context.sr);
if (!(tsk->thread.cpu_context.sr & SR_D)) {
/*
* Set a breakpoint at the current pc to force the
* process into debug mode. The syscall/exception
* exit code will set a breakpoint at the return
* address when this flag is set.
*/
pr_debug("ptrace_single_step: Setting TIF_BREAKPOINT\n");
set_tsk_thread_flag(tsk, TIF_BREAKPOINT);
}
/* The monitor code will do the actual step for us */
set_tsk_thread_flag(tsk, TIF_SINGLE_STEP);
}
/*
* Called by kernel/ptrace.c when detaching
*
* Make sure any single step bits, etc. are not set
*/
void ptrace_disable(struct task_struct *child)
{
clear_tsk_thread_flag(child, TIF_SINGLE_STEP);
}
/*
* Handle hitting a breakpoint
*/
static void ptrace_break(struct task_struct *tsk, struct pt_regs *regs)
{
siginfo_t info;
info.si_signo = SIGTRAP;
info.si_errno = 0;
info.si_code = TRAP_BRKPT;
info.si_addr = (void __user *)instruction_pointer(regs);
pr_debug("ptrace_break: Sending SIGTRAP to PID %u (pc = 0x%p)\n",
tsk->pid, info.si_addr);
force_sig_info(SIGTRAP, &info, tsk);
}
/*
* Read the word at offset "offset" into the task's "struct user". We
* actually access the pt_regs struct stored on the kernel stack.
*/
static int ptrace_read_user(struct task_struct *tsk, unsigned long offset,
unsigned long __user *data)
{
unsigned long *regs;
unsigned long value;
pr_debug("ptrace_read_user(%p, %#lx, %p)\n",
tsk, offset, data);
if (offset & 3 || offset >= sizeof(struct user)) {
printk("ptrace_read_user: invalid offset 0x%08lx\n", offset);
return -EIO;
}
regs = (unsigned long *)get_user_regs(tsk);
value = 0;
if (offset < sizeof(struct pt_regs))
value = regs[offset / sizeof(regs[0])];
return put_user(value, data);
}
/*
* Write the word "value" to offset "offset" into the task's "struct
* user". We actually access the pt_regs struct stored on the kernel
* stack.
*/
static int ptrace_write_user(struct task_struct *tsk, unsigned long offset,
unsigned long value)
{
unsigned long *regs;
if (offset & 3 || offset >= sizeof(struct user)) {
printk("ptrace_write_user: invalid offset 0x%08lx\n", offset);
return -EIO;
}
if (offset >= sizeof(struct pt_regs))
return 0;
regs = (unsigned long *)get_user_regs(tsk);
regs[offset / sizeof(regs[0])] = value;
return 0;
}
static int ptrace_getregs(struct task_struct *tsk, void __user *uregs)
{
struct pt_regs *regs = get_user_regs(tsk);
return copy_to_user(uregs, regs, sizeof(*regs)) ? -EFAULT : 0;
}
static int ptrace_setregs(struct task_struct *tsk, const void __user *uregs)
{
struct pt_regs newregs;
int ret;
ret = -EFAULT;
if (copy_from_user(&newregs, uregs, sizeof(newregs)) == 0) {
struct pt_regs *regs = get_user_regs(tsk);
ret = -EINVAL;
if (valid_user_regs(&newregs)) {
*regs = newregs;
ret = 0;
}
}
return ret;
}
long arch_ptrace(struct task_struct *child, long request, long addr, long data)
{
unsigned long tmp;
int ret;
pr_debug("arch_ptrace(%ld, %ld, %#lx, %#lx)\n",
request, child->pid, addr, data);
pr_debug("ptrace: Enabling monitor mode...\n");
__mtdr(DBGREG_DC, __mfdr(DBGREG_DC) | DC_MM | DC_DBE);
switch (request) {
/* Read the word at location addr in the child process */
case PTRACE_PEEKTEXT:
case PTRACE_PEEKDATA:
ret = access_process_vm(child, addr, &tmp, sizeof(tmp), 0);
if (ret == sizeof(tmp))
ret = put_user(tmp, (unsigned long __user *)data);
else
ret = -EIO;
break;
case PTRACE_PEEKUSR:
ret = ptrace_read_user(child, addr,
(unsigned long __user *)data);
break;
/* Write the word in data at location addr */
case PTRACE_POKETEXT:
case PTRACE_POKEDATA:
ret = access_process_vm(child, addr, &data, sizeof(data), 1);
if (ret == sizeof(data))
ret = 0;
else
ret = -EIO;
break;
case PTRACE_POKEUSR:
ret = ptrace_write_user(child, addr, data);
break;
/* continue and stop at next (return from) syscall */
case PTRACE_SYSCALL:
/* restart after signal */
case PTRACE_CONT:
ret = -EIO;
if (!valid_signal(data))
break;
if (request == PTRACE_SYSCALL)
set_tsk_thread_flag(child, TIF_SYSCALL_TRACE);
else
clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE);
child->exit_code = data;
/* XXX: Are we sure no breakpoints are active here? */
wake_up_process(child);
ret = 0;
break;
/*
* Make the child exit. Best I can do is send it a
* SIGKILL. Perhaps it should be put in the status that it
* wants to exit.
*/
case PTRACE_KILL:
ret = 0;
if (child->exit_state == EXIT_ZOMBIE)
break;
child->exit_code = SIGKILL;
wake_up_process(child);
break;
/*
* execute single instruction.
*/
case PTRACE_SINGLESTEP:
ret = -EIO;
if (!valid_signal(data))
break;
clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE);
ptrace_single_step(child);
child->exit_code = data;
wake_up_process(child);
ret = 0;
break;
/* Detach a process that was attached */
case PTRACE_DETACH:
ret = ptrace_detach(child, data);
break;
case PTRACE_GETREGS:
ret = ptrace_getregs(child, (void __user *)data);
break;
case PTRACE_SETREGS:
ret = ptrace_setregs(child, (const void __user *)data);
break;
default:
ret = ptrace_request(child, request, addr, data);
break;
}
pr_debug("sys_ptrace returning %d (DC = 0x%08lx)\n", ret, __mfdr(DBGREG_DC));
return ret;
}
asmlinkage void syscall_trace(void)
{
pr_debug("syscall_trace called\n");
if (!test_thread_flag(TIF_SYSCALL_TRACE))
return;
if (!(current->ptrace & PT_PTRACED))
return;
pr_debug("syscall_trace: notifying parent\n");
/* The 0x80 provides a way for the tracing parent to
* distinguish between a syscall stop and SIGTRAP delivery */
ptrace_notify(SIGTRAP | ((current->ptrace & PT_TRACESYSGOOD)
? 0x80 : 0));
/*
* this isn't the same as continuing with a signal, but it
* will do for normal use. strace only continues with a
* signal if the stopping signal is not SIGTRAP. -brl
*/
if (current->exit_code) {
pr_debug("syscall_trace: sending signal %d to PID %u\n",
current->exit_code, current->pid);
send_sig(current->exit_code, current, 1);
current->exit_code = 0;
}
}
asmlinkage void do_debug_priv(struct pt_regs *regs)
{
unsigned long dc, ds;
unsigned long die_val;
ds = __mfdr(DBGREG_DS);
pr_debug("do_debug_priv: pc = %08lx, ds = %08lx\n", regs->pc, ds);
if (ds & DS_SSS)
die_val = DIE_SSTEP;
else
die_val = DIE_BREAKPOINT;
if (notify_die(die_val, regs, 0, SIGTRAP) == NOTIFY_STOP)
return;
if (likely(ds & DS_SSS)) {
extern void itlb_miss(void);
extern void tlb_miss_common(void);
struct thread_info *ti;
dc = __mfdr(DBGREG_DC);
dc &= ~DC_SS;
__mtdr(DBGREG_DC, dc);
ti = current_thread_info();
ti->flags |= _TIF_BREAKPOINT;
/* The TLB miss handlers don't check thread flags */
if ((regs->pc >= (unsigned long)&itlb_miss)
&& (regs->pc <= (unsigned long)&tlb_miss_common)) {
__mtdr(DBGREG_BWA2A, sysreg_read(RAR_EX));
__mtdr(DBGREG_BWC2A, 0x40000001 | (get_asid() << 1));
}
/*
* If we're running in supervisor mode, the breakpoint
* will take us where we want directly, no need to
* single step.
*/
if ((regs->sr & MODE_MASK) != MODE_SUPERVISOR)
ti->flags |= TIF_SINGLE_STEP;
} else {
panic("Unable to handle debug trap at pc = %08lx\n",
regs->pc);
}
}
/*
* Handle breakpoints, single steps and other debuggy things. To keep
* things simple initially, we run with interrupts and exceptions
* disabled all the time.
*/
asmlinkage void do_debug(struct pt_regs *regs)
{
unsigned long dc, ds;
ds = __mfdr(DBGREG_DS);
pr_debug("do_debug: pc = %08lx, ds = %08lx\n", regs->pc, ds);
if (test_thread_flag(TIF_BREAKPOINT)) {
pr_debug("TIF_BREAKPOINT set\n");
/* We're taking care of it */
clear_thread_flag(TIF_BREAKPOINT);
__mtdr(DBGREG_BWC2A, 0);
}
if (test_thread_flag(TIF_SINGLE_STEP)) {
pr_debug("TIF_SINGLE_STEP set, ds = 0x%08lx\n", ds);
if (ds & DS_SSS) {
dc = __mfdr(DBGREG_DC);
dc &= ~DC_SS;
__mtdr(DBGREG_DC, dc);
clear_thread_flag(TIF_SINGLE_STEP);
ptrace_break(current, regs);
}
} else {
/* regular breakpoint */
ptrace_break(current, regs);
}
}