[alsa-devel] [PATCH v3 2/2] ALSA: fireworks: accessing to user space outside spinlock

Takashi Iwai tiwai at suse.de
Wed Aug 31 14:21:08 CEST 2016


On Wed, 31 Aug 2016 13:15:33 +0200,
Takashi Sakamoto wrote:
> 
> In hwdep interface of fireworks driver, accessing to user space is in a
> critical section with disabled local interrupt. Depending on architecture,
> accessing to user space can cause page fault exception. Then local
> processor stores machine status and handles the synchronous event. A
> handler corresponding to the event can call task scheduler to wait for
> preparing pages. In a case of usage of single core processor, the state to
> disable local interrupt is worse because it don't handle usual interrupts
> from hardware.
> 
> This commit fixes this bug, performing the accessing outside spinlock.
> 
> Reported-by: Vaishali Thakkar <vaishali.thakkar at oracle.com>
> Cc: stable at vger.kernel.org
> Fixes: 555e8a8f7f14('ALSA: fireworks: Add command/response functionality into hwdep interface')
> Signed-off-by: Takashi Sakamoto <o-takashi at sakamocchi.jp>
> ---
>  sound/firewire/fireworks/fireworks_hwdep.c       | 52 ++++++++++++++++++------
>  sound/firewire/fireworks/fireworks_transaction.c |  4 +-
>  2 files changed, 42 insertions(+), 14 deletions(-)
> 
> diff --git a/sound/firewire/fireworks/fireworks_hwdep.c b/sound/firewire/fireworks/fireworks_hwdep.c
> index 33df865..2563490 100644
> --- a/sound/firewire/fireworks/fireworks_hwdep.c
> +++ b/sound/firewire/fireworks/fireworks_hwdep.c
> @@ -25,6 +25,7 @@ hwdep_read_resp_buf(struct snd_efw *efw, char __user *buf, long remained,
>  {
>  	unsigned int length, till_end, type;
>  	struct snd_efw_transaction *t;
> +	u8 *pull_ptr;
>  	long count = 0;
>  
>  	if (remained < sizeof(type) + sizeof(struct snd_efw_transaction))
> @@ -38,8 +39,17 @@ hwdep_read_resp_buf(struct snd_efw *efw, char __user *buf, long remained,
>  	buf += sizeof(type);
>  
>  	/* write into buffer as many responses as possible */
> +	spin_lock_irq(&efw->lock);
> +
> +	/*
> +	 * When another task reaches here during this task's access to user
> +	 * space, it picks up current position in buffer and can read the same
> +	 * series of responses.
> +	 */
> +	pull_ptr = efw->pull_ptr;
> +
>  	while (efw->resp_queues > 0) {
> -		t = (struct snd_efw_transaction *)(efw->pull_ptr);
> +		t = (struct snd_efw_transaction *)(pull_ptr);
>  		length = be32_to_cpu(t->length) * sizeof(__be32);
>  
>  		/* confirm enough space for this response */
> @@ -49,16 +59,19 @@ hwdep_read_resp_buf(struct snd_efw *efw, char __user *buf, long remained,
>  		/* copy from ring buffer to user buffer */
>  		while (length > 0) {
>  			till_end = snd_efw_resp_buf_size -
> -				(unsigned int)(efw->pull_ptr - efw->resp_buf);
> +				(unsigned int)(pull_ptr - efw->resp_buf);
>  			till_end = min_t(unsigned int, length, till_end);
>  
> -			if (copy_to_user(buf, efw->pull_ptr, till_end))
> +			spin_unlock_irq(&efw->lock);
> +
> +			if (copy_to_user(buf, pull_ptr, till_end))
>  				return -EFAULT;
>  
> -			efw->pull_ptr += till_end;
> -			if (efw->pull_ptr >= efw->resp_buf +
> -					     snd_efw_resp_buf_size)
> -				efw->pull_ptr -= snd_efw_resp_buf_size;
> +			spin_lock_irq(&efw->lock);
> +
> +			pull_ptr += till_end;
> +			if (pull_ptr >= efw->resp_buf + snd_efw_resp_buf_size)
> +				pull_ptr -= snd_efw_resp_buf_size;
>  
>  			length -= till_end;
>  			buf += till_end;
> @@ -69,6 +82,18 @@ hwdep_read_resp_buf(struct snd_efw *efw, char __user *buf, long remained,
>  		efw->resp_queues--;
>  	}
>  
> +	/*
> +	 * All of tasks can read from the buffer nearly simultaneously, but the
> +	 * position of each task is different depending on the length of given
> +	 * buffer. Here, for simplicity, a position of buffer is set by the
> +	 * latest task. It's better for a listening application to allow one
> +	 * thread to read from the buffer. Unless, each task can read different
> +	 * sequence of responses depending on variation of buffer length.
> +	 */
> +	efw->pull_ptr = pull_ptr;
> +
> +	spin_unlock_irq(&efw->lock);

Hrm, I'm afraid that it still doesn't work properly when accessed
concurrently.  In your code, efw->pull_ptr isn't updated until the end
of the loop, while dfw->resp_queues are decremented in the loop.

Suppose resp_queues = 2, and two threads read concurrently.  What
happens?   Both threads read from the first element only once, and
resp_queues are decremented twice (one per each).  And now both
threads go out of the loops, and both set the pull_ptr to the same
next item, although the second item hasn't been processed.


Takashi


More information about the Alsa-devel mailing list