/*
 *  lcrash/arch/s390/lib/trace.c
 *   S/390 stack trace functions
 *
 *    Copyright (C) 2000 IBM Deutschland Entwicklung GmbH,
 *                       IBM Corporation
 *    Author(s): Michael Holzheu (holzheu@de.ibm.com),
 *
 *    based on SGI version of trace.c
 *
 *    Bugreports to: <Linux390@de.ibm.com>
 */

#include <lcrash.h>
#include <asm/lc_s390-util.h>
#include <asm/kl_s390_util.h>

static int using_async_stack = 0; 
static int si = 0; 

/*
 * init_async_stck() - get lower memory boundary for async_stack
 */
int
init_async_stack(void* lc, trace_t *trace)
{
	kaddr_t saddr = 0;

	using_async_stack = 0;
	si = 0;

	/* get start of async stack */
	if(!(kl_is_member("_lowcore", "async_stack"))){
		return(1);
	}
	if(lc){
		saddr = *((kaddr_t * )K_ADDR(lc, "_lowcore","async_stack"));
		trace->stack[1].addr = saddr - trace->stack[1].size;
		/* stack[1].ptr allocated before calling setup_trace_rec() */
		GET_BLOCK(trace->stack[1].addr, STACK_SIZE, 
			  trace->stack[1].ptr);
		if (KL_ERROR) {
			return(1);
		}
		si = 1;
		using_async_stack = 1;
	}
	return(0);
}

/*
 * switch_from_async_stack() - change stack page frames
 */
int
switch_from_async_stack(kaddr_t saddr, trace_t* trace)
{

	if(CURRENT_TASK(saddr) != CURRENT_TASK(trace->stack[0].addr)) {
		return(1);
	}

	si = 0;
	using_async_stack = 0;
	return(0);
}

/*
 * intr_backchain() - determine backchain for interrupt stack frames
 */
kaddr_t
intr_backchain(kaddr_t sp)
{
	static int r15_off = 0;
	kaddr_t new_sp = 0;

	if(!r15_off){
		/* we just look for gr15 in stack frame */
		r15_off += STD_SFRAME_SIZE;
		r15_off += 2 * KL_NBPW; /* skip PSW */
		r15_off += 15 * KL_NBPW; /* skip gr0 to gr14 */
	}
	/* we have to check if sp + r15_off falls within stack page frames */
	if(CURRENT_TASK(sp) == CURRENT_TASK(sp + r15_off)) {
		GET_BLOCK(sp + r15_off, KL_NBPW, &new_sp);
	}
	return (new_sp);
}


/*
 * intr_check_psw() - check for user(return(1)) or kernel (0) psw
 */
int
intr_check_psw(kaddr_t sp)
{
	static int psw_off = 0;
	kaddr_t psw_flags = 0;

	if(!psw_off){
		/* we just look for gr15 in stack frame */
		psw_off += STD_SFRAME_SIZE;
	}

	/* read first part of psw */
	/* we have to check if sp + psw_off falls within stack page frames */
	if(CURRENT_TASK(sp) == CURRENT_TASK(sp + psw_off)) {
		GET_BLOCK(sp + psw_off, KL_NBPW, &psw_flags);
	}
	return (IS_USER_PSW(psw_flags));
}

/*
 * alloc_sframe() -- Allocate a stack frame record
 */
sframe_t *
alloc_sframe(trace_t *trace, int flags)
{
        sframe_t *f;

	if (flags & C_PERM) {
        	f = (sframe_t *)kl_alloc_block(sizeof(sframe_t), K_PERM);
	} else {
        	f = (sframe_t *)kl_alloc_block(sizeof(sframe_t), K_TEMP);
	}
        if (!f) {
                return((sframe_t *)NULL);
        }
        f->level = trace->nframes;
        return(f);
}

/*
 * free_sframes() -- Free all stack frames allocated to a trace record.
 */
void
free_sframes(trace_t *t)
{
        sframe_t *sf;

        t->nframes = 0;
        sf = t->frame;
        while(t->frame) {
                sf = (sframe_t *)kl_dequeue((element_t **)&t->frame);
                if (sf->srcfile) {
                        kl_free_block((void *)sf->srcfile);
                }
                kl_free_block((void *)sf);
        }
	t->frame = (sframe_t *)NULL;
}

