hwdep.c 13.4 KB
Newer Older
L
Linus Torvalds 已提交
1 2
/*
 *  Hardware dependent layer
3
 *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
L
Linus Torvalds 已提交
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
 *
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
 *
 */

#include <linux/major.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/time.h>
26
#include <linux/mutex.h>
27
#include <linux/module.h>
L
Linus Torvalds 已提交
28 29 30 31 32 33
#include <sound/core.h>
#include <sound/control.h>
#include <sound/minors.h>
#include <sound/hwdep.h>
#include <sound/info.h>

34
MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
L
Linus Torvalds 已提交
35 36 37
MODULE_DESCRIPTION("Hardware dependent layer");
MODULE_LICENSE("GPL");

38
static LIST_HEAD(snd_hwdep_devices);
39
static DEFINE_MUTEX(register_mutex);
L
Linus Torvalds 已提交
40

41 42 43
static int snd_hwdep_free(struct snd_hwdep *hwdep);
static int snd_hwdep_dev_free(struct snd_device *device);
static int snd_hwdep_dev_register(struct snd_device *device);
44
static int snd_hwdep_dev_disconnect(struct snd_device *device);
L
Linus Torvalds 已提交
45 46


47 48 49 50
static struct snd_hwdep *snd_hwdep_search(struct snd_card *card, int device)
{
	struct snd_hwdep *hwdep;

51
	list_for_each_entry(hwdep, &snd_hwdep_devices, list)
52 53 54 55
		if (hwdep->card == card && hwdep->device == device)
			return hwdep;
	return NULL;
}
L
Linus Torvalds 已提交
56 57 58

static loff_t snd_hwdep_llseek(struct file * file, loff_t offset, int orig)
{
59
	struct snd_hwdep *hw = file->private_data;
L
Linus Torvalds 已提交
60 61 62 63 64
	if (hw->ops.llseek)
		return hw->ops.llseek(hw, file, offset, orig);
	return -ENXIO;
}

65 66
static ssize_t snd_hwdep_read(struct file * file, char __user *buf,
			      size_t count, loff_t *offset)
L
Linus Torvalds 已提交
67
{
68
	struct snd_hwdep *hw = file->private_data;
L
Linus Torvalds 已提交
69 70 71 72 73
	if (hw->ops.read)
		return hw->ops.read(hw, buf, count, offset);
	return -ENXIO;	
}

74 75
static ssize_t snd_hwdep_write(struct file * file, const char __user *buf,
			       size_t count, loff_t *offset)
L
Linus Torvalds 已提交
76
{
77
	struct snd_hwdep *hw = file->private_data;
L
Linus Torvalds 已提交
78 79 80 81 82 83 84 85
	if (hw->ops.write)
		return hw->ops.write(hw, buf, count, offset);
	return -ENXIO;	
}

static int snd_hwdep_open(struct inode *inode, struct file * file)
{
	int major = imajor(inode);
86
	struct snd_hwdep *hw;
L
Linus Torvalds 已提交
87 88 89
	int err;
	wait_queue_t wait;

90
	if (major == snd_major) {
91 92
		hw = snd_lookup_minor_data(iminor(inode),
					   SNDRV_DEVICE_TYPE_HWDEP);
L
Linus Torvalds 已提交
93
#ifdef CONFIG_SND_OSSEMUL
94
	} else if (major == SOUND_MAJOR) {
95 96
		hw = snd_lookup_oss_minor_data(iminor(inode),
					       SNDRV_OSS_DEVICE_TYPE_DMFM);
L
Linus Torvalds 已提交
97
#endif
98
	} else
L
Linus Torvalds 已提交
99 100 101 102
		return -ENXIO;
	if (hw == NULL)
		return -ENODEV;

103 104
	if (!try_module_get(hw->card->module)) {
		snd_card_unref(hw->card);
L
Linus Torvalds 已提交
105
		return -EFAULT;
106
	}
L
Linus Torvalds 已提交
107 108 109

	init_waitqueue_entry(&wait, current);
	add_wait_queue(&hw->open_wait, &wait);
110
	mutex_lock(&hw->open_mutex);
L
Linus Torvalds 已提交
111 112 113 114 115
	while (1) {
		if (hw->exclusive && hw->used > 0) {
			err = -EBUSY;
			break;
		}
116 117 118 119
		if (!hw->ops.open) {
			err = 0;
			break;
		}
L
Linus Torvalds 已提交
120 121 122 123 124 125 126 127 128 129 130
		err = hw->ops.open(hw, file);
		if (err >= 0)
			break;
		if (err == -EAGAIN) {
			if (file->f_flags & O_NONBLOCK) {
				err = -EBUSY;
				break;
			}
		} else
			break;
		set_current_state(TASK_INTERRUPTIBLE);
131
		mutex_unlock(&hw->open_mutex);
L
Linus Torvalds 已提交
132
		schedule();
133
		mutex_lock(&hw->open_mutex);
134 135 136 137
		if (hw->card->shutdown) {
			err = -ENODEV;
			break;
		}
L
Linus Torvalds 已提交
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
		if (signal_pending(current)) {
			err = -ERESTARTSYS;
			break;
		}
	}
	remove_wait_queue(&hw->open_wait, &wait);
	if (err >= 0) {
		err = snd_card_file_add(hw->card, file);
		if (err >= 0) {
			file->private_data = hw;
			hw->used++;
		} else {
			if (hw->ops.release)
				hw->ops.release(hw, file);
		}
	}
154
	mutex_unlock(&hw->open_mutex);
L
Linus Torvalds 已提交
155 156
	if (err < 0)
		module_put(hw->card->module);
157
	snd_card_unref(hw->card);
L
Linus Torvalds 已提交
158 159 160 161 162
	return err;
}

