/*
 * nasd_od_kio.c
 *
 * DUX kernel I/O for integrated NASD.
 *
 * 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>
#include <nasd/nasd_drive_options.h>
#include <nasd/nasd_types.h>
#include <nasd/nasd_freelist.h>
#include <nasd/nasd_itypes.h>
#include <nasd/nasd_mem.h>
#include <nasd/nasd_od.h>
#include <nasd/nasd_cache.h>
#include <nasd/nasd_sys.h>
#define NASD_DRIVE_IO_MODULE 1
#include <nasd/nasd_drive_io.h>
#include <nasd/nasd_ioqueue.h>

#include <sys/secdefines.h>
#if SEC_BASE
#include <sys/security.h>
#include <sys/audit.h>
#include <sys/secpolicy.h>
#endif /* SEC_BASE */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/user.h>
#include <sys/kernel.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/buf.h>
#include <sys/proc.h>
#include <sys/uio.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/conf.h>
#include <sys/mount.h>
#include <sys/vnode.h>
#include <sys/specdev.h>
#include <sys/flock.h>
#include <sys/syslimits.h>
#include <kern/assert.h>
#include <kern/parallel.h>
#include <mach/mach_types.h>
#include <vm/vm_page.h>
#include <vm/vm_vppage.h>
#include <sys/vfs_ubc.h>
#include <vm/vm_mmap.h>
#include <vm/vm_vp.h>
#include <vm/vm_debug.h>
#include <sys/malloc.h>
#include <machine/rpb.h>

/*
 * This I/O module is for the NASD drive when compiled into
 * the Digital Unix(TM) kernel.
 *
 * Since all I/Os in the dux kernel are asynchronous in nature,
 * launch operations merely increment the number of outstanding
 * I/Os in the system and fire another such off. On completion,
 * if nasd_od_io_deq_next() yields more work, the resulting I/O is
 * initiated immediately. Otherwise, nasd_od_io_ios_outstanding
 * is decremented.
 */

extern struct buf *ubc_bufget();

extern nasd_svinfo_t *nasd_sva;

extern int nasd_k_flush_check_milliseconds; /* how often to check */
extern int nasd_k_flush_interval;           /* how many checks before force */
extern int nasd_k_dirty_percent;            /* % can be dirty before flush */
int nasd_k_enable                          = 0;
int nasd_k_flush_check_interval;
int nasd_k_counter;

static struct ucred nasd_cred;

nasd_threadgroup_t nasd_k_flush_group;
nasd_thread_t nasd_k_flush_thread;

#if NASD_DRIVE_DEBUG_PHYS_OUTSTANDING > 0
nasd_odc_ent_t nasd_dux_kios;
int nasd_dux_kios_in_queue;
#define KIO_ENQ(_ent_) { \
  nasd_gettime(&(_ent_)->kintime); \
  (_ent_)->kprev = &nasd_dux_kios; \
  (_ent_)->knext = nasd_dux_kios.knext; \
  (_ent_)->kprev->knext = (_ent_); \
  (_ent_)->knext->kprev = (_ent_); \
  nasd_dux_kios_in_queue++; \
}
#define KIO_DEQ(_ent_) { \
  (_ent_)->kprev->knext = (_ent_)->knext; \
  (_ent_)->knext->kprev = (_ent_)->kprev; \
  (_ent_)->kprev = NULL; \
  (_ent_)->knext = NULL; \
  nasd_gettime(&(_ent_)->kouttime); \
  nasd_dux_kios_in_queue--; \
}
#else /* NASD_DRIVE_DEBUG_PHYS_OUTSTANDING > 0 */
#define KIO_ENQ(_ent_) { /* noop */ }
#define KIO_DEQ(_ent_) { /* noop */ }
#endif /* NASD_DRIVE_DEBUG_PHYS_OUTSTANDING > 0 */

pid_t nasd_kernel_drive_pid = 0;

nasd_status_t
nasd_get_cred(
  struct ucred  **crpp)
{
  *crpp = &nasd_cred;
  return(NASD_SUCCESS);
}

