/*
 * lkcdutils/lcrash/arch/ia64/lib/trace.c
 *
 * Copyright (C) 1999 - 2002 Silicon Graphics, Inc. All rights reserved.
 * Copyright (C) 2002 Junichi Nomura, NEC Solutions <j-nomura@ce.jp.nec.com>
 * Copyright (C) 2002 Free Software Foundation, Inc. All rights reserved.
 */
#include "dis-asm.h"
#include <lcrash.h>

typedef struct ia64_stack_s {
	kaddr_t		limit;
	kaddr_t		top;
} ia64_stack_t;

typedef struct ia64_stkinfo_s {
	ia64_stack_t	regstk;	
	kaddr_t		ra;	/* Current return address */
	kaddr_t		bsp;	/* Current bsp pointer */
	kaddr_t		cfm;	/* Current CFM */
	kaddr_t		rnat;
} ia64_stkinfo_t;

typedef struct frame_info_s {
	struct frame_info_s	*next;
	struct frame_info_s	*prev;
	kaddr_t			 sp;
	kaddr_t	 		 ra;
	kaddr_t	 		 call_pc;
	kaddr_t	 		 pfs;
	kaddr_t	 		 bsp;
} frame_info_t;

/* Forward Declarations
 */
kaddr_t get_call_pc(kaddr_t);
static frame_info_t *get_frame_list(kaddr_t);

static long
rse_slot_num(kaddr_t addr)
{
	return((((kaddr_t) addr) >> 3) & 0x3f);
}

static kaddr_t
rse_rnat_addr(kaddr_t slot_addr)
{
	return ((kaddr_t)((kaddr_t)slot_addr | (0x3f << 3)));
}
 
static kaddr_t
rse_skip_regs(kaddr_t addr, int num_regs)
{
	long delta = rse_slot_num(addr) + num_regs;

	if (num_regs < 0) {
		delta -= 0x3e;
	}
	return(addr + ((num_regs + delta/0x3f) * 8));
}

static kaddr_t
rse_read_reg(ia64_stkinfo_t *info, int regnum, int *is_nat, trace_t *trace)
{
	kaddr_t addr, rnat_addr, ret_addr;
	unsigned long rnat, off;

	addr = rse_skip_regs(info->bsp, regnum);
	if ((addr < info->regstk.limit) || (addr >= info->regstk.top) || 
			(((uaddr_t)addr & 0x7)) != 0) {
		*is_nat = 1;
		return (0xdeadbeefdeadbeef);
	}
	rnat_addr = rse_rnat_addr(addr);
	if (rnat_addr >= info->regstk.top) {
		rnat = info->rnat;
	} else {
		off = (rnat_addr - trace->stack[0].addr);
		rnat = *(unsigned long*)((uaddr_t)trace->stack[0].ptr + off);
	}
	*is_nat = (rnat & (1UL << rse_slot_num(addr))) != 0;
	off = (addr - trace->stack[0].addr);
	ret_addr = *(kaddr_t*)((uaddr_t)trace->stack[0].ptr + off);
	return (ret_addr);
}

/*
 * ia64_find_trace()
 */
