/*
 * sound/midibuf.c
 *
 * Device file manager for /dev/midi#
 */
/*
 * Copyright (C) by Hannu Savolainen 1993-1997
 *
 * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL)
 * Version 2 (June 1991). See the "COPYING" file distributed with this software
 * for more info.
 */
/*
 * Thomas Sailer   : ioctl code reworked (vmalloc/vfree removed)
 */
#include <linux/config.h>

#define MIDIBUF_C

#include "sound_config.h"

#ifdef CONFIG_MIDI

/*
 * Don't make MAX_QUEUE_SIZE larger than 4000
 */

#define MAX_QUEUE_SIZE	4000

static struct wait_queue *midi_sleeper[MAX_MIDI_DEV] =
{NULL};
static volatile struct snd_wait midi_sleep_flag[MAX_MIDI_DEV] =
{
	{0}};
static struct wait_queue *input_sleeper[MAX_MIDI_DEV] =
{NULL};
static volatile struct snd_wait input_sleep_flag[MAX_MIDI_DEV] =
{
	{0}};

struct midi_buf
  {
	  int             len, head, tail;
	  unsigned char   queue[MAX_QUEUE_SIZE];
  };

struct midi_parms
  {
	  int             prech_timeout;	/*
						 * Timeout before the first ch
						 */
  };

static struct midi_buf *midi_out_buf[MAX_MIDI_DEV] =
{NULL};
static struct midi_buf *midi_in_buf[MAX_MIDI_DEV] =
{NULL};
static struct midi_parms parms[MAX_MIDI_DEV];

static void     midi_poll(unsigned long dummy);


static struct timer_list poll_timer =
{NULL, NULL, 0, 0, midi_poll};
static volatile int open_devs = 0;

#define DATA_AVAIL(q) (q->len)
#define SPACE_AVAIL(q) (MAX_QUEUE_SIZE - q->len)

#define QUEUE_BYTE(q, data) \
	if (SPACE_AVAIL(q)) \
	{ \
	  unsigned long flags; \
	  save_flags( flags);cli(); \
	  q->queue[q->tail] = (data); \
	  q->len++; q->tail = (q->tail+1) % MAX_QUEUE_SIZE; \
	  restore_flags(flags); \
	}

#define REMOVE_BYTE(q, data) \
	if (DATA_AVAIL(q)) \
	{ \
	  unsigned long flags; \
	  save_flags( flags);cli(); \
	  data = q->queue[q->head]; \
	  q->len--; q->head = (q->head+1) % MAX_QUEUE_SIZE; \
	  restore_flags(flags); \
	}

static void
drain_midi_queue(int dev)
{

	/*
	 * Give the Midi driver time to drain its output queues
	 */

	if (midi_devs[dev]->buffer_status != NULL)
		while (!signal_pending(current) &&
		       midi_devs[dev]->buffer_status(dev))

		  {
			  unsigned long   tlimit;

			  if (HZ / 10)
				  current->timeout = tlimit = jiffies + (HZ / 10);
			  else
				  tlimit = (unsigned long) -1;
			  midi_sleep_flag[dev].opts = WK_SLEEP;
			  interruptible_sleep_on(&midi_sleeper[dev]);
			  if (!(midi_sleep_flag[dev].opts & WK_WAKEUP))
			    {
				    if (jiffies >= tlimit)
					    midi_sleep_flag[dev].opts |= WK_TIMEOUT;
			    }
			  midi_sleep_flag[dev].opts &= ~WK_SLEEP;
		  };
}

static void
midi_input_intr(int dev, unsigned char data)
{
	if (midi_in_buf[dev] == NULL)
		return;

	if (data == 0xfe)	/*
				 * Active sensing
				 */
		return;		/*
				 * Ignore
				 */

	if (SPACE_AVAIL(midi_in_buf[dev]))
	  {
		  QUEUE_BYTE(midi_in_buf[dev], data);
		  if ((input_sleep_flag[dev].opts & WK_SLEEP))
		    {
			    input_sleep_flag[dev].opts = WK_WAKEUP;
			    wake_up(&input_sleeper[dev]);
		    };
	  }
}

static void
midi_output_intr(int dev)
{
	/*
	 * Currently NOP
	 */
}

static void
midi_poll(unsigned long dummy)
{
	unsigned long   flags;
	int             dev;

	save_flags(flags);
	cli();
	if (open_devs)
	  {
		  for (dev = 0; dev < num_midis; dev++)
			  if (midi_devs[dev] != NULL && midi_out_buf[dev] != NULL)
			    {
				    int             ok = 1;

				    while (DATA_AVAIL(midi_out_buf[dev]) && ok)
				      {
					      int             c = midi_out_buf[dev]->queue[midi_out_buf[dev]->head];

					      restore_flags(flags);	/* Give some time to others */
					      ok = midi_devs[dev]->outputc(dev, c);
					      save_flags(flags);
					      cli();
					      midi_out_buf[dev]->head = (midi_out_buf[dev]->head + 1) % MAX_QUEUE_SIZE;
					      midi_out_buf[dev]->len--;
				      }

				    if (DATA_AVAIL(midi_out_buf[dev]) < 100 &&
					(midi_sleep_flag[dev].opts & WK_SLEEP))
				      {
					      midi_sleep_flag[dev].opts = WK_WAKEUP;
					      wake_up(&midi_sleeper[dev]);
				      };
			    }
		  {
			  poll_timer.expires = (1) + jiffies;
			  add_timer(&poll_timer);
		  };		/*
				   * Come back later
				 */
	  }
	restore_flags(flags);
}

