/*
 * Minimalist Kernel Debugger 
 *
 * Copyright (C) 1999 Silicon Graphics, Inc.
 * Copyright (C) Scott Lurndal (slurn@engr.sgi.com)
 * Copyright (C) Scott Foehner (sfoehner@engr.sgi.com)
 * Copyright (C) Srinivasa Thirumalachar (sprasad@engr.sgi.com)
 *
 * Written March 1999 by Scott Lurndal at Silicon Graphics, Inc.
 *  
 * Modifications from:
 *      Richard Bass                    1999/07/20
 *              Many bug fixes and enhancements.
 *      Scott Foehner  
 *              Port to ia64
 */

#include <linux/string.h>
#include <linux/kernel.h>
#include <linux/kdb.h>
#if defined(__SMP__)
#include <linux/smp.h>
#include <linux/sched.h>
#endif
#include <asm/system.h>
#include <asm/kdbsupport.h>

extern kdb_state_t kdb_state ;

/*
 * Table of breakpoints
 */
kdb_bp_t    breakpoints[KDB_MAXBPT];

char *rwtypes[] = {"Instruction", "Data Write", "I/O", "Data Access"};

/*
 * kdb_bp_install
 *
 * 	Install breakpoints prior to returning from the kernel debugger. 
 * 	This allows the breakpoints to be set upon functions that are
 * 	used internally by kdb, such as printk().
 *
 * Parameters:
 *	None.
 * Outputs:
 *	None.
 * Returns:
 *	None.
 * Locking:
 *	None.
 * Remarks:
 */

void
kdb_bp_install(void)
{
	int i;

	for(i=0; i<KDB_MAXBPT; i++) {
		if ((!breakpoints[i].bp_free) && 
			(breakpoints[i].bp_enabled)){
			kdbinstalldbreg(&breakpoints[i]);
		}
	}
}

#if defined(__SMP__)
/*
 * kdb_global
 *
 * 	Called from each processor when leaving the kernel debugger
 *	barrier IPI to establish the debug register contents for 
 *	global breakpoints.
 *
 * Parameters:
 *	cpuid		Calling logical cpu number
 * Outputs:
 *	None.
 * Returns:
 *	None.
 * Locking:
 *	None.
 * Remarks:
 */

/* ARGSUSED */
void
kdb_global(int cpuid)
{
	int i;

	for(i=0; i<KDB_MAXBPT; i++) {
		if (breakpoints[i].bp_enabled && breakpoints[i].bp_global){
			kdbinstalldbreg(&breakpoints[i]);
		}
	}

}
#endif	/* __SMP__ */

/*
 * kdb_bp_remove
 *
 * 	Remove breakpoints upon entry to the kernel debugger. 
 *
 * Parameters:
 *	None.
 * Outputs:
 *	None.
 * Returns:
 *	None.
 * Locking:
 *	None.
 * Remarks:
 */

void
kdb_bp_remove(void)
{
	int i;

	for(i=0; i<KDB_MAXBPT; i++) {
		if (breakpoints[i].bp_enabled) {
			kdbremovedbreg(&breakpoints[i]);
		}
	}
}

/*
 * kdb_printbp
 *
 * 	Internal function to format and print a breakpoint entry.
 *
 * Parameters:
 *	None.
 * Outputs:
 *	None.
 * Returns:
 *	None.
 * Locking:
 *	None.
 * Remarks:
 */

static void
kdb_printbp(kdb_bp_t *bp, int i)
{
	char *symname;
	long  offset;

	kdb_printf("%s ", rwtypes[bp->bp_mode]);

	kdb_printf("BP #%d at 0x%lx ",
		   i, bp->bp_addr);
	symname = kdbnearsym(bp->bp_addr);
	if (symname){
		kdb_printf("(%s", symname);

		offset = bp->bp_addr - kdbgetsymval(symname);
		if (offset) {
			if (offset > 0)
				kdb_printf("+0x%lx", offset);
			else
				kdb_printf("-0x%lx", offset);
		}
		kdb_printf(") ");
	} 

	if (bp->bp_enabled) {
		kdb_printf("\n    is enabled in dr%d", bp->bp_reg);
		if (bp->bp_global)
			kdb_printf(" globally");
		else 
			kdb_printf(" on cpu %d", bp->bp_cpu);

		if (bp->bp_mode != 0) {
			kdb_printf(" for %d bytes", bp->bp_length + 1);
		}
	} else {
		kdb_printf("\n    is disabled");
	}

	kdb_printf("\n");
}

