Skip to content

Commit

Permalink
[media] pwc: Rework locking
Browse files Browse the repository at this point in the history
While testing gtk-v4l's new ctrl event code, I hit the following deadlock
in the pwc driver:

Thread 1:
-Does a VIDIOC_G_CTRL
-video2_ioctl takes the modlock
-video2_ioctl calls v4l2_g_ctrl
-v4l2_g_ctrl takes the ctrl_handler lock
-v4l2_g_ctrl calls pwc_g_volatile_ctrl
-pwc_g_volatile_ctrl releases the modlock as the usb transfer can take a
 significant amount of time and we don't want to block DQBUF / QBUF too long
Thread 2:
-Does a VIDIOC_FOO_CTRL
-video2_ioctl takes the modlock
-video2_ioctl calls v4l2_foo_ctrl
-v4l2_foo_ctrl blocks while trying to take the ctrl_handler lock
Thread 1:
-Blocks while trying to re-take the modlock, as its caller will eventually
 unlock that

Now we have thread 1 waiting for the modlock while holding the ctrl_handler
lock and thread 2 waiting for the ctrl_handler lock while holding the
modlock -> deadlock.

Conclusion:
1) We cannot unlock modlock from pwc_s_ctrl / pwc_g_volatile_ctrl,
   but this can cause QBUF / DQBUF to block for up to a full second
2) After evaluating various option I came to the conclusion that pwc should
   stop using the v4l2 core locking, and instead do its own locking

Thus this patch stops pwc using the v4l2 core locking, and replaces that with
it doing its own locking where necessary.

Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
  • Loading branch information
