
/* $Id: ptrace.c,v 1.2 2005/06/16 13:46:21 cgoh Exp $
 *
 * This file is subject to the terms and conditions of the GNU General Public
 * License.  See the file "COPYING" in the main directory of this archive
 * for more details.
 *
 * Copyright (C) 1992 Ross Biro
 * Copyright (C) Linus Torvalds
 * Copyright (C) 1994, 1995, 1996, 1997, 1998 Ralf Baechle
 * Copyright (C) 1996 David S. Miller
 */
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <linux/errno.h>
#include <linux/ptrace.h>
#include <linux/smp.h>
#include <linux/smp_lock.h>
#include <linux/user.h>

/*  #include <asm/fp.h> */
/*  #include <asm/mipsregs.h> */
#include <asm/pgtable.h>
#include <asm/page.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include <asm/mmu_context.h>

/*
 * This routine gets a long from any process space by following the page
 * tables. NOTE! You should check that the long isn't on a page boundary,
 * and that it is in the task area before calling this: this routine does
 * no checking.
 */
static unsigned long get_long(struct task_struct * tsk,
			      struct vm_area_struct * vma, unsigned long addr)
{
	pgd_t *pgdir;
	pmd_t *pgmiddle;
	pte_t *pgtable;
	unsigned long page, retval;

repeat:
	pgdir = pgd_offset(vma->vm_mm, addr);
	if (pgd_none(*pgdir)) {
		handle_mm_fault(tsk, vma, addr, 0);
		goto repeat;
	}
	if (pgd_bad(*pgdir)) {
		printk("ptrace: bad page directory %08lx\n", pgd_val(*pgdir));
		pgd_clear(pgdir);
		return 0;
	}
	pgmiddle = pmd_offset(pgdir, addr);
	if (pmd_none(*pgmiddle)) {
		handle_mm_fault(tsk, vma, addr, 0);
		goto repeat;
	}
	if (pmd_bad(*pgmiddle)) {
		printk("ptrace: bad page middle %08lx\n", pmd_val(*pgmiddle));
		pmd_clear(pgmiddle);
		return 0;
	}
	pgtable = pte_offset(pgmiddle, addr);
	if (!pte_present(*pgtable)) {
		handle_mm_fault(tsk, vma, addr, 0);
		goto repeat;
	}
	page = pte_page(*pgtable);
	/* This is a hack for non-kernel-mapped video buffers and similar */
	if (MAP_NR(page) >= MAP_NR(high_memory))
		return 0;

        page += addr & ~PAGE_MASK;

	retval = *(unsigned long *) page;
	return retval;
}

/*
 * This routine puts a long into any process space by following the page
 * tables. NOTE! You should check that the long isn't on a page boundary,
 * and that it is in the task area before calling this: this routine does
 * no checking.
 *
 * Now keeps R/W state of page so that a text page stays readonly
 * even if a debugger scribbles breakpoints into it.  -M.U-
 */
static void put_long(struct task_struct *tsk,
		     struct vm_area_struct * vma, unsigned long addr,
	unsigned long data)
{
	pgd_t *pgdir;
	pmd_t *pgmiddle;
	pte_t *pgtable;
	unsigned long page;

repeat:
	pgdir = pgd_offset(vma->vm_mm, addr);
	if (!pgd_present(*pgdir)) {
		handle_mm_fault(tsk, vma, addr, 1);
		goto repeat;
	}
	if (pgd_bad(*pgdir)) {
		printk("ptrace: bad page directory %08lx\n", pgd_val(*pgdir));
		pgd_clear(pgdir);
		return;
	}
	pgmiddle = pmd_offset(pgdir, addr);
	if (pmd_none(*pgmiddle)) {
		handle_mm_fault(tsk, vma, addr, 1);
		goto repeat;
	}
	if (pmd_bad(*pgmiddle)) {
		printk("ptrace: bad page middle %08lx\n", pmd_val(*pgmiddle));
		pmd_clear(pgmiddle);
		return;
	}
	pgtable = pte_offset(pgmiddle, addr);
	if (!pte_present(*pgtable)) {
		handle_mm_fault(tsk, vma, addr, 1);
		goto repeat;
	}
	page = pte_page(*pgtable);
	if (!pte_write(*pgtable)) {
		handle_mm_fault(tsk, vma, addr, 1);
		goto repeat;
	}

        *(unsigned long *) (page + (addr & ~PAGE_MASK)) = data;

	/* This is a hack for non-kernel-mapped video buffers and similar */
	if (MAP_NR(page) < MAP_NR(high_memory))
		flush_cache_all();
	/*
	 * We're bypassing pagetables, so we have to set the dirty bit
	 * ourselves this should also re-instate whatever read-only mode
	 * there was before
	 */
	set_pte(pgtable, pte_mkdirty(*pgtable));
	flush_tlb_page(vma, addr);
}

