/*
 * Copyright (c) 1995, 1996, 1997, 1998, 1999 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.
 */

#define __NO_VERSION__

#include <xfs/xfs_locl.h>
#ifdef LINUX2_1
#include <linux/poll.h>
#endif /* LINUX2_1 */

RCSID("$Id: xfs_dev.c,v 1.43 1999/01/10 18:48:02 map Exp $");

#include <xfs/xfs_message.h>
#include <xfs/xfs_msg_locl.h>
#include <xfs/xfs_dev.h>
#include <xfs/xfs_fs.h>
#include <xfs/xfs_deb.h>
#include <xfs/nxfs.h>

/*
 * Queues of xfs_links hold outbound messages and processes sleeping
 * for replies. The last field is used to return error 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 error_or_size;	/* error 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;
    struct wait_queue *wait_queue;
    struct xfs_message_header *message_buffer;
    int status;
#define CHANNEL_OPENED	0x1
    struct semaphore channel_sem;
};

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;
    struct xfs *xfsp = &xfs[MINOR(inode->i_rdev)];
    
    XFSDEB(XDEBDEV, ("xfs_devopen dev = %d, flags = %d\n", inode->i_rdev, file->f_flags));
    
    if (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;
    
    xfsp->status |= XFS_DEVOPEN;
    
    chan->message_buffer = xfs_alloc(MAX_XMSG_SIZE, XFS_MEM_MSGBUF);
    
    return 0;
}

#ifndef LINUX2_1
static void
#else
static int
#endif /* LINUX2_1 */
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;
    xfs[MINOR(inode->i_rdev)].status &= ~XFS_DEVOPEN;
    
    /* No one is going to read those messages so empty queue! */
    down(&chan->channel_sem);
    while (!xfs_emptyq(&chan->messageq)) {
	XFSDEB(XDEBDEV, ("before outq(messageq)\n"));
	first = chan->messageq.next;
	xfs_outq(first);
	if (first->error_or_size != 0)
	    xfs_free(first, XFS_MEM_SENDRPC);
	XFSDEB(XDEBDEV, ("after outq(messageq)\n"));
    }
    up(&chan->channel_sem);
    
    down(&chan->channel_sem);
    /* 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);
	up(&chan->channel_sem);
	first->error_or_size = -ENODEV;
	wake_up(&first->wait_queue);
	XFSDEB(XDEBDEV, ("after outq(sleepq)\n"));
	down(&chan->channel_sem);
    }
    up(&chan->channel_sem);
    
    if (chan->message_buffer) {
	xfs_free(chan->message_buffer, XFS_MEM_MSGBUF);
	chan->message_buffer = 0;
    }

    /* Free all xfs_nodes. */
    free_all_xfs_nodes(&xfs[MINOR(inode->i_rdev)]);
    
    MOD_DEC_USE_COUNT;
#ifdef LINUX2_1
    return 0;
#endif /* LINUX2_1 */
}

/*
 * Move messages from kernel to user space.
 */
#ifndef LINUX2_1
static int
xfs_devread(struct inode *inode, struct file *file, char *buf, int count)
#else
static ssize_t
xfs_devread(struct file *file, char *buf, size_t count, loff_t *ppos)
#endif /* LINUX2_1 */
{
#ifndef LINUX2_1
    struct xfs_channel *chan = &xfs_channel[MINOR(inode->i_rdev)];
#else
    struct xfs_channel *chan = &xfs_channel[0]; /* XXXX */
#endif /* LINUX2_1 */
    struct xfs_link *first;
    int ret = 0;
    
    XFSDEB(XDEBDEV, ("xfs_devread: m = %p, m->prev = %p, m->next = %p\n",
		     &chan->messageq, chan->messageq.prev, chan->messageq.next));
    
    down(&chan->channel_sem);
    while (!xfs_emptyq (&chan->messageq)) {
	first = chan->messageq.next;
	XFSDEB(XDEBDEV, ("xfs_devread: first = %p, "
			 "first->prev = %p, first->next = %p\n",
			 first, first->prev, first->next));
	
	if (first->message->size > count)
	    break;
	
	XFSDEB(XDEBDEV, ("xfs_devread: message->size = %u\n",
			 first->message->size));
	
	copy_to_user (buf, first->message, first->message->size);
	buf += first->message->size;
	count -= first->message->size;
	ret += first->message->size;
	
	xfs_outq(first);
	
	if (first->error_or_size != 0)
	    xfs_free(first, XFS_MEM_SENDRPC);
    }
    up(&chan->channel_sem);
    
#ifdef LINUX2_1
    *ppos += ret;
#endif /* LINUX2_1 */
    
    return ret;
}

/*
 * Move messages from user space to kernel space,
 * wakeup sleepers, insert new data in VFS.
 */

