/*
 *  arch/s390/kernel/ptrace.c
 *
 *  S390 version
 *    Copyright (C) 1999,2000 IBM Deutschland Entwicklung GmbH, IBM Corporation
 *    Author(s): Denis Joseph Barrow (djbarrow@de.ibm.com,barrow_dj@yahoo.com),
 *
 *  Based on PowerPC version 
 *    Copyright (C) 1995-1996 Gary Thomas (gdt@linuxppc.org)
 *
 *  Derived from "arch/m68k/kernel/ptrace.c"
 *  Copyright (C) 1994 by Hamish Macdonald
 *  Taken from linux/kernel/ptrace.c and modified for M680x0.
 *  linux/kernel/ptrace.c is by Ross Biro 1/23/92, edited by Linus Torvalds
 *
 * Modified by Cort Dougan (cort@cs.nmt.edu) 
 *
 *
 * This file is subject to the terms and conditions of the GNU General
 * Public License.  See the file README.legal in the main directory of
 * this archive for more details.
 */

#include <stddef.h>
#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <linux/smp.h>
#include <linux/smp_lock.h>
#include <linux/errno.h>
#include <linux/ptrace.h>
#include <linux/user.h>

#include <asm/segment.h>
#include <asm/page.h>
#include <asm/pgtable.h>
#include <asm/pgalloc.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#ifdef CONFIG_S390_SUPPORT
#include "linux32.h"
#else
#define parent_31bit 0
#endif


void FixPerRegisters(struct task_struct *task)
{
	struct pt_regs *regs = __KSTK_PTREGS(task);
	per_struct *per_info=
			(per_struct *)&task->thread.per_info;

	per_info->control_regs.bits.em_instruction_fetch =
	        per_info->single_step | per_info->instruction_fetch;
	
	if (per_info->single_step) {
		per_info->control_regs.bits.starting_addr=0;
#ifdef CONFIG_S390_SUPPORT
		if (current->thread.flags & S390_FLAG_31BIT) {
			per_info->control_regs.bits.ending_addr=0x7fffffffUL;
	        }
		else 
#endif      
		{
		per_info->control_regs.bits.ending_addr=-1L;
		}
	} else {
		per_info->control_regs.bits.starting_addr=
		        per_info->starting_addr;
		per_info->control_regs.bits.ending_addr=
		        per_info->ending_addr;
	}
	/* if any of the control reg tracing bits are on 
	   we switch on per in the psw */
	if (per_info->control_regs.words.cr[0] & PER_EM_MASK)
		regs->psw.mask |= PSW_PER_MASK;
	else
		regs->psw.mask &= ~PSW_PER_MASK;
	if (per_info->control_regs.bits.storage_alt_space_ctl)
		task->thread.user_seg |= USER_STD_MASK;
	else
		task->thread.user_seg &= ~USER_STD_MASK;
}

void set_single_step(struct task_struct *task)
{
	per_struct *per_info= (per_struct *) &task->thread.per_info;	
	
	per_info->single_step = 1;  /* Single step */
	FixPerRegisters (task);
}

void clear_single_step(struct task_struct *task)
{
	per_struct *per_info= (per_struct *) &task->thread.per_info;

	per_info->single_step = 0;
	FixPerRegisters (task);
}

int ptrace_usercopy(addr_t realuseraddr, addr_t copyaddr, int len,
                    int tofromuser, int writeuser, unsigned long mask)
{
        unsigned long *realuserptr, *copyptr;
	unsigned long tempuser;
	int retval;

        retval = 0;
        realuserptr = (unsigned long *) realuseraddr;
        copyptr = (unsigned long *) copyaddr;

	if (writeuser && realuserptr == NULL)
		return 0;

	if (mask != -1L) {
		tempuser = *realuserptr;
		if (!writeuser) {
			tempuser &= mask;
			realuserptr = &tempuser;
		}
	}
	if (tofromuser) {
		if (writeuser) {
			retval = copy_from_user(realuserptr, copyptr, len);
		} else {
			if (realuserptr == NULL)
				retval = clear_user(copyptr, len);
			else
				retval = copy_to_user(copyptr,realuserptr,len);
                        retval = retval ? -EIO : 0;
		}      
	} else {
		if (writeuser)
			memcpy(realuserptr, copyptr, len);
		else
			memcpy(copyptr, realuserptr, len);
	}
	if (mask != -1L && writeuser)
                *realuserptr = (*realuserptr & mask) | (tempuser & ~mask);
	return retval;
}

