/*
 * Copyright (c) 1995, 1996, 1997 Kungliga Tekniska Högskolan
 * (Royal Institute of Technology, Stockholm, Sweden).
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by the Kungliga Tekniska
 *      Högskolan and its contributors.
 * 
 * 4. Neither the name of the Institute nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/* $Id: xfs_dev.c,v 1.10 1998/01/19 02:18:30 map Exp $ */
/* $Source: /usr/local/cvsroot/arla/xfs/linux/xfs_dev.c,v $ */

#define __NO_VERSION__

#include "xfs_locl.h"

#include <xfs_message.h>
#include <xfs_dev.h>
#include <xfs_fs.h>
#include <xdeb.h>
#include <nxfs.h>

/*
 * Queues of xfs_links hold outbound messages and processes sleeping
 * for replies. The last field is used to return errno to sleepers and
 * to keep record of memory to be deallocated when messages have been
 * delivered or dropped.
 */
struct xfs_link {
  struct xfs_link *prev, *next;
  struct xfs_message_header *message;
  u_int errno_or_size;		/* errno on sleepq and size on messageq */
  struct wait_queue *wait_queue;
};  

struct xfs_channel {
  struct xfs_link messageq;	/* Messages not yet read */
  struct xfs_link sleepq;	/* Waiting for reply message */
  u_int nsequence;
#if 0
  struct wait_queue *selecting_proc;
#endif
  struct wait_queue *wait_queue;
  struct xfs_message_header *message_buffer;
  int status;
#define CHANNEL_OPENED	0x1
};

static struct xfs_channel xfs_channel[NXFS];

static void
xfs_initq(struct xfs_link *q)
{
  q->next = q;
  q->prev = q;
  q->wait_queue = NULL;
}

/* Is this queue empty? */
#define xfs_emptyq(q) ((q)->next == (q))

/* Is this link on any queue? Link *must* be inited! */
#define xfs_onq(link) ((link)->next != 0 || (link)->prev != 0)

/* Append q with p */
static void
xfs_appendq(struct xfs_link *q, struct xfs_link *p)     
{
  p->next = q;
  p->prev = q->prev;
  p->prev->next = p;
  q->prev = p;
}

static void
xfs_outq(struct xfs_link *p)     
{
  p->next->prev = p->prev;
  p->prev->next = p->next;
  p->next = p->prev = 0;
}


/*
 * Only allow one open.
 */
static int xfs_devopen(struct inode *inode, struct file *file)
{
  struct xfs_channel *chan;

  XFSDEB(XDEBDEV, ("xfs_devopen dev = %d, flags = %d\n", inode->i_rdev, file->f_flags));
  
  if (MINOR(inode->i_rdev) < 0 || MINOR(inode->i_rdev) >=NXFS)
    return ENXIO;

  MOD_INC_USE_COUNT;

  chan = &xfs_channel[MINOR(inode->i_rdev)];

  /* Only allow one reader/writer */
  if (chan->status & CHANNEL_OPENED)
    {
      MOD_DEC_USE_COUNT;
      return EBUSY;
    }
  else
    chan->status |= CHANNEL_OPENED;

  chan->message_buffer = xfs_alloc(MAX_XMSG_SIZE);

  return 0;
}

