Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a0830dbd authored by Takashi Iwai's avatar Takashi Iwai
Browse files

ALSA: Add a reference counter to card instance



For more strict protection for wild disconnections, a refcount is
introduced to the card instance, and let it up/down when an object is
referred via snd_lookup_*() in the open ops.

The free-after-last-close check is also changed to check this refcount
instead of the empty list, too.

Reported-by: default avatarMatthieu CASTET <matthieu.castet@parrot.com>
Cc: <stable@vger.kernel.org>
Signed-off-by: default avatarTakashi Iwai <tiwai@suse.de>
parent 888ea7d5
Loading
Loading
Loading
Loading
+3 −0
Original line number Original line Diff line number Diff line
@@ -132,6 +132,7 @@ struct snd_card {
	int shutdown;			/* this card is going down */
	int shutdown;			/* this card is going down */
	int free_on_last_close;		/* free in context of file_release */
	int free_on_last_close;		/* free in context of file_release */
	wait_queue_head_t shutdown_sleep;
	wait_queue_head_t shutdown_sleep;
	atomic_t refcount;		/* refcount for disconnection */
	struct device *dev;		/* device assigned to this card */
	struct device *dev;		/* device assigned to this card */
	struct device *card_dev;	/* cardX object for sysfs */
	struct device *card_dev;	/* cardX object for sysfs */


@@ -189,6 +190,7 @@ struct snd_minor {
	const struct file_operations *f_ops;	/* file operations */
	const struct file_operations *f_ops;	/* file operations */
	void *private_data;		/* private data for f_ops->open */
	void *private_data;		/* private data for f_ops->open */
	struct device *dev;		/* device for sysfs */
	struct device *dev;		/* device for sysfs */
	struct snd_card *card_ptr;	/* assigned card instance */
};
};


/* return a device pointer linked to each sound device as a parent */
/* return a device pointer linked to each sound device as a parent */
@@ -295,6 +297,7 @@ int snd_card_info_done(void);
int snd_component_add(struct snd_card *card, const char *component);
int snd_component_add(struct snd_card *card, const char *component);
int snd_card_file_add(struct snd_card *card, struct file *file);
int snd_card_file_add(struct snd_card *card, struct file *file);
int snd_card_file_remove(struct snd_card *card, struct file *file);
int snd_card_file_remove(struct snd_card *card, struct file *file);
void snd_card_unref(struct snd_card *card);


#define snd_card_set_dev(card, devptr) ((card)->dev = (devptr))
#define snd_card_set_dev(card, devptr) ((card)->dev = (devptr))


+7 −2
Original line number Original line Diff line number Diff line
@@ -100,12 +100,15 @@ static int snd_compr_open(struct inode *inode, struct file *f)


	if (dirn != compr->direction) {
	if (dirn != compr->direction) {
		pr_err("this device doesn't support this direction\n");
		pr_err("this device doesn't support this direction\n");
		snd_card_unref(compr->card);
		return -EINVAL;
		return -EINVAL;
	}
	}


	data = kzalloc(sizeof(*data), GFP_KERNEL);
	data = kzalloc(sizeof(*data), GFP_KERNEL);
	if (!data)
	if (!data) {
		snd_card_unref(compr->card);
		return -ENOMEM;
		return -ENOMEM;
	}
	data->stream.ops = compr->ops;
	data->stream.ops = compr->ops;
	data->stream.direction = dirn;
	data->stream.direction = dirn;
	data->stream.private_data = compr->private_data;
	data->stream.private_data = compr->private_data;
@@ -113,6 +116,7 @@ static int snd_compr_open(struct inode *inode, struct file *f)
	runtime = kzalloc(sizeof(*runtime), GFP_KERNEL);
	runtime = kzalloc(sizeof(*runtime), GFP_KERNEL);
	if (!runtime) {
	if (!runtime) {
		kfree(data);
		kfree(data);
		snd_card_unref(compr->card);
		return -ENOMEM;
		return -ENOMEM;
	}
	}
	runtime->state = SNDRV_PCM_STATE_OPEN;
	runtime->state = SNDRV_PCM_STATE_OPEN;
@@ -126,7 +130,8 @@ static int snd_compr_open(struct inode *inode, struct file *f)
		kfree(runtime);
		kfree(runtime);
		kfree(data);
		kfree(data);
	}
	}
	return ret;
	snd_card_unref(compr->card);
	return 0;
}
}


static int snd_compr_free(struct inode *inode, struct file *f)
static int snd_compr_free(struct inode *inode, struct file *f)
+3 −0
Original line number Original line Diff line number Diff line
@@ -86,6 +86,7 @@ static int snd_ctl_open(struct inode *inode, struct file *file)
	write_lock_irqsave(&card->ctl_files_rwlock, flags);
	write_lock_irqsave(&card->ctl_files_rwlock, flags);
	list_add_tail(&ctl->list, &card->ctl_files);
	list_add_tail(&ctl->list, &card->ctl_files);
	write_unlock_irqrestore(&card->ctl_files_rwlock, flags);
	write_unlock_irqrestore(&card->ctl_files_rwlock, flags);
	snd_card_unref(card);
	return 0;
	return 0;


      __error:
      __error:
@@ -93,6 +94,8 @@ static int snd_ctl_open(struct inode *inode, struct file *file)
      __error2:
      __error2:
	snd_card_file_remove(card, file);
	snd_card_file_remove(card, file);
      __error1:
      __error1:
	if (card)
		snd_card_unref(card);
      	return err;
      	return err;
}
}