static int snd_hwdep_release(struct inode *inode, struct file * file)
{
163
	int err = 0;
164
	struct snd_hwdep *hw = file->private_data;
165
	struct module *mod = hw->card->module;
166

167
	mutex_lock(&hw->open_mutex);
168
	if (hw->ops.release)
L
Linus Torvalds 已提交
169 170 171
		err = hw->ops.release(hw, file);
	if (hw->used > 0)
		hw->used--;
172
	mutex_unlock(&hw->open_mutex);
173 174 175
	wake_up(&hw->open_wait);

	snd_card_file_remove(hw->card, file);
176
	module_put(mod);
L
Linus Torvalds 已提交
177 178 179 180 181
	return err;
}

static unsigned int snd_hwdep_poll(struct file * file, poll_table * wait)
{
182
	struct snd_hwdep *hw = file->private_data;
L
Linus Torvalds 已提交
183 184 185 186 187
	if (hw->ops.poll)
		return hw->ops.poll(hw, file, wait);
	return 0;
}

188 189
static int snd_hwdep_info(struct snd_hwdep *hw,
			  struct snd_hwdep_info __user *_info)
L
Linus Torvalds 已提交
190
{
191
	struct snd_hwdep_info info;
L
Linus Torvalds 已提交
192 193 194 195 196 197 198 199 200 201 202
	
	memset(&info, 0, sizeof(info));
	info.card = hw->card->number;
	strlcpy(info.id, hw->id, sizeof(info.id));	
	strlcpy(info.name, hw->name, sizeof(info.name));
	info.iface = hw->iface;
	if (copy_to_user(_info, &info, sizeof(info)))
		return -EFAULT;
	return 0;
}

203 204
static int snd_hwdep_dsp_status(struct snd_hwdep *hw,
				struct snd_hwdep_dsp_status __user *_info)
L
Linus Torvalds 已提交
205
{
206
	struct snd_hwdep_dsp_status info;
L
Linus Torvalds 已提交
207 208 209 210 211 212 213 214 215 216 217 218 219
	int err;
	
	if (! hw->ops.dsp_status)
		return -ENXIO;
	memset(&info, 0, sizeof(info));
	info.dsp_loaded = hw->dsp_loaded;
	if ((err = hw->ops.dsp_status(hw, &info)) < 0)
		return err;
	if (copy_to_user(_info, &info, sizeof(info)))
		return -EFAULT;
	return 0;
}

220 221
static int snd_hwdep_dsp_load(struct snd_hwdep *hw,
			      struct snd_hwdep_dsp_image __user *_info)
L
Linus Torvalds 已提交
222
{
223
	struct snd_hwdep_dsp_image info;
L
Linus Torvalds 已提交
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242
	int err;
	
	if (! hw->ops.dsp_load)
		return -ENXIO;
	memset(&info, 0, sizeof(info));
	if (copy_from_user(&info, _info, sizeof(info)))
		return -EFAULT;
	/* check whether the dsp was already loaded */
	if (hw->dsp_loaded & (1 << info.index))
		return -EBUSY;
	if (!access_ok(VERIFY_READ, info.image, info.length))
		return -EFAULT;
	err = hw->ops.dsp_load(hw, &info);
	if (err < 0)
		return err;
	hw->dsp_loaded |= (1 << info.index);
	return 0;
}

243 244
static long snd_hwdep_ioctl(struct file * file, unsigned int cmd,
			    unsigned long arg)
