Commit d7c0d439 authored by Laurent Pinchart's avatar Laurent Pinchart Committed by Mauro Carvalho Chehab

V4L/DVB (13831): uvcvideo: Fix oops caused by a race condition in buffer dequeuing

Buffers were marked as done before being removed from the IRQ queue. If
a userspace application dequeued and requeued the buffer fast enough
during that time window, the buffer could end up being deleted twice,
generating an oops in interrupt context.

Add a new state, UVC_BUF_STATE_READY, to mark buffers as ready for reuse
but not yet removed from the queue, and transition to UVC_BUF_STATE_DONE
only when the buffer is removed from the queue.
Signed-off-by: default avatarLaurent Pinchart <laurent.pinchart@ideasonboard.com>
Signed-off-by: default avatarMauro Carvalho Chehab <mchehab@redhat.com>
parent 2c4d9de8
...@@ -59,9 +59,9 @@ ...@@ -59,9 +59,9 @@
* returns immediately. * returns immediately.
* *
* When the buffer is full, the completion handler removes it from the irq * When the buffer is full, the completion handler removes it from the irq
* queue, marks it as ready (UVC_BUF_STATE_DONE) and wakes its wait queue. * queue, marks it as done (UVC_BUF_STATE_DONE) and wakes its wait queue.
* At that point, any process waiting on the buffer will be woken up. If a * At that point, any process waiting on the buffer will be woken up. If a
* process tries to dequeue a buffer after it has been marked ready, the * process tries to dequeue a buffer after it has been marked done, the
* dequeing will succeed immediately. * dequeing will succeed immediately.
* *
* 2. Buffers are queued, user is waiting on a buffer and the device gets * 2. Buffers are queued, user is waiting on a buffer and the device gets
...@@ -201,6 +201,7 @@ static void __uvc_query_buffer(struct uvc_buffer *buf, ...@@ -201,6 +201,7 @@ static void __uvc_query_buffer(struct uvc_buffer *buf,
break; break;
case UVC_BUF_STATE_QUEUED: case UVC_BUF_STATE_QUEUED:
case UVC_BUF_STATE_ACTIVE: case UVC_BUF_STATE_ACTIVE:
case UVC_BUF_STATE_READY:
v4l2_buf->flags |= V4L2_BUF_FLAG_QUEUED; v4l2_buf->flags |= V4L2_BUF_FLAG_QUEUED;
break; break;
case UVC_BUF_STATE_IDLE: case UVC_BUF_STATE_IDLE:
...@@ -295,13 +296,15 @@ static int uvc_queue_waiton(struct uvc_buffer *buf, int nonblocking) ...@@ -295,13 +296,15 @@ static int uvc_queue_waiton(struct uvc_buffer *buf, int nonblocking)
{ {
if (nonblocking) { if (nonblocking) {
return (buf->state != UVC_BUF_STATE_QUEUED && return (buf->state != UVC_BUF_STATE_QUEUED &&
buf->state != UVC_BUF_STATE_ACTIVE) buf->state != UVC_BUF_STATE_ACTIVE &&
buf->state != UVC_BUF_STATE_READY)
? 0 : -EAGAIN; ? 0 : -EAGAIN;
} }
return wait_event_interruptible(buf->wait, return wait_event_interruptible(buf->wait,
buf->state != UVC_BUF_STATE_QUEUED && buf->state != UVC_BUF_STATE_QUEUED &&
buf->state != UVC_BUF_STATE_ACTIVE); buf->state != UVC_BUF_STATE_ACTIVE &&
buf->state != UVC_BUF_STATE_READY);
} }
/* /*
...@@ -348,6 +351,7 @@ int uvc_dequeue_buffer(struct uvc_video_queue *queue, ...@@ -348,6 +351,7 @@ int uvc_dequeue_buffer(struct uvc_video_queue *queue,
case UVC_BUF_STATE_IDLE: case UVC_BUF_STATE_IDLE:
case UVC_BUF_STATE_QUEUED: case UVC_BUF_STATE_QUEUED:
case UVC_BUF_STATE_ACTIVE: case UVC_BUF_STATE_ACTIVE:
case UVC_BUF_STATE_READY:
default: default:
uvc_trace(UVC_TRACE_CAPTURE, "[E] Invalid buffer state %u " uvc_trace(UVC_TRACE_CAPTURE, "[E] Invalid buffer state %u "
"(driver bug?).\n", buf->state); "(driver bug?).\n", buf->state);
...@@ -489,6 +493,7 @@ struct uvc_buffer *uvc_queue_next_buffer(struct uvc_video_queue *queue, ...@@ -489,6 +493,7 @@ struct uvc_buffer *uvc_queue_next_buffer(struct uvc_video_queue *queue,
spin_lock_irqsave(&queue->irqlock, flags); spin_lock_irqsave(&queue->irqlock, flags);
list_del(&buf->queue); list_del(&buf->queue);
buf->state = UVC_BUF_STATE_DONE;
if (!list_empty(&queue->irqqueue)) if (!list_empty(&queue->irqqueue))
nextbuf = list_first_entry(&queue->irqqueue, struct uvc_buffer, nextbuf = list_first_entry(&queue->irqqueue, struct uvc_buffer,
queue); queue);
......
...@@ -441,7 +441,7 @@ static int uvc_video_decode_start(struct uvc_streaming *stream, ...@@ -441,7 +441,7 @@ static int uvc_video_decode_start(struct uvc_streaming *stream,
if (fid != stream->last_fid && buf->buf.bytesused != 0) { if (fid != stream->last_fid && buf->buf.bytesused != 0) {
uvc_trace(UVC_TRACE_FRAME, "Frame complete (FID bit " uvc_trace(UVC_TRACE_FRAME, "Frame complete (FID bit "
"toggled).\n"); "toggled).\n");
buf->state = UVC_BUF_STATE_DONE; buf->state = UVC_BUF_STATE_READY;
return -EAGAIN; return -EAGAIN;
} }
...@@ -470,7 +470,7 @@ static void uvc_video_decode_data(struct uvc_streaming *stream, ...@@ -470,7 +470,7 @@ static void uvc_video_decode_data(struct uvc_streaming *stream,
/* Complete the current frame if the buffer size was exceeded. */ /* Complete the current frame if the buffer size was exceeded. */
if (len > maxlen) { if (len > maxlen) {
uvc_trace(UVC_TRACE_FRAME, "Frame complete (overflow).\n"); uvc_trace(UVC_TRACE_FRAME, "Frame complete (overflow).\n");
buf->state = UVC_BUF_STATE_DONE; buf->state = UVC_BUF_STATE_READY;
} }
} }
...@@ -482,7 +482,7 @@ static void uvc_video_decode_end(struct uvc_streaming *stream, ...@@ -482,7 +482,7 @@ static void uvc_video_decode_end(struct uvc_streaming *stream,
uvc_trace(UVC_TRACE_FRAME, "Frame complete (EOF found).\n"); uvc_trace(UVC_TRACE_FRAME, "Frame complete (EOF found).\n");
if (data[0] == len) if (data[0] == len)
uvc_trace(UVC_TRACE_FRAME, "EOF in empty payload.\n"); uvc_trace(UVC_TRACE_FRAME, "EOF in empty payload.\n");
buf->state = UVC_BUF_STATE_DONE; buf->state = UVC_BUF_STATE_READY;
if (stream->dev->quirks & UVC_QUIRK_STREAM_NO_FID) if (stream->dev->quirks & UVC_QUIRK_STREAM_NO_FID)
stream->last_fid ^= UVC_STREAM_FID; stream->last_fid ^= UVC_STREAM_FID;
} }
...@@ -568,8 +568,7 @@ static void uvc_video_decode_isoc(struct urb *urb, struct uvc_streaming *stream, ...@@ -568,8 +568,7 @@ static void uvc_video_decode_isoc(struct urb *urb, struct uvc_streaming *stream,
uvc_video_decode_end(stream, buf, mem, uvc_video_decode_end(stream, buf, mem,
urb->iso_frame_desc[i].actual_length); urb->iso_frame_desc[i].actual_length);
if (buf->state == UVC_BUF_STATE_DONE || if (buf->state == UVC_BUF_STATE_READY)
buf->state == UVC_BUF_STATE_ERROR)
buf = uvc_queue_next_buffer(&stream->queue, buf); buf = uvc_queue_next_buffer(&stream->queue, buf);
} }
} }
...@@ -627,8 +626,7 @@ static void uvc_video_decode_bulk(struct urb *urb, struct uvc_streaming *stream, ...@@ -627,8 +626,7 @@ static void uvc_video_decode_bulk(struct urb *urb, struct uvc_streaming *stream,
if (!stream->bulk.skip_payload && buf != NULL) { if (!stream->bulk.skip_payload && buf != NULL) {
uvc_video_decode_end(stream, buf, stream->bulk.header, uvc_video_decode_end(stream, buf, stream->bulk.header,
stream->bulk.payload_size); stream->bulk.payload_size);
if (buf->state == UVC_BUF_STATE_DONE || if (buf->state == UVC_BUF_STATE_READY)
buf->state == UVC_BUF_STATE_ERROR)
buf = uvc_queue_next_buffer(&stream->queue, buf = uvc_queue_next_buffer(&stream->queue,
buf); buf);
} }
...@@ -669,7 +667,7 @@ static void uvc_video_encode_bulk(struct urb *urb, struct uvc_streaming *stream, ...@@ -669,7 +667,7 @@ static void uvc_video_encode_bulk(struct urb *urb, struct uvc_streaming *stream,
stream->bulk.payload_size == stream->bulk.max_payload_size) { stream->bulk.payload_size == stream->bulk.max_payload_size) {
if (buf->buf.bytesused == stream->queue.buf_used) { if (buf->buf.bytesused == stream->queue.buf_used) {
stream->queue.buf_used = 0; stream->queue.buf_used = 0;
buf->state = UVC_BUF_STATE_DONE; buf->state = UVC_BUF_STATE_READY;
uvc_queue_next_buffer(&stream->queue, buf); uvc_queue_next_buffer(&stream->queue, buf);
stream->last_fid ^= UVC_STREAM_FID; stream->last_fid ^= UVC_STREAM_FID;
} }
......
...@@ -365,8 +365,9 @@ enum uvc_buffer_state { ...@@ -365,8 +365,9 @@ enum uvc_buffer_state {
UVC_BUF_STATE_IDLE = 0, UVC_BUF_STATE_IDLE = 0,
UVC_BUF_STATE_QUEUED = 1, UVC_BUF_STATE_QUEUED = 1,
UVC_BUF_STATE_ACTIVE = 2, UVC_BUF_STATE_ACTIVE = 2,
UVC_BUF_STATE_DONE = 3, UVC_BUF_STATE_READY = 3,
UVC_BUF_STATE_ERROR = 4, UVC_BUF_STATE_DONE = 4,
UVC_BUF_STATE_ERROR = 5,
}; };
struct uvc_buffer { struct uvc_buffer {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment