mirror of
https://github.com/torvalds/linux.git
synced 2024-12-25 20:32:22 +00:00
69803ecf3a
Unplugging uvc video camera trigger following oops: eeepc kernel: [ 1393.500719] usb 3-2: USB disconnect, device number 4 eeepc kernel: [ 1393.504351] uvcvideo: Failed to resubmit video URB (-19). eeepc kernel: [ 1495.428853] BUG: unable to handle kernel paging request at 6b6b6bcb eeepc kernel: [ 1495.429017] IP: [<b0358d37>] dev_get_drvdata+0x17/0x20 eeepc kernel: [ 1495.429017] *pde = 00000000 eeepc kernel: [ 1495.429017] Oops: 0000 [#1] DEBUG_PAGEALLOC eeepc kernel: [ 1495.429017] eeepc kernel: [ 1495.429017] Pid: 3476, comm: cheese Not tainted 3.1.0-rc3-00270-g7a54f5e-dirty #485 ASUSTeK Computer INC. 900/900 eeepc kernel: [ 1495.429017] EIP: 0060:[<b0358d37>] EFLAGS: 00010202 CPU: 0 eeepc kernel: [ 1495.429017] EIP is at dev_get_drvdata+0x17/0x20 eeepc kernel: [ 1495.429017] EAX: 6b6b6b6b EBX: eb08d870 ECX: 00000000 EDX: eb08d930 eeepc kernel: [ 1495.429017] ESI: eb08d870 EDI: eb08d870 EBP: d3249cac ESP: d3249cac eeepc kernel: [ 1495.429017] DS: 007b ES: 007b FS: 0000 GS: 00e0 SS: 0068 eeepc kernel: [ 1495.429017] Process cheese (pid: 3476, ti=d3248000 task=df46d870 task.ti=d3248000) eeepc kernel: [ 1495.429017] Stack: eeepc kernel: [ 1495.429017] d3249cb8 b03e77a1 d307b840 d3249ccc b03e77d1 d307b840 eb08d870 eb08d830 eeepc kernel: [ 1495.429017] d3249ce4 b03ed3b7 00000246 d307b840 eb08d870 d3021b80 d3249cec b03ed565 eeepc kernel: [ 1495.429017] d3249cfc b03e044d e8323d10 b06e013c d3249d18 b0355fb9 fffffffe d3249d1c eeepc kernel: [ 1495.429017] Call Trace: eeepc kernel: [ 1495.429017] [<b03e77a1>] v4l2_device_disconnect+0x11/0x30 eeepc kernel: [ 1495.429017] [<b03e77d1>] v4l2_device_unregister+0x11/0x50 eeepc kernel: [ 1495.429017] [<b03ed3b7>] uvc_delete+0x37/0x110 eeepc kernel: [ 1495.429017] [<b03ed565>] uvc_release+0x25/0x30 eeepc kernel: [ 1495.429017] [<b03e044d>] v4l2_device_release+0x9d/0xc0 eeepc kernel: [ 1495.429017] [<b0355fb9>] device_release+0x19/0x90 eeepc kernel: [ 1495.429017] [<b03adfdc>] ? usb_hcd_unlink_urb+0x7c/0x90 eeepc kernel: [ 1495.429017] [<b026b99c>] kobject_release+0x3c/0x90 eeepc kernel: [ 1495.429017] [<b026b960>] ? kobject_del+0x30/0x30 eeepc kernel: [ 1495.429017] [<b026ca4c>] kref_put+0x2c/0x60 eeepc kernel: [ 1495.429017] [<b026b88d>] kobject_put+0x1d/0x50 eeepc kernel: [ 1495.429017] [<b03b2385>] ? usb_autopm_put_interface+0x25/0x30 eeepc kernel: [ 1495.429017] [<b03f0e5d>] ? uvc_v4l2_release+0x5d/0xd0 eeepc kernel: [ 1495.429017] [<b0355d2f>] put_device+0xf/0x20 eeepc kernel: [ 1495.429017] [<b03dfa96>] v4l2_release+0x56/0x60 eeepc kernel: [ 1495.429017] [<b019c8dc>] fput+0xcc/0x220 eeepc kernel: [ 1495.429017] [<b01990f4>] filp_close+0x44/0x70 eeepc kernel: [ 1495.429017] [<b012b238>] put_files_struct+0x158/0x180 eeepc kernel: [ 1495.429017] [<b012b100>] ? put_files_struct+0x20/0x180 eeepc kernel: [ 1495.429017] [<b012b2a0>] exit_files+0x40/0x50 eeepc kernel: [ 1495.429017] [<b012b9e7>] do_exit+0x5a7/0x660 eeepc kernel: [ 1495.429017] [<b0135f72>] ? __dequeue_signal+0x12/0x120 eeepc kernel: [ 1495.429017] [<b055edf2>] ? _raw_spin_unlock_irq+0x22/0x30 eeepc kernel: [ 1495.429017] [<b012badc>] do_group_exit+0x3c/0xb0 eeepc kernel: [ 1495.429017] [<b015792b>] ? trace_hardirqs_on+0xb/0x10 eeepc kernel: [ 1495.429017] [<b013755f>] get_signal_to_deliver+0x18f/0x570 eeepc kernel: [ 1495.429017] [<b01020f7>] do_signal+0x47/0x9e0 eeepc kernel: [ 1495.429017] [<b055edf2>] ? _raw_spin_unlock_irq+0x22/0x30 eeepc kernel: [ 1495.429017] [<b015792b>] ? trace_hardirqs_on+0xb/0x10 eeepc kernel: [ 1495.429017] [<b0123300>] ? T.1034+0x30/0xc0 eeepc kernel: [ 1495.429017] [<b055c45f>] ? schedule+0x29f/0x640 eeepc kernel: [ 1495.429017] [<b0102ac8>] do_notify_resume+0x38/0x40 eeepc kernel: [ 1495.429017] [<b055f154>] work_notifysig+0x9/0x11 eeepc kernel: [ 1495.429017] Code: e5 5d 83 f8 01 19 c0 f7 d0 83 e0 f0 c3 8d b4 26 00 00 00 00 55 85 c0 89 e5 75 09 31 c0 5d c3 90 8d 74 26 00 8b 40 04 85 c0 74 f0 <8b> 40 60 5d c3 8d 74 26 00 55 89 e5 53 89 c3 83 ec 04 8b 40 04 eeepc kernel: [ 1495.429017] EIP: [<b0358d37>] dev_get_drvdata+0x17/0x20 SS:ESP 0068:d3249cac eeepc kernel: [ 1495.429017] CR2: 000000006b6b6bcb eeepc kernel: [ 1495.466975] uvcvideo: Failed to resubmit video URB (-27). eeepc kernel: [ 1495.467860] uvcvideo: Failed to resubmit video URB (-27). eeepc kernel: last message repeated 3 times eeepc kernel: [ 1495.512610] ---[ end trace 73ec16848794e5a5 ]--- For uvc device, dev->vdev.dev is the &intf->dev, uvc_delete code is as below: usb_put_intf(dev->intf); usb_put_dev(dev->udev); uvc_status_cleanup(dev); uvc_ctrl_cleanup_device(dev); if (dev->vdev.dev) v4l2_device_unregister(&dev->vdev); Fix it by get_device in v4l2_device_register and put_device in v4l2_device_disconnect Reported-by: Sitsofe Wheeler <sitsofe@yahoo.com> Signed-off-by: Dave Young <hidave.darkstar@gmail.com> Tested-by: Sitsofe Wheeler <sitsofe@yahoo.com> Acked-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
254 lines
6.6 KiB
C
254 lines
6.6 KiB
C
/*
|
|
V4L2 device support.
|
|
|
|
Copyright (C) 2008 Hans Verkuil <hverkuil@xs4all.nl>
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include <linux/types.h>
|
|
#include <linux/ioctl.h>
|
|
#include <linux/i2c.h>
|
|
#if defined(CONFIG_SPI)
|
|
#include <linux/spi/spi.h>
|
|
#endif
|
|
#include <linux/videodev2.h>
|
|
#include <media/v4l2-device.h>
|
|
#include <media/v4l2-ctrls.h>
|
|
|
|
int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev)
|
|
{
|
|
if (v4l2_dev == NULL)
|
|
return -EINVAL;
|
|
|
|
INIT_LIST_HEAD(&v4l2_dev->subdevs);
|
|
spin_lock_init(&v4l2_dev->lock);
|
|
mutex_init(&v4l2_dev->ioctl_lock);
|
|
v4l2_prio_init(&v4l2_dev->prio);
|
|
kref_init(&v4l2_dev->ref);
|
|
get_device(dev);
|
|
v4l2_dev->dev = dev;
|
|
if (dev == NULL) {
|
|
/* If dev == NULL, then name must be filled in by the caller */
|
|
WARN_ON(!v4l2_dev->name[0]);
|
|
return 0;
|
|
}
|
|
|
|
/* Set name to driver name + device name if it is empty. */
|
|
if (!v4l2_dev->name[0])
|
|
snprintf(v4l2_dev->name, sizeof(v4l2_dev->name), "%s %s",
|
|
dev->driver->name, dev_name(dev));
|
|
if (!dev_get_drvdata(dev))
|
|
dev_set_drvdata(dev, v4l2_dev);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(v4l2_device_register);
|
|
|
|
static void v4l2_device_release(struct kref *ref)
|
|
{
|
|
struct v4l2_device *v4l2_dev =
|
|
container_of(ref, struct v4l2_device, ref);
|
|
|
|
if (v4l2_dev->release)
|
|
v4l2_dev->release(v4l2_dev);
|
|
}
|
|
|
|
int v4l2_device_put(struct v4l2_device *v4l2_dev)
|
|
{
|
|
return kref_put(&v4l2_dev->ref, v4l2_device_release);
|
|
}
|
|
EXPORT_SYMBOL_GPL(v4l2_device_put);
|
|
|
|
int v4l2_device_set_name(struct v4l2_device *v4l2_dev, const char *basename,
|
|
atomic_t *instance)
|
|
{
|
|
int num = atomic_inc_return(instance) - 1;
|
|
int len = strlen(basename);
|
|
|
|
if (basename[len - 1] >= '0' && basename[len - 1] <= '9')
|
|
snprintf(v4l2_dev->name, sizeof(v4l2_dev->name),
|
|
"%s-%d", basename, num);
|
|
else
|
|
snprintf(v4l2_dev->name, sizeof(v4l2_dev->name),
|
|
"%s%d", basename, num);
|
|
return num;
|
|
}
|
|
EXPORT_SYMBOL_GPL(v4l2_device_set_name);
|
|
|
|
void v4l2_device_disconnect(struct v4l2_device *v4l2_dev)
|
|
{
|
|
if (v4l2_dev->dev == NULL)
|
|
return;
|
|
|
|
if (dev_get_drvdata(v4l2_dev->dev) == v4l2_dev)
|
|
dev_set_drvdata(v4l2_dev->dev, NULL);
|
|
put_device(v4l2_dev->dev);
|
|
v4l2_dev->dev = NULL;
|
|
}
|
|
EXPORT_SYMBOL_GPL(v4l2_device_disconnect);
|
|
|
|
void v4l2_device_unregister(struct v4l2_device *v4l2_dev)
|
|
{
|
|
struct v4l2_subdev *sd, *next;
|
|
|
|
if (v4l2_dev == NULL)
|
|
return;
|
|
v4l2_device_disconnect(v4l2_dev);
|
|
|
|
/* Unregister subdevs */
|
|
list_for_each_entry_safe(sd, next, &v4l2_dev->subdevs, list) {
|
|
v4l2_device_unregister_subdev(sd);
|
|
#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE))
|
|
if (sd->flags & V4L2_SUBDEV_FL_IS_I2C) {
|
|
struct i2c_client *client = v4l2_get_subdevdata(sd);
|
|
|
|
/* We need to unregister the i2c client explicitly.
|
|
We cannot rely on i2c_del_adapter to always
|
|
unregister clients for us, since if the i2c bus
|
|
is a platform bus, then it is never deleted. */
|
|
if (client)
|
|
i2c_unregister_device(client);
|
|
continue;
|
|
}
|
|
#endif
|
|
#if defined(CONFIG_SPI)
|
|
if (sd->flags & V4L2_SUBDEV_FL_IS_SPI) {
|
|
struct spi_device *spi = v4l2_get_subdevdata(sd);
|
|
|
|
if (spi)
|
|
spi_unregister_device(spi);
|
|
continue;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
EXPORT_SYMBOL_GPL(v4l2_device_unregister);
|
|
|
|
int v4l2_device_register_subdev(struct v4l2_device *v4l2_dev,
|
|
struct v4l2_subdev *sd)
|
|
{
|
|
#if defined(CONFIG_MEDIA_CONTROLLER)
|
|
struct media_entity *entity = &sd->entity;
|
|
#endif
|
|
int err;
|
|
|
|
/* Check for valid input */
|
|
if (v4l2_dev == NULL || sd == NULL || !sd->name[0])
|
|
return -EINVAL;
|
|
|
|
/* Warn if we apparently re-register a subdev */
|
|
WARN_ON(sd->v4l2_dev != NULL);
|
|
|
|
if (!try_module_get(sd->owner))
|
|
return -ENODEV;
|
|
|
|
sd->v4l2_dev = v4l2_dev;
|
|
if (sd->internal_ops && sd->internal_ops->registered) {
|
|
err = sd->internal_ops->registered(sd);
|
|
if (err) {
|
|
module_put(sd->owner);
|
|
return err;
|
|
}
|
|
}
|
|
|
|
/* This just returns 0 if either of the two args is NULL */
|
|
err = v4l2_ctrl_add_handler(v4l2_dev->ctrl_handler, sd->ctrl_handler);
|
|
if (err) {
|
|
if (sd->internal_ops && sd->internal_ops->unregistered)
|
|
sd->internal_ops->unregistered(sd);
|
|
module_put(sd->owner);
|
|
return err;
|
|
}
|
|
|
|
#if defined(CONFIG_MEDIA_CONTROLLER)
|
|
/* Register the entity. */
|
|
if (v4l2_dev->mdev) {
|
|
err = media_device_register_entity(v4l2_dev->mdev, entity);
|
|
if (err < 0) {
|
|
if (sd->internal_ops && sd->internal_ops->unregistered)
|
|
sd->internal_ops->unregistered(sd);
|
|
module_put(sd->owner);
|
|
return err;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
spin_lock(&v4l2_dev->lock);
|
|
list_add_tail(&sd->list, &v4l2_dev->subdevs);
|
|
spin_unlock(&v4l2_dev->lock);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(v4l2_device_register_subdev);
|
|
|
|
int v4l2_device_register_subdev_nodes(struct v4l2_device *v4l2_dev)
|
|
{
|
|
struct video_device *vdev;
|
|
struct v4l2_subdev *sd;
|
|
int err;
|
|
|
|
/* Register a device node for every subdev marked with the
|
|
* V4L2_SUBDEV_FL_HAS_DEVNODE flag.
|
|
*/
|
|
list_for_each_entry(sd, &v4l2_dev->subdevs, list) {
|
|
if (!(sd->flags & V4L2_SUBDEV_FL_HAS_DEVNODE))
|
|
continue;
|
|
|
|
vdev = &sd->devnode;
|
|
strlcpy(vdev->name, sd->name, sizeof(vdev->name));
|
|
vdev->v4l2_dev = v4l2_dev;
|
|
vdev->fops = &v4l2_subdev_fops;
|
|
vdev->release = video_device_release_empty;
|
|
vdev->ctrl_handler = sd->ctrl_handler;
|
|
err = __video_register_device(vdev, VFL_TYPE_SUBDEV, -1, 1,
|
|
sd->owner);
|
|
if (err < 0)
|
|
return err;
|
|
#if defined(CONFIG_MEDIA_CONTROLLER)
|
|
sd->entity.v4l.major = VIDEO_MAJOR;
|
|
sd->entity.v4l.minor = vdev->minor;
|
|
#endif
|
|
}
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(v4l2_device_register_subdev_nodes);
|
|
|
|
void v4l2_device_unregister_subdev(struct v4l2_subdev *sd)
|
|
{
|
|
struct v4l2_device *v4l2_dev;
|
|
|
|
/* return if it isn't registered */
|
|
if (sd == NULL || sd->v4l2_dev == NULL)
|
|
return;
|
|
|
|
v4l2_dev = sd->v4l2_dev;
|
|
|
|
spin_lock(&v4l2_dev->lock);
|
|
list_del(&sd->list);
|
|
spin_unlock(&v4l2_dev->lock);
|
|
|
|
if (sd->internal_ops && sd->internal_ops->unregistered)
|
|
sd->internal_ops->unregistered(sd);
|
|
sd->v4l2_dev = NULL;
|
|
|
|
#if defined(CONFIG_MEDIA_CONTROLLER)
|
|
if (v4l2_dev->mdev)
|
|
media_device_unregister_entity(&sd->entity);
|
|
#endif
|
|
video_unregister_device(&sd->devnode);
|
|
module_put(sd->owner);
|
|
}
|
|
EXPORT_SYMBOL_GPL(v4l2_device_unregister_subdev);
|