/*
 *  Abstract layer for MIDI v1.0 stream
 *  Copyright (c) by Jaroslav Kysela <perex@jcu.cz>
 *
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include "driver.h"
#include "minors.h"
#include "midi.h"
#include "control.h"
#include "info.h"

snd_rawmidi_t *snd_rawmidi_devices[ SND_CARDS * SND_RAWMIDI_DEVICES ];

snd_mutex_define_static( register );

static int snd_rawmidi_receive( snd_rawmidi_t *rmidi, char *buffer, int count );
static int snd_rawmidi_transmit( snd_rawmidi_t *rmidi, char *buffer, int count );

static unsigned short snd_rawmidi_file_flags( unsigned short f_flags )
{
  switch ( f_flags & O_ACCMODE ) {
    case O_WRONLY:      return SND_RAWMIDI_LFLG_OUTPUT;
    case O_RDONLY:      return SND_RAWMIDI_LFLG_INPUT;
    default:            return SND_RAWMIDI_LFLG_OPEN;
  }
}

static inline int snd_rawmidi_input_ok( snd_rawmidi_t *rmidi )
{
  unsigned long flags;
  int result;

  snd_spin_lock( rmidi, input, &flags );
  result = rmidi -> input.u.s.used >= rmidi -> input.u.s.used_min;
  snd_spin_unlock( rmidi, input, &flags );
  return result;
}

static inline int snd_rawmidi_output_ok( snd_rawmidi_t *rmidi )
{
  unsigned long flags;
  int result;

  snd_spin_lock( rmidi, output, &flags );
  result = rmidi -> output.u.s.used <= rmidi -> output.u.s.used_max &&
           rmidi -> output.u.s.size - rmidi -> output.u.s.used >= rmidi -> output.u.s.used_room;
  snd_spin_unlock( rmidi, output, &flags );
  return result;
}

static int snd_rawmidi_init_buffer( snd_rawmidi_t *rmidi, int direction )
{
  struct snd_stru_rawmidi_direction *pdir;

  pdir = direction == SND_RAWMIDI_LFLG_INPUT ? &rmidi -> input : &rmidi -> output;
  pdir -> mode = SND_RAWMIDI_MODE_STREAM;
  pdir -> reset = NULL;
  pdir -> u.s.size = PAGE_SIZE;
  pdir -> u.s.used_max = pdir -> u.s.size - 1;
  pdir -> u.s.used_room = 1;
  pdir -> u.s.used_min = 1;
  pdir -> u.s.buffer = NULL;
  if ( (pdir -> u.s.buffer = snd_malloc( pdir -> u.s.size )) == NULL ) {
    return -ENOMEM;
  }
  pdir -> u.s.head = pdir -> u.s.tail = pdir -> u.s.used = 0;
  return 0;
}

static int snd_rawmidi_done_buffer( snd_rawmidi_t *rmidi, int direction )
{
  struct snd_stru_rawmidi_direction *pdir;

  pdir = direction == SND_RAWMIDI_LFLG_INPUT ? &rmidi -> input : &rmidi -> output;
  if ( pdir -> u.s.buffer ) {
    snd_free( pdir -> u.s.buffer, pdir -> u.s.size );
    pdir -> u.s.buffer = NULL;
  }
  return 0;
}

static void snd_rawmidi_poll_output( unsigned long data )
{
  unsigned long flags;
  snd_rawmidi_t *rmidi;
  
  rmidi = (snd_rawmidi_t *)data;
  rmidi -> output.hw.io.write( rmidi );  
  snd_spin_lock( rmidi, output, &flags );
  if ( rmidi -> output.u.s.used > 0 ) {
    rmidi -> output.flags |= SND_RAWMIDI_FLG_TIMER;
    rmidi -> output.u.s.timer.expires = 1 + jiffies;
    add_timer( &rmidi -> output.u.s.timer );
  } else {
    del_timer( &rmidi -> output.u.s.timer );
    rmidi -> output.flags &= ~SND_RAWMIDI_FLG_TIMER;
  }
  snd_spin_unlock( rmidi, output, &flags );
}

static void snd_rawmidi_trigger_output( snd_rawmidi_t *rmidi, int up )
{
  unsigned long flags;
  int flg;

#if 0
  snd_printk( "trigger_output - %i\n", up );
#endif
  if ( !(rmidi -> output.hw.flags & SND_RAWMIDI_HW_POLL) ) {
    flg = up ? SND_RAWMIDI_FLG_TRIGGER : 0;
    snd_spin_lock( rmidi, output, &flags );
    if ( (rmidi -> output.flags & SND_RAWMIDI_FLG_TRIGGER) != flg ) {
      if ( up ) {
        rmidi -> output.flags |= SND_RAWMIDI_FLG_TRIGGER;
      } else {
        rmidi -> output.flags &= ~SND_RAWMIDI_FLG_TRIGGER;
      }
      snd_spin_unlock( rmidi, output, &flags );
      rmidi -> output.hw.trigger( rmidi, up );
      return;
    }
    snd_spin_unlock( rmidi, output, &flags );
  } else {
    snd_spin_lock( rmidi, output, &flags );
    if ( up ) {
      if ( !(rmidi -> output.flags & SND_RAWMIDI_FLG_TIMER) ) {
        snd_spin_unlock( rmidi, output, &flags );
        rmidi -> output.hw.io.write( rmidi );
        snd_spin_lock( rmidi, output, &flags );
        if ( rmidi -> output.u.s.used > 0 ) {
          rmidi -> output.flags |= SND_RAWMIDI_FLG_TIMER;
          rmidi -> output.u.s.timer.expires = 1 + jiffies;
          add_timer( &rmidi -> output.u.s.timer );
        }
      }
    } else {
      if ( rmidi -> output.flags & SND_RAWMIDI_FLG_TIMER ) {
        rmidi -> output.flags &= ~SND_RAWMIDI_FLG_TIMER;
        del_timer( &rmidi -> output.u.s.timer );
      }
    }
    snd_spin_unlock( rmidi, output, &flags );
  }
}

static void snd_rawmidi_poll_input( unsigned long data )
{
  unsigned long flags;
  snd_rawmidi_t *rmidi;
  
  rmidi = (snd_rawmidi_t *)data;
  rmidi -> output.hw.io.read( rmidi );  
  snd_spin_lock( rmidi, input, &flags );
  rmidi -> input.flags |= SND_RAWMIDI_FLG_TIMER;
  rmidi -> input.u.s.timer.data = (unsigned long)rmidi;
  rmidi -> input.u.s.timer.expires = 1 + jiffies;
  add_timer( &rmidi -> input.u.s.timer );
  snd_spin_unlock( rmidi, input, &flags );
}

static void snd_rawmidi_trigger_input( snd_rawmidi_t *rmidi, int up )
{
  unsigned long flags;
  int flg;

#if 0
  snd_printk( "trigger_input - %i\n", up );
#endif
  if ( !(rmidi -> input.hw.flags & SND_RAWMIDI_HW_POLL) ) {
    flg = up ? SND_RAWMIDI_FLG_TRIGGER : 0;
    snd_spin_lock( rmidi, input, &flags );
    if ( (rmidi -> input.flags & SND_RAWMIDI_FLG_TRIGGER) != flg ) {
      if ( up ) {
        rmidi -> input.flags |= SND_RAWMIDI_FLG_TRIGGER;
      } else {
        rmidi -> input.flags &= ~SND_RAWMIDI_FLG_TRIGGER;
      }
      snd_spin_unlock( rmidi, input, &flags );
      rmidi -> input.hw.trigger( rmidi, up );
      return;
    }
    snd_spin_unlock( rmidi, input, &flags );
  } else {
    snd_spin_lock( rmidi, input, &flags );
    if ( up ) {
      if ( !(rmidi -> input.flags & SND_RAWMIDI_FLG_TIMER) ) {
        snd_spin_unlock( rmidi, input, &flags );
        rmidi -> input.hw.io.write( rmidi );
        snd_spin_lock( rmidi, input, &flags );
        rmidi -> input.flags |= SND_RAWMIDI_FLG_TIMER;
        rmidi -> input.u.s.timer.expires = 1 + jiffies;
        add_timer( &rmidi -> input.u.s.timer );
      }
    } else {
      if ( rmidi -> input.flags & SND_RAWMIDI_FLG_TIMER ) {
        rmidi -> input.flags &= ~SND_RAWMIDI_FLG_TIMER;
        del_timer( &rmidi -> input.u.s.timer );
      }
    }
    snd_spin_unlock( rmidi, input, &flags );
  }
}

static int snd_rawmidi_drain_output( snd_rawmidi_t *rmidi )
{
  snd_rawmidi_trigger_output( rmidi, 0 );
  /* interrupts aren't enabled at this moment, so spinlock isn't needed */
  rmidi -> output.u.s.used = rmidi -> output.u.s.head = rmidi -> output.u.s.tail = 0;
  return 0;
}

