| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1 | /* | 
 | 2 |  *  Initialization routines | 
 | 3 |  *  Copyright (c) by Jaroslav Kysela <perex@suse.cz> | 
 | 4 |  * | 
 | 5 |  * | 
 | 6 |  *   This program is free software; you can redistribute it and/or modify | 
 | 7 |  *   it under the terms of the GNU General Public License as published by | 
 | 8 |  *   the Free Software Foundation; either version 2 of the License, or | 
 | 9 |  *   (at your option) any later version. | 
 | 10 |  * | 
 | 11 |  *   This program is distributed in the hope that it will be useful, | 
 | 12 |  *   but WITHOUT ANY WARRANTY; without even the implied warranty of | 
 | 13 |  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
 | 14 |  *   GNU General Public License for more details. | 
 | 15 |  * | 
 | 16 |  *   You should have received a copy of the GNU General Public License | 
 | 17 |  *   along with this program; if not, write to the Free Software | 
 | 18 |  *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA | 
 | 19 |  * | 
 | 20 |  */ | 
 | 21 |  | 
 | 22 | #include <sound/driver.h> | 
 | 23 | #include <linux/init.h> | 
 | 24 | #include <linux/sched.h> | 
 | 25 | #include <linux/file.h> | 
 | 26 | #include <linux/slab.h> | 
 | 27 | #include <linux/time.h> | 
 | 28 | #include <linux/ctype.h> | 
 | 29 | #include <linux/pci.h> | 
 | 30 | #include <linux/pm.h> | 
| Russell King | d052d1b | 2005-10-29 19:07:23 +0100 | [diff] [blame] | 31 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 32 | #include <sound/core.h> | 
 | 33 | #include <sound/control.h> | 
 | 34 | #include <sound/info.h> | 
 | 35 |  | 
 | 36 | struct snd_shutdown_f_ops { | 
 | 37 | 	struct file_operations f_ops; | 
 | 38 | 	struct snd_shutdown_f_ops *next; | 
 | 39 | }; | 
 | 40 |  | 
 | 41 | unsigned int snd_cards_lock = 0;	/* locked for registering/using */ | 
| Takashi Iwai | 512bbd6 | 2005-11-17 13:51:18 +0100 | [diff] [blame] | 42 | struct snd_card *snd_cards[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = NULL}; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 43 | DEFINE_RWLOCK(snd_card_rwlock); | 
 | 44 |  | 
 | 45 | #if defined(CONFIG_SND_MIXER_OSS) || defined(CONFIG_SND_MIXER_OSS_MODULE) | 
| Takashi Iwai | 512bbd6 | 2005-11-17 13:51:18 +0100 | [diff] [blame] | 46 | int (*snd_mixer_oss_notify_callback)(struct snd_card *card, int free_flag); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 47 | #endif | 
 | 48 |  | 
| Takashi Iwai | e28563c | 2005-12-01 10:42:42 +0100 | [diff] [blame^] | 49 | #ifdef CONFIG_PROC_FS | 
| Takashi Iwai | 512bbd6 | 2005-11-17 13:51:18 +0100 | [diff] [blame] | 50 | static void snd_card_id_read(struct snd_info_entry *entry, | 
 | 51 | 			     struct snd_info_buffer *buffer) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 52 | { | 
 | 53 | 	snd_iprintf(buffer, "%s\n", entry->card->id); | 
 | 54 | } | 
 | 55 |  | 
| Takashi Iwai | e28563c | 2005-12-01 10:42:42 +0100 | [diff] [blame^] | 56 | static inline int init_info_for_card(struct snd_card *card) | 
 | 57 | { | 
 | 58 | 	int err; | 
 | 59 | 	struct snd_info_entry *entry; | 
 | 60 |  | 
 | 61 | 	if ((err = snd_info_card_register(card)) < 0) { | 
 | 62 | 		snd_printd("unable to create card info\n"); | 
 | 63 | 		return err; | 
 | 64 | 	} | 
 | 65 | 	if ((entry = snd_info_create_card_entry(card, "id", card->proc_root)) == NULL) { | 
 | 66 | 		snd_printd("unable to create card entry\n"); | 
 | 67 | 		return err; | 
 | 68 | 	} | 
 | 69 | 	entry->c.text.read_size = PAGE_SIZE; | 
 | 70 | 	entry->c.text.read = snd_card_id_read; | 
 | 71 | 	if (snd_info_register(entry) < 0) { | 
 | 72 | 		snd_info_free_entry(entry); | 
 | 73 | 		entry = NULL; | 
 | 74 | 	} | 
 | 75 | 	card->proc_id = entry; | 
 | 76 | 	return 0; | 
 | 77 | } | 
 | 78 | #else /* !CONFIG_PROC_FS */ | 
 | 79 | #define init_info_for_card(card) | 
 | 80 | #endif | 
 | 81 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 82 | static void snd_card_free_thread(void * __card); | 
 | 83 |  | 
 | 84 | /** | 
 | 85 |  *  snd_card_new - create and initialize a soundcard structure | 
 | 86 |  *  @idx: card index (address) [0 ... (SNDRV_CARDS-1)] | 
 | 87 |  *  @xid: card identification (ASCII string) | 
 | 88 |  *  @module: top level module for locking | 
 | 89 |  *  @extra_size: allocate this extra size after the main soundcard structure | 
 | 90 |  * | 
 | 91 |  *  Creates and initializes a soundcard structure. | 
 | 92 |  * | 
| Takashi Iwai | 512bbd6 | 2005-11-17 13:51:18 +0100 | [diff] [blame] | 93 |  *  Returns kmallocated snd_card structure. Creates the ALSA control interface | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 94 |  *  (which is blocked until snd_card_register function is called). | 
 | 95 |  */ | 
