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:
Takashi Iwai 2012-10-16 13:05:59 +02:00
parent 888ea7d5ac
commit a0830dbd4e
11 changed files with 86 additions and 32 deletions

View File

@ -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))

View File

@ -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)

View File

@ -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;
} }

View File

@ -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;
} }

View File

@ -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); card->free_on_last_close = 1;
if (list_empty(&card->files_list)) if (atomic_dec_and_test(&card->refcount))
free_now = 1;
else
card->free_on_last_close = 1;
spin_unlock(&card->files_lock);
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;
} }

View File

@ -52,14 +52,19 @@ static int snd_mixer_oss_open(struct inode *inode, struct file *file)
SNDRV_OSS_DEVICE_TYPE_MIXER); SNDRV_OSS_DEVICE_TYPE_MIXER);
if (card == NULL) if (card == NULL)
return -ENODEV; return -ENODEV;
if (card->mixer_oss == NULL) if (card->mixer_oss == NULL) {
snd_card_unref(card);
return -ENODEV; return -ENODEV;
}
err = snd_card_file_add(card, file); err = snd_card_file_add(card, file);
if (err < 0) if (err < 0) {
snd_card_unref(card);
return err; return err;
}
fmixer = kzalloc(sizeof(*fmixer), GFP_KERNEL); fmixer = kzalloc(sizeof(*fmixer), GFP_KERNEL);
if (fmixer == NULL) { if (fmixer == NULL) {
snd_card_file_remove(card, file); snd_card_file_remove(card, file);
snd_card_unref(card);
return -ENOMEM; return -ENOMEM;
} }
fmixer->card = card; 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)) { if (!try_module_get(card->module)) {
kfree(fmixer); kfree(fmixer);
snd_card_file_remove(card, file); snd_card_file_remove(card, file);
snd_card_unref(card);
return -EFAULT; return -EFAULT;
} }
return 0; return 0;

View File

@ -2457,6 +2457,8 @@ static int snd_pcm_oss_open(struct inode *inode, struct file *file)
__error2: __error2:
snd_card_file_remove(pcm->card, file); snd_card_file_remove(pcm->card, file);
__error1: __error1:
if (pcm)
snd_card_unref(pcm->card);
return err; return err;
} }

View File

@ -1642,6 +1642,7 @@ static int snd_pcm_link(struct snd_pcm_substream *substream, int fd)
write_unlock_irq(&snd_pcm_link_rwlock); write_unlock_irq(&snd_pcm_link_rwlock);
up_write(&snd_pcm_link_rwsem); up_write(&snd_pcm_link_rwsem);
_nolock: _nolock:
snd_card_unref(substream1->pcm->card);
fput_light(file, fput_needed); fput_light(file, fput_needed);
if (res < 0) if (res < 0)
kfree(group); kfree(group);
@ -2116,7 +2117,9 @@ static int snd_pcm_playback_open(struct inode *inode, struct file *file)
return err; return err;
pcm = snd_lookup_minor_data(iminor(inode), pcm = snd_lookup_minor_data(iminor(inode),
SNDRV_DEVICE_TYPE_PCM_PLAYBACK); 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) 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; return err;
pcm = snd_lookup_minor_data(iminor(inode), pcm = snd_lookup_minor_data(iminor(inode),
SNDRV_DEVICE_TYPE_PCM_CAPTURE); 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) static int snd_pcm_open(struct file *file, struct snd_pcm *pcm, int stream)

View File

