/*
 * nasd_linux_sys.c
 *
 * In-kernel NASD system support for Linux
 *
 * Authors: Sean Levy, Jim Zelenka, Marc Unangst
 */
/*
 * Copyright (c) of Carnegie Mellon University, 1996,1997,1998,1999.
 *
 * Permission to reproduce, use, and prepare derivative works of
 * this software for internal use is granted provided the copyright
 * and "No Warranty" statements are included with all reproductions
 * and derivative works. This software may also be redistributed
 * without charge provided that the copyright and "No Warranty"
 * statements are included in all redistributions.
 *
 * NO WARRANTY. THIS SOFTWARE IS FURNISHED ON AN "AS IS" BASIS.
 * CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY KIND, EITHER
 * EXPRESSED OR IMPLIED AS TO THE MATTER INCLUDING, BUT NOT LIMITED
 * TO: WARRANTY OF FITNESS FOR PURPOSE OR MERCHANTABILITY, EXCLUSIVITY
 * OF RESULTS OR RESULTS OBTAINED FROM USE OF THIS SOFTWARE. CARNEGIE
 * MELLON UNIVERSITY DOES NOT MAKE ANY WARRANTY OF ANY KIND WITH RESPECT
 * TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.
 */


#define __KERNEL_SYSCALLS__
#include <nasd/nasd_options.h>
#include <nasd/nasd_types.h>
#include <nasd/nasd_mem.h>
#include <nasd/nasd_threadstuff.h>
#include <nasd/nasd_timer.h>
#include <nasd/nasd_timeout.h>
#include <nasd/nasd_common.h>
#include <nasd/nasd_sys.h>
#include <nasd/nasd_security.h>

#undef asm

#include <linux/config.h>
#include <linux/version.h>
#include <linux/types.h>
#include <linux/malloc.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <linux/smp.h>
#include <linux/smp_lock.h>
#include <linux/errno.h>
#include <linux/ptrace.h>
#include <linux/user.h>
#include <linux/utime.h>
#include <linux/time.h>
#include <linux/kdev_t.h>
#include <linux/wait.h>
#include <linux/locks.h>
#include <linux/file.h>
#include <linux/kmod.h>
#include <linux/unistd.h>

#include <asm/uaccess.h>
#include <asm/io.h>
#include <asm/unistd.h>

static inline _syscall0(int,sched_yield)
static inline _syscall2(int,mlock,void *,addr,size_t,len);
static inline _syscall2(int,munlock,void *,addr,size_t,len);
static int errno; /* burn in hell, me, yep */

int (*nasd_linux_drive_launch)(nasd_svinfo_t *, void *) = NULL;
nasd_svinfo_t *nasd_sva = NULL;

int (*nasd_linux_edrfs_launch)(nasd_edrfs_svinfo_t *) = NULL;
nasd_edrfs_svinfo_t *nasd_edrfs_sva = NULL;

int nasd_linux_kpdev_support = 0;
int nasd_linux_drive_colocates = 0;

#define nasd_page_base(_addr_)   (((unsigned long)(_addr_))&(~(PAGE_SIZE-1)))
#define nasd_page_offset(_addr_) (((unsigned long)(_addr_))&(PAGE_SIZE-1))
#define nasd_page_atomic(_addr_,_len_) \
  (nasd_page_base(_addr_) == nasd_page_base(((unsigned long)(_addr_))+(_len_)))

void
nasd_linux_set_kpdev_support(
  int  support)
{
  NASD_D_LOCK();
  nasd_linux_kpdev_support = support;
  NASD_D_UNLOCK();
}

void
nasd_linux_set_drive_launch(
  int  (*launch)(nasd_svinfo_t *, void *),
  int    does_colocate)
{
  NASD_D_LOCK();
  nasd_linux_drive_launch = launch;
  if (launch == NULL) {
    NASD_ASSERT(does_colocate == 0);
  }
  nasd_linux_drive_colocates = does_colocate;
  NASD_D_UNLOCK();
}