| Takashi Iwai | 512bbd6 | 2005-11-17 13:51:18 +0100 | [diff] [blame] | 96 | struct snd_card *snd_card_new(int idx, const char *xid, | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 97 | 			 struct module *module, int extra_size) | 
 | 98 | { | 
| Takashi Iwai | 512bbd6 | 2005-11-17 13:51:18 +0100 | [diff] [blame] | 99 | 	struct snd_card *card; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 100 | 	int err; | 
 | 101 |  | 
 | 102 | 	if (extra_size < 0) | 
 | 103 | 		extra_size = 0; | 
| Takashi Iwai | ca2c096 | 2005-09-09 14:20:23 +0200 | [diff] [blame] | 104 | 	card = kzalloc(sizeof(*card) + extra_size, GFP_KERNEL); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 105 | 	if (card == NULL) | 
 | 106 | 		return NULL; | 
 | 107 | 	if (xid) { | 
 | 108 | 		if (!snd_info_check_reserved_words(xid)) | 
 | 109 | 			goto __error; | 
 | 110 | 		strlcpy(card->id, xid, sizeof(card->id)); | 
 | 111 | 	} | 
 | 112 | 	err = 0; | 
 | 113 | 	write_lock(&snd_card_rwlock); | 
 | 114 | 	if (idx < 0) { | 
 | 115 | 		int idx2; | 
 | 116 | 		for (idx2 = 0; idx2 < SNDRV_CARDS; idx2++) | 
 | 117 | 			if (~snd_cards_lock & idx & 1<<idx2) { | 
 | 118 | 				idx = idx2; | 
 | 119 | 				if (idx >= snd_ecards_limit) | 
 | 120 | 					snd_ecards_limit = idx + 1; | 
 | 121 | 				break; | 
 | 122 | 			} | 
 | 123 | 	} else if (idx < snd_ecards_limit) { | 
 | 124 | 		if (snd_cards_lock & (1 << idx)) | 
 | 125 | 			err = -ENODEV;	/* invalid */ | 
 | 126 | 	} else if (idx < SNDRV_CARDS) | 
 | 127 | 		snd_ecards_limit = idx + 1; /* increase the limit */ | 
 | 128 | 	else | 
 | 129 | 		err = -ENODEV; | 
 | 130 | 	if (idx < 0 || err < 0) { | 
 | 131 | 		write_unlock(&snd_card_rwlock); | 
 | 132 | 		snd_printk(KERN_ERR "cannot find the slot for index %d (range 0-%i)\n", idx, snd_ecards_limit - 1); | 
 | 133 | 		goto __error; | 
 | 134 | 	} | 
 | 135 | 	snd_cards_lock |= 1 << idx;		/* lock it */ | 
 | 136 | 	write_unlock(&snd_card_rwlock); | 
 | 137 | 	card->number = idx; | 
 | 138 | 	card->module = module; | 
 | 139 | 	INIT_LIST_HEAD(&card->devices); | 
 | 140 | 	init_rwsem(&card->controls_rwsem); | 
 | 141 | 	rwlock_init(&card->ctl_files_rwlock); | 
 | 142 | 	INIT_LIST_HEAD(&card->controls); | 
 | 143 | 	INIT_LIST_HEAD(&card->ctl_files); | 
 | 144 | 	spin_lock_init(&card->files_lock); | 
 | 145 | 	init_waitqueue_head(&card->shutdown_sleep); | 
 | 146 | 	INIT_WORK(&card->free_workq, snd_card_free_thread, card); | 
 | 147 | #ifdef CONFIG_PM | 
 | 148 | 	init_MUTEX(&card->power_lock); | 
 | 149 | 	init_waitqueue_head(&card->power_sleep); | 
 | 150 | #endif | 
 | 151 | 	/* the control interface cannot be accessed from the user space until */ | 
 | 152 | 	/* snd_cards_bitmask and snd_cards are set with snd_card_register */ | 
 | 153 | 	if ((err = snd_ctl_create(card)) < 0) { | 
 | 154 | 		snd_printd("unable to register control minors\n"); | 
 | 155 | 		goto __error; | 
 | 156 | 	} | 
 | 157 | 	if ((err = snd_info_card_create(card)) < 0) { | 
 | 158 | 		snd_printd("unable to create card info\n"); | 
 | 159 | 		goto __error_ctl; | 
 | 160 | 	} | 
 | 161 | 	if (extra_size > 0) | 
| Takashi Iwai | 512bbd6 | 2005-11-17 13:51:18 +0100 | [diff] [blame] | 162 | 		card->private_data = (char *)card + sizeof(struct snd_card); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 163 | 	return card; | 
 | 164 |  | 
 | 165 |       __error_ctl: | 
 | 166 | 	snd_device_free_all(card, SNDRV_DEV_CMD_PRE); | 
 | 167 |       __error: | 
 | 168 | 	kfree(card); | 
 | 169 |       	return NULL; | 
 | 170 | } | 
 | 171 |  | 
 | 172 | static unsigned int snd_disconnect_poll(struct file * file, poll_table * wait) | 
 | 173 | { | 
 | 174 | 	return POLLERR | POLLNVAL; | 
 | 175 | } | 
 | 176 |  | 
 | 177 | /** | 
 | 178 |  *  snd_card_disconnect - disconnect all APIs from the file-operations (user space) | 
 | 179 |  *  @card: soundcard structure | 
 | 180 |  * | 
 | 181 |  *  Disconnects all APIs from the file-operations (user space). | 
 | 182 |  * | 
 | 183 |  *  Returns zero, otherwise a negative error code. | 
 | 184 |  * | 
 | 185 |  *  Note: The current implementation replaces all active file->f_op with special | 
 | 186 |  *        dummy file operations (they do nothing except release). | 
 | 187 |  */ | 