Hans de Goede authored and Mauro Carvalho Chehab committed Jan 6, 2012
1 parent f4af659 commit c20d78c
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 82 deletions.
70 changes: 61 additions & 9 deletions drivers/media/video/pwc/pwc-ctrl.c
Original file line number Diff line number Diff line change
Expand Up @@ -649,10 +649,20 @@ static int pwc_get_leds(struct pwc_device *pdev, int *on_value, int *off_value)
static int _pwc_mpt_reset(struct pwc_device *pdev, int flags)
{
unsigned char buf;
int r;

mutex_lock(&pdev->udevlock);
if (!pdev->udev) {
r = -ENODEV;
goto leave;
}

buf = flags & 0x03; // only lower two bits are currently used
return send_control_msg(pdev,
r = send_control_msg(pdev,
SET_MPT_CTL, PT_RESET_CONTROL_FORMATTER, &buf, sizeof(buf));
leave:
mutex_unlock(&pdev->udevlock);
return r;
}

int pwc_mpt_reset(struct pwc_device *pdev, int flags)
Expand All @@ -669,6 +679,13 @@ int pwc_mpt_reset(struct pwc_device *pdev, int flags)
static int _pwc_mpt_set_angle(struct pwc_device *pdev, int pan, int tilt)
{
unsigned char buf[4];
int r;

mutex_lock(&pdev->udevlock);
if (!pdev->udev) {
r = -ENODEV;
goto leave;
}

/* set new relative angle; angles are expressed in degrees * 100,
but cam as .5 degree resolution, hence divide by 200. Also
Expand All @@ -681,8 +698,11 @@ static int _pwc_mpt_set_angle(struct pwc_device *pdev, int pan, int tilt)
buf[1] = (pan >> 8) & 0xFF;
buf[2] = tilt & 0xFF;
buf[3] = (tilt >> 8) & 0xFF;
return send_control_msg(pdev,
r = send_control_msg(pdev,
SET_MPT_CTL, PT_RELATIVE_CONTROL_FORMATTER, &buf, sizeof(buf));
leave:
mutex_unlock(&pdev->udevlock);
return r;
}

int pwc_mpt_set_angle(struct pwc_device *pdev, int pan, int tilt)
Expand Down Expand Up @@ -718,14 +738,22 @@ static int pwc_mpt_get_status(struct pwc_device *pdev, struct pwc_mpt_status *st
int ret;
unsigned char buf[5];

mutex_lock(&pdev->udevlock);
if (!pdev->udev) {
ret = -ENODEV;
goto leave;
}

ret = recv_control_msg(pdev,
GET_MPT_CTL, PT_STATUS_FORMATTER, &buf, sizeof(buf));
if (ret < 0)
return ret;
goto leave;
status->status = buf[0] & 0x7; // 3 bits are used for reporting
status->time_pan = (buf[1] << 8) + buf[2];
status->time_tilt = (buf[3] << 8) + buf[4];
return 0;
leave:
mutex_unlock(&pdev->udevlock);
return ret;
}

#ifdef CONFIG_USB_PWC_DEBUG
Expand Down Expand Up @@ -794,31 +822,39 @@ long pwc_ioctl(struct pwc_device *pdev, unsigned int cmd, void *arg)

switch(cmd) {
case VIDIOCPWCRUSER:
ret = pwc_button_ctrl(pdev, RESTORE_USER_DEFAULTS_FORMATTER);
ret = v4l2_ctrl_s_ctrl(pdev->restore_user, 0);
break;

case VIDIOCPWCSUSER:
ret = pwc_button_ctrl(pdev, SAVE_USER_DEFAULTS_FORMATTER);
ret = v4l2_ctrl_s_ctrl(pdev->save_user, 0);
break;

case VIDIOCPWCFACTORY:
ret = pwc_button_ctrl(pdev, RESTORE_FACTORY_DEFAULTS_FORMATTER);
ret = v4l2_ctrl_s_ctrl(pdev->restore_factory, 0);
break;

case VIDIOCPWCSCQUAL:
{
ARG_DEF(int, qual)

if (vb2_is_streaming(&pdev->vb_queue)) {
mutex_lock(&pdev->udevlock);
if (!pdev->udev) {
ret = -ENODEV;
goto leave;
}

if (pdev->iso_init) {
ret = -EBUSY;
break;
goto leave;
}

ARG_IN(qual)
if (ARGR(qual) < 0 || ARGR(qual) > 3)
ret = -EINVAL;
else
ret = pwc_set_video_mode(pdev, pdev->view.x, pdev->view.y, pdev->vframes, ARGR(qual), pdev->vsnapshot);
leave:
mutex_unlock(&pdev->udevlock);
break;
}

Expand Down Expand Up @@ -939,8 +975,16 @@ long pwc_ioctl(struct pwc_device *pdev, unsigned int cmd, void *arg)
{
ARG_DEF(struct pwc_leds, leds)

mutex_lock(&pdev->udevlock);
if (!pdev->udev) {
ret = -ENODEV;
break;
}

ARG_IN(leds)
ret = pwc_set_leds(pdev, ARGR(leds).led_on, ARGR(leds).led_off);

mutex_unlock(&pdev->udevlock);
break;
}

Expand All @@ -949,8 +993,16 @@ long pwc_ioctl(struct pwc_device *pdev, unsigned int cmd, void *arg)
{
ARG_DEF(struct pwc_leds, leds)

mutex_lock(&pdev->udevlock);
if (!pdev->udev) {
ret = -ENODEV;
break;
}

ret = pwc_get_leds(pdev, &ARGR(leds).led_on, &ARGR(leds).led_off);
ARG_OUT(leds)

mutex_unlock(&pdev->udevlock);
break;
}

Expand Down
7 changes: 7 additions & 0 deletions drivers/media/video/pwc/pwc-dec23.c
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,8 @@ int pwc_dec23_init(struct pwc_device *pwc, int type, unsigned char *cmd)
}
pdec = pwc->decompress_data;

mutex_init(&pdec->lock);

if (DEVICE_USE_CODEC3(type)) {
flags = cmd[2] & 0x18;
if (flags == 8)
Expand Down Expand Up @@ -858,6 +860,9 @@ void pwc_dec23_decompress(const struct pwc_device *pwc,
int flags)
{
int bandlines_left, stride, bytes_per_block;
struct pwc_dec23_private *pdec = pwc->decompress_data;

mutex_lock(&pdec->lock);

bandlines_left = pwc->image.y / 4;
bytes_per_block = pwc->view.x * 4;
Expand Down Expand Up @@ -917,4 +922,6 @@ void pwc_dec23_decompress(const struct pwc_device *pwc,

}
}

mutex_unlock(&pdec->lock);
}
2 changes: 2 additions & 0 deletions drivers/media/video/pwc/pwc-dec23.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@

struct pwc_dec23_private
{
struct mutex lock;

unsigned int scalebits;
unsigned int nbitsmask, nbits; /* Number of bits of a color in the compressed stream */

Expand Down
75 changes: 42 additions & 33 deletions drivers/media/video/pwc/pwc-if.c
Original file line number Diff line number Diff line change
Expand Up @@ -515,12 +515,11 @@ static void pwc_isoc_cleanup(struct pwc_device *pdev)
PWC_DEBUG_OPEN("<< pwc_isoc_cleanup()\n");
}

/*
* Release all queued buffers, no need to take queued_bufs_lock, since all
* iso urbs have been killed when we're called so pwc_isoc_handler won't run.
*/
static void pwc_cleanup_queued_bufs(struct pwc_device *pdev)
{
unsigned long flags = 0;

spin_lock_irqsave(&pdev->queued_bufs_lock, flags);
while (!list_empty(&pdev->queued_bufs)) {
struct pwc_frame_buf *buf;

Expand All @@ -529,6 +528,7 @@ static void pwc_cleanup_queued_bufs(struct pwc_device *pdev)
list_del(&buf->list);
vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR);
}
spin_unlock_irqrestore(&pdev->queued_bufs_lock, flags);
}

/*********
Expand Down Expand Up @@ -642,6 +642,22 @@ static const char *pwc_sensor_type_to_string(unsigned int sensor_type)
/***************************************************************************/
/* Video4Linux functions */

int pwc_test_n_set_capt_file(struct pwc_device *pdev, struct file *file)
{
int r = 0;

mutex_lock(&pdev->capt_file_lock);
if (pdev->capt_file != NULL &&
pdev->capt_file != file) {
r = -EBUSY;
goto leave;
}
pdev->capt_file = file;
leave:
mutex_unlock(&pdev->capt_file_lock);
return r;
}

