/*
 *	deadlock.c -- run-time deadlock debugging
 *	Also includes run-time Floating Point error stuff
 *	Copyright (C) 2000-2002 Fred Barnes  <frmb2@ukc.ac.uk>
 *
 *	This program is free software; you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation; either version 2 of the License, or
 *	(at your option) any later version.
 *
 *	This program is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *	GNU General Public License for more details.
 *
 *	You should have received a copy of the GNU General Public License
 *	along with this program; if not, write to the Free Software
 *	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#ifdef OOS_BUILD
	#include <oos_funcs.h>
#else
	#include <stdio.h>
	#include <setjmp.h>
#endif

#include <sched_consts.h>
#include <arch/sched_types.h>
#include <arch/occam_io.h>
#include <arch/timer.h>

#ifndef RTS_H
	#define RTS_H
	#include <rts.h>
#endif

#define __DEADLOCK_C

#include <arch/deadlock.h>

typedef void (*FUNCPTR)(void);

#if !defined(OOS_BUILD)
static int deadlock_debug (int *iws_ptr, int bytes, int *did_print);
static void segv_handler (int);
static void debug_deadlock_out (FILE *stream, char *ops, char *proc, char *file, int line);
#endif	/* !defined(OOS_BUILD) */

static unsigned char *lowest_code_address;
static char *ws_ptrs[MAX_WORKSPACES];
static int ws_sizes[MAX_WORKSPACES];
static int num_ws = 0;

static int debug_dead;
#if !defined(OOS_BUILD)
static jmp_buf segenv;
#endif	/* !defined(OOS_BUILD) */

void (*old_segv_handler)(int);

#if !defined(OOS_BUILD)
extern jmp_buf kroc_out_jump_buffer;
#endif
extern int occam_finished;

#define INS_X86_JMPIMMS		(0xeb)
#define INS_SPARC_JMPIMMS	(0x48)
#define INS_PPC_JMPIMMS		(0x48)


#if defined(OOS_BUILD)
	#define MESSAGE(fmt,args...) oos_printk(fmt,##args)
#else
	#define MESSAGE(fmt,args...) fprintf(stderr,fmt,##args)
#endif


/*
 *	called from _enter() in occam_entry.c to setup initial workspace/size/code
 *	-- first thing called, so some self-initialisation in here
 */
void give_initial_ws_code (char *ws, int ws_bytes, unsigned char *codeptr)
{
	debug_dead = 0;
	if (!ws || !ws_bytes || !codeptr) {
		MESSAGE ("KRoC: %s: ws, ws_bytes or codeptr given as 0.  Not enabling deadlock debugging.\n", __FILE__);
		return;
	}
	num_ws = 1;
	*ws_ptrs = ws;
	*ws_sizes = ws_bytes;
	lowest_code_address = codeptr;
	debug_dead = 1;
	return;
}


/*
 *	used by dynamic procs (and anything else that wants to) to add a workspace
 */
void give_ws_code (char *ws, int ws_bytes, unsigned char *codeptr)
{
	int slot;

	if (!ws || !ws_bytes || !codeptr) {
		MESSAGE ("KRoC: %s: ws, ws_bytes or codeptr given as 0.  Ignoring workspace.\n", __FILE__);
		return;
	}
	if (num_ws == MAX_WORKSPACES) {
		for (slot=0; slot < MAX_WORKSPACES; slot++) {
			if (!ws_ptrs[slot]) {
				break;
			}
		}
		if (slot == MAX_WORKSPACES) {
			/* can be fixed by changing constant and re-compiling the RTS */
/*			fprintf (stderr, "KRoC: %s: no more room!  Ignoring workspace.  (increase MAX_WORKSPACES in deadlock.h)\n", __FILE__); */
			return;
		}
	} else {
		slot = num_ws++;
	}
	ws_ptrs[slot] = ws;
	ws_sizes[slot] = ws_bytes;
	if ((unsigned int)codeptr < (unsigned int)lowest_code_address) {
		lowest_code_address = codeptr;
	}
	return;
}


/*
 *	removes a workspace added with `give_ws_code'
 */