/*
 * alloc_trace_rec() -- Allocate stack trace header
 */
trace_t *
alloc_trace_rec(int flags)
{
        trace_t *t;

	if (flags & C_PERM) {
		t = (trace_t *)kl_alloc_block(sizeof(trace_t), K_PERM);
	} else {
		t = (trace_t *)kl_alloc_block(sizeof(trace_t), K_TEMP);
	}
        return(t);
}

/*
 * free_trace_rec() -- Free memory associated with stack trace header
 */
void
free_trace_rec(trace_t *t)
{
        int i;

        if (t->tsp) {
                kl_free_block(t->tsp);
        }
        for (i = 0; i < STACK_SEGMENTS; i++) {
                if (t->stack[i].ptr) {
                        kl_free_block((void *)t->stack[i].ptr);
                }
        }
        free_sframes(t);
        kl_free_block((void *)t);
}

/*
 * clean_trace_rec() -- Clean up stack trace record without releasing
 *                      any of the allocated memory (except sframes).
 */
void
clean_trace_rec(trace_t *t)
{
	int i;

	t->flags = 0;
	t->task = 0;
	if (t->tsp) {
		kl_free_block(t->tsp);
		t->tsp = 0;
	}
	t->stackcnt = 0;
	for (i = 0; i < STACK_SEGMENTS; i++) {
		if (t->stack[i].ptr) {
			t->stack[i].type = 0;
			t->stack[i].size = 0;
			t->stack[i].addr = (kaddr_t)NULL;
			kl_free_block((void *)t->stack[i].ptr);
			t->stack[i].ptr = (uint *)NULL;
		}
	}
	free_sframes(t);
}

/* 
 * setup_trace_rec()
 */
int
setup_trace_rec(kaddr_t saddr, kaddr_t task, int flag, trace_t *trace)
{
	int aflag = K_TEMP;
	kl_reset_error();
	if (flag & C_PERM) {
		aflag = K_PERM;
	}

	if (task) {
		trace->tsp = kl_alloc_block(TASK_STRUCT_SZ, aflag);
		if (kl_get_task_struct(task, 2, trace->tsp)) {
			kl_free_block(trace->tsp);
			trace->tsp = NULL;
			return(1);
		}
	}

	trace->stack[0].type = S_KERNELSTACK;
	trace->stack[0].size = STACK_SIZE;

	/* Get the base address of the stack (is task)
	 */
	trace->stack[0].addr = saddr - trace->stack[0].size;
	trace->stack[0].ptr = kl_alloc_block(STACK_SIZE, aflag);
	if (KL_ERROR) {
		clean_trace_rec(trace);
		return(1);
	}
	GET_BLOCK(trace->stack[0].addr, STACK_SIZE, trace->stack[0].ptr);
	if (KL_ERROR) {
		clean_trace_rec(trace);
		return(1);
	}

	trace->stack[1].type = S_ASYNCSTACK;
	trace->stack[1].size = STACK_SIZE;

	trace->stack[1].addr = 0;
	trace->stack[1].ptr = kl_alloc_block(STACK_SIZE, aflag);
	if (KL_ERROR) {
		clean_trace_rec(trace);
		return(1);
	}

	return(0);
}

/*
 * find_trace()
 *
 *   Given a starting pc (start_cp), starting stack pointer (start_sp), 
 *   and stack address, check to see if a valid trace is possible. A
 *   trace is considered valid if no errors are encountered (bad PC,
 *   bad SP, etc.) Certain errors are tolorated however. For example,
 *   if the current stack frame is an exception frame (e.g., VEC_*),
 *   go ahead and return success -- even if PC and SP obtained from
 *   the exception frame are bad (a partial trace is better than no
 *   trace)..
 *
 *   Return zero if no valid trace was found. Otherwise, return the
 *   number of frames found. If the C_ALL flag is passed in, then
 *   return a trace even if it is a subtrace of a trace that was
 *   previously found.
 *
 *   Parameters:
 *
 *   start_pc       starting program counter
 *   start_sp       starting stack pointer
 *   check_pc       if non-NULL, check to see if check_pc/check_sp
 *   check_sp       are a sub-trace of trace beginning with spc/ssp
 *   trace          structure containing all trace related info (frames,
 *                  pages, page/frame counts, etc.
 *   flags
 */
 