static void xfs_devclose(struct inode * inode, struct file * file)
{
  struct xfs_channel *chan = &xfs_channel[MINOR(inode->i_rdev)];
  struct xfs_link *first;

  XFSDEB(XDEBDEV, ("xfs_devclose dev = %d, flags = %d\n", inode->i_rdev, file->f_flags));

  /* Sanity check, paranoia? */
  if (!(chan->status & CHANNEL_OPENED))
    panic("xfs_devclose never opened?");

  chan->status &= ~CHANNEL_OPENED;

  /* No one is going to read those messages so empty queue! */
  while (!xfs_emptyq(&chan->messageq))
    {
      XFSDEB(XDEBDEV, ("before outq(messageq)\n"));
      first = chan->messageq.next;
      xfs_outq(first);
      if (first->errno_or_size != 0)
        xfs_free(first);
      XFSDEB(XDEBDEV, ("after outq(messageq)\n"));
    }

  /* Wakeup those waiting for replies that will never arrive. */
  while (!xfs_emptyq(&chan->sleepq))
    {
      XFSDEB(XDEBDEV, ("before outq(sleepq)\n"));
      first = chan->sleepq.next;
      xfs_outq(first);
      first->errno_or_size = ENODEV;
      wake_up(&first->wait_queue);
      XFSDEB(XDEBDEV, ("after outq(sleepq)\n"));
    }

  if (chan->message_buffer)
    {
      xfs_free(chan->message_buffer);
      chan->message_buffer = 0;
    }

  /* Free all xfs_nodes. */
  free_all_xfs_nodes(&xfs[MINOR(inode->i_rdev)]);

  MOD_DEC_USE_COUNT;
}

/*
 * Move messages from kernel to user space.
 */
#if 0
static int
xfs_devread(dev_t dev, struct uio *uiop, int ioflag)
#endif
static int
xfs_devread(struct inode *inode, struct file *file, char *buf, int count)
{
  struct xfs_channel *chan = &xfs_channel[MINOR(inode->i_rdev)];
  struct xfs_link *first;

  XFSDEB(XDEBDEV, ("xfs_devread dev = %d\n", inode->i_rdev));

  XFSDEB(XDEBDEV, ("xfs_devread: m = %p, m->prev = %p, m->next = %p\n",
		   &chan->messageq, chan->messageq.prev, chan->messageq.next));

  if (xfs_emptyq(&chan->messageq))
    return 0;			/* Nothing to read */
  
  /* Remove message */
  first = chan->messageq.next;
  XFSDEB(XDEBDEV, ("xfs_devread: first = %p, first->prev = %p, first->next = %p\n",
		   first, first->prev, first->next));

  xfs_outq(first);

  XFSDEB(XDEBDEV, ("xfs_devread: message->size = %u\n",
		   first->message->size));

  if (count > first->message->size)
    count = first->message->size;
  
  memcpy_tofs(buf, first->message, count);

  if (first->errno_or_size != 0)
    xfs_free(first);
  return count;
}

/*
 * Move messages from user space to kernel space,
 * wakeup sleepers, insert new data in VFS.
 */
static int
#if 0
xfs_devwrite(dev_t dev, struct uio *uiop, int ioflag)
#endif
xfs_devwrite(struct inode *inode, struct file *file, const char *buf, int count)
{
  struct xfs_channel *chan = &xfs_channel[MINOR(inode->i_rdev)];

  XFSDEB(XDEBDEV, ("xfs_devwrite dev = %d\n", inode->i_rdev));

  if (count > MAX_XMSG_SIZE)
    count = MAX_XMSG_SIZE;
  memcpy_fromfs(chan->message_buffer, buf, count);
  
  /*
   * This thread handles the received message.
   */
  return xfs_message_receive(MINOR(inode->i_rdev), chan->message_buffer, chan->message_buffer->size);
}

/*
 * Not used.
 */
static int
#if 0
xfs_devioctl(dev_t dev, int cmd, caddr_t data, int flags, struct proc *p)
#endif
xfs_devioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
  XFSDEB(XDEBDEV, ("xfs_devioctl dev = %d, flags = %d\n", inode->i_rdev, file->f_flags));
  return EINVAL;
}

/*
 * Are there any messages on this filesystem?
 */
static int
#if 0
xfs_devselect(dev_t dev, int which, struct proc *p)
#endif
xfs_devselect(struct inode *inode, struct file *file, int sel_type, select_table *wait)
{
  struct xfs_channel *chan = &xfs_channel[MINOR(inode->i_rdev)];

#if 0
  XFSDEB(XDEBDEV, ("xfs_devselect dev = %d, sel_type = %d\n", inode->i_rdev, sel_type));
#endif

  if (sel_type != SEL_IN)
    return 0;

  if (!xfs_emptyq(&chan->messageq))
    return 1;			/* Something to read */

  /*
   * No need to handle a "collision" since we only allow one
   * concurrent open. */
  select_wait(&chan->wait_queue, wait);

  return 0;
}

