/*
 *  Information interface for ALSA driver
 *  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 "info.h"
#include "sndpersist.h"
#include <stdarg.h>

#ifndef CONFIG_PROC_FS
#error "ALSA driver can't be compiled without disabled /proc filesystem..."
#endif

#define SNDSTAT_STRING \
"Sound driver - compatibility text for gmod : 3.5\n"

snd_mutex_define_static( info );

struct snd_info_private_data {
  snd_info_buffer_t *rbuffer;
  snd_info_buffer_t *wbuffer;
  snd_info_entry_t *entry;
  void *file_private_data;
};

static snd_info_entry_t *snd_info_entries;

static int snd_info_version_init( void );
static int snd_info_version_done( void );

/*
 *
 */

int snd_iprintf( snd_info_buffer_t *buffer, char *fmt, ... )
{
  va_list args;
  int res;
  char sbuffer[ 512 ];
  
  if ( buffer -> stop || buffer -> error ) return 0;
  va_start( args, fmt );
  res = vsprintf( sbuffer, fmt, args );
  va_end( args );
  if ( buffer -> size + res >= buffer -> len )
    {
      buffer -> stop = 1;
      return 0;
    }
  strcpy( buffer -> curr, sbuffer );
  buffer -> curr += res;
  buffer -> size += res;
  return res;
}

/*
 *
 */
 
int snd_info_check_reserved_words( const char *str )
{
  static char *reserved[] = {
    "version",
    "meminfo",
    "memdebug",
    "detect",
    "devices",
    "oss-devices",
    "cards",
    NULL
  };
  char **xstr = reserved;

  while ( *xstr ) {
    if ( !strcmp( *xstr, str ) ) return 0;
    xstr++;
  }
  return 1;
}

/*
 *
 */

struct proc_dir_entry *snd_proc_root = NULL;

#ifndef LINUX_2_1

static struct inode * snd_info_get_inode(struct super_block * s, int ino, struct proc_dir_entry * de)
{
        struct inode * inode = iget(s, ino);
        if (inode && inode->i_sb == s) {
                inode->u.generic_ip = (void *) de;
                if (de) {
                        if (de->mode) {
                                inode->i_mode = de->mode;
                                inode->i_uid = de->uid;
                                inode->i_gid = de->gid;
                        }
                        if (de->size)
                                inode->i_size = de->size;
                        if (de->ops)
                                inode->i_op = de->ops;
                        if (de->nlink)
                                inode->i_nlink = de->nlink;
                        if (de->fill_inode)
                                de->fill_inode(inode);
                }
        }
        return inode;
}

static int snd_info_match(int len,const char * name,struct proc_dir_entry * de)
{
        if (!de || !de->low_ino)
                return 0;
        /* "" means "." ---> so paths like "/usr/lib//libc.a" work */
        if (!len && (de->name[0]=='.') && (de->name[1]=='\0'))
                return 1;
        if (de->namelen != len)
                return 0;
        return !memcmp(name, de->name, len);
}

static int snd_info_lookup(struct inode * dir,const char * name, int len,
	struct inode ** result)
{
	struct proc_dir_entry * de;
	int ino;

	*result = NULL;
	if (!dir || !S_ISDIR(dir->i_mode)) {
		iput(dir);
		return -ENOTDIR;
	}

	de = (struct proc_dir_entry *) dir->u.generic_ip;
	if (!de) {
		iput(dir);
		return -EINVAL;
	}

	/* Special case "." and "..": they aren't on the directory list */
	*result = dir;
	if (!len)
		return 0;
	if (name[0] == '.') {
		if (len == 1)
			return 0;
		if (name[1] == '.' && len == 2) {
			struct inode * inode;
			inode = snd_info_get_inode(dir->i_sb, de->parent->low_ino, de->parent);
			iput(dir);
			if (!inode)
				return -EINVAL;
			*result = inode;
			return 0;
		}
	}

	*result = NULL;
	for (de = de->subdir; de ; de = de->next) {
		if (snd_info_match(len, name, de))
			break;
	}
	if (!de) {
		iput(dir);
		return -ENOENT;
	}

	ino = de->low_ino | (dir->i_ino & ~(0xffff));

	if (!(*result = snd_info_get_inode(dir->i_sb, ino, de))) {
		iput(dir);
		return -EINVAL;
	}
	iput(dir);
	return 0;
}

/*
 * This returns non-zero if at EOF, so that the /proc
 * root directory can use this and check if it should
 * continue with the <pid> entries..
 *
 * Note that the VFS-layer doesn't care about the return
 * value of the readdir() call, as long as it's non-negative
 * for success..
 */
static int snd_info_readdir(struct inode * inode, struct file * filp,
	void * dirent, filldir_t filldir)
{
	struct proc_dir_entry * de;
	unsigned int ino;
	int i;

	if (!inode || !S_ISDIR(inode->i_mode))
		return -ENOTDIR;
	ino = inode->i_ino;
	de = (struct proc_dir_entry *) inode->u.generic_ip;
	if (!de)
		return -EINVAL;
	i = filp->f_pos;
	switch (i) {
		case 0:
			if (filldir(dirent, ".", 1, i, ino) < 0)
				return 0;
			i++;
			filp->f_pos++;
			/* fall through */
		case 1:
			if (filldir(dirent, "..", 2, i, de->parent->low_ino) < 0)
				return 0;
			i++;
			filp->f_pos++;
			/* fall through */
		default:
			ino &= ~0xffff;
			de = de->subdir;
			i -= 2;
			for (;;) {
				if (!de)
					return 1;
				if (!i)
					break;
				de = de->next;
				i--;
			}

			do {
				if (filldir(dirent, de->name, de->namelen, filp->f_pos, ino | de->low_ino) < 0)
					return 0;
				filp->f_pos++;
				de = de->next;
			} while (de);
	}
	return 1;
}