void
nasd_release_cred(
  struct ucred  *crp)
{
  /* do nothing */
}

/* XXX need a better place to do this */
lock_data_t nasd_d_lock;
struct lockinfo *nasd_d_lockinfo;

int
nasd_srv_init()
{
  NASD_D_LOCK_INIT();
  return(0);
}

void
nasd_k_flush_killgroup(
  nasd_threadarg_t  ignored)
{
  nasd_status_t rc;

  rc = nasd_destroy_threadgroup(&nasd_k_flush_group);
  if (rc) {
    printf("WARNING: could not destroy k-dirty thread group rc=0x%x (%s)\n",
      rc, nasd_error_string(rc));
  }
}

void
nasd_k_flush_proc(
  nasd_threadarg_t  ignored)
{
  nasd_status_t rc;

  NASD_THREADGROUP_RUNNING(&nasd_k_flush_group);

  while(!NASD_THREADGROUP_SHUTDOWNP(&nasd_k_flush_group)) {
    assert_wait((vm_offset_t)&nasd_k_enable, TRUE);
    if (NASD_THREADGROUP_SHUTDOWNP(&nasd_k_flush_group)) {
      clear_wait(current_thread(), THREAD_AWAKENED, FALSE);
      break;
    }
    thread_block();
    if (NASD_THREADGROUP_SHUTDOWNP(&nasd_k_flush_group)) {
      break;
    }
    NASD_IO_INC_STAT(auto_flush);
    rc = nasd_odc_flush_dirty(0);
    if (rc != NASD_SUCCESS) {
      NASD_PANIC();
    }
  }

  NASD_THREADGROUP_DONE(&nasd_k_flush_group);
  NASD_THREAD_KILL_SELF();
}

void
nasd_k_flush_killthread(
  nasd_threadarg_t  ignored)
{
  NASD_THREADGROUP_INDICATE_SHUTDOWN(&nasd_k_flush_group);
  thread_wakeup((vm_offset_t)&nasd_k_enable);
  NASD_THREADGROUP_WAIT_STOP(&nasd_k_flush_group);
}

void
nasd_k_flush_check()
{
  int nasd_k_dirty_pages_force;
  nasd_status_t rc;

  if (nasd_k_enable == 0)
    return;
  nasd_k_counter--;
  if (nasd_k_counter == 0) {
    nasd_k_counter = nasd_k_flush_interval;
    thread_wakeup((vm_offset_t)&nasd_k_enable);
    goto done;
  }
  nasd_k_dirty_pages_force = nasd_odc_size * nasd_k_dirty_percent / 100;
  if (nasd_odc_dirtycnt > nasd_k_dirty_pages_force) {
    thread_wakeup((vm_offset_t)&nasd_k_enable);
    goto done;
  }
done:
  timeout(nasd_k_flush_check, (caddr_t)NULL, nasd_k_flush_check_interval);
}

/*
 * Alloc actual page of storage for a cache handle.
 */
nasd_status_t
nasd_odc_io_alloc_page(
  nasd_odc_ent_t  *ent)
{
  vm_page_t pp;
  void *buf;

  NASD_ASSERT(PAGE_SIZE >= NASD_OD_BASIC_BLOCKSIZE);

  vm_pg_alloc_kmem(&pp);
  if (pp == NULL) {
    printf("ERROR: %s:%d no page\n", __FILE__, __LINE__);
    return(NASD_NO_MEM);
  }

  ent->pp = pp;

  buf = (void *)PHYS_TO_KSEG(page_to_phys(pp));
  bzero(buf, PAGE_SIZE);
  ent->data.buf = buf;

  return(NASD_SUCCESS);
}

/*
 * Release page of storage
 */
void
nasd_odc_io_release_page(
  nasd_odc_ent_t  *ent)
{
  if (ent->data.buf == NULL) {
    /* nothing here */
    NASD_ASSERT(ent->pp == VM_PAGE_NULL);
    return;
  }
  NASD_ASSERT(ent->pp != VM_PAGE_NULL);

  vm_pg_free(ent->pp);

  ent->pp = NULL;
  ent->data.buf = NULL;
}