/*
 * kdb_bp
 *
 * 	Handle the bp, and bpa commands.
 *
 *	[bp|bpa] <addr-expression> [DATAR|DATAW|IO [length]]
 *
 * Parameters:
 *	argc	Count of arguments in argv
 *	argv	Space delimited command line arguments
 *	envp	Environment value
 *	regs	Exception frame at entry to kernel debugger
 * Outputs:
 *	None.
 * Returns:
 *	Zero for success, a kdb diagnostic if failure.
 * Locking:
 *	None.
 * Remarks:
 */

int
kdb_bp(int argc, const char **argv, const char **envp, struct pt_regs *regs)
{
	int     i;
	kdb_bp_t *bp;
	int     diag;
	int     free, same;
	k_machreg_t addr;
	char   *symname = NULL;
	long    offset = 0ul;
	int	nextarg;
	unsigned long 	mode, length;
#if defined(__SMP__)
	int	global;
#endif

	if (argc == 0) {
		/*
		 * Display breakpoint table
		 */
		for(i=0,bp=breakpoints; i<KDB_MAXBPT; i++, bp++) {
			if (bp->bp_free) continue;

			kdb_printbp(bp, i);
		}

		return 0;
	}
	
#if defined(__SMP__)
	global = (strcmp(argv[0], "bpa") == 0);
#endif

	nextarg = 1;
	diag = kdbgetaddrarg(argc, argv, &nextarg, &addr, &offset, &symname, regs);
	if (diag)
		return diag;

	mode = BKPTMODE_INST;	/* Default to instruction breakpoint */
	length = 0;		/* Length must be zero for insn bp */
	if ((argc + 1) != nextarg) {
		if (strnicmp(argv[nextarg], "datar", sizeof("datar")) == 0) {
			mode = BKPTMODE_DATAR;
		} else if (strnicmp(argv[nextarg], "dataw", sizeof("dataw")) == 0) {
			mode = BKPTMODE_DATAW;
		} else if (strnicmp(argv[nextarg], "io", sizeof("io")) == 0) {
			mode = BKPTMODE_IO;
		} else {
			return KDB_ARGCOUNT;
		}

		length = 3;	/* Default to 4 byte */

		nextarg++;

		if ((argc + 1) != nextarg) {
			diag = kdbgetularg((char *)argv[nextarg], &length);
			if (diag)
				return diag;

			if ((length > 4) || (length == 3)) 
				return KDB_BADLENGTH;

			length--;	/* Normalize for debug register */
			nextarg++;
		}

		if ((argc + 1) != nextarg) 
			return KDB_ARGCOUNT;
	}
	
	/*
	 * Allocate a new bp structure 
	 */
	free = same = KDB_MAXBPT;
	for(i=0; i<KDB_MAXBPT; i++) {
		if (breakpoints[i].bp_free) {
			if (free == KDB_MAXBPT) 
				free = i;
		} else if (breakpoints[i].bp_addr == addr) {
			same = i;
		}
	}
	if (same != KDB_MAXBPT) 
		return KDB_DUPBPT;

	if (free == KDB_MAXBPT)
		return KDB_TOOMANYBPT;

	bp = &breakpoints[free];

	bp->bp_addr = addr;
	bp->bp_mode = mode;
	bp->bp_length = length;
	
	bp->bp_reg = kdb_allocdbreg(bp) ;
	if (bp->bp_reg == -1) {
		return KDB_TOOMANYDBREGS;
	}

	bp->bp_free = 0;
	bp->bp_enabled = 1;
	bp->bp_instvalid = 0;
	
#if defined(__SMP__)
	if (global) 
		bp->bp_global = 1;
	else
		bp->bp_cpu = smp_processor_id();
#endif

	kdb_printbp(bp, free);

	return 0;
}

/*
 * kdb_bc
 *
 * 	Handles the 'bc', 'be', and 'bd' commands
 *
 *	[bd|bc|be] <breakpoint-number>
 *
 * Parameters:
 *	argc	Count of arguments in argv
 *	argv	Space delimited command line arguments
 *	envp	Environment value
 *	regs	Exception frame at entry to kernel debugger
 * Outputs:
 *	None.
 * Returns:
 *	Zero for success, a kdb diagnostic for failure
 * Locking:
 *	None.
 * Remarks:
 */
#define KDBCMD_BC	0
#define KDBCMD_BE	1
#define KDBCMD_BD	2