static struct file_operations snd_info_dir_operations = {
        NULL,                   /* lseek - default */
        NULL,                   /* read - bad */
        NULL,                   /* write - bad */
        snd_info_readdir,       /* readdir */
        NULL,                   /* select - default */
        NULL,                   /* ioctl - default */
        NULL,                   /* mmap */
        NULL,                   /* no special open code */
        NULL,                   /* no special release code */
        NULL                    /* can't fsync */
};

static struct inode_operations snd_info_dir_inode_operations = {
        &snd_info_dir_operations, /* default net directory file-ops */
        NULL,                   /* create */
        snd_info_lookup,        /* lookup */
        NULL,                   /* link */
        NULL,                   /* unlink */
        NULL,                   /* symlink */
        NULL,                   /* mkdir */
        NULL,                   /* rmdir */
        NULL,                   /* mknod */
        NULL,                   /* rename */
        NULL,                   /* readlink */
        NULL,                   /* follow_link */
        NULL,                   /* readpage */
        NULL,                   /* writepage */
        NULL,                   /* bmap */
        NULL,                   /* truncate */
        NULL                    /* permission */
};

struct inode *snd_proc_get_inode(struct super_block * s, int ino, struct proc_dir_entry *de)
{
        struct inode * inode = iget(s, ino);
        if (inode && inode->i_sb == s) {
                inode->u.generic_ip = (void *) de;
                if (de) {
                        if (de->mode) {
                                inode->i_mode = de->mode;
                                inode->i_uid = de->uid;
                                inode->i_gid = de->gid;
                        }
                        if (de->size)
                                inode->i_size = de->size;
                        if (de->ops)
                                inode->i_op = de->ops;
                        if (de->nlink)
                                inode->i_nlink = de->nlink;
                        if (de->fill_inode)
                                de->fill_inode(inode);
                }
        }
        return inode;
}

#endif

static snd_info_entry_t *snd_info_find_entry( unsigned short ino )
{
  int tmp;
  snd_card_t *card;
  snd_info_entry_t *entry;

  for ( entry = snd_info_entries; entry; entry = entry -> next ) {
    if ( !entry -> p ) continue;
    if ( entry -> p -> low_ino == ino ) break;
  }
  if ( !entry ) {
    for ( tmp = 0; tmp < SND_CARDS && !entry; tmp++ ) {
      card = snd_cards[ tmp ];
      if ( !card ) continue;
      for ( entry = card -> info_entries; entry; entry = entry -> next ) {
        if ( !entry -> p ) continue;
        if ( entry -> p -> low_ino == ino ) break;
      }
    }
  }
  return entry;
}

static snd_card_t *snd_info_find_card( unsigned short ino )
{
  int tmp;
  snd_card_t *card;

  for ( tmp = 0; tmp < SND_CARDS; tmp++ ) {
    card = snd_cards[ tmp ];
    if ( !card || !card -> proc_dir_link ) continue;
    if ( card -> proc_dir_link -> low_ino == ino )
      return card;
  }
  return NULL;
}

#ifdef LINUX_2_1
static loff_t snd_info_entry_lseek( struct file *file, loff_t offset, int orig )
#else
static int snd_info_entry_lseek( struct inode *inode, struct file *file, off_t offset, int orig )
#endif
{
  struct snd_info_private_data *data;
  struct snd_info_entry *entry;
  
  data = (struct snd_info_private_data *)file -> private_data;
  if ( !data ) return -EIO;
  entry = data -> entry;
  if ( entry -> type == SND_INFO_ENTRY_DATA ) {
    if ( entry -> t.data.lseek )
      return entry -> t.data.lseek( entry -> private_data, data -> file_private_data, offset, orig );
  }
  return -ENXIO;
}

#ifdef LINUX_2_1
static ssize_t snd_info_entry_read( struct file *file, char *buffer, size_t count, loff_t *offset )
#else
static int snd_info_entry_read( struct inode *inode, struct file *file, char *buffer, int count )
#endif
{
  struct snd_info_private_data *data;
  struct snd_info_entry *entry;
  snd_info_buffer_t *buf;
  long size, size1;
  
  data = (struct snd_info_private_data *)file -> private_data;
  if ( !data ) return -EIO;
  entry = data -> entry;
  if ( entry -> type == SND_INFO_ENTRY_TEXT ) {
    buf = data -> rbuffer;
    if ( !buf ) return -EIO;
    if ( file -> f_pos >= buf -> size ) return 0;
    size = buf -> size < count ? buf -> size : count;
    size1 = buf -> size - file -> f_pos;
    if ( size1 < size ) size = size1;
    if ( verify_area( VERIFY_WRITE, buffer, size ) ) return -EFAULT;
    copy_to_user( buffer, buf -> buffer + file -> f_pos, size );
    file -> f_pos += size;
  } else {
    if ( entry -> t.data.read )
      size = entry -> t.data.read( entry -> private_data, data -> file_private_data, file, buffer, count );
     else
      size = 0;
    if ( size > 0 )
      file -> f_pos += size;
  } 
  return size;
}

