/*
 *  linux/abi/emulate.c
 *
 *  Copyright (C) 1993  Linus Torvalds
 *   Modified by Eric Youngdale to include all ibcs syscalls.
 *   Re-written by Drew Sullivan to handle lots more of the syscalls correctly.
 *
 *   Jan 30 1994, Merged Joe Portman's code -- Drew
 *   Jan 31 1994, Merged Eric Yongdale's code for elf support -- Drew
 *
 *   Feb 4 1994
 *     Rebuilt with handling for multiple binary personalities
 *     -- Mike Jagdis (jaggy@purplet.demon.co.uk)
 *
 *  Feb 14 1994
 *     Dual mode. Compiled in if you say yes to the configure iBCS
 *     question during 'make config'. Loadable module with kernel
 *     hooks otherwise.
 *     -- Mike Jagdis (jaggy@purplet.demon.co.uk)
 *
 *  Feb 18 1994
 *     Added the /dev/socksys emulator. This allows applications which
 *     use the socket interface to Lachman streams based TCP/IP to use
 *     the Linux TCP/IP stack.
 *     -- Mike Jagdis (jaggy@purplet.demon.co.uk)
 *
 * $Id: emulate.c,v 1.13 1994/05/26 16:15:10 mike Exp $
 * $Source: /var/CVS/ibcs/emulate.c,v $
 */

/*
 * Emulate.c contains the entry point for the 'lcall 7,xxx' handler.
 */
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/stddef.h>
#include <linux/unistd.h>
#include <linux/segment.h>
#include <linux/ptrace.h>
#include <linux/config.h>
#include <linux/fcntl.h>

#include <asm/segment.h>
#include <asm/system.h>
#include <linux/fs.h>
#include <linux/sys.h>
#include <linux/personality.h>

#ifndef CONFIG_BINFMT_IBCS
#include <linux/binfmts.h>
#include <linux/module.h>
#endif

#include <ibcs/ibcs.h>
#include <ibcs/abi4.h>
#include <ibcs/xnx.h>

#ifdef IBCS_TRACE
#include <ibcs/trace.h>
static void plist(int id, char *name, char *args, int *list);
static void fail(int id, long eax, char *name);
int ibcs_trace = 0;
int ibcs_id = 0;

static char *sig_names[] = {
	"SIGHUP",	"SIGINT",	"SIGQUIT",	"SIGILL",
	"SIGTRAP",	"SIGABRT/SIGIOT","SIGUNUSED",	"SIGFPE",
	"SIGKILL",	"SIGUSR1",	"SIGSEGV",	"SIGUSR2",
	"SIGPIPE",	"SIGALRM",	"SIGTERM",	"SIGSTKFLT",
	"SIGCHLD",	"SIGCONT",	"SIGSTOP",	"SIGTSTP",
	"SIGTTIN",	"SIGTTOU",	"SIGIO/SIGPOLL/SIGURG",
	"SIGXCPU",	"SIGXFSZ",	"SIGVTALRM",	"SIGPROF",
	"SIGWINCH",	"SIGLOST",	"SIGPWR",	"SIG 31",
	"SIG 32"
};