L
Linus Torvalds 已提交
245
{
246
	struct snd_hwdep *hw = file->private_data;
L
Linus Torvalds 已提交
247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
	void __user *argp = (void __user *)arg;
	switch (cmd) {
	case SNDRV_HWDEP_IOCTL_PVERSION:
		return put_user(SNDRV_HWDEP_VERSION, (int __user *)argp);
	case SNDRV_HWDEP_IOCTL_INFO:
		return snd_hwdep_info(hw, argp);
	case SNDRV_HWDEP_IOCTL_DSP_STATUS:
		return snd_hwdep_dsp_status(hw, argp);
	case SNDRV_HWDEP_IOCTL_DSP_LOAD:
		return snd_hwdep_dsp_load(hw, argp);
	}
	if (hw->ops.ioctl)
		return hw->ops.ioctl(hw, file, cmd, arg);
	return -ENOTTY;
}

static int snd_hwdep_mmap(struct file * file, struct vm_area_struct * vma)
{
265
	struct snd_hwdep *hw = file->private_data;
L
Linus Torvalds 已提交
266 267 268 269 270
	if (hw->ops.mmap)
		return hw->ops.mmap(hw, file, vma);
	return -ENXIO;
}

271 272
static int snd_hwdep_control_ioctl(struct snd_card *card,
				   struct snd_ctl_file * control,
L
Linus Torvalds 已提交
273 274 275 276 277 278 279 280 281
				   unsigned int cmd, unsigned long arg)
{
	switch (cmd) {
	case SNDRV_CTL_IOCTL_HWDEP_NEXT_DEVICE:
		{
			int device;

			if (get_user(device, (int __user *)arg))
				return -EFAULT;
282
			mutex_lock(&register_mutex);
283 284 285 286 287 288 289 290

			if (device < 0)
				device = 0;
			else if (device < SNDRV_MINOR_HWDEPS)
				device++;
			else
				device = SNDRV_MINOR_HWDEPS;

L
Linus Torvalds 已提交
291
			while (device < SNDRV_MINOR_HWDEPS) {
292
				if (snd_hwdep_search(card, device))
L
Linus Torvalds 已提交
293 294 295 296 297
					break;
				device++;
			}
			if (device >= SNDRV_MINOR_HWDEPS)
				device = -1;
298
			mutex_unlock(&register_mutex);
L
Linus Torvalds 已提交
299 300 301 302 303 304
			if (put_user(device, (int __user *)arg))
				return -EFAULT;
			return 0;
		}
	case SNDRV_CTL_IOCTL_HWDEP_INFO:
		{
305
			struct snd_hwdep_info __user *info = (struct snd_hwdep_info __user *)arg;
306
			int device, err;
307
			struct snd_hwdep *hwdep;
L
Linus Torvalds 已提交
308 309 310

			if (get_user(device, &info->device))
				return -EFAULT;
311
			mutex_lock(&register_mutex);
312 313 314 315 316
			hwdep = snd_hwdep_search(card, device);
			if (hwdep)
				err = snd_hwdep_info(hwdep, info);
			else
				err = -ENXIO;
317
			mutex_unlock(&register_mutex);
318
			return err;
L
Linus Torvalds 已提交
319 320 321 322 323 324 325 326 327 328 329 330 331 332 333
		}
	}
	return -ENOIOCTLCMD;
}

#ifdef CONFIG_COMPAT
#include "hwdep_compat.c"
#else
#define snd_hwdep_ioctl_compat	NULL
#endif

/*

 */

334
static const struct file_operations snd_hwdep_f_ops =
L
Linus Torvalds 已提交
335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358
{
	.owner = 	THIS_MODULE,
	.llseek =	snd_hwdep_llseek,
	.read = 	snd_hwdep_read,
	.write =	snd_hwdep_write,
	.open =		snd_hwdep_open,
	.release =	snd_hwdep_release,
	.poll =		snd_hwdep_poll,
	.unlocked_ioctl =	snd_hwdep_ioctl,
	.compat_ioctl =	snd_hwdep_ioctl_compat,
	.mmap =		snd_hwdep_mmap,
};

/**
 * snd_hwdep_new - create a new hwdep instance
 * @card: the card instance
 * @id: the id string
 * @device: the device index (zero-based)
 * @rhwdep: the pointer to store the new hwdep instance
 *
 * Creates a new hwdep instance with the given index on the card.
 * The callbacks (hwdep->ops) must be set on the returned instance
 * after this call manually by the caller.
 *
359
 * Return: Zero if successful, or a negative error code on failure.
L
Linus Torvalds 已提交
360
 */