nasd_status_t
nasd_od_io_go()
{
  nasd_status_t rc;

  rc = nasd_thread_create(&nasd_k_flush_thread, nasd_k_flush_proc, NULL);
  if (rc)
    return(rc);
  NASD_THREADGROUP_STARTED(&nasd_k_flush_group);
  NASD_THREADGROUP_WAIT_START(&nasd_k_flush_group);
  rc = nasd_shutdown_proc(nasd_odc_shutdown, nasd_k_flush_killthread, NULL);
  if (rc) {
    nasd_k_flush_killthread(NULL);
    return(rc);
  }

  nasd_k_enable = 1;
  timeout(nasd_k_flush_check, (caddr_t)NULL, nasd_k_flush_check_interval);

  return(NASD_SUCCESS);
}

nasd_status_t
nasd_od_k_init(
  nasd_od_config_t  *config)
{
  nasd_status_t rc;
  int ret;

  nasd_od_ioq_max_outstanding = config->ios_outstanding;
  if (nasd_od_ioq_max_outstanding < 1)
    return(NASD_BAD_IOQUEUE_LEN);

  NASD_IO_MODULE_INIT();

#if NASD_DRIVE_DEBUG_PHYS_OUTSTANDING > 0
  nasd_dux_kios.knext = nasd_dux_kios.kprev = &nasd_dux_kios;
  nasd_dux_kios_in_queue = 0;
#endif /* NASD_DRIVE_DEBUG_PHYS_OUTSTANDING > 0 */

  nasd_k_flush_check_interval = nasd_k_flush_check_milliseconds * hz / 1000;
  nasd_k_counter = nasd_k_flush_interval;

  rc = nasd_init_threadgroup(&nasd_k_flush_group);
  if (rc)
    return(rc);
  rc = nasd_shutdown_proc(nasd_odc_shutdown, nasd_k_flush_killgroup, NULL);
  if (rc) {
    nasd_k_flush_killgroup(NULL);
    return(rc);
  }

  return(NASD_SUCCESS);
}

/*
 * Called by kernel when an I/O completes.
 */
void
nasd_od_k_iodone(
  struct buf  *bp)
{
  int iodir, err, need_release;
  nasd_odc_ent_t *el, *e, *n;
  nasd_io_cbfunc_t iocb;
  nasd_status_t rc;
  void *iocb_arg;
  off_t off;

  el = (nasd_odc_ent_t *)bp->b_driver_un_1.pointvalue;

  NASD_IO_LOCK();
  for(e=el;e;e=n) {
    n = e->inext;
    e->io_flags &= ~NASD_CI_DISPATCH;
    NASD_IO_TM_COMPLETE(e);
    KIO_DEQ(e);
    e->io_flags &= ~(NASD_CI_DISPATCH|NASD_CI_IOHEAD);
#if NASD_DRIVE_DEBUG_PHYS_OUTSTANDING > 0
    e->kbuf = NULL;
#endif /* NASD_DRIVE_DEBUG_PHYS_OUTSTANDING > 0 */
  }
  NASD_IO_UNLOCK();

  ubc_buffree(bp);

  if (bp->b_flags&B_ERROR) {
    printf("ERROR %s:%d bp->b_flags=0x%lx bp->b_error=%d\n",
      __FILE__, __LINE__, (unsigned long)bp->b_flags,
      bp->b_error);
    err = 1;
  }
  else {
    err = 0;
  }

  if (err == 0) {
    off = (bp->b_bcount - bp->b_resid) >> NASD_OD_SECT_SHIFT;
    nasd_od_io_last_completed_sect = bp->b_blkno + off;
  }

  for(e=el;e;e=n) {
    n = e->inext;
    e->inext = e->iprev = NULL;
    e->cnext = e->cprev = NULL;
    nasd_od_io_iodone(e);
  }

  NASD_IO_LOCK();

  nasd_od_io_deq_next(&el, 1);

  if (el) {
    rc = nasd_od_io_launch_internal(el);
    if (rc) {
      NASD_PANIC();
    }
  }
  else {
    nasd_od_io_ios_outstanding--;
  }
  NASD_IO_UNLOCK();
}