static void pwc_video_release(struct v4l2_device *v)
{
struct pwc_device *pdev = container_of(v, struct pwc_device, v4l2_dev);
Expand Down Expand Up @@ -684,12 +700,9 @@ static ssize_t pwc_video_read(struct file *file, char __user *buf,
if (!pdev->udev)
return -ENODEV;

if (pdev->capt_file != NULL &&
pdev->capt_file != file)
if (pwc_test_n_set_capt_file(pdev, file))
return -EBUSY;

pdev->capt_file = file;

return vb2_read(&pdev->vb_queue, buf, count, ppos,
file->f_flags & O_NONBLOCK);
}
Expand Down Expand Up @@ -785,16 +798,24 @@ static void buffer_queue(struct vb2_buffer *vb)
unsigned long flags = 0;

spin_lock_irqsave(&pdev->queued_bufs_lock, flags);
list_add_tail(&buf->list, &pdev->queued_bufs);
/* Check the device has not disconnected between prep and queuing */
if (pdev->udev)
list_add_tail(&buf->list, &pdev->queued_bufs);
else
vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR);
spin_unlock_irqrestore(&pdev->queued_bufs_lock, flags);
}

static int start_streaming(struct vb2_queue *vq, unsigned int count)
{
struct pwc_device *pdev = vb2_get_drv_priv(vq);
int r;

if (!pdev->udev)
return -ENODEV;
mutex_lock(&pdev->udevlock);
if (!pdev->udev) {
r = -ENODEV;
goto leave;
}

/* Turn on camera and set LEDS on */
pwc_camera_power(pdev, 1);
Expand All @@ -806,35 +827,29 @@ static int start_streaming(struct vb2_queue *vq, unsigned int count)
}
pwc_set_leds(pdev, led_on, led_off);

return pwc_isoc_init(pdev);
r = pwc_isoc_init(pdev);
leave:
mutex_unlock(&pdev->udevlock);
return r;
}

static int stop_streaming(struct vb2_queue *vq)
{
struct pwc_device *pdev = vb2_get_drv_priv(vq);

mutex_lock(&pdev->udevlock);
if (pdev->udev) {
pwc_set_leds(pdev, 0, 0);
pwc_camera_power(pdev, 0);
pwc_isoc_cleanup(pdev);
}
mutex_unlock(&pdev->udevlock);

pwc_cleanup_queued_bufs(pdev);

return 0;
}

static void pwc_lock(struct vb2_queue *vq)
{
struct pwc_device *pdev = vb2_get_drv_priv(vq);
mutex_lock(&pdev->modlock);
}

static void pwc_unlock(struct vb2_queue *vq)
{
struct pwc_device *pdev = vb2_get_drv_priv(vq);
mutex_unlock(&pdev->modlock);
}

static struct vb2_ops pwc_vb_queue_ops = {
.queue_setup = queue_setup,
.buf_init = buffer_init,
Expand All @@ -844,8 +859,6 @@ static struct vb2_ops pwc_vb_queue_ops = {
.buf_queue = buffer_queue,
.start_streaming = start_streaming,
.stop_streaming = stop_streaming,
.wait_prepare = pwc_unlock,
.wait_finish = pwc_lock,
};

/***************************************************************************/
Expand Down Expand Up @@ -1137,7 +1150,7 @@ static int usb_pwc_probe(struct usb_interface *intf, const struct usb_device_id
}
pwc_construct(pdev); /* set min/max sizes correct */

mutex_init(&pdev->modlock);
mutex_init(&pdev->capt_file_lock);
mutex_init(&pdev->udevlock);
spin_lock_init(&pdev->queued_bufs_lock);
INIT_LIST_HEAD(&pdev->queued_bufs);
Expand All @@ -1158,7 +1171,6 @@ static int usb_pwc_probe(struct usb_interface *intf, const struct usb_device_id

/* Init video_device structure */
memcpy(&pdev->vdev, &pwc_template, sizeof(pwc_template));
pdev->vdev.lock = &pdev->modlock;
strcpy(pdev->vdev.name, name);
set_bit(V4L2_FL_USE_FH_PRIO, &pdev->vdev.flags);
video_set_drvdata(&pdev->vdev, pdev);
Expand Down Expand Up @@ -1285,16 +1297,13 @@ static void usb_pwc_disconnect(struct usb_interface *intf)
struct pwc_device *pdev = container_of(v, struct pwc_device, v4l2_dev);

mutex_lock(&pdev->udevlock);
mutex_lock(&pdev->modlock);

/* No need to keep the urbs around after disconnection */
pwc_isoc_cleanup(pdev);
pwc_cleanup_queued_bufs(pdev);
pdev->udev = NULL;

mutex_unlock(&pdev->modlock);
mutex_unlock(&pdev->udevlock);

pwc_cleanup_queued_bufs(pdev);

pwc_remove_sysfs_files(pdev);
video_unregister_device(&pdev->vdev);
v4l2_device_unregister(&pdev->v4l2_dev);
Expand Down
Loading

0 comments on commit c20d78c

Please sign in to comment.