/*
 *  linux/ibcs/ipc.c
 *
 *  Copyright (C) 1993  Joe Portman (baron@hebron.connected.com)
 *	 First stab at ibcs shm, sem and msg handlers
 *
 * $Id: ipc.c,v 1.1.1.2 1994/02/23 17:22:42 mike Exp $
 * $Source: /var/CVS/ibcs/ipc.c,v $
 */
#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 <asm/segment.h>
#include <asm/system.h>
#include <linux/fs.h>
#include <linux/sys.h>
#include <linux/ipc.h>
#include <linux/sem.h>
#include <linux/shm.h>
#include <linux/string.h>

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

extern int sys_semop (int semid, struct sembuf *tsops, unsigned nsops);

extern int sys_shmat (int shmid, char *shmaddr, int shmflg, ulong * raddr);
extern int sys_shmdt (char *shmaddr);

/*
** I think this is what an IBCS semid_ds
** structure looks like
*/
struct ibcs_ipc_perm
  {
	 unsigned short uid;			  /* owner's user id */
	 unsigned short gid;			  /* owner's group id */
	 unsigned short cuid;		  /* creator's user id */
	 unsigned short cgid;		  /* creator's group id */
	 unsigned short mode;		  /* access modes */
	 unsigned short seq;			  /* slot usage sequence number */
	 long key;						  /* key */
  };

struct ibcs_semid_ds
  {
	 struct ibcs_ipc_perm sem_perm;
	 struct sem *sem_base;
	 unsigned short sem_nsems;
	 char __pad[2];
	 time_t sem_otime;
	 time_t sem_ctime;
  };


/*
** I think this is what an IBCS shmid_ds
** structure looks like
*/

struct ibcs_shmid_ds
  {
	 struct ibcs_ipc_perm shm_perm;	 /* operation permission struct */
	 int shm_segsz;				  /* size of segment in bytes */
	 struct region *__pad1;		  /* ptr to region structure */
	 char __pad2[4];				  /* for swap compatibility */
	 ushort shm_lpid;				  /* pid of last shmop */
	 ushort shm_cpid;				  /* pid of creator */
	 unsigned short shm_nattch;  /* used only for shminfo */
	 unsigned short __pad3;
	 time_t shm_atime;			  /* last shmat time */
	 time_t shm_dtime;			  /* last shmdt time */
	 time_t shm_ctime;			  /* last change time */
  };

/*
**	OK here we do some magic with shm and sem calls
**	SHM calls can have command + 1 or 3 args
**	SEM calls can have command + 3 or 4 args
*/
#define U_SEMCTL (0)
#define U_SEMGET (1)
#define U_SEMOP  (2)

#define U_GETNCNT (3)
#define U_GETPID  (4)
#define U_GETVAL	(5)
#define U_GETALL	(6)
#define U_GETZCNT	(7)
#define U_SETVAL	(8)
#define U_SETALL	(9)

static int
ibcs_sem_trans (int arg)
{
  switch (arg)
	 {
	 case U_GETNCNT:
		return GETNCNT;
	 case U_GETPID:
		return GETPID;
	 case U_GETVAL:
		return GETVAL;
	 case U_GETALL:
		return GETALL;
	 case U_GETZCNT:
		return GETZCNT;
	 case U_SETVAL:
		return SETVAL;
	 case U_SETALL:
		return SETALL;
	 }
  return arg;
}