+4 −1
Original line number Original line Diff line number Diff line
@@ -100,8 +100,10 @@ static int snd_hwdep_open(struct inode *inode, struct file * file)
	if (hw == NULL)
	if (hw == NULL)
		return -ENODEV;
		return -ENODEV;


	if (!try_module_get(hw->card->module))
	if (!try_module_get(hw->card->module)) {
		snd_card_unref(hw->card);
		return -EFAULT;
		return -EFAULT;
	}


	init_waitqueue_entry(&wait, current);
	init_waitqueue_entry(&wait, current);
	add_wait_queue(&hw->open_wait, &wait);
	add_wait_queue(&hw->open_wait, &wait);
@@ -148,6 +150,7 @@ static int snd_hwdep_open(struct inode *inode, struct file * file)
	mutex_unlock(&hw->open_mutex);
	mutex_unlock(&hw->open_mutex);
	if (err < 0)
	if (err < 0)
		module_put(hw->card->module);
		module_put(hw->card->module);
	snd_card_unref(hw->card);
	return err;
	return err;
}
}


+30 −20
Original line number Original line Diff line number Diff line
@@ -213,6 +213,7 @@ int snd_card_create(int idx, const char *xid,
	spin_lock_init(&card->files_lock);
	spin_lock_init(&card->files_lock);
	INIT_LIST_HEAD(&card->files_list);
	INIT_LIST_HEAD(&card->files_list);
	init_waitqueue_head(&card->shutdown_sleep);
	init_waitqueue_head(&card->shutdown_sleep);
	atomic_set(&card->refcount, 0);
#ifdef CONFIG_PM
#ifdef CONFIG_PM
	mutex_init(&card->power_lock);
	mutex_init(&card->power_lock);
	init_waitqueue_head(&card->power_sleep);
	init_waitqueue_head(&card->power_sleep);
@@ -446,21 +447,36 @@ static int snd_card_do_free(struct snd_card *card)
	return 0;
	return 0;
}
}


/**
 * snd_card_unref - release the reference counter
 * @card: the card instance
 *
 * Decrements the reference counter.  When it reaches to zero, wake up
 * the sleeper and call the destructor if needed.
 */
void snd_card_unref(struct snd_card *card)
{
	if (atomic_dec_and_test(&card->refcount)) {
		wake_up(&card->shutdown_sleep);
		if (card->free_on_last_close)
			snd_card_do_free(card);
	}
}
EXPORT_SYMBOL(snd_card_unref);

int snd_card_free_when_closed(struct snd_card *card)
int snd_card_free_when_closed(struct snd_card *card)
{
{
	int free_now = 0;
	int ret;
	int ret = snd_card_disconnect(card);

	if (ret)
	atomic_inc(&card->refcount);
	ret = snd_card_disconnect(card);
	if (ret) {
		atomic_dec(&card->refcount);
		return ret;
		return ret;
	}


	spin_lock(&card->files_lock);
	if (list_empty(&card->files_list))
		free_now = 1;
	else
	card->free_on_last_close = 1;
	card->free_on_last_close = 1;
	spin_unlock(&card->files_lock);
	if (atomic_dec_and_test(&card->refcount))

	if (free_now)
		snd_card_do_free(card);
		snd_card_do_free(card);
	return 0;
	return 0;
}
}
@@ -474,7 +490,7 @@ int snd_card_free(struct snd_card *card)
		return ret;
		return ret;


	/* wait, until all devices are ready for the free operation */
	/* wait, until all devices are ready for the free operation */
	wait_event(card->shutdown_sleep, list_empty(&card->files_list));
	wait_event(card->shutdown_sleep, !atomic_read(&card->refcount));
	snd_card_do_free(card);
	snd_card_do_free(card);
	return 0;
	return 0;
}
}
@@ -886,6 +902,7 @@ int snd_card_file_add(struct snd_card *card, struct file *file)
		return -ENODEV;
		return -ENODEV;
	}
	}
	list_add(&mfile->list, &card->files_list);
	list_add(&mfile->list, &card->files_list);
	atomic_inc(&card->refcount);
	spin_unlock(&card->files_lock);
	spin_unlock(&card->files_lock);
	return 0;
	return 0;
}
}
@@ -908,7 +925,6 @@ EXPORT_SYMBOL(snd_card_file_add);
int snd_card_file_remove(struct snd_card *card, struct file *file)
int snd_card_file_remove(struct snd_card *card, struct file *file)
{
{
	struct snd_monitor_file *mfile, *found = NULL;
	struct snd_monitor_file *mfile, *found = NULL;
	int last_close = 0;


	spin_lock(&card->files_lock);
	spin_lock(&card->files_lock);
	list_for_each_entry(mfile, &card->files_list, list) {
	list_for_each_entry(mfile, &card->files_list, list) {
@@ -923,19 +939,13 @@ int snd_card_file_remove(struct snd_card *card, struct file *file)
			break;
			break;
		}
		}
	}
	}
	if (list_empty(&card->files_list))
		last_close = 1;
	spin_unlock(&card->files_lock);
	spin_unlock(&card->files_lock);
	if (last_close) {
		wake_up(&card->shutdown_sleep);
		if (card->free_on_last_close)
			snd_card_do_free(card);
	}
	if (!found) {
	if (!found) {
		snd_printk(KERN_ERR "ALSA card file remove problem (%p)\n", file);
		snd_printk(KERN_ERR "ALSA card file remove problem (%p)\n", file);
		return -ENOENT;
		return -ENOENT;
	}
	}
	kfree(found);
	kfree(found);
	snd_card_unref(card);
	return 0;
	return 0;
}
}


Loading