static struct vm_area_struct * find_extend_vma(struct task_struct * tsk, unsigned long addr)
{
	struct vm_area_struct * vma;

	addr &= PAGE_MASK;
	vma = find_vma(tsk->mm, addr);
	if (!vma)
		return NULL;
	if (vma->vm_start <= addr)
		return vma;
	if (!(vma->vm_flags & VM_GROWSDOWN))
		return NULL;
	if (vma->vm_end - addr > tsk->rlim[RLIMIT_STACK].rlim_cur)
		return NULL;
	vma->vm_offset -= vma->vm_start - addr;
	vma->vm_start = addr;
	return vma;
}

/*
 * This routine checks the page boundaries, and that the offset is
 * within the task area. It then calls get_long() to read a long.
 */
static int read_long(struct task_struct * tsk, unsigned long addr,
	unsigned long * result)
{
	struct vm_area_struct * vma = find_extend_vma(tsk, addr);

	if (!vma)
		return -EIO;
	if ((addr & ~PAGE_MASK) > PAGE_SIZE-sizeof(long)) {
		unsigned long low,high;
		struct vm_area_struct * vma_high = vma;

		if (addr + sizeof(long) >= vma->vm_end) {
			vma_high = vma->vm_next;
			if (!vma_high || vma_high->vm_start != vma->vm_end)
				return -EIO;
		}
		low = get_long(tsk, vma, addr & ~(sizeof(long)-1));
		high = get_long(tsk, vma_high, (addr+sizeof(long)) & ~(sizeof(long)-1));
		switch (addr & (sizeof(long)-1)) {
			case 1:
				low >>= 8;
				low |= high << 24;
				break;
			case 2:
				low >>= 16;
				low |= high << 16;
				break;
			case 3:
				low >>= 24;
				low |= high << 8;
				break;
		}
		*result = low;
	} else
		*result = get_long(tsk, vma, addr);
	return 0;
}

/*
 * This routine checks the page boundaries, and that the offset is
 * within the task area. It then calls put_long() to write a long.
 */
static int write_long(struct task_struct * tsk, unsigned long addr,
	unsigned long data)
{
	struct vm_area_struct * vma = find_extend_vma(tsk, addr);

	if (!vma)
		return -EIO;
	if ((addr & ~PAGE_MASK) > PAGE_SIZE-sizeof(long)) {
		unsigned long low,high;
		struct vm_area_struct * vma_high = vma;

		if (addr + sizeof(long) >= vma->vm_end) {
			vma_high = vma->vm_next;
			if (!vma_high || vma_high->vm_start != vma->vm_end)
				return -EIO;
		}
		low = get_long(tsk, vma, addr & ~(sizeof(long)-1));
		high = get_long(tsk, vma_high, (addr+sizeof(long)) & ~(sizeof(long)-1));
		switch (addr & (sizeof(long)-1)) {
			case 0: /* shouldn't happen, but safety first */
				low = data;
				break;
			case 1:
				low &= 0x000000ff;
				low |= data << 8;
				high &= ~0xff;
				high |= data >> 24;
				break;
			case 2:
				low &= 0x0000ffff;
				low |= data << 16;
				high &= ~0xffff;
				high |= data >> 16;
				break;
			case 3:
				low &= 0x00ffffff;
				low |= data << 24;
				high &= ~0xffffff;
				high |= data >> 8;
				break;
		}
		put_long(tsk, vma, addr & ~(sizeof(long)-1),low);
		put_long(tsk, vma_high, (addr+sizeof(long)) & ~(sizeof(long)-1),high);
	} else
		put_long(tsk, vma, addr, data);
	return 0;
}

void single_step_setup  (struct task_struct *, ctxContext_t, unsigned long);
void single_step_cleanup(struct task_struct *, ctxContext_t);