int 
ia64_find_trace(
	kaddr_t start_pc, 
	kaddr_t start_bsp, 
	kaddr_t pfs,
	trace_t *trace, 
	int flags)
{
	int is_nat, curstkidx = 0, after_trap = 0;
	unsigned long sol, sof;
	kaddr_t cfm, limit, top;
	ia64_stkinfo_t info;
	kaddr_t pc, ra, bsp;
	char *func_name;
	sframe_t *curframe;
	syment_t *symp = (syment_t *)NULL;

	limit = (uaddr_t)trace->task + IA64_RBS_OFFSET;
	sol = (pfs >> 7) & 0x7f; /* size of locals */
	top = (unsigned long)rse_skip_regs(start_bsp, sol);
	if (top - (uaddr_t)trace->task >= KL_STACK_OFFSET) {
		top = limit;
	}
	memset((void*)&info, 0, sizeof(info));
	info.regstk.limit = limit;
	info.regstk.top = top;
	info.bsp = start_bsp;
	info.cfm = pfs;
	info.ra = start_pc;

	while (1) {
		curframe = alloc_sframe(trace, flags);
		ra = info.ra;
		bsp = info.bsp;
		cfm = info.cfm;

		/* When unwinding the stack, we need to take advantage of
		 * the fact that gcc stores the return address (ra) and 
		 * pfs values in the last two slots in the register stack 
		 * frame. This means that the number of local registers 
		 * must be at least 2. If it's less than that, we reached 
		 * the end of the C call stack. The possible exception to
		 * this is when we are in the first frame after a kernel
		 * trap. In such a case, there will be pt_regs data in
		 * the dump header and we should try and walk back from
		 * there (obviously, this is does not apply to a live 
		 * system).
		 */
		sol = (cfm >> 7) & 0x7f;        /* size of locals */
		if (sol < 2) {
			void *dh_asm, *regs;
			syment_t *sp;

			if (MIP->core_type == dev_kmem) {
				break;
			}
			dh_asm = kl_alloc_block(_DUMP_HEADER_ASM_S_SZ, K_TEMP);
			sp = kl_lkup_symname("dump_header_asm");
			GET_BLOCK(sp->s_addr, _DUMP_HEADER_ASM_S_SZ, dh_asm);
			if (KL_ERROR) {
				kl_free_block(dh_asm);
				break;
			}
			regs = K_PTR(dh_asm, "_dump_header_asm_s", "dha_regs");

			if (kl_kaddr(regs, "pt_regs", "cr_iip")) {
				/* There has been a trap 
				 */
				if (after_trap) {
					/* XXX -- what about nested traps?
					 */
					break;
				}
				pc = kl_kaddr(regs, "pt_regs", "cr_iip");
				ra = kl_kaddr(regs, "pt_regs", "b0");
				cfm = kl_kaddr(regs, "pt_regs", "cr_ifs");

				sol = (cfm >> 7) & 0x7f;        
				sof = cfm & 0x7f;        
				bsp = rse_skip_regs(info.bsp, -sof);

				if (!(symp = kl_lkup_symaddr(pc)) || 
					((symp->s_type != SYM_LOCAL_TEXT) &&
					 (symp->s_type != SYM_GLOBAL_TEXT))) {
					curframe->error = KLE_BAD_PC;
					UPDATE_FRAME(0, pc, ra, 0, 
						0, 0, 0, 0, 0);
					kl_free_block(regs);
					break;
				} 
				func_name = symp->s_name;
				UPDATE_FRAME(func_name, pc, ra, cfm, 
					bsp, 0, 0, 0, sol);

				/* Set up for the next frame
				 */
				info.ra = ra;
				info.cfm = kl_kaddr(regs, "pt_regs", "ar_pfs");
				sol = (info.cfm >> 7) & 0x7f;        
				info.bsp = rse_skip_regs(bsp, -sol);
				after_trap = 1;
				kl_free_block(regs);
				continue;
			}
			break;
		} 
		if (!ra) {
			curframe->error = KLE_BAD_RA;
			UPDATE_FRAME(0, 0, 0, 0, 0, 0, 0, 0, 0);
			break;
		}
		if (!(pc = get_call_pc(ra))) {
			/* XXX -- is this right? */
			pc = ra;
#ifdef NOT
			curframe->error = KLE_BAD_RA;
			UPDATE_FRAME(0, 0, ra, 0, 0, 0, 0, 0, 0);
			break;
#endif
		}
		if (!(symp = kl_lkup_symaddr(pc)) || 
				((symp->s_type != SYM_LOCAL_TEXT) &&
				 (symp->s_type != SYM_GLOBAL_TEXT))) {
			curframe->error = KLE_BAD_PC;
			UPDATE_FRAME(0, pc, ra, 0, 0, 0, 0, 0, 0);
			break;
		} 
		func_name = symp->s_name;

		info.ra = rse_read_reg(&info, sol - 2, &is_nat, trace);
		if (is_nat) {
			break;
		}

		UPDATE_FRAME(func_name, pc, info.ra, cfm, bsp, 0, 0, 0, sol);

		/* Make sure we have the correct frame size (this frame
		 * might contain rnat slot).
		 */
		if (curframe->level) {
			curframe->frame_size = (curframe->prev->fp - bsp)/8;
		}

		cfm = rse_read_reg(&info, sol - 1, &is_nat, trace);
		if (is_nat) {
			break;
		}
		sol = (cfm >> 7) & 0x7f;
		info.cfm = cfm;
		info.bsp = rse_skip_regs(info.bsp, -sol);
	}
	return(0);
}