void
nasd_linux_set_edrfs_launch(
  int  (*launch)(nasd_edrfs_svinfo_t *))
{
  NASD_D_LOCK();
  nasd_linux_edrfs_launch = launch;
  NASD_D_UNLOCK();
}

void
nasd_linux_kinit_once()
{
  nasd_sva = NULL;
  nasd_linux_drive_launch = NULL;
}

asmlinkage int nasd_srv(int cmd, void *arg)
{
  nasd_timeout_test_t timeout_test;
  struct nasd_edrfs_mount *edrfsmounts;
  struct nasd_edrfs_config edrfscfg;
  struct nasd_serv nasdsrv;
  nasd_options_t opts;
  nasd_status_t rc;
  int rez, ret;

  switch (cmd) {
  case NASD_SC_GET_OPTIONS:
    NASD_D_LOCK();
    bzero((char *)&opts, sizeof(opts));
#if NASD_SECURE_RPCS_ENABLE > 0
    opts.opts1 |= NASD_OPTS1_SECURITY;
#endif /* NASD_SECURE_RPCS_ENABLE > 0 */
    /*opts.opts1 |= NASD_OPTS1_EDRFS_CLIENT;*/
    if (nasd_linux_edrfs_launch != NULL) {
      opts.opts1 |= NASD_OPTS1_EDRFS_FM;
    }
    if (nasd_linux_drive_launch != NULL) {
      opts.opts1 |= NASD_OPTS1_DRIVE;
      if (nasd_linux_drive_colocates) {
        opts.opts_rpc |= NASD_OPTS_RPC_COLOCATE;
      }
    }
    if (nasd_linux_kpdev_support) {
      opts.opts1 |= NASD_OPTS1_KPDEV;
    }
#if NASD_RPC_PACKAGE == NASD_RPC_PACKAGE_DCE
    opts.opts_rpc |= NASD_OPTS_RPC_DCE;
#endif /* NASD_RPC_PACKAGE == NASD_RPC_PACKAGE_DCE */
#if NASD_RPC_PACKAGE == NASD_RPC_PACKAGE_SRPC
    opts.opts_rpc |= NASD_OPTS_RPC_SRPC;
#endif /* NASD_RPC_PACKAGE == NASD_RPC_PACKAGE_SRPC */
    opts.opts_seclevel = NASD_MAX_SECURITY_LEVEL;
    NASD_D_UNLOCK();
    ret = nasd_sys_copyout(&opts, arg, sizeof(opts));
    rez = -ret;
    break;

    case NASD_SC_TIMEOUT_TEST:
      ret = nasd_sys_copyin(arg, (caddr_t)&timeout_test,
        sizeof(timeout_test));
      if (ret) {
        rez = -ret;
        break;
      }
#if NASD_TIMEOUT_TEST > 0
      rc = nasd_timeout_test(timeout_test.delta);
#else /* NASD_TIMEOUT_TEST > 0 */
      rc = NASD_OP_NOT_SUPPORTED;
#endif /* NASD_TIMEOUT_TEST > 0 */
      ret = nasd_sys_copyout((caddr_t)&timeout_test, (caddr_t)arg,
        sizeof(timeout_test));
      rez = -ret;
      break;

  case NASD_SC_PSRV_GO:
    NASD_D_LOCK();
    if (nasd_linux_drive_launch != NULL) {
      if (nasd_sva != NULL) {
        rez = EADDRINUSE;
        goto psrv_done;
      }
      if (arg == NULL) {
        rez = -EFAULT;
        goto psrv_done;
      } else {
        rez = nasd_sys_copyin(arg, &nasdsrv, sizeof(nasdsrv));
        if(rez) {
          rez = -rez;
          goto psrv_done;
        }
      }
      rc = nasd_threads_init();
      if (rc) {
        rez = -EIO;
        goto psrv_done;
      }
      rc = nasd_mem_init();
      if (rc) {
        rez = -EIO;
        nasd_threads_shutdown();
        goto psrv_done;
      }
      NASD_Malloc(nasd_sva, sizeof(nasd_svinfo_t),
                  (nasd_svinfo_t *));
      if (nasd_sva == NULL) {
        rez = -ENOMEM;
        nasd_mem_shutdown();
        nasd_threads_shutdown();
        goto psrv_done;
      }
      bcopy((char *)&nasdsrv, (char *)&nasd_sva->srv,
            sizeof(nasdsrv));

      /* !!! consumes D_LOCK */
      rez = nasd_linux_drive_launch(nasd_sva, arg);

      NASD_D_LOCK();

      NASD_Free(nasd_sva, sizeof(nasd_svinfo_t));
      nasd_sva = NULL;

      nasd_mem_shutdown();
      nasd_threads_shutdown();

      if (nasdsrv.verbose) {
        nasd_printf("DRIVE: done shutdown\n");
#if NASD_MEM_COUNT_ALLOC > 0
        nasd_printf("DRIVE: %" NASD_MEMALLOC_FMT
                    " bytes of memory still outstanding\n",
                    nasd_mem_allocated);
#endif /* NASD_MEM_COUNT_ALLOC > 0 */
      }
    }
    else {
      rez = -ENOSYS;
    }
  psrv_done:
    NASD_D_UNLOCK();
    break;


  case NASD_SC_SRV_PANIC:
    NASD_PANIC();
    break;


  case NASD_SC_EDRFS_SRV_GO:
    NASD_D_LOCK();
    if (nasd_linux_edrfs_launch != NULL) {
      if (nasd_edrfs_sva != NULL) {
#if NASD_DEBUG_SRV_ERRS > 0
        nasd_printf("EDRFS_SRV_GO: already running\n");
#endif
        rez = -EADDRINUSE;
        goto edrfs_srv_done;
      }
      if (arg == NULL) {
#if NASD_DEBUG_SRV_ERRS > 0
        nasd_printf("EDRFS_SRV_GO: arg==NULL\n");
#endif
        rez = -EFAULT;
        goto edrfs_srv_done;
      } else {
        rez = nasd_sys_copyin(arg, &edrfscfg, sizeof(edrfscfg));
        if(rez) {
          rez = -rez;
          goto edrfs_srv_done;
        }
      }
      if (edrfscfg.mount_array_len <= 0) {
#if NASD_DEBUG_SRV_ERRS > 0
        nasd_printf("EDRFS_SRV_GO: mount_array_len==%d (<=0)\n",
                    edrfscfg.mount_array_len);
#endif
        rez = -EINVAL;
        goto edrfs_srv_done;
      }
      rc = nasd_threads_init();
      if (rc) {
        rez = -EIO;
        goto edrfs_srv_done;
      }
      rc = nasd_mem_init();
      if (rc) {
        rez = -EIO;
        nasd_threads_shutdown();
        goto edrfs_srv_done;
      }
      NASD_Malloc(nasd_edrfs_sva, sizeof(nasd_edrfs_svinfo_t),
                  (nasd_edrfs_svinfo_t *));
      if (nasd_edrfs_sva == NULL) {
        rez = -ENOMEM;
        nasd_mem_shutdown();
        nasd_threads_shutdown();
        goto edrfs_srv_done;
      }
      NASD_Malloc(edrfsmounts,
                  sizeof(struct nasd_edrfs_mount)*edrfscfg.mount_array_len,
                  (struct nasd_edrfs_mount *));
      if (edrfsmounts == NULL) {
        NASD_Free(nasd_edrfs_sva, sizeof(nasd_edrfs_svinfo_t));
        nasd_edrfs_sva = NULL;
        rez = -ENOMEM;
        nasd_mem_shutdown();
        nasd_threads_shutdown();
        goto edrfs_srv_done;
      }
      bcopy((char *)&edrfscfg, (char *)&nasd_edrfs_sva->config,
            sizeof(struct nasd_edrfs_config));
      nasd_edrfs_sva->config.mount_array = NULL;
      if (edrfscfg.mount_array == NULL) {
        NASD_Free(edrfsmounts,
                  sizeof(struct nasd_edrfs_mount)*edrfscfg.mount_array_len);
        NASD_Free(nasd_edrfs_sva, sizeof(nasd_edrfs_svinfo_t));
        nasd_edrfs_sva = NULL;
#if NASD_DEBUG_SRV_ERRS > 0
        nasd_printf("EDRFS_SRV_GO: mount_array==NULL\n");
#endif
        rez = -EFAULT;
        nasd_mem_shutdown();
        nasd_threads_shutdown();
        goto edrfs_srv_done;
      } else {
        rez = nasd_sys_copyin(edrfscfg.mount_array, edrfsmounts,
                              sizeof(struct nasd_edrfs_mount)*edrfscfg.mount_array_len);
        if(rez) {
          NASD_Free(edrfsmounts,
                    sizeof(struct nasd_edrfs_mount)*edrfscfg.mount_array_len);
          NASD_Free(nasd_edrfs_sva, sizeof(nasd_edrfs_svinfo_t));
          nasd_edrfs_sva = NULL;
#if NASD_DEBUG_SRV_ERRS > 0
          nasd_printf("EDRFS_SRV_GO: could not copyin mount_array=0x%x\n",
                      (unsigned int) edrfscfg.mount_array);
#endif
          rez = -rez;
          nasd_mem_shutdown();
          nasd_threads_shutdown();
          goto edrfs_srv_done;
        }
      }
      nasd_edrfs_sva->config.mount_array = edrfsmounts;

      /* !!! consumes D_LOCK and frees edrfsmounts and nasd_edrfs_sva */
      rez = nasd_linux_edrfs_launch(nasd_edrfs_sva);

      NASD_D_LOCK();

      nasd_mem_shutdown();
      nasd_threads_shutdown();

      if (nasdsrv.verbose) {
        nasd_printf("EDRFS: done shutdown\n");
#if NASD_MEM_COUNT_ALLOC > 0
        /* note that this should not necessarily be 0, since the drive is
           still running, and all of the in-kernel NASD components use the
           same memory allocator. */
        nasd_printf("EDRFS: %" NASD_MEMALLOC_FMT
                    " bytes of memory still outstanding\n",
                    nasd_mem_allocated);
#endif /* NASD_MEM_COUNT_ALLOC > 0 */
      }
    }
    else {
      rez = -ENOSYS;
    }
  edrfs_srv_done:
    NASD_D_UNLOCK();
    break;


    case NASD_SC_ATOMIC_MATH_TEST:
      rc = nasd_threads_init();
      if (rc) {
        nasd_printf("NASD: could not initialize threads subsystem\n");
        rez = -EIO;
        break;
      }
      rc = nasd_atomic_test();
      nasd_threads_shutdown();
      if (rc == NASD_OP_NOT_SUPPORTED) {
        rez = -ENOSYS;
        break;
      }
      else if (rc) {
        rez = -EIO;
        break;
      }
      else {
        rez = 0;
      }
      break;


    default:
      rez = -EINVAL;
      break;
  }
  return rez;
}