int
ibcs_semsys (struct pt_regs *regs)
{
  int command = get_fs_long (((unsigned long *) regs->esp) + (1));
  int arg1, arg2, arg3, arg5;
  char *arg4;
  int retval;
  arg1 = get_fs_long (((unsigned long *) regs->esp) + (2));
  arg2 = get_fs_long (((unsigned long *) regs->esp) + (3));
  arg3 = get_fs_long (((unsigned long *) regs->esp) + (4));
  switch (command)
	 {
		/* hard one first */
	 case U_SEMCTL:
		arg4 = (int *) regs->esp + (5);
		arg5 = get_fs_long (((int *) regs->esp) + (5));
#ifdef IBCS_TRACE
		if ((ibcs_trace & TRACE_API) || ibcs_func_p->trace)
		  printk (KERN_DEBUG "iBCS: ibcs_semctl: args: %d %d %d %x(%d)\n",
			arg1, arg2, arg3, arg4, arg5);
#endif
		switch (ibcs_sem_trans (arg3))
		  {
		  case IPC_SET:
		  case IPC_RMID:
		  case SETVAL:
		  case GETVAL:
			 return sys_ipc (SEMCTL, arg1, arg2, ibcs_sem_trans (arg3), arg4);
		  case IPC_STAT:
			 retval = sys_ipc (SEMCTL, arg1, arg2, ibcs_sem_trans (arg3), arg4);
			 if (retval < 0)
				return retval;
			 else
				{
				  struct ibcs_semid_ds os;
				  struct semid_ds is;
				  memcpy_fromfs (&is, arg4, sizeof (is));
				  memset (&os, 0, sizeof (os));
				  memcpy (&os.sem_perm, &is.sem_perm, sizeof (is.sem_perm));
				  os.sem_otime = is.sem_otime;
				  os.sem_ctime = is.sem_ctime;
				  os.sem_base = is.sem_base;
				  os.sem_nsems = is.sem_nsems;
				  memcpy_tofs (arg4, &os, sizeof (os));
				  return retval;
				}

		  default:
			 printk (KERN_ERR "ibcs_semctl: unsupported command %d\n",
				arg3);
		  }

	 case U_SEMGET:
#ifdef IBCS_TRACE
		if ((ibcs_trace & TRACE_API) || ibcs_func_p->trace)
		  printk (KERN_DEBUG "iBCS: ibcs_semget: args: %d %d %o \n",
			arg1, arg2, arg3);
#endif
		return sys_ipc (SEMGET, arg1, arg2, arg3);
	 case U_SEMOP:
#ifdef IBCS_TRACE
		if ((ibcs_trace & TRACE_API) || ibcs_func_p->trace)
		  {
			 printk (KERN_DEBUG "iBCS: ibcs_semop: args: %d %d %o \n",
				arg1, arg2, arg3);
			 {
				int x;
				struct sembuf tmp;
				struct sembuf *tp = (struct sembuf *) arg2;
				for (x = 0; x < arg3; x++)
				  {
					 memcpy_fromfs (&tmp, tp, sizeof (tmp));
					 printk (KERN_DEBUG "iBCS: ibcs_semop args: %d %d %o \n",
						tmp.sem_num, tmp.sem_op, tmp.sem_flg);
					 tp++;
				  }
			 }
		  }
#endif
		return sys_semop (arg1, (struct sembuf *) arg2, arg3);
	 }
  return -EINVAL;
}