/*
 * find_trace() -- This is just a stub routine for ia64.
 *
 */
int
find_trace(
	kaddr_t start_pc, 
	kaddr_t start_bsp, 
	kaddr_t pfs, 
	kaddr_t dummy,
	trace_t *trace, 
	int flags)
{
	return(ia64_find_trace(start_pc, start_bsp, pfs, trace, flags));
}

/*
 * find_task_trace()
 */
int
find_task_trace(trace_t *trace, int flags)
{
	if (trace->task == kl_dumptask()) {
		frame_info_t *fip = (frame_info_t *)NULL, *frame_info;
		syment_t *symp;	

		if ((frame_info = get_frame_list(trace->task))) {
			fip = frame_info;
			while (fip) {
				symp = kl_lkup_symaddr(fip->call_pc);
				if (symp && !strcmp(symp->s_name, "dump_execute")) {
					break;
				}
				fip = fip->next;
			}
		}
		if (fip) {
			ia64_find_trace(fip->call_pc, fip->bsp, 
				fip->pfs, trace, flags);
		} else {
			fprintf(stderr, "Can't find trace for "
				"running task!\n"); 
		}	
	} else {
		unsigned long sol;
		uaddr_t ksp_offset;
		kaddr_t ksp, bsp;
		struct switch_stack *sw;

		ksp = KL_UINT(K_PTR(trace->tsp, "task_struct", "thread"),
			"thread_struct", "ksp");
		ksp_offset = (ksp + 16) - (uaddr_t)trace->task;
		sw = (struct switch_stack *)
			((uaddr_t)trace->stack[0].ptr + ksp_offset);        

		sol = (sw->ar_pfs >> 7) & 0x7f;
		bsp = (unsigned long)rse_skip_regs(sw->ar_bspstore, -sol);
		ia64_find_trace(sw->b0, bsp, sw->ar_pfs, trace, flags);
	}
	return(0);
}

#ifdef NOT_USED
typedef struct bit_field_s {            
	int     bits;                   
	int     shift;          
} bit_field_t;                  
				
#define MAX_BITFIELDS   4
		
bit_field_t call_bits[MAX_BITFIELDS] = {{20, 13}, {1, 36}};
	
static kaddr_t
ext_imms_scaled(bit_field_t *field, kaddr_t code, int scale)
{       
	int i, bits = 0, total = 0, shift;
	BFD_HOST_64_BIT val = 0;
	kaddr_t value;
	
	for (i = 0; i < MAX_BITFIELDS && field[i].bits; ++i) {
		bits = field[i].bits;
		val |= ((code >> field[i].shift) 
		      & ((((kaddr_t) 1) << bits) - 1)) << total;
		total += bits;
	}
	
	/* sign extend:
	 */
	shift = 64 - total;
	val = (val << shift) >> shift;
	value = (val << scale);
	return(value);
}
#endif

#define SLOT(pc) (((pc & 0xf) < 6) ? 0 : (((pc & 0xf) < 0xc) ? 1 : 2))
#define OP_CODE(instr) ((instr >> 37) & 0x1f)

/*
 * is_branch() 
 */
int 
is_branch(bfd_byte *bundle, int slot)
{
	unsigned char template;
	uint64_t t0;

	t0 = bfd_getl64(bundle);
	template = (t0 & 0x1f);
	switch (slot) {
		case 0:
			if ((template == 0x16) || (template == 0x17)) {
				return(1);
			}
			break;
		case 1:
			if ((template == 0x12) || (template == 0x13) ||
			    (template == 0x16) || (template == 0x17)) {
				return(1);
			}
			break;
		case 2:
			if (((template >= 0x10) || (template <= 0x13)) ||
			    ((template >= 0x16) || (template <= 0x19)) ||
			    (template == 0x1c) || (template == 0x1d)) {
				return(1);
			}
			break;
	}
	return(0);
}

/*
 * get_call_pc()
 */