#ifdef CONFIG_S390_SUPPORT

typedef struct
{
	__u32 cr[3];
} per_cr_words32  __attribute__((packed));

typedef struct
{
	__u16          perc_atmid;          /* 0x096 */
	__u32          address;             /* 0x098 */
	__u8           access_id;           /* 0x0a1 */
} per_lowcore_words32  __attribute__((packed));

typedef struct
{
	union {
		per_cr_words32   words;
	} control_regs  __attribute__((packed));
	/*
	 * Use these flags instead of setting em_instruction_fetch
	 * directly they are used so that single stepping can be
	 * switched on & off while not affecting other tracing
	 */
	unsigned  single_step       : 1;
	unsigned  instruction_fetch : 1;
	unsigned                    : 30;
	/*
	 * These addresses are copied into cr10 & cr11 if single
	 * stepping is switched off
	 */
	__u32     starting_addr;
	__u32     ending_addr;
	union {
		per_lowcore_words32 words;
	} lowcore; 
} per_struct32 __attribute__((packed));

struct user_regs_struct32
{
	_psw_t32 psw;
	u32 gprs[NUM_GPRS];
	u32 acrs[NUM_ACRS];
	u32 orig_gpr2;
	s390_fp_regs fp_regs;
	/*
	 * These per registers are in here so that gdb can modify them
	 * itself as there is no "official" ptrace interface for hardware
	 * watchpoints. This is the way intel does it.
	 */
	per_struct32 per_info;
	u32  ieee_instruction_pointer; 
	/* Used to give failing instruction back to user for ieee exceptions */
};

struct user32 {
                                  /* We start with the registers, to mimic the way that "memory" is returned
                                   from the ptrace(3,...) function.  */
  struct user_regs_struct32 regs; /* Where the registers are actually stored */
                                  /* The rest of this junk is to help gdb figure out what goes where */
  u32 u_tsize;	                  /* Text segment size (pages). */
  u32 u_dsize;	                  /* Data segment size (pages). */
  u32 u_ssize;	                  /* Stack segment size (pages). */
  u32 start_code;                 /* Starting virtual address of text. */
  u32 start_stack;	          /* Starting virtual address of stack area.
				   This is actually the bottom of the stack,
				   the top of the stack is always found in the
				   esp register.  */
  s32 signal;     		  /* Signal that caused the core dump. */
  u32 u_ar0;                      /* Used by gdb to help find the values for */
				  /* the registers. */
  u32 magic;		          /* To uniquely identify a core file */
  char u_comm[32];		  /* User command that was responsible */
};


#define PT32_PSWMASK  0x0
#define PT32_PSWADDR  0x04
#define PT32_GPR0     0x08
#define PT32_GPR15    0x44
#define PT32_ACR0     0x48
#define PT32_ACR15    0x84
#define PT32_ORIGGPR2 0x88
#define PT32_FPC      0x90
#define PT32_FPR0_HI  0x98
#define PT32_FPR15_LO 0x114
#define PT32_CR_9     0x118
#define PT32_CR_11    0x120
#define PT32_IEEE_IP  0x13C
#define PT32_LASTOFF  PT32_IEEE_IP
#define PT32_ENDREGS  0x140-1
#define U32OFFSETOF(member) offsetof(struct user32,regs.member)
#define U64OFFSETOF(member) offsetof(struct user,regs.member)
#define U6432DIFF(member) (U64OFFSETOF(member) - U32OFFSETOF(member))
#define PT_SINGLE_STEP   (PT_CR_11+8)
#define PT32_SINGLE_STEP (PT32_CR_11+4)