361 362
int snd_hwdep_new(struct snd_card *card, char *id, int device,
		  struct snd_hwdep **rhwdep)
L
Linus Torvalds 已提交
363
{
364
	struct snd_hwdep *hwdep;
L
Linus Torvalds 已提交
365
	int err;
366
	static struct snd_device_ops ops = {
L
Linus Torvalds 已提交
367 368
		.dev_free = snd_hwdep_dev_free,
		.dev_register = snd_hwdep_dev_register,
369
		.dev_disconnect = snd_hwdep_dev_disconnect,
L
Linus Torvalds 已提交
370 371
	};

372 373 374 375
	if (snd_BUG_ON(!card))
		return -ENXIO;
	if (rhwdep)
		*rhwdep = NULL;
376
	hwdep = kzalloc(sizeof(*hwdep), GFP_KERNEL);
T
Takashi Iwai 已提交
377 378
	if (hwdep == NULL) {
		snd_printk(KERN_ERR "hwdep: cannot allocate\n");
L
Linus Torvalds 已提交
379
		return -ENOMEM;
T
Takashi Iwai 已提交
380
	}
L
Linus Torvalds 已提交
381 382
	hwdep->card = card;
	hwdep->device = device;
T
Takashi Iwai 已提交
383
	if (id)
L
Linus Torvalds 已提交
384 385 386 387 388 389 390 391 392
		strlcpy(hwdep->id, id, sizeof(hwdep->id));
#ifdef CONFIG_SND_OSSEMUL
	hwdep->oss_type = -1;
#endif
	if ((err = snd_device_new(card, SNDRV_DEV_HWDEP, hwdep, &ops)) < 0) {
		snd_hwdep_free(hwdep);
		return err;
	}
	init_waitqueue_head(&hwdep->open_wait);
393
	mutex_init(&hwdep->open_mutex);
394 395
	if (rhwdep)
		*rhwdep = hwdep;
L
Linus Torvalds 已提交
396 397 398
	return 0;
}

399
static int snd_hwdep_free(struct snd_hwdep *hwdep)
L
Linus Torvalds 已提交
400
{
401 402
	if (!hwdep)
		return 0;
L
Linus Torvalds 已提交
403 404 405 406 407 408
	if (hwdep->private_free)
		hwdep->private_free(hwdep);
	kfree(hwdep);
	return 0;
}

409
static int snd_hwdep_dev_free(struct snd_device *device)
L
Linus Torvalds 已提交
410
{
411
	struct snd_hwdep *hwdep = device->device_data;
L
Linus Torvalds 已提交
412 413 414
	return snd_hwdep_free(hwdep);
}

415
static int snd_hwdep_dev_register(struct snd_device *device)
L
Linus Torvalds 已提交
416
{
417
	struct snd_hwdep *hwdep = device->device_data;
418
	int err;
L
Linus Torvalds 已提交
419 420
	char name[32];

421
	mutex_lock(&register_mutex);
422
	if (snd_hwdep_search(hwdep->card, hwdep->device)) {
423
		mutex_unlock(&register_mutex);
L
Linus Torvalds 已提交
424 425
		return -EBUSY;
	}
426
	list_add_tail(&hwdep->list, &snd_hwdep_devices);
L
Linus Torvalds 已提交
427 428 429
	sprintf(name, "hwC%iD%i", hwdep->card->number, hwdep->device);
	if ((err = snd_register_device(SNDRV_DEVICE_TYPE_HWDEP,
				       hwdep->card, hwdep->device,
430
				       &snd_hwdep_f_ops, hwdep, name)) < 0) {
L
Linus Torvalds 已提交
431 432
		snd_printk(KERN_ERR "unable to register hardware dependent device %i:%i\n",
			   hwdep->card->number, hwdep->device);
433
		list_del(&hwdep->list);
434
		mutex_unlock(&register_mutex);
L
Linus Torvalds 已提交
435 436 437 438 439 440 441 442 443 444
		return err;
	}
#ifdef CONFIG_SND_OSSEMUL
	hwdep->ossreg = 0;
	if (hwdep->oss_type >= 0) {
		if ((hwdep->oss_type == SNDRV_OSS_DEVICE_TYPE_DMFM) && (hwdep->device != 0)) {
			snd_printk (KERN_WARNING "only hwdep device 0 can be registered as OSS direct FM device!\n");
		} else {
			if (snd_register_oss_device(hwdep->oss_type,
						    hwdep->card, hwdep->device,
445
						    &snd_hwdep_f_ops, hwdep,
446
						    hwdep->oss_dev) < 0) {
L
Linus Torvalds 已提交
447 448 449 450 451 452 453
				snd_printk(KERN_ERR "unable to register OSS compatibility device %i:%i\n",
					   hwdep->card->number, hwdep->device);
			} else
				hwdep->ossreg = 1;
		}
	}
#endif
454
	mutex_unlock(&register_mutex);
L
Linus Torvalds 已提交
455 456 457
	return 0;
}