/*
 * Send a message to user space.
 */
int
xfs_message_send(int fd, struct xfs_message_header *message, u_int size)
{
  struct xfs_channel *chan = &xfs_channel[fd];
  struct {
    struct xfs_link this_message;
    struct xfs_message_header msg;
  } *t;

  XFSDEB(XDEBMSG, ("xfs_message_send opcode = %d\n", message->opcode));

  if (!(chan->status & CHANNEL_OPENED))	/* No receiver? */
    return ENODEV;
  
  /* Prepare message and copy it later */
  message->size = size;
  message->sequence_num = chan->nsequence++;

  t = xfs_alloc(sizeof(t->this_message) + size);
  t->this_message.errno_or_size = sizeof(t->this_message) + size;
  memmove(&t->msg, message, size);

  t->this_message.message = &t->msg;
  xfs_appendq(&chan->messageq, &t->this_message);
  t->this_message.wait_queue = NULL;
  if ( chan->wait_queue )
      wake_up(&chan->wait_queue);
  return 0;
}

/*
 * Send a message to user space and wait for reply.
 */
int
xfs_message_rpc(int fd, struct xfs_message_header *message, u_int size)
{
  int ret;
  struct xfs_channel *chan = &xfs_channel[fd];
  struct xfs_link *this_message = xfs_alloc(sizeof(struct xfs_link));
  struct xfs_link *this_process = xfs_alloc(sizeof(struct xfs_link));
  struct xfs_message_header *msg;

  XFSDEB(XDEBMSG, ("xfs_message_rpc opcode = %d\n", message->opcode));

  if (!(chan->status & CHANNEL_OPENED))	/* No receiver? */
    return ENODEV;
  
  if (size < sizeof(struct xfs_message_wakeup)) {
      printk("XFS PANIC Error: Message to small to receive wakeup, opcode = %d\n", message->opcode);
      return ENOMEM;
  }

  msg = xfs_alloc(size);
  memcpy(msg, message, size);

  msg->size = size;
  msg->sequence_num = chan->nsequence++;
  this_message->errno_or_size = 0;

  this_message->wait_queue = NULL;
  this_process->wait_queue = NULL;

  this_message->message = msg; /* message;*/
  this_process->message = msg; /* message; */
  xfs_appendq(&chan->messageq, this_message);
  xfs_appendq(&chan->sleepq, this_process);
  if ( chan->wait_queue )
      wake_up(&chan->wait_queue);

  this_process->errno_or_size = 0;

  interruptible_sleep_on(&this_process->wait_queue);
  if (signal_pending(current))
    {
      XFSDEB(XDEBMSG, ("caught signal\n"));
      this_process->errno_or_size = EINTR;
    }

  /*
   * Caught signal, got reply message or device was closed.
   * Need to clean up both messageq and sleepq.
   */
  if (xfs_onq(this_message))
    {
      xfs_outq(this_message);
    }
  if (xfs_onq(this_process))
    {
      xfs_outq(this_process);
    }
  ret = this_process->errno_or_size;
  XFSDEB(XDEBMSG, ("xfs_message_rpc opcode this_process->errno_or_size = %d\n", this_process->errno_or_size));
  XFSDEB(XDEBMSG, ("xfs_message_rpc opcode ((xfs_message_wakeup*)(this_process->message))->errno = %d\n", ((struct xfs_message_wakeup*)(this_process->message))->errno ));

  memcpy(message, msg, size);

  xfs_free (this_message);
  xfs_free (this_process);
  xfs_free (msg);
  return ret;
}

/*
 * For each message type there is a message handler
 * that implements its action, xfs_message_receive
 * invokes the correct function.
 */