#endif /* CONFIG_S390_SUPPORT */

int copy_user(struct task_struct *task,saddr_t useraddr, addr_t copyaddr,
              int len, int tofromuser, int writingtouser)
{
	int copylen=0,copymax;
	addr_t  realuseraddr;
	saddr_t enduseraddr;
	unsigned long mask;
#ifdef CONFIG_S390_SUPPORT
	int     parent_31bit=current->thread.flags & S390_FLAG_31BIT;
	int     skip;
#endif
	enduseraddr=useraddr+len;
	if ((useraddr<0||useraddr&3||enduseraddr&3)||
#ifdef CONFIG_S390_SUPPORT
	    (parent_31bit && enduseraddr > sizeof(struct user32)) ||
#endif
	    enduseraddr > sizeof(struct user))
		return (-EIO);

#ifdef CONFIG_S390_SUPPORT
	if(parent_31bit)
	{
		if(useraddr != PT32_PSWMASK)
		{
			if (useraddr == PT32_PSWADDR)
				useraddr = PT_PSWADDR+4;
			else if(useraddr <= PT32_GPR15)
				useraddr = ((useraddr-PT32_GPR0)*2) + PT_GPR0+4;
			else if(useraddr <= PT32_ACR15)
				useraddr += PT_ACR0-PT32_ACR0;
			else if(useraddr == PT32_ORIGGPR2)
				useraddr = PT_ORIGGPR2+4;
			else if(useraddr <= PT32_FPR15_LO)
				useraddr += PT_FPR0-PT32_FPR0_HI;
			else if(useraddr <= PT32_CR_11)
				useraddr = ((useraddr-PT32_CR_9)*2) + PT_CR_9+4;
			else if(useraddr ==  PT32_SINGLE_STEP)
				useraddr = PT_SINGLE_STEP; 
			else if(useraddr <= U32OFFSETOF(per_info.ending_addr))	
				useraddr = (((useraddr-U32OFFSETOF(per_info.starting_addr)))*2) + 
					U64OFFSETOF(per_info.starting_addr)+4;
			else if( useraddr == U32OFFSETOF(per_info.lowcore.words.perc_atmid))
				useraddr = U64OFFSETOF(per_info.lowcore.words.perc_atmid);
			else if( useraddr == U32OFFSETOF(per_info.lowcore.words.address))
				useraddr = U64OFFSETOF(per_info.lowcore.words.address)+4;
			else if(useraddr == U32OFFSETOF(per_info.lowcore.words.access_id))
				useraddr = U64OFFSETOF(per_info.lowcore.words.access_id);
			else if(useraddr == PT32_IEEE_IP)
				useraddr = PT_IEEE_IP+4;
		}
	}
#endif /* CONFIG_S390_SUPPORT */

	while(len>0)
	{
#ifdef CONFIG_S390_SUPPORT
		skip=0;
#endif
		mask=PSW_ADDR_MASK;
		if(useraddr<PT_FPC)
		{
			realuseraddr=((addr_t) __KSTK_PTREGS(task)) + useraddr;
			if(useraddr<(PT_PSWMASK+8))
			{
				if(parent_31bit)
				{
					copymax=PT_PSWMASK+4;
#ifdef CONFIG_S390_SUPPORT
					skip=8;
#endif
				}
				else
				{
					copymax=PT_PSWMASK+8;
				}
				if(writingtouser)
					mask=PSW_MASK_DEBUGCHANGE;
			}
			else if(useraddr<(PT_PSWADDR+8))
			{
				copymax=PT_PSWADDR+8;
				mask=PSW_ADDR_DEBUGCHANGE;
#ifdef CONFIG_S390_SUPPORT
				if(parent_31bit)
					skip=4;
#endif

			}
			else
			{
#ifdef CONFIG_S390_SUPPORT
				if(parent_31bit && useraddr <= PT_GPR15+4)
				{
					copymax=useraddr+4;
					if(useraddr<PT_GPR15+4)
						skip=4;
				}
				else
#endif
					copymax=PT_FPC;
			}
		}
		else if(useraddr<(PT_FPR15+sizeof(freg_t)))
		{
			copymax=(PT_FPR15+sizeof(freg_t));
			realuseraddr=(addr_t)&(((u8 *)&task->thread.fp_regs)[useraddr-PT_FPC]);
		}
		else if(useraddr<sizeof(struct user_regs_struct))
		{
#ifdef CONFIG_S390_SUPPORT
			if( parent_31bit && useraddr <= PT_IEEE_IP+4)
			{
				switch(useraddr)
				{
				case PT_CR_11+4:
				case U64OFFSETOF(per_info.ending_addr)+4:
				case U64OFFSETOF(per_info.lowcore.words.address)+4:
					copymax=useraddr+4;
					break;
				case  PT_SINGLE_STEP:
				case  U64OFFSETOF(per_info.lowcore.words.perc_atmid):
					/* We copy 2 bytes in excess for the atmid member this also gets around */
					/* alignment for this member in 32 bit */
					skip=8;
					copymax=useraddr+4;
					break;
				default: 
					copymax=useraddr+4;
					skip=4;
				}
			}
			else
#endif
			{
				copymax=sizeof(struct user_regs_struct);
			}
			realuseraddr=(addr_t)&(((u8 *)&task->thread.per_info)[useraddr-PT_CR_9]);
		}
		else 
		{
			copymax=sizeof(struct user);
			realuseraddr=(addr_t)NULL;
		}
		copylen=copymax-useraddr;
		copylen=(copylen>len ? len:copylen);
		if(ptrace_usercopy(realuseraddr,copyaddr,copylen,tofromuser,writingtouser,mask))
			return (-EIO);
		copyaddr+=copylen;
		len-=copylen;
		useraddr+=copylen
#if CONFIG_S390_SUPPORT
			+skip
#endif
			;
	}
	FixPerRegisters(task);
	return(0);
}