/*
 * Caller holds I/O lock here
 */
nasd_status_t
nasd_od_io_launch_internal(
  nasd_odc_ent_t  *entlist)
{
  nasd_odc_ent_t *e;
  vm_page_t lp, fp;
  struct buf *bp;
  int dir, ret;

  NASD_IO_INC_STAT(pull_ios);

  dir = entlist->iodir;

  bp = ubc_bufget();
  if (bp == NULL) {
    /* should never happen in DU */
    NASD_PANIC();
  }
  if (dir == NASD_U_WRITE) {
    bp->b_flags = B_ASYNC | B_PHYS | B_WRITE;
  }
  else {
    bp->b_flags = B_ASYNC | B_PHYS | B_READ;
  }

  bp->b_bcount = 0;
  bp->b_vp = NULL;
  bp->b_error = 0;
  bp->b_dev = nasd_odc_state->dev;
  bp->b_blkno = entlist->real_sectno;
  bp->b_resid = 0;
  bp->b_proc = NULL;
  bp->b_iodone = nasd_od_k_iodone;
  bp->b_driver_un_1.pointvalue = (void *)entlist;
  lp = fp = VM_PAGE_NULL;

  for(e=entlist;e;e=e->inext) {
    NASD_ASSERT((e->io_flags&NASD_CI_DISPATCH) == NASD_CI_DISPATCH);
    NASD_ASSERT(e->iodir == dir);
#if NASD_DRIVE_DEBUG_PHYS_OUTSTANDING > 0
    e->kbuf = bp;
#endif /* NASD_DRIVE_DEBUG_PHYS_OUTSTANDING > 0 */
    bp->b_bcount += NASD_OD_BASIC_BLOCKSIZE;
    if (lp)
      lp->pg_pnext = e->pp;
    else
      fp = e->pp;
    lp = e->pp;
  }

  entlist->io_flags |= NASD_CI_IOHEAD;

  lp->pg_pnext = VM_PAGE_NULL;
  fp->pg_pprev = lp;
  bp->b_bufsize = bp->b_bcount;
  bp->b_pagelist = fp;
  bp->b_un.b_addr = (caddr_t)(page_to_phys(fp));
  ret = bp_mapin(bp);
  if (ret) {
    printf("ERROR %s:%d ret=%d\n", __FILE__, __LINE__, ret);
    NASD_PANIC();
  }
  BDEVSW_STRATEGY(major(nasd_odc_state->dev), bp, ret);

  return(NASD_SUCCESS);
}

nasd_status_t
nasd_od_io_launch(
  nasd_odc_ent_t  *entlist)
{
  nasd_status_t rc;

  if (nasd_od_io_ios_outstanding >= nasd_od_ioq_max_outstanding) {
    return(NASD_IOSYS_FULL);
  }
  nasd_od_io_ios_outstanding++;

  rc = nasd_od_io_launch_internal(entlist);
  return(rc);
}

void
nasd_od_io_sys_flush_block(
  nasd_odc_ent_t  *ent)
{
  nasd_status_t rc;

  NASD_IO_INC_SIZE_STAT(1,write);
  nasd_od_io_sync_launch(ent->real_sectno);
  NASD_IO_TM_LAUNCH(ent);

  rc = nasd_od_k_blkio(ent, ent->real_sectno, ent->data.buf,
    NASD_OD_BASIC_BLOCKSIZE, NASD_U_WRITE);
  if (rc != NASD_SUCCESS) {
    NASD_PANIC();
  }

  NASD_IO_TM_COMPLETE(ent);
}

void
nasd_od_io_sys_flush_block_async(
  nasd_odc_ent_t  *ent)
{
  nasd_status_t rc;

  NASD_IO_INC_SIZE_STAT(1,write);
  nasd_od_io_sync_launch(ent->real_sectno);
  NASD_IO_TM_LAUNCH(ent);

  rc = nasd_od_k_blkio_cb(ent, ent->real_sectno, ent->data.buf,
    NASD_OD_BASIC_BLOCKSIZE, NASD_U_WRITE,
    (void (*)(void *))nasd_od_k_finish_flush, ent);
  if (rc != NASD_SUCCESS) {
    NASD_PANIC();
  }
}