| Takashi Iwai | 512bbd6 | 2005-11-17 13:51:18 +0100 | [diff] [blame] | 188 | int snd_card_disconnect(struct snd_card *card) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 189 | { | 
 | 190 | 	struct snd_monitor_file *mfile; | 
 | 191 | 	struct file *file; | 
 | 192 | 	struct snd_shutdown_f_ops *s_f_ops; | 
 | 193 | 	struct file_operations *f_ops, *old_f_ops; | 
 | 194 | 	int err; | 
 | 195 |  | 
 | 196 | 	spin_lock(&card->files_lock); | 
 | 197 | 	if (card->shutdown) { | 
 | 198 | 		spin_unlock(&card->files_lock); | 
 | 199 | 		return 0; | 
 | 200 | 	} | 
 | 201 | 	card->shutdown = 1; | 
 | 202 | 	spin_unlock(&card->files_lock); | 
 | 203 |  | 
 | 204 | 	/* phase 1: disable fops (user space) operations for ALSA API */ | 
 | 205 | 	write_lock(&snd_card_rwlock); | 
 | 206 | 	snd_cards[card->number] = NULL; | 
 | 207 | 	write_unlock(&snd_card_rwlock); | 
 | 208 | 	 | 
 | 209 | 	/* phase 2: replace file->f_op with special dummy operations */ | 
 | 210 | 	 | 
 | 211 | 	spin_lock(&card->files_lock); | 
 | 212 | 	mfile = card->files; | 
 | 213 | 	while (mfile) { | 
 | 214 | 		file = mfile->file; | 
 | 215 |  | 
 | 216 | 		/* it's critical part, use endless loop */ | 
 | 217 | 		/* we have no room to fail */ | 
 | 218 | 		s_f_ops = kmalloc(sizeof(struct snd_shutdown_f_ops), GFP_ATOMIC); | 
 | 219 | 		if (s_f_ops == NULL) | 
 | 220 | 			panic("Atomic allocation failed for snd_shutdown_f_ops!"); | 
 | 221 |  | 
 | 222 | 		f_ops = &s_f_ops->f_ops; | 
 | 223 |  | 
 | 224 | 		memset(f_ops, 0, sizeof(*f_ops)); | 
 | 225 | 		f_ops->owner = file->f_op->owner; | 
 | 226 | 		f_ops->release = file->f_op->release; | 
 | 227 | 		f_ops->poll = snd_disconnect_poll; | 
 | 228 |  | 
 | 229 | 		s_f_ops->next = card->s_f_ops; | 
 | 230 | 		card->s_f_ops = s_f_ops; | 
 | 231 | 		 | 
 | 232 | 		f_ops = fops_get(f_ops); | 
 | 233 |  | 
 | 234 | 		old_f_ops = file->f_op; | 
 | 235 | 		file->f_op = f_ops;	/* must be atomic */ | 
 | 236 | 		fops_put(old_f_ops); | 
 | 237 | 		 | 
 | 238 | 		mfile = mfile->next; | 
 | 239 | 	} | 
 | 240 | 	spin_unlock(&card->files_lock);	 | 
 | 241 |  | 
 | 242 | 	/* phase 3: notify all connected devices about disconnection */ | 
 | 243 | 	/* at this point, they cannot respond to any calls except release() */ | 
 | 244 |  | 
 | 245 | #if defined(CONFIG_SND_MIXER_OSS) || defined(CONFIG_SND_MIXER_OSS_MODULE) | 
 | 246 | 	if (snd_mixer_oss_notify_callback) | 
 | 247 | 		snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_DISCONNECT); | 
 | 248 | #endif | 
 | 249 |  | 
 | 250 | 	/* notify all devices that we are disconnected */ | 
 | 251 | 	err = snd_device_disconnect_all(card); | 
 | 252 | 	if (err < 0) | 
 | 253 | 		snd_printk(KERN_ERR "not all devices for card %i can be disconnected\n", card->number); | 
 | 254 |  | 
 | 255 | 	return 0;	 | 
 | 256 | } | 
 | 257 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 258 | /** | 
 | 259 |  *  snd_card_free - frees given soundcard structure | 
 | 260 |  *  @card: soundcard structure | 
 | 261 |  * | 
 | 262 |  *  This function releases the soundcard structure and the all assigned | 
 | 263 |  *  devices automatically.  That is, you don't have to release the devices | 
 | 264 |  *  by yourself. | 
 | 265 |  * | 
 | 266 |  *  Returns zero. Frees all associated devices and frees the control | 
 | 267 |  *  interface associated to given soundcard. | 
 | 268 |  */ | 