int
kdb_bc(int argc, const char **argv, const char **envp, struct pt_regs *regs)
{
	k_machreg_t 	addr;
	kdb_bp_t	*bp = 0;
	int lowbp = KDB_MAXBPT;
	int highbp = 0;
	int done = 0;
	int i;
	int diag;
	int cmd;			/* KDBCMD_B? */

	if (strcmp(argv[0], "be") == 0) {
		cmd = KDBCMD_BE;
	} else if (strcmp(argv[0], "bd") == 0) {
		cmd = KDBCMD_BD;
	} else 
		cmd = KDBCMD_BC;

	if (argc != 1) 
		return KDB_ARGCOUNT;

	if (strcmp(argv[1], "*") == 0) {
		lowbp = 0;
		highbp = KDB_MAXBPT;
	} else {
		diag = kdbgetularg(argv[1], &addr);
		if (diag) 
			return diag;

		/*
		 * For addresses less than the maximum breakpoint number, 
		 * assume that the breakpoint number is desired.
		 */
		if (addr < KDB_MAXBPT) {
			bp = &breakpoints[addr];
			lowbp = highbp = addr;
			highbp++;
		} else {
			for(i=0; i<KDB_MAXBPT; i++) {
				if (breakpoints[i].bp_addr == addr) {
					lowbp = highbp = i;
					highbp++;
					break;
				}
			}
		}
	}

	for(bp=&breakpoints[lowbp], i=lowbp; 
	    i < highbp;
	    i++, bp++) {
		if (bp->bp_free) 
			continue;
		
		done++;

		switch (cmd) {
		case KDBCMD_BC:
			kdbremovedbreg(bp);
			kdb_freedbreg(bp);
			kdb_printf("Breakpoint %d at 0x%lx in dr%d cleared\n",
				i, bp->bp_addr, bp->bp_reg);
			bp->bp_free = 1;
			bp->bp_addr = 0;
			bp->bp_instvalid = 0;
			break;
		case KDBCMD_BE:
		{
			int r;

			r = kdb_allocdbreg(bp);
			if (r == -1) {
				return KDB_TOOMANYDBREGS;
			}

			bp->bp_reg = r;
			bp->bp_enabled = 1;
			kdb_printf("Breakpoint %d at 0x%lx in dr%d enabled\n",
				i, bp->bp_addr, bp->bp_reg);

			break;
		}
		case KDBCMD_BD:
			kdbremovedbreg(bp);
			kdb_freedbreg(bp);
			bp->bp_enabled = 0;
			bp->bp_instvalid = 0;
			kdb_printf("Breakpoint %d at 0x%lx in dr%d disabled\n",
				i, bp->bp_addr, bp->bp_reg);
			break;
		}
	}

	return (!done)?KDB_BPTNOTFOUND:0;
}

/*
 * kdb_initbptab
 *
 *	Initialize the breakpoint table.
 *
 * Parameters:
 *	None.
 * Outputs:
 *	None.
 * Returns:
 *	None.
 * Locking:
 *	None.
 * Remarks:
 */

void
kdb_initbptab(void)
{
	int i;

	/*
	 * First time initialization.  
	 */
	for (i=0; i<KDB_MAXBPT; i++) {
		breakpoints[i].bp_free = 1;
		breakpoints[i].bp_enabled = 0;
		breakpoints[i].bp_global = 0;
		breakpoints[i].bp_reg = 0;
		breakpoints[i].bp_mode = 0;
		breakpoints[i].bp_length = 0;
		breakpoints[i].bp_instvalid = 0;
	}

	kdb_initdbregs();
}

/*
 * kdb_ss
 *
 *	Process the 'ss' (Single Step) and 'ssb' (Single Step to Branch)
 *	commands.
 *
 *	ss [<insn-count>]
 *	ssb
 *
 * Parameters:
 *	argc	Argument count
 *	argv	Argument vector
 *	envp	Environment vector
 *	regs	Registers at time of entry to kernel debugger
 * Outputs:
 *	None.
 * Returns:
 *	0 for success, a kdb error if failure.
 * Locking:
 *	None.
 * Remarks:
 *
 *	Set the trace flag in the EFLAGS register to trigger 
 *	a debug trap after the next instruction.   Print the 
 *	current instruction.
 *
 *	For 'ssb', set the trace flag in the debug trap handler 
 *	after printing the current insn and return directly without
 *	invoking the kdb command processor, until a branch instruction
 *	is encountered or SSCOUNT lines are printed.
 */

int
kdb_ss(int argc, const char **argv, const char **envp, struct pt_regs *regs)
{
	int ssb = 0;

	ssb = (strcmp(argv[0], "ssb") == 0);
	if ((ssb && (argc != 0)) 
	 || (!ssb && (argc > 1))) {
		return KDB_ARGCOUNT;
	}

#if 0
	/*
	 * Fetch provided count
	 */
	diag = kdbgetularg(argv[1], &sscount);
	if (diag)
		return diag;
#endif

	/*
	 * Print current insn
	 */
#if 0
	kdb_id1(kdb_getpc(regs));	
#endif
	/*
  	 * Set trace flag and go.
	 */
	kdb_setsinglestep(regs);
	kdb_state.cmd_given = CMDGIVEN_SSTEP ;

	if (ssb) 
		kdb_flags |= KDB_FLAG_SSB;

	return KDB_GO;
}