int
MIDIbuf_open(int dev, struct fileinfo *file)
{
	int             mode, err;

	dev = dev >> 4;
	mode = file->mode & O_ACCMODE;

	if (num_midis > MAX_MIDI_DEV)
	  {
		  printk("Sound: FATAL ERROR: Too many midi interfaces\n");
		  num_midis = MAX_MIDI_DEV;
	  }
	if (dev < 0 || dev >= num_midis || midi_devs[dev] == NULL)
	  {
		  printk("Sound: Nonexistent MIDI interface %d\n", dev);
		  return -ENXIO;
	  }
	/*
	   *    Interrupts disabled. Be careful
	 */

	if ((err = midi_devs[dev]->open(dev, mode,
				 midi_input_intr, midi_output_intr)) < 0)
	  {
		  return err;
	  }
	parms[dev].prech_timeout = 0;

	midi_in_buf[dev] = (struct midi_buf *) vmalloc(sizeof(struct midi_buf));

	if (midi_in_buf[dev] == NULL)
	  {
		  printk("midi: Can't allocate buffer\n");
		  midi_devs[dev]->close(dev);
		  return -EIO;
	  }
	midi_in_buf[dev]->len = midi_in_buf[dev]->head = midi_in_buf[dev]->tail = 0;

	midi_out_buf[dev] = (struct midi_buf *) vmalloc(sizeof(struct midi_buf));

	if (midi_out_buf[dev] == NULL)
	  {
		  printk("midi: Can't allocate buffer\n");
		  midi_devs[dev]->close(dev);
		  vfree(midi_in_buf[dev]);
		  midi_in_buf[dev] = NULL;
		  return -EIO;
	  }
	midi_out_buf[dev]->len = midi_out_buf[dev]->head = midi_out_buf[dev]->tail = 0;
	open_devs++;

	midi_sleep_flag[dev].opts = WK_NONE;
	input_sleep_flag[dev].opts = WK_NONE;

	if (open_devs < 2)	/* This was first open */
	  {
		  ;

		  {
			  poll_timer.expires = (1) + jiffies;
			  add_timer(&poll_timer);
		  };		/* Start polling */
	  }
	return err;
}

void
MIDIbuf_release(int dev, struct fileinfo *file)
{
	int             mode;
	unsigned long   flags;

	dev = dev >> 4;
	mode = file->mode & O_ACCMODE;

	if (dev < 0 || dev >= num_midis || midi_devs[dev] == NULL)
		return;

	save_flags(flags);
	cli();

	/*
	   * Wait until the queue is empty
	 */

	if (mode != OPEN_READ)
	  {
		  midi_devs[dev]->outputc(dev, 0xfe);	/*
							   * Active sensing to shut the
							   * devices
							 */

		  while (!signal_pending(current) &&
			 DATA_AVAIL(midi_out_buf[dev]))

		    {
			    unsigned long   tlimit;

			    if (0)
				    current->timeout = tlimit = jiffies + (0);
			    else
				    tlimit = (unsigned long) -1;
			    midi_sleep_flag[dev].opts = WK_SLEEP;
			    interruptible_sleep_on(&midi_sleeper[dev]);
			    if (!(midi_sleep_flag[dev].opts & WK_WAKEUP))
			      {
				      if (jiffies >= tlimit)
					      midi_sleep_flag[dev].opts |= WK_TIMEOUT;
			      }
			    midi_sleep_flag[dev].opts &= ~WK_SLEEP;
		    };		/*
				   * Sync
				 */

		  drain_midi_queue(dev);	/*
						 * Ensure the output queues are empty
						 */
	  }
	restore_flags(flags);

	midi_devs[dev]->close(dev);

	vfree(midi_in_buf[dev]);
	vfree(midi_out_buf[dev]);
	midi_in_buf[dev] = NULL;
	midi_out_buf[dev] = NULL;
	if (open_devs < 2)
		del_timer(&poll_timer);;
	open_devs--;
}