asmlinkage int sys_ptrace(long request, long pid, long addr, long data)
{
	struct task_struct *child;
	unsigned int flags;
	int res;
	ctxContext_t stack_regs;
        struct eti_mm_ctx *mm_ctx;

	lock_kernel();
#if 0
	printk("ptrace(r=%d,pid=%d,addr=%08lx,data=%08lx)\n",
	       (int) request, (int) pid, (unsigned long) addr,
	       (unsigned long) data);
#endif
	if (request > PTRACE_SYSCALL) {
	  printk("sys_ptrace(): request 0x%x out of range.\n", request);
	  res = -EINVAL;
	  goto out;
	}
	if (request == PTRACE_TRACEME) {
		/* are we already being traced? */
		if (current->ptrace & PT_PTRACED) {
			res = -EPERM;
			goto out;
		}
		/* set the ptrace bit in the process flags. */
		current->ptrace |= PT_PTRACED;
		res = 0;
		goto out;
	}
	if (pid == 1) {		/* you may not mess with init */
		res = -EPERM;
		goto out;
	}
	if (!(child = find_task_by_pid(pid))) {
		res = -ESRCH;
		goto out;
	}
	if (request == PTRACE_ATTACH) {
		if (child == current) {
			res = -EPERM;
			goto out;
		}
		if ((!child->dumpable ||
		    (current->uid != child->euid) ||
		    (current->uid != child->suid) ||
		    (current->uid != child->uid) ||
	 	    (current->gid != child->egid) ||
		    (current->gid != child->sgid) ||
	 	    (current->gid != child->gid) ||
		    (!cap_issubset(child->cap_permitted,
		                  current->cap_permitted)) ||
                    (current->gid != child->gid)) && !capable(CAP_SYS_PTRACE)){
			res = -EPERM;
			goto out;
		}
		/* the same process cannot be attached many times */
		if (child->ptrace & PT_PTRACED)
			goto out;
		child->ptrace |= PT_PTRACED;

		write_lock_irqsave(&tasklist_lock, flags);
		if (child->p_pptr != current) {
			REMOVE_LINKS(child);
			child->p_pptr = current;
			SET_LINKS(child);
		}
		write_unlock_irqrestore(&tasklist_lock, flags);

		send_sig(SIGSTOP, child, 1);
		res = 0;
		goto out;
	}
	if (!(child->ptrace & PT_PTRACED)) {
		res = -ESRCH;
		goto out;
	}
	if (child->state != TASK_STOPPED) {
		if (request != PTRACE_KILL) {
			res = -ESRCH;
			goto out;
		}
	}
	if (child->p_pptr != current) {
		res = -ESRCH;
		goto out;
	}

        mm_ctx = &child->mm->context;

	/* stack context is aligned on cache-line boundary */
	stack_regs = (ctxContext_t)(((u32)child +  KERNEL_STACK_SIZE -
					 sizeof(ctxContext)));
	stack_regs = (ctxContext_t)((u32)stack_regs & ~(L1_CACHE_BYTES-1));

	switch (request) {
	case PTRACE_PEEKTEXT: /* read word at location addr. */ 
	case PTRACE_PEEKDATA: {
		unsigned long tmp;

		down(&child->mm->mmap_sem);
		res = read_long(child, addr, &tmp);
		up(&child->mm->mmap_sem);
		if (res < 0)
			goto out;
		res = put_user(tmp,(unsigned long *) data);
		goto out;
		}

	/* Read the word at location addr in the USER area.  */
	case PTRACE_PEEKUSR: {
		unsigned long tmp;
		u32 *regblock;

                /* debug/breakpoint regs */
                if ((addr >= PT_DBG_BASE) && (addr <= PT_DBG_MAX)) {
                        regblock = (u32 *)mm_ctx;

                        if (mm_ctx) {
                                addr -= PT_DBG_BASE;
                                tmp = regblock[addr];
                                res = put_user(tmp, (unsigned long *) data);
                        } else {
                                res = -EIO;
                        }
                        
                        goto out;
                }

		/* Regular exceptions */
		if (stack_regs->u.header.u.s.type == ETI_EXCEPTION_CONTEXT) {
			if (addr > sizeof(ctxContext)) {
				res = -EIO;
				goto out;
			}
			regblock = (u32 *)stack_regs;
			tmp = regblock[addr];
			res = put_user(tmp, (unsigned long *) data);
			goto out;
		}

		/* System calls (strace() gets us here) */
		else  if (stack_regs->u.header.u.s.type ==
			  ETI_SYSCALL_CONTEXT) {
			if (addr > sizeof(ctxSyscallContext)) {
#if 1
                                tmp = ~0;
#else
				res = -EIO;
				goto out;
#endif
			}
                        else {
                                regblock = (u32 *)stack_regs;
                                tmp = regblock[addr];
                        }
			res = put_user(tmp, (unsigned long *) data);
			goto out;
		}

		else {
		  printk("sys_ptrace(PEEKUSR): unknown context type 0x%x "
			 "(stack_regs at 0x%08x).\n", 
			 stack_regs->u.header.u.s.type, (u32)stack_regs);
		  res = -EIO;
		  goto out;
		}
	}
	case PTRACE_POKEUSR: {
		u32 *regblock;

                /* debug/breakpoint regs */
                if ((addr >= PT_DBG_BASE) && (addr <= PT_DBG_MAX)) {
                        if (mm_ctx) {
                                regblock = (u32 *)mm_ctx;
                                addr -= PT_DBG_BASE;
                                regblock[addr] = data;
                        } else {
                                res = -EIO;
                        }
                        goto out;
                }

		/* Check for known types */
		if (stack_regs->u.header.u.s.type == ETI_EXCEPTION_CONTEXT) {
                        if (addr > sizeof(ctxContext)) {
                                res = -EIO;
                                goto out;
                        }
                        regblock = (u32 *)stack_regs;
                        regblock[addr] = (unsigned long)data;
                } else if (stack_regs->u.header.u.s.type == ETI_SYSCALL_CONTEXT) {
			if (addr > sizeof(ctxSyscallContext)) {
                                /* if ~0, assume it was from a previous read */
                                if (data == ~0)
                                        goto out;

                                printk("sys_ptrace(POKEUSR): ERROR: child=%d, syscall ctx: reg 0x%x(%d)=0x%08x\n",
                                       child->pid, addr, addr, data);
				res = -EIO;
				goto out;
                        }
                        else {
                                regblock = (u32 *)stack_regs;
/*                                  regblock[addr] = (unsigned long)data; */
                        }
		} else {
			printk("sys_ptrace(POKEUSR): Unknown context type 0x%x "
                               "(stack_regs at 0x%08x.)\n", 
                               stack_regs->u.header.u.s.type, (u32)stack_regs);
			res = -EIO;
			goto out;
                }
		goto out;
        }
        
	case PTRACE_POKETEXT: /* write the word at location addr. */
	case PTRACE_POKEDATA:
		down(&child->mm->mmap_sem);
		res = write_long(child,addr,data);
		up(&child->mm->mmap_sem);
		goto out;

	case PTRACE_SINGLESTEP: {
		u32 next_pc, epcr0, epcr1;
		ctxContext_t regs = (ctxContext_t)&child->tss.regs;

                switch (stack_regs->u.header.u.s.type) {
                case ETI_EXCEPTION_CONTEXT:
                        epcr0 = stack_regs->u.exc.epcr0;
                        epcr1 = stack_regs->u.exc.epcr1;
                        break;
                case ETI_SYSCALL_CONTEXT:
                        epcr0 = stack_regs->u.syscall.epcr0;
                        epcr1 = stack_regs->u.syscall.epcr1;
                        printk("K: PTRACE_SINGLESTEP in syscall ctx: pid=%d, epcr0=0x%08x, pc=0x%08x\n", child->pid, epcr0, stack_regs->u.syscall.pc);
                        break;
                default:
                        printk("sys_ptrace(SINGLESTEP): unkown context "
                               "(stack_regs at 0x%08x(0x%x).\n", 
                               (u32)stack_regs, stack_regs->u.header.u.s.type);
                        res = -EIO;
                        goto out;
		}
		
                if (child->tss.debug_flags & TSS_DEBUG_SAVED_PC_VALID) {
                        single_step_cleanup(child, stack_regs);
                }

		/* If we have a valid epcr1 use it */
		if ((epcr1 & 0xfe) && (epcr1 & 0x1)) {
			next_pc = epcr1;
		}

		/* Otherwise, we have to determine the instruction size */
		else {
                        u32 curr_pc = epcr0;
			u8 instr_header[32];
			u8 extra_byte, extra_nib, attr_bits=0, last_attr_bits;
			u8 *iblock_ptr = &instr_header[0];
			u32 len=0, op_num, block_offset = curr_pc & 0xff;
			u32 i, j, block_start = curr_pc & ~0xff;

			/* NOTE: We know we're at a PC which is at the
			 *       beginning of an instruction so we
			 *       don't worry about the prev instr page */

			/* Read in instr block header */
			for(i=0; i<32; i+=4) {
				read_long(child, block_start + i, 
					  &instr_header[i]);
			}
			
			for(i=0; i<4; i++) {
				op_num = (block_offset - 0x20) / 4;
				extra_byte = *(u8 *)
					(iblock_ptr + (op_num/2) + 4);
				extra_nib = (block_offset & 0x4) ? 
					((extra_byte & 0xf0) >> 4) : (
						extra_byte & 0x0f);

				last_attr_bits = attr_bits;
				attr_bits = extra_nib >> 2;

				/* Is this op part of current instruction */
				if (i>0) 
					if (attr_bits <= last_attr_bits) 
						break;

				len += 4;
				block_offset += 4;

				/* Check if we're wrapping into next block */
				if (block_offset > 0xfc) {
					/* Adjust to start of next block */
					block_offset = 0x20;
					block_start += 0x100;

					/* read in next attribute block */
					for(j=0; j<32; j+=4) {
						read_long(child, 
							  block_start + j, 
							  &instr_header[j]);
					}
					
				}
			}
			next_pc = curr_pc + len;

			/* Adjust out of attribute section */
			if ((next_pc & 0xff) < 0x20) {
				next_pc = next_pc + 0x20;
			}
		}

                single_step_setup(child, stack_regs, next_pc);

		wake_up_process(child);
		child->exit_code = data;
		/* give it a chance to run. */
		res = 0;
		goto out;
	}
	case PTRACE_SYSCALL: /* continue and stop at next (return from) syscall */
	case PTRACE_CONT: { /* restart after signal. */
		if ((unsigned long) data > _NSIG) {
			res = -EIO;
			goto out;
		}
		if (request == PTRACE_SYSCALL)
			child->ptrace |= PT_TRACESYS;
		else
			child->ptrace &= ~PT_TRACESYS;

		/* Ensure single stepping is disabled */
                if (child->tss.debug_flags & TSS_DEBUG_SINGLE_STEPPING) {
                        single_step_cleanup(child, stack_regs);
                }

		child->exit_code = data;
		wake_up_process(child);
		res = data;
		goto out;
		}

	/*
	 * 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: {
		if (child->state != TASK_ZOMBIE) {
			child->exit_code = SIGKILL;
			wake_up_process(child);
		}
		res = 0;
		goto out;
		}

	case PTRACE_DETACH: { /* detach a process that was attached. */
		if ((unsigned long) data > _NSIG) {
			res = -EIO;
			goto out;
		}
		child->ptrace &= ~(PT_PTRACED|PT_TRACESYS);
		child->exit_code = data;
		REMOVE_LINKS(child);
		child->p_pptr = child->p_opptr;
		SET_LINKS(child);
		wake_up_process(child);
		res = 0;
		goto out;
		}

	default:
		res = -EIO;
		goto out;
	}
