brintos

brintos / linux-shallow public Read only

0
0
Text · 12.3 KiB · 09200df Raw
540 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 *  Hardware dependent layer4 *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>5 */6 7#include <linux/major.h>8#include <linux/init.h>9#include <linux/slab.h>10#include <linux/time.h>11#include <linux/mutex.h>12#include <linux/module.h>13#include <linux/sched/signal.h>14#include <sound/core.h>15#include <sound/control.h>16#include <sound/minors.h>17#include <sound/hwdep.h>18#include <sound/info.h>19 20MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");21MODULE_DESCRIPTION("Hardware dependent layer");22MODULE_LICENSE("GPL");23 24static LIST_HEAD(snd_hwdep_devices);25static DEFINE_MUTEX(register_mutex);26 27static int snd_hwdep_dev_free(struct snd_device *device);28static int snd_hwdep_dev_register(struct snd_device *device);29static int snd_hwdep_dev_disconnect(struct snd_device *device);30 31 32static struct snd_hwdep *snd_hwdep_search(struct snd_card *card, int device)33{34	struct snd_hwdep *hwdep;35 36	list_for_each_entry(hwdep, &snd_hwdep_devices, list)37		if (hwdep->card == card && hwdep->device == device)38			return hwdep;39	return NULL;40}41 42static loff_t snd_hwdep_llseek(struct file * file, loff_t offset, int orig)43{44	struct snd_hwdep *hw = file->private_data;45	if (hw->ops.llseek)46		return hw->ops.llseek(hw, file, offset, orig);47	return -ENXIO;48}49 50static ssize_t snd_hwdep_read(struct file * file, char __user *buf,51			      size_t count, loff_t *offset)52{53	struct snd_hwdep *hw = file->private_data;54	if (hw->ops.read)55		return hw->ops.read(hw, buf, count, offset);56	return -ENXIO;	57}58 59static ssize_t snd_hwdep_write(struct file * file, const char __user *buf,60			       size_t count, loff_t *offset)61{62	struct snd_hwdep *hw = file->private_data;63	if (hw->ops.write)64		return hw->ops.write(hw, buf, count, offset);65	return -ENXIO;	66}67 68static int snd_hwdep_open(struct inode *inode, struct file * file)69{70	int major = imajor(inode);71	struct snd_hwdep *hw;72	int err;73	wait_queue_entry_t wait;74 75	if (major == snd_major) {76		hw = snd_lookup_minor_data(iminor(inode),77					   SNDRV_DEVICE_TYPE_HWDEP);78#ifdef CONFIG_SND_OSSEMUL79	} else if (major == SOUND_MAJOR) {80		hw = snd_lookup_oss_minor_data(iminor(inode),81					       SNDRV_OSS_DEVICE_TYPE_DMFM);82#endif83	} else84		return -ENXIO;85	if (hw == NULL)86		return -ENODEV;87 88	if (!try_module_get(hw->card->module)) {89		snd_card_unref(hw->card);90		return -EFAULT;91	}92 93	init_waitqueue_entry(&wait, current);94	add_wait_queue(&hw->open_wait, &wait);95	mutex_lock(&hw->open_mutex);96	while (1) {97		if (hw->exclusive && hw->used > 0) {98			err = -EBUSY;99			break;100		}101		if (!hw->ops.open) {102			err = 0;103			break;104		}105		err = hw->ops.open(hw, file);106		if (err >= 0)107			break;108		if (err == -EAGAIN) {109			if (file->f_flags & O_NONBLOCK) {110				err = -EBUSY;111				break;112			}113		} else114			break;115		set_current_state(TASK_INTERRUPTIBLE);116		mutex_unlock(&hw->open_mutex);117		schedule();118		mutex_lock(&hw->open_mutex);119		if (hw->card->shutdown) {120			err = -ENODEV;121			break;122		}123		if (signal_pending(current)) {124			err = -ERESTARTSYS;125			break;126		}127	}128	remove_wait_queue(&hw->open_wait, &wait);129	if (err >= 0) {130		err = snd_card_file_add(hw->card, file);131		if (err >= 0) {132			file->private_data = hw;133			hw->used++;134		} else {135			if (hw->ops.release)136				hw->ops.release(hw, file);137		}138	}139	mutex_unlock(&hw->open_mutex);140	if (err < 0)141		module_put(hw->card->module);142	snd_card_unref(hw->card);143	return err;144}145 146static int snd_hwdep_release(struct inode *inode, struct file * file)147{148	int err = 0;149	struct snd_hwdep *hw = file->private_data;150	struct module *mod = hw->card->module;151 152	scoped_guard(mutex, &hw->open_mutex) {153		if (hw->ops.release)154			err = hw->ops.release(hw, file);155		if (hw->used > 0)156			hw->used--;157	}158	wake_up(&hw->open_wait);159 160	snd_card_file_remove(hw->card, file);161	module_put(mod);162	return err;163}164 165static __poll_t snd_hwdep_poll(struct file * file, poll_table * wait)166{167	struct snd_hwdep *hw = file->private_data;168	if (hw->ops.poll)169		return hw->ops.poll(hw, file, wait);170	return 0;171}172 173static int snd_hwdep_info(struct snd_hwdep *hw,174			  struct snd_hwdep_info __user *_info)175{176	struct snd_hwdep_info info;177	178	memset(&info, 0, sizeof(info));179	info.card = hw->card->number;180	strscpy(info.id, hw->id, sizeof(info.id));181	strscpy(info.name, hw->name, sizeof(info.name));182	info.iface = hw->iface;183	if (copy_to_user(_info, &info, sizeof(info)))184		return -EFAULT;185	return 0;186}187 188static int snd_hwdep_dsp_status(struct snd_hwdep *hw,189				struct snd_hwdep_dsp_status __user *_info)190{191	struct snd_hwdep_dsp_status info;192	int err;193	194	if (! hw->ops.dsp_status)195		return -ENXIO;196	memset(&info, 0, sizeof(info));197	info.dsp_loaded = hw->dsp_loaded;198	err = hw->ops.dsp_status(hw, &info);199	if (err < 0)200		return err;201	if (copy_to_user(_info, &info, sizeof(info)))202		return -EFAULT;203	return 0;204}205 206static int snd_hwdep_dsp_load(struct snd_hwdep *hw,207			      struct snd_hwdep_dsp_image *info)208{209	int err;210	211	if (! hw->ops.dsp_load)212		return -ENXIO;213	if (info->index >= 32)214		return -EINVAL;215	/* check whether the dsp was already loaded */216	if (hw->dsp_loaded & (1u << info->index))217		return -EBUSY;218	err = hw->ops.dsp_load(hw, info);219	if (err < 0)220		return err;221	hw->dsp_loaded |= (1u << info->index);222	return 0;223}224 225static int snd_hwdep_dsp_load_user(struct snd_hwdep *hw,226				   struct snd_hwdep_dsp_image __user *_info)227{228	struct snd_hwdep_dsp_image info = {};229 230	if (copy_from_user(&info, _info, sizeof(info)))231		return -EFAULT;232	return snd_hwdep_dsp_load(hw, &info);233}234 235 236static long snd_hwdep_ioctl(struct file * file, unsigned int cmd,237			    unsigned long arg)238{239	struct snd_hwdep *hw = file->private_data;240	void __user *argp = (void __user *)arg;241	switch (cmd) {242	case SNDRV_HWDEP_IOCTL_PVERSION:243		return put_user(SNDRV_HWDEP_VERSION, (int __user *)argp);244	case SNDRV_HWDEP_IOCTL_INFO:245		return snd_hwdep_info(hw, argp);246	case SNDRV_HWDEP_IOCTL_DSP_STATUS:247		return snd_hwdep_dsp_status(hw, argp);248	case SNDRV_HWDEP_IOCTL_DSP_LOAD:249		return snd_hwdep_dsp_load_user(hw, argp);250	}251	if (hw->ops.ioctl)252		return hw->ops.ioctl(hw, file, cmd, arg);253	return -ENOTTY;254}255 256static int snd_hwdep_mmap(struct file * file, struct vm_area_struct * vma)257{258	struct snd_hwdep *hw = file->private_data;259	if (hw->ops.mmap)260		return hw->ops.mmap(hw, file, vma);261	return -ENXIO;262}263 264static int snd_hwdep_control_ioctl(struct snd_card *card,265				   struct snd_ctl_file * control,266				   unsigned int cmd, unsigned long arg)267{268	switch (cmd) {269	case SNDRV_CTL_IOCTL_HWDEP_NEXT_DEVICE:270		{271			int device;272 273			if (get_user(device, (int __user *)arg))274				return -EFAULT;275 276			scoped_guard(mutex, &register_mutex) {277				if (device < 0)278					device = 0;279				else if (device < SNDRV_MINOR_HWDEPS)280					device++;281				else282					device = SNDRV_MINOR_HWDEPS;283 284				while (device < SNDRV_MINOR_HWDEPS) {285					if (snd_hwdep_search(card, device))286						break;287					device++;288				}289				if (device >= SNDRV_MINOR_HWDEPS)290					device = -1;291			}292			if (put_user(device, (int __user *)arg))293				return -EFAULT;294			return 0;295		}296	case SNDRV_CTL_IOCTL_HWDEP_INFO:297		{298			struct snd_hwdep_info __user *info = (struct snd_hwdep_info __user *)arg;299			int device;300			struct snd_hwdep *hwdep;301 302			if (get_user(device, &info->device))303				return -EFAULT;304			scoped_guard(mutex, &register_mutex) {305				hwdep = snd_hwdep_search(card, device);306				if (!hwdep)307					return -ENXIO;308				return snd_hwdep_info(hwdep, info);309			}310			break;311		}312	}313	return -ENOIOCTLCMD;314}315 316#ifdef CONFIG_COMPAT317#include "hwdep_compat.c"318#else319#define snd_hwdep_ioctl_compat	NULL320#endif321 322/*323 324 */325 326static const struct file_operations snd_hwdep_f_ops =327{328	.owner = 	THIS_MODULE,329	.llseek =	snd_hwdep_llseek,330	.read = 	snd_hwdep_read,331	.write =	snd_hwdep_write,332	.open =		snd_hwdep_open,333	.release =	snd_hwdep_release,334	.poll =		snd_hwdep_poll,335	.unlocked_ioctl =	snd_hwdep_ioctl,336	.compat_ioctl =	snd_hwdep_ioctl_compat,337	.mmap =		snd_hwdep_mmap,338};339 340static void snd_hwdep_free(struct snd_hwdep *hwdep)341{342	if (!hwdep)343		return;344	if (hwdep->private_free)345		hwdep->private_free(hwdep);346	put_device(hwdep->dev);347	kfree(hwdep);348}349 350/**351 * snd_hwdep_new - create a new hwdep instance352 * @card: the card instance353 * @id: the id string354 * @device: the device index (zero-based)355 * @rhwdep: the pointer to store the new hwdep instance356 *357 * Creates a new hwdep instance with the given index on the card.358 * The callbacks (hwdep->ops) must be set on the returned instance359 * after this call manually by the caller.360 *361 * Return: Zero if successful, or a negative error code on failure.362 */363int snd_hwdep_new(struct snd_card *card, char *id, int device,364		  struct snd_hwdep **rhwdep)365{366	struct snd_hwdep *hwdep;367	int err;368	static const struct snd_device_ops ops = {369		.dev_free = snd_hwdep_dev_free,370		.dev_register = snd_hwdep_dev_register,371		.dev_disconnect = snd_hwdep_dev_disconnect,372	};373 374	if (snd_BUG_ON(!card))375		return -ENXIO;376	if (rhwdep)377		*rhwdep = NULL;378	hwdep = kzalloc(sizeof(*hwdep), GFP_KERNEL);379	if (!hwdep)380		return -ENOMEM;381 382	init_waitqueue_head(&hwdep->open_wait);383	mutex_init(&hwdep->open_mutex);384	hwdep->card = card;385	hwdep->device = device;386	if (id)387		strscpy(hwdep->id, id, sizeof(hwdep->id));388 389	err = snd_device_alloc(&hwdep->dev, card);390	if (err < 0) {391		snd_hwdep_free(hwdep);392		return err;393	}394 395	dev_set_name(hwdep->dev, "hwC%iD%i", card->number, device);396#ifdef CONFIG_SND_OSSEMUL397	hwdep->oss_type = -1;398#endif399 400	err = snd_device_new(card, SNDRV_DEV_HWDEP, hwdep, &ops);401	if (err < 0) {402		snd_hwdep_free(hwdep);403		return err;404	}405 406	if (rhwdep)407		*rhwdep = hwdep;408	return 0;409}410EXPORT_SYMBOL(snd_hwdep_new);411 412static int snd_hwdep_dev_free(struct snd_device *device)413{414	snd_hwdep_free(device->device_data);415	return 0;416}417 418static int snd_hwdep_dev_register(struct snd_device *device)419{420	struct snd_hwdep *hwdep = device->device_data;421	struct snd_card *card = hwdep->card;422	int err;423 424	guard(mutex)(&register_mutex);425	if (snd_hwdep_search(card, hwdep->device))426		return -EBUSY;427	list_add_tail(&hwdep->list, &snd_hwdep_devices);428	err = snd_register_device(SNDRV_DEVICE_TYPE_HWDEP,429				  hwdep->card, hwdep->device,430				  &snd_hwdep_f_ops, hwdep, hwdep->dev);431	if (err < 0) {432		dev_err(hwdep->dev, "unable to register\n");433		list_del(&hwdep->list);434		return err;435	}436 437#ifdef CONFIG_SND_OSSEMUL438	hwdep->ossreg = 0;439	if (hwdep->oss_type >= 0) {440		if (hwdep->oss_type == SNDRV_OSS_DEVICE_TYPE_DMFM &&441		    hwdep->device)442			dev_warn(hwdep->dev,443				 "only hwdep device 0 can be registered as OSS direct FM device!\n");444		else if (snd_register_oss_device(hwdep->oss_type,445						 card, hwdep->device,446						 &snd_hwdep_f_ops, hwdep) < 0)447			dev_warn(hwdep->dev,448				 "unable to register OSS compatibility device\n");449		else450			hwdep->ossreg = 1;451	}452#endif453	return 0;454}455 456static int snd_hwdep_dev_disconnect(struct snd_device *device)457{458	struct snd_hwdep *hwdep = device->device_data;459 460	if (snd_BUG_ON(!hwdep))461		return -ENXIO;462	guard(mutex)(&register_mutex);463	if (snd_hwdep_search(hwdep->card, hwdep->device) != hwdep)464		return -EINVAL;465	guard(mutex)(&hwdep->open_mutex);466	wake_up(&hwdep->open_wait);467#ifdef CONFIG_SND_OSSEMUL468	if (hwdep->ossreg)469		snd_unregister_oss_device(hwdep->oss_type, hwdep->card, hwdep->device);470#endif471	snd_unregister_device(hwdep->dev);472	list_del_init(&hwdep->list);473	return 0;474}475 476#ifdef CONFIG_SND_PROC_FS477/*478 *  Info interface479 */480 481static void snd_hwdep_proc_read(struct snd_info_entry *entry,482				struct snd_info_buffer *buffer)483{484	struct snd_hwdep *hwdep;485 486	guard(mutex)(&register_mutex);487	list_for_each_entry(hwdep, &snd_hwdep_devices, list)488		snd_iprintf(buffer, "%02i-%02i: %s\n",489			    hwdep->card->number, hwdep->device, hwdep->name);490}491 492static struct snd_info_entry *snd_hwdep_proc_entry;493 494static void __init snd_hwdep_proc_init(void)495{496	struct snd_info_entry *entry;497 498	entry = snd_info_create_module_entry(THIS_MODULE, "hwdep", NULL);499	if (entry) {500		entry->c.text.read = snd_hwdep_proc_read;501		if (snd_info_register(entry) < 0) {502			snd_info_free_entry(entry);503			entry = NULL;504		}505	}506	snd_hwdep_proc_entry = entry;507}508 509static void __exit snd_hwdep_proc_done(void)510{511	snd_info_free_entry(snd_hwdep_proc_entry);512}513#else /* !CONFIG_SND_PROC_FS */514#define snd_hwdep_proc_init()515#define snd_hwdep_proc_done()516#endif /* CONFIG_SND_PROC_FS */517 518 519/*520 *  ENTRY functions521 */522 523static int __init alsa_hwdep_init(void)524{525	snd_hwdep_proc_init();526	snd_ctl_register_ioctl(snd_hwdep_control_ioctl);527	snd_ctl_register_ioctl_compat(snd_hwdep_control_ioctl);528	return 0;529}530 531static void __exit alsa_hwdep_exit(void)532{533	snd_ctl_unregister_ioctl(snd_hwdep_control_ioctl);534	snd_ctl_unregister_ioctl_compat(snd_hwdep_control_ioctl);535	snd_hwdep_proc_done();536}537 538module_init(alsa_hwdep_init)539module_exit(alsa_hwdep_exit)540