/*
 *  linux/ibcs/ioctl.c
 *
 *  Copyright (C) 1991, 1992  Linus Torvalds
 *
 *  Writtin by Drew Sullivan.
 *
 * $Id: ioctl.c,v 1.13 1994/05/23 07:58:51 mike Exp $
 * $Source: /var/CVS/ibcs/ioctl.c,v $
 */
#include <linux/errno.h>
#include <linux/stat.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/termios.h>
#include <linux/time.h>
#include <asm/segment.h>

#include <ibcs/ibcs.h>
#include <ibcs/trace.h>

static int ibcs_ioctl_termios(int fd, unsigned int func, void *arg);
static int ibcs_ioctl_termio(int fd, unsigned int func, void *arg);
static int ibcs_ioctl_console(int fd, unsigned int func, void *arg);
static int ibcs_ioctl_video(int fd, unsigned int func, void *arg);

static char *fix(int n) {
	static char char_class[4];
	
	 char_class[0] = n & 0xFF0000 ? (char)((n >> 16) & 0xFF) : '.';
	 char_class[1] = n & 0x00FF00 ? (char)((n >>  8) & 0xFF) : '.';
	 char_class[2] = n & 0x0000FF ? (char)((n      ) & 0xFF) : '.';
	 char_class[3] = 0;

	return char_class;
}

/* ibcs_ioctl is a meta mapper, that is
   it looks up the class of the ioctl and then
   dispatchs to lower level routines to handle the
   mapping of the actual ioctls
*/

#ifdef __cplusplus
extern "C" 
#endif

int ibcs_ioctl(int fd, unsigned int ioctl_num, void *arg) 
{
	unsigned int class = ioctl_num >> 8;

	switch (class) {
		case 'A': /* ??? SCO console keyboard stuff? */
			return -EINVAL;

		case 'f':
			switch (ioctl_num & 0xff) {
				case 1: /* FIOCLEX */
					FD_SET(fd, &current->FDI(close_on_exec));
					return 0;
				case 2: /* FIONCLEX */
					FD_CLR(fd, &current->FDI(close_on_exec));
					return 0;
			}
			break;

		case 'T':	/* xenix ioctl compatibility */
			return ibcs_ioctl_termio(fd, ioctl_num & 0xFF, arg);

		case ('i' << 16) | ('X' << 8):	/* iBCS2 POSIX */
		case 'x':	/* Pre-iBCS2 POSIX */
			return ibcs_ioctl_termios(fd, ioctl_num & 0xFF, arg);

		case 'C':
			return ibcs_ioctl_console(fd, ioctl_num & 0xFF, arg);

		case ('i' << 16) | ('C' << 8):	/* iBCS2 POSIX */
		case 'X':
			return ibcs_ioctl_video(fd, ioctl_num & 0xFF, arg);

		/* These aren't implemented and are never likely to be as they
		 * are specific to drivers for obscure hardware. (For those
		 * that don't know they're the JERQ ioctls. Says it all
		 * really!)
		 */
		case 'j':
			return -EINVAL;
	}

	/* If we haven't handled it yet it must be a BSD style ioctl
	 * with an argument description in the high word of the opcode.
	 */
	switch (class & 0xff) {
		/* From SVR4 as specified in sys/iocomm.h */
		case 'f':
			switch (ioctl_num) {
				case 0x4004667f:
					return SYS(ioctl)(fd, FIONREAD, arg);
			}
			break;

		/* Wyse V/386 3.2.1A TCP/IP ioctls. */
		case 's':
		case 'r':
		case 'i':
			return wv386_ioctl(fd, ioctl_num, arg);

		/* SVR3 streams based socket TCP/IP ioctls.
		 * These are handed over to the standard ioctl
		 * handler since /dev/socksys is an emulated device
		 * and front ends any sockets created through it.
		 */
		case 'S':
		case 'R':
		case 'I':
			return SYS(ioctl)(fd, ioctl_num, arg);
	}

	/* If nothing has handled it yet someone may have to do some
	 * more work...
	 */
	printk(KERN_ERR "iBCS: ioctl(%d, %x[%s], 0x%lx) unsupported\n",
		fd, ioctl_num, fix(class), (unsigned long)arg);

	return -EINVAL;
}


struct svr_termio {
	unsigned short c_iflag;
	unsigned short c_oflag;
	unsigned short c_cflag;
	unsigned short c_lflag;
	char c_line;
	unsigned char c_cc[8];
};

struct svr_termios {
	unsigned short c_iflag;
	unsigned short c_oflag;
	unsigned short c_cflag;
	unsigned short c_lflag;
	char c_line;
	unsigned char c_cc[8];
	char c_ispeed;
	char c_ospeed;
};

static int svr_to_linux_termio(int fd, int op, struct svr_termio *it)
{
	struct termio t;
	int old_fs;
	char eof;
	unsigned short lflag;
	int error;

	error = verify_area(VERIFY_READ, it, sizeof(struct svr_termio));
	if (error)
		return error;

	old_fs = get_fs();
	set_fs(get_ds());
	error = SYS(ioctl)(fd, TCGETA, &t);
	set_fs(old_fs);
	if (error)
		return error;

	/* Save things we may need later. */
	eof = t.c_cc[4];
	lflag = t.c_lflag;

	/* Copy the entire structure then fix up as necessary. */
	memcpy_fromfs(&t, it, sizeof(struct svr_termio));

	/* If ICANON isn't set then we've been given VMIN in place
	 * of VEOF.
	 */
	if (!(t.c_lflag & 0000002)) {
		t.c_cc[6] = t.c_cc[4];
		t.c_cc[4] = eof;
	}

	if (t.c_cflag & 0100000) /* CRTSFL - SCO only? */
		t.c_cflag |= CRTSCTS;
	t.c_cflag &= ~0170000; /* LOBLK|CTSFLOW|RTSFLOW|CRTSFL */

	set_fs(get_ds());
	error = SYS(ioctl)(fd, op, &t);
	set_fs(old_fs);

	return error;
}

static int linux_to_svr_termio(int fd, int op, struct svr_termio *it)
{
	struct termio t;
	int old_fs;
	int error;

	error = verify_area(VERIFY_WRITE, it, sizeof(struct svr_termio));
	if (error)
		return error;

	old_fs = get_fs();
	set_fs(get_ds());
	error = SYS(ioctl)(fd, op, &t);
	set_fs(old_fs);
	if (error)
		return error;

	/* If ICANON isn't set then we substitute VEOF with VMIN. */
	if (!(t.c_lflag & 0000002)) {
		t.c_cc[4] = t.c_cc[6];
	}

	/* Copy to the user supplied structure. */
	memcpy_tofs(it, &t, sizeof(struct svr_termio));

	return error;
}


