[alsa-devel] [PATCH v3 2/2] ALSA: fireworks: accessing to user space outside spinlock
Takashi Sakamoto
o-takashi at sakamocchi.jp
Wed Aug 31 15:03:33 CEST 2016
On Aug 31 2016 21:21, Takashi Iwai wrote:
> 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.
Just from my overlooking.
Thanks
Takashi Sakamoto
More information about the Alsa-devel
mailing list