| Takashi Iwai | 512bbd6 | 2005-11-17 13:51:18 +0100 | [diff] [blame] | 269 | int snd_card_free(struct snd_card *card) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 270 | { | 
 | 271 | 	struct snd_shutdown_f_ops *s_f_ops; | 
 | 272 |  | 
 | 273 | 	if (card == NULL) | 
 | 274 | 		return -EINVAL; | 
 | 275 | 	write_lock(&snd_card_rwlock); | 
 | 276 | 	snd_cards[card->number] = NULL; | 
 | 277 | 	write_unlock(&snd_card_rwlock); | 
 | 278 |  | 
 | 279 | #ifdef CONFIG_PM | 
 | 280 | 	wake_up(&card->power_sleep); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 281 | #endif | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 282 | 	/* wait, until all devices are ready for the free operation */ | 
 | 283 | 	wait_event(card->shutdown_sleep, card->files == NULL); | 
 | 284 |  | 
 | 285 | #if defined(CONFIG_SND_MIXER_OSS) || defined(CONFIG_SND_MIXER_OSS_MODULE) | 
 | 286 | 	if (snd_mixer_oss_notify_callback) | 
 | 287 | 		snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_FREE); | 
 | 288 | #endif | 
 | 289 | 	if (snd_device_free_all(card, SNDRV_DEV_CMD_PRE) < 0) { | 
 | 290 | 		snd_printk(KERN_ERR "unable to free all devices (pre)\n"); | 
 | 291 | 		/* Fatal, but this situation should never occur */ | 
 | 292 | 	} | 
 | 293 | 	if (snd_device_free_all(card, SNDRV_DEV_CMD_NORMAL) < 0) { | 
 | 294 | 		snd_printk(KERN_ERR "unable to free all devices (normal)\n"); | 
 | 295 | 		/* Fatal, but this situation should never occur */ | 
 | 296 | 	} | 
 | 297 | 	if (snd_device_free_all(card, SNDRV_DEV_CMD_POST) < 0) { | 
 | 298 | 		snd_printk(KERN_ERR "unable to free all devices (post)\n"); | 
 | 299 | 		/* Fatal, but this situation should never occur */ | 
 | 300 | 	} | 
 | 301 | 	if (card->private_free) | 
 | 302 | 		card->private_free(card); | 
| Takashi Iwai | e28563c | 2005-12-01 10:42:42 +0100 | [diff] [blame^] | 303 | 	snd_info_unregister(card->proc_id); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 304 | 	if (snd_info_card_free(card) < 0) { | 
 | 305 | 		snd_printk(KERN_WARNING "unable to free card info\n"); | 
 | 306 | 		/* Not fatal error */ | 
 | 307 | 	} | 
 | 308 | 	while (card->s_f_ops) { | 
 | 309 | 		s_f_ops = card->s_f_ops; | 
 | 310 | 		card->s_f_ops = s_f_ops->next; | 
 | 311 | 		kfree(s_f_ops); | 
 | 312 | 	} | 
 | 313 | 	write_lock(&snd_card_rwlock); | 
 | 314 | 	snd_cards_lock &= ~(1 << card->number); | 
 | 315 | 	write_unlock(&snd_card_rwlock); | 
 | 316 | 	kfree(card); | 
 | 317 | 	return 0; | 
 | 318 | } | 
 | 319 |  | 
 | 320 | static void snd_card_free_thread(void * __card) | 
 | 321 | { | 
| Takashi Iwai | 512bbd6 | 2005-11-17 13:51:18 +0100 | [diff] [blame] | 322 | 	struct snd_card *card = __card; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 323 | 	struct module * module = card->module; | 
 | 324 |  | 
 | 325 | 	if (!try_module_get(module)) { | 
 | 326 | 		snd_printk(KERN_ERR "unable to lock toplevel module for card %i in free thread\n", card->number); | 
 | 327 | 		module = NULL; | 
 | 328 | 	} | 
 | 329 |  | 
 | 330 | 	snd_card_free(card); | 
 | 331 |  | 
 | 332 | 	module_put(module); | 
 | 333 | } | 
 | 334 |  | 
 | 335 | /** | 
 | 336 |  *  snd_card_free_in_thread - call snd_card_free() in thread | 
 | 337 |  *  @card: soundcard structure | 
 | 338 |  * | 
 | 339 |  *  This function schedules the call of snd_card_free() function in a | 
 | 340 |  *  work queue.  When all devices are released (non-busy), the work | 
 | 341 |  *  is woken up and calls snd_card_free(). | 
 | 342 |  * | 
 | 343 |  *  When a card can be disconnected at any time by hotplug service, | 
 | 344 |  *  this function should be used in disconnect (or detach) callback | 
 | 345 |  *  instead of calling snd_card_free() directly. | 
 | 346 |  *   | 
 | 347 |  *  Returns - zero otherwise a negative error code if the start of thread failed. | 
 | 348 |  */ | 