int
MIDIbuf_write(int dev, struct fileinfo *file, const char *buf, int count)
{
	unsigned long   flags;
	int             c, n, i;
	unsigned char   tmp_data;

	dev = dev >> 4;

	if (!count)
		return 0;

	save_flags(flags);
	cli();

	c = 0;

	while (c < count)
	  {
		  n = SPACE_AVAIL(midi_out_buf[dev]);

		  if (n == 0)	/*
				 * No space just now. We have to sleep
				 */
		    {

			    {
				    unsigned long   tlimit;

				    if (0)
					    current->timeout = tlimit = jiffies + (0);
				    else
					    tlimit = (unsigned long) -1;
				    midi_sleep_flag[dev].opts = WK_SLEEP;
				    interruptible_sleep_on(&midi_sleeper[dev]);
				    if (!(midi_sleep_flag[dev].opts & WK_WAKEUP))
				      {
					      if (jiffies >= tlimit)
						      midi_sleep_flag[dev].opts |= WK_TIMEOUT;
				      }
				    midi_sleep_flag[dev].opts &= ~WK_SLEEP;
			    };
			    if (signal_pending(current))
			      {
				      restore_flags(flags);
				      return -EINTR;
			      }
			    n = SPACE_AVAIL(midi_out_buf[dev]);
		    }
		  if (n > (count - c))
			  n = count - c;

		  for (i = 0; i < n; i++)
		    {
			    copy_from_user((char *) &tmp_data, &(buf)[c], 1);
			    QUEUE_BYTE(midi_out_buf[dev], tmp_data);
			    c++;
		    }
	  }

	restore_flags(flags);

	return c;
}


int
MIDIbuf_read(int dev, struct fileinfo *file, char *buf, int count)
{
	int             n, c = 0;
	unsigned long   flags;
	unsigned char   tmp_data;

	dev = dev >> 4;

	save_flags(flags);
	cli();

	if (!DATA_AVAIL(midi_in_buf[dev]))	/*
						 * No data yet, wait
						 */
	  {

		  {
			  unsigned long   tlimit;

			  if (parms[dev].prech_timeout)
				  current->timeout = tlimit = jiffies + (parms[dev].prech_timeout);
			  else
				  tlimit = (unsigned long) -1;
			  input_sleep_flag[dev].opts = WK_SLEEP;
			  interruptible_sleep_on(&input_sleeper[dev]);
			  if (!(input_sleep_flag[dev].opts & WK_WAKEUP))
			    {
				    if (jiffies >= tlimit)
					    input_sleep_flag[dev].opts |= WK_TIMEOUT;
			    }
			  input_sleep_flag[dev].opts &= ~WK_SLEEP;
		  };
		  if (signal_pending(current))
			  c = -EINTR;	/*
					   * The user is getting restless
					 */
	  }
	if (c == 0 && DATA_AVAIL(midi_in_buf[dev]))	/*
							 * Got some bytes
							 */
	  {
		  n = DATA_AVAIL(midi_in_buf[dev]);
		  if (n > count)
			  n = count;
		  c = 0;

		  while (c < n)
		    {
			    REMOVE_BYTE(midi_in_buf[dev], tmp_data);
			    {
				    char           *fixit = (char *) &tmp_data;

				    copy_to_user(&(buf)[c], fixit, 1);
			    };
			    c++;
		    }
	  }
	restore_flags(flags);

	return c;
}

int MIDIbuf_ioctl(int dev, struct fileinfo *file,
		  unsigned int cmd, caddr_t arg)
{
	int val;

	dev = dev >> 4;
	if (((cmd >> 8) & 0xff) == 'C') {
		  if (midi_devs[dev]->coproc)	/* Coprocessor ioctl */
			  return midi_devs[dev]->coproc->ioctl(midi_devs[dev]->coproc->devc, cmd, arg, 0);
		  printk("/dev/midi%d: No coprocessor for this device\n", dev);
		  return -ENXIO;
	} else
		switch (cmd) {
		case SNDCTL_MIDI_PRETIME:
			if (__get_user(val, (int *)arg))
				return -EFAULT;
			if (val < 0)
				val = 0;
			val = (HZ * val) / 10;
			parms[dev].prech_timeout = val;
			return __put_user(val, (int *)arg);
			
		default:
			if (!midi_devs[dev]->ioctl)
				return -EINVAL;
			return midi_devs[dev]->ioctl(dev, cmd, arg);
		}
}

int
MIDIbuf_select(int dev, struct fileinfo *file, int sel_type, poll_table * wait)
{
	dev = dev >> 4;

	switch (sel_type)
	  {
	  case SEL_IN:
		  if (!DATA_AVAIL(midi_in_buf[dev]))
		    {

			    input_sleep_flag[dev].opts = WK_SLEEP;
			    poll_wait(&input_sleeper[dev], wait);
			    return 0;
		    }
		  return 1;
		  break;

	  case SEL_OUT:
		  if (SPACE_AVAIL(midi_out_buf[dev]))
		    {

			    midi_sleep_flag[dev].opts = WK_SLEEP;
			    poll_wait(&midi_sleeper[dev], wait);
			    return 0;
		    }
		  return 1;
		  break;

	  case SEL_EX:
		  return 0;
	  }

	return 0;
}


void
MIDIbuf_init(void)
{
}

int
MIDIbuf_avail(int dev)
{
	return DATA_AVAIL (midi_in_buf[dev]);
}


#endif
