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: Matthieu CASTET <matthieu.castet@parrot.com> Cc: <stable@vger.kernel.org> Signed-off-by: Takashi Iwai <tiwai@suse.de>
This commit is contained in:
parent
888ea7d5ac
commit
a0830dbd4e
|
@ -132,6 +132,7 @@ struct snd_card {
|
|||
int shutdown; /* this card is going down */
|
||||
int free_on_last_close; /* free in context of file_release */
|
||||
wait_queue_head_t shutdown_sleep;
|
||||
atomic_t refcount; /* refcount for disconnection */
|
||||
struct device *dev; /* device assigned to this card */
|
||||
struct device *card_dev; /* cardX object for sysfs */
|
||||
|
||||
|
@ -189,6 +190,7 @@ struct snd_minor {
|
|||
const struct file_operations *f_ops; /* file operations */
|
||||
void *private_data; /* private data for f_ops->open */
|
||||
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 */
|
||||
|
@ -295,6 +297,7 @@ int snd_card_info_done(void);
|
|||
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_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))
|
||||
|
||||
|
|
|
@ -100,12 +100,15 @@ static int snd_compr_open(struct inode *inode, struct file *f)
|
|||
|
||||
if (dirn != compr->direction) {
|
||||
pr_err("this device doesn't support this direction\n");
|
||||
snd_card_unref(compr->card);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
data = kzalloc(sizeof(*data), GFP_KERNEL);
|
||||
if (!data)
|
||||
if (!data) {
|
||||
snd_card_unref(compr->card);
|
||||
return -ENOMEM;
|
||||
}
|
||||
data->stream.ops = compr->ops;
|
||||
data->stream.direction = dirn;
|
||||
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);
|
||||
if (!runtime) {
|
||||
kfree(data);
|
||||
snd_card_unref(compr->card);
|
||||
return -ENOMEM;
|
||||
}
|
||||
runtime->state = SNDRV_PCM_STATE_OPEN;
|
||||
|
@ -126,7 +130,8 @@ static int snd_compr_open(struct inode *inode, struct file *f)
|
|||
kfree(runtime);
|
||||
kfree(data);
|
||||
}
|
||||
return ret;
|
||||
snd_card_unref(compr->card);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_compr_free(struct inode *inode, struct file *f)
|
||||
|
|
|
@ -86,6 +86,7 @@ static int snd_ctl_open(struct inode *inode, struct file *file)
|
|||
write_lock_irqsave(&card->ctl_files_rwlock, flags);
|
||||
list_add_tail(&ctl->list, &card->ctl_files);
|
||||
write_unlock_irqrestore(&card->ctl_files_rwlock, flags);
|
||||
snd_card_unref(card);
|
||||
return 0;
|
||||
|
||||
__error:
|
||||
|
@ -93,6 +94,8 @@ static int snd_ctl_open(struct inode *inode, struct file *file)
|
|||
__error2:
|
||||
snd_card_file_remove(card, file);
|
||||
__error1:
|
||||
if (card)
|
||||
snd_card_unref(card);
|
||||
return err;
|
||||
}
|
||||
|
||||
|
|
|
@ -100,8 +100,10 @@ static int snd_hwdep_open(struct inode *inode, struct file * file)
|
|||
if (hw == NULL)
|
||||
return -ENODEV;
|
||||
|
||||
if (!try_module_get(hw->card->module))
|
||||
if (!try_module_get(hw->card->module)) {
|
||||
snd_card_unref(hw->card);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
init_waitqueue_entry(&wait, current);
|
||||
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);
|
||||
if (err < 0)
|
||||
module_put(hw->card->module);
|
||||
snd_card_unref(hw->card);
|
||||
return err;
|
||||
}
|
||||
|
||||
|
|
|
@ -213,6 +213,7 @@ int snd_card_create(int idx, const char *xid,
|
|||
spin_lock_init(&card->files_lock);
|
||||
INIT_LIST_HEAD(&card->files_list);
|
||||
init_waitqueue_head(&card->shutdown_sleep);
|
||||
atomic_set(&card->refcount, 0);
|
||||
#ifdef CONFIG_PM
|
||||
mutex_init(&card->power_lock);
|
||||
init_waitqueue_head(&card->power_sleep);
|
||||
|
@ -446,21 +447,36 @@ static int snd_card_do_free(struct snd_card *card)
|
|||
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 free_now = 0;
|
||||
int ret = snd_card_disconnect(card);
|
||||
if (ret)
|
||||
int ret;
|
||||
|
||||
atomic_inc(&card->refcount);
|
||||
ret = snd_card_disconnect(card);
|
||||
if (ret) {
|
||||
atomic_dec(&card->refcount);
|
||||
return ret;
|
||||
}
|
||||
|
||||
spin_lock(&card->files_lock);
|
||||
if (list_empty(&card->files_list))
|
||||
free_now = 1;
|
||||
else
|
||||
card->free_on_last_close = 1;
|
||||
spin_unlock(&card->files_lock);
|
||||
|
||||
if (free_now)
|
||||
card->free_on_last_close = 1;
|
||||
if (atomic_dec_and_test(&card->refcount))
|
||||
snd_card_do_free(card);
|
||||
return 0;
|
||||
}
|
||||
|
@ -474,7 +490,7 @@ int snd_card_free(struct snd_card *card)
|
|||
return ret;
|
||||
|
||||
/* 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);
|
||||
return 0;
|
||||
}
|
||||
|
@ -886,6 +902,7 @@ int snd_card_file_add(struct snd_card *card, struct file *file)
|
|||
return -ENODEV;
|
||||
}
|
||||
list_add(&mfile->list, &card->files_list);
|
||||
atomic_inc(&card->refcount);
|
||||
spin_unlock(&card->files_lock);
|
||||
return 0;
|
||||
}
|
||||
|
@ -908,7 +925,6 @@ EXPORT_SYMBOL(snd_card_file_add);
|
|||
int snd_card_file_remove(struct snd_card *card, struct file *file)
|
||||
{
|
||||
struct snd_monitor_file *mfile, *found = NULL;
|
||||
int last_close = 0;
|
||||
|
||||
spin_lock(&card->files_lock);
|
||||
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;
|
||||
}
|
||||
}
|
||||
if (list_empty(&card->files_list))
|
||||
last_close = 1;
|
||||
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) {
|
||||
snd_printk(KERN_ERR "ALSA card file remove problem (%p)\n", file);
|
||||
return -ENOENT;
|
||||
}
|
||||
kfree(found);
|
||||
snd_card_unref(card);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -52,14 +52,19 @@ static int snd_mixer_oss_open(struct inode *inode, struct file *file)
|
|||
SNDRV_OSS_DEVICE_TYPE_MIXER);
|
||||
if (card == NULL)
|
||||
return -ENODEV;
|
||||
if (card->mixer_oss == NULL)
|
||||
if (card->mixer_oss == NULL) {
|
||||
snd_card_unref(card);
|
||||
return -ENODEV;
|
||||
}
|
||||
err = snd_card_file_add(card, file);
|
||||
if (err < 0)
|
||||
if (err < 0) {
|
||||
snd_card_unref(card);
|
||||
return err;
|
||||
}
|
||||
fmixer = kzalloc(sizeof(*fmixer), GFP_KERNEL);
|
||||
if (fmixer == NULL) {
|
||||
snd_card_file_remove(card, file);
|
||||
snd_card_unref(card);
|
||||
return -ENOMEM;
|
||||
}
|
||||
fmixer->card = card;
|
||||
|
@ -68,6 +73,7 @@ static int snd_mixer_oss_open(struct inode *inode, struct file *file)
|
|||
if (!try_module_get(card->module)) {
|
||||
kfree(fmixer);
|
||||
snd_card_file_remove(card, file);
|
||||
snd_card_unref(card);
|
||||
return -EFAULT;
|
||||
}
|
||||
return 0;
|
||||
|
|
|
@ -2457,6 +2457,8 @@ static int snd_pcm_oss_open(struct inode *inode, struct file *file)
|
|||
__error2:
|
||||
snd_card_file_remove(pcm->card, file);
|
||||
__error1:
|
||||
if (pcm)
|
||||
snd_card_unref(pcm->card);
|
||||
return err;
|
||||
}
|
||||
|
||||
|
|
|
@ -1642,6 +1642,7 @@ static int snd_pcm_link(struct snd_pcm_substream *substream, int fd)
|
|||
write_unlock_irq(&snd_pcm_link_rwlock);
|
||||
up_write(&snd_pcm_link_rwsem);
|
||||
_nolock:
|
||||
snd_card_unref(substream1->pcm->card);
|
||||
fput_light(file, fput_needed);
|
||||
if (res < 0)
|
||||
kfree(group);
|
||||
|
@ -2116,7 +2117,9 @@ static int snd_pcm_playback_open(struct inode *inode, struct file *file)
|
|||
return err;
|
||||
pcm = snd_lookup_minor_data(iminor(inode),
|
||||
SNDRV_DEVICE_TYPE_PCM_PLAYBACK);
|
||||
return snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_PLAYBACK);
|
||||
err = snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_PLAYBACK);
|
||||
snd_card_unref(pcm->card);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int snd_pcm_capture_open(struct inode *inode, struct file *file)
|
||||
|
@ -2127,7 +2130,9 @@ static int snd_pcm_capture_open(struct inode *inode, struct file *file)
|
|||
return err;
|
||||
pcm = snd_lookup_minor_data(iminor(inode),
|
||||
SNDRV_DEVICE_TYPE_PCM_CAPTURE);
|
||||
return snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_CAPTURE);
|
||||
err = snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_CAPTURE);
|
||||
snd_card_unref(pcm->card);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int snd_pcm_open(struct file *file, struct snd_pcm *pcm, int stream)
|
||||
|
|
|
@ -379,8 +379,10 @@ static int snd_rawmidi_open(struct inode *inode, struct file *file)
|
|||
if (rmidi == NULL)
|
||||
return -ENODEV;
|
||||
|
||||
if (!try_module_get(rmidi->card->module))
|
||||
if (!try_module_get(rmidi->card->module)) {
|
||||
snd_card_unref(rmidi->card);
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
mutex_lock(&rmidi->open_mutex);
|
||||
card = rmidi->card;
|
||||
|
@ -440,6 +442,7 @@ static int snd_rawmidi_open(struct inode *inode, struct file *file)
|
|||
#endif
|
||||
file->private_data = rawmidi_file;
|
||||
mutex_unlock(&rmidi->open_mutex);
|
||||
snd_card_unref(rmidi->card);
|
||||
return 0;
|
||||
|
||||
__error:
|
||||
|
@ -447,6 +450,7 @@ static int snd_rawmidi_open(struct inode *inode, struct file *file)
|
|||
__error_card:
|
||||
mutex_unlock(&rmidi->open_mutex);
|
||||
module_put(rmidi->card->module);
|
||||
snd_card_unref(rmidi->card);
|
||||
return err;
|
||||
}
|
||||
|
||||
|
|
|
@ -98,6 +98,10 @@ static void snd_request_other(int minor)
|
|||
*
|
||||
* Checks that a minor device with the specified type is registered, and returns
|
||||
* its user data pointer.
|
||||
*
|
||||
* This function increments the reference counter of the card instance
|
||||
* if an associated instance with the given minor number and type is found.
|
||||
* The caller must call snd_card_unref() appropriately later.
|
||||
*/
|
||||
void *snd_lookup_minor_data(unsigned int minor, int type)
|
||||
{
|
||||
|
@ -108,9 +112,11 @@ void *snd_lookup_minor_data(unsigned int minor, int type)
|
|||
return NULL;
|
||||
mutex_lock(&sound_mutex);
|
||||
mreg = snd_minors[minor];
|
||||
if (mreg && mreg->type == type)
|
||||
if (mreg && mreg->type == type) {
|
||||
private_data = mreg->private_data;
|
||||
else
|
||||
if (mreg->card_ptr)
|
||||
atomic_inc(&mreg->card_ptr->refcount);
|
||||
} else
|
||||
private_data = NULL;
|
||||
mutex_unlock(&sound_mutex);
|
||||
return private_data;
|
||||
|
@ -275,6 +281,7 @@ int snd_register_device_for_dev(int type, struct snd_card *card, int dev,
|
|||
preg->device = dev;
|
||||
preg->f_ops = f_ops;
|
||||
preg->private_data = private_data;
|
||||
preg->card_ptr = card;
|
||||
mutex_lock(&sound_mutex);
|
||||
#ifdef CONFIG_SND_DYNAMIC_MINORS
|
||||
minor = snd_find_free_minor(type);
|
||||
|
|
|
@ -40,6 +40,9 @@
|
|||
static struct snd_minor *snd_oss_minors[SNDRV_OSS_MINORS];
|
||||
static DEFINE_MUTEX(sound_oss_mutex);
|
||||
|
||||
/* NOTE: This function increments the refcount of the associated card like
|
||||
* snd_lookup_minor_data(); the caller must call snd_card_unref() appropriately
|
||||
*/
|
||||
void *snd_lookup_oss_minor_data(unsigned int minor, int type)
|
||||
{
|
||||
struct snd_minor *mreg;
|
||||
|
@ -49,9 +52,11 @@ void *snd_lookup_oss_minor_data(unsigned int minor, int type)
|
|||
return NULL;
|
||||
mutex_lock(&sound_oss_mutex);
|
||||
mreg = snd_oss_minors[minor];
|
||||
if (mreg && mreg->type == type)
|
||||
if (mreg && mreg->type == type) {
|
||||
private_data = mreg->private_data;
|
||||
else
|
||||
if (mreg->card_ptr)
|
||||
atomic_inc(&mreg->card_ptr->refcount);
|
||||
} else
|
||||
private_data = NULL;
|
||||
mutex_unlock(&sound_oss_mutex);
|
||||
return private_data;
|
||||
|
@ -123,6 +128,7 @@ int snd_register_oss_device(int type, struct snd_card *card, int dev,
|
|||
preg->device = dev;
|
||||
preg->f_ops = f_ops;
|
||||
preg->private_data = private_data;
|
||||
preg->card_ptr = card;
|
||||
mutex_lock(&sound_oss_mutex);
|
||||
snd_oss_minors[minor] = preg;
|
||||
minor_unit = SNDRV_MINOR_OSS_DEVICE(minor);
|
||||
|
|
Loading…
Reference in New Issue