nasd_status_t
nasd_od_k_blkio(
  nasd_odc_ent_t  *ent,   /* not guaranteed to be "real" */
  nasd_blkno_t     sectno,
  void            *buf,
  int              len,
  int              iodir)
{
  struct buf *bp;
  off_t off;
  int ret;

  bp = ubc_bufget();
  if (bp == NULL) {
    /* should never happen in DU */
    NASD_PANIC();
  }
  if (iodir == NASD_U_WRITE) {
    bp->b_flags = B_PHYS | B_WRITE;
  }
  else {
    bp->b_flags = B_PHYS | B_READ;
  }
  bp->b_bcount = len;
  bp->b_bufsize = bp->b_bcount;
  bp->b_error = 0;
  bp->b_dev = nasd_odc_state->dev;
  bp->b_un.b_addr = buf;
  bp->b_blkno = sectno;
  bp->b_resid = 0;
  bp->b_proc = NULL;
  bp->b_iodone = NULL;
  bp->b_driver_un_1.pointvalue = NULL;
  bp->b_vp = NULL;

  nasd_od_io_sync_launch(sectno);

  NASD_IO_TM_LAUNCH(ent);
  BDEVSW_STRATEGY(major(bp->b_dev), bp, ret);
  /* no actual retcode here: cdisk_strategy has type void */

  ret = biowait(bp);
  NASD_IO_TM_COMPLETE(ent);
  if (ret) {
    printf("bp=%lx bp->b_count=%d bp->b_blkno=%d bp->b_resid=%d\n",
      bp, bp->b_bcount, bp->b_blkno, bp->b_resid);
    printf("ret=%d bp->b_error=%d\n", ret, bp->b_error);
    NASD_PANIC();
  }
  else {
    off = (bp->b_bcount - bp->b_resid) >> NASD_OD_SECT_SHIFT;
    nasd_od_io_last_completed_sect = bp->b_blkno + off;
  }
  ubc_buffree(bp);
  return(NASD_SUCCESS);
}

nasd_status_t
nasd_od_k_blkio_cb(
  nasd_odc_ent_t  *ent,   /* not guaranteed to be "real" */
  nasd_blkno_t     sectno,
  void            *buf,
  int              len,
  int              iodir,
  void           (*proc)(void *),
  void            *arg)
{
  struct buf *bp;
  int ret;

  bp = ubc_bufget();
  if (bp == NULL) {
    /* should never happen in DU */
    NASD_PANIC();
  }
  if (iodir == NASD_U_WRITE) {
    bp->b_flags = B_PHYS | B_WRITE;
  }
  else {
    bp->b_flags = B_PHYS | B_READ;
  }
  bp->b_bcount = len;
  bp->b_bufsize = bp->b_bcount;
  bp->b_error = 0;
  bp->b_dev = nasd_odc_state->dev;
  bp->b_un.b_addr = buf;
  bp->b_blkno = sectno;
  bp->b_resid = 0;
  bp->b_proc = NULL;
  bp->b_iodone = proc;
  bp->b_driver_un_1.pointvalue = arg;
  bp->b_vp = NULL;
  NASD_IO_TM_LAUNCH(ent);
  BDEVSW_STRATEGY(major(bp->b_dev), bp, ret);
  return(NASD_SUCCESS);
}

nasd_status_t
nasd_od_k_write_header(
  nasd_odc_ent_t  *fake_ent,
  nasd_blkno_t     sectno,
  nasd_od_disk_t  *disk)
{
  nasd_status_t rc;

  NASD_IO_INC_IO_STAT(header_write,write);
  rc = nasd_od_k_blkio(fake_ent, sectno, (void *)disk,
    NASD_OD_SECT_SIZE, NASD_U_WRITE);
  return(rc);
}