#ifdef VERBOSE_ERRORS
static char *errmsg[] = {
	"No error",
	"Operation not permitted",
	"No such file or directory",
	"No such process",
	"Interrupted system call",
	"I/O error",
	"No such device or address",
	"Arg list too long",
	"Exec format error",
	"Bad file number",
	"No child processes",
	"Try again",
	"Out of memory",
	"Permission denied",
	"Bad address",
	"Block device required",
	"Device or resource busy",
	"File exists",
	"Cross-device link",
	"No such device",
	"Not a directory",
	"Is a directory",
	"Invalid argument",
	"File table overflow",
	"Too many open files",
	"Not a typewriter",
	"Text file busy",
	"File too large",
	"No space left on device",
	"Illegal seek",
	"Read-only file system",
	"Too many links",
	"Broken pipe",
	"Math argument out of domain of func",
	"Math result not representable",
	"Resource deadlock would occur",
	"File name too long",
	"No record locks available",
	"Function not implemented",
	"Directory not empty",
	"Too many symbolic links encountered",
	"",
	"No message of desired type",
	"Identifier removed",
	"Channel number out of range",
	"Level 2 not synchronized",
	"Level 3 halted",
	"Level 3 reset",
	"Link number out of range",
	"Protocol driver not attached",
	"No CSI structure available",
	"Level 2 halted",
	"Invalid exchange",
	"Invalid request descriptor",
	"Exchange full",
	"No anode",
	"Invalid request code",
	"Invalid slot",
	"File locking deadlock error",
	"Bad font file format",
	"Device not a stream",
	"No data available",
	"Timer expired",
	"Out of streams resources",
	"Machine is not on the network",
	"Package not installed",
	"Object is remote",
	"Link has been severed",
	"Advertise error",
	"Srmount error",
	"Communication error on send",
	"Protocol error",
	"Multihop attempted",
	"RFS specific error",
	"Not a data message",
	"Value too large for defined data type",
	"Name not unique on network",
	"File descriptor in bad state",
	"Remote address changed",
	"Can not access a needed shared library",
	"Accessing a corrupted shared library",
	".lib section in a.out corrupted",
	"Attempting to link in too many shared libraries",
	"Cannot exec a shared library directly",
	"Illegal byte sequence",
	"Interrupted system call should be restarted",
	"Streams pipe error",
	"Too many users",
	"Socket operation on non-socket",
	"Destination address required",
	"Message too long",
	"Protocol wrong type for socket",
	"Protocol not available",
	"Protocol not supported",
	"Socket type not supported",
	"Operation not supported on transport endpoint",
	"Protocol family not supported",
	"Address family not supported by protocol",
	"Address already in use",
	"Cannot assign requested address",
	"Network is down",
	"Network is unreachable",
	"Network dropped connection because of reset",
	"Software caused connection abort",
	"Connection reset by peer",
	"No buffer space available",
	"Transport endpoint is already connected",
	"Transport endpoint is not connected",
	"Cannot send after transport endpoint shutdown",
	"Too many references: cannot splice",
	"Connection timed out",
	"Connection refused",
	"Host is down",
	"No route to host",
	"Operation already in progress",
	"Operation now in progress",
	"Stale NFS file handle",
	"Structure needs cleaning",
	"Not a XENIX named type file",
	"No XENIX semaphores available",
	"Is a named type file",
	"Remote I/O error",
	"Quota exceeded",
};
#endif /* VERBOSE_ERRORS */

#else /* IBCS_TRACE */

static void fail(long eax);

#endif /* IBCS_TRACE */

IBCS_func *ibcs_func_p;


/* This table contains the appropriate kernel routines that can be run
 * to perform the syscalls in question.  If an entry is 'Ukn' we don't
 * know how to handle it yet. (We also set trace on by default for these)
 * Spl means that we need to do special processing for this syscall
 *	(see ibcs_wait or ibcs_getpid)
 * Fast means that even the error return handling is done by the function call.
 */
#define ZERO	64	/* Um, magic zero for callmap. Don't ask :-). */
#define Spl	65	/* pass the regs structure down */
#define Ukn	66	/* no code to handle this case yet */
#define Fast	67	/* magic on return, return regs structure already set up */
#define Class	68	/* is a pointer to a subclass */
#define ISC	69	/* is a pointer to a sysisc subclass */
			/* subfun # is first argument on stack */

#define last(x)	((sizeof(x)/sizeof(*x))-1)

#include "maps/callmap.inc"

#ifndef CONFIG_BINFMT_IBCS
char kernel_version[] = KVERSION;