void take_ws (char *ws)
{
	int slot;

	if (!ws) {
		MESSAGE ("KRoC: %s: ws given as 0.  Ignoring.\n", __FILE__);
		return;
	}
	for (slot=1; slot<num_ws; slot++) {
		if (ws_ptrs[slot] == ws) {
			break;
		}
	}
	if (slot == num_ws) {
		MESSAGE ("KRoC: %s: cannot remove workspace, not found.  Ignoring.\n", __FILE__);
		return;
	}
	ws_ptrs[slot] = 0;
	ws_sizes[slot] = 0;
	if (slot == (num_ws - 1)) {
		num_ws--;
	}
	return;
}


#if !defined(OOS_BUILD)
/*{{{  static int deadlock_debug (int *iws_ptr, int bytes, int *did_print)*/
/*
 * Performs a WS search over `iws_ptr' for `bytes' bytes
 * Returns number-of-matches on success, -1 on failure (ie, program compiled without -X5)
 */
static int deadlock_debug (int *iws_ptr, int bytes, int *did_print)
{
	char *search, *debug_filename, *debug_procname;
	static unsigned char *codeptr;
	static int *search_int;
	int *search_limit;
	static int found;
	int debug_dlop, debug_kentry, debug_line, debug_file, debug_proc;
	unsigned char ins_1, ins_2;
	static char *dlops[] = { "INVALID", "IN", "OUT", "OUTBYTE", "OUTWORD", "ALTWT", "TALTWT", "XABLE" };
	static int num_dlops = 8;

	found = 0;
	if ((int)iws_ptr | 0x03) {
		search = (char *)(((unsigned int)iws_ptr & 0xfffffffc) + 4);
	} else {
		search = (char *)iws_ptr;
	}
	old_segv_handler = signal (SIGSEGV, segv_handler);
	/* Nicely aligned workspace */
	search_int = (int *)((unsigned int)search + 8);
	search_limit = (int *)((unsigned int)search + (unsigned int)bytes);
	#ifdef DEBUG_DEBUG
		MESSAGE ("KRoC: %s: searching from 0x%lx to 0x%lx...\n", __FILE__, (unsigned long int)search_int, (unsigned long int)search_limit);
	#endif
	for (; search_int < search_limit; search_int++) {
		if ((int *)search_int[-2] == search_int) {
			/* Possibility */
			codeptr = (unsigned char *)search_int[-1];
			if (codeptr < lowest_code_address) {
				/* Nope.. */
				#ifdef DEBUG_DEBUG
					MESSAGE ("KRoC: %s: codeptr (0x%lx) < lowest_code_address (0x%lx)\n", __FILE__, (unsigned long int)codeptr,
						(unsigned long int)lowest_code_address);
				#endif
				continue;
			}
			if (!setjmp (segenv)) {
				/* Try dereferencing it... */
				ins_1 = *codeptr;
#if defined(TARGET_CPU_SPARC) || defined(TARGET_CPU_PPC64)
				ins_2 = *(codeptr + 4);		/* next word please */
#else
				ins_2 = *(codeptr + 1);
#endif
			} else {
				/* Nope, obviously not.. */
				#ifdef DEBUG_DEBUG
					MESSAGE ("KRoC: %s: SEGV while dereferencing at 0x%lx.  lowest_code_address = 0x%lx\n", __FILE__,
						(unsigned long int)codeptr, (unsigned long int)lowest_code_address);
				#endif
				continue;
			}
			/* They should be two halves */
#if defined(TARGET_CPU_SPARC)
			if (ins_1 != INS_SPARC_JMPIMMS) {
#elif defined(TARGET_CPU_PPC64)
			if (ins_1 != INS_PPC_JMPIMMS) {
#else
			if (ins_1 != INS_X86_JMPIMMS) {
#endif
				#ifdef DEBUG_DEBUG
					MESSAGE ("KRoC: %s: INS_X86_JMPIMMS=0x%2.2x, read 0x%2.2x.\n", __FILE__, INS_X86_JMPIMMS, ins_1);
				#endif
				continue;
			}
#if defined(TARGET_CPU_SPARC) 
			if (ins_2 != 0x11) {
#elif defined(TARGET_CPU_PPC64)
			if (ins_2 != 0x60) {
#else
			if (ins_2 != 0x11) {
#endif
				#ifdef DEBUG_DEBUG
					MESSAGE ("KRoC: %s: jump (0x%2.2x) out of range (should be 0x11/0x60)\n", __FILE__, ins_2);
				#endif
				continue;
			}
			if (!setjmp (segenv)) {
				/* Could be likely.. */
#if defined(TARGET_CPU_SPARC) || defined(TARGET_CPU_PPC64)
				codeptr += 8;
#else
				codeptr += 2;
#endif
				/* codeptr should be pointing at 12 bytes of debugging info + 5 bytes of jump code */
#ifdef TARGET_BIGENDIAN
				ins_1 = codeptr[0];
				ins_2 = codeptr[1];
#else
				ins_1 = codeptr[3];
				ins_2 = codeptr[2];
#endif
				debug_kentry = ins_2;
				debug_dlop = ins_1;
#ifdef TARGET_BIGENDIAN
				ins_1 = codeptr[3];
				ins_2 = codeptr[2];
#else
				ins_1 = codeptr[0];
				ins_2 = codeptr[1];
#endif
				debug_line = (ins_2 << 8) + ins_1;
			} else {
				continue;
			}
			if ((debug_dlop >= num_dlops) || (debug_dlop <= 0)) {
				/* Well..  nearly */
				#ifdef DEBUG_DEBUG
					MESSAGE ("KRoC: %s: DLOP read as %d.  Bytes are: %2.2X %2.2X %2.2X %2.2X\n", __FILE__, debug_dlop, codeptr[0], codeptr[1],
						codeptr[2], codeptr[3]);
				#endif
				continue;
			}
			if (!setjmp (segenv)) {
				/* Next word is filename/procname */
				codeptr += 4;
				ins_1 = codeptr[0];
				ins_2 = codeptr[1];
				debug_proc = ((int)ins_2 << 8) + ins_1;
				ins_1 = codeptr[2];
				ins_2 = codeptr[3];
				debug_file = ((int)ins_2 << 8) + ins_1;
			} else {
				#ifdef DEBUG_DEBUG
					MESSAGE ("KRoC: %s: SEGV while deferencing at 0x%lx.  lowest_code_address = 0x%lx\n", __FILE__,
						(unsigned long int)codeptr, (unsigned long int)lowest_code_address);
				#endif
				continue;
			}
			if (!setjmp (segenv)) {
				/* Next word is some magic */
				codeptr += 4;
				ins_1 = codeptr[0];
				ins_2 = codeptr[1];
				if ((ins_1 != 0xde) || (ins_2 != 0xad)) {
					/* Incorrect first magic */
					#ifdef DEBUG_DEBUG
						MESSAGE ("KRoC: %s: Read magic as 0x%2.2x%2.2x, should have been 0xdead\n", __FILE__, ins_1, ins_2);
					#endif
					continue;
				}
				ins_1 = codeptr[2];
				ins_2 = codeptr[3];
				if ((ins_1 != 0xbe) || (ins_2 != 0xef)) {
					/* Incorrect second magic */
					#ifdef DEBUG_DEBUG
						MESSAGE ("KRoC: %s: Read magic as 0x%2.2x%2.2x, should have been 0xbeef\n", __FILE__, ins_1, ins_2);
					#endif
					continue;
				}
				/* Jump into codeptr... */
				codeptr += 4;
				ins_1 = *codeptr;
#if defined(TARGET_CPU_SPARC)
				if (ins_1 != SPARC_JUMP_INS) {
#elif defined(TARGET_CPU_PPC64)
				if (ins_1 != PPC_JUMP_INS) {
#else
				if (ins_1 != I386_JUMP_INS) {
#endif
					/* Not a jump :( */
					#ifdef DEBUG_DEBUG
						MESSAGE ("KRoC: %s: Expected opcode 0xe9, found 0x%2.2x\n", __FILE__, ins_1);
					#endif
					continue;
				}
				DEADLOCK_CODE_BLOCK (debug_filename, debug_procname, codeptr);
			} else {
				#ifdef DEBUG_DEBUG
					MESSAGE ("KRoC: %s: SEGV while executing at 0x%lx\n", __FILE__, (unsigned long int)codeptr);
				#endif
				continue;
			}
			if (debug_filename < (char *)codeptr) {
				#ifdef DEBUG_DEBUG
					MESSAGE ("KRoC: %s: filename table (0x%lx) < codeptr (0x%lx)\n", __FILE__,
						(unsigned long int)debug_filename, (unsigned long int)codeptr);
				#endif
				continue;
			}
			if (debug_procname < (char *)codeptr) {
				#ifdef DEBUG_DEBUG
					MESSAGE ("KRoC: %s: procname table (0x%lx) < codeptr (0x%lx)\n", __FILE__,
						(unsigned long int)debug_procname, (unsigned long int)codeptr);
				#endif
				continue;
			}
			#ifdef DEBUG_DEBUG
			MESSAGE ("KRoC: %s: debug_file=0x%8.8x, debug_filename=0x%8.8x, debug_proc=0x%8.8x, debug_procname=0x%8.8x\n", __FILE__, (unsigned int)debug_file, (unsigned int)debug_filename, (unsigned int)debug_proc, (unsigned int)debug_procname);
			#endif
			/* Check them */
			if (debug_file >= *(int *)(debug_filename)) {
				#ifdef DEBUG_DEBUG
					MESSAGE ("KRoC: %s: filename %d out of range of table (%d)\n", __FILE__,
						debug_file, *(int *)(debug_filename));
				#endif
				continue;
			}
			if (debug_proc >= *(int *)(debug_procname)) {
				#ifdef DEBUG_DEBUG
					MESSAGE ("KRoC: %s: procname %d out of range of table (%d)\n", __FILE__,
						debug_proc, *(int *)(debug_procname));
				#endif
				continue;
			}
			/* Okay.. */
			debug_filename += *(int *)(debug_filename + ((1 + debug_file) * 4));
			debug_procname += *(int *)(debug_procname + ((1 + debug_proc) * 4));
			found++;
			if (!*did_print) {
				MESSAGE ("KRoC: Deadlock:\n");
				*did_print = 1;
			}
			debug_deadlock_out (stderr, dlops[debug_dlop], debug_procname, debug_filename, debug_line);
		}
	}
	signal (SIGSEGV, old_segv_handler);
	return found;
}
/*}}}*/
#endif	/* !defined(OOS_BUILD) */


#if !defined(OOS_BUILD)
/*
 * void debug_deadlock_out (FILE *stream, char *ops, char *proc, char *file, int line)
 * Sends out debugging info on 'stream'
 */
static void debug_deadlock_out (FILE *stream, char *ops, char *proc, char *file, int line)
{
	#if defined(DEADLOCK_TERSE)
		fprintf (stream, "%s(%d)-%s  PROC %s (...)\n", file, line, ops, proc);
	#elif defined(DEADLOCK_BREIF)
		fprintf (stream, "\t%s:%d\n", file, line);
		fprintf (stream, "\t\tPROC %s () instruction %s\n", proc, ops);
	#else
		fprintf (stream, "\n  Instruction \"%s\"\n", ops);
		fprintf (stream, "    in PROC \"%s\"\n", proc);
		fprintf (stream, "    in file \"%s\"\n", file);
		fprintf (stream, "    near line %d\n", line);
	#endif
	return;
}
#endif


#if !defined(OOS_BUILD)
/*
 * void segv_handler (int signum)
 * SIGSEGV handler
 */
static void segv_handler (int signum)
{
	signal (SIGSEGV, segv_handler);
	longjmp (segenv, 1);
	return;
}
#endif	/* !defined(OOS_BUILD) */


/*
 * void kernel_dead (int status)
 * Called when the kernel deadlocks
 */
void kernel_dead (int status)
{
#if !defined(OOS_BUILD)
	int found, i;

#endif
	int did_print;

	did_print = 0;
	if (occam_finished) {
#if defined(OOS_BUILD)
		oos_exit (1);
#else
		longjmp (kroc_out_jump_buffer, 1);
#endif
	}
#if !defined(OOS_BUILD)
	if (debug_dead) {
		found = 0;
		for (i=0; i<num_ws; i++) {
			found += deadlock_debug ((int *)ws_ptrs[i], ws_sizes[i], &did_print);
		}
		if (!found) {
			MESSAGE ("KRoC: deadlock: no valid processes found in workspace(s)\n");
			debug_dead = 0;
		}
	}
#else	/* defined(OOS_BUILD) */
	oos_exit (2);
	/* longjmp (kroc_out_jump_buffer, 2); */
#endif	/* defined(OOS_BUILD) */
	if (!debug_dead) {
		dead (status);
	} else {
		dead_quiet (status);
	}
	return;
}


/*
 * int fpe_debug (void)
 * Called upon SIGFPE -- when a floating point error occurs
 */
int fpe_debug (void)
{
	return 0;
}