#ifndef LINUX2_1
static int
xfs_devwrite(struct inode *inode, struct file *file, const char *buf, int count)
#else
static ssize_t
xfs_devwrite(struct file *file, const char *buf, size_t count, loff_t *ppos)
#endif /* LINUX2_1 */
{
#ifndef LINUX2_1
    struct xfs_channel *chan = &xfs_channel[MINOR(inode->i_rdev)];
#else
    struct xfs_channel *chan = &xfs_channel[0];
#endif /* LINUX2_1 */
    char *p;
    struct xfs_message_header *msg_buf;
    int ret = 0;
    
    XFSDEB(XDEBDEV, ("xfs_devwrite\n"));
    
    if (count > MAX_XMSG_SIZE)
	count = MAX_XMSG_SIZE;
    copy_from_user(chan->message_buffer, buf, count);
    
    /*
     * This thread handles the received message.
     */
    
    for (p = (char *)chan->message_buffer;
	 count > 0;
	 p += msg_buf->size, count -= msg_buf->size) {
	int error;
	msg_buf = (struct xfs_message_header *)p;
	
#ifndef LINUX2_1
	error = xfs_message_receive (MINOR(inode->i_rdev),
				     msg_buf,
				     msg_buf->size);
#else
	error = xfs_message_receive (0,
				     msg_buf,
				     msg_buf->size);
#endif /* LINUX2_1 */
	if (error) {
	    ret = error;
	    break;
	}
	ret += msg_buf->size;
	
	if (msg_buf->size == 0) {
	    XFSDEB(XDEBDEV, ("xfs_devwrite: error: size == 0\n"));
	    return -EINVAL;
	}
    }
    XFSDEB(XDEBDEV, ("xfs_devwrite ret = %d\n", ret));
    
#ifdef LINUX2_1
    if (ret >= 0)
	*ppos += ret;
#endif /* LINUX2_1 */
    
    return ret;
}

/*
 * Not used.
 */
static int
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?
 */
#ifndef LINUX2_1
static int
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 (sel_type != SEL_IN)
	return 0;
    
    down(&chan->channel_sem);
    if (!xfs_emptyq(&chan->messageq)) {
	up(&chan->channel_sem);
	return 1;                   /* Something to read */
    }
    up(&chan->channel_sem);
    
    /*
     * No need to handle a "collision" since we only allow one
     * concurrent open.
     */
    select_wait(&chan->wait_queue, wait);
    
    return 0;
}
#else
static unsigned int
xfs_devselect(struct file *file, poll_table *wait)
{
    struct xfs_channel *chan = &xfs_channel[0];
    
    poll_wait(file, &chan->wait_queue, wait);
    
    down(&chan->channel_sem);
    if (!xfs_emptyq(&chan->messageq)) {
	up(&chan->channel_sem);
	return POLLIN;                    /* Something to read */
    }
    up(&chan->channel_sem);
    return 0;
}
#endif /* LINUX2_1 */

/*
 * 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++;
    XFSDEB(XDEBMSG, ("xfs_message_send seq = %d\n", message->sequence_num));
    
    t = xfs_alloc(sizeof(t->this_message) + size, XFS_MEM_SENDRPC);
    t->this_message.error_or_size = sizeof(t->this_message) + size;
    memmove(&t->msg, message, size);
    
    t->this_message.message = &t->msg;
    down(&chan->channel_sem);
    xfs_appendq(&chan->messageq, &t->this_message);
    t->this_message.wait_queue = NULL;
    up(&chan->channel_sem);
    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;
    struct xfs_link *this_process;
    struct xfs_message_header *msg;
    sigset_t oldblocked;
    
    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(KERN_EMERG "XFS Panic: Message to small to receive wakeup, opcode = %d\n", message->opcode);
	return -ENOMEM;
    }
    
    this_message = xfs_alloc(sizeof(struct xfs_link), XFS_MEM_SENDRPC);
    this_process = xfs_alloc(sizeof(struct xfs_link), XFS_MEM_SENDRPC);
    msg = xfs_alloc(size, XFS_MEM_SENDRPC);
    memcpy(msg, message, size);
    
    msg->size = size;
    msg->sequence_num = chan->nsequence++;
    XFSDEB(XDEBMSG, ("xfs_message_rpc seq = %d\n", msg->sequence_num));
    this_message->error_or_size = 0;
    
    this_message->wait_queue = NULL;
    this_process->wait_queue = NULL;
    
    this_message->message = msg; /* message;*/
    this_process->message = msg; /* message; */
    down(&chan->channel_sem);
    xfs_appendq(&chan->messageq, this_message);
    xfs_appendq(&chan->sleepq, this_process);
    
    this_process->error_or_size = 0;
    
    up(&chan->channel_sem);
    
    if ( chan->wait_queue )
	wake_up(&chan->wait_queue);
    
#ifdef LINUX2_1
    spin_lock_irq(&current->sigmask_lock);
    oldblocked = current->blocked;
    siginitsetinv(&current->blocked,
		  sigmask(SIGINT) | sigmask(SIGTERM) | sigmask(SIGSTOP) |
		  sigmask(SIGKILL));
    recalc_sigpending(current);
    spin_unlock_irq(&current->sigmask_lock);
    
    interruptible_sleep_on(&this_process->wait_queue);