static int snd_rawmidi_flush_output( snd_rawmidi_t *rmidi )
{
  unsigned long flags;
  int err;

  err = 0;
  while ( rmidi -> output.u.s.used > 0 ) {
    snd_spin_lock( rmidi, output_sleep, &flags );
    rmidi -> output.flags |= SND_RAWMIDI_FLG_FLUSH;
    snd_sleep( rmidi, output, 10 * HZ );
    rmidi -> output.flags &= ~SND_RAWMIDI_FLG_FLUSH;
    snd_spin_unlock( rmidi, output_sleep, &flags );
    if ( snd_sleep_abort( rmidi, output ) ) {
      err = -EINTR;
      break;
    }
    if ( rmidi -> output.u.s.used >= rmidi -> output.u.s.size && snd_timeout( rmidi, output ) ) {
      err = -EIO;
      break;
    }
  }
  snd_rawmidi_drain_output( rmidi );
  return err;
}

static int snd_rawmidi_flush_input( snd_rawmidi_t *rmidi )
{
  snd_rawmidi_trigger_input( rmidi, 0 );
  /* interrupts aren't enabled at this moment, so spinlock isn't needed */
  rmidi -> input.u.s.used = rmidi -> input.u.s.head = rmidi -> input.u.s.tail = 0;
  return 0;
}