int
xfs_message_receive(int fd, struct xfs_message_header *message, u_int size)
{
  XFSDEB(XDEBMSG, ("xfs_message_receive opcode = %d\n", message->opcode));

  /* Dispatch and coerce message type */
  switch (message->opcode) {
  case XFS_MSG_WAKEUP:
    return xfs_message_wakeup(fd,
			      (struct xfs_message_wakeup *) message,
			      message->size);
  case XFS_MSG_WAKEUP_DATA:
    return xfs_message_wakeup_data(fd,
				   (struct xfs_message_wakeup_data *) message,
				   message->size);
  case XFS_MSG_INSTALLROOT:
    return xfs_message_installroot(fd,
				   (struct xfs_message_installroot *) message,
				   message->size);
  case XFS_MSG_INSTALLNODE:
    return xfs_message_installnode(fd,
				   (struct xfs_message_installnode *) message, 
				   message->size);
  case XFS_MSG_INSTALLATTR:
    return xfs_message_installattr(fd,
				   (struct xfs_message_installattr *) message, 
				   message->size);
  case XFS_MSG_INSTALLDATA:
    return xfs_message_installdata(fd,
				   (struct xfs_message_installdata *) message, 
				   message->size);
  case XFS_MSG_INVALIDNODE:
    return xfs_message_invalidnode(fd,
				   (struct xfs_message_invalidnode *) message, 
				   message->size);
  default:
    printk("XFS PANIC Warning xfs_dev: Unknown message opcode == %d\n",
	   message->opcode);
    return EINVAL;
  }
}

int
xfs_message_wakeup(int fd, struct xfs_message_wakeup *message, u_int size)
{
  struct xfs_channel *chan = &xfs_channel[fd];
  struct xfs_link *sleepq = &chan->sleepq;
  struct xfs_link *t = chan->sleepq.next; /* Really first in q */

  XFSDEB(XDEBMSG, ("xfs_message_wakeup\n"));

  for (; t != sleepq; t = t->next)
    if (t->message->sequence_num == message->sleepers_sequence_num)
      {
	if (t->message->size < size)
	  {
	    printk("XFS PANIC Error: Could not wakeup requestor with opcode = %d properly, to small receive buffer.\n", t->message->opcode);
	    t->errno_or_size = ENOMEM;
	  }
	else
	  memmove(t->message, message, size);
	if (t->wait_queue)
	  wake_up(&t->wait_queue);
	break;
      }

  return 0;
}

int
xfs_message_wakeup_data(int fd, struct xfs_message_wakeup_data *message,
			u_int size)
{
    struct xfs_channel *chan = &xfs_channel[fd];
    struct xfs_link *sleepq = &chan->sleepq;
    struct xfs_link *t = chan->sleepq.next; /* Really first in q */
    
    XFSDEB(XDEBMSG, ("xfs_message_wakeup errno: %d\n", message->errno));
    
    for (; t != sleepq; t = t->next)
	if (t->message->sequence_num == message->sleepers_sequence_num){
	    if (t->message->size < size) {
		printk("XFS PANIC Error: Could not wakeup requestor with opcode = %d properly, to small receive buffer.\n", t->message->opcode);
		t->errno_or_size = ENOMEM;
	    }
	    else
		memmove(t->message, message, size);
	    if (t->wait_queue)
		wake_up(&t->wait_queue);
	    break;
	}
    return 0;
}

struct file_operations xfs_fops = {
        NULL,                   /* lseek - default */
        xfs_devread,            /* read */
        xfs_devwrite,           /* write */
        NULL,                   /* readdir - bad */
        xfs_devselect,          /* select - default */
        xfs_devioctl,           /* ioctl - default */
        NULL,                   /* mmap */
        xfs_devopen,            /* open */
        xfs_devclose,           /* release */
        NULL,                   /* fsync */
	NULL,			/* fasync */
	NULL,			/* check_media_change */
	NULL			/* revalidate */
};

int
xfs_init_device(void)
{
  int i;

  for (i = 0; i < NXFS; i++)
    {
      XFSDEB(XDEBDEV, ("before initq(messageq and sleepq)\n"));
      xfs_channel[i].wait_queue = NULL;
      xfs_initq(&xfs_channel[i].messageq);
      xfs_initq(&xfs_channel[i].sleepq);
    }
  return 0;
}