| Takashi Iwai | 512bbd6 | 2005-11-17 13:51:18 +0100 | [diff] [blame] | 349 | int snd_card_free_in_thread(struct snd_card *card) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 350 | { | 
 | 351 | 	if (card->files == NULL) { | 
 | 352 | 		snd_card_free(card); | 
 | 353 | 		return 0; | 
 | 354 | 	} | 
 | 355 |  | 
 | 356 | 	if (schedule_work(&card->free_workq)) | 
 | 357 | 		return 0; | 
 | 358 |  | 
 | 359 | 	snd_printk(KERN_ERR "schedule_work() failed in snd_card_free_in_thread for card %i\n", card->number); | 
 | 360 | 	/* try to free the structure immediately */ | 
 | 361 | 	snd_card_free(card); | 
 | 362 | 	return -EFAULT; | 
 | 363 | } | 
 | 364 |  | 
| Takashi Iwai | 512bbd6 | 2005-11-17 13:51:18 +0100 | [diff] [blame] | 365 | static void choose_default_id(struct snd_card *card) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 366 | { | 
| Clemens Ladisch | d001544 | 2005-11-20 14:09:05 +0100 | [diff] [blame] | 367 | 	int i, len, idx_flag = 0, loops = SNDRV_CARDS; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 368 | 	char *id, *spos; | 
 | 369 | 	 | 
 | 370 | 	id = spos = card->shortname;	 | 
 | 371 | 	while (*id != '\0') { | 
 | 372 | 		if (*id == ' ') | 
 | 373 | 			spos = id + 1; | 
 | 374 | 		id++; | 
 | 375 | 	} | 
 | 376 | 	id = card->id; | 
 | 377 | 	while (*spos != '\0' && !isalnum(*spos)) | 
 | 378 | 		spos++; | 
 | 379 | 	if (isdigit(*spos)) | 
 | 380 | 		*id++ = isalpha(card->shortname[0]) ? card->shortname[0] : 'D'; | 
 | 381 | 	while (*spos != '\0' && (size_t)(id - card->id) < sizeof(card->id) - 1) { | 
 | 382 | 		if (isalnum(*spos)) | 
 | 383 | 			*id++ = *spos; | 
 | 384 | 		spos++; | 
 | 385 | 	} | 
 | 386 | 	*id = '\0'; | 
 | 387 |  | 
 | 388 | 	id = card->id; | 
 | 389 | 	 | 
 | 390 | 	if (*id == '\0') | 
 | 391 | 		strcpy(id, "default"); | 
 | 392 |  | 
 | 393 | 	while (1) { | 
 | 394 | 	      	if (loops-- == 0) { | 
 | 395 |       			snd_printk(KERN_ERR "unable to choose default card id (%s)\n", id); | 
 | 396 |       			strcpy(card->id, card->proc_root->name); | 
 | 397 |       			return; | 
 | 398 |       		} | 
 | 399 | 	      	if (!snd_info_check_reserved_words(id)) | 
 | 400 |       			goto __change; | 
 | 401 | 		for (i = 0; i < snd_ecards_limit; i++) { | 
 | 402 | 			if (snd_cards[i] && !strcmp(snd_cards[i]->id, id)) | 
 | 403 | 				goto __change; | 
 | 404 | 		} | 
 | 405 | 		break; | 
 | 406 |  | 
 | 407 | 	      __change: | 
 | 408 | 		len = strlen(id); | 
| Clemens Ladisch | d001544 | 2005-11-20 14:09:05 +0100 | [diff] [blame] | 409 | 		if (idx_flag) { | 
 | 410 | 			if (id[len-1] != '9') | 
 | 411 | 				id[len-1]++; | 
 | 412 | 			else | 
 | 413 | 				id[len-1] = 'A'; | 
 | 414 | 		} else if ((size_t)len <= sizeof(card->id) - 3) { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 415 | 			strcat(id, "_1"); | 
 | 416 | 			idx_flag++; | 
 | 417 | 		} else { | 
 | 418 | 			spos = id + len - 2; | 
 | 419 | 			if ((size_t)len <= sizeof(card->id) - 2) | 
 | 420 | 				spos++; | 
 | 421 | 			*spos++ = '_'; | 
 | 422 | 			*spos++ = '1'; | 
 | 423 | 			*spos++ = '\0'; | 
 | 424 | 			idx_flag++; | 
 | 425 | 		} | 
 | 426 | 	} | 
 | 427 | } | 
 | 428 |  | 
 | 429 | /** | 
 | 430 |  *  snd_card_register - register the soundcard | 
 | 431 |  *  @card: soundcard structure | 
 | 432 |  * | 
 | 433 |  *  This function registers all the devices assigned to the soundcard. | 
 | 434 |  *  Until calling this, the ALSA control interface is blocked from the | 
 | 435 |  *  external accesses.  Thus, you should call this function at the end | 
 | 436 |  *  of the initialization of the card. | 
 | 437 |  * | 
 | 438 |  *  Returns zero otherwise a negative error code if the registrain failed. | 
 | 439 |  */ | 
