/*
 * Copyright (c) 1999, 2000 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.1 2000/05/08 05:20:49 lha Exp $ */

#include <xfs_locl.h>

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

/* Is this queue empty? */
int
xfs_emptyq(const struct xfs_link *q)
{
    return q->next == q;
}

/* Is this link on any queue? Link *must* be inited! */
int
xfs_onq(const struct xfs_link *link)
{
    return link->next != NULL || link->prev != NULL;
}

/* Append q with p */
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;
}

/* remove `p' from its queue */
void
xfs_outq(struct xfs_link *p)
{
    p->next->prev = p->prev;
    p->prev->next = p->next;
    p->next = p->prev = NULL;
}

/*
 *
 */

int
xfs_message_wakeup(struct xfs_channel *chan,
		   struct xfs_message_wakeup * message,
		   u_int size)
{
    struct xfs_link *sleepq;
    struct xfs_link *t;

    XFSDEB(XDEBMSG, ("xfs_message_wakeup error: %d\n", message->error));

    KeWaitForSingleObject (&chan->sleep_sem, Executive,
			   KernelMode, FALSE, NULL);

    sleepq = &chan->sleepq;
    t  = chan->sleepq.next;			/* Really first in q */

    for (; t != sleepq; t = t->next)
	if (t->message->sequence_num == message->sleepers_sequence_num) {
	    if (t->message->size < size) {
		DbgPrint ("XFS PANIC Error: "
			  "Could not wakeup requestor with opcode = %d "
			  "properly, to small receive buffer.\n",
			  t->message->opcode);
		t->error_or_size = STATUS_NO_MEMORY;
	    } else
		RtlCopyMemory (message, t->message, size);

	    KeSetEvent (&t->event, 0, FALSE);
	    break;
	}

    KeReleaseSemaphore (&chan->sleep_sem, 0, 1, FALSE);

    return STATUS_SUCCESS;
}

/*
 *
 */

int
xfs_message_wakeup_data(struct xfs_channel *chan,
			struct xfs_message_wakeup_data * message,
			u_int size)
{
    struct xfs_link *sleepq;
    struct xfs_link *t;

    XFSDEB(XDEBMSG, ("xfs_message_wakeup_data error: %d\n", message->error));

    KeWaitForSingleObject (&chan->sleep_sem, Executive,
			   KernelMode, FALSE, NULL);

    sleepq = &chan->sleepq;
    t  = chan->sleepq.next;			/* Really first in q */

    for (; t != sleepq; t = t->next)
	if (t->message->sequence_num == message->sleepers_sequence_num) {
	    if (t->message->size < size) {
		DbgPrint ("XFS PANIC Error: "
			  "Could not wakeup requestor with opcode = %d "
			  "properly, to small receive buffer.\n",
			  t->message->opcode);
		t->error_or_size = STATUS_NO_MEMORY;
	    } else
		RtlCopyMemory (message, t->message, size);

	    KeSetEvent (&t->event, 0, FALSE);
	    break;
	}

    KeReleaseSemaphore (&chan->message_sem, 0, 1, FALSE);

    return STATUS_SUCCESS;
}

/*
 * Send a message to user space and wait for reply.
 */

int
xfs_message_rpc(struct xfs_channel *chan,
		struct xfs_message_header * message,
		u_int size)
{
    int ret;
    struct xfs_link *this_message;
    struct xfs_link *this_process;
    struct xfs_message_header *msg = NULL;
    int catch = 0;

    xfs_debug (XDEBMSG, "xfs_message_rpc opcode = %d\n", message->opcode);
    
    if (!XFS_TESTFLAGS (chan->flags, XFSCHAN_FLAGS_OPEN))
	return STATUS_NO_SUCH_DEVICE;

    if (size < sizeof(struct xfs_message_wakeup)) {
	DbgPrint ("XFS PANIC Error: "
		  "Message to small to receive wakeup, opcode = %d\n",
		  message->opcode);
	return STATUS_NO_MEMORY;
    }

    this_message = xfs_alloc_link (chan);
    this_process = xfs_alloc_link (chan);
    msg = xfs_alloc(size);
    bcopy(message, msg, size);

    msg->size = size;
    msg->sequence_num = chan->nsequence++;
    this_message->error_or_size = 0;
    this_message->message = msg;
    this_process->message = msg;

    KeInitializeEvent (&this_process->event, SynchronizationEvent, FALSE);

    xfs_appendq(&chan->messageq, this_message);
    xfs_appendq(&chan->sleepq, this_process);

    KeSetEvent (&chan->pending_event, 0, FALSE);
    this_process->error_or_size = 0;

#if 0
    if (chan->flags & CHANNEL_WAITING) {
	chan->flags &= ~CHANNEL_WAITING;
	wakeup((caddr_t) chan);
    }
#endif

    /*
     * We have to check if we have a receiver here too because the
     * daemon could have terminated before we sleep. This seems to
     * happen sometimes when rebooting.  */

    if (XFS_TESTFLAGS (chan->flags, XFSCHAN_FLAGS_OPEN)) {
	int ret = 0;
	
	KeWaitForSingleObject(&this_process->event, 
			      Executive,
			      KernelMode,
			      TRUE,
			      NULL);
#if 0
	xfs_debug (XDEBMSG, "caught signal (%d)\n", ret);
	this_process->error_or_size = STATUS_UNEXPECTED_IO_ERROR;
#endif
    }

    /*
     * 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->error_or_size;

    xfs_debug (XDEBMSG, 
	       "xfs_message_rpc "
	       "this_process->error_or_size = %d\n",
	       this_process->error_or_size);
    xfs_debug (XDEBMSG,
	       "xfs_message_rpc opcode "
	       "((xfs_message_wakeup*)"
	       "(this_process->message))->error = %d\n",
	       ((struct xfs_message_wakeup *)
		(this_process->message))->error);

    bcopy(msg, message, size);

    xfs_free_link (chan, this_message);
    xfs_free_link (chan, this_process);
    xfs_free (msg, size);

    return ret;
}

/*
 *
 */

int
init_event (struct xfs_channel *chan)
{
    if (chan->init_event == 0) {
	KeInitializeEvent (&chan->pending_event, SynchronizationEvent, FALSE);
	KeInitializeSemaphore (&chan->message_sem, 0, 1);
	chan->init_event = 1;
    }
    return STATUS_SUCCESS;
}

/*
 * For each message type there is a message handler
 * that implements its action, xfs_message_receive
 * invokes the correct function.
 */
int
xfs_message_receive(struct xfs_channel *chan,
		    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(chan,
				  (struct xfs_message_wakeup *)
				  message,
				  message->size);
    case XFS_MSG_WAKEUP_DATA:
	return xfs_message_wakeup_data(chan,
				       (struct xfs_message_wakeup_data *)
				       message,
				       message->size);
    case XFS_MSG_INSTALLROOT:
	return xfs_message_installroot(chan,
				       (struct xfs_message_installroot *)
				       message,
				       message->size);
    case XFS_MSG_INSTALLNODE:
	return xfs_message_installnode(chan,
				       (struct xfs_message_installnode *)
				       message,
				       message->size);
    case XFS_MSG_INSTALLATTR:
	return xfs_message_installattr(chan,
				       (struct xfs_message_installattr *)
				       message,
				       message->size);
    case XFS_MSG_INSTALLDATA:
	return xfs_message_installdata(chan,
				       (struct xfs_message_installdata *)
				       message,
				       message->size);
    case XFS_MSG_INVALIDNODE:
	return xfs_message_invalidnode(chan,
				       (struct xfs_message_invalidnode *)
				       message,
				       message->size);
    case XFS_MSG_UPDATEFID:
	return xfs_message_updatefid(chan,
				     (struct xfs_message_updatefid *)
				     message,
				     message->size);
    case XFS_MSG_GC_NODES:
	return xfs_message_gc_nodes(chan,
				    (struct xfs_message_gc_nodes *)
				    message,
				    message->size);
    case XFS_MSG_VERSION:
	return xfs_message_version(chan,
				   (struct xfs_message_version *)
				   message,
				   message->size);
    default:
	xfs_debug (XDEBDEV, "XFS PANIC "
		   "Warning xfs_chan: Unknown message opcode == %d\n",
		   message->opcode);
	return STATUS_INVALID_PARAMETER;
    }
}

/*
 *
 */

static int
xfs_devwrite (struct xfs_channel *chan, char *msg, u_int cnt)
{
    char *p;
    int error;
    struct xfs_message_header *msg_buf;

    /*
     * This thread handles the received message.
     */
    for (p = msg;
	 cnt > 0;
	 p += msg_buf->size, cnt -= msg_buf->size) {

	msg_buf = (struct xfs_message_header *)p;
	error = xfs_message_receive (chan,
				     msg_buf,
				     msg_buf->size);
    }
    return error;
}

/*
 * Move messages from kernel to user space.
 */