#ifdef LINUX_2_1
static ssize_t snd_info_entry_write( struct file *file, const char *buffer, size_t count, loff_t *offset )
#else
static int snd_info_entry_write( struct inode *inode, struct file *file, const char *buffer, int count )
#endif
{
  struct snd_info_private_data *data;
  struct snd_info_entry *entry;
  snd_info_buffer_t *buf;
  long size, size1;
  
  data = (struct snd_info_private_data *)file -> private_data;
  if ( !data ) return -EIO;
  entry = data -> entry;
  if ( entry -> type == SND_INFO_ENTRY_TEXT ) {
    buf = data -> wbuffer;
    if ( !buf ) return -EIO;
    if ( file -> f_pos < 0 || file -> f_pos >= buf -> len ) return 0;
    size = buf -> len < count ? buf -> len : count;
    size1 = buf -> len - file -> f_pos;
    if ( size1 < size ) size = size1;
    if ( verify_area( VERIFY_READ, buffer, size ) ) return -EFAULT;
    copy_from_user( buf -> buffer + file -> f_pos, buffer, size );
    if ( buf -> size < file -> f_pos + size )
      buf -> size = file -> f_pos + size;
    file -> f_pos += size;
  } else {
    if ( entry -> t.data.write )
      size = entry -> t.data.write( entry -> private_data, data -> file_private_data, file, buffer, count );
     else
      size = 0;
    if ( size > 0 )
      file -> f_pos += size;
  }
  return size;
}

static int snd_info_entry_open( struct inode *inode, struct file *file )
{
  snd_info_entry_t *entry;
  struct snd_info_private_data *data;
  snd_info_buffer_t *buffer;
  int mode, err;
  
  snd_mutex_down_static( info );
  entry = snd_info_find_entry( inode -> i_ino );
  if ( !entry ) {
    snd_mutex_up_static( info );
    return -ENODEV;
  }
  mode = file -> f_flags & O_ACCMODE;
  if ( mode == O_RDONLY || mode == O_RDWR ) {
    if ( (entry -> type == SND_INFO_ENTRY_TEXT && !entry -> t.text.read_size) ||
         (entry -> type == SND_INFO_ENTRY_DATA && !entry -> t.data.read) ) {
      snd_mutex_up_static( info );
      return -ENODEV;
    }
  }
  if ( mode == O_WRONLY || mode == O_RDWR ) {
    if ( (entry -> type == SND_INFO_ENTRY_TEXT && !entry -> t.text.write_size) || 
         (entry -> type == SND_INFO_ENTRY_DATA && !entry -> t.data.write) ) {
      snd_mutex_up_static( info );
      return -ENODEV;
    }
  }
  data = (struct snd_info_private_data *)snd_malloc( sizeof( struct snd_info_private_data ) );
  if ( !data ) {
    snd_mutex_up_static( info );
    return -ENOMEM;
  }
  memset( data, 0, sizeof( struct snd_info_private_data ) );
  data -> entry = entry;
  if ( entry -> type == SND_INFO_ENTRY_TEXT ) {
    if ( mode == O_RDONLY || mode == O_RDWR ) {
      buffer = (snd_info_buffer_t *)snd_malloc( sizeof( snd_info_buffer_t ) );
      if ( !buffer ) {
        snd_free( data, sizeof( struct snd_info_private_data ) );
        snd_mutex_up_static( info );
        return -ENOMEM;
      }
      memset( buffer, 0, sizeof( snd_info_buffer_t ) );
      buffer -> len = (entry -> t.text.read_size + (PAGE_SIZE-1)) & ~PAGE_SIZE;
      buffer -> buffer = vmalloc( buffer -> len );
      if ( !buffer -> buffer ) {
        snd_free( buffer, sizeof( snd_info_buffer_t ) );
        snd_free( data, sizeof( struct snd_info_private_data ) );
        snd_mutex_up_static( info );
        return -ENOMEM;
      }
      buffer -> curr = buffer -> buffer;
      data -> rbuffer = buffer;
    }
    if ( mode == O_WRONLY || mode == O_RDWR ) {
      buffer = (snd_info_buffer_t *)snd_malloc( sizeof( snd_info_buffer_t ) );
      if ( !buffer ) {
        if ( mode == O_RDWR ) {
          vfree( data -> rbuffer -> buffer );
          snd_free( data -> rbuffer, sizeof( snd_info_buffer_t ) );
        }
        snd_free( data, sizeof( struct snd_info_private_data ) );
        snd_mutex_up_static( info );
        return -ENOMEM;
      }
      memset( buffer, 0, sizeof( snd_info_buffer_t ) );
      buffer -> len = (entry -> t.text.write_size + (PAGE_SIZE-1)) % PAGE_SIZE;
      buffer -> buffer = vmalloc( buffer -> len );
      if ( !buffer -> buffer ) {
        if ( mode == O_RDWR ) {
          vfree( data -> rbuffer -> buffer );
          snd_free( data -> rbuffer, sizeof( snd_info_buffer_t ) );
        }
        snd_free( buffer, sizeof( snd_info_buffer_t ) );
        snd_free( data, sizeof( struct snd_info_private_data ) );
        snd_mutex_up_static( info );
        return -ENOMEM;
      }
      buffer -> curr = buffer -> buffer;
      data -> wbuffer = buffer;    
    }
  } else {		/* data */
    if ( entry -> t.data.open ) {
      if ( (err = entry -> t.data.open( entry -> private_data, entry, mode, &data -> file_private_data )) < 0 ) {
        snd_free( data, sizeof( struct snd_info_private_data ) );
        snd_mutex_up_static( info );
        return err;
      }
    }
  }
  file -> private_data = data;
  MOD_INC_USE_COUNT;
  if ( entry -> use_inc )
    entry -> use_inc( entry -> card );
  snd_mutex_up_static( info );
  if ( entry -> type == SND_INFO_ENTRY_TEXT && (mode == O_RDONLY || mode == O_RDWR) ) {
    if ( entry -> t.text.read ) {
      snd_mutex_down( entry, access );
      entry -> t.text.read( data -> rbuffer, entry -> private_data );
      snd_mutex_up( entry, access );
    }
  }
  return 0;
}