out:
	unlock_kernel();
	return res;
}

asmlinkage void syscall_trace(void)
{
	if ((current->ptrace & (PT_PTRACED|PT_TRACESYS))
			!= (PT_PTRACED|PT_TRACESYS))
		return;
	current->exit_code = SIGTRAP;
	current->state = TASK_STOPPED;
	notify_parent(current, SIGCHLD);
	schedule();
	/*
	 * 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) {
		send_sig(current->exit_code, current, 1);
		current->exit_code = 0;
	}
}

/* Single-step support */

void single_step_setup (struct task_struct *child, ctxContext_t __ctx,
                        unsigned long next_pc) {
        ctxExceptionContext_t ctx = (ctxExceptionContext_t)__ctx;

        if (child->tss.debug_flags & TSS_DEBUG_SAVED_PC_VALID)
            single_step_cleanup(child, __ctx);
        child->tss.saved_pc = next_pc;
        ctx->epcr1 = (next_pc & ~0xff) | 0x1;
        child->tss.debug_flags |= TSS_DEBUG_SAVED_PC_VALID;
        child->tss.debug_flags |= TSS_DEBUG_SINGLE_STEPPING;
}

void single_step_cleanup(struct task_struct *child, ctxContext_t __ctx) {
        ctxExceptionContext_t ctx = (ctxExceptionContext_t)__ctx;

        if (child->tss.debug_flags & TSS_DEBUG_SAVED_PC_VALID) {
                ctx->epcr1 = child->tss.saved_pc;
                child->tss.debug_flags &= ~TSS_DEBUG_SAVED_PC_VALID;
        }
        child->tss.debug_flags &= ~TSS_DEBUG_SINGLE_STEPPING;
}

