[media] uvcvideo: Fix race-related crash in uvc_video_clock_update()

The driver frees the clock samples buffer before stopping the video
buffers queue. If a DQBUF call arrives in-between,
uvc_video_clock_update() will be called with a NULL clock samples
buffer, leading to a crash. This occurs very frequently when using the
webcam with the flash browser plugin.

Move clock initialization/cleanup to uvc_video_enable() in order to free
the clock samples buffer after the queue is stopped. Make sure the clock
is reset at resume time to avoid miscalculating timestamps.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Cc: stable@vger.kernel.org
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
This commit is contained in:
Laurent Pinchart 2012-03-27 05:51:00 -03:00 коммит произвёл Mauro Carvalho Chehab
Родитель d3a92d6248
Коммит ed0ee0ce0a
1 изменённых файлов: 33 добавлений и 19 удалений

Просмотреть файл

@ -468,22 +468,30 @@ uvc_video_clock_decode(struct uvc_streaming *stream, struct uvc_buffer *buf,
spin_unlock_irqrestore(&stream->clock.lock, flags); spin_unlock_irqrestore(&stream->clock.lock, flags);
} }
static void uvc_video_clock_reset(struct uvc_streaming *stream)
{
struct uvc_clock *clock = &stream->clock;
clock->head = 0;
clock->count = 0;
clock->last_sof = -1;
clock->sof_offset = -1;
}
static int uvc_video_clock_init(struct uvc_streaming *stream) static int uvc_video_clock_init(struct uvc_streaming *stream)
{ {
struct uvc_clock *clock = &stream->clock; struct uvc_clock *clock = &stream->clock;
spin_lock_init(&clock->lock); spin_lock_init(&clock->lock);
clock->head = 0;
clock->count = 0;
clock->size = 32; clock->size = 32;
clock->last_sof = -1;
clock->sof_offset = -1;
clock->samples = kmalloc(clock->size * sizeof(*clock->samples), clock->samples = kmalloc(clock->size * sizeof(*clock->samples),
GFP_KERNEL); GFP_KERNEL);
if (clock->samples == NULL) if (clock->samples == NULL)
return -ENOMEM; return -ENOMEM;
uvc_video_clock_reset(stream);
return 0; return 0;
} }
@ -1424,8 +1432,6 @@ static void uvc_uninit_video(struct uvc_streaming *stream, int free_buffers)
if (free_buffers) if (free_buffers)
uvc_free_urb_buffers(stream); uvc_free_urb_buffers(stream);
uvc_video_clock_cleanup(stream);
} }
/* /*
@ -1555,10 +1561,6 @@ static int uvc_init_video(struct uvc_streaming *stream, gfp_t gfp_flags)
uvc_video_stats_start(stream); uvc_video_stats_start(stream);
ret = uvc_video_clock_init(stream);
if (ret < 0)
return ret;
if (intf->num_altsetting > 1) { if (intf->num_altsetting > 1) {
struct usb_host_endpoint *best_ep = NULL; struct usb_host_endpoint *best_ep = NULL;
unsigned int best_psize = 3 * 1024; unsigned int best_psize = 3 * 1024;
@ -1683,6 +1685,8 @@ int uvc_video_resume(struct uvc_streaming *stream, int reset)
stream->frozen = 0; stream->frozen = 0;
uvc_video_clock_reset(stream);
ret = uvc_commit_video(stream, &stream->ctrl); ret = uvc_commit_video(stream, &stream->ctrl);
if (ret < 0) { if (ret < 0) {
uvc_queue_enable(&stream->queue, 0); uvc_queue_enable(&stream->queue, 0);
@ -1819,25 +1823,35 @@ int uvc_video_enable(struct uvc_streaming *stream, int enable)
uvc_uninit_video(stream, 1); uvc_uninit_video(stream, 1);
usb_set_interface(stream->dev->udev, stream->intfnum, 0); usb_set_interface(stream->dev->udev, stream->intfnum, 0);
uvc_queue_enable(&stream->queue, 0); uvc_queue_enable(&stream->queue, 0);
uvc_video_clock_cleanup(stream);
return 0; return 0;
} }
ret = uvc_queue_enable(&stream->queue, 1); ret = uvc_video_clock_init(stream);
if (ret < 0) if (ret < 0)
return ret; return ret;
ret = uvc_queue_enable(&stream->queue, 1);
if (ret < 0)
goto error_queue;
/* Commit the streaming parameters. */ /* Commit the streaming parameters. */
ret = uvc_commit_video(stream, &stream->ctrl); ret = uvc_commit_video(stream, &stream->ctrl);
if (ret < 0) { if (ret < 0)
uvc_queue_enable(&stream->queue, 0); goto error_commit;
return ret;
}
ret = uvc_init_video(stream, GFP_KERNEL); ret = uvc_init_video(stream, GFP_KERNEL);
if (ret < 0) { if (ret < 0)
usb_set_interface(stream->dev->udev, stream->intfnum, 0); goto error_video;
uvc_queue_enable(&stream->queue, 0);
} return 0;
error_video:
usb_set_interface(stream->dev->udev, stream->intfnum, 0);
error_commit:
uvc_queue_enable(&stream->queue, 0);
error_queue:
uvc_video_clock_cleanup(stream);
return ret; return ret;
} }