void
_nasd_linux_thread_yield()
{
  mm_segment_t oldfs;

  oldfs = get_fs();
  set_fs(KERNEL_DS);

  sched_yield();

  set_fs(oldfs);
}

/*
 * nasd_sys_copyin() and nasd_sys_copyout() are defined
 * to return 0=success, positive error value = error.
 * It will get inverted to the linux-style negative errno
 * when it propagates to the high-level entrypoint.
 */

int
nasd_sys_copyin(
  void  *src,
  void  *dst,
  int    len)
{
  if (src == NULL)
    return(EFAULT);

  if (verify_area(VERIFY_READ, src, len))
    return(EFAULT);

  if (copy_from_user(dst, src, len))
    return(EFAULT);

  return(0);
}

int
nasd_sys_copyout(
  void  *src,
  void  *dst,
  int    len)
{
  if (dst == NULL)
    return(EFAULT);

  if (verify_area(VERIFY_WRITE, dst, len))
    return(EFAULT);

  if (copy_to_user(dst, src, len))
    return(EFAULT);

  return(0);
}

int
nasd_sys_wire_buf_onepage(
  void   *buf,
  int     len,
  void  **iobufp,
  int     dir)
{
  unsigned long phys, offset;
  mm_segment_t oldfs;
  pte_t *ptep, pte;
  int ret, ret2;
  pgd_t *pgd;
  pmd_t *pmd;

  if (dir == NASD_WIRE_DIR_WRITE) {
    if (verify_area(VERIFY_WRITE, buf, len)) {
      return(EFAULT);
    }
  }
  else {
    if (verify_area(VERIFY_READ, buf, len)) {
      return(EFAULT);
    }
  }

  oldfs = get_fs();
  set_fs(KERNEL_DS);

  ret = mlock(buf, len);

  set_fs(oldfs);

  if (ret)
    return(ret);

  offset = ((unsigned long)buf)&(PAGE_SIZE-1);

  pgd = pgd_offset(current->mm, (unsigned long)buf);
  if (pgd_none(*pgd)) {
    ret = EFAULT;
    goto bad;
  }

  pmd = pmd_offset(pgd, (unsigned long)buf);
  if (pmd_none(*pmd))
    return(EFAULT);

  ptep = pte_offset(pmd, (unsigned long)buf);
  pte = *ptep;

  if (!pte_present(pte)) {
    /*
     * page is not swapped in- should never happen (we
     * should prevent this with mlock())
     */
   NASD_PANIC();
  }

  phys = virt_to_phys((void *)(pte_page(pte)|offset));

  *iobufp = phys_to_virt(phys);

  return(0);

bad:
  oldfs = get_fs();
  set_fs(KERNEL_DS);

  ret2 = munlock(buf, len);

  if (ret2) {
    NASD_PANIC();
  }

  set_fs(oldfs);

  return(ret);
}