static int snd_info_entry_release( struct inode *inode, struct file *file )
{
  snd_info_entry_t *entry;
  struct snd_info_private_data *data;
  int mode;

  mode = file -> f_flags & O_ACCMODE;
  if ( (data = (struct snd_info_private_data *)file -> private_data) == NULL ) {
    MOD_DEC_USE_COUNT;
    return -EINVAL;
  }
  entry = data -> entry;
  if ( entry -> type == SND_INFO_ENTRY_TEXT ) {
    if ( mode == O_RDONLY || mode == O_RDWR ) {
      vfree( data -> rbuffer -> buffer );
      snd_free( data -> rbuffer, sizeof( snd_info_buffer_t ) );
    }
    if ( mode == O_WRONLY || mode == O_RDWR ) {
      if ( entry -> t.text.write ) {
        entry -> t.text.write( data -> wbuffer, entry -> private_data );
        if ( data -> wbuffer -> error ) {
          snd_printk( "data write error to /proc/sound/%s%s%s (%i)\n", entry -> card ? entry -> card -> id : "", entry -> card ? "/" : "", entry -> name, data -> wbuffer -> error );
        }
      }
      vfree( data -> wbuffer -> buffer );
      snd_free( data -> wbuffer, sizeof( snd_info_buffer_t ) );
    }
  } else {
    if ( entry -> t.data.release )
      entry -> t.data.release( entry -> private_data, entry, mode, data -> file_private_data );
  }
  if ( entry -> use_dec )
    entry -> use_dec( entry -> card );
  MOD_DEC_USE_COUNT;
  snd_free( data, sizeof( struct snd_info_private_data ) );
  return 0;
}

#ifdef SND_POLL
static unsigned int snd_info_entry_poll( struct file *file, poll_table *wait )
{
  struct snd_info_private_data *data;
  struct snd_info_entry *entry;
  unsigned int mask;
  
  data = (struct snd_info_private_data *)file -> private_data;
  if ( !data ) return 0;
  entry = data -> entry;
  mask = 0;
  if ( entry -> type == SND_INFO_ENTRY_DATA ) {
    if ( entry -> t.data.poll )
      return entry -> t.data.poll( entry -> private_data, data -> file_private_data, file, wait );
    if ( entry -> t.data.read )
      mask |= POLLIN | POLLRDNORM;
    if ( entry -> t.data.write )
      mask |= POLLOUT | POLLWRNORM; 
  }
  return mask;
}
#else
static int snd_info_entry_select( struct inode *inode, struct file *file, int sel_type, select_table *wait )
{
  struct snd_info_private_data *data;
  struct snd_info_entry *entry;
  
  data = (struct snd_info_private_data *)file -> private_data;
  if ( !data ) return 0;
  entry = data -> entry;
  if ( entry -> type == SND_INFO_ENTRY_DATA ) {
    if ( entry -> t.data.select )
      return entry -> t.data.select( entry -> private_data, data -> file_private_data, file, sel_type, wait );
    if ( sel_type == SEL_IN && entry -> t.data.read )
      return 1;
    if ( sel_type == SEL_OUT && entry -> t.data.write )
      return 1;
  }
  return 0;
}
#endif

static struct file_operations snd_info_entry_operations = {
        snd_info_entry_lseek,	/* lseek */
        snd_info_entry_read,    /* read */
        snd_info_entry_write,	/* write */
        NULL,                   /* readdir */
#ifdef SND_POLL
        snd_info_entry_poll,	/* poll */
#else
	snd_info_entry_select,	/* select */
#endif        
        NULL,                   /* ioctl - default */
        NULL,                   /* mmap */
        snd_info_entry_open,    /* open */
#ifdef LINUX_2_1
        snd_info_entry_release,	/* release */
#else
        (void (*)( struct inode * inode, struct file *file ))snd_info_entry_release, /* release */
#endif
        NULL,                   /* can't fsync */
#ifdef LINUX_2_1
  	NULL,        		/* fasync */
	NULL,         		/* check_media_change */
	NULL,         		/* revalidate */
        NULL,         		/* lock */
#endif
};

static struct inode_operations snd_info_entry_inode_operations = {
        &snd_info_entry_operations, /* default sound info directory file-ops */
        NULL,                   /* create */
        NULL,                   /* lookup */
        NULL,                   /* link */
        NULL,                   /* unlink */
        NULL,                   /* symlink */
        NULL,                   /* mkdir */
        NULL,                   /* rmdir */
        NULL,                   /* mknod */
        NULL,                   /* rename */
        NULL,                   /* readlink */
        NULL,                   /* follow_link */
        NULL,                   /* readpage */
        NULL,                   /* writepage */
        NULL,                   /* bmap */
        NULL,                   /* truncate */
        NULL                    /* permission */
};