void
nasd_od_io_read_header(
  nasd_blkno_t     sectno,
  nasd_od_disk_t  *disk)
{
  nasd_odc_ent_t fake_ent;
  nasd_status_t rc;

  NASD_IO_TM_ENQ(&fake_ent);
  rc = nasd_od_k_blkio(&fake_ent, sectno, (void *)disk,
    NASD_OD_SECT_SIZE, NASD_U_READ);
  if (rc != NASD_SUCCESS) {
    NASD_PANIC();
  }
  NASD_IO_TM_DONE(&fake_ent);
}

/*
 * Call with diskstate locked
 */
nasd_status_t
nasd_od_write_diskstate(
  int  force_sync)
{
  nasd_sectno_t last_comp, diff1, diff2;
  nasd_odc_ent_t fake_ent;
  nasd_status_t rc1, rc2;
  int write_which;

  NASD_IO_TM_ENQ(&fake_ent);

  nasd_odc_state->disk.mod_time = nasd_odc_state->nvstate->mod_time;

  if (force_sync) {
    NASD_IO_INC_IO_STAT(header_force_sync,write);
  }

  write_which = 0; /* shut up whiner compiler */
  if (force_sync == 0) {
    last_comp = nasd_od_io_last_completed_sect;
    if (last_comp >= nasd_diskheader_dup_blk) {
      write_which = 2;
    }
    else {
      NASD_ASSERT(nasd_diskheader_blk <= last_comp);
      NASD_ASSERT(nasd_diskheader_dup_blk > last_comp);
      diff1 = last_comp - nasd_diskheader_blk;
      diff2 = nasd_diskheader_dup_blk - last_comp;
      if (diff1 > diff2)
        write_which = 2;
      else
        write_which = 1;
    }
  }

  if (force_sync || (write_which == 1)) {
    NASD_IO_INC_IO_STAT(header1_write,write);
    rc1 = nasd_od_k_write_header(&fake_ent, nasd_diskheader_blk,
      &nasd_odc_state->disk);
    NASD_IO_TM_DONE(&fake_ent);
    NASD_IO_TM_ENQ(&fake_ent);
  }
  else {
    rc1 = NASD_SUCCESS;
  }

  if (force_sync || (write_which == 2)) {
    NASD_IO_INC_IO_STAT(header2_write,write);
    rc2 = nasd_od_k_write_header(&fake_ent, nasd_diskheader_dup_blk,
      &nasd_odc_state->disk);
  }
  else {
    rc2 = NASD_SUCCESS;
  }
  NASD_IO_TM_DONE(&fake_ent);

  if (rc1)
    return(rc1);
  return(rc2);
}

void
nasd_od_k_finish_flush(
  struct buf  *bp)
{
  nasd_odc_ent_t *ent;
  off_t off;
  int err;

  ent = (nasd_odc_ent_t *)bp->b_driver_un_1.pointvalue;

  NASD_IO_TM_COMPLETE(ent);

  if (bp->b_flags&B_ERROR) {
    printf("ERROR %s:%d bp->b_flags=0x%lx bp->b_error=%d\n",
      __FILE__, __LINE__, (unsigned long)bp->b_flags,
      bp->b_error);
    err = 1;
  }
  else {
    err = 0;
  }

  if (err == 0) {
    off = (bp->b_bcount - bp->b_resid) >> NASD_OD_SECT_SHIFT;
    nasd_od_io_last_completed_sect = bp->b_blkno + off;
  }

  nasd_od_io_flush_block_async_finish(ent);

  ubc_buffree(bp);
}

#define STOP_PSRV_THREAD() { \
  thread_terminate(current_thread()); \
  thread_halt_self(); \
}

void
nasd_psrv_thread(
  nasd_threadarg_t  arg)
{
  nasd_svinfo_t *svinfo;
  struct nasd_serv *nasdsrv;
  nasd_status_t rc;

  svinfo = (nasd_svinfo_t *)arg;
  nasdsrv = &svinfo->srv;

  if (nasdsrv->use_tcp) {
    nasd_drive_dce_setup_tcp();
    printf("DRIVE: using DCE-TCP\n");
  }
  else {
    nasd_drive_dce_setup_udp();
    printf("DRIVE: using DCE-UDP\n");
  }

  if (nasdsrv->verbose) {
    printf("DRIVE: startup DCE-RPC\n");
  }

  rc = nasd_drive_startup_rpc();
  if (rc) {
    printf("DRIVE: got 0x%x (%s) from nasd_startup_rpc()\n",
      rc, nasd_error_string(rc));
    STOP_PSRV_THREAD();
  }

  if (nasdsrv->stack_size) {
    rc = nasd_drive_rpc_set_stacksize(nasdsrv->stack_size);
    if (rc == NASD_OP_NOT_SUPPORTED) {
      printf("DRIVE: RPC package does not support stack size adjustment\n");
    }
    else if (rc) {
      printf("DRIVE: got 0x%x (%s) setting RPC stack size to %d\n",
        rc, nasd_error_string(rc), nasdsrv->stack_size);
    }
  }

  rc = nasd_drive_rpc_listen(nasdsrv->svc_threads, nasdsrv->ipport);
  if (rc) {
    printf("DRIVE: got 0x%x (%s) from RPC listener\n",
      rc, nasd_error_string(rc));
  }

  STOP_PSRV_THREAD();
}

/*
 * Call with NASD_D_LOCK held, consumes the lock.
 */