int
nasd_sys_unwire_buf_onepage(
  void  *buf,
  int    len,
  void  *iobuf,
  int    dir)
{
  mm_segment_t oldfs;
  int ret;

  oldfs = get_fs();
  set_fs(KERNEL_DS);

  ret = munlock(buf, len);

  set_fs(oldfs);

  return(-ret);
}

/*
 * XXX these implementations are pretty bad
 * I don't really have the time to sit and figure
 * out how to map multiple pages into the kernel's
 * address space contiguously, so I only really do
 * map-and-wire if the buffer is all in one page.
 * Otherwise, I copy everything. Sigh.
 *
 * !!! note that dir is in terms of the USER operation.
 * Thus, NASD_WIRE_DIR_WRITE means the USER is writing
 * to us, so we copyin, whereas for reads we copyout.
 */

int
nasd_sys_wire_buf(
  void   *buf,
  int     len,
  void  **iobufp,
  int     dir)
{
  void *kbuf;
  int ret;

  if (nasd_page_atomic(buf, len)) {
    ret = nasd_sys_wire_buf_onepage(buf, len, iobufp, dir);
    return(ret);
  }

  NASD_Malloc(kbuf, len, (void *));
  if (kbuf == NULL)
    return(ENOMEM);

  if ((dir == NASD_WIRE_DIR_ALL) || (dir == NASD_WIRE_DIR_WRITE)) {
    ret = nasd_sys_copyin(buf, kbuf, len);
    if (ret) {
      NASD_Free(kbuf, len);
      return(ret);
    }
  }

  *iobufp = kbuf;
  return(0);
}

int
nasd_sys_unwire_buf(
  void  *buf,
  int    len,
  void  *iobuf,
  int    dir)
{
  int ret;

  if (nasd_page_atomic(buf, len)) {
    ret = nasd_sys_unwire_buf_onepage(buf, len, iobuf, dir);
    return(ret);
  }

  if ((dir == NASD_WIRE_DIR_ALL) || (dir != NASD_WIRE_DIR_WRITE)) {
    ret = nasd_sys_copyout(iobuf, buf, len);
  }
  else {
    ret = 0;
  }

  NASD_Free(iobuf, len);

  return(ret);
}

/* Local Variables:  */
/* indent-tabs-mode: nil */
/* tab-width: 2 */
/* End: */