static NTSTATUS
xfs_devread(struct xfs_channel *chan, unsigned char *buf, u_int *size)
{
    struct xfs_link *first;

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

    KeWaitForSingleObject (&chan->message_sem, Executive,
			   KernelMode, FALSE, NULL);

    while (!xfs_emptyq (&chan->messageq)) {
	/* Remove message */
	first = chan->messageq.next;
	XFSDEB(XDEBDEV, ("xfs_devread: first = %lx, "
			 "first->prev = %lx, first->next = %lx\n",
			 (unsigned long)first,
			 (unsigned long)first->prev,
			 (unsigned long)first->next));
	
	XFSDEB(XDEBDEV, ("xfs_devread: message->size = %u\n",
			 first->message->size));
	
	if (first->message->size > *size)
	    break;
	
	memcpy (buf, first->message, first->message->size);
	buf += first->message->size;
	*size -= first->message->size;
	
	xfs_outq(first);
	
	if (first->error_or_size != 0)
	    xfs_free(first, first->error_or_size);
    }
    KeReleaseSemaphore (&chan->message_sem, 0, 1, FALSE);

    xfs_debug (XDEBDEV, "xfs_devread done");

    return STATUS_SUCCESS;
}

/*
 *
 */

NTSTATUS 
xfs_devctl (PDEVICE_OBJECT device, PIRP irp)
{ 
    PIO_STACK_LOCATION io_stack;
    struct xfs_channel *chan = &XFSGlobalData;
    unsigned char *buf;
    NTSTATUS status = STATUS_SUCCESS;
    u_int len;

    xfs_debug (XDEBVNOPS, "xfs_devctl\n");

    io_stack = IoGetCurrentIrpStackLocation(irp);
    ASSERT (io_stack);

    xfs_debug (XDEBDEV, "xfs_devctl: ControlCode = %d\n", 
	       io_stack->Parameters.DeviceIoControl.IoControlCode);

    switch (io_stack->Parameters.DeviceIoControl.IoControlCode) {
    case IOCTL_XFS_PUTMSG:
	xfs_debug (XDEBDEV, "IOCTL_XFS_PUTMSG\n");
	
	ASSERT(irp->MdlAddress == NULL);
	    
	len = io_stack->Parameters.DeviceIoControl.InputBufferLength; 
	buf = irp->AssociatedIrp.SystemBuffer;

	status = xfs_devwrite (chan, buf, len);

	break;

    case IOCTL_XFS_WAKEMSG: {
	int len = io_stack->Parameters.DeviceIoControl.InputBufferLength; 
	unsigned char *buf = irp->AssociatedIrp.SystemBuffer;
	u_int32_t sequence_num;

	ASSERT(irp->MdlAddress == NULL);
	    
	KeWaitForSingleObject (&chan->message_sem, Executive,
			       KernelMode, FALSE, NULL);
	if (len == 0)
	    /* wake_all() */;
	else if (len != sizeof(sequence_num))
	    break;

	if (chan->pending_count)
	    KeSetEvent (&chan->pending_event, 0, FALSE);

	KeReleaseSemaphore (&chan->message_sem, 0, 1, FALSE);

	break;
    }
    case IOCTL_XFS_GETMSG:
	xfs_debug (XDEBDEV, "IOCTL_XFS_GETMSG\n");
	
	ASSERT(irp->MdlAddress == NULL);

	KeWaitForSingleObject (&chan->message_sem, Executive,
			       KernelMode, FALSE, NULL);
	if (xfs_emptyq (&chan->messageq)) {
	    KeReleaseSemaphore (&chan->message_sem, 0, 1, FALSE);
	    init_event(&XFSGlobalData);
	    chan->pending_count++;
	    KeWaitForSingleObject(&chan->pending_event, 
				  Executive,
				  KernelMode,
				  FALSE,
				  NULL);
	    
	    chan->pending_count--;
	    KeWaitForSingleObject (&chan->message_sem, Executive,
				   KernelMode, FALSE, NULL);
	}
	if (!xfs_emptyq (&chan->messageq)) {
	    buf = irp->AssociatedIrp.SystemBuffer;
	    len = io_stack->Parameters.DeviceIoControl.OutputBufferLength;
	    status = xfs_devread (chan, buf, &len);
	}
	KeReleaseSemaphore (&chan->message_sem, 0, 1, FALSE);
	break;
    default:
	xfs_debug (XDEBDEV, "xfs_devctl: default\n");
	break;
    }
    
    irp->IoStatus.Status = status;
    irp->IoStatus.Information = 0;
    IoCompleteRequest(irp, IO_NO_INCREMENT);
    
    return status;
}
