Some devices can implement a physical switch to disable the input of the
camera on demand. Think of it like an elegant privacy sticker.
The system can read the status of the privacy switch via a GPIO.
The ACPI table maps this GPIO to the USB device via _CRS and _DSD
descriptors, so the kernel can find it.
The userspace applications need to know if the privacy pin is enabled
or not.
The obvious way to show it to userspace is via the V4L2_CID_PRIVACY control.
This patchset implement this functionality.
v2: Thanks to all the comments from Laurent!
- move guid to unit
- support entities with no pads
- CodeStyle
- Irq handling
- pr_cont
- new ids
Ricardo Ribalda (7):
media: uvcvideo: Use pr_cont() macro
media: uvcvideo: Move guid to entity
media: uvcvideo: Allow external entities
media: uvcvideo: Allow entities with no pads
media: uvcvideo: Entity defined get_info and get_cur
media: uvcvideo: Implement UVC_EXT_GPIO_UNIT
media: uvcvideo: Add Privacy control based on EXT_GPIO
drivers/media/usb/uvc/uvc_ctrl.c | 73 ++++++++-----
drivers/media/usb/uvc/uvc_driver.c | 166 +++++++++++++++++++++++++----
drivers/media/usb/uvc/uvcvideo.h | 19 +++-
3 files changed, 207 insertions(+), 51 deletions(-)
--
2.29.1.341.ge80a0c044ae-goog
Increase the size of the id, to avoid collisions with external entities.
Signed-off-by: Ricardo Ribalda <[email protected]>
---
drivers/media/usb/uvc/uvc_driver.c | 2 +-
drivers/media/usb/uvc/uvcvideo.h | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c
index 77fea26faa9a..7a1b2decccc8 100644
--- a/drivers/media/usb/uvc/uvc_driver.c
+++ b/drivers/media/usb/uvc/uvc_driver.c
@@ -1024,7 +1024,7 @@ static const u8 uvc_media_transport_input_guid[16] =
UVC_GUID_UVC_MEDIA_TRANSPORT_INPUT;
static const u8 uvc_processing_guid[16] = UVC_GUID_UVC_PROCESSING;
-static struct uvc_entity *uvc_alloc_entity(u16 type, u8 id,
+static struct uvc_entity *uvc_alloc_entity(u16 type, u16 id,
unsigned int num_pads, unsigned int extra_size)
{
struct uvc_entity *entity;
diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h
index df7bf2d104a3..00f985001c1d 100644
--- a/drivers/media/usb/uvc/uvcvideo.h
+++ b/drivers/media/usb/uvc/uvcvideo.h
@@ -301,7 +301,7 @@ struct uvc_entity {
* chain. */
unsigned int flags;
- u8 id;
+ u16 id; /* 0-255: usb entity. 256-65535: external entities */
u16 type;
char name[64];
u8 guid[16];
--
2.29.1.341.ge80a0c044ae-goog
Allows controls to get their properties and current value
from an entity-defined function instead of via a query to the USB
device.
Signed-off-by: Ricardo Ribalda <[email protected]>
---
drivers/media/usb/uvc/uvc_ctrl.c | 21 +++++++++++++++++----
drivers/media/usb/uvc/uvcvideo.h | 3 +++
2 files changed, 20 insertions(+), 4 deletions(-)
diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c
index 0e480b75e724..076f63af1031 100644
--- a/drivers/media/usb/uvc/uvc_ctrl.c
+++ b/drivers/media/usb/uvc/uvc_ctrl.c
@@ -980,10 +980,19 @@ static int __uvc_ctrl_get(struct uvc_video_chain *chain,
return -EACCES;
if (!ctrl->loaded) {
- ret = uvc_query_ctrl(chain->dev, UVC_GET_CUR, ctrl->entity->id,
- chain->dev->intfnum, ctrl->info.selector,
+ if (ctrl->entity->get_cur) {
+ ret = ctrl->entity->get_cur(ctrl->entity,
+ ctrl->info.selector,
uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),
ctrl->info.size);
+ } else {
+ ret = uvc_query_ctrl(chain->dev, UVC_GET_CUR,
+ ctrl->entity->id,
+ chain->dev->intfnum,
+ ctrl->info.selector,
+ uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),
+ ctrl->info.size);
+ }
if (ret < 0)
return ret;
@@ -1687,8 +1696,12 @@ static int uvc_ctrl_get_flags(struct uvc_device *dev,
if (data == NULL)
return -ENOMEM;
- ret = uvc_query_ctrl(dev, UVC_GET_INFO, ctrl->entity->id, dev->intfnum,
- info->selector, data, 1);
+ if (ctrl->entity->get_info)
+ ret = ctrl->entity->get_info(ctrl->entity, ctrl->info.selector,
+ data);
+ else
+ ret = uvc_query_ctrl(dev, UVC_GET_INFO, ctrl->entity->id,
+ dev->intfnum, info->selector, data, 1);
if (!ret)
info->flags |= (data[0] & UVC_CONTROL_CAP_GET ?
UVC_CTRL_FLAG_GET_CUR : 0)
diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h
index 00f985001c1d..776b083ed466 100644
--- a/drivers/media/usb/uvc/uvcvideo.h
+++ b/drivers/media/usb/uvc/uvcvideo.h
@@ -353,6 +353,9 @@ struct uvc_entity {
u8 bNrInPins;
u8 *baSourceID;
+ int (*get_info)(struct uvc_entity *entity, u8 cs, u8 *caps);
+ int (*get_cur)(struct uvc_entity *entity, u8 cs, void *data, u16 size);
+
unsigned int ncontrols;
struct uvc_control *controls;
};
--
2.29.1.341.ge80a0c044ae-goog
Some devices can implement a physical switch to disable the input of the
camera on demand. Think of it like an elegant privacy sticker.
The system can read the status of the privacy switch via a GPIO.
It is important to know the status of the switch, e.g. to notify the
user when the camera will produce black frames and a videochat
application is used.
Since the uvc device is not aware of this pin (and it should't), we need
to implement a virtual entity that can interact with such pin.
The location of the GPIO is specified via acpi or DT. on the usb device Eg:
Scope (\_SB.PCI0.XHCI.RHUB.HS07)
{
/.../
Name (_CRS, ResourceTemplate () // _CRS: Current Resource Settings
{
GpioIo (Exclusive, PullDefault, 0x0000, 0x0000, IoRestrictionOutputOnly,
"\\_SB.PCI0.GPIO", 0x00, ResourceConsumer, ,
)
{ // Pin list
0x0064
}
})
Name (_DSD, Package (0x02) // _DSD: Device-Specific Data
{
ToUUID ("daffd814-6eba-4d8c-8a91-bc9bbf4aa301") /* Device Properties for _DSD */,
Package (0x01)
{
Package (0x02)
{
"privacy-gpio",
Package (0x04)
{
\_SB.PCI0.XHCI.RHUB.HS07,
Zero,
Zero,
One
}
}
}
})
}
Signed-off-by: Ricardo Ribalda <[email protected]>
---
drivers/media/usb/uvc/uvc_ctrl.c | 6 ++
drivers/media/usb/uvc/uvc_driver.c | 105 +++++++++++++++++++++++++++++
drivers/media/usb/uvc/uvcvideo.h | 12 ++++
3 files changed, 123 insertions(+)
diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c
index 076f63af1031..f9382f8c10a8 100644
--- a/drivers/media/usb/uvc/uvc_ctrl.c
+++ b/drivers/media/usb/uvc/uvc_ctrl.c
@@ -1301,6 +1301,9 @@ static void uvc_ctrl_status_event_work(struct work_struct *work)
mutex_unlock(&chain->ctrl_mutex);
+ if (!w->urb)
+ return;
+
/* Resubmit the URB. */
w->urb->interval = dev->int_ep->desc.bInterval;
ret = usb_submit_urb(w->urb, GFP_KERNEL);
@@ -2285,6 +2288,9 @@ int uvc_ctrl_init_device(struct uvc_device *dev)
} else if (UVC_ENTITY_TYPE(entity) == UVC_ITT_CAMERA) {
bmControls = entity->camera.bmControls;
bControlSize = entity->camera.bControlSize;
+ } else if (UVC_ENTITY_TYPE(entity) == UVC_EXT_GPIO_UNIT) {
+ bmControls = entity->gpio.bmControls;
+ bControlSize = entity->gpio.bControlSize;
}
/* Remove bogus/blacklisted controls */
diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c
index 27acc221f0d6..bb977b333752 100644
--- a/drivers/media/usb/uvc/uvc_driver.c
+++ b/drivers/media/usb/uvc/uvc_driver.c
@@ -7,6 +7,7 @@
*/
#include <linux/atomic.h>
+#include <linux/gpio/consumer.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/module.h>
@@ -1020,6 +1021,7 @@ static int uvc_parse_streaming(struct uvc_device *dev,
}
static const u8 uvc_camera_guid[16] = UVC_GUID_UVC_CAMERA;
+static const u8 uvc_gpio_guid[16] = UVC_GUID_EXT_GPIO_CONTROLLER;
static const u8 uvc_media_transport_input_guid[16] =
UVC_GUID_UVC_MEDIA_TRANSPORT_INPUT;
static const u8 uvc_processing_guid[16] = UVC_GUID_UVC_PROCESSING;
@@ -1048,6 +1050,9 @@ static struct uvc_entity *uvc_alloc_entity(u16 type, u16 id,
entity->type = type;
switch (type) {
+ case UVC_EXT_GPIO_UNIT:
+ memcpy(entity->guid, uvc_gpio_guid, 16);
+ break;
case UVC_ITT_CAMERA:
memcpy(entity->guid, uvc_camera_guid, 16);
break;
@@ -1461,6 +1466,92 @@ static int uvc_parse_control(struct uvc_device *dev)
return 0;
}
+static int uvc_gpio_get_cur(struct uvc_entity *entity, u8 cs, void *data,
+ u16 size)
+{
+ if (cs != UVC_CT_PRIVACY_CONTROL || size < 1)
+ return -EINVAL;
+
+ *(uint8_t *)data = gpiod_get_value(entity->gpio.gpio_privacy);
+ return 0;
+}
+
+static int uvc_gpio_get_info(struct uvc_entity *entity, u8 cs, u8 *caps)
+{
+ if (cs != UVC_CT_PRIVACY_CONTROL)
+ return -EINVAL;
+
+ *caps = UVC_CONTROL_CAP_GET | UVC_CONTROL_CAP_AUTOUPDATE;
+ return 0;
+}
+
+static irqreturn_t uvc_privacy_gpio_irq(int irq, void *data)
+{
+ struct uvc_device *dev = data;
+ struct uvc_video_chain *chain;
+ struct uvc_entity *unit;
+ u8 value;
+
+ /* GPIO entities are always on the first chain */
+ chain = list_first_entry(&dev->chains, struct uvc_video_chain, list);
+ list_for_each_entry(unit, &dev->entities, list) {
+ if (UVC_ENTITY_TYPE(unit) != UVC_EXT_GPIO_UNIT)
+ continue;
+ value = gpiod_get_value(unit->gpio.gpio_privacy);
+ uvc_ctrl_status_event(NULL, chain, unit->controls, &value);
+ return IRQ_HANDLED;
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int uvc_parse_gpio(struct uvc_device *dev)
+{
+ struct uvc_entity *unit;
+ struct gpio_desc *gpio_privacy;
+ int irq;
+ int ret;
+
+ gpio_privacy = devm_gpiod_get_optional(&dev->udev->dev, "privacy",
+ GPIOD_IN);
+ if (IS_ERR(gpio_privacy))
+ return PTR_ERR(gpio_privacy);
+
+ if (!gpio_privacy)
+ return 0;
+
+ unit = uvc_alloc_entity(UVC_EXT_GPIO_UNIT, UVC_EXT_GPIO_UNIT_ID, 0, 1);
+ if (!unit)
+ return -ENOMEM;
+
+ unit->gpio.gpio_privacy = gpio_privacy;
+ unit->gpio.bControlSize = 1;
+ unit->gpio.bmControls = (u8 *)unit + sizeof(*unit);
+ unit->gpio.bmControls[0] = 1;
+ unit->get_cur = uvc_gpio_get_cur;
+ unit->get_info = uvc_gpio_get_info;
+
+ sprintf(unit->name, "GPIO Unit");
+
+ list_add_tail(&unit->list, &dev->entities);
+
+ irq = gpiod_to_irq(gpio_privacy);
+ if (irq == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+
+ if (irq < 0)
+ return 0;
+
+ ret = devm_request_irq(&dev->udev->dev, irq, uvc_privacy_gpio_irq,
+ IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
+ "uvc_privacy_gpio", dev);
+ if (ret < 0)
+ dev_warn(&dev->udev->dev,
+ "Unable to request uvc_privacy_gpio irq. Continuing\n");
+
+ return 0;
+}
+
/* ------------------------------------------------------------------------
* UVC device scan
*/
@@ -1912,6 +2003,7 @@ static int uvc_scan_device(struct uvc_device *dev)
{
struct uvc_video_chain *chain;
struct uvc_entity *term;
+ struct uvc_entity *unit;
list_for_each_entry(term, &dev->entities, list) {
if (!UVC_ENTITY_IS_OTERM(term))
@@ -1950,6 +2042,13 @@ static int uvc_scan_device(struct uvc_device *dev)
return -1;
}
+ /* Add GPIO entities to the first chain */
+ chain = list_first_entry(&dev->chains, struct uvc_video_chain, list);
+ list_for_each_entry(unit, &dev->entities, list) {
+ if (UVC_ENTITY_TYPE(unit) == UVC_EXT_GPIO_UNIT)
+ list_add_tail(&unit->chain, &chain->entities);
+ }
+
return 0;
}
@@ -2282,6 +2381,12 @@ static int uvc_probe(struct usb_interface *intf,
goto error;
}
+ /* Parse the associated GPIOs */
+ if (uvc_parse_gpio(dev) < 0) {
+ uvc_trace(UVC_TRACE_PROBE, "Unable to parse UVC GPIOs\n");
+ goto error;
+ }
+
uvc_printk(KERN_INFO, "Found UVC %u.%02x device %s (%04x:%04x)\n",
dev->uvc_version >> 8, dev->uvc_version & 0xff,
udev->product ? udev->product : "<unnamed>",
diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h
index 776b083ed466..689f95ff4f12 100644
--- a/drivers/media/usb/uvc/uvcvideo.h
+++ b/drivers/media/usb/uvc/uvcvideo.h
@@ -6,6 +6,7 @@
#error "The uvcvideo.h header is deprecated, use linux/uvcvideo.h instead."
#endif /* __KERNEL__ */
+#include <linux/gpio/consumer.h>
#include <linux/kernel.h>
#include <linux/poll.h>
#include <linux/usb.h>
@@ -37,6 +38,8 @@
(UVC_ENTITY_IS_TERM(entity) && \
((entity)->type & 0x8000) == UVC_TERM_OUTPUT)
+#define UVC_EXT_GPIO_UNIT 0x7ffe
+#define UVC_EXT_GPIO_UNIT_ID 0x100
/* ------------------------------------------------------------------------
* GUIDs
@@ -56,6 +59,9 @@
#define UVC_GUID_UVC_SELECTOR \
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02}
+#define UVC_GUID_EXT_GPIO_CONTROLLER \
+ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03}
#define UVC_GUID_FORMAT_MJPEG \
{ 'M', 'J', 'P', 'G', 0x00, 0x00, 0x10, 0x00, \
@@ -348,6 +354,12 @@ struct uvc_entity {
u8 *bmControls;
u8 *bmControlsType;
} extension;
+
+ struct {
+ u8 bControlSize;
+ u8 *bmControls;
+ struct gpio_desc *gpio_privacy;
+ } gpio;
};
u8 bNrInPins;
--
2.29.1.341.ge80a0c044ae-goog