static int svr_to_linux_termios(int fd, int op, struct svr_termios *it)
{
	struct termios t;
	int old_fs;
	unsigned short lflag;
	char svr_cc[13];
	int error;

	error = verify_area(VERIFY_READ, it, sizeof(struct svr_termios));
	if (error)
		return error;

	old_fs = get_fs();
	set_fs(get_ds());
	error = SYS(ioctl)(fd, TCGETS, &t);
	set_fs(old_fs);
	if (error)
		return error;

	t.c_iflag = get_fs_long(&it->c_iflag);
	t.c_iflag &= ~0100000; /* DOSMODE */

	t.c_oflag = get_fs_long(&it->c_oflag);

	t.c_cflag = get_fs_long(&it->c_cflag);
	if (t.c_cflag & 0100000) /* CRTSFL - SCO only? */
		t.c_cflag |= CRTSCTS;
	t.c_cflag &= ~0170000; /* LOBLK|CTSFLOW|RTSFLOW|CRTSFL */

	lflag = t.c_lflag;
	t.c_lflag &= ~0100777;
	t.c_lflag |= get_fs_long(&it->c_lflag);
	if ((t.c_lflag & 0100000))
		SYS(ioctl)(fd, TIOCEXCL, 0);
	else
		SYS(ioctl)(fd, TIOCNXCL, 0);
	t.c_lflag &= ~0100000;
	t.c_lflag |= (t.c_lflag & 0000400) << 7; /* Move IEXTEN */
	t.c_lflag &= ~0000400;
	t.c_lflag |= (t.c_lflag & 0001000) >> 1; /* Move TOSTOP */
	t.c_lflag &= ~0001000;
	t.c_lflag |= (lflag & 0001000); /* Restore ECHOCTL */

	t.c_line = get_fs_byte(&it->c_line); /* XXX Map this? */

	memcpy_fromfs(svr_cc, &it->c_cc, 13);
	t.c_cc[0] = svr_cc[0];
	t.c_cc[1] = svr_cc[1];
	t.c_cc[2] = svr_cc[2];
	t.c_cc[3] = svr_cc[3];
	t.c_cc[7] = svr_cc[7];
	t.c_cc[8] = svr_cc[11];
	t.c_cc[9] = svr_cc[12];
	t.c_cc[10] = svr_cc[10];
	t.c_cc[16] = svr_cc[16];
	if (t.c_lflag & ICANON) {
		t.c_cc[4] = svr_cc[4];
		t.c_cc[11] = svr_cc[5];
	} else {
		t.c_cc[4] = svr_cc[8];
		t.c_cc[5] = svr_cc[5];
		t.c_cc[6] = svr_cc[4];
		t.c_cc[11] = svr_cc[9];
	}

	set_fs(get_ds());
	error = SYS(ioctl)(fd, op, &t);
	set_fs(old_fs);

	return error;
}

static int linux_to_svr_termios(int fd, int op, struct svr_termios *it)
{
	struct termios t;
	char svr_cc[13];
	int old_fs;
	int error;

	error = verify_area(VERIFY_WRITE, it, sizeof(struct svr_termios));
	if (error)
		return error;

	old_fs = get_fs();
	set_fs(get_ds());
	error = SYS(ioctl)(fd, op, &t);
	set_fs(old_fs);
	if (error)
		return error;

	put_fs_word(t.c_iflag & 0017777, &it->c_iflag);

	put_fs_word(t.c_oflag & 0177777, &it->c_oflag);

	if (t.c_cflag & CRTSCTS)
		t.c_cflag |= 0100000; /* CRTSFL - SCO only? */
	put_fs_word(t.c_cflag & 0177777, &it->c_cflag);

	t.c_lflag &= ~0001000;
	t.c_lflag |= (t.c_lflag & 0000400) << 1;
	t.c_lflag &= ~0000400;
	t.c_lflag |= (t.c_lflag & 0100000) >> 7;
	t.c_lflag &= ~0100000;
	put_fs_word(t.c_lflag & 0001777, &it->c_lflag);

	put_fs_byte(t.c_line, &it->c_line); /* XXX Map this? */

	svr_cc[0] = t.c_cc[0];
	svr_cc[1] = t.c_cc[1];
	svr_cc[2] = t.c_cc[2];
	svr_cc[3] = t.c_cc[3];
	svr_cc[6] = t.c_cc[16];
	svr_cc[7] = t.c_cc[7];
	svr_cc[8] = t.c_cc[4];
	svr_cc[9] = t.c_cc[11];
	svr_cc[10] = t.c_cc[10];
	svr_cc[11] = t.c_cc[8];
	svr_cc[12] = t.c_cc[9];
	if (t.c_lflag & ICANON) {
		svr_cc[4] = t.c_cc[4];
		svr_cc[5] = t.c_cc[11];
	} else {
		svr_cc[4] = t.c_cc[6];
		svr_cc[5] = t.c_cc[5];
	}

	memcpy_tofs(&it->c_cc, svr_cc, 13);

	return error;
}