/*
 * Called by kernel/ptrace.c when detaching..
 *
 * Make sure single step bits etc are not set.
 */
void ptrace_disable(struct task_struct *child)
{
	/* make sure the single step bit is not set. */
	clear_single_step(child);
}

typedef struct
{
__u32	len;
__u32	kernel_addr;
__u32	process_addr;
} ptrace_area_emu31;


asmlinkage int sys_ptrace(long request, long pid, long addr, long data)
{
	struct task_struct *child;
	int ret = -EPERM;
	int copied;
#ifdef CONFIG_S390_SUPPORT
	int           parent_31bit;
	int           sizeof_parent_long;
	u8            *dataptr;
#else
#define sizeof_parent_long 8
#define dataptr (u8 *)&data
#endif
	lock_kernel();
	if (request == PTRACE_TRACEME) 
	{
		/* are we already being traced? */
		if (current->ptrace & PT_PTRACED)
			goto out;
		/* set the ptrace bit in the process flags. */
		current->ptrace |= PT_PTRACED;
		ret = 0;
		goto out;
	}
	ret = -ESRCH;
	read_lock(&tasklist_lock);
	child = find_task_by_pid(pid);
	if (child)
		get_task_struct(child);
	read_unlock(&tasklist_lock);
	if (!child)
		goto out;
	ret = -EPERM;
	if (pid == 1)		/* you may not mess with init */
		goto out_tsk;
	if (request == PTRACE_ATTACH) 
	{
		ret = ptrace_attach(child);
		goto out_tsk;
	}
	ret = -ESRCH;
	// printk("child=%lX child->flags=%lX",child,child->flags);
	/* I added child!=current line so we can get the */
	/* ieee_instruction_pointer from the user structure DJB */
	if(child!=current)
	{
		if (!(child->ptrace & PT_PTRACED))
			goto out_tsk;
		if (child->state != TASK_STOPPED) 
		{
			if (request != PTRACE_KILL)
				goto out_tsk;
		}
		if (child->parent != current)
			goto out_tsk;
	}
#ifdef CONFIG_S390_SUPPORT
	parent_31bit=(current->thread.flags & S390_FLAG_31BIT);
	sizeof_parent_long=(parent_31bit ? 4:8);
	dataptr=&(((u8 *)&data)[parent_31bit ? 4:0]);
#endif
	switch (request) 
	{
		/* If I and D space are separate, these will need to be fixed. */
	case PTRACE_PEEKTEXT: /* read word at location addr. */ 
	case PTRACE_PEEKDATA: 
	{
		u8 tmp[8];
		copied = access_process_vm(child, addr, tmp, sizeof_parent_long, 0);
		ret = -EIO;
		if (copied != sizeof_parent_long)
			break;
		ret = copy_to_user((void *)data,tmp,sizeof_parent_long);
		break;
	
	}
		/* read the word at location addr in the USER area. */
	case PTRACE_PEEKUSR:
		ret=copy_user(child,addr,data,sizeof_parent_long,1,0);
		break;

		/* If I and D space are separate, this will have to be fixed. */
	case PTRACE_POKETEXT: /* write the word at location addr. */
	case PTRACE_POKEDATA:
		ret = 0;
		if (access_process_vm(child, addr,dataptr, sizeof_parent_long, 1) == sizeof_parent_long)
			break;
		ret = -EIO;
		break;

	case PTRACE_POKEUSR: /* write the word at location addr in the USER area */
		ret=copy_user(child,addr,(addr_t)dataptr,sizeof_parent_long,0,1);
		break;

	case PTRACE_SYSCALL: 	/* continue and stop at next (return from) syscall */
	case PTRACE_CONT: 	 /* restart after signal. */
		ret = -EIO;
		if ((unsigned long) data >= _NSIG)
			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;
		/* make sure the single step bit is not set. */
		clear_single_step(child);
		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->state == TASK_ZOMBIE) /* already dead */
			break;
		child->exit_code = SIGKILL;
		clear_single_step(child);
		wake_up_process(child);
		/* make sure the single step bit is not set. */
		break;

	case PTRACE_SINGLESTEP:  /* set the trap flag. */
		ret = -EIO;
		if ((unsigned long) data >= _NSIG)
			break;
		clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE);
		child->exit_code = data;
		set_single_step(child);
		/* give it a chance to run. */
		wake_up_process(child);
		ret = 0;
		break;

	case PTRACE_DETACH:  /* detach a process that was attached. */
		ret = ptrace_detach(child, data);
		break;

	case PTRACE_PEEKUSR_AREA:
	case PTRACE_POKEUSR_AREA:
		if(parent_31bit)
		{
			ptrace_area_emu31   parea; 
			if((ret=copy_from_user(&parea,(void *)addr,sizeof(parea)))==0)  
				ret=copy_user(child,parea.kernel_addr,parea.process_addr,
					      parea.len,1,(request==PTRACE_POKEUSR_AREA));
		}
		else
		{
			ptrace_area   parea; 
			if((ret=copy_from_user(&parea,(void *)addr,sizeof(parea)))==0)  
				ret=copy_user(child,parea.kernel_addr,parea.process_addr,
					      parea.len,1,(request==PTRACE_POKEUSR_AREA));
		}
		break;
	case PTRACE_SETOPTIONS: {
		if (data & PTRACE_O_TRACESYSGOOD)
			child->ptrace |= PT_TRACESYSGOOD;
		else
			child->ptrace &= ~PT_TRACESYSGOOD;
		ret = 0;
		break;
	}
	default:
		ret = -EIO;
		break;
	}
 out_tsk:
	put_task_struct(child);
 out:
	unlock_kernel();
	return ret;
}



asmlinkage void syscall_trace(void)
{
	if (!test_thread_flag(TIF_SYSCALL_TRACE))
		return;
	if (!(current->ptrace & PT_PTRACED))
		return;
	current->exit_code = SIGTRAP | ((current->ptrace & PT_TRACESYSGOOD)
					? 0x80 : 0);
	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;
	}
}