458
static int snd_hwdep_dev_disconnect(struct snd_device *device)
L
Linus Torvalds 已提交
459
{
460
	struct snd_hwdep *hwdep = device->device_data;
L
Linus Torvalds 已提交
461

462 463
	if (snd_BUG_ON(!hwdep))
		return -ENXIO;
464
	mutex_lock(&register_mutex);
465
	if (snd_hwdep_search(hwdep->card, hwdep->device) != hwdep) {
466
		mutex_unlock(&register_mutex);
L
Linus Torvalds 已提交
467 468
		return -EINVAL;
	}
469 470
	mutex_lock(&hwdep->open_mutex);
	wake_up(&hwdep->open_wait);
L
Linus Torvalds 已提交
471 472 473 474 475
#ifdef CONFIG_SND_OSSEMUL
	if (hwdep->ossreg)
		snd_unregister_oss_device(hwdep->oss_type, hwdep->card, hwdep->device);
#endif
	snd_unregister_device(SNDRV_DEVICE_TYPE_HWDEP, hwdep->card, hwdep->device);
476
	list_del_init(&hwdep->list);
477
	mutex_unlock(&hwdep->open_mutex);
478
	mutex_unlock(&register_mutex);
479
	return 0;
L
Linus Torvalds 已提交
480 481
}

482
#ifdef CONFIG_PROC_FS
L
Linus Torvalds 已提交
483 484 485 486
/*
 *  Info interface
 */

487 488
static void snd_hwdep_proc_read(struct snd_info_entry *entry,
				struct snd_info_buffer *buffer)
L
Linus Torvalds 已提交
489
{
490
	struct snd_hwdep *hwdep;
L
Linus Torvalds 已提交
491

492
	mutex_lock(&register_mutex);
493
	list_for_each_entry(hwdep, &snd_hwdep_devices, list)
L
Linus Torvalds 已提交
494
		snd_iprintf(buffer, "%02i-%02i: %s\n",
495
			    hwdep->card->number, hwdep->device, hwdep->name);
496
	mutex_unlock(&register_mutex);
L
Linus Torvalds 已提交
497 498
}

499
static struct snd_info_entry *snd_hwdep_proc_entry;
L
Linus Torvalds 已提交
500

501
static void __init snd_hwdep_proc_init(void)
L
Linus Torvalds 已提交
502
{
503
	struct snd_info_entry *entry;
L
Linus Torvalds 已提交
504 505 506 507 508 509 510 511 512

	if ((entry = snd_info_create_module_entry(THIS_MODULE, "hwdep", NULL)) != NULL) {
		entry->c.text.read = snd_hwdep_proc_read;
		if (snd_info_register(entry) < 0) {
			snd_info_free_entry(entry);
			entry = NULL;
		}
	}
	snd_hwdep_proc_entry = entry;
513 514 515 516
}

static void __exit snd_hwdep_proc_done(void)
{
517
	snd_info_free_entry(snd_hwdep_proc_entry);
518 519 520 521 522 523 524 525 526 527 528 529 530 531
}
#else /* !CONFIG_PROC_FS */
#define snd_hwdep_proc_init()
#define snd_hwdep_proc_done()
#endif /* CONFIG_PROC_FS */


/*
 *  ENTRY functions
 */

static int __init alsa_hwdep_init(void)
{
	snd_hwdep_proc_init();
L
Linus Torvalds 已提交
532 533 534 535 536 537 538 539 540
	snd_ctl_register_ioctl(snd_hwdep_control_ioctl);
	snd_ctl_register_ioctl_compat(snd_hwdep_control_ioctl);
	return 0;
}

static void __exit alsa_hwdep_exit(void)
{
	snd_ctl_unregister_ioctl(snd_hwdep_control_ioctl);
	snd_ctl_unregister_ioctl_compat(snd_hwdep_control_ioctl);
541
	snd_hwdep_proc_done();
L
Linus Torvalds 已提交
542 543 544 545 546 547
}

module_init(alsa_hwdep_init)
module_exit(alsa_hwdep_exit)

EXPORT_SYMBOL(snd_hwdep_new);