kaddr_t
get_call_pc(kaddr_t ra)
{
	unsigned char opcode;
	int count = 20, slot;
	uint64_t t0, t1, slot0, slot1, slot2;
	kaddr_t bundlep, pc, addr = 0;
	bfd_byte bundle[16];

	addr = ra;
	bundlep = (addr & 0xfffffffffffffff0);
	slot = SLOT(addr);
	if (slot == 0) {
		bundlep -= 16;
		slot = 2;
	}
	GET_BLOCK(bundlep, sizeof(bundle), bundle);
	t0 = bfd_getl64(bundle);
	t1 = bfd_getl64(bundle + 8);
	slot0 = (t0 >>  5) & 0x1ffffffffffLL;
	slot1 = ((t0 >> 46) & 0x3ffff) | ((t1 & 0x7fffff) << 18);
	slot2 = (t1 >> 23) & 0x1ffffffffffLL;
	while (count) {
		if (is_branch(bundle, slot)) {
			switch (slot) {
				case 0:
					opcode = OP_CODE(slot0);
					pc = bundlep;
					break;
				case 1:
					opcode = OP_CODE(slot1);
					pc = bundlep|0x6;
					break;
				case 2:
					pc = bundlep|0xc;
					opcode = OP_CODE(slot2);
					break;
			}
			if ((opcode == 5) || (opcode == 1)) {
				/* Call instruction */
				return(pc);
			}
		}
		switch(slot) {
			case 0:
				bundlep -= 16;
				GET_BLOCK(bundlep, sizeof(bundle), bundle);
				t0 = bfd_getl64(bundle);
				t1 = bfd_getl64(bundle + 8);
				slot0 = (t0 >>  5) & 0x1ffffffffffLL;
				slot1 = ((t0 >> 46) & 0x3ffff) |
						((t1 & 0x7fffff) << 18);
				slot2 = (t1 >> 23) & 0x1ffffffffffLL;
				slot = 2;
				break;
			case 1:
			case 2:
				slot--;
				break;
		}
		count--;
	}
	return(0);
}

/*
 * 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 = (uaddr_t *)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->task = 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
	 */
	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);
	}
	return(0);
}

/*
 * 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;

	for (i = 0; i < curframe->frame_size / 4; i++) {
		if (!(i % 4)) {
			if (first_time) {
				first_time = 0;
				fprintf(ofp, "   %"FMTPTR"x: %016"FMTPTR"x  ", 
					sp, *asp++);
			} else {
				fprintf(ofp, "\n   %"FMTPTR"x: ", sp);
				fprintf(ofp, "%016"FMTPTR"x  ", *asp++);
			}
			sp += 16;
		} else  {
			fprintf(ofp, "%016"FMTPTR"x  ", *asp++);
		}
	}
	if (curframe->frame_size) {
		fprintf(ofp, "\n\n");
	}
}

/*
 * print_trace()
 */