static int snd_rawmidi_open( unsigned short minor, int cardnum, int device, struct file *file )
{
  unsigned short fflags;
  snd_rawmidi_t *rmidi;
  int err;

  fflags = snd_rawmidi_file_flags( file -> f_flags );
  rmidi = snd_rawmidi_devices[ (cardnum * SND_RAWMIDI_DEVICES) + device ];
  if ( !rmidi ) return -ENODEV;
  snd_mutex_down( rmidi, open );
  if ( fflags & SND_RAWMIDI_LFLG_INPUT ) {
    if ( !(rmidi -> info_flags & SND_RAWMIDI_INFO_INPUT) ) {
      snd_mutex_up( rmidi, open );
      return -ENXIO;
    }
  }
  if ( fflags & SND_RAWMIDI_LFLG_OUTPUT ) {
    if ( !(rmidi -> info_flags & SND_RAWMIDI_INFO_OUTPUT) ) {
      snd_mutex_up( rmidi, open );
      return -ENXIO;
    }
  }
  if ( rmidi -> flags & fflags ) {
    snd_mutex_up( rmidi, open );
    return -EBUSY;
  }
  if ( minor < SND_MINOR_BEGIN )
    file -> f_flags |= O_NONBLOCK;
  if ( fflags & SND_RAWMIDI_LFLG_INPUT ) {
    if ( snd_rawmidi_init_buffer( rmidi, SND_RAWMIDI_LFLG_INPUT ) < 0 ) {
      snd_mutex_up( rmidi, open );
      return -ENOMEM;
    }
    if ( (err = rmidi -> input.hw.open( rmidi )) < 0 ) {
      snd_rawmidi_done_buffer( rmidi, SND_RAWMIDI_LFLG_INPUT );
      return err;
    }
    rmidi -> input.data = snd_rawmidi_receive;
    if ( minor < SND_MINOR_BEGIN )
      rmidi -> input.flags |= SND_RAWMIDI_FLG_OSS;
     else
      rmidi -> input.flags &= ~SND_RAWMIDI_FLG_OSS;
  }
  if ( fflags & SND_RAWMIDI_LFLG_OUTPUT ) {
    if ( snd_rawmidi_init_buffer( rmidi, SND_RAWMIDI_LFLG_OUTPUT ) < 0 ) {
      if ( fflags & SND_RAWMIDI_LFLG_INPUT ) {
        rmidi -> input.hw.close( rmidi );
        snd_rawmidi_done_buffer( rmidi, SND_RAWMIDI_LFLG_INPUT );
        rmidi -> input.data = NULL;
      }
      snd_mutex_up( rmidi, open );
      return -ENOMEM;
    }
    if ( (err = rmidi -> output.hw.open( rmidi )) < 0 ) {
      if ( fflags & SND_RAWMIDI_LFLG_INPUT ) {
        rmidi -> input.hw.close( rmidi );
        snd_rawmidi_done_buffer( rmidi, SND_RAWMIDI_LFLG_INPUT );
      }
      snd_mutex_up( rmidi, open );
      return err;
    }
    rmidi -> output.data = snd_rawmidi_transmit;
    if ( minor < SND_MINOR_BEGIN )
      rmidi -> output.flags |= SND_RAWMIDI_FLG_OSS;
     else
      rmidi -> output.flags &= ~SND_RAWMIDI_FLG_OSS;
  }
  rmidi -> flags |= fflags;
  file -> private_data = rmidi;
  MOD_INC_USE_COUNT;
  rmidi -> card -> use_inc( rmidi -> card );
  snd_mutex_up( rmidi, open );
  return 0;
}

static int snd_rawmidi_release( unsigned short minor, int cardnum, int device, struct file *file )
{
  unsigned short fflags;
  snd_rawmidi_t *rmidi;

  fflags = snd_rawmidi_file_flags( file -> f_flags );
  rmidi = (snd_rawmidi_t *)file -> private_data;
  if ( !rmidi ) {
    MOD_DEC_USE_COUNT;
    return -ENODEV;
  }
  snd_mutex_down( rmidi, open );
  if ( fflags & SND_RAWMIDI_LFLG_INPUT ) {
    snd_rawmidi_trigger_input( rmidi, 0 );
    rmidi -> input.hw.close( rmidi );
    rmidi -> input.data = NULL;
    snd_rawmidi_done_buffer( rmidi, SND_RAWMIDI_LFLG_INPUT );
  }
  if ( fflags & SND_RAWMIDI_LFLG_OUTPUT ) {
    if ( !(file -> f_flags & O_NONBLOCK) )
      snd_rawmidi_flush_output( rmidi );
    snd_rawmidi_trigger_output( rmidi, 0 );
    rmidi -> output.hw.close( rmidi );
    rmidi -> output.data = NULL;
    snd_rawmidi_done_buffer( rmidi, SND_RAWMIDI_LFLG_OUTPUT );
  }
  rmidi -> flags &= ~fflags;
  rmidi -> card -> use_dec( rmidi -> card );
  MOD_DEC_USE_COUNT;
  snd_mutex_up( rmidi, open );
  return 0;
}

static int snd_rawmidi_info( snd_rawmidi_t *rmidi, snd_rawmidi_info_t *_info )
{
  snd_rawmidi_info_t info;

  if ( verify_area( VERIFY_WRITE, _info, sizeof( snd_rawmidi_info_t ) ) ) return -EFAULT;
  info.type = rmidi -> card -> type;
  info.flags = rmidi -> info_flags;
  strcpy( info.id, rmidi -> id );
  strcpy( info.name, rmidi -> name );
  copy_to_user( _info, &info, sizeof( snd_rawmidi_info_t ) );
  return 0;
}

static int snd_rawmidi_output_params( snd_rawmidi_t *rmidi, snd_rawmidi_output_params_t *_params )
{
  snd_rawmidi_output_params_t params;
  char *newbuf;

  if ( verify_area( VERIFY_READ, _params, sizeof( snd_rawmidi_output_params_t ) ) ) return -EFAULT;
  copy_from_user( &params, _params, sizeof( snd_rawmidi_output_params_t ) );
  snd_rawmidi_flush_output( rmidi );
  if ( params.size < 32 || params.size > 1024L * 1024L ) return -EINVAL;
  if ( params.room < 1 || params.room >= params.size ) return -EINVAL;
  if ( params.max < 1 || params.max >= params.size || params.max < params.room ) return -EINVAL;
  if ( (newbuf = (char *)snd_malloc( params.size )) == NULL ) return -ENOMEM;
  snd_free( rmidi -> output.u.s.buffer, rmidi -> output.u.s.size );
  rmidi -> output.u.s.buffer = newbuf;
  rmidi -> output.u.s.size = params.size;
  rmidi -> output.u.s.used_max = params.max;
  rmidi -> output.u.s.used_room = params.room;
  return 0;
}