void iABI_emulate_real(struct pt_regs * regs) {
#else
void iABI_emulate(struct pt_regs * regs) {
#endif
	int	pers, i, j, k;
	int	of;
	int	args[8];
	int	rvalue;
	IBCS_func **group_map, *p;
	void	*kfunc;
	short	nargs;
#ifdef IBCS_TRACE
	int	id = ++ibcs_id;
#endif

#ifdef CONFIG_BINFMT_IBCS
	static int first_call = 1;

	if (first_call) {
		init_socksys();
		first_call = 0;
	}
#else
	MOD_INC_USE_COUNT;
#endif

	/* First decide which personality map we should be looking
	 * at by looking at the personality of this process.
	 */
	pers = current->personality & PER_MASK;
	if (pers > last(iBCS_personality_map)
	|| !(group_map = iBCS_personality_map[pers])) {
#ifdef IBCS_TRACE
		printk(KERN_DEBUG "[%d]%d iBCS: bad personality %d\n",
			id, current->pid, pers);
#else
		printk(KERN_DEBUG "%d iBCS: bad personality %d\n",
			current->pid, pers);
#endif
		regs->eflags |= 1; /* Set carry flag */
		regs->eax = iABI_errors(EINVAL);
#ifndef CONFIG_BINFMT_IBCS
		MOD_DEC_USE_COUNT;
#endif
		return;
	}

	i = regs->eax;
	of = 1;

do_subgroup:
	j = (i & 0xff) >> 3;
	k = i & 0x07;
	if (!group_map[j]) {
		/* This catches NULLs in group maps. It's better to
		 * point the block to the Unused set for tracing rather
		 * than using NULL but we'll trap it anyway.
		 */
#ifdef IBCS_TRACE
		printk(KERN_DEBUG "[%d]%d iBCS: function 0x%lx not supported under personality %ld\n",
			id, current->pid,
			(unsigned long)regs->eax, (unsigned long)pers);
#else
		printk(KERN_DEBUG "%d iBCS: function 0x%lx not supported under personality %lu\n",
			current->pid,
			(unsigned long)regs->eax, (unsigned long)pers);
#endif
		regs->eflags |= 1; /* Set carry flag */
		regs->eax = iABI_errors(EINVAL);
#ifndef CONFIG_BINFMT_IBCS
		MOD_DEC_USE_COUNT;
#endif
		return;
	}

	/* And finally we can get the function definition */
	p = &(group_map[j][k]);
	ibcs_func_p = p;
	kfunc = p->kfunc;
	nargs = p->nargs;

	/* If it is a subgroup move the pointer to the subgroup, roll
	 * the function number down a byte and try again.
	 */
	if (nargs == Class) {
		group_map = (IBCS_func **)kfunc;
		i >>= 8;
		goto do_subgroup;
	}
	if (nargs == ISC) {
		group_map = (IBCS_func **)kfunc;
		i = get_fs_long(((unsigned long *)regs->esp)+1);
		of = 2;
		goto do_subgroup;
	}

	/* If the number of arguments is negative this is an unfudged
	 * system call function and we need to look up the real function
	 * address in the kernel's sys_call_table.
	 * Note that we never modify the callmap itself but do the lookup
	 * for each call. This allows modules that provide syscalls to
	 * be loaded and unloaded without any messy locking.
	 */
	if (nargs < 0) {
		kfunc = sys_call_table[(int)kfunc];

		/* Watch for a magic zero. This exists because we
		 * can't use -0 to represent a system call that
		 * takes no arguments.
		 */
		if (nargs == -ZERO)
			nargs = 0;
		else
			nargs = -nargs;
	}

	if (nargs <= sizeof(args)/sizeof(args[0]))
		for(i=0; i < nargs; i++)
			args[i] = get_fs_long(((unsigned long *)regs->esp)+(i+of));

#ifdef IBCS_TRACE
	if ((ibcs_trace & TRACE_API) || p->trace) {
		if (nargs == Spl) {
			for(i=0; i < strlen(p->args); i++)
				args[i] = get_fs_long(((unsigned long *) regs->esp) + (i+of));
		}
		plist(id, p->name, p->args, args);
	}
#endif

	rvalue = -ENOSYS;
	if (kfunc) {
		switch(nargs) {
		case Fast:
			((sysfun_p)kfunc)(regs);
#ifdef IBCS_TRACE
			if ((ibcs_trace & (TRACE_API|TRACE_SIGNAL))
			&& (current->signal & (~current->blocked))) {
				unsigned long signr = current->signal & (~current->blocked);

				__asm__("bsf %2,%1\n\t"
					:"=m" (current->signal),"=r" (signr)
					:"1" (signr));
				printk(KERN_DEBUG "[%d]%d SIGNAL %lu <%s>\n",
					id, current->pid,
					signr+1, sig_names[signr]);
			}
#endif
#ifndef CONFIG_BINFMT_IBCS
			MOD_DEC_USE_COUNT;
#endif
			return;
		case Spl:
			rvalue = ((sysfun_p)kfunc)(regs);
			break;
		case 0:
			rvalue = ((sysfun_p)kfunc)();
			break;
		case 1:
			rvalue = ((sysfun_p)kfunc)(args[0]);
			break;
		case 2:
			rvalue = ((sysfun_p)kfunc)(args[0], args[1]);
			break;
		case 3:
			rvalue = ((sysfun_p)kfunc)(args[0], args[1], args[2]);
			break;
		case 4:
			rvalue = ((sysfun_p)kfunc)(args[0], args[1], args[2], args[3]);
			break;
		case 5:
			rvalue = ((sysfun_p)kfunc)(args[0], args[1], args[2],
					     args[3], args[4]);
			break;
		case 6:
			rvalue = ((sysfun_p)kfunc)(args[0], args[1], args[2],
					     args[3], args[4], args[5]);
			break;
		default:
#ifdef IBCS_TRACE
			if ((ibcs_trace & TRACE_API) || p->trace)
				fail(id, regs->eax, p->name);
#else
			fail(regs->eax);
#endif
		}
	} else  {
#ifdef IBCS_TRACE
		if ((ibcs_trace & TRACE_API) || p->trace)
			fail(id, regs->eax, p->name);
#else
		fail(regs->eax);
#endif
	}
	
	if (rvalue >= 0) {
		regs->eflags &= ~1; /* Clear carry flag */
		regs->eax = rvalue;
#ifdef IBCS_TRACE
		if ((ibcs_trace & TRACE_API) || p->trace) {
			printk(KERN_DEBUG "[%d]%d %s returns %ld {%ld}\n",
				id, current->pid, p->name,
				regs->eax, regs->edx);
		}
#endif
	} else {
		regs->eflags |= 1; /* Set carry flag */
		regs->eax = iABI_errors(-rvalue);
#ifdef IBCS_TRACE
		if ((ibcs_trace & TRACE_API) || p->trace) {
			printk(KERN_DEBUG "[%d]%d %s error return "
#ifdef VERBOSE_ERRORS
				"linux=%d -> ibcs=%ld <%s>\n",
				id, current->pid, p->name,
				rvalue, regs->eax,
				-rvalue < sizeof(errmsg)/sizeof(errmsg[0])
					? errmsg[-rvalue]
					: "unknown");
#else
				"linux=%d -> ibcs=%ld\n",
				id, current->pid, p->name,
				rvalue, regs->eax);
#endif
		}
#endif
	}
#ifdef IBCS_TRACE
	if ((ibcs_trace & (TRACE_API|TRACE_SIGNAL))
	&& (current->signal & (~current->blocked))) {
		unsigned long signr = current->signal & (~current->blocked);

		__asm__("bsf %2,%1\n\t"
			:"=m" (current->signal),"=r" (signr)
			:"1" (signr));
		printk(KERN_DEBUG "[%d]%d SIGNAL %lu <%s>, queued 0x%08lx\n",
			id, current->pid, signr+1, sig_names[signr], current->signal);
	}
#endif
#ifndef CONFIG_BINFMT_IBCS
	MOD_DEC_USE_COUNT;
#endif
}

