/*
 * nasd_pdev.c
 *
 * pseudo-device for performing nasd operations in the kernel on
 * behalf of a user-process
 *
 * Author: Jim Zelenka
 */
/*
 * Copyright (c) of Carnegie Mellon University, 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.
 */


#include <nasd/nasd_options.h>

#if NASD_NUM_KPDEV > 0

#include <nasd/nasd_pdrive_client.h>
#include <nasd/nasd_mem.h>
#include <nasd/nasd_edrfs_client.h>
#include <nasd/nasd_threadstuff.h>
#include <nasd/nasd_freelist.h>
#include <nasd/nasd_pdev.h>
#include <nasd/nasd_sys.h>
#include <nasd/nasd_timer.h>

#ifdef DEC_OSF
#include <kern/lock.h>
#include <vm/vm_map.h>
#include <vm/vm_kern.h>
#include <vm/vm_lock.h>
#include <sys/time.h>
#include <sys/mode.h>
#include <io/common/devdriver.h>
#endif /* DEC_OSF */

#ifdef LINUX
#include <linux/fs.h>
#include <asm/segment.h>
#endif /* LINUX */

#if NASD_RPC_PACKAGE == NASD_RPC_PACKAGE_DCE
#include <dce/ker/pthread_exc.h>
#endif /* NASD_RPC_PACKAGE == NASD_RPC_PACKAGE_DCE */

char nasdpd_bogusbuf[8192];

NASDpd_t NASDPDs[NASD_NUM_KPDEV];

int nasdpd_do_fake_byte_ops = 1;

NASD_DECLARE_ONCE(nasdpd_init_once)
NASD_DECLARE_MUTEX(nasdpd_lock) /* never destroyed */

#define NASDPD_LOCK()    NASD_LOCK_MUTEX(nasdpd_lock)
#define NASDPD_UNLOCK()  NASD_UNLOCK_MUTEX(nasdpd_lock)

nasd_status_t nasdpd_init_status = NASD_SYS_NOT_INITIALIZED;

void
nasdpd_real_init_once()
{
  nasd_status_t rc;
  NASDpd_t *npd;
  int i;

  for(i=0;i<NASD_NUM_KPDEV;i++) {
    npd = &NASDPDs[i];
    NASD_ASSERT((npd->state == NPD_STATE_BOGUS) || (npd->state == NPD_STATE_UNBOUND));
    npd->state = NPD_STATE_UNBOUND;
  }

  if (nasdpd_init_status == NASD_SYS_NOT_INITIALIZED) {
    rc = nasd_mutex_init(&nasdpd_lock);
    if (rc) {
      nasdpd_init_status = NASD_FAIL;
    }
  }

  nasdpd_init_status = NASD_SUCCESS;
}

nasd_status_t
nasdpd_init()
{
  nasd_once(&nasdpd_init_once, nasdpd_real_init_once);
  return(nasdpd_init_status);
}

int
nasdpd_unbind(
  NASDpd_t         *npd,
  NASDPD_unbind_t  *kpunbind)
{
  nasd_status_t rc;

  NASDPD_LOCK();
  if (npd->state != NPD_STATE_BOUND) {
    /* already done */
    NASDPD_UNLOCK();
    return(0);
  }
  rc = nasd_unbind_drive(&npd->h);
  npd->state = NPD_STATE_UNBOUND;
  nasd_cl_p_shutdown();
  if (kpunbind == NULL) {
    if (rc) {
      NASDPD_UNLOCK();
      return(EIO);
    }
  }
  else {
    kpunbind->unbind_status = rc;
  }
  NASDPD_UNLOCK();
  return(0);
}

int
nasdpd_real_open(
  NASDpd_t  *npd)
{
  nasd_status_t rc;

  rc = nasdpd_init();
  if (rc)
    return(EIO);

  NASDPD_LOCK();
  npd->open_count++;
  NASDPD_UNLOCK();

  return(0);
}

int
nasdpd_real_close(
  NASDpd_t  *npd)
{
  NASDPD_LOCK();
  npd->open_count--;
  NASDPD_UNLOCK();

  return(0);
}

