xref: /openbmc/linux/sound/core/info.c (revision 886364f6)
11da177e4SLinus Torvalds /*
21da177e4SLinus Torvalds  *  Information interface for ALSA driver
3c1017a4cSJaroslav Kysela  *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
41da177e4SLinus Torvalds  *
51da177e4SLinus Torvalds  *
61da177e4SLinus Torvalds  *   This program is free software; you can redistribute it and/or modify
71da177e4SLinus Torvalds  *   it under the terms of the GNU General Public License as published by
81da177e4SLinus Torvalds  *   the Free Software Foundation; either version 2 of the License, or
91da177e4SLinus Torvalds  *   (at your option) any later version.
101da177e4SLinus Torvalds  *
111da177e4SLinus Torvalds  *   This program is distributed in the hope that it will be useful,
121da177e4SLinus Torvalds  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
131da177e4SLinus Torvalds  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
141da177e4SLinus Torvalds  *   GNU General Public License for more details.
151da177e4SLinus Torvalds  *
161da177e4SLinus Torvalds  *   You should have received a copy of the GNU General Public License
171da177e4SLinus Torvalds  *   along with this program; if not, write to the Free Software
181da177e4SLinus Torvalds  *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
191da177e4SLinus Torvalds  *
201da177e4SLinus Torvalds  */
211da177e4SLinus Torvalds 
221da177e4SLinus Torvalds #include <linux/init.h>
231da177e4SLinus Torvalds #include <linux/time.h>
2427ac792cSAndrea Righi #include <linux/mm.h>
255a0e3ad6STejun Heo #include <linux/slab.h>
26543537bdSPaulo Marques #include <linux/string.h>
27da155d5bSPaul Gortmaker #include <linux/module.h>
281da177e4SLinus Torvalds #include <sound/core.h>
291da177e4SLinus Torvalds #include <sound/minors.h>
301da177e4SLinus Torvalds #include <sound/info.h>
3142662748SJaroslav Kysela #include <linux/utsname.h>
321da177e4SLinus Torvalds #include <linux/proc_fs.h>
331a60d4c5SIngo Molnar #include <linux/mutex.h>
341da177e4SLinus Torvalds #include <stdarg.h>
351da177e4SLinus Torvalds 
361da177e4SLinus Torvalds /*
371da177e4SLinus Torvalds  *
381da177e4SLinus Torvalds  */
391da177e4SLinus Torvalds 
40e28563ccSTakashi Iwai #ifdef CONFIG_PROC_FS
41e28563ccSTakashi Iwai 
421da177e4SLinus Torvalds int snd_info_check_reserved_words(const char *str)
431da177e4SLinus Torvalds {
441da177e4SLinus Torvalds 	static char *reserved[] =
451da177e4SLinus Torvalds 	{
461da177e4SLinus Torvalds 		"version",
471da177e4SLinus Torvalds 		"meminfo",
481da177e4SLinus Torvalds 		"memdebug",
491da177e4SLinus Torvalds 		"detect",
501da177e4SLinus Torvalds 		"devices",
511da177e4SLinus Torvalds 		"oss",
521da177e4SLinus Torvalds 		"cards",
531da177e4SLinus Torvalds 		"timers",
541da177e4SLinus Torvalds 		"synth",
551da177e4SLinus Torvalds 		"pcm",
561da177e4SLinus Torvalds 		"seq",
571da177e4SLinus Torvalds 		NULL
581da177e4SLinus Torvalds 	};
591da177e4SLinus Torvalds 	char **xstr = reserved;
601da177e4SLinus Torvalds 
611da177e4SLinus Torvalds 	while (*xstr) {
621da177e4SLinus Torvalds 		if (!strcmp(*xstr, str))
631da177e4SLinus Torvalds 			return 0;
641da177e4SLinus Torvalds 		xstr++;
651da177e4SLinus Torvalds 	}
661da177e4SLinus Torvalds 	if (!strncmp(str, "card", 4))
671da177e4SLinus Torvalds 		return 0;
681da177e4SLinus Torvalds 	return 1;
691da177e4SLinus Torvalds }
701da177e4SLinus Torvalds 
711a60d4c5SIngo Molnar static DEFINE_MUTEX(info_mutex);
721da177e4SLinus Torvalds 
7324c1f931STakashi Iwai struct snd_info_private_data {
7424c1f931STakashi Iwai 	struct snd_info_buffer *rbuffer;
7524c1f931STakashi Iwai 	struct snd_info_buffer *wbuffer;
7624c1f931STakashi Iwai 	struct snd_info_entry *entry;
771da177e4SLinus Torvalds 	void *file_private_data;
7824c1f931STakashi Iwai };
791da177e4SLinus Torvalds 
801da177e4SLinus Torvalds static int snd_info_version_init(void);
811da177e4SLinus Torvalds static int snd_info_version_done(void);
82746d4a02STakashi Iwai static void snd_info_disconnect(struct snd_info_entry *entry);
831da177e4SLinus Torvalds 
841da177e4SLinus Torvalds /*
851da177e4SLinus Torvalds 
861da177e4SLinus Torvalds  */
871da177e4SLinus Torvalds 
886581f4e7STakashi Iwai static struct proc_dir_entry *snd_proc_root;
896581f4e7STakashi Iwai struct snd_info_entry *snd_seq_root;
90c0d3fb39STakashi Iwai EXPORT_SYMBOL(snd_seq_root);
91c0d3fb39STakashi Iwai 
921da177e4SLinus Torvalds #ifdef CONFIG_SND_OSSEMUL
936581f4e7STakashi Iwai struct snd_info_entry *snd_oss_root;
941da177e4SLinus Torvalds #endif
951da177e4SLinus Torvalds 
964adb7bcbSTakashi Iwai static int alloc_info_private(struct snd_info_entry *entry,
974adb7bcbSTakashi Iwai 			      struct snd_info_private_data **ret)
984adb7bcbSTakashi Iwai {
994adb7bcbSTakashi Iwai 	struct snd_info_private_data *data;
1004adb7bcbSTakashi Iwai 
1014adb7bcbSTakashi Iwai 	if (!entry || !entry->p)
1024adb7bcbSTakashi Iwai 		return -ENODEV;
1034adb7bcbSTakashi Iwai 	if (!try_module_get(entry->module))
1044adb7bcbSTakashi Iwai 		return -EFAULT;
1054adb7bcbSTakashi Iwai 	data = kzalloc(sizeof(*data), GFP_KERNEL);
1064adb7bcbSTakashi Iwai 	if (!data) {
1074adb7bcbSTakashi Iwai 		module_put(entry->module);
1084adb7bcbSTakashi Iwai 		return -ENOMEM;
1094adb7bcbSTakashi Iwai 	}
1104adb7bcbSTakashi Iwai 	data->entry = entry;
1114adb7bcbSTakashi Iwai 	*ret = data;
1124adb7bcbSTakashi Iwai 	return 0;
1134adb7bcbSTakashi Iwai }
1144adb7bcbSTakashi Iwai 
1154adb7bcbSTakashi Iwai static bool valid_pos(loff_t pos, size_t count)
1164adb7bcbSTakashi Iwai {
1174adb7bcbSTakashi Iwai 	if (pos < 0 || (long) pos != pos || (ssize_t) count < 0)
1184adb7bcbSTakashi Iwai 		return false;
1194adb7bcbSTakashi Iwai 	if ((unsigned long) pos + (unsigned long) count < (unsigned long) pos)
1204adb7bcbSTakashi Iwai 		return false;
1214adb7bcbSTakashi Iwai 	return true;
1224adb7bcbSTakashi Iwai }
1234adb7bcbSTakashi Iwai 
1244adb7bcbSTakashi Iwai /*
1254adb7bcbSTakashi Iwai  * file ops for binary proc files
1264adb7bcbSTakashi Iwai  */
1271da177e4SLinus Torvalds static loff_t snd_info_entry_llseek(struct file *file, loff_t offset, int orig)
1281da177e4SLinus Torvalds {
12924c1f931STakashi Iwai 	struct snd_info_private_data *data;
1301da177e4SLinus Torvalds 	struct snd_info_entry *entry;
13173029e0fSTakashi Iwai 	loff_t ret = -EINVAL, size;
1321da177e4SLinus Torvalds 
1331da177e4SLinus Torvalds 	data = file->private_data;
1341da177e4SLinus Torvalds 	entry = data->entry;
1355b5cd553STakashi Iwai 	mutex_lock(&entry->access);
1364adb7bcbSTakashi Iwai 	if (entry->c.ops->llseek) {
13773029e0fSTakashi Iwai 		offset = entry->c.ops->llseek(entry,
1381da177e4SLinus Torvalds 					      data->file_private_data,
1391da177e4SLinus Torvalds 					      file, offset, orig);
1401da177e4SLinus Torvalds 		goto out;
1411da177e4SLinus Torvalds 	}
1424adb7bcbSTakashi Iwai 
14373029e0fSTakashi Iwai 	size = entry->size;
14473029e0fSTakashi Iwai 	switch (orig) {
14573029e0fSTakashi Iwai 	case SEEK_SET:
1461da177e4SLinus Torvalds 		break;
14773029e0fSTakashi Iwai 	case SEEK_CUR:
14873029e0fSTakashi Iwai 		offset += file->f_pos;
14973029e0fSTakashi Iwai 		break;
15073029e0fSTakashi Iwai 	case SEEK_END:
15173029e0fSTakashi Iwai 		if (!size)
15273029e0fSTakashi Iwai 			goto out;
15373029e0fSTakashi Iwai 		offset += size;
15473029e0fSTakashi Iwai 		break;
15573029e0fSTakashi Iwai 	default:
15673029e0fSTakashi Iwai 		goto out;
1571da177e4SLinus Torvalds 	}
15873029e0fSTakashi Iwai 	if (offset < 0)
15973029e0fSTakashi Iwai 		goto out;
16073029e0fSTakashi Iwai 	if (size && offset > size)
16173029e0fSTakashi Iwai 		offset = size;
16273029e0fSTakashi Iwai 	file->f_pos = offset;
16373029e0fSTakashi Iwai 	ret = offset;
1641da177e4SLinus Torvalds  out:
1655b5cd553STakashi Iwai 	mutex_unlock(&entry->access);
1661da177e4SLinus Torvalds 	return ret;
1671da177e4SLinus Torvalds }
1681da177e4SLinus Torvalds 
1691da177e4SLinus Torvalds static ssize_t snd_info_entry_read(struct file *file, char __user *buffer,
1701da177e4SLinus Torvalds 				   size_t count, loff_t * offset)
1711da177e4SLinus Torvalds {
1724adb7bcbSTakashi Iwai 	struct snd_info_private_data *data = file->private_data;
1734adb7bcbSTakashi Iwai 	struct snd_info_entry *entry = data->entry;
1744adb7bcbSTakashi Iwai 	size_t size;
1751da177e4SLinus Torvalds 	loff_t pos;
1761da177e4SLinus Torvalds 
1771da177e4SLinus Torvalds 	pos = *offset;
1784adb7bcbSTakashi Iwai 	if (!valid_pos(pos, count))
1791da177e4SLinus Torvalds 		return -EIO;
180d97e1b78STakashi Iwai 	if (pos >= entry->size)
181d97e1b78STakashi Iwai 		return 0;
182d97e1b78STakashi Iwai 	size = entry->size - pos;
183d97e1b78STakashi Iwai 	size = min(count, size);
1844adb7bcbSTakashi Iwai 	size = entry->c.ops->read(entry, data->file_private_data,
185d97e1b78STakashi Iwai 				  file, buffer, size, pos);
1861da177e4SLinus Torvalds 	if ((ssize_t) size > 0)
1871da177e4SLinus Torvalds 		*offset = pos + size;
1881da177e4SLinus Torvalds 	return size;
1891da177e4SLinus Torvalds }
1901da177e4SLinus Torvalds 
1911da177e4SLinus Torvalds static ssize_t snd_info_entry_write(struct file *file, const char __user *buffer,
1921da177e4SLinus Torvalds 				    size_t count, loff_t * offset)
1931da177e4SLinus Torvalds {
1944adb7bcbSTakashi Iwai 	struct snd_info_private_data *data = file->private_data;
1954adb7bcbSTakashi Iwai 	struct snd_info_entry *entry = data->entry;
1967e4eeec8STakashi Iwai 	ssize_t size = 0;
1971da177e4SLinus Torvalds 	loff_t pos;
1981da177e4SLinus Torvalds 
1991da177e4SLinus Torvalds 	pos = *offset;
2004adb7bcbSTakashi Iwai 	if (!valid_pos(pos, count))
2011da177e4SLinus Torvalds 		return -EIO;
2024adb7bcbSTakashi Iwai 	if (count > 0) {
203d97e1b78STakashi Iwai 		size_t maxsize = entry->size - pos;
204d97e1b78STakashi Iwai 		count = min(count, maxsize);
2054adb7bcbSTakashi Iwai 		size = entry->c.ops->write(entry, data->file_private_data,
2061da177e4SLinus Torvalds 					   file, buffer, count, pos);
207d97e1b78STakashi Iwai 	}
2084adb7bcbSTakashi Iwai 	if (size > 0)
2091da177e4SLinus Torvalds 		*offset = pos + size;
2101da177e4SLinus Torvalds 	return size;
2111da177e4SLinus Torvalds }
2121da177e4SLinus Torvalds 
2131da177e4SLinus Torvalds static unsigned int snd_info_entry_poll(struct file *file, poll_table *wait)
2141da177e4SLinus Torvalds {
2154adb7bcbSTakashi Iwai 	struct snd_info_private_data *data = file->private_data;
2164adb7bcbSTakashi Iwai 	struct snd_info_entry *entry = data->entry;
2174adb7bcbSTakashi Iwai 	unsigned int mask = 0;
2181da177e4SLinus Torvalds 
2191da177e4SLinus Torvalds 	if (entry->c.ops->poll)
2201da177e4SLinus Torvalds 		return entry->c.ops->poll(entry,
2211da177e4SLinus Torvalds 					  data->file_private_data,
2221da177e4SLinus Torvalds 					  file, wait);
2231da177e4SLinus Torvalds 	if (entry->c.ops->read)
2241da177e4SLinus Torvalds 		mask |= POLLIN | POLLRDNORM;
2251da177e4SLinus Torvalds 	if (entry->c.ops->write)
2261da177e4SLinus Torvalds 		mask |= POLLOUT | POLLWRNORM;
2271da177e4SLinus Torvalds 	return mask;
2281da177e4SLinus Torvalds }
2291da177e4SLinus Torvalds 
230d99e9889SIngo Molnar static long snd_info_entry_ioctl(struct file *file, unsigned int cmd,
231d99e9889SIngo Molnar 				unsigned long arg)
2321da177e4SLinus Torvalds {
2334adb7bcbSTakashi Iwai 	struct snd_info_private_data *data = file->private_data;
2344adb7bcbSTakashi Iwai 	struct snd_info_entry *entry = data->entry;
2351da177e4SLinus Torvalds 
2364adb7bcbSTakashi Iwai 	if (!entry->c.ops->ioctl)
2371da177e4SLinus Torvalds 		return -ENOTTY;
2384adb7bcbSTakashi Iwai 	return entry->c.ops->ioctl(entry, data->file_private_data,
2394adb7bcbSTakashi Iwai 				   file, cmd, arg);
2401da177e4SLinus Torvalds }
2411da177e4SLinus Torvalds 
2421da177e4SLinus Torvalds static int snd_info_entry_mmap(struct file *file, struct vm_area_struct *vma)
2431da177e4SLinus Torvalds {
244496ad9aaSAl Viro 	struct inode *inode = file_inode(file);
24524c1f931STakashi Iwai 	struct snd_info_private_data *data;
2461da177e4SLinus Torvalds 	struct snd_info_entry *entry;
2471da177e4SLinus Torvalds 
2481da177e4SLinus Torvalds 	data = file->private_data;
2491da177e4SLinus Torvalds 	if (data == NULL)
2501da177e4SLinus Torvalds 		return 0;
2511da177e4SLinus Torvalds 	entry = data->entry;
2524adb7bcbSTakashi Iwai 	if (!entry->c.ops->mmap)
2531da177e4SLinus Torvalds 		return -ENXIO;
2544adb7bcbSTakashi Iwai 	return entry->c.ops->mmap(entry, data->file_private_data,
2554adb7bcbSTakashi Iwai 				  inode, file, vma);
2564adb7bcbSTakashi Iwai }
2574adb7bcbSTakashi Iwai 
2584adb7bcbSTakashi Iwai static int snd_info_entry_open(struct inode *inode, struct file *file)
2594adb7bcbSTakashi Iwai {
2604adb7bcbSTakashi Iwai 	struct snd_info_entry *entry = PDE_DATA(inode);
2614adb7bcbSTakashi Iwai 	struct snd_info_private_data *data;
2624adb7bcbSTakashi Iwai 	int mode, err;
2634adb7bcbSTakashi Iwai 
2644adb7bcbSTakashi Iwai 	mutex_lock(&info_mutex);
2654adb7bcbSTakashi Iwai 	err = alloc_info_private(entry, &data);
2664adb7bcbSTakashi Iwai 	if (err < 0)
2674adb7bcbSTakashi Iwai 		goto unlock;
2684adb7bcbSTakashi Iwai 
2694adb7bcbSTakashi Iwai 	mode = file->f_flags & O_ACCMODE;
2704adb7bcbSTakashi Iwai 	if (((mode == O_RDONLY || mode == O_RDWR) && !entry->c.ops->read) ||
2714adb7bcbSTakashi Iwai 	    ((mode == O_WRONLY || mode == O_RDWR) && !entry->c.ops->write)) {
2724adb7bcbSTakashi Iwai 		err = -ENODEV;
2734adb7bcbSTakashi Iwai 		goto error;
2744adb7bcbSTakashi Iwai 	}
2754adb7bcbSTakashi Iwai 
2764adb7bcbSTakashi Iwai 	if (entry->c.ops->open) {
2774adb7bcbSTakashi Iwai 		err = entry->c.ops->open(entry, mode, &data->file_private_data);
2784adb7bcbSTakashi Iwai 		if (err < 0)
2794adb7bcbSTakashi Iwai 			goto error;
2804adb7bcbSTakashi Iwai 	}
2814adb7bcbSTakashi Iwai 
2824adb7bcbSTakashi Iwai 	file->private_data = data;
2834adb7bcbSTakashi Iwai 	mutex_unlock(&info_mutex);
2844adb7bcbSTakashi Iwai 	return 0;
2854adb7bcbSTakashi Iwai 
2864adb7bcbSTakashi Iwai  error:
2874adb7bcbSTakashi Iwai 	kfree(data);
2884adb7bcbSTakashi Iwai 	module_put(entry->module);
2894adb7bcbSTakashi Iwai  unlock:
2904adb7bcbSTakashi Iwai 	mutex_unlock(&info_mutex);
2914adb7bcbSTakashi Iwai 	return err;
2924adb7bcbSTakashi Iwai }
2934adb7bcbSTakashi Iwai 
2944adb7bcbSTakashi Iwai static int snd_info_entry_release(struct inode *inode, struct file *file)
2954adb7bcbSTakashi Iwai {
2964adb7bcbSTakashi Iwai 	struct snd_info_private_data *data = file->private_data;
2974adb7bcbSTakashi Iwai 	struct snd_info_entry *entry = data->entry;
2984adb7bcbSTakashi Iwai 
2994adb7bcbSTakashi Iwai 	if (entry->c.ops->release)
3004adb7bcbSTakashi Iwai 		entry->c.ops->release(entry, file->f_flags & O_ACCMODE,
3014adb7bcbSTakashi Iwai 				      data->file_private_data);
3024adb7bcbSTakashi Iwai 	module_put(entry->module);
3034adb7bcbSTakashi Iwai 	kfree(data);
3044adb7bcbSTakashi Iwai 	return 0;
3051da177e4SLinus Torvalds }
3061da177e4SLinus Torvalds 
3079c2e08c5SArjan van de Ven static const struct file_operations snd_info_entry_operations =
3081da177e4SLinus Torvalds {
3091da177e4SLinus Torvalds 	.owner =		THIS_MODULE,
3101da177e4SLinus Torvalds 	.llseek =		snd_info_entry_llseek,
3111da177e4SLinus Torvalds 	.read =			snd_info_entry_read,
3121da177e4SLinus Torvalds 	.write =		snd_info_entry_write,
3131da177e4SLinus Torvalds 	.poll =			snd_info_entry_poll,
314d99e9889SIngo Molnar 	.unlocked_ioctl =	snd_info_entry_ioctl,
3151da177e4SLinus Torvalds 	.mmap =			snd_info_entry_mmap,
3161da177e4SLinus Torvalds 	.open =			snd_info_entry_open,
3171da177e4SLinus Torvalds 	.release =		snd_info_entry_release,
3181da177e4SLinus Torvalds };
3191da177e4SLinus Torvalds 
3204adb7bcbSTakashi Iwai /*
3214adb7bcbSTakashi Iwai  * file ops for text proc files
3224adb7bcbSTakashi Iwai  */
3234adb7bcbSTakashi Iwai static ssize_t snd_info_text_entry_write(struct file *file,
3244adb7bcbSTakashi Iwai 					 const char __user *buffer,
3254adb7bcbSTakashi Iwai 					 size_t count, loff_t *offset)
3264adb7bcbSTakashi Iwai {
3274adb7bcbSTakashi Iwai 	struct seq_file *m = file->private_data;
3284adb7bcbSTakashi Iwai 	struct snd_info_private_data *data = m->private;
3294adb7bcbSTakashi Iwai 	struct snd_info_entry *entry = data->entry;
3304adb7bcbSTakashi Iwai 	struct snd_info_buffer *buf;
3314adb7bcbSTakashi Iwai 	loff_t pos;
3324adb7bcbSTakashi Iwai 	size_t next;
3334adb7bcbSTakashi Iwai 	int err = 0;
3344adb7bcbSTakashi Iwai 
3354adb7bcbSTakashi Iwai 	pos = *offset;
3364adb7bcbSTakashi Iwai 	if (!valid_pos(pos, count))
3374adb7bcbSTakashi Iwai 		return -EIO;
3384adb7bcbSTakashi Iwai 	next = pos + count;
3394adb7bcbSTakashi Iwai 	mutex_lock(&entry->access);
3404adb7bcbSTakashi Iwai 	buf = data->wbuffer;
3414adb7bcbSTakashi Iwai 	if (!buf) {
3424adb7bcbSTakashi Iwai 		data->wbuffer = buf = kzalloc(sizeof(*buf), GFP_KERNEL);
3434adb7bcbSTakashi Iwai 		if (!buf) {
3444adb7bcbSTakashi Iwai 			err = -ENOMEM;
3454adb7bcbSTakashi Iwai 			goto error;
3464adb7bcbSTakashi Iwai 		}
3474adb7bcbSTakashi Iwai 	}
3484adb7bcbSTakashi Iwai 	if (next > buf->len) {
3494adb7bcbSTakashi Iwai 		char *nbuf = krealloc(buf->buffer, PAGE_ALIGN(next),
3504adb7bcbSTakashi Iwai 				      GFP_KERNEL | __GFP_ZERO);
3514adb7bcbSTakashi Iwai 		if (!nbuf) {
3524adb7bcbSTakashi Iwai 			err = -ENOMEM;
3534adb7bcbSTakashi Iwai 			goto error;
3544adb7bcbSTakashi Iwai 		}
3554adb7bcbSTakashi Iwai 		buf->buffer = nbuf;
3564adb7bcbSTakashi Iwai 		buf->len = PAGE_ALIGN(next);
3574adb7bcbSTakashi Iwai 	}
3584adb7bcbSTakashi Iwai 	if (copy_from_user(buf->buffer + pos, buffer, count)) {
3594adb7bcbSTakashi Iwai 		err = -EFAULT;
3604adb7bcbSTakashi Iwai 		goto error;
3614adb7bcbSTakashi Iwai 	}
3624adb7bcbSTakashi Iwai 	buf->size = next;
3634adb7bcbSTakashi Iwai  error:
3644adb7bcbSTakashi Iwai 	mutex_unlock(&entry->access);
3654adb7bcbSTakashi Iwai 	if (err < 0)
3664adb7bcbSTakashi Iwai 		return err;
3674adb7bcbSTakashi Iwai 	*offset = next;
3684adb7bcbSTakashi Iwai 	return count;
3694adb7bcbSTakashi Iwai }
3704adb7bcbSTakashi Iwai 
3714adb7bcbSTakashi Iwai static int snd_info_seq_show(struct seq_file *seq, void *p)
3724adb7bcbSTakashi Iwai {
3734adb7bcbSTakashi Iwai 	struct snd_info_private_data *data = seq->private;
3744adb7bcbSTakashi Iwai 	struct snd_info_entry *entry = data->entry;
3754adb7bcbSTakashi Iwai 
3764adb7bcbSTakashi Iwai 	if (entry->c.text.read) {
3774adb7bcbSTakashi Iwai 		data->rbuffer->buffer = (char *)seq; /* XXX hack! */
3784adb7bcbSTakashi Iwai 		entry->c.text.read(entry, data->rbuffer);
3794adb7bcbSTakashi Iwai 	}
3804adb7bcbSTakashi Iwai 	return 0;
3814adb7bcbSTakashi Iwai }
3824adb7bcbSTakashi Iwai 
3834adb7bcbSTakashi Iwai static int snd_info_text_entry_open(struct inode *inode, struct file *file)
3844adb7bcbSTakashi Iwai {
3854adb7bcbSTakashi Iwai 	struct snd_info_entry *entry = PDE_DATA(inode);
3864adb7bcbSTakashi Iwai 	struct snd_info_private_data *data;
3874adb7bcbSTakashi Iwai 	int err;
3884adb7bcbSTakashi Iwai 
3894adb7bcbSTakashi Iwai 	mutex_lock(&info_mutex);
3904adb7bcbSTakashi Iwai 	err = alloc_info_private(entry, &data);
3914adb7bcbSTakashi Iwai 	if (err < 0)
3924adb7bcbSTakashi Iwai 		goto unlock;
3934adb7bcbSTakashi Iwai 
3944adb7bcbSTakashi Iwai 	data->rbuffer = kzalloc(sizeof(*data->rbuffer), GFP_KERNEL);
3954adb7bcbSTakashi Iwai 	if (!data->rbuffer) {
3964adb7bcbSTakashi Iwai 		err = -ENOMEM;
3974adb7bcbSTakashi Iwai 		goto error;
3984adb7bcbSTakashi Iwai 	}
3994adb7bcbSTakashi Iwai 	if (entry->size)
4004adb7bcbSTakashi Iwai 		err = single_open_size(file, snd_info_seq_show, data,
4014adb7bcbSTakashi Iwai 				       entry->size);
4024adb7bcbSTakashi Iwai 	else
4034adb7bcbSTakashi Iwai 		err = single_open(file, snd_info_seq_show, data);
4044adb7bcbSTakashi Iwai 	if (err < 0)
4054adb7bcbSTakashi Iwai 		goto error;
4064adb7bcbSTakashi Iwai 	mutex_unlock(&info_mutex);
4074adb7bcbSTakashi Iwai 	return 0;
4084adb7bcbSTakashi Iwai 
4094adb7bcbSTakashi Iwai  error:
4104adb7bcbSTakashi Iwai 	kfree(data->rbuffer);
4114adb7bcbSTakashi Iwai 	kfree(data);
4124adb7bcbSTakashi Iwai 	module_put(entry->module);
4134adb7bcbSTakashi Iwai  unlock:
4144adb7bcbSTakashi Iwai 	mutex_unlock(&info_mutex);
4154adb7bcbSTakashi Iwai 	return err;
4164adb7bcbSTakashi Iwai }
4174adb7bcbSTakashi Iwai 
4184adb7bcbSTakashi Iwai static int snd_info_text_entry_release(struct inode *inode, struct file *file)
4194adb7bcbSTakashi Iwai {
4204adb7bcbSTakashi Iwai 	struct seq_file *m = file->private_data;
4214adb7bcbSTakashi Iwai 	struct snd_info_private_data *data = m->private;
4224adb7bcbSTakashi Iwai 	struct snd_info_entry *entry = data->entry;
4234adb7bcbSTakashi Iwai 
4244adb7bcbSTakashi Iwai 	if (data->wbuffer && entry->c.text.write)
4254adb7bcbSTakashi Iwai 		entry->c.text.write(entry, data->wbuffer);
4264adb7bcbSTakashi Iwai 
4274adb7bcbSTakashi Iwai 	single_release(inode, file);
4284adb7bcbSTakashi Iwai 	kfree(data->rbuffer);
4294adb7bcbSTakashi Iwai 	if (data->wbuffer) {
4304adb7bcbSTakashi Iwai 		kfree(data->wbuffer->buffer);
4314adb7bcbSTakashi Iwai 		kfree(data->wbuffer);
4324adb7bcbSTakashi Iwai 	}
4334adb7bcbSTakashi Iwai 
4344adb7bcbSTakashi Iwai 	module_put(entry->module);
4354adb7bcbSTakashi Iwai 	kfree(data);
4364adb7bcbSTakashi Iwai 	return 0;
4374adb7bcbSTakashi Iwai }
4384adb7bcbSTakashi Iwai 
4394adb7bcbSTakashi Iwai static const struct file_operations snd_info_text_entry_ops =
4404adb7bcbSTakashi Iwai {
4414adb7bcbSTakashi Iwai 	.owner =		THIS_MODULE,
4424adb7bcbSTakashi Iwai 	.open =			snd_info_text_entry_open,
4434adb7bcbSTakashi Iwai 	.release =		snd_info_text_entry_release,
4444adb7bcbSTakashi Iwai 	.write =		snd_info_text_entry_write,
4454adb7bcbSTakashi Iwai 	.llseek =		seq_lseek,
4464adb7bcbSTakashi Iwai 	.read =			seq_read,
4474adb7bcbSTakashi Iwai };
4484adb7bcbSTakashi Iwai 
449886364f6STakashi Iwai static struct snd_info_entry *create_subdir(struct module *mod,
450886364f6STakashi Iwai 					    const char *name)
451886364f6STakashi Iwai {
452886364f6STakashi Iwai 	struct snd_info_entry *entry;
453886364f6STakashi Iwai 
454886364f6STakashi Iwai 	entry = snd_info_create_module_entry(mod, name, NULL);
455886364f6STakashi Iwai 	if (!entry)
456886364f6STakashi Iwai 		return NULL;
457886364f6STakashi Iwai 	entry->mode = S_IFDIR | S_IRUGO | S_IXUGO;
458886364f6STakashi Iwai 	if (snd_info_register(entry) < 0) {
459886364f6STakashi Iwai 		snd_info_free_entry(entry);
460886364f6STakashi Iwai 		return NULL;
461886364f6STakashi Iwai 	}
462886364f6STakashi Iwai 	return entry;
463886364f6STakashi Iwai }
464886364f6STakashi Iwai 
4651da177e4SLinus Torvalds int __init snd_info_init(void)
4661da177e4SLinus Torvalds {
4671da177e4SLinus Torvalds 	struct proc_dir_entry *p;
4681da177e4SLinus Torvalds 
469e55d92b9SAl Viro 	p = proc_mkdir("asound", NULL);
4701da177e4SLinus Torvalds 	if (p == NULL)
4711da177e4SLinus Torvalds 		return -ENOMEM;
4721da177e4SLinus Torvalds 	snd_proc_root = p;
4731da177e4SLinus Torvalds #ifdef CONFIG_SND_OSSEMUL
474886364f6STakashi Iwai 	snd_oss_root = create_subdir(THIS_MODULE, "oss");
475886364f6STakashi Iwai 	if (!snd_oss_root)
476886364f6STakashi Iwai 		goto error;
4771da177e4SLinus Torvalds #endif
4788eeaa2f9STakashi Iwai #if IS_ENABLED(CONFIG_SND_SEQUENCER)
479886364f6STakashi Iwai 	snd_seq_root = create_subdir(THIS_MODULE, "seq");
480886364f6STakashi Iwai 	if (!snd_seq_root)
481886364f6STakashi Iwai 		goto error;
4821da177e4SLinus Torvalds #endif
4831da177e4SLinus Torvalds 	snd_info_version_init();
4841da177e4SLinus Torvalds 	snd_minor_info_init();
4851da177e4SLinus Torvalds 	snd_minor_info_oss_init();
4861da177e4SLinus Torvalds 	snd_card_info_init();
4871da177e4SLinus Torvalds 	return 0;
488886364f6STakashi Iwai 
489886364f6STakashi Iwai  error:
490886364f6STakashi Iwai #ifdef CONFIG_SND_OSSEMUL
491886364f6STakashi Iwai 	snd_info_free_entry(snd_oss_root);
492886364f6STakashi Iwai #endif
493886364f6STakashi Iwai 	proc_remove(snd_proc_root);
494886364f6STakashi Iwai 	return -ENOMEM;
4951da177e4SLinus Torvalds }
4961da177e4SLinus Torvalds 
4971da177e4SLinus Torvalds int __exit snd_info_done(void)
4981da177e4SLinus Torvalds {
4991da177e4SLinus Torvalds 	snd_card_info_done();
5001da177e4SLinus Torvalds 	snd_minor_info_oss_done();
5011da177e4SLinus Torvalds 	snd_minor_info_done();
5021da177e4SLinus Torvalds 	snd_info_version_done();
5031da177e4SLinus Torvalds 	if (snd_proc_root) {
5048eeaa2f9STakashi Iwai #if IS_ENABLED(CONFIG_SND_SEQUENCER)
505746d4a02STakashi Iwai 		snd_info_free_entry(snd_seq_root);
5061da177e4SLinus Torvalds #endif
5071da177e4SLinus Torvalds #ifdef CONFIG_SND_OSSEMUL
508746d4a02STakashi Iwai 		snd_info_free_entry(snd_oss_root);
5091da177e4SLinus Torvalds #endif
510a8ca16eaSDavid Howells 		proc_remove(snd_proc_root);
5111da177e4SLinus Torvalds 	}
5121da177e4SLinus Torvalds 	return 0;
5131da177e4SLinus Torvalds }
5141da177e4SLinus Torvalds 
5151da177e4SLinus Torvalds /*
5161da177e4SLinus Torvalds 
5171da177e4SLinus Torvalds  */
5181da177e4SLinus Torvalds 
5191da177e4SLinus Torvalds 
5201da177e4SLinus Torvalds /*
5211da177e4SLinus Torvalds  * create a card proc file
5221da177e4SLinus Torvalds  * called from init.c
5231da177e4SLinus Torvalds  */
52424c1f931STakashi Iwai int snd_info_card_create(struct snd_card *card)
5251da177e4SLinus Torvalds {
5261da177e4SLinus Torvalds 	char str[8];
52724c1f931STakashi Iwai 	struct snd_info_entry *entry;
5281da177e4SLinus Torvalds 
5297eaa943cSTakashi Iwai 	if (snd_BUG_ON(!card))
5307eaa943cSTakashi Iwai 		return -ENXIO;
5311da177e4SLinus Torvalds 
5321da177e4SLinus Torvalds 	sprintf(str, "card%i", card->number);
533886364f6STakashi Iwai 	entry = create_subdir(card->module, str);
534886364f6STakashi Iwai 	if (!entry)
5351da177e4SLinus Torvalds 		return -ENOMEM;
5361da177e4SLinus Torvalds 	card->proc_root = entry;
5371da177e4SLinus Torvalds 	return 0;
5381da177e4SLinus Torvalds }
5391da177e4SLinus Torvalds 
5401da177e4SLinus Torvalds /*
5411da177e4SLinus Torvalds  * register the card proc file
5421da177e4SLinus Torvalds  * called from init.c
5431da177e4SLinus Torvalds  */
54424c1f931STakashi Iwai int snd_info_card_register(struct snd_card *card)
5451da177e4SLinus Torvalds {
5461da177e4SLinus Torvalds 	struct proc_dir_entry *p;
5471da177e4SLinus Torvalds 
5487eaa943cSTakashi Iwai 	if (snd_BUG_ON(!card))
5497eaa943cSTakashi Iwai 		return -ENXIO;
5501da177e4SLinus Torvalds 
5511da177e4SLinus Torvalds 	if (!strcmp(card->id, card->proc_root->name))
5521da177e4SLinus Torvalds 		return 0;
5531da177e4SLinus Torvalds 
5541da177e4SLinus Torvalds 	p = proc_symlink(card->id, snd_proc_root, card->proc_root->name);
5551da177e4SLinus Torvalds 	if (p == NULL)
5561da177e4SLinus Torvalds 		return -ENOMEM;
5571da177e4SLinus Torvalds 	card->proc_root_link = p;
5581da177e4SLinus Torvalds 	return 0;
5591da177e4SLinus Torvalds }
5601da177e4SLinus Torvalds 
5611da177e4SLinus Torvalds /*
562c2eb9c4eSJaroslav Kysela  * called on card->id change
563c2eb9c4eSJaroslav Kysela  */
564c2eb9c4eSJaroslav Kysela void snd_info_card_id_change(struct snd_card *card)
565c2eb9c4eSJaroslav Kysela {
566c2eb9c4eSJaroslav Kysela 	mutex_lock(&info_mutex);
567c2eb9c4eSJaroslav Kysela 	if (card->proc_root_link) {
568a8ca16eaSDavid Howells 		proc_remove(card->proc_root_link);
569c2eb9c4eSJaroslav Kysela 		card->proc_root_link = NULL;
570c2eb9c4eSJaroslav Kysela 	}
571c2eb9c4eSJaroslav Kysela 	if (strcmp(card->id, card->proc_root->name))
572c2eb9c4eSJaroslav Kysela 		card->proc_root_link = proc_symlink(card->id,
573c2eb9c4eSJaroslav Kysela 						    snd_proc_root,
574c2eb9c4eSJaroslav Kysela 						    card->proc_root->name);
575c2eb9c4eSJaroslav Kysela 	mutex_unlock(&info_mutex);
576c2eb9c4eSJaroslav Kysela }
577c2eb9c4eSJaroslav Kysela 
578c2eb9c4eSJaroslav Kysela /*
5791da177e4SLinus Torvalds  * de-register the card proc file
5801da177e4SLinus Torvalds  * called from init.c
5811da177e4SLinus Torvalds  */
582746d4a02STakashi Iwai void snd_info_card_disconnect(struct snd_card *card)
5831da177e4SLinus Torvalds {
5847eaa943cSTakashi Iwai 	if (!card)
5857eaa943cSTakashi Iwai 		return;
586746d4a02STakashi Iwai 	mutex_lock(&info_mutex);
587a8ca16eaSDavid Howells 	proc_remove(card->proc_root_link);
5881da177e4SLinus Torvalds 	card->proc_root_link = NULL;
589746d4a02STakashi Iwai 	if (card->proc_root)
590746d4a02STakashi Iwai 		snd_info_disconnect(card->proc_root);
591746d4a02STakashi Iwai 	mutex_unlock(&info_mutex);
5921da177e4SLinus Torvalds }
593746d4a02STakashi Iwai 
594746d4a02STakashi Iwai /*
595746d4a02STakashi Iwai  * release the card proc file resources
596746d4a02STakashi Iwai  * called from init.c
597746d4a02STakashi Iwai  */
598746d4a02STakashi Iwai int snd_info_card_free(struct snd_card *card)
599746d4a02STakashi Iwai {
6007eaa943cSTakashi Iwai 	if (!card)
6017eaa943cSTakashi Iwai 		return 0;
602746d4a02STakashi Iwai 	snd_info_free_entry(card->proc_root);
603746d4a02STakashi Iwai 	card->proc_root = NULL;
6041da177e4SLinus Torvalds 	return 0;
6051da177e4SLinus Torvalds }
6061da177e4SLinus Torvalds 
6071da177e4SLinus Torvalds 
6081da177e4SLinus Torvalds /**
6091da177e4SLinus Torvalds  * snd_info_get_line - read one line from the procfs buffer
6101da177e4SLinus Torvalds  * @buffer: the procfs buffer
6111da177e4SLinus Torvalds  * @line: the buffer to store
612ddc64b27SClemens Ladisch  * @len: the max. buffer size
6131da177e4SLinus Torvalds  *
6141da177e4SLinus Torvalds  * Reads one line from the buffer and stores the string.
6151da177e4SLinus Torvalds  *
616eb7c06e8SYacine Belkadi  * Return: Zero if successful, or 1 if error or EOF.
6171da177e4SLinus Torvalds  */
61824c1f931STakashi Iwai int snd_info_get_line(struct snd_info_buffer *buffer, char *line, int len)
6191da177e4SLinus Torvalds {
6201da177e4SLinus Torvalds 	int c = -1;
6211da177e4SLinus Torvalds 
6220bc0ec90STakashi Iwai 	if (snd_BUG_ON(!buffer || !buffer->buffer))
6230bc0ec90STakashi Iwai 		return 1;
6241da177e4SLinus Torvalds 	if (len <= 0 || buffer->stop || buffer->error)
6251da177e4SLinus Torvalds 		return 1;
6260bc0ec90STakashi Iwai 	while (!buffer->stop) {
6277e4eeec8STakashi Iwai 		c = buffer->buffer[buffer->curr++];
6287e4eeec8STakashi Iwai 		if (buffer->curr >= buffer->size)
6291da177e4SLinus Torvalds 			buffer->stop = 1;
6300bc0ec90STakashi Iwai 		if (c == '\n')
6311da177e4SLinus Torvalds 			break;
632ddc64b27SClemens Ladisch 		if (len > 1) {
6330bc0ec90STakashi Iwai 			len--;
6341da177e4SLinus Torvalds 			*line++ = c;
6351da177e4SLinus Torvalds 		}
6361da177e4SLinus Torvalds 	}
6371da177e4SLinus Torvalds 	*line = '\0';
6381da177e4SLinus Torvalds 	return 0;
6391da177e4SLinus Torvalds }
6401da177e4SLinus Torvalds 
641c0d3fb39STakashi Iwai EXPORT_SYMBOL(snd_info_get_line);
642c0d3fb39STakashi Iwai 
6431da177e4SLinus Torvalds /**
644856def8aSHenrik Kretzschmar  * snd_info_get_str - parse a string token
6451da177e4SLinus Torvalds  * @dest: the buffer to store the string token
6461da177e4SLinus Torvalds  * @src: the original string
6471da177e4SLinus Torvalds  * @len: the max. length of token - 1
6481da177e4SLinus Torvalds  *
6491da177e4SLinus Torvalds  * Parses the original string and copy a token to the given
6501da177e4SLinus Torvalds  * string buffer.
6511da177e4SLinus Torvalds  *
652eb7c06e8SYacine Belkadi  * Return: The updated pointer of the original string so that
6531da177e4SLinus Torvalds  * it can be used for the next call.
6541da177e4SLinus Torvalds  */
6554f7454a9STakashi Iwai const char *snd_info_get_str(char *dest, const char *src, int len)
6561da177e4SLinus Torvalds {
6571da177e4SLinus Torvalds 	int c;
6581da177e4SLinus Torvalds 
6591da177e4SLinus Torvalds 	while (*src == ' ' || *src == '\t')
6601da177e4SLinus Torvalds 		src++;
6611da177e4SLinus Torvalds 	if (*src == '"' || *src == '\'') {
6621da177e4SLinus Torvalds 		c = *src++;
6631da177e4SLinus Torvalds 		while (--len > 0 && *src && *src != c) {
6641da177e4SLinus Torvalds 			*dest++ = *src++;
6651da177e4SLinus Torvalds 		}
6661da177e4SLinus Torvalds 		if (*src == c)
6671da177e4SLinus Torvalds 			src++;
6681da177e4SLinus Torvalds 	} else {
6691da177e4SLinus Torvalds 		while (--len > 0 && *src && *src != ' ' && *src != '\t') {
6701da177e4SLinus Torvalds 			*dest++ = *src++;
6711da177e4SLinus Torvalds 		}
6721da177e4SLinus Torvalds 	}
6731da177e4SLinus Torvalds 	*dest = 0;
6741da177e4SLinus Torvalds 	while (*src == ' ' || *src == '\t')
6751da177e4SLinus Torvalds 		src++;
6761da177e4SLinus Torvalds 	return src;
6771da177e4SLinus Torvalds }
6781da177e4SLinus Torvalds 
679c0d3fb39STakashi Iwai EXPORT_SYMBOL(snd_info_get_str);
680c0d3fb39STakashi Iwai 
6811da177e4SLinus Torvalds /**
6821da177e4SLinus Torvalds  * snd_info_create_entry - create an info entry
6831da177e4SLinus Torvalds  * @name: the proc file name
6841da177e4SLinus Torvalds  *
6851da177e4SLinus Torvalds  * Creates an info entry with the given file name and initializes as
6861da177e4SLinus Torvalds  * the default state.
6871da177e4SLinus Torvalds  *
6881da177e4SLinus Torvalds  * Usually called from other functions such as
6891da177e4SLinus Torvalds  * snd_info_create_card_entry().
6901da177e4SLinus Torvalds  *
691eb7c06e8SYacine Belkadi  * Return: The pointer of the new instance, or %NULL on failure.
6921da177e4SLinus Torvalds  */
69324c1f931STakashi Iwai static struct snd_info_entry *snd_info_create_entry(const char *name)
6941da177e4SLinus Torvalds {
69524c1f931STakashi Iwai 	struct snd_info_entry *entry;
696ca2c0966STakashi Iwai 	entry = kzalloc(sizeof(*entry), GFP_KERNEL);
6971da177e4SLinus Torvalds 	if (entry == NULL)
6981da177e4SLinus Torvalds 		return NULL;
699543537bdSPaulo Marques 	entry->name = kstrdup(name, GFP_KERNEL);
7001da177e4SLinus Torvalds 	if (entry->name == NULL) {
7011da177e4SLinus Torvalds 		kfree(entry);
7021da177e4SLinus Torvalds 		return NULL;
7031da177e4SLinus Torvalds 	}
7041da177e4SLinus Torvalds 	entry->mode = S_IFREG | S_IRUGO;
7051da177e4SLinus Torvalds 	entry->content = SNDRV_INFO_CONTENT_TEXT;
7061a60d4c5SIngo Molnar 	mutex_init(&entry->access);
707746d4a02STakashi Iwai 	INIT_LIST_HEAD(&entry->children);
708746d4a02STakashi Iwai 	INIT_LIST_HEAD(&entry->list);
7091da177e4SLinus Torvalds 	return entry;
7101da177e4SLinus Torvalds }
7111da177e4SLinus Torvalds 
7121da177e4SLinus Torvalds /**
7131da177e4SLinus Torvalds  * snd_info_create_module_entry - create an info entry for the given module
7141da177e4SLinus Torvalds  * @module: the module pointer
7151da177e4SLinus Torvalds  * @name: the file name
7161da177e4SLinus Torvalds  * @parent: the parent directory
7171da177e4SLinus Torvalds  *
7181da177e4SLinus Torvalds  * Creates a new info entry and assigns it to the given module.
7191da177e4SLinus Torvalds  *
720eb7c06e8SYacine Belkadi  * Return: The pointer of the new instance, or %NULL on failure.
7211da177e4SLinus Torvalds  */
72224c1f931STakashi Iwai struct snd_info_entry *snd_info_create_module_entry(struct module * module,
7231da177e4SLinus Torvalds 					       const char *name,
72424c1f931STakashi Iwai 					       struct snd_info_entry *parent)
7251da177e4SLinus Torvalds {
72624c1f931STakashi Iwai 	struct snd_info_entry *entry = snd_info_create_entry(name);
7271da177e4SLinus Torvalds 	if (entry) {
7281da177e4SLinus Torvalds 		entry->module = module;
7291da177e4SLinus Torvalds 		entry->parent = parent;
7301da177e4SLinus Torvalds 	}
7311da177e4SLinus Torvalds 	return entry;
7321da177e4SLinus Torvalds }
7331da177e4SLinus Torvalds 
734c0d3fb39STakashi Iwai EXPORT_SYMBOL(snd_info_create_module_entry);
735c0d3fb39STakashi Iwai 
7361da177e4SLinus Torvalds /**
7371da177e4SLinus Torvalds  * snd_info_create_card_entry - create an info entry for the given card
7381da177e4SLinus Torvalds  * @card: the card instance
7391da177e4SLinus Torvalds  * @name: the file name
7401da177e4SLinus Torvalds  * @parent: the parent directory
7411da177e4SLinus Torvalds  *
7421da177e4SLinus Torvalds  * Creates a new info entry and assigns it to the given card.
7431da177e4SLinus Torvalds  *
744eb7c06e8SYacine Belkadi  * Return: The pointer of the new instance, or %NULL on failure.
7451da177e4SLinus Torvalds  */
74624c1f931STakashi Iwai struct snd_info_entry *snd_info_create_card_entry(struct snd_card *card,
7471da177e4SLinus Torvalds 					     const char *name,
74824c1f931STakashi Iwai 					     struct snd_info_entry * parent)
7491da177e4SLinus Torvalds {
75024c1f931STakashi Iwai 	struct snd_info_entry *entry = snd_info_create_entry(name);
7511da177e4SLinus Torvalds 	if (entry) {
7521da177e4SLinus Torvalds 		entry->module = card->module;
7531da177e4SLinus Torvalds 		entry->card = card;
7541da177e4SLinus Torvalds 		entry->parent = parent;
7551da177e4SLinus Torvalds 	}
7561da177e4SLinus Torvalds 	return entry;
7571da177e4SLinus Torvalds }
7581da177e4SLinus Torvalds 
759c0d3fb39STakashi Iwai EXPORT_SYMBOL(snd_info_create_card_entry);
760c0d3fb39STakashi Iwai 
761746d4a02STakashi Iwai static void snd_info_disconnect(struct snd_info_entry *entry)
762746d4a02STakashi Iwai {
763746d4a02STakashi Iwai 	struct list_head *p, *n;
764746d4a02STakashi Iwai 
765746d4a02STakashi Iwai 	list_for_each_safe(p, n, &entry->children) {
766746d4a02STakashi Iwai 		snd_info_disconnect(list_entry(p, struct snd_info_entry, list));
767746d4a02STakashi Iwai 	}
768746d4a02STakashi Iwai 
769746d4a02STakashi Iwai 	if (! entry->p)
770746d4a02STakashi Iwai 		return;
771746d4a02STakashi Iwai 	list_del_init(&entry->list);
772a8ca16eaSDavid Howells 	proc_remove(entry->p);
773746d4a02STakashi Iwai 	entry->p = NULL;
774746d4a02STakashi Iwai }
775746d4a02STakashi Iwai 
77624c1f931STakashi Iwai static int snd_info_dev_free_entry(struct snd_device *device)
7771da177e4SLinus Torvalds {
77824c1f931STakashi Iwai 	struct snd_info_entry *entry = device->device_data;
7791da177e4SLinus Torvalds 	snd_info_free_entry(entry);
7801da177e4SLinus Torvalds 	return 0;
7811da177e4SLinus Torvalds }
7821da177e4SLinus Torvalds 
78324c1f931STakashi Iwai static int snd_info_dev_register_entry(struct snd_device *device)
7841da177e4SLinus Torvalds {
78524c1f931STakashi Iwai 	struct snd_info_entry *entry = device->device_data;
7861da177e4SLinus Torvalds 	return snd_info_register(entry);
7871da177e4SLinus Torvalds }
7881da177e4SLinus Torvalds 
7891da177e4SLinus Torvalds /**
7901da177e4SLinus Torvalds  * snd_card_proc_new - create an info entry for the given card
7911da177e4SLinus Torvalds  * @card: the card instance
7921da177e4SLinus Torvalds  * @name: the file name
7931da177e4SLinus Torvalds  * @entryp: the pointer to store the new info entry
7941da177e4SLinus Torvalds  *
7951da177e4SLinus Torvalds  * Creates a new info entry and assigns it to the given card.
7961da177e4SLinus Torvalds  * Unlike snd_info_create_card_entry(), this function registers the
7971da177e4SLinus Torvalds  * info entry as an ALSA device component, so that it can be
7981da177e4SLinus Torvalds  * unregistered/released without explicit call.
7991da177e4SLinus Torvalds  * Also, you don't have to register this entry via snd_info_register(),
8001da177e4SLinus Torvalds  * since this will be registered by snd_card_register() automatically.
8011da177e4SLinus Torvalds  *
8021da177e4SLinus Torvalds  * The parent is assumed as card->proc_root.
8031da177e4SLinus Torvalds  *
8041da177e4SLinus Torvalds  * For releasing this entry, use snd_device_free() instead of
8051da177e4SLinus Torvalds  * snd_info_free_entry().
8061da177e4SLinus Torvalds  *
807eb7c06e8SYacine Belkadi  * Return: Zero if successful, or a negative error code on failure.
8081da177e4SLinus Torvalds  */
80924c1f931STakashi Iwai int snd_card_proc_new(struct snd_card *card, const char *name,
81024c1f931STakashi Iwai 		      struct snd_info_entry **entryp)
8111da177e4SLinus Torvalds {
81224c1f931STakashi Iwai 	static struct snd_device_ops ops = {
8131da177e4SLinus Torvalds 		.dev_free = snd_info_dev_free_entry,
8141da177e4SLinus Torvalds 		.dev_register =	snd_info_dev_register_entry,
815746d4a02STakashi Iwai 		/* disconnect is done via snd_info_card_disconnect() */
8161da177e4SLinus Torvalds 	};
81724c1f931STakashi Iwai 	struct snd_info_entry *entry;
8181da177e4SLinus Torvalds 	int err;
8191da177e4SLinus Torvalds 
8201da177e4SLinus Torvalds 	entry = snd_info_create_card_entry(card, name, card->proc_root);
8211da177e4SLinus Torvalds 	if (! entry)
8221da177e4SLinus Torvalds 		return -ENOMEM;
8231da177e4SLinus Torvalds 	if ((err = snd_device_new(card, SNDRV_DEV_INFO, entry, &ops)) < 0) {
8241da177e4SLinus Torvalds 		snd_info_free_entry(entry);
8251da177e4SLinus Torvalds 		return err;
8261da177e4SLinus Torvalds 	}
8271da177e4SLinus Torvalds 	if (entryp)
8281da177e4SLinus Torvalds 		*entryp = entry;
8291da177e4SLinus Torvalds 	return 0;
8301da177e4SLinus Torvalds }
8311da177e4SLinus Torvalds 
832c0d3fb39STakashi Iwai EXPORT_SYMBOL(snd_card_proc_new);
833c0d3fb39STakashi Iwai 
8341da177e4SLinus Torvalds /**
8351da177e4SLinus Torvalds  * snd_info_free_entry - release the info entry
8361da177e4SLinus Torvalds  * @entry: the info entry
8371da177e4SLinus Torvalds  *
8381da177e4SLinus Torvalds  * Releases the info entry.  Don't call this after registered.
8391da177e4SLinus Torvalds  */
84024c1f931STakashi Iwai void snd_info_free_entry(struct snd_info_entry * entry)
8411da177e4SLinus Torvalds {
8421da177e4SLinus Torvalds 	if (entry == NULL)
8431da177e4SLinus Torvalds 		return;
844746d4a02STakashi Iwai 	if (entry->p) {
845746d4a02STakashi Iwai 		mutex_lock(&info_mutex);
846746d4a02STakashi Iwai 		snd_info_disconnect(entry);
847746d4a02STakashi Iwai 		mutex_unlock(&info_mutex);
848746d4a02STakashi Iwai 	}
8491da177e4SLinus Torvalds 	kfree(entry->name);
8501da177e4SLinus Torvalds 	if (entry->private_free)
8511da177e4SLinus Torvalds 		entry->private_free(entry);
8521da177e4SLinus Torvalds 	kfree(entry);
8531da177e4SLinus Torvalds }
8541da177e4SLinus Torvalds 
855c0d3fb39STakashi Iwai EXPORT_SYMBOL(snd_info_free_entry);
856c0d3fb39STakashi Iwai 
8571da177e4SLinus Torvalds /**
8581da177e4SLinus Torvalds  * snd_info_register - register the info entry
8591da177e4SLinus Torvalds  * @entry: the info entry
8601da177e4SLinus Torvalds  *
8611da177e4SLinus Torvalds  * Registers the proc info entry.
8621da177e4SLinus Torvalds  *
863eb7c06e8SYacine Belkadi  * Return: Zero if successful, or a negative error code on failure.
8641da177e4SLinus Torvalds  */
86524c1f931STakashi Iwai int snd_info_register(struct snd_info_entry * entry)
8661da177e4SLinus Torvalds {
8671da177e4SLinus Torvalds 	struct proc_dir_entry *root, *p = NULL;
8681da177e4SLinus Torvalds 
8697eaa943cSTakashi Iwai 	if (snd_BUG_ON(!entry))
8707eaa943cSTakashi Iwai 		return -ENXIO;
8711da177e4SLinus Torvalds 	root = entry->parent == NULL ? snd_proc_root : entry->parent->p;
8721a60d4c5SIngo Molnar 	mutex_lock(&info_mutex);
873aee0c612SAl Viro 	if (S_ISDIR(entry->mode)) {
874aee0c612SAl Viro 		p = proc_mkdir_mode(entry->name, entry->mode, root);
8751da177e4SLinus Torvalds 		if (!p) {
8761a60d4c5SIngo Molnar 			mutex_unlock(&info_mutex);
8771da177e4SLinus Torvalds 			return -ENOMEM;
8781da177e4SLinus Torvalds 		}
879aee0c612SAl Viro 	} else {
8804adb7bcbSTakashi Iwai 		const struct file_operations *ops;
8814adb7bcbSTakashi Iwai 		if (entry->content == SNDRV_INFO_CONTENT_DATA)
8824adb7bcbSTakashi Iwai 			ops = &snd_info_entry_operations;
8834adb7bcbSTakashi Iwai 		else
8844adb7bcbSTakashi Iwai 			ops = &snd_info_text_entry_ops;
885aee0c612SAl Viro 		p = proc_create_data(entry->name, entry->mode, root,
8864adb7bcbSTakashi Iwai 				     ops, entry);
887aee0c612SAl Viro 		if (!p) {
888aee0c612SAl Viro 			mutex_unlock(&info_mutex);
889aee0c612SAl Viro 			return -ENOMEM;
890aee0c612SAl Viro 		}
891271a15eaSDavid Howells 		proc_set_size(p, entry->size);
892aee0c612SAl Viro 	}
8931da177e4SLinus Torvalds 	entry->p = p;
894746d4a02STakashi Iwai 	if (entry->parent)
895746d4a02STakashi Iwai 		list_add_tail(&entry->list, &entry->parent->children);
8961a60d4c5SIngo Molnar 	mutex_unlock(&info_mutex);
8971da177e4SLinus Torvalds 	return 0;
8981da177e4SLinus Torvalds }
8991da177e4SLinus Torvalds 
900c0d3fb39STakashi Iwai EXPORT_SYMBOL(snd_info_register);
901c0d3fb39STakashi Iwai 
9021da177e4SLinus Torvalds /*
9031da177e4SLinus Torvalds 
9041da177e4SLinus Torvalds  */
9051da177e4SLinus Torvalds 
9066581f4e7STakashi Iwai static struct snd_info_entry *snd_info_version_entry;
9071da177e4SLinus Torvalds 
90824c1f931STakashi Iwai static void snd_info_version_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
9091da177e4SLinus Torvalds {
9101da177e4SLinus Torvalds 	snd_iprintf(buffer,
91142662748SJaroslav Kysela 		    "Advanced Linux Sound Architecture Driver Version k%s.\n",
91242662748SJaroslav Kysela 		    init_utsname()->release);
9131da177e4SLinus Torvalds }
9141da177e4SLinus Torvalds 
9151da177e4SLinus Torvalds static int __init snd_info_version_init(void)
9161da177e4SLinus Torvalds {
91724c1f931STakashi Iwai 	struct snd_info_entry *entry;
9181da177e4SLinus Torvalds 
9191da177e4SLinus Torvalds 	entry = snd_info_create_module_entry(THIS_MODULE, "version", NULL);
9201da177e4SLinus Torvalds 	if (entry == NULL)
9211da177e4SLinus Torvalds 		return -ENOMEM;
9221da177e4SLinus Torvalds 	entry->c.text.read = snd_info_version_read;
9231da177e4SLinus Torvalds 	if (snd_info_register(entry) < 0) {
9241da177e4SLinus Torvalds 		snd_info_free_entry(entry);
9251da177e4SLinus Torvalds 		return -ENOMEM;
9261da177e4SLinus Torvalds 	}
9271da177e4SLinus Torvalds 	snd_info_version_entry = entry;
9281da177e4SLinus Torvalds 	return 0;
9291da177e4SLinus Torvalds }
9301da177e4SLinus Torvalds 
9311da177e4SLinus Torvalds static int __exit snd_info_version_done(void)
9321da177e4SLinus Torvalds {
933746d4a02STakashi Iwai 	snd_info_free_entry(snd_info_version_entry);
9341da177e4SLinus Torvalds 	return 0;
9351da177e4SLinus Torvalds }
9361da177e4SLinus Torvalds 
9371da177e4SLinus Torvalds #endif /* CONFIG_PROC_FS */
938