int
nasd_psrv_go(
  nasd_svinfo_t  *svinfo)
{
  nasd_thread_t nasd_k_handler_thread;
  int ret, verbose, error, abort;
  struct nasd_serv *nasdsrv;
  struct ucred *nasd_credp;
  struct bdevsw *bsw;
  nasd_status_t rc;
  size_t size;
  dev_t dev;

  nasd_kernel_drive_pid = u.u_procp->p_pid;

  nasdsrv = &svinfo->srv;
  verbose = nasdsrv->verbose;
  abort = 1;

  dev = nasdsrv->dev;
  nasd_odc_force_format = nasdsrv->do_format;

  rc = nasd_get_cred(&nasd_credp);
  if (rc) {
    error = nasd_dux_nasd_status_to_errno(rc);
    goto done;
  }

  bsw = &bdevsw[major(dev)];
  if (bsw->d_open == NULL)
    ret = 0;
  else
    ret = bsw->d_open(dev, 0, S_IFBLK, nasd_credp);

  nasd_release_cred(nasd_credp);

  if ((nasdsrv->cache_blocks < 10) || (nasdsrv->cache_blocks > 262144)) {
    printf("DRIVE: bad cache_blocks value %d\n", nasdsrv->cache_blocks);
    error = EINVAL;
    goto done;
  }
  nasd_odc_size = nasdsrv->cache_blocks;

  if (ret) {
    printf("DRIVE: %s cannot be opened\n", nasdsrv->root_path);
    error = EIO;
    goto done;
  }

  if (bsw->d_psize) {
    size = bsw->d_psize(dev);
  }
  else {
    size = 0;
  }

  if (size <= 0) {
    printf("DRIVE: %s has size %d, cannot use\n", nasdsrv->root_path,
      bsw->d_psize);
    error = EINVAL;
    goto done;
  }

  if (verbose) {
    printf("DRIVE: %lu sectors\n", size);
  }

  if (nasdsrv->max_disk_len) {
    svinfo->size = NASD_MIN(size, nasdsrv->max_disk_len);
  }
  else {
    svinfo->size = size;
  }

  /* GC unused pages */
  pmap_collect(vm_map_pmap(current_thread()->task->map));

  rc = nasd_basic_init();
  if (rc) {
    printf("DRIVE: failed nasd_basic_init with %s\n",
      nasd_error_string(rc));
    error = nasd_dux_nasd_status_to_errno(rc);
    goto done;
  }

  rc = nasd_od_k_init(&nasdsrv->config);
  if (rc) {
    printf("DRIVE: failed nasd_od_k_init with %s\n",
      nasd_error_string(rc));
    error = nasd_dux_nasd_status_to_errno(rc);
    rc = nasd_basic_shutdown();
    if (rc) {
      printf("DRIVE: failed nasd_basic_shutdown() with %s\n",
        nasd_error_string(rc));
      NASD_PANIC();
    }
    goto done;
  }

  rc = nasd_setup_disk(svinfo->size, nasdsrv->dev, &nasdsrv->config);
  if (rc) {
    printf("DRIVE: failed nasd_setup_disk() with %s\n",
      nasd_error_string(rc));
    error = nasd_dux_nasd_status_to_errno(rc);
    rc = nasd_basic_shutdown();
    if (rc) {
      printf("DRIVE: failed nasd_basic_shutdown() with %s\n",
        nasd_error_string(rc));
      NASD_PANIC();
    }
    goto done;
  }

  rc = nasd_od_io_go();
  if (rc) {
    printf("DRIVE: failed nasd_od_io_go() with %s\n",
      nasd_error_string(rc));
    error = nasd_dux_nasd_status_to_errno(rc);
    rc = nasd_basic_shutdown();
    if (rc) {
      printf("DRIVE: failed nasd_basic_shutdown() with %s\n",
        nasd_error_string(rc));
      NASD_PANIC();
    }
    goto done;
  }

  rc = nasd_thread_create(&nasd_k_handler_thread, (void (*)())nasd_psrv_thread,
    (void *)svinfo);
  if (rc) {
    printf("nasd_psrv_go(): cannot create handler thread rc=0x%x (%s)\n",
      rc, nasd_error_string(rc));
    error = nasd_dux_nasd_status_to_errno(rc);
    goto done;
  }

  /* wait for kill */
  assert_wait(0, TRUE);
  NASD_D_UNLOCK();

  if (verbose) {
    printf("DRIVE: thread away\n");
  }

  thread_block();

  if (verbose) {
    printf("DRIVE: begin shutdown\n");
  }

  /* cleanup */
  NASD_D_LOCK();

  nasd_k_enable = 0;

  if (verbose) {
    printf("DRIVE: stop RPC subsystem\n");
  }

  nasd_drive_stop_rpc();

  if (verbose) {
    printf("DRIVE: sequence shutdown\n");
  }

  rc = nasd_basic_shutdown();
  if (rc) {
    printf("DRIVE: nasd_basic_shutdown() returned %s!\n", nasd_error_string(rc));
    NASD_PANIC();
  }

  abort = 0;
  error = EINTR;

done:

#ifdef free
#define xxx_xxxs_goats free
#undef free
#endif /* free */
  NASD_Free(svinfo, sizeof(nasd_svinfo_t));
#ifdef xxx_xxxs_goats
#define free xxx_xxxs_goats
#undef xxx_xxxs_goats
#endif /* xxx_xxxs_goats */

  nasd_kernel_drive_pid = 0;

  nasd_sva = NULL;

  NASD_D_UNLOCK();

  if (verbose && (abort == 0)) {
    printf("DRIVE: shutdown memory subsystem\n");
  }

  nasd_mem_shutdown();

  if (verbose && (abort == 0)) {
    printf("DRIVE: done shutdown\n");
#if NASD_MEM_COUNT_ALLOC > 0
    printf("DRIVE: %" NASD_MEMALLOC_FMT " bytes of memory still outstanding\n",
      nasd_mem_allocated);
#endif /* NASD_MEM_COUNT_ALLOC > 0 */
  }

  return(error);
}

nasd_status_t
nasd_od_sys_rshutdown(
  nasd_drive_rshutdown_flags_t  flags)
{
  struct bogus_kill_args_s {
    long  pid;
    long  signum;
  } bogus_kill_args;
  struct proc *this_proc;
  long retval;
  int ret;

  if (nasd_kernel_drive_pid == 0)
    return(NASD_SUCCESS);

  this_proc = task_to_proc(current_task());

  ret = P_REF(this_proc);
  if (ret == 0)
    return(NASD_FAIL);

  bogus_kill_args.pid = nasd_kernel_drive_pid;
  bogus_kill_args.signum = SIGINT;

  retval = 0;

  ret = kill(this_proc, &bogus_kill_args, &retval);

  P_UNREF(this_proc);

  if (ret || retval)
    return(NASD_FAIL);

  return(NASD_SUCCESS);
}

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