#ifndef CONFIG_BINFMT_IBCS
void ibcs_exit(int n)
{
	MOD_DEC_USE_COUNT;
	SYS(exit)(n);
}
#endif


int ibcs_syscall(struct pt_regs *regs) {
	regs->eax = get_fs_long(((unsigned long *) regs->esp) + 1);

	++regs->esp;
#ifndef CONFIG_BINFMT_IBCS
	iABI_emulate_real(regs);
#else
	iABI_emulate(regs);
#endif
	--regs->esp;

	return 0;
}

#ifdef IBCS_TRACE
int ibcs_trace_set(int arg)
{
	if (arg != -1) {
		ibcs_trace = arg;
		printk(KERN_DEBUG "iBCS: trace code set to 0x%x\n", ibcs_trace);
	}

	return ibcs_trace;
}

int ibcs_trace_func(int per, int func, int val)
{
	unsigned int	i, j, k;
	IBCS_func **group_map, *p;

	if (per > last(iBCS_personality_map)
	|| !(group_map = iBCS_personality_map[per]))
		return -EINVAL;

	i = func;

do_subgroup:
	j = (i & 0xff) >> 3;
	k = i & 0x07;
	if (!group_map[j]) {
		return -EINVAL;
	}

	/* And finally we can get the function definition */
	p = &(group_map[j][k]);

	/* If it is a subgroup move the pointer to the subgroup, roll
	 * the function number down a byte and try again.
	 */
	if (p->nargs == Class) {
		group_map = (IBCS_func **)p->kfunc;
		i >>= 8;
		goto do_subgroup;
	}

	if (val != -1)
		p->trace = val;

	return p->trace;
}

