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

Commit 3a3385ad authored by Takashi Iwai's avatar Takashi Iwai Committed by Greg Kroah-Hartman
Browse files

ALSA: timer: Fix mutex deadlock at releasing card



[ Upstream commit a39331867335d4a94b6165e306265c9e24aca073 ]

When a card is disconnected while in use, the system waits until all
opened files are closed then releases the card.  This is done via
put_device() of the card device in each device release code.

The recently reported mutex deadlock bug happens in this code path;
snd_timer_close() for the timer device deals with the global
register_mutex and it calls put_device() there.  When this timer
device is the last one, the card gets freed and it eventually calls
snd_timer_free(), which has again the protection with the global
register_mutex -- boom.

Basically put_device() call itself is race-free, so a relative simple
workaround is to move this put_device() call out of the mutex.  For
achieving that, in this patch, snd_timer_close_locked() got a new
argument to store the card device pointer in return, and each caller
invokes put_device() with the returned object after the mutex unlock.

Reported-and-tested-by: default avatarKirill A. Shutemov <kirill.shutemov@linux.intel.com>
Cc: <stable@vger.kernel.org>
Signed-off-by: default avatarTakashi Iwai <tiwai@suse.de>
Signed-off-by: default avatarSasha Levin <sashal@kernel.org>
parent 681789d5
Loading
Loading
Loading
Loading
+17 −7
Original line number Original line Diff line number Diff line
@@ -239,7 +239,8 @@ static int snd_timer_check_master(struct snd_timer_instance *master)
	return 0;
	return 0;
}
}


static int snd_timer_close_locked(struct snd_timer_instance *timeri);
static int snd_timer_close_locked(struct snd_timer_instance *timeri,
				  struct device **card_devp_to_put);


/*
/*
 * open a timer instance
 * open a timer instance
@@ -251,6 +252,7 @@ int snd_timer_open(struct snd_timer_instance **ti,
{
{
	struct snd_timer *timer;
	struct snd_timer *timer;
	struct snd_timer_instance *timeri = NULL;
	struct snd_timer_instance *timeri = NULL;
	struct device *card_dev_to_put = NULL;
	int err;
	int err;


	mutex_lock(&register_mutex);
	mutex_lock(&register_mutex);
@@ -274,7 +276,7 @@ int snd_timer_open(struct snd_timer_instance **ti,
		list_add_tail(&timeri->open_list, &snd_timer_slave_list);
		list_add_tail(&timeri->open_list, &snd_timer_slave_list);
		err = snd_timer_check_slave(timeri);
		err = snd_timer_check_slave(timeri);
		if (err < 0) {
		if (err < 0) {
			snd_timer_close_locked(timeri);
			snd_timer_close_locked(timeri, &card_dev_to_put);
			timeri = NULL;
			timeri = NULL;
		}
		}
		goto unlock;
		goto unlock;
@@ -326,7 +328,7 @@ int snd_timer_open(struct snd_timer_instance **ti,
			timeri = NULL;
			timeri = NULL;


			if (timer->card)
			if (timer->card)
				put_device(&timer->card->card_dev);
				card_dev_to_put = &timer->card->card_dev;
			module_put(timer->module);
			module_put(timer->module);
			goto unlock;
			goto unlock;
		}
		}
@@ -336,12 +338,15 @@ int snd_timer_open(struct snd_timer_instance **ti,
	timer->num_instances++;
	timer->num_instances++;
	err = snd_timer_check_master(timeri);
	err = snd_timer_check_master(timeri);
	if (err < 0) {
	if (err < 0) {
		snd_timer_close_locked(timeri);
		snd_timer_close_locked(timeri, &card_dev_to_put);
		timeri = NULL;
		timeri = NULL;
	}
	}


 unlock:
 unlock:
	mutex_unlock(&register_mutex);
	mutex_unlock(&register_mutex);
	/* put_device() is called after unlock for avoiding deadlock */
	if (card_dev_to_put)
		put_device(card_dev_to_put);
	*ti = timeri;
	*ti = timeri;
	return err;
	return err;
}
}
@@ -351,7 +356,8 @@ EXPORT_SYMBOL(snd_timer_open);
 * close a timer instance
 * close a timer instance
 * call this with register_mutex down.
 * call this with register_mutex down.
 */
 */
static int snd_timer_close_locked(struct snd_timer_instance *timeri)
static int snd_timer_close_locked(struct snd_timer_instance *timeri,
				  struct device **card_devp_to_put)
{
{
	struct snd_timer *timer = NULL;
	struct snd_timer *timer = NULL;
	struct snd_timer_instance *slave, *tmp;
	struct snd_timer_instance *slave, *tmp;
@@ -403,7 +409,7 @@ static int snd_timer_close_locked(struct snd_timer_instance *timeri)
			timer->hw.close(timer);
			timer->hw.close(timer);
		/* release a card refcount for safe disconnection */
		/* release a card refcount for safe disconnection */
		if (timer->card)
		if (timer->card)
			put_device(&timer->card->card_dev);
			*card_devp_to_put = &timer->card->card_dev;
		module_put(timer->module);
		module_put(timer->module);
	}
	}


@@ -415,14 +421,18 @@ static int snd_timer_close_locked(struct snd_timer_instance *timeri)
 */
 */
int snd_timer_close(struct snd_timer_instance *timeri)
int snd_timer_close(struct snd_timer_instance *timeri)
{
{
	struct device *card_dev_to_put = NULL;
	int err;
	int err;


	if (snd_BUG_ON(!timeri))
	if (snd_BUG_ON(!timeri))
		return -ENXIO;
		return -ENXIO;


	mutex_lock(&register_mutex);
	mutex_lock(&register_mutex);
	err = snd_timer_close_locked(timeri);
	err = snd_timer_close_locked(timeri, &card_dev_to_put);
	mutex_unlock(&register_mutex);
	mutex_unlock(&register_mutex);
	/* put_device() is called after unlock for avoiding deadlock */
	if (card_dev_to_put)
		put_device(card_dev_to_put);
	return err;
	return err;
}
}
EXPORT_SYMBOL(snd_timer_close);
EXPORT_SYMBOL(snd_timer_close);