#ifdef LINUX_2_1
static int snd_info_card_readlink( struct dentry *dentry, char *buffer, int buflen )
{
  int len;
  snd_card_t *card;

  snd_mutex_down_static( info );
  card = snd_info_find_card( dentry -> d_inode -> i_ino );
  if ( !card ) {
    snd_mutex_up_static( info );
    return -ENOENT;
  }
  len = strlen( card -> id ) + 1;
  if ( buflen < len )
    len = buflen;
  copy_to_user( buffer, card -> id, len );
  snd_mutex_up_static( info );
  return len;
}

static struct dentry *snd_info_card_followlink( struct dentry *dentry, struct dentry *base )
{
  snd_card_t *card;
  char tmp[16];

  snd_mutex_down_static( info );
  card = snd_info_find_card( dentry -> d_inode -> i_ino );
  if ( !card || !card -> proc_dir ) {
    snd_mutex_up_static( info );
    return NULL;
  }
  strcpy( tmp, card -> id );
  snd_mutex_up_static( info );
  return lookup_dentry( tmp, base, 1 );
}
#else
static int snd_info_card_readlink( struct inode *inode, char *buffer, int buflen )
{
  int len;
  snd_card_t *card;

  iput( inode );
  snd_mutex_down_static( info );
  card = snd_info_find_card( inode -> i_ino );
  if ( !card ) {
    snd_mutex_up_static( info );
    return -ENOENT;
  }
  len = strlen( card -> id ) + 1;
  if ( buflen < len )
    len = buflen;
  copy_to_user( buffer, card -> id, len );
  snd_mutex_up_static( info );
  return len;
}

static int snd_info_card_followlink( struct inode *dir, struct inode *inode, int flag, int mode, struct inode **res_inode )
{
  struct inode *in;
  snd_card_t *card;
  struct proc_dir_entry *entry;

  *res_inode = NULL;
  iput( dir );
  snd_mutex_down_static( info );
  card = snd_info_find_card( inode -> i_ino );
  if ( !card || !(entry = card -> proc_dir) ) {
    snd_mutex_up_static( info );
    return -ENOENT;
  }
  in = snd_proc_get_inode( inode -> i_sb, entry -> low_ino, entry );
  iput( inode );
  snd_mutex_up_static( info );
  if ( !in ) return -ENOENT;
  *res_inode = in;
  return 0;
}
#endif

static struct file_operations snd_info_card_link_operations = {
        NULL,                   /* lseek - default */
        NULL,                   /* read - bad */
        NULL,                   /* write - bad */
        NULL,                   /* readdir - bad */
        NULL,                   /* select - default */
        NULL,                   /* ioctl - default */
        NULL,                   /* mmap */
        NULL,                   /* very special open code */
        NULL,                   /* no special release code */
        NULL                    /* can't fsync */
};

struct inode_operations snd_info_card_link_inode_operations = {
        &snd_info_card_link_operations,/* file-operations */
        NULL,                   /* create */
        NULL,                   /* lookup */
        NULL,                   /* link */
        NULL,                   /* unlink */
        NULL,                   /* symlink */
        NULL,                   /* mkdir */
        NULL,                   /* rmdir */
        NULL,                   /* mknod */
        NULL,                   /* rename */
        snd_info_card_readlink, /* readlink */
        snd_info_card_followlink, /* follow_link */
        NULL,                   /* readpage */
        NULL,                   /* writepage */
        NULL,                   /* bmap */
        NULL,                   /* truncate */
        NULL                    /* permission */
};


#ifndef LINUX_2_1
static struct proc_dir_entry snd_info_proc_root = {
  0, 5, "sound",			/* inode, namelen, name */
  S_IFDIR | S_IRUGO | S_IXUGO, 2, 0, 0,	/* mode, nlink, uid, gid */
  0, &snd_info_dir_inode_operations,	/* size, ops */
  NULL, NULL, 				/* get_info, fill_inode */
  NULL,					/* next */
  NULL, NULL				/* parent, subdir */  
};
#endif

int snd_info_init( void )
{
#ifdef LINUX_2_1
  struct proc_dir_entry *p;
#endif

  snd_info_entries = NULL;
#ifdef LINUX_2_1
  p = create_proc_entry( "sound", S_IFDIR | S_IRUGO | S_IXUGO, &proc_root );
  if ( !p ) return -ENOMEM;
  snd_proc_root = p;
#else
  if ( proc_register_dynamic( &proc_root, &snd_info_proc_root ) < 0 ) {
    return -EAGAIN;
  }
  snd_proc_root = &snd_info_proc_root;
#endif
  snd_info_version_init();
  snd_memory_info_init();
  snd_minor_info_init();
  snd_card_info_init();
  return 0;
}

int snd_info_done( void )
{
  snd_card_info_done();
  snd_minor_info_done();
  snd_memory_info_done();
  snd_info_version_done();
#ifdef SNDCFG_DEBUG
  if ( snd_info_entries ) {
    snd_printd( "Oops.. Info entries still allocated!!!\n" );
  }
#endif
  if ( snd_proc_root ) {
    proc_unregister( &proc_root, snd_proc_root -> low_ino );
  }
  return 0;
}

/*
 *
 */