void
print_trace(trace_t *trace, int flags, FILE *ofp)
{
	int i, offset;
	sframe_t *frmp;
	kaddr_t addr, reg_val;

	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, " [0x%"FMTPTR"x]\n", frmp->pc);
			if (flags & C_FULL) {
				fprintf(ofp, "\n");
				fprintf(ofp, "   RA=0x%"FMTPTR"x, "
					"CFM=0x%"FMTPTR"x, BSP=0x%"FMTPTR"x, "
					"SIZE=%d\n\n", frmp->ra, frmp->sp, 
					frmp->fp, frmp->frame_size);
#ifdef NOT
				dump_stack_frame(trace, frmp, ofp);
#else
				addr = frmp->fp;
				for (i = 0; i < frmp->frame_size; i++) {
					offset = (addr - trace->stack[0].addr);
					reg_val = *(kaddr_t*)
						((uaddr_t)trace->stack[0].ptr +
						offset);

					fprintf(ofp, "  %2d: 0x%016"FMTPTR"x",
						i, reg_val); 
					if (!((i + 1) % 2)) {
						fprintf(ofp, "\n");
					}
					addr += 8;
				}
				if (frmp->frame_size % 2) {
					fprintf(ofp, "\n");
				}
				fprintf(ofp, "\n");
#endif
			}
			if (frmp->error) {
				fprintf(ofp, "TRACE ERROR 0x%"FMT64"x\n", 
					frmp->error);
			}
			frmp = frmp->next;
		} while (frmp != trace->frame);
	}
}

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

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

	if (!(trace = (trace_t *)alloc_trace_rec(C_TEMP))) {
		fprintf(KL_ERRORFP, "Could not alloc trace rec!\n");
		return(1);
	} else {
		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);
		}
		find_task_trace(trace, 0);
		trace_banner(ofp);
		fprintf(ofp, "STACK TRACE FOR TASK: ");
		print_kaddr(task, ofp, 0);
		fprintf(ofp, " (%s)\n\n", 
			(char*)K_PTR(trace->tsp, "task_struct", "comm"));
		print_trace(trace, flags, ofp);
	}
	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)
{
#ifdef NOT
	int nfrms;
	unsigned char instr; 
	int offset;
	char *fname, *cfname;
	uaddr_t *wordp, *stackp;
	trace_t *trace;
	kaddr_t addr, isp, caddr, sbase;
	
	stackp = (uaddr_t*)kl_alloc_block(STACK_SIZE, K_TEMP);
	sbase = saddr - STACK_SIZE;
	GET_BLOCK(sbase, STACK_SIZE, stackp);
	if (KL_ERROR) {
		kl_free_block(stackp);
		return(1);
	}

	if (!(trace = (trace_t *)alloc_trace_rec(K_TEMP))) {
		fprintf(KL_ERRORFP, "Could not alloc trace rec!\n");
		kl_free_block(stackp);
		return(1);
	}
	setup_trace_rec(saddr, 0, 0, trace);

	wordp = stackp;
	while(wordp < (stackp + (STACK_SIZE / 4))) {
		if (addr =  (kaddr_t)(*(uaddr_t*)wordp)) {

			/* check to see if this is a valid code address
			 */
			if (fname = kl_funcname(addr)) {
				/* Now use the instruction to back up and
				 * see if this RA was saved after a call.
				 * If it was, then try to determine what 
				 * function was called. At the very least,
				 * only print out info for true return
				 * addresses (coming right after a call
				 * instruction -- even if we can't tell
				 * what function was called).
				 */
				isp = sbase + (((uaddr_t)wordp) - 
						((uaddr_t)stackp));

				cfname = (char *)NULL;
				caddr = 0;
				if (get_jmp_instr(addr, isp, 
						&caddr, fname, &cfname)) {
					wordp++;
					continue;
				}

				/* We have found a valid jump address. Now, 
				 * try and get a backtrace.
				 */
				nfrms = find_trace(addr, isp, 0, 0, trace, 0);
				if (nfrms) {
					if ((nfrms >= level) &&
						 (!trace->frame->prev->error ||
							(flags & C_ALL))) {
						fprintf(ofp, "\nPC=");
						print_kaddr(addr, ofp, 0);
						fprintf(ofp, "  SP=");
						print_kaddr(isp, ofp, 0);
						fprintf(ofp, "  SADDR=");
						print_kaddr(saddr, ofp, 0);
						fprintf(ofp, "\n");
						trace_banner(ofp);
						print_trace(trace, flags, ofp);
						trace_banner(ofp);
					}
					free_sframes(trace);
				}
			}
			wordp++;
		} else {
			wordp++;
		}
	}
	kl_free_block(stackp);
#endif
	return(0);
}

/*
 * get_frame_list()
 */
static frame_info_t *
get_frame_list(kaddr_t task)
{
	unsigned long sol, sof;
	uaddr_t *wordp, pfs, *stackp;
	kaddr_t limit, addr, sp, bsp;
	frame_info_t *fip, *frame_info = (frame_info_t*)NULL;

	stackp = (uaddr_t*)kl_alloc_block(STACK_SIZE, K_TEMP);
	limit = (uaddr_t)task + IA64_RBS_OFFSET;
	GET_BLOCK(task, STACK_SIZE, stackp);
	if (KL_ERROR) {
		kl_free_block(stackp);
		return((frame_info_t *)NULL);
	}
	wordp = (uaddr_t*)((uaddr_t)stackp + (limit - task)); 
	wordp = (stackp + ((STACK_SIZE / KL_NBPW) - 1));
	while(wordp >= (uaddr_t*)((uaddr_t)stackp + (limit - task))) {
		if ((addr = (kaddr_t)(*(uaddr_t*)wordp))) {
			if (kl_funcname(addr)) {
				sp = task + ((uaddr_t)wordp - (uaddr_t)stackp);
				pfs = (kaddr_t)(*(uaddr_t*)(wordp + 1));
				if (frame_info) {
					frame_info->prev = (frame_info_t*)
						kl_alloc_block(sizeof(
						frame_info_t), K_TEMP);
					frame_info->prev->next = frame_info;
					frame_info = frame_info->prev;
				} else {
					frame_info = (frame_info_t*)
						kl_alloc_block(
						sizeof(frame_info_t), K_TEMP);
				}
				frame_info->sp = sp;
				frame_info->ra = addr;
				frame_info->pfs = pfs;
			}	
			wordp--;
		} else {
			wordp--;
		}
	}
	kl_free_block(stackp);

	/* Walk out to the end of the list (the earliest frame) and
	 * then fill in some more detail.
	 */
	if ((fip = frame_info)) {
		while (fip->next) {
			fip = fip->next;
		}
	} else {
		return((frame_info_t *)NULL);
	}

	while (1) {
		sol = ((fip->pfs >> 7) & 0x7f);
		sof = (fip->pfs & 0x7f);
		fip->call_pc = get_call_pc(fip->ra);
		if (fip->prev) {
			/* XXX - Need a sanity check to make sure we have a
			 * valid bsp value.
			 */
			bsp = fip->prev->sp + 16;
			bsp = rse_skip_regs(bsp, -(sol));
			fip->bsp = bsp;
		} 
		if (!(fip = fip->prev)) {
			break;
		}
	}	
	return(frame_info);
}