| Takashi Iwai | 512bbd6 | 2005-11-17 13:51:18 +0100 | [diff] [blame] | 440 | int snd_card_register(struct snd_card *card) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 441 | { | 
 | 442 | 	int err; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 443 |  | 
| Takashi Iwai | 7c22f1a | 2005-10-10 11:46:31 +0200 | [diff] [blame] | 444 | 	snd_assert(card != NULL, return -EINVAL); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 445 | 	if ((err = snd_device_register_all(card)) < 0) | 
 | 446 | 		return err; | 
 | 447 | 	write_lock(&snd_card_rwlock); | 
 | 448 | 	if (snd_cards[card->number]) { | 
 | 449 | 		/* already registered */ | 
 | 450 | 		write_unlock(&snd_card_rwlock); | 
 | 451 | 		return 0; | 
 | 452 | 	} | 
 | 453 | 	if (card->id[0] == '\0') | 
 | 454 | 		choose_default_id(card); | 
 | 455 | 	snd_cards[card->number] = card; | 
 | 456 | 	write_unlock(&snd_card_rwlock); | 
| Takashi Iwai | e28563c | 2005-12-01 10:42:42 +0100 | [diff] [blame^] | 457 | 	init_info_for_card(card); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 458 | #if defined(CONFIG_SND_MIXER_OSS) || defined(CONFIG_SND_MIXER_OSS_MODULE) | 
 | 459 | 	if (snd_mixer_oss_notify_callback) | 
 | 460 | 		snd_mixer_oss_notify_callback(card, SND_MIXER_OSS_NOTIFY_REGISTER); | 
 | 461 | #endif | 
 | 462 | 	return 0; | 
 | 463 | } | 
 | 464 |  | 
| Takashi Iwai | e28563c | 2005-12-01 10:42:42 +0100 | [diff] [blame^] | 465 | #ifdef CONFIG_PROC_FS | 
| Takashi Iwai | 512bbd6 | 2005-11-17 13:51:18 +0100 | [diff] [blame] | 466 | static struct snd_info_entry *snd_card_info_entry = NULL; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 467 |  | 
| Takashi Iwai | a381a7a | 2005-11-17 15:55:49 +0100 | [diff] [blame] | 468 | static void snd_card_info_read(struct snd_info_entry *entry, | 
 | 469 | 			       struct snd_info_buffer *buffer) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 470 | { | 
 | 471 | 	int idx, count; | 
| Takashi Iwai | 512bbd6 | 2005-11-17 13:51:18 +0100 | [diff] [blame] | 472 | 	struct snd_card *card; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 473 |  | 
 | 474 | 	for (idx = count = 0; idx < SNDRV_CARDS; idx++) { | 
 | 475 | 		read_lock(&snd_card_rwlock); | 
 | 476 | 		if ((card = snd_cards[idx]) != NULL) { | 
 | 477 | 			count++; | 
| Clemens Ladisch | d001544 | 2005-11-20 14:09:05 +0100 | [diff] [blame] | 478 | 			snd_iprintf(buffer, "%2i [%-15s]: %s - %s\n", | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 479 | 					idx, | 
 | 480 | 					card->id, | 
 | 481 | 					card->driver, | 
 | 482 | 					card->shortname); | 
| Clemens Ladisch | d001544 | 2005-11-20 14:09:05 +0100 | [diff] [blame] | 483 | 			snd_iprintf(buffer, "                      %s\n", | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 484 | 					card->longname); | 
 | 485 | 		} | 
 | 486 | 		read_unlock(&snd_card_rwlock); | 
 | 487 | 	} | 
 | 488 | 	if (!count) | 
 | 489 | 		snd_iprintf(buffer, "--- no soundcards ---\n"); | 
 | 490 | } | 
 | 491 |  | 
| Takashi Iwai | e28563c | 2005-12-01 10:42:42 +0100 | [diff] [blame^] | 492 | #ifdef CONFIG_SND_OSSEMUL | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 493 |  | 
| Takashi Iwai | 512bbd6 | 2005-11-17 13:51:18 +0100 | [diff] [blame] | 494 | void snd_card_info_read_oss(struct snd_info_buffer *buffer) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 495 | { | 
 | 496 | 	int idx, count; | 
| Takashi Iwai | 512bbd6 | 2005-11-17 13:51:18 +0100 | [diff] [blame] | 497 | 	struct snd_card *card; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 498 |  | 
 | 499 | 	for (idx = count = 0; idx < SNDRV_CARDS; idx++) { | 
 | 500 | 		read_lock(&snd_card_rwlock); | 
 | 501 | 		if ((card = snd_cards[idx]) != NULL) { | 
 | 502 | 			count++; | 
 | 503 | 			snd_iprintf(buffer, "%s\n", card->longname); | 
 | 504 | 		} | 
 | 505 | 		read_unlock(&snd_card_rwlock); | 
 | 506 | 	} | 
 | 507 | 	if (!count) { | 
 | 508 | 		snd_iprintf(buffer, "--- no soundcards ---\n"); | 
 | 509 | 	} | 
 | 510 | } | 
 | 511 |  | 
 | 512 | #endif | 
 | 513 |  | 
 | 514 | #ifdef MODULE | 