#ifndef LINUX_2_1
static struct proc_dir_entry snd_info_proc_card_template = {
  0, 0, 0,				/* inode, namelen, name */
  S_IFDIR | S_IRUGO | S_IXUGO, 2, 0, 0,	/* mode, nlink, uid, gid */
  0, &snd_info_dir_inode_operations,	/* size, ops */
  NULL, NULL, 				/* get_info, fill_inode */
  NULL,					/* next */
  NULL, NULL				/* parent, subdir */
};
static struct proc_dir_entry snd_info_proc_card_link_template = {
  0, 0, 0,				/* inode, namelen, name */
  S_IFLNK | S_IRUGO | S_IWUGO | S_IXUGO, 1, 0, 0, /* mode, nlink, uid, gid */
  0, &snd_info_card_link_inode_operations,	/* size, ops */
  NULL, NULL, 				/* get_info, fill_inode */
  NULL,					/* next */
  NULL, NULL				/* parent, subdir */
};
#endif

int snd_info_card_register( snd_card_t *card )
{
  char str[ 4 ];
#ifdef LINUX_2_1
  struct proc_dir_entry *p;
#endif

  if ( !card ) return -EINVAL;
#ifdef LINUX_2_1
  p = create_proc_entry( card -> id, S_IFDIR | S_IRUGO | S_IXUGO, snd_proc_root );
  if ( !p ) return -ENOMEM;
  card -> proc_dir = p;
  sprintf( str, "%i", card -> number );
  p = create_proc_entry( str, S_IFLNK | S_IRUGO | S_IWUGO | S_IXUGO, snd_proc_root );
  if ( !p ) {
    proc_unregister( snd_proc_root, card -> proc_dir -> low_ino );
    card -> proc_dir = NULL;
    return -ENOMEM;
  }
  p -> ops = &snd_info_card_link_inode_operations;
  card -> proc_dir_link = p;
#else
  card -> proc_dir = (struct proc_dir_entry *)snd_malloc( sizeof( struct proc_dir_entry ) );
  if ( !card -> proc_dir ) {
    return -ENOMEM;
  }
  *(card -> proc_dir) = snd_info_proc_card_template;
  card -> proc_dir -> namelen = strlen( card -> proc_dir -> name = card -> id );
  if ( proc_register_dynamic( snd_proc_root, card -> proc_dir ) < 0 ) {
    snd_free( card -> proc_dir, sizeof( struct proc_dir_entry ) );
    card -> proc_dir = NULL;
    return -EAGAIN;
  }
  card -> proc_dir_link = (struct proc_dir_entry *)snd_malloc( sizeof( struct proc_dir_entry ) );
  if ( !card -> proc_dir_link ) {
    proc_unregister( snd_proc_root, card -> proc_dir -> low_ino );
    snd_free( card -> proc_dir, sizeof( struct proc_dir_entry ) );
    card -> proc_dir = NULL;
    return -ENOMEM;
  }
  *(card -> proc_dir_link) = snd_info_proc_card_link_template;
  sprintf( str, "%i", card -> number );
  card -> proc_dir_link -> name = snd_malloc( 4 );
  if ( !card -> proc_dir_link -> name ) {
    snd_free( card -> proc_dir_link, sizeof( struct proc_dir_entry ) );
    proc_unregister( snd_proc_root, card -> proc_dir -> low_ino );
    snd_free( card -> proc_dir, sizeof( struct proc_dir_entry ) );
    card -> proc_dir_link = NULL;
    card -> proc_dir = NULL;
    return -ENOMEM;
  }
  strcpy( (char *)card -> proc_dir_link -> name, str );
  card -> proc_dir_link -> namelen = strlen( str );
  if ( proc_register_dynamic( snd_proc_root, card -> proc_dir_link ) < 0 ) {
    snd_free( card -> proc_dir_link, sizeof( struct proc_dir_entry ) );
    proc_unregister( snd_proc_root, card -> proc_dir -> low_ino );
    snd_free( card -> proc_dir, sizeof( struct proc_dir_entry ) );
    card -> proc_dir_link = NULL;
    card -> proc_dir = NULL;
    return -EAGAIN;
  }
#endif
  return 0;
}

int snd_info_card_unregister( snd_card_t *card )
{
  if ( !card ) return -EINVAL;
  if ( card -> proc_dir_link ) {
    proc_unregister( snd_proc_root, card -> proc_dir_link -> low_ino );
#ifndef LINUX_2_1
    snd_free( (char *)card -> proc_dir_link -> name, 4 );
    snd_free( card -> proc_dir_link, sizeof( struct proc_dir_entry ) );
#endif
  }
  if ( card -> proc_dir ) {
    proc_unregister( snd_proc_root, card -> proc_dir -> low_ino );
#ifndef LINUX_2_1
    snd_free( card -> proc_dir, sizeof( struct proc_dir_entry ) );
#endif
  }
  card -> proc_dir = NULL;
  return 0;
}

/*
 *
 */

int snd_info_get_line( snd_info_buffer_t *buffer, char *line, int len )
{
  int c = -1;

  if ( len <= 0 || buffer -> stop || buffer -> error ) return 1;
  while ( --len > 0 ) {
    c = *buffer -> curr++;
    if ( c == '\n' ) {
      if ( (buffer -> curr - buffer -> buffer) >= buffer -> size ) {
        buffer -> stop = 1;
      }
      break;
    }
    *line++ = c;
    if ( (buffer -> curr - buffer -> buffer) >= buffer -> size ) {
      buffer -> stop = 1;
      break;
    }
  }
  while ( c != '\n' && !buffer -> stop ) {
    c = *buffer -> curr++;
    if ( (buffer -> curr - buffer -> buffer) >= buffer -> size ) {
      buffer -> stop = 1;
    }
  }
  *line = '\0';
  return 0;
}