static int snd_rawmidi_input_params( snd_rawmidi_t *rmidi, snd_rawmidi_input_params_t *_params )
{
  snd_rawmidi_input_params_t params;
  char *newbuf;

  if ( verify_area( VERIFY_READ, _params, sizeof( snd_rawmidi_input_params_t ) ) ) return -EFAULT;
  copy_from_user( &params, _params, sizeof( snd_rawmidi_input_params_t ) );
  snd_rawmidi_flush_input( rmidi );
  if ( params.size < 32 || params.size > 1024L * 1024L ) return -EINVAL;
  if ( params.min < 1 || params.min >= params.size ) return -EINVAL;
  if ( (newbuf = (char *)snd_malloc( params.size )) == NULL ) return -ENOMEM;
  snd_free( rmidi -> input.u.s.buffer, rmidi -> input.u.s.size );
  rmidi -> input.u.s.buffer = newbuf;
  rmidi -> input.u.s.size = params.size;
  rmidi -> input.u.s.used_min = params.min;
  return 0;
}

static int snd_rawmidi_output_status( snd_rawmidi_t *rmidi, snd_rawmidi_output_status_t *_status )
{
  unsigned long flags;
  snd_rawmidi_output_status_t status;

  if ( verify_area( VERIFY_WRITE, _status, sizeof( snd_rawmidi_output_status_t ) ) ) return -EFAULT;
  snd_rawmidi_flush_output( rmidi );
  snd_spin_lock( rmidi, output, &flags );
  status.size = rmidi -> output.u.s.size;
  status.count = rmidi -> output.u.s.size - rmidi -> output.u.s.used;
  status.queue = rmidi -> output.u.s.used;
  snd_spin_unlock( rmidi, output, &flags );
  copy_from_user( &status, _status, sizeof( snd_rawmidi_output_status_t ) );
  return 0;
}

static int snd_rawmidi_input_status( snd_rawmidi_t *rmidi, snd_rawmidi_input_status_t *_status )
{
  unsigned long flags;
  snd_rawmidi_input_status_t status;

  if ( verify_area( VERIFY_WRITE, _status, sizeof( snd_rawmidi_input_status_t ) ) ) return -EFAULT;
  snd_rawmidi_flush_input( rmidi );
  snd_spin_lock( rmidi, input, &flags );
  status.size = rmidi -> input.u.s.size;
  status.count = rmidi -> input.u.s.used;
  status.free = rmidi -> input.u.s.size - rmidi -> input.u.s.used;
  status.overrun = rmidi -> input.u.s.xruns; rmidi -> input.u.s.xruns = 0;
  snd_spin_unlock( rmidi, input, &flags );
  copy_from_user( &status, _status, sizeof( snd_rawmidi_input_status_t ) );
  return 0;
}

static int snd_rawmidi_ioctl( struct file *file, unsigned int cmd, unsigned long arg )
{
  snd_rawmidi_t *rmidi;
  unsigned short fflags;

  rmidi = (snd_rawmidi_t *)file -> private_data;
  if ( !rmidi ) return -EIO;
  if ( ( ( cmd >> 8 ) & 0xff ) != 'W' ) return -EIO;
  fflags = snd_rawmidi_file_flags( file -> f_flags );
  switch ( cmd ) {
    case SND_RAWMIDI_IOCTL_PVERSION:
      return snd_ioctl_out( (long *)arg, SND_RAWMIDI_VERSION );
    case SND_RAWMIDI_IOCTL_INFO:
      return snd_rawmidi_info( rmidi, (snd_rawmidi_info_t *)arg );
    case SND_RAWMIDI_IOCTL_OUTPUT_PARAMS:
      if ( !(fflags & SND_RAWMIDI_LFLG_OUTPUT) ) return -EIO;
      return snd_rawmidi_output_params( rmidi, (snd_rawmidi_output_params_t *)arg );
    case SND_RAWMIDI_IOCTL_INPUT_PARAMS:
      if ( !(fflags & SND_RAWMIDI_LFLG_INPUT) ) return -EIO;
      return snd_rawmidi_input_params( rmidi, (snd_rawmidi_input_params_t *)arg );
    case SND_RAWMIDI_IOCTL_OUTPUT_STATUS:
      if ( !(fflags & SND_RAWMIDI_LFLG_OUTPUT) ) return -EIO;
      return snd_rawmidi_output_status( rmidi, (snd_rawmidi_output_status_t *)arg );
    case SND_RAWMIDI_IOCTL_INPUT_STATUS:
      if ( !(fflags & SND_RAWMIDI_LFLG_INPUT) ) return -EIO;
      return snd_rawmidi_input_status( rmidi, (snd_rawmidi_input_status_t *)arg );
    case SND_RAWMIDI_IOCTL_DRAIN_OUTPUT:
      if ( !(fflags & SND_RAWMIDI_LFLG_OUTPUT) ) return -EIO;
      return snd_rawmidi_drain_output( rmidi );
    case SND_RAWMIDI_IOCTL_FLUSH_OUTPUT:
      if ( !(fflags & SND_RAWMIDI_LFLG_OUTPUT) ) return -EIO;
      return snd_rawmidi_flush_output( rmidi );
    case SND_RAWMIDI_IOCTL_FLUSH_INPUT:
      if ( !(fflags & SND_RAWMIDI_LFLG_INPUT) ) return -EIO;
      return snd_rawmidi_flush_input( rmidi );
#ifdef SNDCFG_DEBUG
    default:
      snd_printk( "rawmidi: unknown command = 0x%x\n", cmd );
#endif
  }
  return -EIO;
}