| Takashi Iwai | 512bbd6 | 2005-11-17 13:51:18 +0100 | [diff] [blame] | 515 | static struct snd_info_entry *snd_card_module_info_entry; | 
 | 516 | static void snd_card_module_info_read(struct snd_info_entry *entry, | 
 | 517 | 				      struct snd_info_buffer *buffer) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 518 | { | 
 | 519 | 	int idx; | 
| Takashi Iwai | 512bbd6 | 2005-11-17 13:51:18 +0100 | [diff] [blame] | 520 | 	struct snd_card *card; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 521 |  | 
 | 522 | 	for (idx = 0; idx < SNDRV_CARDS; idx++) { | 
 | 523 | 		read_lock(&snd_card_rwlock); | 
 | 524 | 		if ((card = snd_cards[idx]) != NULL) | 
| Clemens Ladisch | d001544 | 2005-11-20 14:09:05 +0100 | [diff] [blame] | 525 | 			snd_iprintf(buffer, "%2i %s\n", | 
 | 526 | 				    idx, card->module->name); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 527 | 		read_unlock(&snd_card_rwlock); | 
 | 528 | 	} | 
 | 529 | } | 
 | 530 | #endif | 
 | 531 |  | 
 | 532 | int __init snd_card_info_init(void) | 
 | 533 | { | 
| Takashi Iwai | 512bbd6 | 2005-11-17 13:51:18 +0100 | [diff] [blame] | 534 | 	struct snd_info_entry *entry; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 535 |  | 
 | 536 | 	entry = snd_info_create_module_entry(THIS_MODULE, "cards", NULL); | 
| Takashi Iwai | 7c22f1a | 2005-10-10 11:46:31 +0200 | [diff] [blame] | 537 | 	if (! entry) | 
 | 538 | 		return -ENOMEM; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 539 | 	entry->c.text.read_size = PAGE_SIZE; | 
 | 540 | 	entry->c.text.read = snd_card_info_read; | 
 | 541 | 	if (snd_info_register(entry) < 0) { | 
 | 542 | 		snd_info_free_entry(entry); | 
 | 543 | 		return -ENOMEM; | 
 | 544 | 	} | 
 | 545 | 	snd_card_info_entry = entry; | 
 | 546 |  | 
 | 547 | #ifdef MODULE | 
 | 548 | 	entry = snd_info_create_module_entry(THIS_MODULE, "modules", NULL); | 
 | 549 | 	if (entry) { | 
 | 550 | 		entry->c.text.read_size = PAGE_SIZE; | 
 | 551 | 		entry->c.text.read = snd_card_module_info_read; | 
 | 552 | 		if (snd_info_register(entry) < 0) | 
 | 553 | 			snd_info_free_entry(entry); | 
 | 554 | 		else | 
 | 555 | 			snd_card_module_info_entry = entry; | 
 | 556 | 	} | 
 | 557 | #endif | 
 | 558 |  | 
 | 559 | 	return 0; | 
 | 560 | } | 
 | 561 |  | 
 | 562 | int __exit snd_card_info_done(void) | 
 | 563 | { | 
| Takashi Iwai | e28563c | 2005-12-01 10:42:42 +0100 | [diff] [blame^] | 564 | 	snd_info_unregister(snd_card_info_entry); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 565 | #ifdef MODULE | 
| Takashi Iwai | e28563c | 2005-12-01 10:42:42 +0100 | [diff] [blame^] | 566 | 	snd_info_unregister(snd_card_module_info_entry); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 567 | #endif | 
 | 568 | 	return 0; | 
 | 569 | } | 
 | 570 |  | 
| Takashi Iwai | e28563c | 2005-12-01 10:42:42 +0100 | [diff] [blame^] | 571 | #endif /* CONFIG_PROC_FS */ | 
 | 572 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 573 | /** | 
 | 574 |  *  snd_component_add - add a component string | 
 | 575 |  *  @card: soundcard structure | 
 | 576 |  *  @component: the component id string | 
 | 577 |  * | 
 | 578 |  *  This function adds the component id string to the supported list. | 
 | 579 |  *  The component can be referred from the alsa-lib. | 
 | 580 |  * | 
 | 581 |  *  Returns zero otherwise a negative error code. | 
 | 582 |  */ | 
 | 583 |    | 
| Takashi Iwai | 512bbd6 | 2005-11-17 13:51:18 +0100 | [diff] [blame] | 584 | int snd_component_add(struct snd_card *card, const char *component) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 585 | { | 
 | 586 | 	char *ptr; | 
 | 587 | 	int len = strlen(component); | 
 | 588 |  | 
 | 589 | 	ptr = strstr(card->components, component); | 
 | 590 | 	if (ptr != NULL) { | 
 | 591 | 		if (ptr[len] == '\0' || ptr[len] == ' ')	/* already there */ | 
 | 592 | 			return 1; | 
 | 593 | 	} | 
 | 594 | 	if (strlen(card->components) + 1 + len + 1 > sizeof(card->components)) { | 
 | 595 | 		snd_BUG(); | 
 | 596 | 		return -ENOMEM; | 
 | 597 | 	} | 
 | 598 | 	if (card->components[0] != '\0') | 
 | 599 | 		strcat(card->components, " "); | 
 | 600 | 	strcat(card->components, component); | 
 | 601 | 	return 0; | 
 | 602 | } | 
 | 603 |  | 
 | 604 | /** | 
 | 605 |  *  snd_card_file_add - add the file to the file list of the card | 
 | 606 |  *  @card: soundcard structure | 
 | 607 |  *  @file: file pointer | 
 | 608 |  * | 
 | 609 |  *  This function adds the file to the file linked-list of the card. | 
 | 610 |  *  This linked-list is used to keep tracking the connection state, | 
 | 611 |  *  and to avoid the release of busy resources by hotplug. | 
 | 612 |  * | 
 | 613 |  *  Returns zero or a negative error code. | 
 | 614 |  */ | 