char *snd_info_get_str( char *dest, char *src, int len )
{
  int c;
  
  while ( *src == ' ' || *src == '\t' ) src++;
  if ( *src == '"' || *src == '\'' ) {
    c = *src++;
    while ( --len >= 0 && *src && *src != c ) {
      *dest++ = *src++;
    }
    if ( *src == c ) src++;
  } else {
    while ( --len >= 0 && *src && *src != ' ' && *src != '\t' ) {
      *dest++ = *src++;
    }
  }
  *dest = 0;
  while ( *src == ' ' || *src == '\t' ) src++;
  return src;
}

snd_info_entry_t *snd_info_create_entry( snd_card_t *card, const char *name )
{
  snd_info_entry_t *entry;
  
  entry = (snd_info_entry_t *)snd_malloc( sizeof( snd_info_entry_t ) );
  if ( !entry ) return NULL;
  memset( entry, 0, sizeof( snd_info_entry_t ) );
  entry -> name = snd_malloc_strdup( (char *)name );
  if ( !entry -> name ) {
    snd_free( entry, sizeof( snd_info_entry_t ) );
    return NULL;
  }
  entry -> mode = S_IFREG | S_IRUGO;
  entry -> type = SND_INFO_ENTRY_TEXT;
  entry -> card = card;
  if ( card ) {
    entry -> use_inc = card -> use_inc;
    entry -> use_dec = card -> use_dec;
  }
  snd_mutex_prepare( entry, access );
  return entry;
}

void snd_info_free_entry( snd_info_entry_t *entry )
{
  if ( !entry ) return;
  if ( entry -> name )
    snd_free_str( (char *)entry -> name );
  snd_free( entry, sizeof( snd_info_entry_t ) );
}

#ifndef LINUX_2_1
static struct proc_dir_entry snd_info_entry_template = {
  0, 0, NULL,			/* inode, namelen, name */
  S_IFREG | S_IRUGO, 1, 0, 0,	/* mode, nlink, uid, gid */
  0, &snd_info_entry_inode_operations, /* size, ops */
  NULL, NULL, 			/* get_info, fill_inode */
  NULL,				/* next */
  NULL, NULL			/* parent, subdir */
};
#endif

int snd_info_register( snd_info_entry_t *entry )
{
  snd_info_entry_t *e;
  struct proc_dir_entry *p = NULL;
  snd_card_t *card;
  int len;

  if ( !entry ) return -EINVAL;
  entry -> next = NULL;
#if 0
  snd_printk( "snd_info_register: %s, 0x%lx\n", entry -> name, (long)entry -> card );
#endif
  len = strlen( entry -> name );
  card = entry -> card;
  snd_mutex_down_static( info );
#ifdef LINUX_2_1
  p = create_proc_entry( entry -> name, entry -> mode, card ? card -> proc_dir : snd_proc_root );
  if ( p ) {
    p -> ops = &snd_info_entry_inode_operations;
  } else {
    snd_mutex_up_static( info );
    return -ENOMEM;
  }
#else
  p = (struct proc_dir_entry *)snd_malloc( sizeof( struct proc_dir_entry ) );
  if ( !p ) {
    snd_mutex_up_static( info );
    return -ENOMEM;
  }
  *p = snd_info_entry_template;
  p -> namelen = strlen( p -> name = entry -> name );
  p -> mode = entry -> mode;
  if ( proc_register_dynamic( card ? card -> proc_dir : snd_proc_root, p ) < 0 ) {
    snd_free( p, sizeof( struct proc_dir_entry ) );
    snd_mutex_up_static( info );
    return -ENOMEM;
  }
#endif
  entry -> p = p;
  if ( !card ) {
    if ( !(e = snd_info_entries) ) {
      snd_info_entries = entry;
    } else {
      while ( e -> next ) e = e -> next;
      e -> next = entry;
    }
  } else {
    if ( !(e = card -> info_entries) ) {
      card -> info_entries = entry;
    } else {
      while ( e -> next ) e = e -> next;
      e -> next = entry;
    }
  }
  snd_mutex_up_static( info );
  return 0;
}

int snd_info_unregister( snd_info_entry_t *entry )
{
  snd_info_entry_t *e;

  if ( !entry || !entry -> p ) return -EINVAL;
  snd_mutex_down_static( info );
  if ( !entry -> card ) {
    proc_unregister( snd_proc_root, entry -> p -> low_ino );
    if ( entry == (e = snd_info_entries) ) {
      snd_info_entries = snd_info_entries -> next;
    } else {
      while ( e -> next && e -> next != entry ) e = e -> next;
      if ( e -> next )
        e -> next = e -> next -> next;
    }
  } else {
    proc_unregister( entry -> card -> proc_dir, entry -> p -> low_ino );
    if ( entry == (e = entry -> card -> info_entries) ) {
      entry -> card -> info_entries = entry -> card -> info_entries -> next;
    } else {
      while ( e -> next && e -> next != entry ) e = e -> next;
      if ( e -> next )
        e -> next = e -> next -> next;
    }
  }
  snd_mutex_up_static( info );
#ifndef LINUX_2_1
  snd_free( entry -> p, sizeof( struct proc_dir_entry ) );
#endif
  snd_info_free_entry( entry );
  return 0;
}

