mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-12-26 17:25:16 +07:00
10e1fdb958
Currently, disconnecting a USB webcam while it is in use prints out a number of warnings, such as: WARNING: CPU: 2 PID: 3118 at /build/linux-ezBi1T/linux-4.8.0/fs/sysfs/group.c:237 sysfs_remove_group+0x8b/0x90 sysfs group ffffffffa7cd0780 not found for kobject 'event13' This has been noticed before. [0] This is because of the order in which things are torn down. If there are no streams active during a USB disconnect: - uvc_disconnect() is invoked via device_del() through the bus notifier mechanism. - this calls uvc_unregister_video(). - uvc_unregister_video() unregisters the video device for each stream, - because there are no streams open, it calls uvc_delete() - uvc_delete() calls uvc_status_cleanup(), which cleans up the status input device. - uvc_delete() calls media_device_unregister(), which cleans up the media device - uvc_delete(), uvc_unregister_video() and uvc_disconnect() all return, and we end up back in device_del(). - device_del() then cleans up the sysfs folder for the camera with dpm_sysfs_remove(). Because uvc_status_cleanup() and media_device_unregister() have already been called, this all works nicely. If, on the other hand, there *are* streams active during a USB disconnect: - uvc_disconnect() is invoked - this calls uvc_unregister_video() - uvc_unregister_video() unregisters the video device for each stream, - uvc_unregister_video() and uvc_disconnect() return, and we end up back in device_del(). - device_del() then cleans up the sysfs folder for the camera with dpm_sysfs_remove(). Because the status input device and the media device are children of the USB device, this also deletes their sysfs folders. - Sometime later, the final stream is closed, invoking uvc_release(). - uvc_release() calls uvc_delete() - uvc_delete() calls uvc_status_cleanup(), which cleans up the status input device. Because the sysfs directory has already been removed, this causes a WARNing. - uvc_delete() calls media_device_unregister(), which cleans up the media device. Because the sysfs directory has already been removed, this causes another WARNing. To fix this, we need to make sure the devices are always unregistered before the end of uvc_disconnect(). To this, move the unregistration into the disconnect path: - split uvc_status_cleanup() into two parts, one on disconnect that unregisters and one on delete that frees. - move v4l2_device_unregister() and media_device_unregister() into the disconnect path. [0]: https://lkml.org/lkml/2016/12/8/657 [Renamed uvc_input_cleanup() to uvc_input_unregister()] Signed-off-by: Daniel Axtens <dja@axtens.net> Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Signed-off-by: Mauro Carvalho Chehab <mchehab+samsung@kernel.org>
319 lines
7.3 KiB
C
319 lines
7.3 KiB
C
/*
|
|
* uvc_status.c -- USB Video Class driver - Status endpoint
|
|
*
|
|
* Copyright (C) 2005-2009
|
|
* Laurent Pinchart (laurent.pinchart@ideasonboard.com)
|
|
*
|
|
* 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.
|
|
*
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/input.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/usb.h>
|
|
#include <linux/usb/input.h>
|
|
|
|
#include "uvcvideo.h"
|
|
|
|
/* --------------------------------------------------------------------------
|
|
* Input device
|
|
*/
|
|
#ifdef CONFIG_USB_VIDEO_CLASS_INPUT_EVDEV
|
|
static int uvc_input_init(struct uvc_device *dev)
|
|
{
|
|
struct input_dev *input;
|
|
int ret;
|
|
|
|
input = input_allocate_device();
|
|
if (input == NULL)
|
|
return -ENOMEM;
|
|
|
|
usb_make_path(dev->udev, dev->input_phys, sizeof(dev->input_phys));
|
|
strlcat(dev->input_phys, "/button", sizeof(dev->input_phys));
|
|
|
|
input->name = dev->name;
|
|
input->phys = dev->input_phys;
|
|
usb_to_input_id(dev->udev, &input->id);
|
|
input->dev.parent = &dev->intf->dev;
|
|
|
|
__set_bit(EV_KEY, input->evbit);
|
|
__set_bit(KEY_CAMERA, input->keybit);
|
|
|
|
if ((ret = input_register_device(input)) < 0)
|
|
goto error;
|
|
|
|
dev->input = input;
|
|
return 0;
|
|
|
|
error:
|
|
input_free_device(input);
|
|
return ret;
|
|
}
|
|
|
|
static void uvc_input_unregister(struct uvc_device *dev)
|
|
{
|
|
if (dev->input)
|
|
input_unregister_device(dev->input);
|
|
}
|
|
|
|
static void uvc_input_report_key(struct uvc_device *dev, unsigned int code,
|
|
int value)
|
|
{
|
|
if (dev->input) {
|
|
input_report_key(dev->input, code, value);
|
|
input_sync(dev->input);
|
|
}
|
|
}
|
|
|
|
#else
|
|
#define uvc_input_init(dev)
|
|
#define uvc_input_unregister(dev)
|
|
#define uvc_input_report_key(dev, code, value)
|
|
#endif /* CONFIG_USB_VIDEO_CLASS_INPUT_EVDEV */
|
|
|
|
/* --------------------------------------------------------------------------
|
|
* Status interrupt endpoint
|
|
*/
|
|
struct uvc_streaming_status {
|
|
u8 bStatusType;
|
|
u8 bOriginator;
|
|
u8 bEvent;
|
|
u8 bValue[];
|
|
} __packed;
|
|
|
|
struct uvc_control_status {
|
|
u8 bStatusType;
|
|
u8 bOriginator;
|
|
u8 bEvent;
|
|
u8 bSelector;
|
|
u8 bAttribute;
|
|
u8 bValue[];
|
|
} __packed;
|
|
|
|
static void uvc_event_streaming(struct uvc_device *dev,
|
|
struct uvc_streaming_status *status, int len)
|
|
{
|
|
if (len < 3) {
|
|
uvc_trace(UVC_TRACE_STATUS, "Invalid streaming status event "
|
|
"received.\n");
|
|
return;
|
|
}
|
|
|
|
if (status->bEvent == 0) {
|
|
if (len < 4)
|
|
return;
|
|
uvc_trace(UVC_TRACE_STATUS, "Button (intf %u) %s len %d\n",
|
|
status->bOriginator,
|
|
status->bValue[0] ? "pressed" : "released", len);
|
|
uvc_input_report_key(dev, KEY_CAMERA, status->bValue[0]);
|
|
} else {
|
|
uvc_trace(UVC_TRACE_STATUS,
|
|
"Stream %u error event %02x len %d.\n",
|
|
status->bOriginator, status->bEvent, len);
|
|
}
|
|
}
|
|
|
|
#define UVC_CTRL_VALUE_CHANGE 0
|
|
#define UVC_CTRL_INFO_CHANGE 1
|
|
#define UVC_CTRL_FAILURE_CHANGE 2
|
|
#define UVC_CTRL_MIN_CHANGE 3
|
|
#define UVC_CTRL_MAX_CHANGE 4
|
|
|
|
static struct uvc_control *uvc_event_entity_find_ctrl(struct uvc_entity *entity,
|
|
u8 selector)
|
|
{
|
|
struct uvc_control *ctrl;
|
|
unsigned int i;
|
|
|
|
for (i = 0, ctrl = entity->controls; i < entity->ncontrols; i++, ctrl++)
|
|
if (ctrl->info.selector == selector)
|
|
return ctrl;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct uvc_control *uvc_event_find_ctrl(struct uvc_device *dev,
|
|
const struct uvc_control_status *status,
|
|
struct uvc_video_chain **chain)
|
|
{
|
|
list_for_each_entry((*chain), &dev->chains, list) {
|
|
struct uvc_entity *entity;
|
|
struct uvc_control *ctrl;
|
|
|
|
list_for_each_entry(entity, &(*chain)->entities, chain) {
|
|
if (entity->id != status->bOriginator)
|
|
continue;
|
|
|
|
ctrl = uvc_event_entity_find_ctrl(entity,
|
|
status->bSelector);
|
|
if (ctrl)
|
|
return ctrl;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static bool uvc_event_control(struct urb *urb,
|
|
const struct uvc_control_status *status, int len)
|
|
{
|
|
static const char *attrs[] = { "value", "info", "failure", "min", "max" };
|
|
struct uvc_device *dev = urb->context;
|
|
struct uvc_video_chain *chain;
|
|
struct uvc_control *ctrl;
|
|
|
|
if (len < 6 || status->bEvent != 0 ||
|
|
status->bAttribute >= ARRAY_SIZE(attrs)) {
|
|
uvc_trace(UVC_TRACE_STATUS, "Invalid control status event "
|
|
"received.\n");
|
|
return false;
|
|
}
|
|
|
|
uvc_trace(UVC_TRACE_STATUS, "Control %u/%u %s change len %d.\n",
|
|
status->bOriginator, status->bSelector,
|
|
attrs[status->bAttribute], len);
|
|
|
|
/* Find the control. */
|
|
ctrl = uvc_event_find_ctrl(dev, status, &chain);
|
|
if (!ctrl)
|
|
return false;
|
|
|
|
switch (status->bAttribute) {
|
|
case UVC_CTRL_VALUE_CHANGE:
|
|
return uvc_ctrl_status_event(urb, chain, ctrl, status->bValue);
|
|
|
|
case UVC_CTRL_INFO_CHANGE:
|
|
case UVC_CTRL_FAILURE_CHANGE:
|
|
case UVC_CTRL_MIN_CHANGE:
|
|
case UVC_CTRL_MAX_CHANGE:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void uvc_status_complete(struct urb *urb)
|
|
{
|
|
struct uvc_device *dev = urb->context;
|
|
int len, ret;
|
|
|
|
switch (urb->status) {
|
|
case 0:
|
|
break;
|
|
|
|
case -ENOENT: /* usb_kill_urb() called. */
|
|
case -ECONNRESET: /* usb_unlink_urb() called. */
|
|
case -ESHUTDOWN: /* The endpoint is being disabled. */
|
|
case -EPROTO: /* Device is disconnected (reported by some
|
|
* host controller). */
|
|
return;
|
|
|
|
default:
|
|
uvc_printk(KERN_WARNING, "Non-zero status (%d) in status "
|
|
"completion handler.\n", urb->status);
|
|
return;
|
|
}
|
|
|
|
len = urb->actual_length;
|
|
if (len > 0) {
|
|
switch (dev->status[0] & 0x0f) {
|
|
case UVC_STATUS_TYPE_CONTROL: {
|
|
struct uvc_control_status *status =
|
|
(struct uvc_control_status *)dev->status;
|
|
|
|
if (uvc_event_control(urb, status, len))
|
|
/* The URB will be resubmitted in work context. */
|
|
return;
|
|
break;
|
|
}
|
|
|
|
case UVC_STATUS_TYPE_STREAMING: {
|
|
struct uvc_streaming_status *status =
|
|
(struct uvc_streaming_status *)dev->status;
|
|
|
|
uvc_event_streaming(dev, status, len);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
uvc_trace(UVC_TRACE_STATUS, "Unknown status event "
|
|
"type %u.\n", dev->status[0]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Resubmit the URB. */
|
|
urb->interval = dev->int_ep->desc.bInterval;
|
|
if ((ret = usb_submit_urb(urb, GFP_ATOMIC)) < 0) {
|
|
uvc_printk(KERN_ERR, "Failed to resubmit status URB (%d).\n",
|
|
ret);
|
|
}
|
|
}
|
|
|
|
int uvc_status_init(struct uvc_device *dev)
|
|
{
|
|
struct usb_host_endpoint *ep = dev->int_ep;
|
|
unsigned int pipe;
|
|
int interval;
|
|
|
|
if (ep == NULL)
|
|
return 0;
|
|
|
|
uvc_input_init(dev);
|
|
|
|
dev->status = kzalloc(UVC_MAX_STATUS_SIZE, GFP_KERNEL);
|
|
if (dev->status == NULL)
|
|
return -ENOMEM;
|
|
|
|
dev->int_urb = usb_alloc_urb(0, GFP_KERNEL);
|
|
if (dev->int_urb == NULL) {
|
|
kfree(dev->status);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
pipe = usb_rcvintpipe(dev->udev, ep->desc.bEndpointAddress);
|
|
|
|
/* For high-speed interrupt endpoints, the bInterval value is used as
|
|
* an exponent of two. Some developers forgot about it.
|
|
*/
|
|
interval = ep->desc.bInterval;
|
|
if (interval > 16 && dev->udev->speed == USB_SPEED_HIGH &&
|
|
(dev->quirks & UVC_QUIRK_STATUS_INTERVAL))
|
|
interval = fls(interval) - 1;
|
|
|
|
usb_fill_int_urb(dev->int_urb, dev->udev, pipe,
|
|
dev->status, UVC_MAX_STATUS_SIZE, uvc_status_complete,
|
|
dev, interval);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void uvc_status_unregister(struct uvc_device *dev)
|
|
{
|
|
usb_kill_urb(dev->int_urb);
|
|
uvc_input_unregister(dev);
|
|
}
|
|
|
|
void uvc_status_cleanup(struct uvc_device *dev)
|
|
{
|
|
usb_free_urb(dev->int_urb);
|
|
kfree(dev->status);
|
|
}
|
|
|
|
int uvc_status_start(struct uvc_device *dev, gfp_t flags)
|
|
{
|
|
if (dev->int_urb == NULL)
|
|
return 0;
|
|
|
|
return usb_submit_urb(dev->int_urb, flags);
|
|
}
|
|
|
|
void uvc_status_stop(struct uvc_device *dev)
|
|
{
|
|
usb_kill_urb(dev->int_urb);
|
|
}
|