int snd_rawmidi_control_ioctl( snd_card_t *card, snd_control_t *control, unsigned int cmd, unsigned long arg )
{
  unsigned int tmp;
  int idx;

  tmp = card -> number << 2;
  switch ( cmd ) {
    case SND_CTL_IOCTL_HW_INFO:
      {
        struct snd_ctl_hw_info *ptr = (struct snd_ctl_hw_info *)arg;
        
        ptr -> mididevs = 0;
        for ( idx = SND_RAWMIDI_DEVICES - 1; idx >= 0; idx-- ) {
          if ( snd_rawmidi_devices[ tmp + idx ] ) {
            ptr -> mididevs = idx + 1;
            break;
          }
        }
        return 0;
      }
    case SND_CTL_IOCTL_RAWMIDI_DEVICE:
      {
        int val = snd_ioctl_in( (long *)arg );
        if ( val < 0 || val >= SND_RAWMIDI_DEVICES ) return -EINVAL;
        if ( !snd_rawmidi_devices[ tmp + val ] ) return -EINVAL;
        control -> rawmidi_device = val;
        return 0;
      }
    case SND_CTL_IOCTL_RAWMIDI_INFO:
      return snd_rawmidi_info( snd_rawmidi_devices[ tmp + control -> rawmidi_device ], (snd_rawmidi_info_t *)arg );
  }
  return -EAGAIN;
}

static int snd_rawmidi_receive( snd_rawmidi_t *rmidi, char *buffer, int count )
{
  unsigned long flags;
  int count1;

#if 0
  snd_printk( "rx: count = %i\n", count );
#endif
  if ( !rmidi -> input.u.s.buffer ) {
    snd_printd( "snd_rawmidi_receive: input isn't active!!!\n" );
  }
  snd_spin_lock( rmidi, input, &flags );
  count1 = rmidi -> input.u.s.size - rmidi -> input.u.s.head;
  if ( count1 > count ) count1 = count;
  if ( count1 > rmidi -> input.u.s.size - rmidi -> input.u.s.used ) {
    count1 = rmidi -> input.u.s.size - rmidi -> input.u.s.used;
  }
  memcpy( rmidi -> input.u.s.buffer + rmidi -> input.u.s.head, buffer, count1 );
  rmidi -> input.u.s.head += count1;
  rmidi -> input.u.s.head %= rmidi -> input.u.s.size;
  rmidi -> input.u.s.used += count1;
  count -= count1;
  if ( count > 0 ) {
    count1 = count;
    if ( count > rmidi -> input.u.s.size - rmidi -> input.u.s.used ) {
      count1 = rmidi -> input.u.s.size - rmidi -> input.u.s.used;
      rmidi -> input.u.s.xruns = count - count1;
    }
    if ( count1 > 0 ) {
      memcpy( rmidi -> input.u.s.buffer, buffer + count1, count1 );
      rmidi -> input.u.s.head = count1;
      rmidi -> input.u.s.used += count1;
    }
  }
  snd_spin_unlock( rmidi, input, &flags );
  if ( (rmidi -> input.flags & SND_RAWMIDI_FLG_SLEEP) && snd_rawmidi_input_ok( rmidi ) ) {
    snd_wakeup( rmidi, input );
  }
#if 0
  snd_printk( "rx: head = %i, tail = %i, used = %i, size = %i\n", rmidi -> input.head, rmidi -> input.tail, rmidi -> input.used, rmidi -> input.size );
#endif
  return 0;
}

static long snd_rawmidi_read( struct file *file, char *buf, long count )
{
  unsigned long flags;
  long result;
  int count1;
  snd_rawmidi_t *rmidi;

  rmidi = (snd_rawmidi_t *)file -> private_data;
  if ( !rmidi ) return -EIO;
  if ( verify_area( VERIFY_WRITE, buf, count ) ) return -EFAULT;
  result = 0;
  snd_rawmidi_trigger_input( rmidi, 1 );
  while ( count > 0 ) {
    while ( !snd_rawmidi_input_ok( rmidi ) ) {
      if ( file -> f_flags & O_NONBLOCK ) return result;
      snd_spin_lock( rmidi, input_sleep, &flags );
      rmidi -> input.flags |= SND_RAWMIDI_FLG_SLEEP;
      snd_sleep( rmidi, input, 10 * HZ );
      rmidi -> input.flags &= ~SND_RAWMIDI_FLG_SLEEP;
      snd_spin_unlock( rmidi, input_sleep, &flags );
      if ( snd_sleep_abort( rmidi, input ) ) return result;
      if ( !rmidi -> input.u.s.used && snd_timeout( rmidi, input ) ) return -EIO;
    }
    while ( count > 0 && rmidi -> input.u.s.used ) {
      count1 = rmidi -> input.u.s.size - rmidi -> input.u.s.tail;
      if ( count1 > count ) count1 = count;
      snd_spin_lock( rmidi, input, &flags );
      if ( count1 > rmidi -> input.u.s.used ) count1 = rmidi -> input.u.s.used;
      snd_spin_unlock( rmidi, input, &flags );
      copy_to_user( buf, rmidi -> input.u.s.buffer + rmidi -> input.u.s.tail, count1 );
      snd_spin_lock( rmidi, input, &flags );
      rmidi -> input.u.s.tail += count1;
      rmidi -> input.u.s.tail %= rmidi -> input.u.s.size;
      rmidi -> input.u.s.used -= count1;
      snd_spin_unlock( rmidi, input, &flags );
      result += count1;
      buf += count1;
      count -= count1;
    }
  }
  return result;
}

