Commit 205d6bcf authored by Takashi Iwai's avatar Takashi Iwai
Browse files

Merge branch 'topic/pcm-lock-refactor' into for-next



Pull PCM lock refactoring.

Signed-off-by: default avatarTakashi Iwai <tiwai@suse.de>
parents 3a554371 ef2056b8
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@
#include <linux/mm.h>
#include <linux/bitops.h>
#include <linux/pm_qos.h>
#include <linux/refcount.h>

#define snd_pcm_substream_chip(substream) ((substream)->private_data)
#define snd_pcm_chip(pcm) ((pcm)->private_data)
@@ -439,7 +440,7 @@ struct snd_pcm_group { /* keep linked substreams */
	spinlock_t lock;
	struct mutex mutex;
	struct list_head substreams;
	int count;
	refcount_t refs;
};

struct pid;
+1 −3
Original line number Diff line number Diff line
@@ -733,9 +733,7 @@ int snd_pcm_new_stream(struct snd_pcm *pcm, int stream, int substream_count)
			}
		}
		substream->group = &substream->self_group;
		spin_lock_init(&substream->self_group.lock);
		mutex_init(&substream->self_group.mutex);
		INIT_LIST_HEAD(&substream->self_group.substreams);
		snd_pcm_group_init(&substream->self_group);
		list_add_tail(&substream->link_list, &substream->self_group.substreams);
		atomic_set(&substream->mmap_count, 0);
		prev = substream;
+1 −0
Original line number Diff line number Diff line
@@ -66,5 +66,6 @@ static inline void snd_pcm_timer_done(struct snd_pcm_substream *substream) {}
#endif

void __snd_pcm_xrun(struct snd_pcm_substream *substream);
void snd_pcm_group_init(struct snd_pcm_group *group);

#endif	/* __SOUND_CORE_PCM_LOCAL_H */
+167 −123
Original line number Diff line number Diff line
@@ -85,71 +85,30 @@ static int snd_pcm_open(struct file *file, struct snd_pcm *pcm, int stream);
 *
 */

static DEFINE_RWLOCK(snd_pcm_link_rwlock);
static DECLARE_RWSEM(snd_pcm_link_rwsem);

/* Writer in rwsem may block readers even during its waiting in queue,
 * and this may lead to a deadlock when the code path takes read sem
 * twice (e.g. one in snd_pcm_action_nonatomic() and another in
 * snd_pcm_stream_lock()).  As a (suboptimal) workaround, let writer to
 * sleep until all the readers are completed without blocking by writer.
 */
static inline void down_write_nonfifo(struct rw_semaphore *lock)
void snd_pcm_group_init(struct snd_pcm_group *group)
{
	while (!down_write_trylock(lock))
		msleep(1);
	spin_lock_init(&group->lock);
	mutex_init(&group->mutex);
	INIT_LIST_HEAD(&group->substreams);
	refcount_set(&group->refs, 0);
}

#define PCM_LOCK_DEFAULT	0
#define PCM_LOCK_IRQ	1
#define PCM_LOCK_IRQSAVE	2