/*
 *
 */

static snd_info_entry_t *snd_info_version_entry = NULL;

static void snd_info_version_read( snd_info_buffer_t *buffer, void *private_data )
{
#ifdef MODULE_PARM
  static char *kernel_version = UTS_RELEASE;
#else
  extern char kernel_version[];
#endif

  snd_iprintf( buffer,
               "Advanced Linux Sound Architecture Driver Version " SND_VERSION ".\n"
               "Compiled in " __DATE__ " for kernel %s"
#ifdef __SMP__
	       " (SMP)"
#endif
#ifdef MODVERSIONS
               " with versioned symbols"
#endif
               ".\n", kernel_version );
}

static int snd_info_version_init( void )
{
  snd_info_entry_t *entry;
  
  entry = snd_info_create_entry( NULL, "version" );
  if ( !entry ) return -ENOMEM;
  entry -> t.text.read_size = 256;
  entry -> t.text.read = snd_info_version_read;
  if ( snd_info_register( entry ) < 0 ) {
    snd_info_free_entry( entry );
    return -ENOMEM;
  }
  snd_info_version_entry = entry;
  return 0;
}

static int snd_info_version_done( void )
{
  if ( snd_info_version_entry )
    snd_info_unregister( snd_info_version_entry );
  return 0;
}

/*
 *  OSS compatible part
 */

static long snd_sndstat_read( struct file *file, char *buffer, long count )
{
  char *s = SNDSTAT_STRING;
  int size;

  size = strlen( s ) + 1;
  if ( file -> f_pos >= size || count < size - file -> f_pos )
    return 0;
  size -= file -> f_pos;
  copy_to_user( buffer, s + file -> f_pos, size );
  file -> f_pos += size;
  return size;
}

static int snd_sndstat_open( unsigned short minor, int cardnum, int device, struct file *file )
{
  MOD_INC_USE_COUNT;
  return 0;
}

static int snd_sndstat_release( unsigned short minor, int cardnum, int device, struct file *file )
{
  MOD_DEC_USE_COUNT;
  return 0;
}

static snd_minor_t snd_sndstat_reg = {
  "sndstat",

  NULL,                                 /* unregister */

  NULL,                                 /* lseek */
  snd_sndstat_read,                     /* read */
  NULL,                                 /* write */
  snd_sndstat_open,                     /* open */
  snd_sndstat_release,                  /* release */
  NULL,                                 /* poll or select */
  NULL,                                 /* ioctl */
  NULL                                  /* mmap */
};

int snd_info_minor_register( void )
{
  return snd_register_minor( SND_MINOR_OSS_SNDSTAT, &snd_sndstat_reg );
}

/*
 *  store/restore settings via persistant module
 */
 
int snd_info_store_text( snd_info_entry_t *entry )
{
  int result = 0;
  snd_info_buffer_t ibuffer;
  char key[128];
  
  if ( !entry ) return -EINVAL;
  if ( entry -> type != SND_INFO_ENTRY_TEXT ) return -EINVAL;
  memset( &ibuffer, 0, sizeof( ibuffer ) );
  ibuffer.len = (entry -> t.text.read_size + (PAGE_SIZE-1)) & ~(PAGE_SIZE-1);
  ibuffer.buffer = ibuffer.curr = vmalloc( ibuffer.len );
  if ( !ibuffer.buffer ) return -ENOMEM;
  snd_mutex_down( entry, access );
  entry -> t.text.read( &ibuffer, entry -> private_data );
  result = ibuffer.error;
  if ( !result ) {
    if ( entry -> card )
      sprintf( key, "snd:%i/%s", entry -> card -> number, entry -> name );
     else
      sprintf( key, "snd:%s", entry -> name );
    if ( ibuffer.size > 0 ) {
      result = snd_persist_store( key, ibuffer.buffer, ibuffer.size );
    } else {
      result = snd_persist_remove( key );
    }
  }
  snd_mutex_up( entry, access );  
  vfree( ibuffer.buffer );
  return result;
}

int snd_info_restore_text( snd_info_entry_t *entry )
{
  int result = 0;
  snd_info_buffer_t ibuffer;
  char key[128];
  
  if ( !entry ) return -EINVAL;
  if ( entry -> type != SND_INFO_ENTRY_TEXT ) return -EINVAL;
  memset( &ibuffer, 0, sizeof( ibuffer ) );
  ibuffer.len = (entry -> t.text.write_size + (PAGE_SIZE-1)) & ~(PAGE_SIZE-1);
  ibuffer.buffer = ibuffer.curr = vmalloc( ibuffer.len );
  if ( !ibuffer.buffer ) return -ENOMEM;
  snd_mutex_down( entry, access );
  if ( entry -> card )
    sprintf( key, "snd:%i/%s", entry -> card -> number, entry -> name );
   else
    sprintf( key, "snd:%s", entry -> name );
  result = snd_persist_restore( key, ibuffer.buffer, ibuffer.len );
  if ( result > 0 ) {
    ibuffer.size = result;
    result = 0;
    entry -> t.text.write( &ibuffer, entry -> private_data );
    result = ibuffer.error;
  }
  snd_mutex_up( entry, access );
  vfree( ibuffer.buffer );
  return result;
}