static int snd_rawmidi_transmit( snd_rawmidi_t *rmidi, char *buffer, int count )
{
  unsigned long flags;
  int result, count1;

  result = 0;
#if 0
  snd_printk( "transmit: count = %i\n", count );
#endif
  if ( !rmidi -> output.u.s.buffer ) {
    snd_printd( "snd_rawmidi_transmit: output isn't active!!!\n" );
    return 0;
  }
  snd_spin_lock( rmidi, output, &flags );
  count1 = rmidi -> output.u.s.size - rmidi -> input.u.s.tail;
  if ( count1 > count ) count1 = count;
  if ( count1 > rmidi -> output.u.s.used ) count1 = rmidi -> output.u.s.used;
  if ( !count1 ) {
    snd_spin_unlock( rmidi, output, &flags );
    snd_rawmidi_trigger_output( rmidi, 0 );
    if ( rmidi -> output.flags & (SND_RAWMIDI_FLG_FLUSH|SND_RAWMIDI_FLG_SLEEP) )
      snd_wakeup( rmidi, output );
    return 0;
  }
  memcpy( buffer, rmidi -> output.u.s.buffer + rmidi -> output.u.s.tail, count1 );
  rmidi -> output.u.s.tail += count1;
  rmidi -> output.u.s.tail %= rmidi -> output.u.s.size;
  rmidi -> output.u.s.used -= count1;
  count -= count1;
  result += count1;
  if ( count > 0 ) {
    memcpy( buffer + count1, rmidi -> output.u.s.buffer, count );
    rmidi -> output.u.s.tail = count;
    rmidi -> output.u.s.used -= count;
    result += count;
  }
  if ( !rmidi -> output.u.s.used ) {
    snd_spin_unlock( rmidi, output, &flags );
    snd_rawmidi_trigger_output( rmidi, 0 );
    if ( rmidi -> output.flags & (SND_RAWMIDI_FLG_FLUSH|SND_RAWMIDI_FLG_SLEEP) )
      snd_wakeup( rmidi, output );
  } else if ( (rmidi -> output.flags & SND_RAWMIDI_FLG_SLEEP) && snd_rawmidi_output_ok( rmidi ) ) {
    snd_spin_unlock( rmidi, output, &flags );
    snd_wakeup( rmidi, output );
  }
#if 0
  snd_printk( "write1: head = %i, tail = %i, used = %i, size = %i\n", rmidi -> output.u.s.head, rmidi -> output.u.s.tail, rmidi -> output.u.s.used, rmidi -> output.u.s.size );
  snd_printk( "transmit result = %i\n", result );
#endif
  return result;
}

static long snd_rawmidi_write( struct file *file, const char *buf, long count )
{
  unsigned long flags;
  long result;
  int count1;
  snd_rawmidi_t *rmidi;

  rmidi = (snd_rawmidi_t *)file -> private_data;
  if ( !rmidi ) return -EIO;
  result = 0;
  while ( count > 0 ) {
    while ( rmidi -> output.u.s.used > 0 ) {
      snd_spin_lock( rmidi, output_sleep, &flags );
      rmidi -> output.flags |= SND_RAWMIDI_FLG_SLEEP;
      snd_sleep( rmidi, output, 10 * HZ );
      rmidi -> output.flags &= ~SND_RAWMIDI_FLG_SLEEP;
      snd_spin_unlock( rmidi, output_sleep, &flags );
      if ( snd_sleep_abort( rmidi, output ) ) return -EINTR;
      if ( rmidi -> output.u.s.used >= rmidi -> output.u.s.size && snd_timeout( rmidi, output ) ) return -EIO;
    }
    while ( count > 0 && rmidi -> output.u.s.used < rmidi -> output.u.s.size ) {
      count1 = rmidi -> output.u.s.size - rmidi -> output.u.s.head;
      if ( count1 > count ) count1 = count;
      snd_spin_lock( rmidi, output, &flags );
      if ( count1 > rmidi -> output.u.s.size - rmidi -> output.u.s.used )
        count1 = rmidi -> output.u.s.size - rmidi -> output.u.s.used;
      snd_spin_unlock( rmidi, output, &flags );
      copy_from_user( rmidi -> output.u.s.buffer + rmidi -> output.u.s.head, buf, count1 );
      snd_spin_lock( rmidi, output, &flags );
      rmidi -> output.u.s.head += count1;
      rmidi -> output.u.s.head %= rmidi -> output.u.s.size;
      rmidi -> output.u.s.used += count1;
      snd_spin_unlock( rmidi, output, &flags );
      result += count1;
      buf += count1;
      count -= count1;
    }
    snd_rawmidi_trigger_output( rmidi, 1 );
  }
  return result;
}