int
find_trace(
	kaddr_t start_pc, 
	kaddr_t start_sp, 
	kaddr_t check_pc, 
	kaddr_t check_sp,
	trace_t *trace, 
	int flags)
{
	fprintf(stderr, "find_trace() not implemented for S/390\n");
  	return -1;
}

/*
 * pc_offset()
 */
int
pc_offset(kaddr_t pc) 
{
	kaddr_t func_addr;

	if ((func_addr = kl_funcaddr(pc))) {
		return(pc - func_addr);
	}
	return(-1);
}

/*
 * dump_stack_frame()
 */
void
dump_stack_frame(trace_t *trace, sframe_t *curframe, FILE *ofp)
{
	int i, first_time = 1;
	kaddr_t sp;
	uaddr_t *asp;

	sp = curframe->sp;
	asp = curframe->asp;
#ifdef _ARCH_S390X 
	for (i = 0; i < curframe->frame_size / KL_NBPW; i++) {
		if (!(i % 2)) {
#else
	for (i = 0; i < curframe->frame_size / KL_NBPW; i++) {
		if (!(i % 4)) {
#endif
			if (first_time) {
				first_time = 0;
#ifdef _ARCH_S390X
				fprintf(ofp, "   %lx: %016lx  ", sp, *asp++);
#else
				fprintf(ofp, "   %x: %08x  ", sp, *asp++);
#endif
			} else {
#ifdef _ARCH_S390X
				fprintf(ofp, "\n   %lx: %016lx  ", sp, *asp++);
#else
				fprintf(ofp, "\n   %x: %08x  ", sp, *asp++);
#endif
			}
			sp += 16;
		} else  {
#ifdef _ARCH_S390X
			fprintf(ofp, "%016lx  ", *asp++);
#else
			fprintf(ofp, "%08x  ", *asp++);
#endif
		}
	}
	if (curframe->frame_size) {
		fprintf(ofp, "\n\n");
	}
}

/*
 * print_trace()
 */
void
print_trace(trace_t *trace, int flags, FILE *ofp)
{
	int offset;
	sframe_t *frmp;
	if ((frmp = trace->frame)) {
		do {
			fprintf(ofp, "%2d %s", frmp->level, frmp->funcname);
			offset = pc_offset(frmp->pc);
			if (offset > 0) {
				fprintf(ofp, "+%d", offset);
			} else if (offset < 0) {
				fprintf(ofp, "+<ERROR>");
			}
			fprintf(ofp, " [%#lx]\n", (unsigned long) frmp->pc);
			if (flags & C_FULL) {
				fprintf(ofp, "\n");
				fprintf(ofp, "   SP=%#lx, FP=%#lx, "
					"SIZE=%d\n\n",
					(unsigned long) frmp->sp, 
					(unsigned long) frmp->fp,
					frmp->frame_size);
				if(frmp->asp){
					dump_stack_frame(trace, frmp, ofp);
				}
			}
			if (frmp->error) {
				fprintf(ofp, "TRACE ERROR %#llx\n", 
					(unsigned long long) frmp->error);
			}
			frmp = frmp->next;
		} while (frmp != trace->frame);
	}
}

/* 
 * trace_banner()
 */
void
trace_banner(FILE *ofp)
{
	fprintf(ofp, "===================================================="
			"============\n");
}

/***********************************************************************/
/* The L/390 kernel stack has the following structure:                 */
/*                                                                     */
/* +------------------------------------+ <-- task                     */
/* | task-struct                        |                              */
/* .               .                    .                              */
/* .               .                    .                              */
/* |               .                    |                              */
/* |------------------------------------| <-- task.tss.ksp             */
/* | address of previous stack frame  -----+   (Kernel stack pointer)  */
/* |                                    |  |                           */
/* | <<< last stack frame >>>>          |  |                           */
/* |                                    |  |                           */
/* | offset 56(112): instruction-ptr    |  |                           */
/* | offset 96: outgoing args           |  |                           */
/* |------------------------------------|  |                           */
/* | address of previous stack frame  <----+                           */
/* |                                  -----+                           */
/* |               .                    |  |                           */
/* .               .                    .  .                           */
/* .               .                    .  .                           */
/* |------------------------------------|  |                           */
/* | NULL                             <----+                           */
/* +------------------------------------+ <-- task + 0x2000 (2 pages)  */
/*                                                 + 0x4000 (s390x)    */
/*                                                                     */
/***********************************************************************/

/*
 * build_stack_strace()
 *   -sp:     stack-pointer where to start the trace
 *   -trace:  pointer to trace structure
 *   -flags:  trace options
 *   -skip:   number of stackframes to skip
 */

kaddr_t build_stack_strace(kaddr_t sp, trace_t* trace, int flags, int skip)
{
	sframe_t* curframe;
        char*     func_name;
	syment_t* sym;
	int       curstkidx = 0;
	int       frame_size;
	int       cur_fsize;
        kaddr_t   backchain; /* next stack frame */
        kaddr_t   ip = 0;    
/* instruction address */
        kaddr_t   sp_copy = 0;   /* address in our stack-copy */
	int       fail_count = 0;
	

        /* check if sp is inside the current stack page frames */
        if(CURRENT_TASK(sp) != CURRENT_TASK(trace->stack[si].addr)){
		if(using_async_stack && trace->tsp) {
			/* switch from async to kernel stack */
			if(switch_from_async_stack(CURRENT_TASK(sp), trace)){
				/* pointing out of kernel stack */
				func_name = "<invalid kernel stack>";
				curframe  = alloc_sframe(trace, flags);
				UPDATE_FRAME(func_name, ip, 0 , sp, sp + 96,
					     0, 0, 0, KL_NBPW);
				return 0;
			}
		} else {
			/* pointing out of kernel_stack */
			func_name = "<back chain invalid>";
			curframe  = alloc_sframe(trace, flags);
			UPDATE_FRAME(func_name, ip, 0 , sp, sp + 96,
				     0, 0, 0, KL_NBPW);
			return 0;
		}
        }

	sp_copy = (kaddr_t)trace->stack[si].ptr + (kaddr_t)sp - 
		(kaddr_t)trace->stack[si].addr;
	/* following cast should be replaced with something more portable? */
	backchain = *((kaddr_t *)sp_copy);

	if (!backchain) {
		if(!intr_check_psw(sp)){
			/* found kernel psw */
			backchain = intr_backchain(sp);
		}
	}

	if(skip <= 0) {

		ip      = (*(kaddr_t *) (sp_copy + RA_OFFSET)) & ADDR_MASK;
		sym     = kl_lkup_symaddr(ip);
		if(!sym){
			func_name = "<sym not found>";
		} else {
			func_name = sym->s_name;
		}

		/* end of stack - sp, this is the maximal frame size*/
		frame_size = ( sp & (~(KSTACK_SIZE-1)) ) +
			trace->stack[si].size - sp ; 

		if(CURRENT_TASK(backchain) == 
		   CURRENT_TASK(trace->stack[si].addr)){
			/* we are still in same stack page frames */
			cur_fsize = backchain - sp;
			if( cur_fsize < frame_size ) {
				frame_size = cur_fsize;
			}
		}
		
		curframe  = alloc_sframe(trace, flags);
		UPDATE_FRAME(func_name, ip, 0 , sp, sp + 96, 
			     (uaddr_t*)sp_copy, 0, 0, frame_size);
	}

	/* check if new backchain is valid */ 
	if( (sp >= backchain) && (backchain != 0) ) {
		if (!using_async_stack || fail_count++){
			/* no switch from async to kernel stack allowed */
			func_name = "<back chain invalid>";
			curframe  = alloc_sframe(trace, flags);
			UPDATE_FRAME(func_name, ip, 0 , backchain, 
				     backchain + 96, 0, 0, 0, KL_NBPW);
			return 0;
		}
	}

        if(backchain) {
                return build_stack_strace(backchain, trace, flags, --skip);
	} else {
                return 0;
	}
}

/*
 * task_trace()
 */
int
task_trace(kaddr_t task, int flags, FILE *ofp)
{
	kaddr_t saddr;
	trace_t *trace;
	kaddr_t ksp;     /* kernel stack pointer */

	trace = (trace_t *)alloc_trace_rec(C_TEMP);	

	if (!trace) {
		fprintf(KL_ERRORFP, "Could not alloc trace rec!\n");
		return(1);
	} 

	saddr = kl_kernelstack(task);
        setup_trace_rec(saddr, task, 0, trace);
	if (KL_ERROR) {
	        fprintf(KL_ERRORFP, "Error setting up trace rec!\n");
		free_trace_rec(trace);
		return(1);
	}

        trace_banner(ofp);

        /* find out kernel stack pointer */

	if (LINUX_2_2_X(KL_LINUX_RELEASE)) {
        	ksp = KL_UINT(K_PTR(trace->tsp,"task_struct","tss"),
                              "thread_struct","ksp");
	}
	else{
		ksp = KL_UINT(K_PTR(trace->tsp,"task_struct","thread"),
                              "thread_struct","ksp");
        }

        /* check if task was active. If yes, reset stack pointer */

        if(task_has_cpu(trace->tsp))
	{
		void* lc;
		char* comm;
		int processor;
		kaddr_t addr;

		comm = K_PTR(trace->tsp,"task_struct","comm");
		processor = KL_UINT(trace->tsp,"task_struct","processor");

		lc = s390_get_cpu_lowcore(KL_UINT(trace->tsp,
                                          "task_struct", "processor"));

		fprintf(ofp, "TASK HAS CPU (%i): %#lx (%s):\n", 
			processor, (unsigned long) task, comm);

		/* register 15 contains stack pointer */
		addr = *((kaddr_t * )
			 (K_ADDR(lc, "_lowcore", "gpregs_save_area") +
			 (15 * KL_NBPW)));

		/* "Normal task stack ? */

		if(CURRENT_TASK(addr) == task){
			/* using kernel stack */
			ksp = addr;
			goto stack_found;
		}

		/* Async stack ? */

		init_async_stack(lc, trace);
		if(using_async_stack && 
			(CURRENT_TASK(addr) == trace->stack[si].addr)) {
			/* using async stack */
			ksp = addr;
			goto stack_found;
		}

		/* neither kernel nor async stack */

		addr = *((kaddr_t * )(K_ADDR(lc, "_lowcore",
			      "st_status_fixed_logout")));
		if(IS_USER_PSW(addr)){
			/* problem state bit set in current psw */
			fprintf(ofp,"Seems to run in userspace.\n");
		}
		else{
			fprintf(ofp, "No valid lowcore "
				"info available ?\n");
		}
		ksp = 0; 
stack_found:
		s390_print_lowcore(lc, ofp);
		fprintf(ofp,"\n");
		s390_free_cpu_lowcore(lc);
	}
	else {
	        fprintf(ofp, "STACK TRACE FOR TASK: %#lx (%s)\n\n",
			(unsigned long) task,
			(char*)K_PTR(trace->tsp, "task_struct", "comm"));
	}

	if(!ksp) goto out;

        /* do the backtrace */
	if(task_has_cpu(trace->tsp)) {
		/* we skip the first frame in case of an active process */
		build_stack_strace(ksp, trace, flags, 1);
	} else {
		build_stack_strace(ksp, trace, flags, 0);
	}

	if (KL_ERROR) {
		fprintf(KL_ERRORFP, "Error setting up trace rec!\n");
		free_trace_rec(trace);
		return -1;
	}
	fprintf(ofp, " STACK:\n");
	print_trace(trace, flags, ofp);
 out:
	free_trace_rec(trace);
	return 0;
}

/*
 * print_traces()
 *
 *   Output a list of all valid code addresses contained in a stack
 *   along with their function name and stack location.
 */
int
print_traces(kaddr_t saddr, int level, int flags, FILE *ofp)
{
        trace_t *trace;
        trace = (trace_t *)alloc_trace_rec(C_TEMP);
        if (!trace) {
                fprintf(KL_ERRORFP, "Could not alloc trace rec!\n");
                return -1;
        }
        setup_trace_rec(saddr+STACK_SIZE, 0, 0, trace);
	init_async_stack(0, 0);
        build_stack_strace(saddr,trace,flags,0);

        if (trace->nframes >= level) {
                        trace_banner(ofp);
                        fprintf(ofp, "TRACE FOR STACK PTR: %#lx \n\n", 
				(unsigned long) saddr);
                        print_trace(trace, flags, ofp);
                        trace_banner(ofp);
        }

        return(0);
}


/*
 * do_list()
 *
 *   Output a list of all valid code addresses contained in a stack
 *   along with their function name and stack location.
 */
int
do_list(kaddr_t saddr, int size, FILE *ofp)
{

/* XXX todo on S/390 */
	return(-1);
}

/*
 * add_frame()
 */
int
add_frame(trace_t *trace, kaddr_t fp, kaddr_t ra)
{
	sframe_t *cf, *sf;

	/* Check to make sure that sp is from the stack in the trace
	 * record.
	 *
	 * XXX -- todo
	 */
	sf = (sframe_t *)alloc_sframe(trace, C_PERM);
	sf->fp = fp;
	sf->ra = ra;
	if ((cf = trace->frame)) {
		do {
			if (cf->fp && (sf->fp < cf->fp)) {
				if (cf->next == cf) {
					cf->prev = sf;
					sf->next = cf;
					cf->next = sf;
					sf->prev = cf;
					trace->frame = sf;
				} else {
					cf->prev->next = sf;
					sf->prev = cf->prev;
					cf->prev = sf;
					sf->next = cf;
				}
				return(0);
			}
			cf = cf->next;
		} while (cf != trace->frame);
		cf = 0;
	} 
	if (!cf) {
		kl_enqueue((element_t **)&trace->frame, (element_t *)sf);
	}
	return 0;
}

/*
 * finish_trace()
 */
void
finish_trace(trace_t *trace)
{
	int level = 0, curstkidx = 0;
	uint *sbp;
	kaddr_t sbase, saddr;
	sframe_t *sf;

	sbp = trace->stack[curstkidx].ptr;
        sbase = trace->stack[curstkidx].addr;
        saddr = sbase + trace->stack[curstkidx].size;

	if ((sf = trace->frame)) {
		do {
			if (!sf->pc) {
				if (sf != trace->frame) {
					sf->sp = sf->prev->fp + 4;
					sf->pc = 0; /* XXX get_call_pc(sf->prev->ra); */
				}
				if (!sf->pc) {
					sf = sf->next;
					continue;
				}
			}
			sf->level = level++;
			sf->frame_size = sf->fp - sf->sp + 4;
			sf->funcname = kl_funcname(sf->pc);
			sf->asp = (void*)((kaddr_t)sbp + 
				(STACK_SIZE - (saddr - sf->sp)));
			sf = sf->next;
		} while (sf != trace->frame);

		if (level > 0) {
			sf = (sframe_t *)alloc_sframe(trace, C_PERM);
			sf->level = level;
			sf->sp = trace->frame->prev->fp + 4;
			sf->pc = 0; /* XXX get_call_pc(trace->frame->prev->ra); */
			sf->funcname = kl_funcname(sf->pc);
			if (sf->funcname && 
					strstr(sf->funcname, "kernel_thread")) {
				sf->ra = 0;
				sf->fp = saddr - 4;
				sf->asp = (void*)((kaddr_t)sbp + 
					(STACK_SIZE - 12));
			} else {
				sf->fp = saddr - 20;
				kl_get_kaddr(sf->fp, &sf->ra);
				sf->asp = (void*)((kaddr_t)sbp + 
					(STACK_SIZE - (saddr - sf->sp)));
			}
			sf->frame_size = sf->fp - sf->sp + 4;
			kl_enqueue((element_t **)&trace->frame, 
				(element_t *)sf);
		}
	}
}

int
dumptask_trace(
        kaddr_t curtask,
        dump_header_asm_t *dump_header_asm,
        int flags,
        FILE *ofp)
{
/* XXX todo on S/390 */
        return(-1);
}

