diff options
author | Laurent Pinchart <laurent.pinchart@ideasonboard.com> | 2009-12-16 19:20:45 -0500 |
---|---|---|
committer | Mauro Carvalho Chehab <mchehab@redhat.com> | 2010-01-17 08:31:35 -0500 |
commit | d7c0d43997cf716617d724554d19b3b8dd465833 (patch) | |
tree | 00ef45adee02318f35775cc9ecce4ae18372a624 /drivers/media/video | |
parent | 2c4d9de8ab1434336248bbc01ee8e64d7e6b8a4f (diff) |
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: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
Diffstat (limited to 'drivers/media/video')
-rw-r--r-- | drivers/media/video/uvc/uvc_queue.c | 13 | ||||
-rw-r--r-- | drivers/media/video/uvc/uvc_video.c | 14 | ||||
-rw-r--r-- | drivers/media/video/uvc/uvcvideo.h | 5 |
3 files changed, 18 insertions, 14 deletions
diff --git a/drivers/media/video/uvc/uvc_queue.c b/drivers/media/video/uvc/uvc_queue.c index f854698c4061..ea11839cba4a 100644 --- a/drivers/media/video/uvc/uvc_queue.c +++ b/drivers/media/video/uvc/uvc_queue.c | |||
@@ -59,9 +59,9 @@ | |||
59 | * returns immediately. | 59 | * returns immediately. |
60 | * | 60 | * |
61 | * When the buffer is full, the completion handler removes it from the irq | 61 | * When the buffer is full, the completion handler removes it from the irq |
62 | * queue, marks it as ready (UVC_BUF_STATE_DONE) and wakes its wait queue. | 62 | * queue, marks it as done (UVC_BUF_STATE_DONE) and wakes its wait queue. |
63 | * At that point, any process waiting on the buffer will be woken up. If a | 63 | * At that point, any process waiting on the buffer will be woken up. If a |
64 | * process tries to dequeue a buffer after it has been marked ready, the | 64 | * process tries to dequeue a buffer after it has been marked done, the |
65 | * dequeing will succeed immediately. | 65 | * dequeing will succeed immediately. |
66 | * | 66 | * |
67 | * 2. Buffers are queued, user is waiting on a buffer and the device gets | 67 | * 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 | break; | 201 | break; |
202 | case UVC_BUF_STATE_QUEUED: | 202 | case UVC_BUF_STATE_QUEUED: |
203 | case UVC_BUF_STATE_ACTIVE: | 203 | case UVC_BUF_STATE_ACTIVE: |
204 | case UVC_BUF_STATE_READY: | ||
204 | v4l2_buf->flags |= V4L2_BUF_FLAG_QUEUED; | 205 | v4l2_buf->flags |= V4L2_BUF_FLAG_QUEUED; |
205 | break; | 206 | break; |
206 | case UVC_BUF_STATE_IDLE: | 207 | case UVC_BUF_STATE_IDLE: |
@@ -295,13 +296,15 @@ static int uvc_queue_waiton(struct uvc_buffer *buf, int nonblocking) | |||
295 | { | 296 | { |
296 | if (nonblocking) { | 297 | if (nonblocking) { |
297 | return (buf->state != UVC_BUF_STATE_QUEUED && | 298 | return (buf->state != UVC_BUF_STATE_QUEUED && |
298 | buf->state != UVC_BUF_STATE_ACTIVE) | 299 | buf->state != UVC_BUF_STATE_ACTIVE && |
300 | buf->state != UVC_BUF_STATE_READY) | ||
299 | ? 0 : -EAGAIN; | 301 | ? 0 : -EAGAIN; |
300 | } | 302 | } |
301 | 303 | ||
302 | return wait_event_interruptible(buf->wait, | 304 | return wait_event_interruptible(buf->wait, |
303 | buf->state != UVC_BUF_STATE_QUEUED && | 305 | buf->state != UVC_BUF_STATE_QUEUED && |
304 | buf->state != UVC_BUF_STATE_ACTIVE); | 306 | buf->state != UVC_BUF_STATE_ACTIVE && |
307 | buf->state != UVC_BUF_STATE_READY); | ||
305 | } | 308 | } |
306 | 309 | ||
307 | /* | 310 | /* |
@@ -348,6 +351,7 @@ int uvc_dequeue_buffer(struct uvc_video_queue *queue, | |||
348 | case UVC_BUF_STATE_IDLE: | 351 | case UVC_BUF_STATE_IDLE: |
349 | case UVC_BUF_STATE_QUEUED: | 352 | case UVC_BUF_STATE_QUEUED: |
350 | case UVC_BUF_STATE_ACTIVE: | 353 | case UVC_BUF_STATE_ACTIVE: |
354 | case UVC_BUF_STATE_READY: | ||
351 | default: | 355 | default: |
352 | uvc_trace(UVC_TRACE_CAPTURE, "[E] Invalid buffer state %u " | 356 | uvc_trace(UVC_TRACE_CAPTURE, "[E] Invalid buffer state %u " |
353 | "(driver bug?).\n", buf->state); | 357 | "(driver bug?).\n", buf->state); |
@@ -489,6 +493,7 @@ struct uvc_buffer *uvc_queue_next_buffer(struct uvc_video_queue *queue, | |||
489 | 493 | ||
490 | spin_lock_irqsave(&queue->irqlock, flags); | 494 | spin_lock_irqsave(&queue->irqlock, flags); |
491 | list_del(&buf->queue); | 495 | list_del(&buf->queue); |
496 | buf->state = UVC_BUF_STATE_DONE; | ||
492 | if (!list_empty(&queue->irqqueue)) | 497 | if (!list_empty(&queue->irqqueue)) |
493 | nextbuf = list_first_entry(&queue->irqqueue, struct uvc_buffer, | 498 | nextbuf = list_first_entry(&queue->irqqueue, struct uvc_buffer, |
494 | queue); | 499 | queue); |
diff --git a/drivers/media/video/uvc/uvc_video.c b/drivers/media/video/uvc/uvc_video.c index e8cc0a9ddadd..7dcf534a0cf3 100644 --- a/drivers/media/video/uvc/uvc_video.c +++ b/drivers/media/video/uvc/uvc_video.c | |||
@@ -441,7 +441,7 @@ static int uvc_video_decode_start(struct uvc_streaming *stream, | |||
441 | if (fid != stream->last_fid && buf->buf.bytesused != 0) { | 441 | if (fid != stream->last_fid && buf->buf.bytesused != 0) { |
442 | uvc_trace(UVC_TRACE_FRAME, "Frame complete (FID bit " | 442 | uvc_trace(UVC_TRACE_FRAME, "Frame complete (FID bit " |
443 | "toggled).\n"); | 443 | "toggled).\n"); |
444 | buf->state = UVC_BUF_STATE_DONE; | 444 | buf->state = UVC_BUF_STATE_READY; |
445 | return -EAGAIN; | 445 | return -EAGAIN; |
446 | } | 446 | } |
447 | 447 | ||
@@ -470,7 +470,7 @@ static void uvc_video_decode_data(struct uvc_streaming *stream, | |||
470 | /* Complete the current frame if the buffer size was exceeded. */ | 470 | /* Complete the current frame if the buffer size was exceeded. */ |
471 | if (len > maxlen) { | 471 | if (len > maxlen) { |
472 | uvc_trace(UVC_TRACE_FRAME, "Frame complete (overflow).\n"); | 472 | uvc_trace(UVC_TRACE_FRAME, "Frame complete (overflow).\n"); |
473 | buf->state = UVC_BUF_STATE_DONE; | 473 | buf->state = UVC_BUF_STATE_READY; |
474 | } | 474 | } |
475 | } | 475 | } |
476 | 476 | ||
@@ -482,7 +482,7 @@ static void uvc_video_decode_end(struct uvc_streaming *stream, | |||
482 | uvc_trace(UVC_TRACE_FRAME, "Frame complete (EOF found).\n"); | 482 | uvc_trace(UVC_TRACE_FRAME, "Frame complete (EOF found).\n"); |
483 | if (data[0] == len) | 483 | if (data[0] == len) |
484 | uvc_trace(UVC_TRACE_FRAME, "EOF in empty payload.\n"); | 484 | uvc_trace(UVC_TRACE_FRAME, "EOF in empty payload.\n"); |
485 | buf->state = UVC_BUF_STATE_DONE; | 485 | buf->state = UVC_BUF_STATE_READY; |
486 | if (stream->dev->quirks & UVC_QUIRK_STREAM_NO_FID) | 486 | if (stream->dev->quirks & UVC_QUIRK_STREAM_NO_FID) |
487 | stream->last_fid ^= UVC_STREAM_FID; | 487 | stream->last_fid ^= UVC_STREAM_FID; |
488 | } | 488 | } |
@@ -568,8 +568,7 @@ static void uvc_video_decode_isoc(struct urb *urb, struct uvc_streaming *stream, | |||
568 | uvc_video_decode_end(stream, buf, mem, | 568 | uvc_video_decode_end(stream, buf, mem, |
569 | urb->iso_frame_desc[i].actual_length); | 569 | urb->iso_frame_desc[i].actual_length); |
570 | 570 | ||
571 | if (buf->state == UVC_BUF_STATE_DONE || | 571 | if (buf->state == UVC_BUF_STATE_READY) |
572 | buf->state == UVC_BUF_STATE_ERROR) | ||
573 | buf = uvc_queue_next_buffer(&stream->queue, buf); | 572 | buf = uvc_queue_next_buffer(&stream->queue, buf); |
574 | } | 573 | } |
575 | } | 574 | } |
@@ -627,8 +626,7 @@ static void uvc_video_decode_bulk(struct urb *urb, struct uvc_streaming *stream, | |||
627 | if (!stream->bulk.skip_payload && buf != NULL) { | 626 | if (!stream->bulk.skip_payload && buf != NULL) { |
628 | uvc_video_decode_end(stream, buf, stream->bulk.header, | 627 | uvc_video_decode_end(stream, buf, stream->bulk.header, |
629 | stream->bulk.payload_size); | 628 | stream->bulk.payload_size); |
630 | if (buf->state == UVC_BUF_STATE_DONE || | 629 | if (buf->state == UVC_BUF_STATE_READY) |
631 | buf->state == UVC_BUF_STATE_ERROR) | ||
632 | buf = uvc_queue_next_buffer(&stream->queue, | 630 | buf = uvc_queue_next_buffer(&stream->queue, |
633 | buf); | 631 | buf); |
634 | } | 632 | } |
@@ -669,7 +667,7 @@ static void uvc_video_encode_bulk(struct urb *urb, struct uvc_streaming *stream, | |||
669 | stream->bulk.payload_size == stream->bulk.max_payload_size) { | 667 | stream->bulk.payload_size == stream->bulk.max_payload_size) { |
670 | if (buf->buf.bytesused == stream->queue.buf_used) { | 668 | if (buf->buf.bytesused == stream->queue.buf_used) { |
671 | stream->queue.buf_used = 0; | 669 | stream->queue.buf_used = 0; |
672 | buf->state = UVC_BUF_STATE_DONE; | 670 | buf->state = UVC_BUF_STATE_READY; |
673 | uvc_queue_next_buffer(&stream->queue, buf); | 671 | uvc_queue_next_buffer(&stream->queue, buf); |
674 | stream->last_fid ^= UVC_STREAM_FID; | 672 | stream->last_fid ^= UVC_STREAM_FID; |
675 | } | 673 | } |
diff --git a/drivers/media/video/uvc/uvcvideo.h b/drivers/media/video/uvc/uvcvideo.h index 7ec9a04ced50..2337585001ea 100644 --- a/drivers/media/video/uvc/uvcvideo.h +++ b/drivers/media/video/uvc/uvcvideo.h | |||
@@ -365,8 +365,9 @@ enum uvc_buffer_state { | |||
365 | UVC_BUF_STATE_IDLE = 0, | 365 | UVC_BUF_STATE_IDLE = 0, |
366 | UVC_BUF_STATE_QUEUED = 1, | 366 | UVC_BUF_STATE_QUEUED = 1, |
367 | UVC_BUF_STATE_ACTIVE = 2, | 367 | UVC_BUF_STATE_ACTIVE = 2, |
368 | UVC_BUF_STATE_DONE = 3, | 368 | UVC_BUF_STATE_READY = 3, |
369 | UVC_BUF_STATE_ERROR = 4, | 369 | UVC_BUF_STATE_DONE = 4, |
370 | UVC_BUF_STATE_ERROR = 5, | ||
370 | }; | 371 | }; |
371 | 372 | ||
372 | struct uvc_buffer { | 373 | struct uvc_buffer { |