static unsigned long __snd_pcm_stream_lock_mode(struct snd_pcm_substream *substream,
						unsigned int mode)
{
	unsigned long flags = 0;
	if (substream->pcm->nonatomic) {
		down_read_nested(&snd_pcm_link_rwsem, SINGLE_DEPTH_NESTING);
		mutex_lock(&substream->self_group.mutex);
	} else {
		switch (mode) {
		case PCM_LOCK_DEFAULT:
			read_lock(&snd_pcm_link_rwlock);
			break;
		case PCM_LOCK_IRQ:
			read_lock_irq(&snd_pcm_link_rwlock);
			break;
		case PCM_LOCK_IRQSAVE:
			read_lock_irqsave(&snd_pcm_link_rwlock, flags);
			break;
		}
		spin_lock(&substream->self_group.lock);
/* define group lock helpers */
#define DEFINE_PCM_GROUP_LOCK(action, mutex_action) \
static void snd_pcm_group_ ## action(struct snd_pcm_group *group, bool nonatomic) \
{ \
	if (nonatomic) \
		mutex_ ## mutex_action(&group->mutex); \
	else \
		spin_ ## action(&group->lock); \
}
	return flags;
}

static void __snd_pcm_stream_unlock_mode(struct snd_pcm_substream *substream,
					 unsigned int mode, unsigned long flags)
{
	if (substream->pcm->nonatomic) {
		mutex_unlock(&substream->self_group.mutex);
		up_read(&snd_pcm_link_rwsem);
	} else {
		spin_unlock(&substream->self_group.lock);

		switch (mode) {
		case PCM_LOCK_DEFAULT:
			read_unlock(&snd_pcm_link_rwlock);
			break;
		case PCM_LOCK_IRQ:
			read_unlock_irq(&snd_pcm_link_rwlock);
			break;
		case PCM_LOCK_IRQSAVE:
			read_unlock_irqrestore(&snd_pcm_link_rwlock, flags);
			break;
		}
	}
}
DEFINE_PCM_GROUP_LOCK(lock, lock);
DEFINE_PCM_GROUP_LOCK(unlock, unlock);
DEFINE_PCM_GROUP_LOCK(lock_irq, lock);
DEFINE_PCM_GROUP_LOCK(unlock_irq, unlock);

/**
 * snd_pcm_stream_lock - Lock the PCM stream
@@ -161,7 +120,7 @@ static void __snd_pcm_stream_unlock_mode(struct snd_pcm_substream *substream,
 */
void snd_pcm_stream_lock(struct snd_pcm_substream *substream)
{
	__snd_pcm_stream_lock_mode(substream, PCM_LOCK_DEFAULT);
	snd_pcm_group_lock(&substream->self_group, substream->pcm->nonatomic);
}
EXPORT_SYMBOL_GPL(snd_pcm_stream_lock);

@@ -173,7 +132,7 @@ EXPORT_SYMBOL_GPL(snd_pcm_stream_lock);
 */
void snd_pcm_stream_unlock(struct snd_pcm_substream *substream)
{
	__snd_pcm_stream_unlock_mode(substream, PCM_LOCK_DEFAULT, 0);
	snd_pcm_group_unlock(&substream->self_group, substream->pcm->nonatomic);
}
EXPORT_SYMBOL_GPL(snd_pcm_stream_unlock);

@@ -187,7 +146,8 @@ EXPORT_SYMBOL_GPL(snd_pcm_stream_unlock);
 */
void snd_pcm_stream_lock_irq(struct snd_pcm_substream *substream)
{
	__snd_pcm_stream_lock_mode(substream, PCM_LOCK_IRQ);
	snd_pcm_group_lock_irq(&substream->self_group,
			       substream->pcm->nonatomic);
}
EXPORT_SYMBOL_GPL(snd_pcm_stream_lock_irq);

@@ -199,13 +159,19 @@ EXPORT_SYMBOL_GPL(snd_pcm_stream_lock_irq);
 */
void snd_pcm_stream_unlock_irq(struct snd_pcm_substream *substream)
{
	__snd_pcm_stream_unlock_mode(substream, PCM_LOCK_IRQ, 0);
	snd_pcm_group_unlock_irq(&substream->self_group,
				 substream->pcm->nonatomic);
}
EXPORT_SYMBOL_GPL(snd_pcm_stream_unlock_irq);

unsigned long _snd_pcm_stream_lock_irqsave(struct snd_pcm_substream *substream)
{
	return __snd_pcm_stream_lock_mode(substream, PCM_LOCK_IRQSAVE);
	unsigned long flags = 0;
	if (substream->pcm->nonatomic)
		mutex_lock(&substream->self_group.mutex);
	else
		spin_lock_irqsave(&substream->self_group.lock, flags);
	return flags;
}
EXPORT_SYMBOL_GPL(_snd_pcm_stream_lock_irqsave);

@@ -219,7 +185,10 @@ EXPORT_SYMBOL_GPL(_snd_pcm_stream_lock_irqsave);
void snd_pcm_stream_unlock_irqrestore(struct snd_pcm_substream *substream,
				      unsigned long flags)
{
	__snd_pcm_stream_unlock_mode(substream, PCM_LOCK_IRQSAVE, flags);
	if (substream->pcm->nonatomic)
		mutex_unlock(&substream->self_group.mutex);
	else
		spin_unlock_irqrestore(&substream->self_group.lock, flags);
}
EXPORT_SYMBOL_GPL(snd_pcm_stream_unlock_irqrestore);

@@ -1124,6 +1093,68 @@ static int snd_pcm_action_single(const struct action_ops *ops,
	return res;
}

static void snd_pcm_group_assign(struct snd_pcm_substream *substream,
				 struct snd_pcm_group *new_group)
{
	substream->group = new_group;
	list_move(&substream->link_list, &new_group->substreams);
}

/*
 * Unref and unlock the group, but keep the stream lock;
 * when the group becomes empty and no longer referred, destroy itself
 */
static void snd_pcm_group_unref(struct snd_pcm_group *group,
				struct snd_pcm_substream *substream)
{
	bool do_free;

	if (!group)
		return;
	do_free = refcount_dec_and_test(&group->refs) &&
		list_empty(&group->substreams);
	snd_pcm_group_unlock(group, substream->pcm->nonatomic);
	if (do_free)
		kfree(group);
}

/*
 * Lock the group inside a stream lock and reference it;
 * return the locked group object, or NULL if not linked
 */
static struct snd_pcm_group *
snd_pcm_stream_group_ref(struct snd_pcm_substream *substream)
{
	bool nonatomic = substream->pcm->nonatomic;
	struct snd_pcm_group *group;
	bool trylock;

	for (;;) {
		if (!snd_pcm_stream_linked(substream))
			return NULL;
		group = substream->group;
		/* block freeing the group object */
		refcount_inc(&group->refs);

		trylock = nonatomic ? mutex_trylock(&group->mutex) :
			spin_trylock(&group->lock);
		if (trylock)
			break; /* OK */

		/* re-lock for avoiding ABBA deadlock */
		snd_pcm_stream_unlock(substream);
		snd_pcm_group_lock(group, nonatomic);
		snd_pcm_stream_lock(substream);

		/* check the group again; the above opens a small race window */
		if (substream->group == group)
			break; /* OK */
		/* group changed, try again */
		snd_pcm_group_unref(group, substream);
	}
	return group;
}

/*
 *  Note: call with stream lock
 */
@@ -1131,28 +1162,15 @@ static int snd_pcm_action(const struct action_ops *ops,
			  struct snd_pcm_substream *substream,
			  int state)
{
	struct snd_pcm_group *group;
	int res;

	if (!snd_pcm_stream_linked(substream))
		return snd_pcm_action_single(ops, substream, state);

	if (substream->pcm->nonatomic) {
		if (!mutex_trylock(&substream->group->mutex)) {
			mutex_unlock(&substream->self_group.mutex);
			mutex_lock(&substream->group->mutex);
			mutex_lock(&substream->self_group.mutex);
		}
	group = snd_pcm_stream_group_ref(substream);
	if (group)
		res = snd_pcm_action_group(ops, substream, state, 1);
		mutex_unlock(&substream->group->mutex);
	} else {
		if (!spin_trylock(&substream->group->lock)) {
			spin_unlock(&substream->self_group.lock);
			spin_lock(&substream->group->lock);
			spin_lock(&substream->self_group.lock);
		}
		res = snd_pcm_action_group(ops, substream, state, 1);
		spin_unlock(&substream->group->lock);
	}
	else
		res = snd_pcm_action_single(ops, substream, state);
	snd_pcm_group_unref(group, substream);
	return res;
}

@@ -1179,6 +1197,7 @@ static int snd_pcm_action_nonatomic(const struct action_ops *ops,
{
	int res;

	/* Guarantee the group members won't change during non-atomic action */
	down_read(&snd_pcm_link_rwsem);
	if (snd_pcm_stream_linked(substream))
		res = snd_pcm_action_group(ops, substream, state, 0);
@@ -1802,6 +1821,7 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream,
	struct snd_card *card;
	struct snd_pcm_runtime *runtime;
	struct snd_pcm_substream *s;
	struct snd_pcm_group *group;
	wait_queue_entry_t wait;
	int result = 0;
	int nonblock = 0;
@@ -1818,7 +1838,6 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream,
	} else if (substream->f_flags & O_NONBLOCK)
		nonblock = 1;

	down_read(&snd_pcm_link_rwsem);
	snd_pcm_stream_lock_irq(substream);
	/* resume pause */
	if (runtime->status->state == SNDRV_PCM_STATE_PAUSED)
@@ -1843,6 +1862,7 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream,
		}
		/* find a substream to drain */
		to_check = NULL;
		group = snd_pcm_stream_group_ref(substream);
		snd_pcm_group_for_each_entry(s, substream) {
			if (s->stream != SNDRV_PCM_STREAM_PLAYBACK)
				continue;
@@ -1852,12 +1872,12 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream,
				break;
			}
		}
		snd_pcm_group_unref(group, substream);
		if (!to_check)
			break; /* all drained */
		init_waitqueue_entry(&wait, current);
		add_wait_queue(&to_check->sleep, &wait);
		snd_pcm_stream_unlock_irq(substream);
		up_read(&snd_pcm_link_rwsem);
		if (runtime->no_period_wakeup)
			tout = MAX_SCHEDULE_TIMEOUT;
		else {
@@ -1869,9 +1889,17 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream,
			tout = msecs_to_jiffies(tout * 1000);
		}
		tout = schedule_timeout_interruptible(tout);
		down_read(&snd_pcm_link_rwsem);

		snd_pcm_stream_lock_irq(substream);
		group = snd_pcm_stream_group_ref(substream);
		snd_pcm_group_for_each_entry(s, substream) {
			if (s->runtime == to_check) {
				remove_wait_queue(&to_check->sleep, &wait);
				break;
			}
		}
		snd_pcm_group_unref(group, substream);

		if (card->shutdown) {
			result = -ENODEV;
			break;
@@ -1891,7 +1919,6 @@ static int snd_pcm_drain(struct snd_pcm_substream *substream,

 unlock:
	snd_pcm_stream_unlock_irq(substream);
	up_read(&snd_pcm_link_rwsem);

	return result;
}
@@ -1930,13 +1957,19 @@ static int snd_pcm_drop(struct snd_pcm_substream *substream)
static bool is_pcm_file(struct file *file)
{
	struct inode *inode = file_inode(file);
	struct snd_pcm *pcm;
	unsigned int minor;

	if (!S_ISCHR(inode->i_mode) || imajor(inode) != snd_major)
		return false;
	minor = iminor(inode);
	return snd_lookup_minor_data(minor, SNDRV_DEVICE_TYPE_PCM_PLAYBACK) ||
		snd_lookup_minor_data(minor, SNDRV_DEVICE_TYPE_PCM_CAPTURE);
	pcm = snd_lookup_minor_data(minor, SNDRV_DEVICE_TYPE_PCM_PLAYBACK);
	if (!pcm)
		pcm = snd_lookup_minor_data(minor, SNDRV_DEVICE_TYPE_PCM_CAPTURE);
	if (!pcm)
		return false;
	snd_card_unref(pcm->card);
	return true;
}

/*
@@ -1947,7 +1980,8 @@ static int snd_pcm_link(struct snd_pcm_substream *substream, int fd)
	int res = 0;
	struct snd_pcm_file *pcm_file;
	struct snd_pcm_substream *substream1;
	struct snd_pcm_group *group;
	struct snd_pcm_group *group, *target_group;
	bool nonatomic = substream->pcm->nonatomic;
	struct fd f = fdget(fd);

	if (!f.file)
@@ -1958,13 +1992,14 @@ static int snd_pcm_link(struct snd_pcm_substream *substream, int fd)
	}
	pcm_file = f.file->private_data;
	substream1 = pcm_file->substream;
	group = kmalloc(sizeof(*group), GFP_KERNEL);
	group = kzalloc(sizeof(*group), GFP_KERNEL);
	if (!group) {
		res = -ENOMEM;
		goto _nolock;
	}
	down_write_nonfifo(&snd_pcm_link_rwsem);
	write_lock_irq(&snd_pcm_link_rwlock);
	snd_pcm_group_init(group);

	down_write(&snd_pcm_link_rwsem);
	if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN ||
	    substream->runtime->status->state != substream1->runtime->status->state ||
	    substream->pcm->nonatomic != substream1->pcm->nonatomic) {
@@ -1975,23 +2010,23 @@ static int snd_pcm_link(struct snd_pcm_substream *substream, int fd)
		res = -EALREADY;
		goto _end;
	}

	snd_pcm_stream_lock_irq(substream);
	if (!snd_pcm_stream_linked(substream)) {
		substream->group = group;
		group = NULL;
		spin_lock_init(&substream->group->lock);
		mutex_init(&substream->group->mutex);
		INIT_LIST_HEAD(&substream->group->substreams);
		list_add_tail(&substream->link_list, &substream->group->substreams);
		substream->group->count = 1;
	}
	list_add_tail(&substream1->link_list, &substream->group->substreams);
	substream->group->count++;
	substream1->group = substream->group;
		snd_pcm_group_assign(substream, group);
		group = NULL; /* assigned, don't free this one below */
	}
	target_group = substream->group;
	snd_pcm_stream_unlock_irq(substream);

	snd_pcm_group_lock_irq(target_group, nonatomic);
	snd_pcm_stream_lock(substream1);
	snd_pcm_group_assign(substream1, target_group);
	snd_pcm_stream_unlock(substream1);
	snd_pcm_group_unlock_irq(target_group, nonatomic);
 _end:
	write_unlock_irq(&snd_pcm_link_rwlock);
	up_write(&snd_pcm_link_rwsem);
 _nolock:
	snd_card_unref(substream1->pcm->card);
	kfree(group);
 _badf:
	fdput(f);