#ifdef SND_POLL
static unsigned int snd_rawmidi_poll( struct file *file, poll_table *wait )
{
  snd_rawmidi_t *rmidi;
  unsigned long flags;
  unsigned int mask;
  unsigned short fflags;

  fflags = snd_rawmidi_file_flags( file -> f_flags );
  rmidi = (snd_rawmidi_t *)file -> private_data;
  if ( !rmidi ) return 0;

  if ( fflags & SND_RAWMIDI_LFLG_INPUT ) {
    snd_rawmidi_trigger_input( rmidi, 1 );
    snd_spin_lock( rmidi, input_sleep, &flags );
    rmidi -> input.flags |= SND_RAWMIDI_FLG_SLEEP;
    snd_poll_wait( file, rmidi, input, wait );
    snd_spin_unlock( rmidi, input_sleep, &flags );
  }
  if ( fflags & SND_RAWMIDI_LFLG_OUTPUT ) {
    snd_spin_lock( rmidi, output_sleep, &flags );
    rmidi -> output.flags |= SND_RAWMIDI_FLG_SLEEP;
    snd_poll_wait( file, rmidi, output, wait );
    snd_spin_unlock( rmidi, output_sleep, &flags );
  }

  mask = 0;
  if ( fflags & SND_RAWMIDI_LFLG_INPUT ) {
    if ( snd_rawmidi_input_ok( rmidi ) ) mask |= POLLIN | POLLRDNORM;
  }
  if ( fflags & SND_RAWMIDI_LFLG_OUTPUT ) {
    if ( snd_rawmidi_output_ok( rmidi ) ) mask |= POLLOUT | POLLWRNORM;
  }

  return mask;
}
#else
static int snd_rawmidi_select( struct file *file, int sel_type, select_table *wait )
{
  snd_rawmidi_t *rmidi;
  unsigned long flags;
  unsigned short fflags;

  fflags = snd_rawmidi_file_flags( file -> f_flags );
  rmidi = (snd_rawmidi_t *)file -> private_data;
  if ( !rmidi ) return -EIO;
  switch ( sel_type ) {
    case SEL_IN:
      if ( !(fflags & SND_RAWMIDI_LFLG_INPUT) ) return 0;
      snd_rawmidi_trigger_input( rmidi, 1 );
      snd_spin_lock( pdir, input_sleep, &flags );
      if ( !snd_rawmidi_input_ok( rmidi ) )
        {
          rmidi -> input.flags |= SND_RAWMIDI_FLG_SLEEP;
          snd_select_wait( rmidi, input, wait );
          snd_spin_unlock( rmidi, input_sleep, &flags );
          return 0;
        }
      rmidi -> input.flags &= ~SND_RAWMIDI_FLG_SLEEP;
      snd_spin_unlock( rmidi, input_sleep, &flags );
      return 1;
    case SEL_OUT:
      if ( !(fflags & SND_RAWMIDI_LFLG_OUTPUT) ) return 0;
      snd_spin_lock( rmidi, output_sleep, &flags );
      if ( !snd_rawmidi_output_ok( rmidi ) )
        {
          rmidi -> output.flags |= SND_RAWMIDI_FLG_SLEEP;
          snd_select_wait( rmidi, output, wait );
          snd_spin_unlock( rmidi, output_sleep, &flags );
          return 0;
        }
      rmidi -> output.flags &= ~SND_RAWMIDI_FLG_SLEEP;
      snd_spin_unlock( rmidi, output_sleep, &flags );
      return 1;
    case SEL_EX:
      break;
  }
  return 0;
}
#endif

/*
 *
 */

void snd_rawmidi_command_reset( snd_rawmidi_t *rmidi, int direction )
{
  struct snd_stru_rawmidi_direction *pdir;

  pdir = direction == SND_RAWMIDI_LFLG_INPUT ? &rmidi -> input : &rmidi -> output;
  pdir -> u.p.cused = 0;
  pdir -> u.p.cprev = 0;
}

/*
 *
 */
 
static void snd_rawmidi_proc_info_read( snd_info_buffer_t *buffer, void *private_data )
{
  snd_rawmidi_t *rmidi;
  
  rmidi = (snd_rawmidi_t *)private_data;
  snd_iprintf( buffer, "%s\n\n", rmidi -> name );
  if ( rmidi -> info_flags & SND_RAWMIDI_INFO_OUTPUT ) {
    if ( rmidi -> flags & SND_RAWMIDI_LFLG_OUTPUT ) {
      snd_iprintf( buffer,
"Output\n"
"  Mode         : %s\n"
"  Buffer size  : %d\n",
	rmidi -> output.flags & SND_RAWMIDI_FLG_OSS ? "OSS compatible" : "native",
	rmidi -> output.u.s.size );
    } else {
      snd_iprintf( buffer, "Output isn't active.\n" );
    }
  }
  if ( rmidi -> info_flags & SND_RAWMIDI_INFO_INPUT ) {
    if ( rmidi -> flags & SND_RAWMIDI_LFLG_INPUT ) {
      snd_iprintf( buffer,
"Input\n"
"  Mode         : %s\n"
"  Buffer size  : %d\n"
"  Overruns     : %d\n",
	rmidi -> input.flags & SND_RAWMIDI_FLG_OSS ? "OSS compatible" : "native",
	rmidi -> input.u.s.size,
	rmidi -> input.u.s.xruns );
    } else {
      snd_iprintf( buffer, "Input isn't active.\n" );
    }
  }
}

/*
 *  Register functions
 */

static snd_minor_t snd_rawmidi_reg = {
  "raw midi",
  
  NULL,				/* unregister */
  
  NULL,				/* lseek */
  snd_rawmidi_read,		/* read */
  snd_rawmidi_write,		/* write */
  snd_rawmidi_open,		/* open */
  snd_rawmidi_release,		/* release */
#ifdef SND_POLL
  snd_rawmidi_poll,		/* poll */
#else
  snd_rawmidi_select,		/* select */
#endif
  snd_rawmidi_ioctl,		/* ioctl */
  NULL,				/* mmap */
};

static snd_minor_t snd_rawmidi_oss_reg = {
  "raw midi",

  NULL,                         /* unregister */

  NULL,                         /* lseek */
  snd_rawmidi_read,             /* read */
  snd_rawmidi_write,            /* write */
  snd_rawmidi_open,             /* open */
  snd_rawmidi_release,          /* release */
#ifdef SND_POLL
  snd_rawmidi_poll,             /* poll */
#else
  snd_rawmidi_select,           /* select */
#endif
  snd_rawmidi_ioctl,            /* ioctl */
  NULL,				/* mmap */
};