/*
 * plist is used by the trace code to show the arg list
 */
static void plist(int id, char *name, char *args, int *list) {
	int	error;
	char	*tmp, *p, arg_buf[512];

	arg_buf[0] = '\0';
	p = arg_buf;
	while (*args) {
		switch(*args++) {
		case 'd': sprintf(p, "%d", *list++);		break;
		case 'o': sprintf(p, "%o", *list++);		break;
		case 'p': sprintf(p, "%p", (void *)(*list++));	break;
		case '?': 
		case 'x': sprintf(p, "%x", *list++);		break;
		case 's': 
			error = getname((char *)(*list++),&tmp);
			if (!error) {
				/* we are debugging, we don't need to see it all */
				tmp[80] = '\0';
				sprintf(p, "\"%s\"", tmp);
				putname(tmp);
			}
			break;
		default:
			sprintf(p, "?%c%c?", '%', args[-1]);
			break;
		}
		while (*p) ++p;
		if (*args) {
			*p++ = ',';
			*p++ = ' ';
			*p = '\0';
		}
	}
	printk(KERN_DEBUG "[%d]%d %s(%s)\n",
		id, current->pid, name, arg_buf);
}

static void fail(int id, long eax, char *name) {
	printk(KERN_ERR "[%d]%d Unsupported iBSC2 function 0x%lx(%s)\n",
		id, current->pid, eax, name);
}
#else /* IBCS_TRACE */
static void fail(long eax) {
	printk(KERN_ERR "Unsupported iBCS function 0x%lx\n", eax);
}
#endif /* IBCS_TRACE */

#ifndef CONFIG_BINFMT_IBCS
extern struct linux_binfmt elf_format;
extern struct linux_binfmt coff_format;
extern struct linux_binfmt xout_format;

extern void (*iABI_hook)(struct pt_regs * regs);
extern int ibcs_lmapsig(int);
extern int (*ibcs_invmapsig)(int);


int init_module(void) {
#ifndef INIT_MM
	struct linux_binfmt *it;
#endif

	/* Register the socksys socket interface to streams based TCP/IP. */
	init_socksys();
	
#ifndef INIT_MM
	/* Connect the emulator. */
	iABI_hook = iABI_emulate_real;
	ibcs_invmapsig = ibcs_lmapsig;
#endif

	/* Install the COFF, ELF and XOUT loaders.
	 * N.B. We *rely* on the table being the right size with the
	 * right number of free slots...
	 */
#ifdef INIT_MM
	register_binfmt(&elf_format);
	register_binfmt(&coff_format);
	register_binfmt(&xout_format);
#else
	/* Install the COFF, ELF and XOUT loaders.
	 * N.B. We *rely* on the table being the right size with the
	 * right number of free slots...
	 */
	it = formats;
	while (it->load_binary) it++;
	*it = elf_format;
	while (it->load_binary) it++;
	*it = coff_format;
	while (it->load_binary) it++;
	*it = xout_format;
#endif
	return 0;
}

void cleanup_module( void) {
#ifndef INIT_MM
	struct linux_binfmt *it;
#endif
	
	if (MOD_IN_USE)
		printk(KERN_INFO "iBCS: module is in use, remove delayed\n");

#ifdef INIT_MM
	/* Remove the COFF and ELF loaders. */
	unregister_binfmt(&elf_format);
	unregister_binfmt(&coff_format);
	unregister_binfmt(&xout_format);
#else
	/* Remove the COFF and ELF loaders. */
	for (it = formats; it->load_binary; it++)
		if (it->load_binary == elf_format.load_binary
		|| it->load_binary == coff_format.load_binary
		|| it->load_binary == xout_format.load_binary) {
			it->load_binary = NULL;
			it->load_shlib = NULL;
		}

	/* Unhook the emulator. */
	ibcs_invmapsig = NULL;
	iABI_hook = NULL;
#endif

	/* Remove the socksys socket interface to streams based TCP/IP */
	cleanup_socksys();
}
#endif
