/*
 * This file contains the procedures for the handling of poll.
 *
 * Copyright (C) 1994 Eric Youngdale
 *
 * Created for Linux based loosely upon linux select code, which
 * in turn is loosely based upon Mathius Lattner's minix
 * patches by Peter MacDonald. Heavily edited by Linus.
 *
 * Poll is used by SVr4 instead of select, and it has considerably
 * more functionality.  Parts of it are related to STREAMS, and since
 * we do not have streams, we fake it.  In fact, select() still exists
 * under SVr4, but libc turns it into a poll() call instead.  We attempt
 * to do the inverse mapping.
 */

#include <linux/types.h>
#include <linux/time.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/string.h>
#include <linux/stat.h>
#include <linux/signal.h>
#include <linux/errno.h>
#include <linux/malloc.h>

#include <asm/segment.h>
#include <asm/system.h>

#include <ibcs/ibcs.h>

#ifdef IBCS_TRACE
#include <ibcs/trace.h>
#endif


#define ROUND_UP(x,y) (((x)+(y)-1)/(y))

#define POLLIN 1
#define POLLPRI 2
#define POLLOUT 4
#define POLLERR 8
#define POLLHUP 16
#define POLLNVAL 32
#define POLLRDNORM 64
#define POLLWRNORM POLLOUT
#define POLLRDBAND 128
#define POLLWRBAND 256

#define LINUX_POLLIN (POLLRDNORM | POLLRDBAND | POLLIN)
#define LINUX_POLLOUT (POLLWRBAND | POLLWRNORM | POLLOUT)
#define LINUX_POLLERR (POLLERR)

static inline void free_wait(select_table * p)
{
	struct select_table_entry * entry = p->entry + p->nr;

	while (p->nr > 0) {
		p->nr--;
		entry--;
		remove_wait_queue(entry->wait_address,&entry->wait);
	}
}


/* Copied directly from fs/select.c */

static int check(int flag, select_table * wait, struct file * file)
{
	struct inode * inode;
	struct file_operations *fops;
	int (*select) (struct inode *, struct file *, int, select_table *);

	inode = file->f_inode;
	if ((fops = file->f_op) && (select = fops->select))
		return select(inode, file, flag, wait)
		    || (wait && select(inode, file, flag, NULL));
	if (S_ISREG(inode->i_mode))
		return 1;
	return 0;
}


int ibcs_poll(struct poll * ufds, size_t nfds, int timeout)
{
        int i,j, count, fdcount, error, retflag;
	unsigned long ktimeout;
	struct poll * fdpnt;
	struct poll * fds, *fds1;
	select_table wait_table, *wait;
	struct select_table_entry *entry;

	if ((error = verify_area(VERIFY_READ, ufds, nfds*sizeof(struct poll))))
	  return error;

	if (nfds > NR_OPEN)
		return -EINVAL;

	if(!(entry = (struct select_table_entry*) __get_free_page(GFP_KERNEL)))
	  return -ENOMEM;

	fds = (struct poll *) kmalloc(nfds*sizeof(struct poll), GFP_ATOMIC);
	memcpy_fromfs(fds, ufds, nfds*sizeof(struct poll));

	ktimeout = jiffies;
	ktimeout += ROUND_UP(timeout,(1000/HZ));
	if (ktimeout <= jiffies)
	  ktimeout = 0;
	if(timeout < 0) ktimeout = 0x7fffffff;	
	current->timeout = ktimeout;
	count = 0;
	wait_table.nr = 0;
	wait_table.entry = entry;
	wait = &wait_table;
	for(fdpnt = fds, j = 0; j < nfds; j++, fdpnt++)
	  {
	    i = fdpnt->fd;
	    fdpnt->revents = 0;
#ifdef IBCS_TRACE
	    if ((ibcs_trace & TRACE_API) || ibcs_func_p->trace)
	      printk(KERN_DEBUG "iBCS: %d: fd=%d events=%x\n",
		current->pid, fdpnt->fd, fdpnt->events);
#endif
	    if (!current->FD[i] || !current->FD[i]->f_inode)
	      fdpnt->revents = POLLNVAL;
	  }
repeat:
	current->state = TASK_INTERRUPTIBLE;
	for(fdpnt = fds, j = 0; j < nfds; j++, fdpnt++)
	  {
	    i = fdpnt->fd;

	    if(i < 0) continue;
	    if (!current->FD[i] || !current->FD[i]->f_inode) continue;

	    if ((fdpnt->events & LINUX_POLLIN) &&
		check(SEL_IN, wait, current->FD[i])) {
	      if (fdpnt->events & POLLIN) retflag = POLLIN;
	      if (fdpnt->events & POLLRDNORM) retflag = POLLRDNORM;
	      if (fdpnt->events & POLLRDBAND) retflag = POLLRDBAND;
	      fdpnt->revents |= retflag; 
	      count++;
	      wait = NULL;
	    }
	    if ((fdpnt->events & LINUX_POLLOUT) &&
		check(SEL_OUT,wait,current->FD[i])) {
	      fdpnt->revents |= (LINUX_POLLOUT & fdpnt->events);
	      count++;
	      wait = NULL;
	    }
	    if ((fdpnt->events & LINUX_POLLERR) &&
		check(SEL_EX,wait,current->FD[i])) {
	      fdpnt->revents |= (LINUX_POLLERR & fdpnt->events);
	      count++;
	      wait = NULL;
	    }
	  }
	wait = NULL;
	if (!count && current->timeout && !(current->signal & ~current->blocked)) {
		schedule();
		goto repeat;
	}
	free_wait(&wait_table);
	free_page((unsigned long) entry);
	/* OK, now copy the revents fields back to user space. */
	fds1 = fds;
	fdcount = 0;
	for(i=0; i< nfds; i++, ufds++, fds++)
	  {
	    if (fds->revents & (LINUX_POLLIN | LINUX_POLLOUT)) fdcount++;
	    put_fs_long(fds->revents,&ufds->revents);
	  };
#ifdef IBCS_TRACE
	if ((ibcs_trace & TRACE_API) || ibcs_func_p->trace)
	  printk(KERN_DEBUG "iBCS: %d: Events=%d %x\n",
		current->pid, count, fds1->revents);
#endif
	kfree(fds1);
	current->state = TASK_RUNNING;
	return fdcount;
}