/*
 * free_frame_info()
 */
static void
free_frame_info(frame_info_t *frame_info)
{
	frame_info_t *fip;

	while (frame_info) {
		fip = frame_info->next;
		kl_free_block(frame_info);	
		frame_info = fip;
	}
}

/*
 * ia64_do_list()
 *
 *   Output a list of all valid code addresses contained in a stack
 *   along with their function name and stack location.
 */
int
ia64_do_list(kaddr_t task, FILE *ofp)
{
	unsigned long sol, sof;
	frame_info_t *fip, *frame_info;

	if (!(frame_info = get_frame_list(task))) {
		return(1);
	}

	/* Walk to the end of the list (the earliest frame).
	 */
	fip = frame_info;
	while (fip->next) {
		fip = fip->next;
	}

	while (1) {
		sol = ((fip->pfs >> 7) & 0x7f);
		sof = (fip->pfs & 0x7f);
		fprintf(ofp, "0x%lx: ra=0x%lx (%s)\n", 
			fip->sp, fip->ra, kl_funcname(fip->ra));
		fprintf(ofp, "                  : call_pc=0x%lx (%s)\n",
			fip->call_pc, kl_funcname(fip->call_pc));

		fprintf(ofp, "                  : sol=%ld, sof=%ld, pfs=0x%lx",
			sol, sof, fip->pfs);
		if (fip->bsp) {
			fprintf(ofp, ", bsp=0x%lx\n", fip->bsp);
		} else {
			fprintf(ofp, ", bsp=BAD\n");
		}
		if (!(fip = fip->prev)) {
			break;
		}
	}	
	free_frame_info(frame_info);
	return(0);
}

int
do_list(kaddr_t saddr, int size, FILE *ofp)
{
	return(ia64_do_list(saddr, ofp));	
}

/*
 * add_frame()
 */
int
add_frame(trace_t *trace, kaddr_t fp, kaddr_t ra)
{
#ifdef NOT
	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);
	}
#endif
	return(0);
}

/*
 * finish_trace()
 */
void
finish_trace(trace_t *trace)
{
#ifdef NOT
	int level = 0, curstkidx = 0;
	uaddr_t *sbp, *asp;
	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 = 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 = (uaddr_t*)((uaddr_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 = 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 = (uaddr_t*)((uaddr_t)sbp + 
					(STACK_SIZE - 12));
			} else {
				sf->fp = saddr - 20;
				kl_get_kaddr(sf->fp, &sf->ra);
				sf->asp = (uaddr_t*)((uaddr_t)sbp + 
					(STACK_SIZE - (saddr - sf->sp)));
			}
			sf->frame_size = sf->fp - sf->sp + 4;
			kl_enqueue((element_t **)&trace->frame, 
				(element_t *)sf);
		}
	}
#endif
}

/*
 * dumptask_trace()
 */
int
dumptask_trace(
	kaddr_t curtask, 
	dump_header_asm_t *dump_header_asm,
	int flags,
	FILE *ofp)
{
	task_trace(curtask, flags, ofp);
	return(0);
}