/*
 * XXX
 *
 * Note lack of serialization!
 */

int
nasdpd_real_ioctl(
  NASDpd_t  *npd,
  int        cmd,
  caddr_t    data)
{
  nasd_status_t nrc;
  void *iobuf;

#if 0
  nasd_printf("XXX nasdpd_real_ioctl cmd is %d (or 0x%x)\n",cmd,cmd); /*DB*/
  nasd_printf("XXX nasdpd_real_ioctl ioctl nr is  %d (or 0x%x)\n",_IOC_NR(cmd),_IOC_NR(cmd));
#endif

  switch(cmd) {

    case NASDPD_GET_OPENCOUNT: {
      int *countp;

      countp = (int *)data;
      NASDPD_LOCK();
      *countp = npd->open_count;
      NASDPD_UNLOCK();
      return(0);
    }
    break;

    case NASDPD_PBIND: {
      int binding_type, ret;
      NASDPD_bind_t *bind;
      char *binding_param;

      bind = (NASDPD_bind_t *)data;
      NASDPD_LOCK();
      if (npd->state == NPD_STATE_BOUND) {
        /* already bound */
        NASDPD_UNLOCK();
        bind->bind_status = NASD_KPDEV_ALREADY_BOUND;
        return(0);
      }
      nrc = nasd_cl_p_init();
      if (nrc != NASD_SUCCESS) {
        nasd_printf("nasd_init_pdrive_client failed: %s:%d nrc=0x%x (%s)\n",
          __FILE__, __LINE__, nrc, nasd_error_string(nrc));
        NASDPD_UNLOCK();
        bind->bind_status = nrc;
        return(0);
      }
      if (bind->binding_param_len) {
        if (bind->binding_param_len < 0) {
          nasd_cl_p_shutdown();
          NASDPD_UNLOCK();
          bind->bind_status = NASD_BAD_BINDING_PARAM_LEN;
          return(0);
        }
        NASD_Malloc(binding_param, bind->binding_param_len+1, (char *));
        ret = nasd_sys_copyin(bind->binding_param, binding_param,
          bind->binding_param_len);
        if (ret)
          return(ret);
        binding_param[bind->binding_param_len] = '\0';
      }
      else {
        binding_param = NULL;
      }
      
      if (bind->binding_type == NASD_BIND_KPDEV_DEFAULT)
        binding_type = NASD_BIND_DEFAULT;
      else
        binding_type = bind->binding_type;
      nrc = nasd_bind_to_drive(bind->drive_name, bind->drive_port,
        binding_type, binding_param, bind->binding_param_len,
        &npd->h);
      bind->bind_status = nrc;
      if (binding_param) {
        NASD_Free(binding_param, bind->binding_param_len+1);
      }
      if (nrc) {
        nasd_cl_p_shutdown();
        NASDPD_UNLOCK();
        bind->bind_status = nrc;
        return(0);
      }

      npd->state = NPD_STATE_BOUND;

      NASDPD_UNLOCK();

      return(0);
    }
    break;
    case NASDPD_PUNBIND: {
      NASDPD_unbind_t *unbind;

      unbind = (NASDPD_unbind_t *)data;
      return(nasdpd_unbind(npd, unbind));
    }
    break;
    case NASDPD_PNULL: {
      NASDPD_null_t *null;
      null = (NASDPD_null_t *)data;
      if (npd->state != NPD_STATE_BOUND) {
        /* not bound */
        return(EBUSY);
      }
      /* _cl_ functions do their own exception handling */
      nasd_cl_p_null_dr(npd->h, &null->nasd_status, &null->op_status);
    }
    break;
    case NASDPD_PSYNC: {
      NASDPD_sync_t *sync;

      sync = (NASDPD_sync_t *)data;
      if (npd->state != NPD_STATE_BOUND) {
        /* not bound */
        return(EBUSY);
      }
      nasd_cl_p_sync_dr(npd->h, &sync->nasd_status, &sync->op_status);
      return(0);
    }
    break;

    case NASDPD_P_PCRPART: {
      NASDPD_p_crpart_t *cmd;
      cmd = (NASDPD_p_crpart_t *)data;
      nasd_cl_p_part_creat_dr(
        npd->h,
        cmd->in_key,
        &cmd->sec_param,
        (cmd->capability_valid ? &cmd->capability : NULL),
        &cmd->args,
        &cmd->res,
        &cmd->op_status);
    }
    break;
    case NASDPD_P_PCREATE: {
      nasd_p_create_dr_args_t args;
      nasd_p_create_dr_res_t res;
      NASDPD_p_create_t *cmd;
      int ret;
      cmd = (NASDPD_p_create_t *)data;
      ret = nasd_sys_copyin(cmd->args, &args, sizeof(args));
      if (ret)
        return(ret);
      nasd_cl_p_create_dr(
        npd->h,
        cmd->in_key,
        &cmd->sec_param,
        (cmd->capability_valid ? &cmd->capability : NULL),
        &args,
        &res,
        &cmd->op_status);
      ret = nasd_sys_copyout(&res, cmd->res, sizeof(res));
      if (ret)
        return(ret);
    }
    break;
    case NASDPD_P_PGETATTR: {
      nasd_p_getattr_dr_args_t args;
      nasd_p_getattr_dr_res_t res;
      NASDPD_p_getattr_t *cmd;
      int ret;
      cmd = (NASDPD_p_getattr_t *)data;
      ret = nasd_sys_copyin(cmd->args, &args, sizeof(args));
      if (ret)
        return(ret);
      nasd_cl_p_getattr_dr(
        npd->h,
        cmd->in_key,
        &cmd->sec_param,
        (cmd->capability_valid ? &cmd->capability : NULL),
        &args,
        &res,
        &cmd->op_status);
      ret = nasd_sys_copyout(&res, cmd->res, sizeof(res));
      if (ret)
        return(ret);
    }
    break;
    case NASDPD_P_PWRITE_SIMPLE: {
      NASDPD_p_write_simple_t *cmd;
      int ret;
      cmd = (NASDPD_p_write_simple_t *)data;
      ret = nasd_sys_wire_buf(cmd->buf, cmd->args.in_len,
        &iobuf, NASD_WIRE_DIR_WRITE);
      if (ret)
        return(ret);
      nasd_cl_p_write_simple_dr(
        npd->h,
        cmd->in_key,
        &cmd->sec_param,
        (cmd->capability_valid ? &cmd->capability : NULL),
        &cmd->args,
        iobuf,
        &cmd->res,
        &cmd->op_status);
      ret = nasd_sys_unwire_buf(cmd->buf, cmd->args.in_len, iobuf,
        NASD_WIRE_DIR_WRITE);
      if (ret)
        return(ret);
    }
    break;
    case NASDPD_P_PREAD_SIMPLE: {
      NASDPD_p_read_simple_t *cmd;
      int ret;
      cmd = (NASDPD_p_read_simple_t *)data;
      ret = nasd_sys_wire_buf(cmd->buf, cmd->args.in_len,
        &iobuf, NASD_WIRE_DIR_READ);
      if (ret)
        return(ret);
      if (cmd->is_read2) {
        nasd_cl_p_read2_simple_dr(
          npd->h,
          cmd->in_key,
          &cmd->sec_param,
          (cmd->capability_valid ? &cmd->capability : NULL),
          &cmd->args,
          iobuf,
          &cmd->res,
          &cmd->op_status);
      }
      else {
        nasd_cl_p_read_simple_dr(
          npd->h,
          cmd->in_key,
          &cmd->sec_param,
          (cmd->capability_valid ? &cmd->capability : NULL),
          &cmd->args,
          iobuf,
          &cmd->res,
          &cmd->op_status);
      }
      ret = nasd_sys_unwire_buf(cmd->buf, cmd->args.in_len, iobuf,
        NASD_WIRE_DIR_READ);
      if (ret)
        return(ret);
    }
    break;
    case NASDPD_P_PTREAD_SIMPLE: {
      NASDPD_p_tread_simple_t *cmd;
      int ret;
      cmd = (NASDPD_p_tread_simple_t *)data;
      ret = nasd_sys_wire_buf(cmd->buf, cmd->args.in_len,
        &iobuf, NASD_WIRE_DIR_TREAD);
      if (ret)
        return(ret);
      nasd_cl_p_tread_simple_dr(
        npd->h,
        cmd->in_key,
        &cmd->sec_param,
        (cmd->capability_valid ? &cmd->capability : NULL),
        &cmd->args,
        iobuf,
        &cmd->res,
        &cmd->op_status);
      ret = nasd_sys_unwire_buf(cmd->buf, cmd->args.in_len, iobuf,
        NASD_WIRE_DIR_TREAD);
      if (ret)
        return(ret);
    }
    break;
    case NASDPD_P_PWRITE_FAKE: {
      NASDPD_p_write_simple_t *cmd;
      int i, j, t, ret;
      cmd = (NASDPD_p_write_simple_t *)data;
      ret = nasd_sys_wire_buf(cmd->buf, cmd->args.in_len,
        &iobuf, NASD_WIRE_DIR_WRITE);
      if (ret)
        return(ret);
      t = cmd->args.in_len;
      if (nasdpd_do_fake_byte_ops) {
        for(i=0;i<t;i+=j) {
          j = NASD_MIN(sizeof(nasdpd_bogusbuf),t-i);
          bcopy((char *)iobuf, (char *)nasdpd_bogusbuf, j);
        }
      }
      cmd->res.out_datalen = t;
      cmd->res.nasd_status = NASD_SUCCESS;
      ret = nasd_sys_unwire_buf(cmd->buf, cmd->args.in_len, iobuf,
        NASD_WIRE_DIR_WRITE);
      if (ret)
        return(ret);
    }
    break;
    case NASDPD_P_PREAD_FAKE: {
      NASDPD_p_read_simple_t *cmd;
      int i, j, t, ret;
      cmd = (NASDPD_p_read_simple_t *)data;
      ret = nasd_sys_wire_buf(cmd->buf, cmd->args.in_len,
        &iobuf, NASD_WIRE_DIR_READ);
      if (ret)
        return(ret);
      t = cmd->args.in_len;
      if (nasdpd_do_fake_byte_ops) {
        for(i=0;i<t;i+=j) {
          j = NASD_MIN(sizeof(nasdpd_bogusbuf),t-i);
          bcopy((char *)nasdpd_bogusbuf, (char *)iobuf, j);
        }
      }
      cmd->res.out_datalen = t;
      cmd->res.nasd_status = NASD_SUCCESS;
      ret = nasd_sys_unwire_buf(cmd->buf, cmd->args.in_len, iobuf,
        NASD_WIRE_DIR_READ);
      if (ret)
        return(ret);
    }
    break;
    case NASDPD_P_PSETATTR: {
      nasd_p_setattr_dr_args_t args;
      nasd_p_setattr_dr_res_t res;
      NASDPD_p_setattr_t *cmd;
      int ret;
      cmd = (NASDPD_p_setattr_t *)data;
      ret = nasd_sys_copyin(cmd->args, &args, sizeof(args));
      if (ret)
        return(ret);
      nasd_cl_p_setattr_dr(
        npd->h,
        cmd->in_key,
        &cmd->sec_param,
        (cmd->capability_valid ? &cmd->capability : NULL),
        &args,
        &res,
        &cmd->op_status);
      ret = nasd_sys_copyout(&res, cmd->res, sizeof(res));
      if (ret)
        return(ret);
    }
    break;
    case NASDPD_P_PFLUSH_OBJ: {
      NASDPD_p_flush_obj_t *cmd;
      cmd = (NASDPD_p_flush_obj_t *)data;
      nasd_cl_p_flush_obj_dr(
        npd->h,
        cmd->in_key,
        &cmd->sec_param,
        (cmd->capability_valid ? &cmd->capability : NULL),
        &cmd->args,
        &cmd->res,
        &cmd->op_status);
    }
    break;
    case NASDPD_P_PEJECT_OBJ: {
      NASDPD_p_eject_obj_t *cmd;
      cmd = (NASDPD_p_eject_obj_t *)data;
      nasd_cl_p_eject_obj_dr(
        npd->h,
        cmd->in_key,
        &cmd->sec_param,
        (cmd->capability_valid ? &cmd->capability : NULL),
        &cmd->args,
        &cmd->res,
        &cmd->op_status);
    }
    break;
    case NASDPD_P_PREMOVE: {
      NASDPD_p_remove_t *cmd;
      cmd = (NASDPD_p_remove_t *)data;
      nasd_cl_p_remove_dr(
        npd->h,
        cmd->in_key,
        &cmd->sec_param,
        (cmd->capability_valid ? &cmd->capability : NULL),
        &cmd->args,
        &cmd->res,
        &cmd->op_status);
    }
    break;
    case NASDPD_P_PINITIALIZE: {
      NASDPD_p_initialize_t *cmd;
      cmd = (NASDPD_p_initialize_t *)data;
      nasd_cl_p_initialize_dr(npd->h, &cmd->args, &cmd->res,
        &cmd->op_status);
    }
    break;
    case NASDPD_P_PRSHUTDOWN: {
      NASDPD_p_rshutdown_t *cmd;
      cmd = (NASDPD_p_rshutdown_t *)data;
      nasd_cl_p_rshutdown_dr(
        npd->h,
        cmd->in_key,
        &cmd->sec_param,
        (cmd->capability_valid ? &cmd->capability : NULL),
        &cmd->args,
        &cmd->res,
        &cmd->op_status);
    }
    break;
    case NASDPD_P_PREMOTE_ATTACH: {
      int ret;
      NASDPD_p_remote_attach_t *cmd;
      nasd_p_remote_attach_dr_args_t  args;
      cmd = (NASDPD_p_remote_attach_t *)data;
      nasd_sys_copyin(cmd->args,&args,sizeof(args));
      ret = nasd_sys_wire_buf(cmd->buf,args.in_args_len,
                              &iobuf,NASD_WIRE_DIR_WRITE);
      if (ret) 
        return ret;
      
      nasd_cl_p_remote_attach_dr(
        npd->h,
        cmd->in_key,
        &cmd->sec_param,
                (cmd->capability_valid ? &cmd->capability : NULL),
        &args,
        iobuf,
        &cmd->res,
        &cmd->op_status);
      ret = nasd_sys_unwire_buf(cmd->buf,args.in_args_len,iobuf,
                                NASD_WIRE_DIR_WRITE);
      if (ret)
        return ret;
    }
    break;
    case NASDPD_P_PREMOTE_DETACH: {
      NASDPD_p_remote_detach_t *cmd;
      cmd = (NASDPD_p_remote_detach_t *)data;
      
      nasd_cl_p_remote_detach_dr(
        npd->h,
        cmd->in_key,
        &cmd->sec_param,
                (cmd->capability_valid ? &cmd->capability : NULL),
        &cmd->args,
        &cmd->res,
        &cmd->op_status);
    }
    break;
    case NASDPD_P_PREMOTE_INVOKE: {
      int ret;
      NASDPD_p_remote_invoke_t  *cmd;
      cmd = (NASDPD_p_remote_invoke_t *) data;
      ret = nasd_sys_wire_buf(cmd->buf, cmd->args.in_len,
                              &iobuf, NASD_WIRE_DIR_READ);
      if (ret)
        return ret;

      nasd_cl_p_remote_invoke_dr(
        npd->h,
        cmd->in_key,
        &cmd->sec_param,
        (cmd->capability_valid ? &cmd->capability : NULL),
        &cmd->args,
        iobuf,
        &cmd->res,
        &cmd->op_status);
      
      ret = nasd_sys_unwire_buf(cmd->buf, cmd->args.in_len,
                              iobuf, NASD_WIRE_DIR_READ);
      if (ret)
        return ret;
    }
    break;
    case NASDPD_P_PGETINFO: {
      NASDPD_p_getinfo_t *cmd;
      cmd = (NASDPD_p_getinfo_t *)data;
      nasd_cl_p_getinfo_dr(npd->h, &cmd->res, &cmd->op_status);
    }
    break;
    default:
      return(EINVAL);
      break;
  }

  return(0);
}

#endif /* NASD_NUM_KPDEV > 0 */

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