xref: /openbmc/linux/sound/core/info.c (revision 359745d7)
11a59d1b8SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
21da177e4SLinus Torvalds /*
31da177e4SLinus Torvalds  *  Information interface for ALSA driver
4c1017a4cSJaroslav Kysela  *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
51da177e4SLinus Torvalds  */
61da177e4SLinus Torvalds 
71da177e4SLinus Torvalds #include <linux/init.h>
81da177e4SLinus Torvalds #include <linux/time.h>
927ac792cSAndrea Righi #include <linux/mm.h>
105a0e3ad6STejun Heo #include <linux/slab.h>
11543537bdSPaulo Marques #include <linux/string.h>
12da155d5bSPaul Gortmaker #include <linux/module.h>
131da177e4SLinus Torvalds #include <sound/core.h>
141da177e4SLinus Torvalds #include <sound/minors.h>
151da177e4SLinus Torvalds #include <sound/info.h>
1642662748SJaroslav Kysela #include <linux/utsname.h>
171da177e4SLinus Torvalds #include <linux/proc_fs.h>
181a60d4c5SIngo Molnar #include <linux/mutex.h>
191da177e4SLinus Torvalds 
201da177e4SLinus Torvalds int snd_info_check_reserved_words(const char *str)
211da177e4SLinus Torvalds {
2251d7847aSTakashi Iwai 	static const char * const reserved[] =
231da177e4SLinus Torvalds 	{
241da177e4SLinus Torvalds 		"version",
251da177e4SLinus Torvalds 		"meminfo",
261da177e4SLinus Torvalds 		"memdebug",
271da177e4SLinus Torvalds 		"detect",
281da177e4SLinus Torvalds 		"devices",
291da177e4SLinus Torvalds 		"oss",
301da177e4SLinus Torvalds 		"cards",
311da177e4SLinus Torvalds 		"timers",
321da177e4SLinus Torvalds 		"synth",
331da177e4SLinus Torvalds 		"pcm",
341da177e4SLinus Torvalds 		"seq",
351da177e4SLinus Torvalds 		NULL
361da177e4SLinus Torvalds 	};
3751d7847aSTakashi Iwai 	const char * const *xstr = reserved;
381da177e4SLinus Torvalds 
391da177e4SLinus Torvalds 	while (*xstr) {
401da177e4SLinus Torvalds 		if (!strcmp(*xstr, str))
411da177e4SLinus Torvalds 			return 0;
421da177e4SLinus Torvalds 		xstr++;
431da177e4SLinus Torvalds 	}
441da177e4SLinus Torvalds 	if (!strncmp(str, "card", 4))
451da177e4SLinus Torvalds 		return 0;
461da177e4SLinus Torvalds 	return 1;
471da177e4SLinus Torvalds }
481da177e4SLinus Torvalds 
491a60d4c5SIngo Molnar static DEFINE_MUTEX(info_mutex);
501da177e4SLinus Torvalds 
5124c1f931STakashi Iwai struct snd_info_private_data {
5224c1f931STakashi Iwai 	struct snd_info_buffer *rbuffer;
5324c1f931STakashi Iwai 	struct snd_info_buffer *wbuffer;
5424c1f931STakashi Iwai 	struct snd_info_entry *entry;
551da177e4SLinus Torvalds 	void *file_private_data;
5624c1f931STakashi Iwai };
571da177e4SLinus Torvalds 
581da177e4SLinus Torvalds static int snd_info_version_init(void);
59746d4a02STakashi Iwai static void snd_info_disconnect(struct snd_info_entry *entry);
601da177e4SLinus Torvalds 
611da177e4SLinus Torvalds /*
621da177e4SLinus Torvalds 
631da177e4SLinus Torvalds  */
641da177e4SLinus Torvalds 
65644dbd64STakashi Iwai static struct snd_info_entry *snd_proc_root;
666581f4e7STakashi Iwai struct snd_info_entry *snd_seq_root;
67c0d3fb39STakashi Iwai EXPORT_SYMBOL(snd_seq_root);
68c0d3fb39STakashi Iwai 
691da177e4SLinus Torvalds #ifdef CONFIG_SND_OSSEMUL
706581f4e7STakashi Iwai struct snd_info_entry *snd_oss_root;
711da177e4SLinus Torvalds #endif
721da177e4SLinus Torvalds 
734adb7bcbSTakashi Iwai static int alloc_info_private(struct snd_info_entry *entry,
744adb7bcbSTakashi Iwai 			      struct snd_info_private_data **ret)
754adb7bcbSTakashi Iwai {
764adb7bcbSTakashi Iwai 	struct snd_info_private_data *data;
774adb7bcbSTakashi Iwai 
784adb7bcbSTakashi Iwai 	if (!entry || !entry->p)
794adb7bcbSTakashi Iwai 		return -ENODEV;
804adb7bcbSTakashi Iwai 	if (!try_module_get(entry->module))
814adb7bcbSTakashi Iwai 		return -EFAULT;
824adb7bcbSTakashi Iwai 	data = kzalloc(sizeof(*data), GFP_KERNEL);
834adb7bcbSTakashi Iwai 	if (!data) {
844adb7bcbSTakashi Iwai 		module_put(entry->module);
854adb7bcbSTakashi Iwai 		return -ENOMEM;
864adb7bcbSTakashi Iwai 	}
874adb7bcbSTakashi Iwai 	data->entry = entry;
884adb7bcbSTakashi Iwai 	*ret = data;
894adb7bcbSTakashi Iwai 	return 0;
904adb7bcbSTakashi Iwai }
914adb7bcbSTakashi Iwai 
924adb7bcbSTakashi Iwai static bool valid_pos(loff_t pos, size_t count)
934adb7bcbSTakashi Iwai {
944adb7bcbSTakashi Iwai 	if (pos < 0 || (long) pos != pos || (ssize_t) count < 0)
954adb7bcbSTakashi Iwai 		return false;
964adb7bcbSTakashi Iwai 	if ((unsigned long) pos + (unsigned long) count < (unsigned long) pos)
974adb7bcbSTakashi Iwai 		return false;
984adb7bcbSTakashi Iwai 	return true;
994adb7bcbSTakashi Iwai }
1004adb7bcbSTakashi Iwai 
1014adb7bcbSTakashi Iwai /*
1024adb7bcbSTakashi Iwai  * file ops for binary proc files
1034adb7bcbSTakashi Iwai  */
1041da177e4SLinus Torvalds static loff_t snd_info_entry_llseek(struct file *file, loff_t offset, int orig)
1051da177e4SLinus Torvalds {
10624c1f931STakashi Iwai 	struct snd_info_private_data *data;
1071da177e4SLinus Torvalds 	struct snd_info_entry *entry;
10873029e0fSTakashi Iwai 	loff_t ret = -EINVAL, size;
1091da177e4SLinus Torvalds 
1101da177e4SLinus Torvalds 	data = file->private_data;
1111da177e4SLinus Torvalds 	entry = data->entry;
1125b5cd553STakashi Iwai 	mutex_lock(&entry->access);
1134adb7bcbSTakashi Iwai 	if (entry->c.ops->llseek) {
11473029e0fSTakashi Iwai 		offset = entry->c.ops->llseek(entry,
1151da177e4SLinus Torvalds 					      data->file_private_data,
1161da177e4SLinus Torvalds 					      file, offset, orig);
1171da177e4SLinus Torvalds 		goto out;
1181da177e4SLinus Torvalds 	}
1194adb7bcbSTakashi Iwai 
12073029e0fSTakashi Iwai 	size = entry->size;
12173029e0fSTakashi Iwai 	switch (orig) {
12273029e0fSTakashi Iwai 	case SEEK_SET:
1231da177e4SLinus Torvalds 		break;
12473029e0fSTakashi Iwai 	case SEEK_CUR:
12573029e0fSTakashi Iwai 		offset += file->f_pos;
12673029e0fSTakashi Iwai 		break;
12773029e0fSTakashi Iwai 	case SEEK_END:
12873029e0fSTakashi Iwai 		if (!size)
12973029e0fSTakashi Iwai 			goto out;
13073029e0fSTakashi Iwai 		offset += size;
13173029e0fSTakashi Iwai 		break;
13273029e0fSTakashi Iwai 	default:
13373029e0fSTakashi Iwai 		goto out;
1341da177e4SLinus Torvalds 	}
13573029e0fSTakashi Iwai 	if (offset < 0)
13673029e0fSTakashi Iwai 		goto out;
13773029e0fSTakashi Iwai 	if (size && offset > size)
13873029e0fSTakashi Iwai 		offset = size;
13973029e0fSTakashi Iwai 	file->f_pos = offset;
14073029e0fSTakashi Iwai 	ret = offset;
1411da177e4SLinus Torvalds  out:
1425b5cd553STakashi Iwai 	mutex_unlock(&entry->access);
1431da177e4SLinus Torvalds 	return ret;
1441da177e4SLinus Torvalds }
1451da177e4SLinus Torvalds 
1461da177e4SLinus Torvalds static ssize_t snd_info_entry_read(struct file *file, char __user *buffer,
1471da177e4SLinus Torvalds 				   size_t count, loff_t * offset)
1481da177e4SLinus Torvalds {
1494adb7bcbSTakashi Iwai 	struct snd_info_private_data *data = file->private_data;
1504adb7bcbSTakashi Iwai 	struct snd_info_entry *entry = data->entry;
1514adb7bcbSTakashi Iwai 	size_t size;
1521da177e4SLinus Torvalds 	loff_t pos;
1531da177e4SLinus Torvalds 
1541da177e4SLinus Torvalds 	pos = *offset;
1554adb7bcbSTakashi Iwai 	if (!valid_pos(pos, count))
1561da177e4SLinus Torvalds 		return -EIO;
157d97e1b78STakashi Iwai 	if (pos >= entry->size)
158d97e1b78STakashi Iwai 		return 0;
159d97e1b78STakashi Iwai 	size = entry->size - pos;
160d97e1b78STakashi Iwai 	size = min(count, size);
1614adb7bcbSTakashi Iwai 	size = entry->c.ops->read(entry, data->file_private_data,
162d97e1b78STakashi Iwai 				  file, buffer, size, pos);
1631da177e4SLinus Torvalds 	if ((ssize_t) size > 0)
1641da177e4SLinus Torvalds 		*offset = pos + size;
1651da177e4SLinus Torvalds 	return size;
1661da177e4SLinus Torvalds }
1671da177e4SLinus Torvalds 
1681da177e4SLinus Torvalds static ssize_t snd_info_entry_write(struct file *file, const char __user *buffer,
1691da177e4SLinus Torvalds 				    size_t count, loff_t * offset)
1701da177e4SLinus Torvalds {
1714adb7bcbSTakashi Iwai 	struct snd_info_private_data *data = file->private_data;
1724adb7bcbSTakashi Iwai 	struct snd_info_entry *entry = data->entry;
1737e4eeec8STakashi Iwai 	ssize_t size = 0;
1741da177e4SLinus Torvalds 	loff_t pos;
1751da177e4SLinus Torvalds 
1761da177e4SLinus Torvalds 	pos = *offset;
1774adb7bcbSTakashi Iwai 	if (!valid_pos(pos, count))
1781da177e4SLinus Torvalds 		return -EIO;
1794adb7bcbSTakashi Iwai 	if (count > 0) {
180d97e1b78STakashi Iwai 		size_t maxsize = entry->size - pos;
181d97e1b78STakashi Iwai 		count = min(count, maxsize);
1824adb7bcbSTakashi Iwai 		size = entry->c.ops->write(entry, data->file_private_data,
1831da177e4SLinus Torvalds 					   file, buffer, count, pos);
184d97e1b78STakashi Iwai 	}
1854adb7bcbSTakashi Iwai 	if (size > 0)
1861da177e4SLinus Torvalds 		*offset = pos + size;
1871da177e4SLinus Torvalds 	return size;
1881da177e4SLinus Torvalds }
1891da177e4SLinus Torvalds 
190680ef72aSAl Viro static __poll_t snd_info_entry_poll(struct file *file, poll_table *wait)
1911da177e4SLinus Torvalds {
1924adb7bcbSTakashi Iwai 	struct snd_info_private_data *data = file->private_data;
1934adb7bcbSTakashi Iwai 	struct snd_info_entry *entry = data->entry;
194680ef72aSAl Viro 	__poll_t mask = 0;
1951da177e4SLinus Torvalds 
1961da177e4SLinus Torvalds 	if (entry->c.ops->poll)
1971da177e4SLinus Torvalds 		return entry->c.ops->poll(entry,
1981da177e4SLinus Torvalds 					  data->file_private_data,
1991da177e4SLinus Torvalds 					  file, wait);
2001da177e4SLinus Torvalds 	if (entry->c.ops->read)
201a9a08845SLinus Torvalds 		mask |= EPOLLIN | EPOLLRDNORM;
2021da177e4SLinus Torvalds 	if (entry->c.ops->write)
203a9a08845SLinus Torvalds 		mask |= EPOLLOUT | EPOLLWRNORM;
2041da177e4SLinus Torvalds 	return mask;
2051da177e4SLinus Torvalds }
2061da177e4SLinus Torvalds 
207d99e9889SIngo Molnar static long snd_info_entry_ioctl(struct file *file, unsigned int cmd,
208d99e9889SIngo Molnar 				unsigned long arg)
2091da177e4SLinus Torvalds {
2104adb7bcbSTakashi Iwai 	struct snd_info_private_data *data = file->private_data;
2114adb7bcbSTakashi Iwai 	struct snd_info_entry *entry = data->entry;
2121da177e4SLinus Torvalds 
2134adb7bcbSTakashi Iwai 	if (!entry->c.ops->ioctl)
2141da177e4SLinus Torvalds 		return -ENOTTY;
2154adb7bcbSTakashi Iwai 	return entry->c.ops->ioctl(entry, data->file_private_data,
2164adb7bcbSTakashi Iwai 				   file, cmd, arg);
2171da177e4SLinus Torvalds }
2181da177e4SLinus Torvalds 
2191da177e4SLinus Torvalds static int snd_info_entry_mmap(struct file *file, struct vm_area_struct *vma)
2201da177e4SLinus Torvalds {
221496ad9aaSAl Viro 	struct inode *inode = file_inode(file);
22224c1f931STakashi Iwai 	struct snd_info_private_data *data;
2231da177e4SLinus Torvalds 	struct snd_info_entry *entry;
2241da177e4SLinus Torvalds 
2251da177e4SLinus Torvalds 	data = file->private_data;
2261da177e4SLinus Torvalds 	if (data == NULL)
2271da177e4SLinus Torvalds 		return 0;
2281da177e4SLinus Torvalds 	entry = data->entry;
2294adb7bcbSTakashi Iwai 	if (!entry->c.ops->mmap)
2301da177e4SLinus Torvalds 		return -ENXIO;
2314adb7bcbSTakashi Iwai 	return entry->c.ops->mmap(entry, data->file_private_data,
2324adb7bcbSTakashi Iwai 				  inode, file, vma);
2334adb7bcbSTakashi Iwai }
2344adb7bcbSTakashi Iwai 
2354adb7bcbSTakashi Iwai static int snd_info_entry_open(struct inode *inode, struct file *file)
2364adb7bcbSTakashi Iwai {
237*359745d7SMuchun Song 	struct snd_info_entry *entry = pde_data(inode);
2384adb7bcbSTakashi Iwai 	struct snd_info_private_data *data;
2394adb7bcbSTakashi Iwai 	int mode, err;
2404adb7bcbSTakashi Iwai 
2414adb7bcbSTakashi Iwai 	mutex_lock(&info_mutex);
2424adb7bcbSTakashi Iwai 	err = alloc_info_private(entry, &data);
2434adb7bcbSTakashi Iwai 	if (err < 0)
2444adb7bcbSTakashi Iwai 		goto unlock;
2454adb7bcbSTakashi Iwai 
2464adb7bcbSTakashi Iwai 	mode = file->f_flags & O_ACCMODE;
2474adb7bcbSTakashi Iwai 	if (((mode == O_RDONLY || mode == O_RDWR) && !entry->c.ops->read) ||
2484adb7bcbSTakashi Iwai 	    ((mode == O_WRONLY || mode == O_RDWR) && !entry->c.ops->write)) {
2494adb7bcbSTakashi Iwai 		err = -ENODEV;
2504adb7bcbSTakashi Iwai 		goto error;
2514adb7bcbSTakashi Iwai 	}
2524adb7bcbSTakashi Iwai 
2534adb7bcbSTakashi Iwai 	if (entry->c.ops->open) {
2544adb7bcbSTakashi Iwai 		err = entry->c.ops->open(entry, mode, &data->file_private_data);
2554adb7bcbSTakashi Iwai 		if (err < 0)
2564adb7bcbSTakashi Iwai 			goto error;
2574adb7bcbSTakashi Iwai 	}
2584adb7bcbSTakashi Iwai 
2594adb7bcbSTakashi Iwai 	file->private_data = data;
2604adb7bcbSTakashi Iwai 	mutex_unlock(&info_mutex);
2614adb7bcbSTakashi Iwai 	return 0;
2624adb7bcbSTakashi Iwai 
2634adb7bcbSTakashi Iwai  error:
2644adb7bcbSTakashi Iwai 	kfree(data);
2654adb7bcbSTakashi Iwai 	module_put(entry->module);
2664adb7bcbSTakashi Iwai  unlock:
2674adb7bcbSTakashi Iwai 	mutex_unlock(&info_mutex);
2684adb7bcbSTakashi Iwai 	return err;
2694adb7bcbSTakashi Iwai }
2704adb7bcbSTakashi Iwai 
2714adb7bcbSTakashi Iwai static int snd_info_entry_release(struct inode *inode, struct file *file)
2724adb7bcbSTakashi Iwai {
2734adb7bcbSTakashi Iwai 	struct snd_info_private_data *data = file->private_data;
2744adb7bcbSTakashi Iwai 	struct snd_info_entry *entry = data->entry;
2754adb7bcbSTakashi Iwai 
2764adb7bcbSTakashi Iwai 	if (entry->c.ops->release)
2774adb7bcbSTakashi Iwai 		entry->c.ops->release(entry, file->f_flags & O_ACCMODE,
2784adb7bcbSTakashi Iwai 				      data->file_private_data);
2794adb7bcbSTakashi Iwai 	module_put(entry->module);
2804adb7bcbSTakashi Iwai 	kfree(data);
2814adb7bcbSTakashi Iwai 	return 0;
2821da177e4SLinus Torvalds }
2831da177e4SLinus Torvalds 
28497a32539SAlexey Dobriyan static const struct proc_ops snd_info_entry_operations =
2851da177e4SLinus Torvalds {
28697a32539SAlexey Dobriyan 	.proc_lseek	= snd_info_entry_llseek,
28797a32539SAlexey Dobriyan 	.proc_read	= snd_info_entry_read,
28897a32539SAlexey Dobriyan 	.proc_write	= snd_info_entry_write,
28997a32539SAlexey Dobriyan 	.proc_poll	= snd_info_entry_poll,
29097a32539SAlexey Dobriyan 	.proc_ioctl	= snd_info_entry_ioctl,
29197a32539SAlexey Dobriyan 	.proc_mmap	= snd_info_entry_mmap,
29297a32539SAlexey Dobriyan 	.proc_open	= snd_info_entry_open,
29397a32539SAlexey Dobriyan 	.proc_release	= snd_info_entry_release,
2941da177e4SLinus Torvalds };
2951da177e4SLinus Torvalds 
2964adb7bcbSTakashi Iwai /*
2974adb7bcbSTakashi Iwai  * file ops for text proc files
2984adb7bcbSTakashi Iwai  */
2994adb7bcbSTakashi Iwai static ssize_t snd_info_text_entry_write(struct file *file,
3004adb7bcbSTakashi Iwai 					 const char __user *buffer,
3014adb7bcbSTakashi Iwai 					 size_t count, loff_t *offset)
3024adb7bcbSTakashi Iwai {
3034adb7bcbSTakashi Iwai 	struct seq_file *m = file->private_data;
3044adb7bcbSTakashi Iwai 	struct snd_info_private_data *data = m->private;
3054adb7bcbSTakashi Iwai 	struct snd_info_entry *entry = data->entry;
3064adb7bcbSTakashi Iwai 	struct snd_info_buffer *buf;
3074adb7bcbSTakashi Iwai 	loff_t pos;
3084adb7bcbSTakashi Iwai 	size_t next;
3094adb7bcbSTakashi Iwai 	int err = 0;
3104adb7bcbSTakashi Iwai 
3116809cd68STakashi Iwai 	if (!entry->c.text.write)
3126809cd68STakashi Iwai 		return -EIO;
3134adb7bcbSTakashi Iwai 	pos = *offset;
3144adb7bcbSTakashi Iwai 	if (!valid_pos(pos, count))
3154adb7bcbSTakashi Iwai 		return -EIO;
3164adb7bcbSTakashi Iwai 	next = pos + count;
317027a9fe6STakashi Iwai 	/* don't handle too large text inputs */
318027a9fe6STakashi Iwai 	if (next > 16 * 1024)
319027a9fe6STakashi Iwai 		return -EIO;
3204adb7bcbSTakashi Iwai 	mutex_lock(&entry->access);
3214adb7bcbSTakashi Iwai 	buf = data->wbuffer;
3224adb7bcbSTakashi Iwai 	if (!buf) {
3234adb7bcbSTakashi Iwai 		data->wbuffer = buf = kzalloc(sizeof(*buf), GFP_KERNEL);
3244adb7bcbSTakashi Iwai 		if (!buf) {
3254adb7bcbSTakashi Iwai 			err = -ENOMEM;
3264adb7bcbSTakashi Iwai 			goto error;
3274adb7bcbSTakashi Iwai 		}
3284adb7bcbSTakashi Iwai 	}
3294adb7bcbSTakashi Iwai 	if (next > buf->len) {
330ffb73b08STakashi Iwai 		char *nbuf = kvzalloc(PAGE_ALIGN(next), GFP_KERNEL);
3314adb7bcbSTakashi Iwai 		if (!nbuf) {
3324adb7bcbSTakashi Iwai 			err = -ENOMEM;
3334adb7bcbSTakashi Iwai 			goto error;
3344adb7bcbSTakashi Iwai 		}
335ffb73b08STakashi Iwai 		kvfree(buf->buffer);
3364adb7bcbSTakashi Iwai 		buf->buffer = nbuf;
3374adb7bcbSTakashi Iwai 		buf->len = PAGE_ALIGN(next);
3384adb7bcbSTakashi Iwai 	}
3394adb7bcbSTakashi Iwai 	if (copy_from_user(buf->buffer + pos, buffer, count)) {
3404adb7bcbSTakashi Iwai 		err = -EFAULT;
3414adb7bcbSTakashi Iwai 		goto error;
3424adb7bcbSTakashi Iwai 	}
3434adb7bcbSTakashi Iwai 	buf->size = next;
3444adb7bcbSTakashi Iwai  error:
3454adb7bcbSTakashi Iwai 	mutex_unlock(&entry->access);
3464adb7bcbSTakashi Iwai 	if (err < 0)
3474adb7bcbSTakashi Iwai 		return err;
3484adb7bcbSTakashi Iwai 	*offset = next;
3494adb7bcbSTakashi Iwai 	return count;
3504adb7bcbSTakashi Iwai }
3514adb7bcbSTakashi Iwai 
3524adb7bcbSTakashi Iwai static int snd_info_seq_show(struct seq_file *seq, void *p)
3534adb7bcbSTakashi Iwai {
3544adb7bcbSTakashi Iwai 	struct snd_info_private_data *data = seq->private;
3554adb7bcbSTakashi Iwai 	struct snd_info_entry *entry = data->entry;
3564adb7bcbSTakashi Iwai 
3576809cd68STakashi Iwai 	if (!entry->c.text.read) {
3586809cd68STakashi Iwai 		return -EIO;
3596809cd68STakashi Iwai 	} else {
3604adb7bcbSTakashi Iwai 		data->rbuffer->buffer = (char *)seq; /* XXX hack! */
3614adb7bcbSTakashi Iwai 		entry->c.text.read(entry, data->rbuffer);
3624adb7bcbSTakashi Iwai 	}
3634adb7bcbSTakashi Iwai 	return 0;
3644adb7bcbSTakashi Iwai }
3654adb7bcbSTakashi Iwai 
3664adb7bcbSTakashi Iwai static int snd_info_text_entry_open(struct inode *inode, struct file *file)
3674adb7bcbSTakashi Iwai {
368*359745d7SMuchun Song 	struct snd_info_entry *entry = pde_data(inode);
3694adb7bcbSTakashi Iwai 	struct snd_info_private_data *data;
3704adb7bcbSTakashi Iwai 	int err;
3714adb7bcbSTakashi Iwai 
3724adb7bcbSTakashi Iwai 	mutex_lock(&info_mutex);
3734adb7bcbSTakashi Iwai 	err = alloc_info_private(entry, &data);
3744adb7bcbSTakashi Iwai 	if (err < 0)
3754adb7bcbSTakashi Iwai 		goto unlock;
3764adb7bcbSTakashi Iwai 
3774adb7bcbSTakashi Iwai 	data->rbuffer = kzalloc(sizeof(*data->rbuffer), GFP_KERNEL);
3784adb7bcbSTakashi Iwai 	if (!data->rbuffer) {
3794adb7bcbSTakashi Iwai 		err = -ENOMEM;
3804adb7bcbSTakashi Iwai 		goto error;
3814adb7bcbSTakashi Iwai 	}
3824adb7bcbSTakashi Iwai 	if (entry->size)
3834adb7bcbSTakashi Iwai 		err = single_open_size(file, snd_info_seq_show, data,
3844adb7bcbSTakashi Iwai 				       entry->size);
3854adb7bcbSTakashi Iwai 	else
3864adb7bcbSTakashi Iwai 		err = single_open(file, snd_info_seq_show, data);
3874adb7bcbSTakashi Iwai 	if (err < 0)
3884adb7bcbSTakashi Iwai 		goto error;
3894adb7bcbSTakashi Iwai 	mutex_unlock(&info_mutex);
3904adb7bcbSTakashi Iwai 	return 0;
3914adb7bcbSTakashi Iwai 
3924adb7bcbSTakashi Iwai  error:
3934adb7bcbSTakashi Iwai 	kfree(data->rbuffer);
3944adb7bcbSTakashi Iwai 	kfree(data);
3954adb7bcbSTakashi Iwai 	module_put(entry->module);
3964adb7bcbSTakashi Iwai  unlock:
3974adb7bcbSTakashi Iwai 	mutex_unlock(&info_mutex);
3984adb7bcbSTakashi Iwai 	return err;
3994adb7bcbSTakashi Iwai }
4004adb7bcbSTakashi Iwai 
4014adb7bcbSTakashi Iwai static int snd_info_text_entry_release(struct inode *inode, struct file *file)
4024adb7bcbSTakashi Iwai {
4034adb7bcbSTakashi Iwai 	struct seq_file *m = file->private_data;
4044adb7bcbSTakashi Iwai 	struct snd_info_private_data *data = m->private;
4054adb7bcbSTakashi Iwai 	struct snd_info_entry *entry = data->entry;
4064adb7bcbSTakashi Iwai 
4074adb7bcbSTakashi Iwai 	if (data->wbuffer && entry->c.text.write)
4084adb7bcbSTakashi Iwai 		entry->c.text.write(entry, data->wbuffer);
4094adb7bcbSTakashi Iwai 
4104adb7bcbSTakashi Iwai 	single_release(inode, file);
4114adb7bcbSTakashi Iwai 	kfree(data->rbuffer);
4124adb7bcbSTakashi Iwai 	if (data->wbuffer) {
413ffb73b08STakashi Iwai 		kvfree(data->wbuffer->buffer);
4144adb7bcbSTakashi Iwai 		kfree(data->wbuffer);
4154adb7bcbSTakashi Iwai 	}
4164adb7bcbSTakashi Iwai 
4174adb7bcbSTakashi Iwai 	module_put(entry->module);
4184adb7bcbSTakashi Iwai 	kfree(data);
4194adb7bcbSTakashi Iwai 	return 0;
4204adb7bcbSTakashi Iwai }
4214adb7bcbSTakashi Iwai 
42297a32539SAlexey Dobriyan static const struct proc_ops snd_info_text_entry_ops =
4234adb7bcbSTakashi Iwai {
42497a32539SAlexey Dobriyan 	.proc_open	= snd_info_text_entry_open,
42597a32539SAlexey Dobriyan 	.proc_release	= snd_info_text_entry_release,
42697a32539SAlexey Dobriyan 	.proc_write	= snd_info_text_entry_write,
42797a32539SAlexey Dobriyan 	.proc_lseek	= seq_lseek,
42897a32539SAlexey Dobriyan 	.proc_read	= seq_read,
4294adb7bcbSTakashi Iwai };
4304adb7bcbSTakashi Iwai 
431886364f6STakashi Iwai static struct snd_info_entry *create_subdir(struct module *mod,
432886364f6STakashi Iwai 					    const char *name)
433886364f6STakashi Iwai {
434886364f6STakashi Iwai 	struct snd_info_entry *entry;
435886364f6STakashi Iwai 
436886364f6STakashi Iwai 	entry = snd_info_create_module_entry(mod, name, NULL);
437886364f6STakashi Iwai 	if (!entry)
438886364f6STakashi Iwai 		return NULL;
4396a73cf46SJoe Perches 	entry->mode = S_IFDIR | 0555;
440886364f6STakashi Iwai 	if (snd_info_register(entry) < 0) {
441886364f6STakashi Iwai 		snd_info_free_entry(entry);
442886364f6STakashi Iwai 		return NULL;
443886364f6STakashi Iwai 	}
444886364f6STakashi Iwai 	return entry;
445886364f6STakashi Iwai }
446886364f6STakashi Iwai 
4478e7ccb7bSTakashi Iwai static struct snd_info_entry *
448a858ee66STakashi Iwai snd_info_create_entry(const char *name, struct snd_info_entry *parent,
449a858ee66STakashi Iwai 		      struct module *module);
450644dbd64STakashi Iwai 
4511da177e4SLinus Torvalds int __init snd_info_init(void)
4521da177e4SLinus Torvalds {
453a858ee66STakashi Iwai 	snd_proc_root = snd_info_create_entry("asound", NULL, THIS_MODULE);
454644dbd64STakashi Iwai 	if (!snd_proc_root)
4551da177e4SLinus Torvalds 		return -ENOMEM;
4566a73cf46SJoe Perches 	snd_proc_root->mode = S_IFDIR | 0555;
457644dbd64STakashi Iwai 	snd_proc_root->p = proc_mkdir("asound", NULL);
458644dbd64STakashi Iwai 	if (!snd_proc_root->p)
459644dbd64STakashi Iwai 		goto error;
4601da177e4SLinus Torvalds #ifdef CONFIG_SND_OSSEMUL
461886364f6STakashi Iwai 	snd_oss_root = create_subdir(THIS_MODULE, "oss");
462886364f6STakashi Iwai 	if (!snd_oss_root)
463886364f6STakashi Iwai 		goto error;
4641da177e4SLinus Torvalds #endif
4658eeaa2f9STakashi Iwai #if IS_ENABLED(CONFIG_SND_SEQUENCER)
466886364f6STakashi Iwai 	snd_seq_root = create_subdir(THIS_MODULE, "seq");
467886364f6STakashi Iwai 	if (!snd_seq_root)
468886364f6STakashi Iwai 		goto error;
4691da177e4SLinus Torvalds #endif
470b591b6e9STakashi Iwai 	if (snd_info_version_init() < 0 ||
471b591b6e9STakashi Iwai 	    snd_minor_info_init() < 0 ||
472b591b6e9STakashi Iwai 	    snd_minor_info_oss_init() < 0 ||
473a0dca822STakashi Iwai 	    snd_card_info_init() < 0 ||
474a0dca822STakashi Iwai 	    snd_info_minor_register() < 0)
475b591b6e9STakashi Iwai 		goto error;
4761da177e4SLinus Torvalds 	return 0;
477886364f6STakashi Iwai 
478886364f6STakashi Iwai  error:
479644dbd64STakashi Iwai 	snd_info_free_entry(snd_proc_root);
480886364f6STakashi Iwai 	return -ENOMEM;
4811da177e4SLinus Torvalds }
4821da177e4SLinus Torvalds 
4831da177e4SLinus Torvalds int __exit snd_info_done(void)
4841da177e4SLinus Torvalds {
485644dbd64STakashi Iwai 	snd_info_free_entry(snd_proc_root);
4861da177e4SLinus Torvalds 	return 0;
4871da177e4SLinus Torvalds }
4881da177e4SLinus Torvalds 
48929b2625fSTakashi Iwai static void snd_card_id_read(struct snd_info_entry *entry,
49029b2625fSTakashi Iwai 			     struct snd_info_buffer *buffer)
49129b2625fSTakashi Iwai {
49229b2625fSTakashi Iwai 	struct snd_card *card = entry->private_data;
49329b2625fSTakashi Iwai 
49429b2625fSTakashi Iwai 	snd_iprintf(buffer, "%s\n", card->id);
49529b2625fSTakashi Iwai }
49629b2625fSTakashi Iwai 
4971da177e4SLinus Torvalds /*
4981da177e4SLinus Torvalds  * create a card proc file
4991da177e4SLinus Torvalds  * called from init.c
5001da177e4SLinus Torvalds  */
50124c1f931STakashi Iwai int snd_info_card_create(struct snd_card *card)
5021da177e4SLinus Torvalds {
5031da177e4SLinus Torvalds 	char str[8];
50424c1f931STakashi Iwai 	struct snd_info_entry *entry;
5051da177e4SLinus Torvalds 
5067eaa943cSTakashi Iwai 	if (snd_BUG_ON(!card))
5077eaa943cSTakashi Iwai 		return -ENXIO;
5081da177e4SLinus Torvalds 
5091da177e4SLinus Torvalds 	sprintf(str, "card%i", card->number);
510886364f6STakashi Iwai 	entry = create_subdir(card->module, str);
511886364f6STakashi Iwai 	if (!entry)
5121da177e4SLinus Torvalds 		return -ENOMEM;
5131da177e4SLinus Torvalds 	card->proc_root = entry;
51429b2625fSTakashi Iwai 
51529b2625fSTakashi Iwai 	return snd_card_ro_proc_new(card, "id", card, snd_card_id_read);
5161da177e4SLinus Torvalds }
5171da177e4SLinus Torvalds 
5181da177e4SLinus Torvalds /*
5191da177e4SLinus Torvalds  * register the card proc file
5201da177e4SLinus Torvalds  * called from init.c
5212471b6c8STakashi Iwai  * can be called multiple times for reinitialization
5221da177e4SLinus Torvalds  */
52324c1f931STakashi Iwai int snd_info_card_register(struct snd_card *card)
5241da177e4SLinus Torvalds {
5251da177e4SLinus Torvalds 	struct proc_dir_entry *p;
5262471b6c8STakashi Iwai 	int err;
5271da177e4SLinus Torvalds 
5287eaa943cSTakashi Iwai 	if (snd_BUG_ON(!card))
5297eaa943cSTakashi Iwai 		return -ENXIO;
5301da177e4SLinus Torvalds 
531348c5ad5STakashi Iwai 	err = snd_info_register(card->proc_root);
5322471b6c8STakashi Iwai 	if (err < 0)
5332471b6c8STakashi Iwai 		return err;
5342471b6c8STakashi Iwai 
5351da177e4SLinus Torvalds 	if (!strcmp(card->id, card->proc_root->name))
5361da177e4SLinus Torvalds 		return 0;
5371da177e4SLinus Torvalds 
5382471b6c8STakashi Iwai 	if (card->proc_root_link)
5392471b6c8STakashi Iwai 		return 0;
540644dbd64STakashi Iwai 	p = proc_symlink(card->id, snd_proc_root->p, card->proc_root->name);
5412471b6c8STakashi Iwai 	if (!p)
5421da177e4SLinus Torvalds 		return -ENOMEM;
5431da177e4SLinus Torvalds 	card->proc_root_link = p;
5441da177e4SLinus Torvalds 	return 0;
5451da177e4SLinus Torvalds }
5461da177e4SLinus Torvalds 
5471da177e4SLinus Torvalds /*
548c2eb9c4eSJaroslav Kysela  * called on card->id change
549c2eb9c4eSJaroslav Kysela  */
550c2eb9c4eSJaroslav Kysela void snd_info_card_id_change(struct snd_card *card)
551c2eb9c4eSJaroslav Kysela {
552c2eb9c4eSJaroslav Kysela 	mutex_lock(&info_mutex);
553c2eb9c4eSJaroslav Kysela 	if (card->proc_root_link) {
554a8ca16eaSDavid Howells 		proc_remove(card->proc_root_link);
555c2eb9c4eSJaroslav Kysela 		card->proc_root_link = NULL;
556c2eb9c4eSJaroslav Kysela 	}
557c2eb9c4eSJaroslav Kysela 	if (strcmp(card->id, card->proc_root->name))
558c2eb9c4eSJaroslav Kysela 		card->proc_root_link = proc_symlink(card->id,
559644dbd64STakashi Iwai 						    snd_proc_root->p,
560c2eb9c4eSJaroslav Kysela 						    card->proc_root->name);
561c2eb9c4eSJaroslav Kysela 	mutex_unlock(&info_mutex);
562c2eb9c4eSJaroslav Kysela }
563c2eb9c4eSJaroslav Kysela 
564c2eb9c4eSJaroslav Kysela /*
5651da177e4SLinus Torvalds  * de-register the card proc file
5661da177e4SLinus Torvalds  * called from init.c
5671da177e4SLinus Torvalds  */
568746d4a02STakashi Iwai void snd_info_card_disconnect(struct snd_card *card)
5691da177e4SLinus Torvalds {
5707eaa943cSTakashi Iwai 	if (!card)
5717eaa943cSTakashi Iwai 		return;
572746d4a02STakashi Iwai 	mutex_lock(&info_mutex);
573a8ca16eaSDavid Howells 	proc_remove(card->proc_root_link);
5741da177e4SLinus Torvalds 	card->proc_root_link = NULL;
575746d4a02STakashi Iwai 	if (card->proc_root)
576746d4a02STakashi Iwai 		snd_info_disconnect(card->proc_root);
577746d4a02STakashi Iwai 	mutex_unlock(&info_mutex);
5781da177e4SLinus Torvalds }
579746d4a02STakashi Iwai 
580746d4a02STakashi Iwai /*
581746d4a02STakashi Iwai  * release the card proc file resources
582746d4a02STakashi Iwai  * called from init.c
583746d4a02STakashi Iwai  */
584746d4a02STakashi Iwai int snd_info_card_free(struct snd_card *card)
585746d4a02STakashi Iwai {
5867eaa943cSTakashi Iwai 	if (!card)
5877eaa943cSTakashi Iwai 		return 0;
588746d4a02STakashi Iwai 	snd_info_free_entry(card->proc_root);
589746d4a02STakashi Iwai 	card->proc_root = NULL;
5901da177e4SLinus Torvalds 	return 0;
5911da177e4SLinus Torvalds }
5921da177e4SLinus Torvalds 
5931da177e4SLinus Torvalds 
5941da177e4SLinus Torvalds /**
5951da177e4SLinus Torvalds  * snd_info_get_line - read one line from the procfs buffer
5961da177e4SLinus Torvalds  * @buffer: the procfs buffer
5971da177e4SLinus Torvalds  * @line: the buffer to store
598ddc64b27SClemens Ladisch  * @len: the max. buffer size
5991da177e4SLinus Torvalds  *
6001da177e4SLinus Torvalds  * Reads one line from the buffer and stores the string.
6011da177e4SLinus Torvalds  *
602eb7c06e8SYacine Belkadi  * Return: Zero if successful, or 1 if error or EOF.
6031da177e4SLinus Torvalds  */
60424c1f931STakashi Iwai int snd_info_get_line(struct snd_info_buffer *buffer, char *line, int len)
6051da177e4SLinus Torvalds {
6060e023687SColin Ian King 	int c;
6071da177e4SLinus Torvalds 
60860379ba0STakashi Iwai 	if (snd_BUG_ON(!buffer))
60960379ba0STakashi Iwai 		return 1;
61060379ba0STakashi Iwai 	if (!buffer->buffer)
6110bc0ec90STakashi Iwai 		return 1;
6121da177e4SLinus Torvalds 	if (len <= 0 || buffer->stop || buffer->error)
6131da177e4SLinus Torvalds 		return 1;
6140bc0ec90STakashi Iwai 	while (!buffer->stop) {
6157e4eeec8STakashi Iwai 		c = buffer->buffer[buffer->curr++];
6167e4eeec8STakashi Iwai 		if (buffer->curr >= buffer->size)
6171da177e4SLinus Torvalds 			buffer->stop = 1;
6180bc0ec90STakashi Iwai 		if (c == '\n')
6191da177e4SLinus Torvalds 			break;
620ddc64b27SClemens Ladisch 		if (len > 1) {
6210bc0ec90STakashi Iwai 			len--;
6221da177e4SLinus Torvalds 			*line++ = c;
6231da177e4SLinus Torvalds 		}
6241da177e4SLinus Torvalds 	}
6251da177e4SLinus Torvalds 	*line = '\0';
6261da177e4SLinus Torvalds 	return 0;
6271da177e4SLinus Torvalds }
628c0d3fb39STakashi Iwai EXPORT_SYMBOL(snd_info_get_line);
629c0d3fb39STakashi Iwai 
6301da177e4SLinus Torvalds /**
631856def8aSHenrik Kretzschmar  * snd_info_get_str - parse a string token
6321da177e4SLinus Torvalds  * @dest: the buffer to store the string token
6331da177e4SLinus Torvalds  * @src: the original string
6341da177e4SLinus Torvalds  * @len: the max. length of token - 1
6351da177e4SLinus Torvalds  *
6361da177e4SLinus Torvalds  * Parses the original string and copy a token to the given
6371da177e4SLinus Torvalds  * string buffer.
6381da177e4SLinus Torvalds  *
639eb7c06e8SYacine Belkadi  * Return: The updated pointer of the original string so that
6401da177e4SLinus Torvalds  * it can be used for the next call.
6411da177e4SLinus Torvalds  */
6424f7454a9STakashi Iwai const char *snd_info_get_str(char *dest, const char *src, int len)
6431da177e4SLinus Torvalds {
6441da177e4SLinus Torvalds 	int c;
6451da177e4SLinus Torvalds 
6461da177e4SLinus Torvalds 	while (*src == ' ' || *src == '\t')
6471da177e4SLinus Torvalds 		src++;
6481da177e4SLinus Torvalds 	if (*src == '"' || *src == '\'') {
6491da177e4SLinus Torvalds 		c = *src++;
6501da177e4SLinus Torvalds 		while (--len > 0 && *src && *src != c) {
6511da177e4SLinus Torvalds 			*dest++ = *src++;
6521da177e4SLinus Torvalds 		}
6531da177e4SLinus Torvalds 		if (*src == c)
6541da177e4SLinus Torvalds 			src++;
6551da177e4SLinus Torvalds 	} else {
6561da177e4SLinus Torvalds 		while (--len > 0 && *src && *src != ' ' && *src != '\t') {
6571da177e4SLinus Torvalds 			*dest++ = *src++;
6581da177e4SLinus Torvalds 		}
6591da177e4SLinus Torvalds 	}
6601da177e4SLinus Torvalds 	*dest = 0;
6611da177e4SLinus Torvalds 	while (*src == ' ' || *src == '\t')
6621da177e4SLinus Torvalds 		src++;
6631da177e4SLinus Torvalds 	return src;
6641da177e4SLinus Torvalds }
665c0d3fb39STakashi Iwai EXPORT_SYMBOL(snd_info_get_str);
666c0d3fb39STakashi Iwai 
667c309c467STakashi Iwai /*
6681da177e4SLinus Torvalds  * snd_info_create_entry - create an info entry
6691da177e4SLinus Torvalds  * @name: the proc file name
6708e7ccb7bSTakashi Iwai  * @parent: the parent directory
6711da177e4SLinus Torvalds  *
6721da177e4SLinus Torvalds  * Creates an info entry with the given file name and initializes as
6731da177e4SLinus Torvalds  * the default state.
6741da177e4SLinus Torvalds  *
6751da177e4SLinus Torvalds  * Usually called from other functions such as
6761da177e4SLinus Torvalds  * snd_info_create_card_entry().
6771da177e4SLinus Torvalds  *
678eb7c06e8SYacine Belkadi  * Return: The pointer of the new instance, or %NULL on failure.
6791da177e4SLinus Torvalds  */
6808e7ccb7bSTakashi Iwai static struct snd_info_entry *
681a858ee66STakashi Iwai snd_info_create_entry(const char *name, struct snd_info_entry *parent,
682a858ee66STakashi Iwai 		      struct module *module)
6831da177e4SLinus Torvalds {
68424c1f931STakashi Iwai 	struct snd_info_entry *entry;
685ca2c0966STakashi Iwai 	entry = kzalloc(sizeof(*entry), GFP_KERNEL);
6861da177e4SLinus Torvalds 	if (entry == NULL)
6871da177e4SLinus Torvalds 		return NULL;
688543537bdSPaulo Marques 	entry->name = kstrdup(name, GFP_KERNEL);
6891da177e4SLinus Torvalds 	if (entry->name == NULL) {
6901da177e4SLinus Torvalds 		kfree(entry);
6911da177e4SLinus Torvalds 		return NULL;
6921da177e4SLinus Torvalds 	}
6936a73cf46SJoe Perches 	entry->mode = S_IFREG | 0444;
6941da177e4SLinus Torvalds 	entry->content = SNDRV_INFO_CONTENT_TEXT;
6951a60d4c5SIngo Molnar 	mutex_init(&entry->access);
696746d4a02STakashi Iwai 	INIT_LIST_HEAD(&entry->children);
697746d4a02STakashi Iwai 	INIT_LIST_HEAD(&entry->list);
6988e7ccb7bSTakashi Iwai 	entry->parent = parent;
699a858ee66STakashi Iwai 	entry->module = module;
7008c2f8708STakashi Iwai 	if (parent) {
7018c2f8708STakashi Iwai 		mutex_lock(&parent->access);
7028e7ccb7bSTakashi Iwai 		list_add_tail(&entry->list, &parent->children);
7038c2f8708STakashi Iwai 		mutex_unlock(&parent->access);
7048c2f8708STakashi Iwai 	}
7051da177e4SLinus Torvalds 	return entry;
7061da177e4SLinus Torvalds }
7071da177e4SLinus Torvalds 
7081da177e4SLinus Torvalds /**
7091da177e4SLinus Torvalds  * snd_info_create_module_entry - create an info entry for the given module
7101da177e4SLinus Torvalds  * @module: the module pointer
7111da177e4SLinus Torvalds  * @name: the file name
7121da177e4SLinus Torvalds  * @parent: the parent directory
7131da177e4SLinus Torvalds  *
7141da177e4SLinus Torvalds  * Creates a new info entry and assigns it to the given module.
7151da177e4SLinus Torvalds  *
716eb7c06e8SYacine Belkadi  * Return: The pointer of the new instance, or %NULL on failure.
7171da177e4SLinus Torvalds  */
71824c1f931STakashi Iwai struct snd_info_entry *snd_info_create_module_entry(struct module * module,
7191da177e4SLinus Torvalds 					       const char *name,
72024c1f931STakashi Iwai 					       struct snd_info_entry *parent)
7211da177e4SLinus Torvalds {
7223a554371STakashi Iwai 	if (!parent)
7233a554371STakashi Iwai 		parent = snd_proc_root;
724a858ee66STakashi Iwai 	return snd_info_create_entry(name, parent, module);
7251da177e4SLinus Torvalds }
726c0d3fb39STakashi Iwai EXPORT_SYMBOL(snd_info_create_module_entry);
727c0d3fb39STakashi Iwai 
7281da177e4SLinus Torvalds /**
7291da177e4SLinus Torvalds  * snd_info_create_card_entry - create an info entry for the given card
7301da177e4SLinus Torvalds  * @card: the card instance
7311da177e4SLinus Torvalds  * @name: the file name
7321da177e4SLinus Torvalds  * @parent: the parent directory
7331da177e4SLinus Torvalds  *
7341da177e4SLinus Torvalds  * Creates a new info entry and assigns it to the given card.
7351da177e4SLinus Torvalds  *
736eb7c06e8SYacine Belkadi  * Return: The pointer of the new instance, or %NULL on failure.
7371da177e4SLinus Torvalds  */
73824c1f931STakashi Iwai struct snd_info_entry *snd_info_create_card_entry(struct snd_card *card,
7391da177e4SLinus Torvalds 					     const char *name,
74024c1f931STakashi Iwai 					     struct snd_info_entry * parent)
7411da177e4SLinus Torvalds {
7423a554371STakashi Iwai 	if (!parent)
7433a554371STakashi Iwai 		parent = card->proc_root;
744a858ee66STakashi Iwai 	return snd_info_create_entry(name, parent, card->module);
7451da177e4SLinus Torvalds }
746c0d3fb39STakashi Iwai EXPORT_SYMBOL(snd_info_create_card_entry);
747c0d3fb39STakashi Iwai 
748746d4a02STakashi Iwai static void snd_info_disconnect(struct snd_info_entry *entry)
749746d4a02STakashi Iwai {
75090a409aaSTakashi Iwai 	struct snd_info_entry *p;
751746d4a02STakashi Iwai 
752746d4a02STakashi Iwai 	if (!entry->p)
753746d4a02STakashi Iwai 		return;
75490a409aaSTakashi Iwai 	list_for_each_entry(p, &entry->children, list)
755c560a679STakashi Iwai 		snd_info_disconnect(p);
756a8ca16eaSDavid Howells 	proc_remove(entry->p);
757746d4a02STakashi Iwai 	entry->p = NULL;
758746d4a02STakashi Iwai }
759746d4a02STakashi Iwai 
7601da177e4SLinus Torvalds /**
7611da177e4SLinus Torvalds  * snd_info_free_entry - release the info entry
7621da177e4SLinus Torvalds  * @entry: the info entry
7631da177e4SLinus Torvalds  *
764c560a679STakashi Iwai  * Releases the info entry.
7651da177e4SLinus Torvalds  */
76624c1f931STakashi Iwai void snd_info_free_entry(struct snd_info_entry * entry)
7671da177e4SLinus Torvalds {
768c560a679STakashi Iwai 	struct snd_info_entry *p, *n;
769c560a679STakashi Iwai 
770c560a679STakashi Iwai 	if (!entry)
7711da177e4SLinus Torvalds 		return;
772746d4a02STakashi Iwai 	if (entry->p) {
773746d4a02STakashi Iwai 		mutex_lock(&info_mutex);
774746d4a02STakashi Iwai 		snd_info_disconnect(entry);
775746d4a02STakashi Iwai 		mutex_unlock(&info_mutex);
776746d4a02STakashi Iwai 	}
777c560a679STakashi Iwai 
778c560a679STakashi Iwai 	/* free all children at first */
779c560a679STakashi Iwai 	list_for_each_entry_safe(p, n, &entry->children, list)
780c560a679STakashi Iwai 		snd_info_free_entry(p);
781c560a679STakashi Iwai 
7828c2f8708STakashi Iwai 	p = entry->parent;
7838c2f8708STakashi Iwai 	if (p) {
7848c2f8708STakashi Iwai 		mutex_lock(&p->access);
78590a409aaSTakashi Iwai 		list_del(&entry->list);
7868c2f8708STakashi Iwai 		mutex_unlock(&p->access);
7878c2f8708STakashi Iwai 	}
7881da177e4SLinus Torvalds 	kfree(entry->name);
7891da177e4SLinus Torvalds 	if (entry->private_free)
7901da177e4SLinus Torvalds 		entry->private_free(entry);
7911da177e4SLinus Torvalds 	kfree(entry);
7921da177e4SLinus Torvalds }
793c0d3fb39STakashi Iwai EXPORT_SYMBOL(snd_info_free_entry);
794c0d3fb39STakashi Iwai 
795348c5ad5STakashi Iwai static int __snd_info_register(struct snd_info_entry *entry)
7961da177e4SLinus Torvalds {
7971da177e4SLinus Torvalds 	struct proc_dir_entry *root, *p = NULL;
7981da177e4SLinus Torvalds 
7997eaa943cSTakashi Iwai 	if (snd_BUG_ON(!entry))
8007eaa943cSTakashi Iwai 		return -ENXIO;
801644dbd64STakashi Iwai 	root = entry->parent == NULL ? snd_proc_root->p : entry->parent->p;
8021a60d4c5SIngo Molnar 	mutex_lock(&info_mutex);
803348c5ad5STakashi Iwai 	if (entry->p || !root)
804348c5ad5STakashi Iwai 		goto unlock;
805aee0c612SAl Viro 	if (S_ISDIR(entry->mode)) {
806aee0c612SAl Viro 		p = proc_mkdir_mode(entry->name, entry->mode, root);
8071da177e4SLinus Torvalds 		if (!p) {
8081a60d4c5SIngo Molnar 			mutex_unlock(&info_mutex);
8091da177e4SLinus Torvalds 			return -ENOMEM;
8101da177e4SLinus Torvalds 		}
811aee0c612SAl Viro 	} else {
81297a32539SAlexey Dobriyan 		const struct proc_ops *ops;
8134adb7bcbSTakashi Iwai 		if (entry->content == SNDRV_INFO_CONTENT_DATA)
8144adb7bcbSTakashi Iwai 			ops = &snd_info_entry_operations;
8154adb7bcbSTakashi Iwai 		else
8164adb7bcbSTakashi Iwai 			ops = &snd_info_text_entry_ops;
817aee0c612SAl Viro 		p = proc_create_data(entry->name, entry->mode, root,
8184adb7bcbSTakashi Iwai 				     ops, entry);
819aee0c612SAl Viro 		if (!p) {
820aee0c612SAl Viro 			mutex_unlock(&info_mutex);
821aee0c612SAl Viro 			return -ENOMEM;
822aee0c612SAl Viro 		}
823271a15eaSDavid Howells 		proc_set_size(p, entry->size);
824aee0c612SAl Viro 	}
8251da177e4SLinus Torvalds 	entry->p = p;
826348c5ad5STakashi Iwai  unlock:
8271a60d4c5SIngo Molnar 	mutex_unlock(&info_mutex);
8281da177e4SLinus Torvalds 	return 0;
8291da177e4SLinus Torvalds }
830348c5ad5STakashi Iwai 
831348c5ad5STakashi Iwai /**
832348c5ad5STakashi Iwai  * snd_info_register - register the info entry
833348c5ad5STakashi Iwai  * @entry: the info entry
834348c5ad5STakashi Iwai  *
835348c5ad5STakashi Iwai  * Registers the proc info entry.
836348c5ad5STakashi Iwai  * The all children entries are registered recursively.
837348c5ad5STakashi Iwai  *
838348c5ad5STakashi Iwai  * Return: Zero if successful, or a negative error code on failure.
839348c5ad5STakashi Iwai  */
840348c5ad5STakashi Iwai int snd_info_register(struct snd_info_entry *entry)
841348c5ad5STakashi Iwai {
842348c5ad5STakashi Iwai 	struct snd_info_entry *p;
843348c5ad5STakashi Iwai 	int err;
844348c5ad5STakashi Iwai 
845348c5ad5STakashi Iwai 	if (!entry->p) {
846348c5ad5STakashi Iwai 		err = __snd_info_register(entry);
847348c5ad5STakashi Iwai 		if (err < 0)
848348c5ad5STakashi Iwai 			return err;
849348c5ad5STakashi Iwai 	}
850348c5ad5STakashi Iwai 
851348c5ad5STakashi Iwai 	list_for_each_entry(p, &entry->children, list) {
852348c5ad5STakashi Iwai 		err = snd_info_register(p);
853348c5ad5STakashi Iwai 		if (err < 0)
854348c5ad5STakashi Iwai 			return err;
855348c5ad5STakashi Iwai 	}
856348c5ad5STakashi Iwai 
857348c5ad5STakashi Iwai 	return 0;
858348c5ad5STakashi Iwai }
859c0d3fb39STakashi Iwai EXPORT_SYMBOL(snd_info_register);
860c0d3fb39STakashi Iwai 
8617453e1daSTakashi Iwai /**
8627453e1daSTakashi Iwai  * snd_card_rw_proc_new - Create a read/write text proc file entry for the card
8637453e1daSTakashi Iwai  * @card: the card instance
8647453e1daSTakashi Iwai  * @name: the file name
8657453e1daSTakashi Iwai  * @private_data: the arbitrary private data
8667453e1daSTakashi Iwai  * @read: the read callback
8677453e1daSTakashi Iwai  * @write: the write callback, NULL for read-only
8687453e1daSTakashi Iwai  *
8697453e1daSTakashi Iwai  * This proc file entry will be registered via snd_card_register() call, and
8707453e1daSTakashi Iwai  * it will be removed automatically at the card removal, too.
8717453e1daSTakashi Iwai  */
8727453e1daSTakashi Iwai int snd_card_rw_proc_new(struct snd_card *card, const char *name,
8737453e1daSTakashi Iwai 			 void *private_data,
8747453e1daSTakashi Iwai 			 void (*read)(struct snd_info_entry *,
8757453e1daSTakashi Iwai 				      struct snd_info_buffer *),
8767453e1daSTakashi Iwai 			 void (*write)(struct snd_info_entry *entry,
8777453e1daSTakashi Iwai 				       struct snd_info_buffer *buffer))
8787453e1daSTakashi Iwai {
8797453e1daSTakashi Iwai 	struct snd_info_entry *entry;
8807453e1daSTakashi Iwai 
8817453e1daSTakashi Iwai 	entry = snd_info_create_card_entry(card, name, card->proc_root);
8827453e1daSTakashi Iwai 	if (!entry)
8837453e1daSTakashi Iwai 		return -ENOMEM;
8847453e1daSTakashi Iwai 	snd_info_set_text_ops(entry, private_data, read);
8857453e1daSTakashi Iwai 	if (write) {
8867453e1daSTakashi Iwai 		entry->mode |= 0200;
8877453e1daSTakashi Iwai 		entry->c.text.write = write;
8887453e1daSTakashi Iwai 	}
8897453e1daSTakashi Iwai 	return 0;
8907453e1daSTakashi Iwai }
8917453e1daSTakashi Iwai EXPORT_SYMBOL_GPL(snd_card_rw_proc_new);
8927453e1daSTakashi Iwai 
8931da177e4SLinus Torvalds /*
8941da177e4SLinus Torvalds 
8951da177e4SLinus Torvalds  */
8961da177e4SLinus Torvalds 
89724c1f931STakashi Iwai static void snd_info_version_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
8981da177e4SLinus Torvalds {
8991da177e4SLinus Torvalds 	snd_iprintf(buffer,
90042662748SJaroslav Kysela 		    "Advanced Linux Sound Architecture Driver Version k%s.\n",
90142662748SJaroslav Kysela 		    init_utsname()->release);
9021da177e4SLinus Torvalds }
9031da177e4SLinus Torvalds 
9041da177e4SLinus Torvalds static int __init snd_info_version_init(void)
9051da177e4SLinus Torvalds {
90624c1f931STakashi Iwai 	struct snd_info_entry *entry;
9071da177e4SLinus Torvalds 
9081da177e4SLinus Torvalds 	entry = snd_info_create_module_entry(THIS_MODULE, "version", NULL);
9091da177e4SLinus Torvalds 	if (entry == NULL)
9101da177e4SLinus Torvalds 		return -ENOMEM;
9111da177e4SLinus Torvalds 	entry->c.text.read = snd_info_version_read;
912b591b6e9STakashi Iwai 	return snd_info_register(entry); /* freed in error path */
9131da177e4SLinus Torvalds }
914