#else
    /* 
     * XXX
     *
     * Use uninterruptible sleep in 2.0 until code for signal blocking
     * has been written.
     */
    
    sleep_on(&this_process->wait_queue);
#endif
    
    if (signal_pending(current)) {
	XFSDEB(XDEBMSG, ("caught signal\n"));
	this_process->error_or_size = -EINTR;
    }
    
#ifdef LINUX2_1
    spin_lock_irq(&current->sigmask_lock);
    current->blocked = oldblocked;
    recalc_sigpending(current);
    spin_unlock_irq(&current->sigmask_lock);
#endif
    
    XFSDEB(XDEBMSG, ("xfs_message_rpc wakeup: seq = %d\n",
		     msg->sequence_num));
    /*
     * Caught signal, got reply message or device was closed.
     * Need to clean up both messageq and sleepq.
     */
    down(&chan->channel_sem);
    if (xfs_onq(this_message)) {
	xfs_outq(this_message);
    }
    if (xfs_onq(this_process)) {
	xfs_outq(this_process);
    }
    up(&chan->channel_sem);
    ret = this_process->error_or_size;
    XFSDEB(XDEBMSG, ("xfs_message_rpc opcode this_process->error_or_size = %d\n", this_process->error_or_size));
    XFSDEB(XDEBMSG, ("xfs_message_rpc opcode ((xfs_message_wakeup*)(this_process->message))->error = %d\n", ((struct xfs_message_wakeup*)(this_process->message))->error ));
    
    memcpy(message, msg, size);
    
    xfs_free (this_message, XFS_MEM_SENDRPC);
    xfs_free (this_process, XFS_MEM_SENDRPC);
    xfs_free (msg, XFS_MEM_SENDRPC);

    if (ret < -1000 || ret > 0) {
	printk(KERN_EMERG
	       "XFS Panic: xfs_message_rpc was about to return error %d\n", ret);
	return -EINVAL;
    }

    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);
    case XFS_MSG_UPDATEFID:
	return xfs_message_updatefid(fd,
				     (struct xfs_message_updatefid *)message,
				     message->size);
    default:
	printk(KERN_EMERG "XFS Panic: 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 error: %d seq = %d\n",
		     message->error, message->sleepers_sequence_num));
    
    down(&chan->channel_sem);
    for (; t != sleepq; t = t->next)
	if (t->message->sequence_num == message->sleepers_sequence_num) {
	    if (t->message->size < size) {
		printk(KERN_EMERG "XFS Panic: Could not wakeup requestor with opcode = %d properly, too small receive buffer.\n", t->message->opcode);
		t->error_or_size = -ENOMEM;
	    } else {
		memmove(t->message, message, size);
		if (message->error < 0 || message->error > 1000)
		    t->error_or_size = -EPROTO;
		else
		    t->error_or_size = -message->error;
	    }
	    if (t->wait_queue)
		wake_up(&t->wait_queue);
	    break;
	}
    up(&chan->channel_sem);
    
    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 error: %d seq = %d\n",
		     message->error, message->sleepers_sequence_num));
    
    down(&chan->channel_sem);
    for (; t != sleepq; t = t->next)
	if (t->message->sequence_num == message->sleepers_sequence_num) {
	    if (t->message->size < size) {
		printk(KERN_EMERG "XFS Panic: Could not wakeup requestor with opcode = %d properly, to small receive buffer.\n", t->message->opcode);
		t->error_or_size = -ENOMEM;
	    } else {
		memmove(t->message, message, size);
		if (message->error < 0 || message->error > 1000)
		    t->error_or_size = -EPROTO;
		else
		    t->error_or_size = -message->error;
	    }
	    if (t->wait_queue)
		wake_up(&t->wait_queue);
	    break;
	}
    up(&chan->channel_sem);

    return 0;
}

struct file_operations xfs_fops = {
    NULL,			/* lseek */
    xfs_devread,		/* read */
    xfs_devwrite,		/* write */
    NULL,			/* readdir */
    xfs_devselect,		/* select */
    xfs_devioctl,		/* ioctl */
    NULL,			/* mmap */
    xfs_devopen,		/* open */
#ifdef HAVE_STRUCT_FILE_OPERATIONS_FLUSH
    NULL,			/* flush */
#endif
    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);
	xfs_channel[i].channel_sem = MUTEX;
    }
    return 0;
}

void
xfs_print_sleep_queue()
{
    struct xfs_channel *chan = &xfs_channel[0];
    struct xfs_link *sleepq = &chan->sleepq;
    struct xfs_link *t = chan->sleepq.next; /* Really first in q */

    printk("Sleeping:");
    for (; t != sleepq; t = t->next) {
	printk(" %d", t->message->sequence_num);
    }
}