snd_rawmidi_t *snd_rawmidi_new_device( snd_card_t *card, char *id )
{
  snd_rawmidi_t *rmidi;

  if ( !card ) return NULL;
  rmidi = (snd_rawmidi_t *)snd_malloc( sizeof( snd_rawmidi_t ) );
  if ( !rmidi ) return NULL;
  memset( rmidi, 0, sizeof( snd_rawmidi_t ) );
  rmidi -> card = card;
  snd_spin_prepare( rmidi, input );
  snd_spin_prepare( rmidi, input_sleep );
  snd_spin_prepare( rmidi, output );
  snd_spin_prepare( rmidi, output_sleep );
  snd_sleep_prepare( rmidi, input );
  snd_sleep_prepare( rmidi, output );
  snd_mutex_prepare( rmidi, open );
  rmidi -> input.u.s.timer.data = (unsigned long)rmidi;
  rmidi -> input.u.s.timer.function = snd_rawmidi_poll_input;
  rmidi -> output.u.s.timer.data = (unsigned long)rmidi;
  rmidi -> output.u.s.timer.function = snd_rawmidi_poll_output;
  if ( id ) {
    strncpy( rmidi -> id, id, sizeof( rmidi -> id ) - 1 );
  }
  return rmidi;
}

int snd_rawmidi_free( snd_rawmidi_t *rmidi )
{
  if ( !rmidi ) return -EINVAL;
  if ( rmidi -> output.hw.private_free )
    rmidi -> output.hw.private_free( rmidi -> output.hw.private_data );
  if ( rmidi -> input.hw.private_free )
    rmidi -> input.hw.private_free( rmidi -> input.hw.private_data );
  if ( rmidi -> private_free )
    rmidi -> private_free( rmidi -> private_data );
  snd_free( rmidi, sizeof( snd_rawmidi_t ) );
  return 0;  
}

int snd_rawmidi_register( snd_rawmidi_t *rmidi, int rawmidi_device )
{
  int idx, err;
  unsigned short minor;
  snd_info_entry_t *entry;
  char name[16];

  snd_mutex_down_static( register );
  if ( rawmidi_device < 0 || rawmidi_device >= SND_RAWMIDI_DEVICES ) {
    snd_mutex_up_static( register );
    return -EINVAL;
  }
  idx = (rmidi -> card -> number * SND_RAWMIDI_DEVICES) + rawmidi_device;
  if ( snd_rawmidi_devices[ idx ] ) {
    snd_mutex_up_static( register );
    return -EBUSY;
  }
  rmidi -> device = rawmidi_device;
  snd_rawmidi_devices[ idx ] = rmidi;
  minor = SND_MINOR_RAWMIDI + idx;
  if ( (err = snd_register_minor( minor, &snd_rawmidi_reg )) < 0 ) {
    snd_rawmidi_devices[ idx ] = NULL;
    snd_mutex_up_static( register );
    return err;
  }
  if ( rawmidi_device == 0 ) {
    idx = rmidi -> card -> number << 4;
    snd_register_minor( idx | SND_MINOR_OSS_MIDI, &snd_rawmidi_oss_reg );
    snd_register_minor( idx | SND_MINOR_OSS_DMMIDI, &snd_rawmidi_oss_reg );
  } else if ( rawmidi_device == 1 ) {
    idx = rmidi -> card -> number << 4;
    snd_register_minor( idx | SND_MINOR_OSS_MIDI1, &snd_rawmidi_oss_reg );
    snd_register_minor( idx | SND_MINOR_OSS_DMMIDI1, &snd_rawmidi_oss_reg );
  }
  snd_mutex_up_static( register );
  sprintf( name, "midi%i", rawmidi_device );
  entry = snd_info_create_entry( rmidi -> card, name );
  if ( entry ) {
    entry -> private_data = rmidi;
    entry -> t.text.read_size = 1024;
    entry -> t.text.read = snd_rawmidi_proc_info_read;
    if ( snd_info_register( entry ) < 0 ) {
      snd_info_free_entry( entry );
      entry = NULL;
    }
    rmidi -> proc_entry = entry;
  }
  return 0;
}

int snd_rawmidi_unregister( snd_rawmidi_t *rmidi )
{
  int idx, idx1;
  
  if ( !rmidi ) return -EINVAL;
  snd_mutex_down_static( register );  
  idx = (rmidi -> card -> number * SND_RAWMIDI_DEVICES) + rmidi -> device;
  if ( snd_rawmidi_devices[ idx ] != rmidi ) {
    snd_mutex_up_static( register );
    return -EINVAL;
  }
  if ( rmidi -> proc_entry ) {
    snd_info_unregister( rmidi -> proc_entry );
    rmidi -> proc_entry = NULL;
  }
  if ( rmidi -> device == 0 ) {
    idx1 = rmidi -> card -> number << 4;
    snd_unregister_minor( idx1 | SND_MINOR_OSS_MIDI );
    snd_unregister_minor( idx1 | SND_MINOR_OSS_DMMIDI );
  } else if ( rmidi -> device == 1 ) {
    idx1 = rmidi -> card -> number << 4;
    snd_unregister_minor( idx1 | SND_MINOR_OSS_MIDI1 );
    snd_unregister_minor( idx1 | SND_MINOR_OSS_DMMIDI1 );
  }
  snd_unregister_minor( SND_MINOR_RAWMIDI + idx );
  snd_rawmidi_devices[ idx ] = NULL;
  snd_mutex_up_static( register );
  return snd_rawmidi_free( rmidi );
}