@ -379,8 +379,10 @@ static int snd_rawmidi_open(struct inode *inode, struct file *file)
if (rmidi == NULL) if (rmidi == NULL)
return -ENODEV; return -ENODEV;
if (!try_module_get(rmidi->card->module)) if (!try_module_get(rmidi->card->module)) {
snd_card_unref(rmidi->card);
return -ENXIO; return -ENXIO;
}
mutex_lock(&rmidi->open_mutex); mutex_lock(&rmidi->open_mutex);
card = rmidi->card; card = rmidi->card;
@ -440,6 +442,7 @@ static int snd_rawmidi_open(struct inode *inode, struct file *file)
#endif #endif
file->private_data = rawmidi_file; file->private_data = rawmidi_file;
mutex_unlock(&rmidi->open_mutex); mutex_unlock(&rmidi->open_mutex);
snd_card_unref(rmidi->card);
return 0; return 0;
__error: __error:
@ -447,6 +450,7 @@ static int snd_rawmidi_open(struct inode *inode, struct file *file)
__error_card: __error_card:
mutex_unlock(&rmidi->open_mutex); mutex_unlock(&rmidi->open_mutex);
module_put(rmidi->card->module); module_put(rmidi->card->module);
snd_card_unref(rmidi->card);
return err; return err;
} }

View File

@ -98,6 +98,10 @@ static void snd_request_other(int minor)
* *
* Checks that a minor device with the specified type is registered, and returns * Checks that a minor device with the specified type is registered, and returns
* its user data pointer. * 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) 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; return NULL;
mutex_lock(&sound_mutex); mutex_lock(&sound_mutex);
mreg = snd_minors[minor]; mreg = snd_minors[minor];
if (mreg && mreg->type == type) if (mreg && mreg->type == type) {
private_data = mreg->private_data; private_data = mreg->private_data;
else if (mreg->card_ptr)
atomic_inc(&mreg->card_ptr->refcount);
} else
private_data = NULL; private_data = NULL;
mutex_unlock(&sound_mutex); mutex_unlock(&sound_mutex);
return private_data; 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->device = dev;
preg->f_ops = f_ops; preg->f_ops = f_ops;
preg->private_data = private_data; preg->private_data = private_data;
preg->card_ptr = card;
mutex_lock(&sound_mutex); mutex_lock(&sound_mutex);
#ifdef CONFIG_SND_DYNAMIC_MINORS #ifdef CONFIG_SND_DYNAMIC_MINORS
minor = snd_find_free_minor(type); minor = snd_find_free_minor(type);

View File

@ -40,6 +40,9 @@
static struct snd_minor *snd_oss_minors[SNDRV_OSS_MINORS]; static struct snd_minor *snd_oss_minors[SNDRV_OSS_MINORS];
static DEFINE_MUTEX(sound_oss_mutex); 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) void *snd_lookup_oss_minor_data(unsigned int minor, int type)
{ {
struct snd_minor *mreg; struct snd_minor *mreg;
@ -49,9 +52,11 @@ void *snd_lookup_oss_minor_data(unsigned int minor, int type)
return NULL; return NULL;
mutex_lock(&sound_oss_mutex); mutex_lock(&sound_oss_mutex);
mreg = snd_oss_minors[minor]; mreg = snd_oss_minors[minor];
if (mreg && mreg->type == type) if (mreg && mreg->type == type) {
private_data = mreg->private_data; private_data = mreg->private_data;
else if (mreg->card_ptr)
atomic_inc(&mreg->card_ptr->refcount);
} else
private_data = NULL; private_data = NULL;
mutex_unlock(&sound_oss_mutex); mutex_unlock(&sound_oss_mutex);
return private_data; return private_data;
@ -123,6 +128,7 @@ int snd_register_oss_device(int type, struct snd_card *card, int dev,
preg->device = dev; preg->device = dev;
preg->f_ops = f_ops; preg->f_ops = f_ops;
preg->private_data = private_data; preg->private_data = private_data;
preg->card_ptr = card;
mutex_lock(&sound_oss_mutex); mutex_lock(&sound_oss_mutex);
snd_oss_minors[minor] = preg; snd_oss_minors[minor] = preg;
minor_unit = SNDRV_MINOR_OSS_DEVICE(minor); minor_unit = SNDRV_MINOR_OSS_DEVICE(minor);