@@ -2000,34 +2035,43 @@ static int snd_pcm_link(struct snd_pcm_substream *substream, int fd)

static void relink_to_local(struct snd_pcm_substream *substream)
{
	substream->group = &substream->self_group;
	INIT_LIST_HEAD(&substream->self_group.substreams);
	list_add_tail(&substream->link_list, &substream->self_group.substreams);
	snd_pcm_stream_lock(substream);
	snd_pcm_group_assign(substream, &substream->self_group);
	snd_pcm_stream_unlock(substream);
}

static int snd_pcm_unlink(struct snd_pcm_substream *substream)
{
	struct snd_pcm_substream *s;
	struct snd_pcm_group *group;
	bool nonatomic = substream->pcm->nonatomic;
	bool do_free = false;
	int res = 0;

	down_write_nonfifo(&snd_pcm_link_rwsem);
	write_lock_irq(&snd_pcm_link_rwlock);
	down_write(&snd_pcm_link_rwsem);

	if (!snd_pcm_stream_linked(substream)) {
		res = -EALREADY;
		goto _end;
	}
	list_del(&substream->link_list);
	substream->group->count--;
	if (substream->group->count == 1) {	/* detach the last stream, too */
		snd_pcm_group_for_each_entry(s, substream) {
			relink_to_local(s);
			break;
		}
		kfree(substream->group);
	}

	group = substream->group;
	snd_pcm_group_lock_irq(group, nonatomic);

	relink_to_local(substream);

	/* detach the last stream, too */
	if (list_is_singular(&group->substreams)) {
		relink_to_local(list_first_entry(&group->substreams,
						 struct snd_pcm_substream,
						 link_list));
		do_free = !refcount_read(&group->refs);
	}

	snd_pcm_group_unlock_irq(group, nonatomic);
	if (do_free)
		kfree(group);

       _end:
	write_unlock_irq(&snd_pcm_link_rwlock);
	up_write(&snd_pcm_link_rwsem);
	return res;
}