/*
**	start with the easy stuff
*/
#define U_SHMAT  (0)
#define U_SHMCTL (1)
#define U_SHMDT  (2)
#define U_SHMGET (3)
/*
** modified to set the flags directly
** does not return to emulate
*/
int
ibcs_shmsys (struct pt_regs *regs)
{
  int command = get_fs_long (((unsigned long *) regs->esp) + (1));
  int arg1, arg2, arg3;
  long retval = 0;
  char *addr;
  int old_fs;
  struct shmid_ds is;
  switch (command)
	 {
	 case U_SHMAT:
	 case U_SHMCTL:
	 case U_SHMGET:
		arg1 = get_fs_long (((unsigned long *) regs->esp) + (2));
		arg2 = get_fs_long (((unsigned long *) regs->esp) + (3));
		arg3 = get_fs_long (((unsigned long *) regs->esp) + (4));
		break;
	 case U_SHMDT:
		addr = (char *) get_fs_long (((unsigned long *) regs->esp) + (2));
		break;
	 default:
		printk (KERN_ERR "iBCS: bad SHM command %d\n", command);
		retval = -EINVAL;
		goto test_exit;
	 }

  switch (command)
	 {

	 case U_SHMAT:
#ifdef IBCS_TRACE
		if ((ibcs_trace & TRACE_API) || ibcs_func_p->trace)
		  printk (KERN_DEBUG "iBCS: ibcs_shmat: args: %d %x %o \n",
			arg1, arg2, arg3);
#endif
		/*
			** raddr = 0 tells sys_shmat to limit to 2G
			**	and we are IBCS, no raddr value to return
			*/
		retval = sys_shmat (arg1, (char *) arg2, arg3, 0);

#ifdef IBCS_TRACE
		if ((ibcs_trace & TRACE_API) || ibcs_func_p->trace)
		  printk (KERN_DEBUG "iBCS: ibcs_shmat: return val is %x\n",
			retval);
#endif
		goto test_exit;

	 case U_SHMGET:
#ifdef IBCS_TRACE
		if ((ibcs_trace & TRACE_API) || ibcs_func_p->trace)
		  printk (KERN_DEBUG "iBCS: ibcs_shmget: args: %d %x %o \n",
			arg1, arg2, arg3);
#endif
		retval = sys_ipc (SHMGET, arg1, arg2, arg3);
		goto test_exit;

	 case U_SHMDT:
#ifdef IBCS_TRACE
		if ((ibcs_trace & TRACE_API) || ibcs_func_p->trace)
		  printk (KERN_DEBUG "iBCS: ibcs_shmdt: arg: %d\n", addr);
#endif
		retval = sys_shmdt (addr);
		goto test_exit;

	 case U_SHMCTL:
#ifdef IBCS_TRACE
		if ((ibcs_trace & TRACE_API) || ibcs_func_p->trace)
		  printk (KERN_DEBUG "iBCS: ibcs_shmctl: args: %d %x %o %d %x\n",
			arg1, arg2, arg3, arg3, arg3);
#endif
		switch (arg2)
		  {
		  case IPC_RMID:
			 retval = (sys_ipc (SHMCTL, arg1, arg2, arg3));
			 goto test_exit;
		  case IPC_STAT:
          old_fs = get_fs();
	       set_fs(get_ds());
          
			 /* retval = sys_ipc (SHMCTL, arg1, arg2, arg3); */
			 retval = sys_ipc (SHMCTL, arg1, arg2, &is);
          set_fs( old_fs);
			 if (retval < 0)
				goto test_exit;
			 else
				{
				  struct ibcs_shmid_ds os;
				  memset (&os, 0, sizeof (os));
				  memcpy (&os.shm_perm, &is.shm_perm, sizeof (is.shm_perm));
				  os.shm_segsz = is.shm_segsz;
				  os.shm_lpid = is.shm_lpid;
				  os.shm_cpid = is.shm_cpid;
				  os.shm_nattch = is.shm_nattch;
				  os.shm_atime = is.shm_atime;
				  os.shm_dtime = is.shm_dtime;
				  os.shm_ctime = is.shm_ctime;
				  memcpy_tofs (arg3, &os, sizeof (os));
				  goto test_exit;
				}
		  default:
			 printk (KERN_ERR "iBCS: ibcs_shmctl: unsupported command %d\n",
				arg2);
		  }
		retval = -EINVAL;
		goto test_exit;

	 default:
#ifdef IBCS_TRACE
		printk (KERN_DEBUG "iBCS: ibcs_shm: command: %x\n", command);
#endif
		retval = -EINVAL;
		goto test_exit;
	 }

test_exit:;

  if ((retval < 0) && (retval > -255))
	 {
		regs->eflags |= 1;		  /* set carry flag */
		regs->eax = iABI_errors(-retval);
#ifdef IBCS_TRACE
		if ((ibcs_trace & TRACE_API) || ibcs_func_p->trace)
		  printk (KERN_DEBUG "iBCS: Error %d\n", regs->eax);
#endif
	 }
  else
	 {
		regs->eflags &= ~1;		  /* Clear carry flag */
		regs->eax = retval;
	 }

  return 0;
}