| Takashi Iwai | 512bbd6 | 2005-11-17 13:51:18 +0100 | [diff] [blame] | 615 | int snd_card_file_add(struct snd_card *card, struct file *file) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 616 | { | 
 | 617 | 	struct snd_monitor_file *mfile; | 
 | 618 |  | 
 | 619 | 	mfile = kmalloc(sizeof(*mfile), GFP_KERNEL); | 
 | 620 | 	if (mfile == NULL) | 
 | 621 | 		return -ENOMEM; | 
 | 622 | 	mfile->file = file; | 
 | 623 | 	mfile->next = NULL; | 
 | 624 | 	spin_lock(&card->files_lock); | 
 | 625 | 	if (card->shutdown) { | 
 | 626 | 		spin_unlock(&card->files_lock); | 
 | 627 | 		kfree(mfile); | 
 | 628 | 		return -ENODEV; | 
 | 629 | 	} | 
 | 630 | 	mfile->next = card->files; | 
 | 631 | 	card->files = mfile; | 
 | 632 | 	spin_unlock(&card->files_lock); | 
 | 633 | 	return 0; | 
 | 634 | } | 
 | 635 |  | 
 | 636 | /** | 
 | 637 |  *  snd_card_file_remove - remove the file from the file list | 
 | 638 |  *  @card: soundcard structure | 
 | 639 |  *  @file: file pointer | 
 | 640 |  * | 
 | 641 |  *  This function removes the file formerly added to the card via | 
 | 642 |  *  snd_card_file_add() function. | 
 | 643 |  *  If all files are removed and the release of the card is | 
 | 644 |  *  scheduled, it will wake up the the thread to call snd_card_free() | 
 | 645 |  *  (see snd_card_free_in_thread() function). | 
 | 646 |  * | 
 | 647 |  *  Returns zero or a negative error code. | 
 | 648 |  */ | 
| Takashi Iwai | 512bbd6 | 2005-11-17 13:51:18 +0100 | [diff] [blame] | 649 | int snd_card_file_remove(struct snd_card *card, struct file *file) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 650 | { | 
 | 651 | 	struct snd_monitor_file *mfile, *pfile = NULL; | 
 | 652 |  | 
 | 653 | 	spin_lock(&card->files_lock); | 
 | 654 | 	mfile = card->files; | 
 | 655 | 	while (mfile) { | 
 | 656 | 		if (mfile->file == file) { | 
 | 657 | 			if (pfile) | 
 | 658 | 				pfile->next = mfile->next; | 
 | 659 | 			else | 
 | 660 | 				card->files = mfile->next; | 
 | 661 | 			break; | 
 | 662 | 		} | 
 | 663 | 		pfile = mfile; | 
 | 664 | 		mfile = mfile->next; | 
 | 665 | 	} | 
 | 666 | 	spin_unlock(&card->files_lock); | 
 | 667 | 	if (card->files == NULL) | 
 | 668 | 		wake_up(&card->shutdown_sleep); | 
 | 669 | 	if (!mfile) { | 
 | 670 | 		snd_printk(KERN_ERR "ALSA card file remove problem (%p)\n", file); | 
 | 671 | 		return -ENOENT; | 
 | 672 | 	} | 
 | 673 | 	kfree(mfile); | 
 | 674 | 	return 0; | 
 | 675 | } | 
 | 676 |  | 
 | 677 | #ifdef CONFIG_PM | 
 | 678 | /** | 
 | 679 |  *  snd_power_wait - wait until the power-state is changed. | 
 | 680 |  *  @card: soundcard structure | 
 | 681 |  *  @power_state: expected power state | 
 | 682 |  *  @file: file structure for the O_NONBLOCK check (optional) | 
 | 683 |  * | 
 | 684 |  *  Waits until the power-state is changed. | 
 | 685 |  * | 
 | 686 |  *  Note: the power lock must be active before call. | 
 | 687 |  */ | 
| Takashi Iwai | 512bbd6 | 2005-11-17 13:51:18 +0100 | [diff] [blame] | 688 | int snd_power_wait(struct snd_card *card, unsigned int power_state, struct file *file) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 689 | { | 
 | 690 | 	wait_queue_t wait; | 
 | 691 | 	int result = 0; | 
 | 692 |  | 
 | 693 | 	/* fastpath */ | 
 | 694 | 	if (snd_power_get_state(card) == power_state) | 
 | 695 | 		return 0; | 
 | 696 | 	init_waitqueue_entry(&wait, current); | 
 | 697 | 	add_wait_queue(&card->power_sleep, &wait); | 
 | 698 | 	while (1) { | 
 | 699 | 		if (card->shutdown) { | 
 | 700 | 			result = -ENODEV; | 
 | 701 | 			break; | 
 | 702 | 		} | 
 | 703 | 		if (snd_power_get_state(card) == power_state) | 
 | 704 | 			break; | 
 | 705 | #if 0 /* block all devices */ | 
 | 706 | 		if (file && (file->f_flags & O_NONBLOCK)) { | 
 | 707 | 			result = -EAGAIN; | 
 | 708 | 			break; | 
 | 709 | 		} | 
 | 710 | #endif | 
 | 711 | 		set_current_state(TASK_UNINTERRUPTIBLE); | 
 | 712 | 		snd_power_unlock(card); | 
 | 713 | 		schedule_timeout(30 * HZ); | 
 | 714 | 		snd_power_lock(card); | 
 | 715 | 	} | 
 | 716 | 	remove_wait_queue(&card->power_sleep, &wait); | 
 | 717 | 	return result; | 
 | 718 | } | 
 | 719 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 720 | #endif /* CONFIG_PM */ |