static int ibcs_ioctl_termios(int fd, unsigned int func, void *arg) {
	switch(func) {
	case 1:	/* XCGETA */
		return linux_to_svr_termios(fd, TCGETS, arg);
	case 2: /* XCSETA */
		return svr_to_linux_termios(fd, TCSETS, arg);
	case 3: /* XCSETAW */
		return svr_to_linux_termios(fd, TCSETSW, arg);
	case 4: /* XCSETAF */
		return svr_to_linux_termios(fd, TCSETSF, arg);
	}
	printk(KERN_ERR "iBCS: termios ioctl %d unsupported\n", func);
	return -EINVAL;
}

static int ibcs_ioctl_termio(int fd, unsigned int func, void *arg) {
	switch(func) {
		case 1: /* TCGETA  (TIOC|1) */
			return linux_to_svr_termio(fd, TCGETA, arg);

		case 2: /* TCSETA  (TIOC|2) */
			return svr_to_linux_termio(fd, TCSETA, arg);
		case 3: /* TCSETAW (TIOC|3) */
			return svr_to_linux_termio(fd, TCSETAW, arg);
		case 4: /* TCSETAF (TIOC|4) */
			return svr_to_linux_termio(fd, TCSETAF, arg);

		case 5: /* TCSBRK  (TIOC|5) */
			return SYS(ioctl)(fd, TCSBRK, arg);
		case 6: /* TCXONC  (TIOC|6) */
			return SYS(ioctl)(fd, TCXONC, arg);
		case 7: /* TCFLSH  (TIOC|7) */
			return SYS(ioctl)(fd, TCFLSH, arg);

		/* These two are specific to ISC. */
		case 20: /* TCSETPGRP  (TIOC|20) set pgrp of tty */
			return SYS(ioctl)(fd, TIOCSPGRP, arg);
		case 21: /* TCGETPGRP  (TIOC|21) get pgrp of tty */
			return SYS(ioctl)(fd, TIOCGPGRP, arg);

		case  34: /* TCGETSC (TIOC|34) ioctl for scancodes */
			return 0x04; /* Translates scancode to ascii */
		case  35: /* TCSETSC (TIOC|35) ioctl for scancodes */
			return 0;

		case 103: /* TIOCSWINSZ (TIOC|103) */
			return SYS(ioctl)(fd, TIOCSWINSZ, arg);
		case 104: /* TIOCGWINSZ (TIOC|104) */
			return SYS(ioctl)(fd, TIOCGWINSZ, arg);

		case 118: /* TIOCSPGRP  (TIOC|118) set pgrp of tty */
			return SYS(ioctl)(fd, TIOCSPGRP, arg);
		case 119: /* TIOCGPGRP  (TIOC|119) get pgrp of tty */
			return SYS(ioctl)(fd, TIOCGPGRP, arg);

		case  32: /* TCDSET  (TIOC|32) */
		case  33: /* RTS_TOG (TIOC|33) 386 - "RTS" toggle define 8A1 protocol */

		case 120: /* TIOSETSAK  (TIOC|120) set SAK sequence for tty */
		case 121: /* TIOGETSAK  (TIOC|121) get SAK sequence for tty */
			printk(KERN_ERR "iBCS: termio ioctl %d unimplemented\n",
				func);
			return -EINVAL;
	}
	printk(KERN_ERR "iBCS: termio ioctl %d unsupported\n", func);
	return -EINVAL;
}

static int ibcs_ioctl_console(int fd, unsigned int func, void *arg) {
	switch(func) {
		case 4: /* _TTYDEVTYPE */
			/* ***BUG*** if on console then 1, if pseudo tty
			 * then 2
			 */
			return 1;
	}
	printk(KERN_ERR "iBCS: console ioctl %d unsupported\n", func);
	return -EINVAL;
}

static int ibcs_ioctl_video(int fd, unsigned int func, void *arg) {
	switch(func) {
	case 4: /* C_IOC */
		/* get video memory map & IO privilege */
		/* see /etc/conf/pack.d/cn/class.h on any SCO unix box :-) */
	}
	printk(KERN_ERR "iBCS: video ioctl %d unsupported\n", func);
	return -EINVAL;
}
