xref: /openbmc/linux/sound/core/info.c (revision 1a59d1b8)
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 #include <stdarg.h>
201da177e4SLinus Torvalds 
211da177e4SLinus Torvalds int snd_info_check_reserved_words(const char *str)
221da177e4SLinus Torvalds {
231da177e4SLinus Torvalds 	static char *reserved[] =
241da177e4SLinus Torvalds 	{
251da177e4SLinus Torvalds 		"version",
261da177e4SLinus Torvalds 		"meminfo",
271da177e4SLinus Torvalds 		"memdebug",
281da177e4SLinus Torvalds 		"detect",
291da177e4SLinus Torvalds 		"devices",
301da177e4SLinus Torvalds 		"oss",
311da177e4SLinus Torvalds 		"cards",
321da177e4SLinus Torvalds 		"timers",
331da177e4SLinus Torvalds 		"synth",
341da177e4SLinus Torvalds 		"pcm",
351da177e4SLinus Torvalds 		"seq",
361da177e4SLinus Torvalds 		NULL
371da177e4SLinus Torvalds 	};
381da177e4SLinus Torvalds 	char **xstr = reserved;
391da177e4SLinus Torvalds 
401da177e4SLinus Torvalds 	while (*xstr) {
411da177e4SLinus Torvalds 		if (!strcmp(*xstr, str))
421da177e4SLinus Torvalds 			return 0;
431da177e4SLinus Torvalds 		xstr++;
441da177e4SLinus Torvalds 	}
451da177e4SLinus Torvalds 	if (!strncmp(str, "card", 4))
461da177e4SLinus Torvalds 		return 0;
471da177e4SLinus Torvalds 	return 1;
481da177e4SLinus Torvalds }
491da177e4SLinus Torvalds 
501a60d4c5SIngo Molnar static DEFINE_MUTEX(info_mutex);
511da177e4SLinus Torvalds 
5224c1f931STakashi Iwai struct snd_info_private_data {
5324c1f931STakashi Iwai 	struct snd_info_buffer *rbuffer;
5424c1f931STakashi Iwai 	struct snd_info_buffer *wbuffer;
5524c1f931STakashi Iwai 	struct snd_info_entry *entry;
561da177e4SLinus Torvalds 	void *file_private_data;
5724c1f931STakashi Iwai };
581da177e4SLinus Torvalds 
591da177e4SLinus Torvalds static int snd_info_version_init(void);
60746d4a02STakashi Iwai static void snd_info_disconnect(struct snd_info_entry *entry);
611da177e4SLinus Torvalds 
621da177e4SLinus Torvalds /*
631da177e4SLinus Torvalds 
641da177e4SLinus Torvalds  */
651da177e4SLinus Torvalds 
66644dbd64STakashi Iwai static struct snd_info_entry *snd_proc_root;
676581f4e7STakashi Iwai struct snd_info_entry *snd_seq_root;
68c0d3fb39STakashi Iwai EXPORT_SYMBOL(snd_seq_root);
69c0d3fb39STakashi Iwai 
701da177e4SLinus Torvalds #ifdef CONFIG_SND_OSSEMUL
716581f4e7STakashi Iwai struct snd_info_entry *snd_oss_root;
721da177e4SLinus Torvalds #endif
731da177e4SLinus Torvalds 
744adb7bcbSTakashi Iwai static int alloc_info_private(struct snd_info_entry *entry,
754adb7bcbSTakashi Iwai 			      struct snd_info_private_data **ret)
764adb7bcbSTakashi Iwai {
774adb7bcbSTakashi Iwai 	struct snd_info_private_data *data;
784adb7bcbSTakashi Iwai 
794adb7bcbSTakashi Iwai 	if (!entry || !entry->p)
804adb7bcbSTakashi Iwai 		return -ENODEV;
814adb7bcbSTakashi Iwai 	if (!try_module_get(entry->module))
824adb7bcbSTakashi Iwai 		return -EFAULT;
834adb7bcbSTakashi Iwai 	data = kzalloc(sizeof(*data), GFP_KERNEL);
844adb7bcbSTakashi Iwai 	if (!data) {
854adb7bcbSTakashi Iwai 		module_put(entry->module);
864adb7bcbSTakashi Iwai 		return -ENOMEM;
874adb7bcbSTakashi Iwai 	}
884adb7bcbSTakashi Iwai 	data->entry = entry;
894adb7bcbSTakashi Iwai 	*ret = data;
904adb7bcbSTakashi Iwai 	return 0;
914adb7bcbSTakashi Iwai }
924adb7bcbSTakashi Iwai 
934adb7bcbSTakashi Iwai static bool valid_pos(loff_t pos, size_t count)
944adb7bcbSTakashi Iwai {
954adb7bcbSTakashi Iwai 	if (pos < 0 || (long) pos != pos || (ssize_t) count < 0)
964adb7bcbSTakashi Iwai 		return false;
974adb7bcbSTakashi Iwai 	if ((unsigned long) pos + (unsigned long) count < (unsigned long) pos)
984adb7bcbSTakashi Iwai 		return false;
994adb7bcbSTakashi Iwai 	return true;
1004adb7bcbSTakashi Iwai }
1014adb7bcbSTakashi Iwai 
1024adb7bcbSTakashi Iwai /*
1034adb7bcbSTakashi Iwai  * file ops for binary proc files
1044adb7bcbSTakashi Iwai  */
1051da177e4SLinus Torvalds static loff_t snd_info_entry_llseek(struct file *file, loff_t offset, int orig)
1061da177e4SLinus Torvalds {
10724c1f931STakashi Iwai 	struct snd_info_private_data *data;
1081da177e4SLinus Torvalds 	struct snd_info_entry *entry;
10973029e0fSTakashi Iwai 	loff_t ret = -EINVAL, size;
1101da177e4SLinus Torvalds 
1111da177e4SLinus Torvalds 	data = file->private_data;
1121da177e4SLinus Torvalds 	entry = data->entry;
1135b5cd553STakashi Iwai 	mutex_lock(&entry->access);
1144adb7bcbSTakashi Iwai 	if (entry->c.ops->llseek) {
11573029e0fSTakashi Iwai 		offset = entry->c.ops->llseek(entry,
1161da177e4SLinus Torvalds 					      data->file_private_data,
1171da177e4SLinus Torvalds 					      file, offset, orig);
1181da177e4SLinus Torvalds 		goto out;
1191da177e4SLinus Torvalds 	}
1204adb7bcbSTakashi Iwai 
12173029e0fSTakashi Iwai 	size = entry->size;
12273029e0fSTakashi Iwai 	switch (orig) {
12373029e0fSTakashi Iwai 	case SEEK_SET:
1241da177e4SLinus Torvalds 		break;
12573029e0fSTakashi Iwai 	case SEEK_CUR:
12673029e0fSTakashi Iwai 		offset += file->f_pos;
12773029e0fSTakashi Iwai 		break;
12873029e0fSTakashi Iwai 	case SEEK_END:
12973029e0fSTakashi Iwai 		if (!size)
13073029e0fSTakashi Iwai 			goto out;
13173029e0fSTakashi Iwai 		offset += size;
13273029e0fSTakashi Iwai 		break;
13373029e0fSTakashi Iwai 	default:
13473029e0fSTakashi Iwai 		goto out;
1351da177e4SLinus Torvalds 	}
13673029e0fSTakashi Iwai 	if (offset < 0)
13773029e0fSTakashi Iwai 		goto out;
13873029e0fSTakashi Iwai 	if (size && offset > size)
13973029e0fSTakashi Iwai 		offset = size;
14073029e0fSTakashi Iwai 	file->f_pos = offset;
14173029e0fSTakashi Iwai 	ret = offset;
1421da177e4SLinus Torvalds  out:
1435b5cd553STakashi Iwai 	mutex_unlock(&entry->access);
1441da177e4SLinus Torvalds 	return ret;
1451da177e4SLinus Torvalds }
1461da177e4SLinus Torvalds 
1471da177e4SLinus Torvalds static ssize_t snd_info_entry_read(struct file *file, char __user *buffer,
1481da177e4SLinus Torvalds 				   size_t count, loff_t * offset)
1491da177e4SLinus Torvalds {
1504adb7bcbSTakashi Iwai 	struct snd_info_private_data *data = file->private_data;
1514adb7bcbSTakashi Iwai 	struct snd_info_entry *entry = data->entry;
1524adb7bcbSTakashi Iwai 	size_t size;
1531da177e4SLinus Torvalds 	loff_t pos;
1541da177e4SLinus Torvalds 
1551da177e4SLinus Torvalds 	pos = *offset;
1564adb7bcbSTakashi Iwai 	if (!valid_pos(pos, count))
1571da177e4SLinus Torvalds 		return -EIO;
158d97e1b78STakashi Iwai 	if (pos >= entry->size)
159d97e1b78STakashi Iwai 		return 0;
160d97e1b78STakashi Iwai 	size = entry->size - pos;
161d97e1b78STakashi Iwai 	size = min(count, size);
1624adb7bcbSTakashi Iwai 	size = entry->c.ops->read(entry, data->file_private_data,
163d97e1b78STakashi Iwai 				  file, buffer, size, pos);
1641da177e4SLinus Torvalds 	if ((ssize_t) size > 0)
1651da177e4SLinus Torvalds 		*offset = pos + size;
1661da177e4SLinus Torvalds 	return size;
1671da177e4SLinus Torvalds }
1681da177e4SLinus Torvalds 
1691da177e4SLinus Torvalds static ssize_t snd_info_entry_write(struct file *file, const 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;
1747e4eeec8STakashi Iwai 	ssize_t size = 0;
1751da177e4SLinus Torvalds 	loff_t pos;
1761da177e4SLinus Torvalds 
1771da177e4SLinus Torvalds 	pos = *offset;
1784adb7bcbSTakashi Iwai 	if (!valid_pos(pos, count))
1791da177e4SLinus Torvalds 		return -EIO;
1804adb7bcbSTakashi Iwai 	if (count > 0) {
181d97e1b78STakashi Iwai 		size_t maxsize = entry->size - pos;
182d97e1b78STakashi Iwai 		count = min(count, maxsize);
1834adb7bcbSTakashi Iwai 		size = entry->c.ops->write(entry, data->file_private_data,
1841da177e4SLinus Torvalds 					   file, buffer, count, pos);
185d97e1b78STakashi Iwai 	}
1864adb7bcbSTakashi Iwai 	if (size > 0)
1871da177e4SLinus Torvalds 		*offset = pos + size;
1881da177e4SLinus Torvalds 	return size;
1891da177e4SLinus Torvalds }
1901da177e4SLinus Torvalds 
191680ef72aSAl Viro static __poll_t snd_info_entry_poll(struct file *file, poll_table *wait)
1921da177e4SLinus Torvalds {
1934adb7bcbSTakashi Iwai 	struct snd_info_private_data *data = file->private_data;
1944adb7bcbSTakashi Iwai 	struct snd_info_entry *entry = data->entry;
195680ef72aSAl Viro 	__poll_t mask = 0;
1961da177e4SLinus Torvalds 
1971da177e4SLinus Torvalds 	if (entry->c.ops->poll)
1981da177e4SLinus Torvalds 		return entry->c.ops->poll(entry,
1991da177e4SLinus Torvalds 					  data->file_private_data,
2001da177e4SLinus Torvalds 					  file, wait);
2011da177e4SLinus Torvalds 	if (entry->c.ops->read)
202a9a08845SLinus Torvalds 		mask |= EPOLLIN | EPOLLRDNORM;
2031da177e4SLinus Torvalds 	if (entry->c.ops->write)
204a9a08845SLinus Torvalds 		mask |= EPOLLOUT | EPOLLWRNORM;
2051da177e4SLinus Torvalds 	return mask;
2061da177e4SLinus Torvalds }
2071da177e4SLinus Torvalds 
208d99e9889SIngo Molnar static long snd_info_entry_ioctl(struct file *file, unsigned int cmd,
209d99e9889SIngo Molnar 				unsigned long arg)
2101da177e4SLinus Torvalds {
2114adb7bcbSTakashi Iwai 	struct snd_info_private_data *data = file->private_data;
2124adb7bcbSTakashi Iwai 	struct snd_info_entry *entry = data->entry;
2131da177e4SLinus Torvalds 
2144adb7bcbSTakashi Iwai 	if (!entry->c.ops->ioctl)
2151da177e4SLinus Torvalds 		return -ENOTTY;
2164adb7bcbSTakashi Iwai 	return entry->c.ops->ioctl(entry, data->file_private_data,
2174adb7bcbSTakashi Iwai 				   file, cmd, arg);
2181da177e4SLinus Torvalds }
2191da177e4SLinus Torvalds 
2201da177e4SLinus Torvalds static int snd_info_entry_mmap(struct file *file, struct vm_area_struct *vma)
2211da177e4SLinus Torvalds {
222496ad9aaSAl Viro 	struct inode *inode = file_inode(file);
22324c1f931STakashi Iwai 	struct snd_info_private_data *data;
2241da177e4SLinus Torvalds 	struct snd_info_entry *entry;
2251da177e4SLinus Torvalds 
2261da177e4SLinus Torvalds 	data = file->private_data;
2271da177e4SLinus Torvalds 	if (data == NULL)
2281da177e4SLinus Torvalds 		return 0;
2291da177e4SLinus Torvalds 	entry = data->entry;
2304adb7bcbSTakashi Iwai 	if (!entry->c.ops->mmap)
2311da177e4SLinus Torvalds 		return -ENXIO;
2324adb7bcbSTakashi Iwai 	return entry->c.ops->mmap(entry, data->file_private_data,
2334adb7bcbSTakashi Iwai 				  inode, file, vma);
2344adb7bcbSTakashi Iwai }
2354adb7bcbSTakashi Iwai 
2364adb7bcbSTakashi Iwai static int snd_info_entry_open(struct inode *inode, struct file *file)
2374adb7bcbSTakashi Iwai {
2384adb7bcbSTakashi Iwai 	struct snd_info_entry *entry = PDE_DATA(inode);
2394adb7bcbSTakashi Iwai 	struct snd_info_private_data *data;
2404adb7bcbSTakashi Iwai 	int mode, err;
2414adb7bcbSTakashi Iwai 
2424adb7bcbSTakashi Iwai 	mutex_lock(&info_mutex);
2434adb7bcbSTakashi Iwai 	err = alloc_info_private(entry, &data);
2444adb7bcbSTakashi Iwai 	if (err < 0)
2454adb7bcbSTakashi Iwai 		goto unlock;
2464adb7bcbSTakashi Iwai 
2474adb7bcbSTakashi Iwai 	mode = file->f_flags & O_ACCMODE;
2484adb7bcbSTakashi Iwai 	if (((mode == O_RDONLY || mode == O_RDWR) && !entry->c.ops->read) ||
2494adb7bcbSTakashi Iwai 	    ((mode == O_WRONLY || mode == O_RDWR) && !entry->c.ops->write)) {
2504adb7bcbSTakashi Iwai 		err = -ENODEV;
2514adb7bcbSTakashi Iwai 		goto error;
2524adb7bcbSTakashi Iwai 	}
2534adb7bcbSTakashi Iwai 
2544adb7bcbSTakashi Iwai 	if (entry->c.ops->open) {
2554adb7bcbSTakashi Iwai 		err = entry->c.ops->open(entry, mode, &data->file_private_data);
2564adb7bcbSTakashi Iwai 		if (err < 0)
2574adb7bcbSTakashi Iwai 			goto error;
2584adb7bcbSTakashi Iwai 	}
2594adb7bcbSTakashi Iwai 
2604adb7bcbSTakashi Iwai 	file->private_data = data;
2614adb7bcbSTakashi Iwai 	mutex_unlock(&info_mutex);
2624adb7bcbSTakashi Iwai 	return 0;
2634adb7bcbSTakashi Iwai 
2644adb7bcbSTakashi Iwai  error:
2654adb7bcbSTakashi Iwai 	kfree(data);
2664adb7bcbSTakashi Iwai 	module_put(entry->module);
2674adb7bcbSTakashi Iwai  unlock:
2684adb7bcbSTakashi Iwai 	mutex_unlock(&info_mutex);
2694adb7bcbSTakashi Iwai 	return err;
2704adb7bcbSTakashi Iwai }
2714adb7bcbSTakashi Iwai 
2724adb7bcbSTakashi Iwai static int snd_info_entry_release(struct inode *inode, struct file *file)
2734adb7bcbSTakashi Iwai {
2744adb7bcbSTakashi Iwai 	struct snd_info_private_data *data = file->private_data;
2754adb7bcbSTakashi Iwai 	struct snd_info_entry *entry = data->entry;
2764adb7bcbSTakashi Iwai 
2774adb7bcbSTakashi Iwai 	if (entry->c.ops->release)
2784adb7bcbSTakashi Iwai 		entry->c.ops->release(entry, file->f_flags & O_ACCMODE,
2794adb7bcbSTakashi Iwai 				      data->file_private_data);
2804adb7bcbSTakashi Iwai 	module_put(entry->module);
2814adb7bcbSTakashi Iwai 	kfree(data);
2824adb7bcbSTakashi Iwai 	return 0;
2831da177e4SLinus Torvalds }
2841da177e4SLinus Torvalds 
2859c2e08c5SArjan van de Ven static const struct file_operations snd_info_entry_operations =
2861da177e4SLinus Torvalds {
2871da177e4SLinus Torvalds 	.owner =		THIS_MODULE,
2881da177e4SLinus Torvalds 	.llseek =		snd_info_entry_llseek,
2891da177e4SLinus Torvalds 	.read =			snd_info_entry_read,
2901da177e4SLinus Torvalds 	.write =		snd_info_entry_write,
2911da177e4SLinus Torvalds 	.poll =			snd_info_entry_poll,
292d99e9889SIngo Molnar 	.unlocked_ioctl =	snd_info_entry_ioctl,
2931da177e4SLinus Torvalds 	.mmap =			snd_info_entry_mmap,
2941da177e4SLinus Torvalds 	.open =			snd_info_entry_open,
2951da177e4SLinus Torvalds 	.release =		snd_info_entry_release,
2961da177e4SLinus Torvalds };
2971da177e4SLinus Torvalds 
2984adb7bcbSTakashi Iwai /*
2994adb7bcbSTakashi Iwai  * file ops for text proc files
3004adb7bcbSTakashi Iwai  */
3014adb7bcbSTakashi Iwai static ssize_t snd_info_text_entry_write(struct file *file,
3024adb7bcbSTakashi Iwai 					 const char __user *buffer,
3034adb7bcbSTakashi Iwai 					 size_t count, loff_t *offset)
3044adb7bcbSTakashi Iwai {
3054adb7bcbSTakashi Iwai 	struct seq_file *m = file->private_data;
3064adb7bcbSTakashi Iwai 	struct snd_info_private_data *data = m->private;
3074adb7bcbSTakashi Iwai 	struct snd_info_entry *entry = data->entry;
3084adb7bcbSTakashi Iwai 	struct snd_info_buffer *buf;
3094adb7bcbSTakashi Iwai 	loff_t pos;
3104adb7bcbSTakashi Iwai 	size_t next;
3114adb7bcbSTakashi Iwai 	int err = 0;
3124adb7bcbSTakashi Iwai 
3136809cd68STakashi Iwai 	if (!entry->c.text.write)
3146809cd68STakashi Iwai 		return -EIO;
3154adb7bcbSTakashi Iwai 	pos = *offset;
3164adb7bcbSTakashi Iwai 	if (!valid_pos(pos, count))
3174adb7bcbSTakashi Iwai 		return -EIO;
3184adb7bcbSTakashi Iwai 	next = pos + count;
319027a9fe6STakashi Iwai 	/* don't handle too large text inputs */
320027a9fe6STakashi Iwai 	if (next > 16 * 1024)
321027a9fe6STakashi Iwai 		return -EIO;
3224adb7bcbSTakashi Iwai 	mutex_lock(&entry->access);
3234adb7bcbSTakashi Iwai 	buf = data->wbuffer;
3244adb7bcbSTakashi Iwai 	if (!buf) {
3254adb7bcbSTakashi Iwai 		data->wbuffer = buf = kzalloc(sizeof(*buf), GFP_KERNEL);
3264adb7bcbSTakashi Iwai 		if (!buf) {
3274adb7bcbSTakashi Iwai 			err = -ENOMEM;
3284adb7bcbSTakashi Iwai 			goto error;
3294adb7bcbSTakashi Iwai 		}
3304adb7bcbSTakashi Iwai 	}
3314adb7bcbSTakashi Iwai 	if (next > buf->len) {
332ffb73b08STakashi Iwai 		char *nbuf = kvzalloc(PAGE_ALIGN(next), GFP_KERNEL);
3334adb7bcbSTakashi Iwai 		if (!nbuf) {
3344adb7bcbSTakashi Iwai 			err = -ENOMEM;
3354adb7bcbSTakashi Iwai 			goto error;
3364adb7bcbSTakashi Iwai 		}
337ffb73b08STakashi Iwai 		kvfree(buf->buffer);
3384adb7bcbSTakashi Iwai 		buf->buffer = nbuf;
3394adb7bcbSTakashi Iwai 		buf->len = PAGE_ALIGN(next);
3404adb7bcbSTakashi Iwai 	}
3414adb7bcbSTakashi Iwai 	if (copy_from_user(buf->buffer + pos, buffer, count)) {
3424adb7bcbSTakashi Iwai 		err = -EFAULT;
3434adb7bcbSTakashi Iwai 		goto error;
3444adb7bcbSTakashi Iwai 	}
3454adb7bcbSTakashi Iwai 	buf->size = next;
3464adb7bcbSTakashi Iwai  error:
3474adb7bcbSTakashi Iwai 	mutex_unlock(&entry->access);
3484adb7bcbSTakashi Iwai 	if (err < 0)
3494adb7bcbSTakashi Iwai 		return err;
3504adb7bcbSTakashi Iwai 	*offset = next;
3514adb7bcbSTakashi Iwai 	return count;
3524adb7bcbSTakashi Iwai }
3534adb7bcbSTakashi Iwai 
3544adb7bcbSTakashi Iwai static int snd_info_seq_show(struct seq_file *seq, void *p)
3554adb7bcbSTakashi Iwai {
3564adb7bcbSTakashi Iwai 	struct snd_info_private_data *data = seq->private;
3574adb7bcbSTakashi Iwai 	struct snd_info_entry *entry = data->entry;
3584adb7bcbSTakashi Iwai 
3596809cd68STakashi Iwai 	if (!entry->c.text.read) {
3606809cd68STakashi Iwai 		return -EIO;
3616809cd68STakashi Iwai 	} else {
3624adb7bcbSTakashi Iwai 		data->rbuffer->buffer = (char *)seq; /* XXX hack! */
3634adb7bcbSTakashi Iwai 		entry->c.text.read(entry, data->rbuffer);
3644adb7bcbSTakashi Iwai 	}
3654adb7bcbSTakashi Iwai 	return 0;
3664adb7bcbSTakashi Iwai }
3674adb7bcbSTakashi Iwai 
3684adb7bcbSTakashi Iwai static int snd_info_text_entry_open(struct inode *inode, struct file *file)
3694adb7bcbSTakashi Iwai {
3704adb7bcbSTakashi Iwai 	struct snd_info_entry *entry = PDE_DATA(inode);
3714adb7bcbSTakashi Iwai 	struct snd_info_private_data *data;
3724adb7bcbSTakashi Iwai 	int err;
3734adb7bcbSTakashi Iwai 
3744adb7bcbSTakashi Iwai 	mutex_lock(&info_mutex);
3754adb7bcbSTakashi Iwai 	err = alloc_info_private(entry, &data);
3764adb7bcbSTakashi Iwai 	if (err < 0)
3774adb7bcbSTakashi Iwai 		goto unlock;
3784adb7bcbSTakashi Iwai 
3794adb7bcbSTakashi Iwai 	data->rbuffer = kzalloc(sizeof(*data->rbuffer), GFP_KERNEL);
3804adb7bcbSTakashi Iwai 	if (!data->rbuffer) {
3814adb7bcbSTakashi Iwai 		err = -ENOMEM;
3824adb7bcbSTakashi Iwai 		goto error;
3834adb7bcbSTakashi Iwai 	}
3844adb7bcbSTakashi Iwai 	if (entry->size)
3854adb7bcbSTakashi Iwai 		err = single_open_size(file, snd_info_seq_show, data,
3864adb7bcbSTakashi Iwai 				       entry->size);
3874adb7bcbSTakashi Iwai 	else
3884adb7bcbSTakashi Iwai 		err = single_open(file, snd_info_seq_show, data);
3894adb7bcbSTakashi Iwai 	if (err < 0)
3904adb7bcbSTakashi Iwai 		goto error;
3914adb7bcbSTakashi Iwai 	mutex_unlock(&info_mutex);
3924adb7bcbSTakashi Iwai 	return 0;
3934adb7bcbSTakashi Iwai 
3944adb7bcbSTakashi Iwai  error:
3954adb7bcbSTakashi Iwai 	kfree(data->rbuffer);
3964adb7bcbSTakashi Iwai 	kfree(data);
3974adb7bcbSTakashi Iwai 	module_put(entry->module);
3984adb7bcbSTakashi Iwai  unlock:
3994adb7bcbSTakashi Iwai 	mutex_unlock(&info_mutex);
4004adb7bcbSTakashi Iwai 	return err;
4014adb7bcbSTakashi Iwai }
4024adb7bcbSTakashi Iwai 
4034adb7bcbSTakashi Iwai static int snd_info_text_entry_release(struct inode *inode, struct file *file)
4044adb7bcbSTakashi Iwai {
4054adb7bcbSTakashi Iwai 	struct seq_file *m = file->private_data;
4064adb7bcbSTakashi Iwai 	struct snd_info_private_data *data = m->private;
4074adb7bcbSTakashi Iwai 	struct snd_info_entry *entry = data->entry;
4084adb7bcbSTakashi Iwai 
4094adb7bcbSTakashi Iwai 	if (data->wbuffer && entry->c.text.write)
4104adb7bcbSTakashi Iwai 		entry->c.text.write(entry, data->wbuffer);
4114adb7bcbSTakashi Iwai 
4124adb7bcbSTakashi Iwai 	single_release(inode, file);
4134adb7bcbSTakashi Iwai 	kfree(data->rbuffer);
4144adb7bcbSTakashi Iwai 	if (data->wbuffer) {
415ffb73b08STakashi Iwai 		kvfree(data->wbuffer->buffer);
4164adb7bcbSTakashi Iwai 		kfree(data->wbuffer);
4174adb7bcbSTakashi Iwai 	}
4184adb7bcbSTakashi Iwai 
4194adb7bcbSTakashi Iwai 	module_put(entry->module);
4204adb7bcbSTakashi Iwai 	kfree(data);
4214adb7bcbSTakashi Iwai 	return 0;
4224adb7bcbSTakashi Iwai }
4234adb7bcbSTakashi Iwai 
4244adb7bcbSTakashi Iwai static const struct file_operations snd_info_text_entry_ops =
4254adb7bcbSTakashi Iwai {
4264adb7bcbSTakashi Iwai 	.owner =		THIS_MODULE,
4274adb7bcbSTakashi Iwai 	.open =			snd_info_text_entry_open,
4284adb7bcbSTakashi Iwai 	.release =		snd_info_text_entry_release,
4294adb7bcbSTakashi Iwai 	.write =		snd_info_text_entry_write,
4304adb7bcbSTakashi Iwai 	.llseek =		seq_lseek,
4314adb7bcbSTakashi Iwai 	.read =			seq_read,
4324adb7bcbSTakashi Iwai };
4334adb7bcbSTakashi Iwai 
434886364f6STakashi Iwai static struct snd_info_entry *create_subdir(struct module *mod,
435886364f6STakashi Iwai 					    const char *name)
436886364f6STakashi Iwai {
437886364f6STakashi Iwai 	struct snd_info_entry *entry;
438886364f6STakashi Iwai 
439886364f6STakashi Iwai 	entry = snd_info_create_module_entry(mod, name, NULL);
440886364f6STakashi Iwai 	if (!entry)
441886364f6STakashi Iwai 		return NULL;
4426a73cf46SJoe Perches 	entry->mode = S_IFDIR | 0555;
443886364f6STakashi Iwai 	if (snd_info_register(entry) < 0) {
444886364f6STakashi Iwai 		snd_info_free_entry(entry);
445886364f6STakashi Iwai 		return NULL;
446886364f6STakashi Iwai 	}
447886364f6STakashi Iwai 	return entry;
448886364f6STakashi Iwai }
449886364f6STakashi Iwai 
4508e7ccb7bSTakashi Iwai static struct snd_info_entry *
451a858ee66STakashi Iwai snd_info_create_entry(const char *name, struct snd_info_entry *parent,
452a858ee66STakashi Iwai 		      struct module *module);
453644dbd64STakashi Iwai 
4541da177e4SLinus Torvalds int __init snd_info_init(void)
4551da177e4SLinus Torvalds {
456a858ee66STakashi Iwai 	snd_proc_root = snd_info_create_entry("asound", NULL, THIS_MODULE);
457644dbd64STakashi Iwai 	if (!snd_proc_root)
4581da177e4SLinus Torvalds 		return -ENOMEM;
4596a73cf46SJoe Perches 	snd_proc_root->mode = S_IFDIR | 0555;
460644dbd64STakashi Iwai 	snd_proc_root->p = proc_mkdir("asound", NULL);
461644dbd64STakashi Iwai 	if (!snd_proc_root->p)
462644dbd64STakashi Iwai 		goto error;
4631da177e4SLinus Torvalds #ifdef CONFIG_SND_OSSEMUL
464886364f6STakashi Iwai 	snd_oss_root = create_subdir(THIS_MODULE, "oss");
465886364f6STakashi Iwai 	if (!snd_oss_root)
466886364f6STakashi Iwai 		goto error;
4671da177e4SLinus Torvalds #endif
4688eeaa2f9STakashi Iwai #if IS_ENABLED(CONFIG_SND_SEQUENCER)
469886364f6STakashi Iwai 	snd_seq_root = create_subdir(THIS_MODULE, "seq");
470886364f6STakashi Iwai 	if (!snd_seq_root)
471886364f6STakashi Iwai 		goto error;
4721da177e4SLinus Torvalds #endif
473b591b6e9STakashi Iwai 	if (snd_info_version_init() < 0 ||
474b591b6e9STakashi Iwai 	    snd_minor_info_init() < 0 ||
475b591b6e9STakashi Iwai 	    snd_minor_info_oss_init() < 0 ||
476a0dca822STakashi Iwai 	    snd_card_info_init() < 0 ||
477a0dca822STakashi Iwai 	    snd_info_minor_register() < 0)
478b591b6e9STakashi Iwai 		goto error;
4791da177e4SLinus Torvalds 	return 0;
480886364f6STakashi Iwai 
481886364f6STakashi Iwai  error:
482644dbd64STakashi Iwai 	snd_info_free_entry(snd_proc_root);
483886364f6STakashi Iwai 	return -ENOMEM;
4841da177e4SLinus Torvalds }
4851da177e4SLinus Torvalds 
4861da177e4SLinus Torvalds int __exit snd_info_done(void)
4871da177e4SLinus Torvalds {
488644dbd64STakashi Iwai 	snd_info_free_entry(snd_proc_root);
4891da177e4SLinus Torvalds 	return 0;
4901da177e4SLinus Torvalds }
4911da177e4SLinus Torvalds 
49229b2625fSTakashi Iwai static void snd_card_id_read(struct snd_info_entry *entry,
49329b2625fSTakashi Iwai 			     struct snd_info_buffer *buffer)
49429b2625fSTakashi Iwai {
49529b2625fSTakashi Iwai 	struct snd_card *card = entry->private_data;
49629b2625fSTakashi Iwai 
49729b2625fSTakashi Iwai 	snd_iprintf(buffer, "%s\n", card->id);
49829b2625fSTakashi Iwai }
49929b2625fSTakashi Iwai 
5001da177e4SLinus Torvalds /*
5011da177e4SLinus Torvalds  * create a card proc file
5021da177e4SLinus Torvalds  * called from init.c
5031da177e4SLinus Torvalds  */
50424c1f931STakashi Iwai int snd_info_card_create(struct snd_card *card)
5051da177e4SLinus Torvalds {
5061da177e4SLinus Torvalds 	char str[8];
50724c1f931STakashi Iwai 	struct snd_info_entry *entry;
5081da177e4SLinus Torvalds 
5097eaa943cSTakashi Iwai 	if (snd_BUG_ON(!card))
5107eaa943cSTakashi Iwai 		return -ENXIO;
5111da177e4SLinus Torvalds 
5121da177e4SLinus Torvalds 	sprintf(str, "card%i", card->number);
513886364f6STakashi Iwai 	entry = create_subdir(card->module, str);
514886364f6STakashi Iwai 	if (!entry)
5151da177e4SLinus Torvalds 		return -ENOMEM;
5161da177e4SLinus Torvalds 	card->proc_root = entry;
51729b2625fSTakashi Iwai 
51829b2625fSTakashi Iwai 	return snd_card_ro_proc_new(card, "id", card, snd_card_id_read);
5191da177e4SLinus Torvalds }
5201da177e4SLinus Torvalds 
5211da177e4SLinus Torvalds /*
5221da177e4SLinus Torvalds  * register the card proc file
5231da177e4SLinus Torvalds  * called from init.c
5242471b6c8STakashi Iwai  * can be called multiple times for reinitialization
5251da177e4SLinus Torvalds  */
52624c1f931STakashi Iwai int snd_info_card_register(struct snd_card *card)
5271da177e4SLinus Torvalds {
5281da177e4SLinus Torvalds 	struct proc_dir_entry *p;
5292471b6c8STakashi Iwai 	int err;
5301da177e4SLinus Torvalds 
5317eaa943cSTakashi Iwai 	if (snd_BUG_ON(!card))
5327eaa943cSTakashi Iwai 		return -ENXIO;
5331da177e4SLinus Torvalds 
534348c5ad5STakashi Iwai 	err = snd_info_register(card->proc_root);
5352471b6c8STakashi Iwai 	if (err < 0)
5362471b6c8STakashi Iwai 		return err;
5372471b6c8STakashi Iwai 
5381da177e4SLinus Torvalds 	if (!strcmp(card->id, card->proc_root->name))
5391da177e4SLinus Torvalds 		return 0;
5401da177e4SLinus Torvalds 
5412471b6c8STakashi Iwai 	if (card->proc_root_link)
5422471b6c8STakashi Iwai 		return 0;
543644dbd64STakashi Iwai 	p = proc_symlink(card->id, snd_proc_root->p, card->proc_root->name);
5442471b6c8STakashi Iwai 	if (!p)
5451da177e4SLinus Torvalds 		return -ENOMEM;
5461da177e4SLinus Torvalds 	card->proc_root_link = p;
5471da177e4SLinus Torvalds 	return 0;
5481da177e4SLinus Torvalds }
5491da177e4SLinus Torvalds 
5501da177e4SLinus Torvalds /*
551c2eb9c4eSJaroslav Kysela  * called on card->id change
552c2eb9c4eSJaroslav Kysela  */
553c2eb9c4eSJaroslav Kysela void snd_info_card_id_change(struct snd_card *card)
554c2eb9c4eSJaroslav Kysela {
555c2eb9c4eSJaroslav Kysela 	mutex_lock(&info_mutex);
556c2eb9c4eSJaroslav Kysela 	if (card->proc_root_link) {
557a8ca16eaSDavid Howells 		proc_remove(card->proc_root_link);
558c2eb9c4eSJaroslav Kysela 		card->proc_root_link = NULL;
559c2eb9c4eSJaroslav Kysela 	}
560c2eb9c4eSJaroslav Kysela 	if (strcmp(card->id, card->proc_root->name))
561c2eb9c4eSJaroslav Kysela 		card->proc_root_link = proc_symlink(card->id,
562644dbd64STakashi Iwai 						    snd_proc_root->p,
563c2eb9c4eSJaroslav Kysela 						    card->proc_root->name);
564c2eb9c4eSJaroslav Kysela 	mutex_unlock(&info_mutex);
565c2eb9c4eSJaroslav Kysela }
566c2eb9c4eSJaroslav Kysela 
567c2eb9c4eSJaroslav Kysela /*
5681da177e4SLinus Torvalds  * de-register the card proc file
5691da177e4SLinus Torvalds  * called from init.c
5701da177e4SLinus Torvalds  */
571746d4a02STakashi Iwai void snd_info_card_disconnect(struct snd_card *card)
5721da177e4SLinus Torvalds {
5737eaa943cSTakashi Iwai 	if (!card)
5747eaa943cSTakashi Iwai 		return;
575746d4a02STakashi Iwai 	mutex_lock(&info_mutex);
576a8ca16eaSDavid Howells 	proc_remove(card->proc_root_link);
5771da177e4SLinus Torvalds 	card->proc_root_link = NULL;
578746d4a02STakashi Iwai 	if (card->proc_root)
579746d4a02STakashi Iwai 		snd_info_disconnect(card->proc_root);
580746d4a02STakashi Iwai 	mutex_unlock(&info_mutex);
5811da177e4SLinus Torvalds }
582746d4a02STakashi Iwai 
583746d4a02STakashi Iwai /*
584746d4a02STakashi Iwai  * release the card proc file resources
585746d4a02STakashi Iwai  * called from init.c
586746d4a02STakashi Iwai  */
587746d4a02STakashi Iwai int snd_info_card_free(struct snd_card *card)
588746d4a02STakashi Iwai {
5897eaa943cSTakashi Iwai 	if (!card)
5907eaa943cSTakashi Iwai 		return 0;
591746d4a02STakashi Iwai 	snd_info_free_entry(card->proc_root);
592746d4a02STakashi Iwai 	card->proc_root = NULL;
5931da177e4SLinus Torvalds 	return 0;
5941da177e4SLinus Torvalds }
5951da177e4SLinus Torvalds 
5961da177e4SLinus Torvalds 
5971da177e4SLinus Torvalds /**
5981da177e4SLinus Torvalds  * snd_info_get_line - read one line from the procfs buffer
5991da177e4SLinus Torvalds  * @buffer: the procfs buffer
6001da177e4SLinus Torvalds  * @line: the buffer to store
601ddc64b27SClemens Ladisch  * @len: the max. buffer size
6021da177e4SLinus Torvalds  *
6031da177e4SLinus Torvalds  * Reads one line from the buffer and stores the string.
6041da177e4SLinus Torvalds  *
605eb7c06e8SYacine Belkadi  * Return: Zero if successful, or 1 if error or EOF.
6061da177e4SLinus Torvalds  */
60724c1f931STakashi Iwai int snd_info_get_line(struct snd_info_buffer *buffer, char *line, int len)
6081da177e4SLinus Torvalds {
6091da177e4SLinus Torvalds 	int c = -1;
6101da177e4SLinus Torvalds 
6110bc0ec90STakashi Iwai 	if (snd_BUG_ON(!buffer || !buffer->buffer))
6120bc0ec90STakashi Iwai 		return 1;
6131da177e4SLinus Torvalds 	if (len <= 0 || buffer->stop || buffer->error)
6141da177e4SLinus Torvalds 		return 1;
6150bc0ec90STakashi Iwai 	while (!buffer->stop) {
6167e4eeec8STakashi Iwai 		c = buffer->buffer[buffer->curr++];
6177e4eeec8STakashi Iwai 		if (buffer->curr >= buffer->size)
6181da177e4SLinus Torvalds 			buffer->stop = 1;
6190bc0ec90STakashi Iwai 		if (c == '\n')
6201da177e4SLinus Torvalds 			break;
621ddc64b27SClemens Ladisch 		if (len > 1) {
6220bc0ec90STakashi Iwai 			len--;
6231da177e4SLinus Torvalds 			*line++ = c;
6241da177e4SLinus Torvalds 		}
6251da177e4SLinus Torvalds 	}
6261da177e4SLinus Torvalds 	*line = '\0';
6271da177e4SLinus Torvalds 	return 0;
6281da177e4SLinus Torvalds }
629c0d3fb39STakashi Iwai EXPORT_SYMBOL(snd_info_get_line);
630c0d3fb39STakashi Iwai 
6311da177e4SLinus Torvalds /**
632856def8aSHenrik Kretzschmar  * snd_info_get_str - parse a string token
6331da177e4SLinus Torvalds  * @dest: the buffer to store the string token
6341da177e4SLinus Torvalds  * @src: the original string
6351da177e4SLinus Torvalds  * @len: the max. length of token - 1
6361da177e4SLinus Torvalds  *
6371da177e4SLinus Torvalds  * Parses the original string and copy a token to the given
6381da177e4SLinus Torvalds  * string buffer.
6391da177e4SLinus Torvalds  *
640eb7c06e8SYacine Belkadi  * Return: The updated pointer of the original string so that
6411da177e4SLinus Torvalds  * it can be used for the next call.
6421da177e4SLinus Torvalds  */
6434f7454a9STakashi Iwai const char *snd_info_get_str(char *dest, const char *src, int len)
6441da177e4SLinus Torvalds {
6451da177e4SLinus Torvalds 	int c;
6461da177e4SLinus Torvalds 
6471da177e4SLinus Torvalds 	while (*src == ' ' || *src == '\t')
6481da177e4SLinus Torvalds 		src++;
6491da177e4SLinus Torvalds 	if (*src == '"' || *src == '\'') {
6501da177e4SLinus Torvalds 		c = *src++;
6511da177e4SLinus Torvalds 		while (--len > 0 && *src && *src != c) {
6521da177e4SLinus Torvalds 			*dest++ = *src++;
6531da177e4SLinus Torvalds 		}
6541da177e4SLinus Torvalds 		if (*src == c)
6551da177e4SLinus Torvalds 			src++;
6561da177e4SLinus Torvalds 	} else {
6571da177e4SLinus Torvalds 		while (--len > 0 && *src && *src != ' ' && *src != '\t') {
6581da177e4SLinus Torvalds 			*dest++ = *src++;
6591da177e4SLinus Torvalds 		}
6601da177e4SLinus Torvalds 	}
6611da177e4SLinus Torvalds 	*dest = 0;
6621da177e4SLinus Torvalds 	while (*src == ' ' || *src == '\t')
6631da177e4SLinus Torvalds 		src++;
6641da177e4SLinus Torvalds 	return src;
6651da177e4SLinus Torvalds }
666c0d3fb39STakashi Iwai EXPORT_SYMBOL(snd_info_get_str);
667c0d3fb39STakashi Iwai 
668c309c467STakashi Iwai /*
6691da177e4SLinus Torvalds  * snd_info_create_entry - create an info entry
6701da177e4SLinus Torvalds  * @name: the proc file name
6718e7ccb7bSTakashi Iwai  * @parent: the parent directory
6721da177e4SLinus Torvalds  *
6731da177e4SLinus Torvalds  * Creates an info entry with the given file name and initializes as
6741da177e4SLinus Torvalds  * the default state.
6751da177e4SLinus Torvalds  *
6761da177e4SLinus Torvalds  * Usually called from other functions such as
6771da177e4SLinus Torvalds  * snd_info_create_card_entry().
6781da177e4SLinus Torvalds  *
679eb7c06e8SYacine Belkadi  * Return: The pointer of the new instance, or %NULL on failure.
6801da177e4SLinus Torvalds  */
6818e7ccb7bSTakashi Iwai static struct snd_info_entry *
682a858ee66STakashi Iwai snd_info_create_entry(const char *name, struct snd_info_entry *parent,
683a858ee66STakashi Iwai 		      struct module *module)
6841da177e4SLinus Torvalds {
68524c1f931STakashi Iwai 	struct snd_info_entry *entry;
686ca2c0966STakashi Iwai 	entry = kzalloc(sizeof(*entry), GFP_KERNEL);
6871da177e4SLinus Torvalds 	if (entry == NULL)
6881da177e4SLinus Torvalds 		return NULL;
689543537bdSPaulo Marques 	entry->name = kstrdup(name, GFP_KERNEL);
6901da177e4SLinus Torvalds 	if (entry->name == NULL) {
6911da177e4SLinus Torvalds 		kfree(entry);
6921da177e4SLinus Torvalds 		return NULL;
6931da177e4SLinus Torvalds 	}
6946a73cf46SJoe Perches 	entry->mode = S_IFREG | 0444;
6951da177e4SLinus Torvalds 	entry->content = SNDRV_INFO_CONTENT_TEXT;
6961a60d4c5SIngo Molnar 	mutex_init(&entry->access);
697746d4a02STakashi Iwai 	INIT_LIST_HEAD(&entry->children);
698746d4a02STakashi Iwai 	INIT_LIST_HEAD(&entry->list);
6998e7ccb7bSTakashi Iwai 	entry->parent = parent;
700a858ee66STakashi Iwai 	entry->module = module;
7018c2f8708STakashi Iwai 	if (parent) {
7028c2f8708STakashi Iwai 		mutex_lock(&parent->access);
7038e7ccb7bSTakashi Iwai 		list_add_tail(&entry->list, &parent->children);
7048c2f8708STakashi Iwai 		mutex_unlock(&parent->access);
7058c2f8708STakashi Iwai 	}
7061da177e4SLinus Torvalds 	return entry;
7071da177e4SLinus Torvalds }
7081da177e4SLinus Torvalds 
7091da177e4SLinus Torvalds /**
7101da177e4SLinus Torvalds  * snd_info_create_module_entry - create an info entry for the given module
7111da177e4SLinus Torvalds  * @module: the module pointer
7121da177e4SLinus Torvalds  * @name: the file name
7131da177e4SLinus Torvalds  * @parent: the parent directory
7141da177e4SLinus Torvalds  *
7151da177e4SLinus Torvalds  * Creates a new info entry and assigns it to the given module.
7161da177e4SLinus Torvalds  *
717eb7c06e8SYacine Belkadi  * Return: The pointer of the new instance, or %NULL on failure.
7181da177e4SLinus Torvalds  */
71924c1f931STakashi Iwai struct snd_info_entry *snd_info_create_module_entry(struct module * module,
7201da177e4SLinus Torvalds 					       const char *name,
72124c1f931STakashi Iwai 					       struct snd_info_entry *parent)
7221da177e4SLinus Torvalds {
7233a554371STakashi Iwai 	if (!parent)
7243a554371STakashi Iwai 		parent = snd_proc_root;
725a858ee66STakashi Iwai 	return snd_info_create_entry(name, parent, module);
7261da177e4SLinus Torvalds }
727c0d3fb39STakashi Iwai EXPORT_SYMBOL(snd_info_create_module_entry);
728c0d3fb39STakashi Iwai 
7291da177e4SLinus Torvalds /**
7301da177e4SLinus Torvalds  * snd_info_create_card_entry - create an info entry for the given card
7311da177e4SLinus Torvalds  * @card: the card instance
7321da177e4SLinus Torvalds  * @name: the file name
7331da177e4SLinus Torvalds  * @parent: the parent directory
7341da177e4SLinus Torvalds  *
7351da177e4SLinus Torvalds  * Creates a new info entry and assigns it to the given card.
7361da177e4SLinus Torvalds  *
737eb7c06e8SYacine Belkadi  * Return: The pointer of the new instance, or %NULL on failure.
7381da177e4SLinus Torvalds  */
73924c1f931STakashi Iwai struct snd_info_entry *snd_info_create_card_entry(struct snd_card *card,
7401da177e4SLinus Torvalds 					     const char *name,
74124c1f931STakashi Iwai 					     struct snd_info_entry * parent)
7421da177e4SLinus Torvalds {
7433a554371STakashi Iwai 	if (!parent)
7443a554371STakashi Iwai 		parent = card->proc_root;
745a858ee66STakashi Iwai 	return snd_info_create_entry(name, parent, card->module);
7461da177e4SLinus Torvalds }
747c0d3fb39STakashi Iwai EXPORT_SYMBOL(snd_info_create_card_entry);
748c0d3fb39STakashi Iwai 
749746d4a02STakashi Iwai static void snd_info_disconnect(struct snd_info_entry *entry)
750746d4a02STakashi Iwai {
75190a409aaSTakashi Iwai 	struct snd_info_entry *p;
752746d4a02STakashi Iwai 
753746d4a02STakashi Iwai 	if (!entry->p)
754746d4a02STakashi Iwai 		return;
75590a409aaSTakashi Iwai 	list_for_each_entry(p, &entry->children, list)
756c560a679STakashi Iwai 		snd_info_disconnect(p);
757a8ca16eaSDavid Howells 	proc_remove(entry->p);
758746d4a02STakashi Iwai 	entry->p = NULL;
759746d4a02STakashi Iwai }
760746d4a02STakashi Iwai 
7611da177e4SLinus Torvalds /**
7621da177e4SLinus Torvalds  * snd_info_free_entry - release the info entry
7631da177e4SLinus Torvalds  * @entry: the info entry
7641da177e4SLinus Torvalds  *
765c560a679STakashi Iwai  * Releases the info entry.
7661da177e4SLinus Torvalds  */
76724c1f931STakashi Iwai void snd_info_free_entry(struct snd_info_entry * entry)
7681da177e4SLinus Torvalds {
769c560a679STakashi Iwai 	struct snd_info_entry *p, *n;
770c560a679STakashi Iwai 
771c560a679STakashi Iwai 	if (!entry)
7721da177e4SLinus Torvalds 		return;
773746d4a02STakashi Iwai 	if (entry->p) {
774746d4a02STakashi Iwai 		mutex_lock(&info_mutex);
775746d4a02STakashi Iwai 		snd_info_disconnect(entry);
776746d4a02STakashi Iwai 		mutex_unlock(&info_mutex);
777746d4a02STakashi Iwai 	}
778c560a679STakashi Iwai 
779c560a679STakashi Iwai 	/* free all children at first */
780c560a679STakashi Iwai 	list_for_each_entry_safe(p, n, &entry->children, list)
781c560a679STakashi Iwai 		snd_info_free_entry(p);
782c560a679STakashi Iwai 
7838c2f8708STakashi Iwai 	p = entry->parent;
7848c2f8708STakashi Iwai 	if (p) {
7858c2f8708STakashi Iwai 		mutex_lock(&p->access);
78690a409aaSTakashi Iwai 		list_del(&entry->list);
7878c2f8708STakashi Iwai 		mutex_unlock(&p->access);
7888c2f8708STakashi Iwai 	}
7891da177e4SLinus Torvalds 	kfree(entry->name);
7901da177e4SLinus Torvalds 	if (entry->private_free)
7911da177e4SLinus Torvalds 		entry->private_free(entry);
7921da177e4SLinus Torvalds 	kfree(entry);
7931da177e4SLinus Torvalds }
794c0d3fb39STakashi Iwai EXPORT_SYMBOL(snd_info_free_entry);
795c0d3fb39STakashi Iwai 
796348c5ad5STakashi Iwai static int __snd_info_register(struct snd_info_entry *entry)
7971da177e4SLinus Torvalds {
7981da177e4SLinus Torvalds 	struct proc_dir_entry *root, *p = NULL;
7991da177e4SLinus Torvalds 
8007eaa943cSTakashi Iwai 	if (snd_BUG_ON(!entry))
8017eaa943cSTakashi Iwai 		return -ENXIO;
802644dbd64STakashi Iwai 	root = entry->parent == NULL ? snd_proc_root->p : entry->parent->p;
8031a60d4c5SIngo Molnar 	mutex_lock(&info_mutex);
804348c5ad5STakashi Iwai 	if (entry->p || !root)
805348c5ad5STakashi Iwai 		goto unlock;
806aee0c612SAl Viro 	if (S_ISDIR(entry->mode)) {
807aee0c612SAl Viro 		p = proc_mkdir_mode(entry->name, entry->mode, root);
8081da177e4SLinus Torvalds 		if (!p) {
8091a60d4c5SIngo Molnar 			mutex_unlock(&info_mutex);
8101da177e4SLinus Torvalds 			return -ENOMEM;
8111da177e4SLinus Torvalds 		}
812aee0c612SAl Viro 	} else {
8134adb7bcbSTakashi Iwai 		const struct file_operations *ops;
8144adb7bcbSTakashi Iwai 		if (entry->content == SNDRV_INFO_CONTENT_DATA)
8154adb7bcbSTakashi Iwai 			ops = &snd_info_entry_operations;
8164adb7bcbSTakashi Iwai 		else
8174adb7bcbSTakashi Iwai 			ops = &snd_info_text_entry_ops;
818aee0c612SAl Viro 		p = proc_create_data(entry->name, entry->mode, root,
8194adb7bcbSTakashi Iwai 				     ops, entry);
820aee0c612SAl Viro 		if (!p) {
821aee0c612SAl Viro 			mutex_unlock(&info_mutex);
822aee0c612SAl Viro 			return -ENOMEM;
823aee0c612SAl Viro 		}
824271a15eaSDavid Howells 		proc_set_size(p, entry->size);
825aee0c612SAl Viro 	}
8261da177e4SLinus Torvalds 	entry->p = p;
827348c5ad5STakashi Iwai  unlock:
8281a60d4c5SIngo Molnar 	mutex_unlock(&info_mutex);
8291da177e4SLinus Torvalds 	return 0;
8301da177e4SLinus Torvalds }
831348c5ad5STakashi Iwai 
832348c5ad5STakashi Iwai /**
833348c5ad5STakashi Iwai  * snd_info_register - register the info entry
834348c5ad5STakashi Iwai  * @entry: the info entry
835348c5ad5STakashi Iwai  *
836348c5ad5STakashi Iwai  * Registers the proc info entry.
837348c5ad5STakashi Iwai  * The all children entries are registered recursively.
838348c5ad5STakashi Iwai  *
839348c5ad5STakashi Iwai  * Return: Zero if successful, or a negative error code on failure.
840348c5ad5STakashi Iwai  */
841348c5ad5STakashi Iwai int snd_info_register(struct snd_info_entry *entry)
842348c5ad5STakashi Iwai {
843348c5ad5STakashi Iwai 	struct snd_info_entry *p;
844348c5ad5STakashi Iwai 	int err;
845348c5ad5STakashi Iwai 
846348c5ad5STakashi Iwai 	if (!entry->p) {
847348c5ad5STakashi Iwai 		err = __snd_info_register(entry);
848348c5ad5STakashi Iwai 		if (err < 0)
849348c5ad5STakashi Iwai 			return err;
850348c5ad5STakashi Iwai 	}
851348c5ad5STakashi Iwai 
852348c5ad5STakashi Iwai 	list_for_each_entry(p, &entry->children, list) {
853348c5ad5STakashi Iwai 		err = snd_info_register(p);
854348c5ad5STakashi Iwai 		if (err < 0)
855348c5ad5STakashi Iwai 			return err;
856348c5ad5STakashi Iwai 	}
857348c5ad5STakashi Iwai 
858348c5ad5STakashi Iwai 	return 0;
859348c5ad5STakashi Iwai }
860c0d3fb39STakashi Iwai EXPORT_SYMBOL(snd_info_register);
861c0d3fb39STakashi Iwai 
8627453e1daSTakashi Iwai /**
8637453e1daSTakashi Iwai  * snd_card_rw_proc_new - Create a read/write text proc file entry for the card
8647453e1daSTakashi Iwai  * @card: the card instance
8657453e1daSTakashi Iwai  * @name: the file name
8667453e1daSTakashi Iwai  * @private_data: the arbitrary private data
8677453e1daSTakashi Iwai  * @read: the read callback
8687453e1daSTakashi Iwai  * @write: the write callback, NULL for read-only
8697453e1daSTakashi Iwai  *
8707453e1daSTakashi Iwai  * This proc file entry will be registered via snd_card_register() call, and
8717453e1daSTakashi Iwai  * it will be removed automatically at the card removal, too.
8727453e1daSTakashi Iwai  */
8737453e1daSTakashi Iwai int snd_card_rw_proc_new(struct snd_card *card, const char *name,
8747453e1daSTakashi Iwai 			 void *private_data,
8757453e1daSTakashi Iwai 			 void (*read)(struct snd_info_entry *,
8767453e1daSTakashi Iwai 				      struct snd_info_buffer *),
8777453e1daSTakashi Iwai 			 void (*write)(struct snd_info_entry *entry,
8787453e1daSTakashi Iwai 				       struct snd_info_buffer *buffer))
8797453e1daSTakashi Iwai {
8807453e1daSTakashi Iwai 	struct snd_info_entry *entry;
8817453e1daSTakashi Iwai 
8827453e1daSTakashi Iwai 	entry = snd_info_create_card_entry(card, name, card->proc_root);
8837453e1daSTakashi Iwai 	if (!entry)
8847453e1daSTakashi Iwai 		return -ENOMEM;
8857453e1daSTakashi Iwai 	snd_info_set_text_ops(entry, private_data, read);
8867453e1daSTakashi Iwai 	if (write) {
8877453e1daSTakashi Iwai 		entry->mode |= 0200;
8887453e1daSTakashi Iwai 		entry->c.text.write = write;
8897453e1daSTakashi Iwai 	}
8907453e1daSTakashi Iwai 	return 0;
8917453e1daSTakashi Iwai }
8927453e1daSTakashi Iwai EXPORT_SYMBOL_GPL(snd_card_rw_proc_new);
8937453e1daSTakashi Iwai 
8941da177e4SLinus Torvalds /*
8951da177e4SLinus Torvalds 
8961da177e4SLinus Torvalds  */
8971da177e4SLinus Torvalds 
89824c1f931STakashi Iwai static void snd_info_version_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
8991da177e4SLinus Torvalds {
9001da177e4SLinus Torvalds 	snd_iprintf(buffer,
90142662748SJaroslav Kysela 		    "Advanced Linux Sound Architecture Driver Version k%s.\n",
90242662748SJaroslav Kysela 		    init_utsname()->release);
9031da177e4SLinus Torvalds }
9041da177e4SLinus Torvalds 
9051da177e4SLinus Torvalds static int __init snd_info_version_init(void)
9061da177e4SLinus Torvalds {
90724c1f931STakashi Iwai 	struct snd_info_entry *entry;
9081da177e4SLinus Torvalds 
9091da177e4SLinus Torvalds 	entry = snd_info_create_module_entry(THIS_MODULE, "version", NULL);
9101da177e4SLinus Torvalds 	if (entry == NULL)
9111da177e4SLinus Torvalds 		return -ENOMEM;
9121da177e4SLinus Torvalds 	entry->c.text.read = snd_info_version_read;
913b591b6e9STakashi Iwai 	return snd_info_register(entry); /* freed in error path */
9141da177e4SLinus Torvalds }
915