diff options
Diffstat (limited to 'drivers/media/video/uvc/uvc_ctrl.c')
-rw-r--r-- | drivers/media/video/uvc/uvc_ctrl.c | 712 |
1 files changed, 413 insertions, 299 deletions
diff --git a/drivers/media/video/uvc/uvc_ctrl.c b/drivers/media/video/uvc/uvc_ctrl.c index a350fad..f169f77 100644 --- a/drivers/media/video/uvc/uvc_ctrl.c +++ b/drivers/media/video/uvc/uvc_ctrl.c @@ -1,8 +1,8 @@ /* * uvc_ctrl.c -- USB Video Class driver - Controls * - * Copyright (C) 2005-2009 - * Laurent Pinchart (laurent.pinchart@skynet.be) + * Copyright (C) 2005-2010 + * 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 @@ -643,7 +643,7 @@ static struct uvc_control_mapping uvc_ctrl_mappings[] = { static inline __u8 *uvc_ctrl_data(struct uvc_control *ctrl, int id) { - return ctrl->uvc_data + id * ctrl->info->size; + return ctrl->uvc_data + id * ctrl->info.size; } static inline int uvc_test_bit(const __u8 *data, int bit) @@ -727,7 +727,8 @@ static const __u8 uvc_camera_guid[16] = UVC_GUID_UVC_CAMERA; static const __u8 uvc_media_transport_input_guid[16] = UVC_GUID_UVC_MEDIA_TRANSPORT_INPUT; -static int uvc_entity_match_guid(struct uvc_entity *entity, __u8 guid[16]) +static int uvc_entity_match_guid(const struct uvc_entity *entity, + const __u8 guid[16]) { switch (UVC_ENTITY_TYPE(entity)) { case UVC_ITT_CAMERA: @@ -765,10 +766,10 @@ static void __uvc_find_control(struct uvc_entity *entity, __u32 v4l2_id, for (i = 0; i < entity->ncontrols; ++i) { ctrl = &entity->controls[i]; - if (ctrl->info == NULL) + if (!ctrl->initialized) continue; - list_for_each_entry(map, &ctrl->info->mappings, list) { + list_for_each_entry(map, &ctrl->info.mappings, list) { if ((map->id == v4l2_id) && !next) { *control = ctrl; *mapping = map; @@ -815,36 +816,36 @@ static int uvc_ctrl_populate_cache(struct uvc_video_chain *chain, { int ret; - if (ctrl->info->flags & UVC_CONTROL_GET_DEF) { + if (ctrl->info.flags & UVC_CONTROL_GET_DEF) { ret = uvc_query_ctrl(chain->dev, UVC_GET_DEF, ctrl->entity->id, - chain->dev->intfnum, ctrl->info->selector, + chain->dev->intfnum, ctrl->info.selector, uvc_ctrl_data(ctrl, UVC_CTRL_DATA_DEF), - ctrl->info->size); + ctrl->info.size); if (ret < 0) return ret; } - if (ctrl->info->flags & UVC_CONTROL_GET_MIN) { + if (ctrl->info.flags & UVC_CONTROL_GET_MIN) { ret = uvc_query_ctrl(chain->dev, UVC_GET_MIN, ctrl->entity->id, - chain->dev->intfnum, ctrl->info->selector, + chain->dev->intfnum, ctrl->info.selector, uvc_ctrl_data(ctrl, UVC_CTRL_DATA_MIN), - ctrl->info->size); + ctrl->info.size); if (ret < 0) return ret; } - if (ctrl->info->flags & UVC_CONTROL_GET_MAX) { + if (ctrl->info.flags & UVC_CONTROL_GET_MAX) { ret = uvc_query_ctrl(chain->dev, UVC_GET_MAX, ctrl->entity->id, - chain->dev->intfnum, ctrl->info->selector, + chain->dev->intfnum, ctrl->info.selector, uvc_ctrl_data(ctrl, UVC_CTRL_DATA_MAX), - ctrl->info->size); + ctrl->info.size); if (ret < 0) return ret; } - if (ctrl->info->flags & UVC_CONTROL_GET_RES) { + if (ctrl->info.flags & UVC_CONTROL_GET_RES) { ret = uvc_query_ctrl(chain->dev, UVC_GET_RES, ctrl->entity->id, - chain->dev->intfnum, ctrl->info->selector, + chain->dev->intfnum, ctrl->info.selector, uvc_ctrl_data(ctrl, UVC_CTRL_DATA_RES), - ctrl->info->size); + ctrl->info.size); if (ret < 0) return ret; } @@ -862,9 +863,15 @@ int uvc_query_v4l2_ctrl(struct uvc_video_chain *chain, unsigned int i; int ret; + ret = mutex_lock_interruptible(&chain->ctrl_mutex); + if (ret < 0) + return -ERESTARTSYS; + ctrl = uvc_find_control(chain, v4l2_ctrl->id, &mapping); - if (ctrl == NULL) - return -EINVAL; + if (ctrl == NULL) { + ret = -EINVAL; + goto done; + } memset(v4l2_ctrl, 0, sizeof *v4l2_ctrl); v4l2_ctrl->id = mapping->id; @@ -872,18 +879,18 @@ int uvc_query_v4l2_ctrl(struct uvc_video_chain *chain, strlcpy(v4l2_ctrl->name, mapping->name, sizeof v4l2_ctrl->name); v4l2_ctrl->flags = 0; - if (!(ctrl->info->flags & UVC_CONTROL_GET_CUR)) + if (!(ctrl->info.flags & UVC_CONTROL_GET_CUR)) v4l2_ctrl->flags |= V4L2_CTRL_FLAG_WRITE_ONLY; - if (!(ctrl->info->flags & UVC_CONTROL_SET_CUR)) + if (!(ctrl->info.flags & UVC_CONTROL_SET_CUR)) v4l2_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; if (!ctrl->cached) { ret = uvc_ctrl_populate_cache(chain, ctrl); if (ret < 0) - return ret; + goto done; } - if (ctrl->info->flags & UVC_CONTROL_GET_DEF) { + if (ctrl->info.flags & UVC_CONTROL_GET_DEF) { v4l2_ctrl->default_value = mapping->get(mapping, UVC_GET_DEF, uvc_ctrl_data(ctrl, UVC_CTRL_DATA_DEF)); } @@ -902,37 +909,39 @@ int uvc_query_v4l2_ctrl(struct uvc_video_chain *chain, } } - return 0; + goto done; case V4L2_CTRL_TYPE_BOOLEAN: v4l2_ctrl->minimum = 0; v4l2_ctrl->maximum = 1; v4l2_ctrl->step = 1; - return 0; + goto done; case V4L2_CTRL_TYPE_BUTTON: v4l2_ctrl->minimum = 0; v4l2_ctrl->maximum = 0; v4l2_ctrl->step = 0; - return 0; + goto done; default: break; } - if (ctrl->info->flags & UVC_CONTROL_GET_MIN) + if (ctrl->info.flags & UVC_CONTROL_GET_MIN) v4l2_ctrl->minimum = mapping->get(mapping, UVC_GET_MIN, uvc_ctrl_data(ctrl, UVC_CTRL_DATA_MIN)); - if (ctrl->info->flags & UVC_CONTROL_GET_MAX) + if (ctrl->info.flags & UVC_CONTROL_GET_MAX) v4l2_ctrl->maximum = mapping->get(mapping, UVC_GET_MAX, uvc_ctrl_data(ctrl, UVC_CTRL_DATA_MAX)); - if (ctrl->info->flags & UVC_CONTROL_GET_RES) + if (ctrl->info.flags & UVC_CONTROL_GET_RES) v4l2_ctrl->step = mapping->get(mapping, UVC_GET_RES, uvc_ctrl_data(ctrl, UVC_CTRL_DATA_RES)); - return 0; +done: + mutex_unlock(&chain->ctrl_mutex); + return ret; } @@ -977,14 +986,14 @@ static int uvc_ctrl_commit_entity(struct uvc_device *dev, for (i = 0; i < entity->ncontrols; ++i) { ctrl = &entity->controls[i]; - if (ctrl->info == NULL) + if (!ctrl->initialized) continue; /* Reset the loaded flag for auto-update controls that were * marked as loaded in uvc_ctrl_get/uvc_ctrl_set to prevent * uvc_ctrl_get from using the cached value. */ - if (ctrl->info->flags & UVC_CONTROL_AUTO_UPDATE) + if (ctrl->info.flags & UVC_CONTROL_AUTO_UPDATE) ctrl->loaded = 0; if (!ctrl->dirty) @@ -992,16 +1001,16 @@ static int uvc_ctrl_commit_entity(struct uvc_device *dev, if (!rollback) ret = uvc_query_ctrl(dev, UVC_SET_CUR, ctrl->entity->id, - dev->intfnum, ctrl->info->selector, + dev->intfnum, ctrl->info.selector, uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT), - ctrl->info->size); + ctrl->info.size); else ret = 0; if (rollback || ret < 0) memcpy(uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT), uvc_ctrl_data(ctrl, UVC_CTRL_DATA_BACKUP), - ctrl->info->size); + ctrl->info.size); ctrl->dirty = 0; @@ -1039,14 +1048,14 @@ int uvc_ctrl_get(struct uvc_video_chain *chain, int ret; ctrl = uvc_find_control(chain, xctrl->id, &mapping); - if (ctrl == NULL || (ctrl->info->flags & UVC_CONTROL_GET_CUR) == 0) + if (ctrl == NULL || (ctrl->info.flags & UVC_CONTROL_GET_CUR) == 0) return -EINVAL; if (!ctrl->loaded) { ret = uvc_query_ctrl(chain->dev, UVC_GET_CUR, ctrl->entity->id, - chain->dev->intfnum, ctrl->info->selector, + chain->dev->intfnum, ctrl->info.selector, uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT), - ctrl->info->size); + ctrl->info.size); if (ret < 0) return ret; @@ -1081,7 +1090,7 @@ int uvc_ctrl_set(struct uvc_video_chain *chain, int ret; ctrl = uvc_find_control(chain, xctrl->id, &mapping); - if (ctrl == NULL || (ctrl->info->flags & UVC_CONTROL_SET_CUR) == 0) + if (ctrl == NULL || (ctrl->info.flags & UVC_CONTROL_SET_CUR) == 0) return -EINVAL; /* Clamp out of range values. */ @@ -1127,16 +1136,16 @@ int uvc_ctrl_set(struct uvc_video_chain *chain, * needs to be loaded from the device to perform the read-modify-write * operation. */ - if (!ctrl->loaded && (ctrl->info->size * 8) != mapping->size) { - if ((ctrl->info->flags & UVC_CONTROL_GET_CUR) == 0) { + if (!ctrl->loaded && (ctrl->info.size * 8) != mapping->size) { + if ((ctrl->info.flags & UVC_CONTROL_GET_CUR) == 0) { memset(uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT), - 0, ctrl->info->size); + 0, ctrl->info.size); } else { ret = uvc_query_ctrl(chain->dev, UVC_GET_CUR, ctrl->entity->id, chain->dev->intfnum, - ctrl->info->selector, + ctrl->info.selector, uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT), - ctrl->info->size); + ctrl->info.size); if (ret < 0) return ret; } @@ -1148,7 +1157,7 @@ int uvc_ctrl_set(struct uvc_video_chain *chain, if (!ctrl->dirty) { memcpy(uvc_ctrl_data(ctrl, UVC_CTRL_DATA_BACKUP), uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT), - ctrl->info->size); + ctrl->info.size); } mapping->set(mapping, value, @@ -1163,12 +1172,138 @@ int uvc_ctrl_set(struct uvc_video_chain *chain, * Dynamic controls */ +static void uvc_ctrl_fixup_xu_info(struct uvc_device *dev, + const struct uvc_control *ctrl, struct uvc_control_info *info) +{ + struct uvc_ctrl_fixup { + struct usb_device_id id; + u8 entity; + u8 selector; + u8 flags; + }; + + static const struct uvc_ctrl_fixup fixups[] = { + { { USB_DEVICE(0x046d, 0x08c2) }, 9, 1, + UVC_CONTROL_GET_MIN | UVC_CONTROL_GET_MAX | + UVC_CONTROL_GET_DEF | UVC_CONTROL_SET_CUR | + UVC_CONTROL_AUTO_UPDATE }, + { { USB_DEVICE(0x046d, 0x08cc) }, 9, 1, + UVC_CONTROL_GET_MIN | UVC_CONTROL_GET_MAX | + UVC_CONTROL_GET_DEF | UVC_CONTROL_SET_CUR | + UVC_CONTROL_AUTO_UPDATE }, + { { USB_DEVICE(0x046d, 0x0994) }, 9, 1, + UVC_CONTROL_GET_MIN | UVC_CONTROL_GET_MAX | + UVC_CONTROL_GET_DEF | UVC_CONTROL_SET_CUR | + UVC_CONTROL_AUTO_UPDATE }, + }; + + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(fixups); ++i) { + if (!usb_match_one_id(dev->intf, &fixups[i].id)) + continue; + + if (fixups[i].entity == ctrl->entity->id && + fixups[i].selector == info->selector) { + info->flags = fixups[i].flags; + return; + } + } +} + +/* + * Query control information (size and flags) for XU controls. + */ +static int uvc_ctrl_fill_xu_info(struct uvc_device *dev, + const struct uvc_control *ctrl, struct uvc_control_info *info) +{ + u8 *data; + int ret; + + data = kmalloc(2, GFP_KERNEL); + if (data == NULL) + return -ENOMEM; + + memcpy(info->entity, ctrl->entity->extension.guidExtensionCode, + sizeof(info->entity)); + info->index = ctrl->index; + info->selector = ctrl->index + 1; + + /* Query and verify the control length (GET_LEN) */ + ret = uvc_query_ctrl(dev, UVC_GET_LEN, ctrl->entity->id, dev->intfnum, + info->selector, data, 2); + if (ret < 0) { + uvc_trace(UVC_TRACE_CONTROL, + "GET_LEN failed on control %pUl/%u (%d).\n", + info->entity, info->selector, ret); + goto done; + } + + info->size = le16_to_cpup((__le16 *)data); + + /* Query the control information (GET_INFO) */ + ret = uvc_query_ctrl(dev, UVC_GET_INFO, ctrl->entity->id, dev->intfnum, + info->selector, data, 1); + if (ret < 0) { + uvc_trace(UVC_TRACE_CONTROL, + "GET_INFO failed on control %pUl/%u (%d).\n", + info->entity, info->selector, ret); + goto done; + } + + info->flags = UVC_CONTROL_GET_MIN | UVC_CONTROL_GET_MAX + | UVC_CONTROL_GET_RES | UVC_CONTROL_GET_DEF + | (data[0] & UVC_CONTROL_CAP_GET ? UVC_CONTROL_GET_CUR : 0) + | (data[0] & UVC_CONTROL_CAP_SET ? UVC_CONTROL_SET_CUR : 0) + | (data[0] & UVC_CONTROL_CAP_AUTOUPDATE ? + UVC_CONTROL_AUTO_UPDATE : 0); + + uvc_ctrl_fixup_xu_info(dev, ctrl, info); + + uvc_trace(UVC_TRACE_CONTROL, "XU control %pUl/%u queried: len %u, " + "flags { get %u set %u auto %u }.\n", + info->entity, info->selector, info->size, + (info->flags & UVC_CONTROL_GET_CUR) ? 1 : 0, + (info->flags & UVC_CONTROL_SET_CUR) ? 1 : 0, + (info->flags & UVC_CONTROL_AUTO_UPDATE) ? 1 : 0); + +done: + kfree(data); + return ret; +} + +static int uvc_ctrl_add_info(struct uvc_device *dev, struct uvc_control *ctrl, + const struct uvc_control_info *info); + +static int uvc_ctrl_init_xu_ctrl(struct uvc_device *dev, + struct uvc_control *ctrl) +{ + struct uvc_control_info info; + int ret; + + if (ctrl->initialized) + return 0; + + ret = uvc_ctrl_fill_xu_info(dev, ctrl, &info); + if (ret < 0) + return ret; + + ret = uvc_ctrl_add_info(dev, ctrl, &info); + if (ret < 0) + uvc_trace(UVC_TRACE_CONTROL, "Failed to initialize control " + "%pUl/%u on device %s entity %u\n", info.entity, + info.selector, dev->udev->devpath, ctrl->entity->id); + + return ret; +} + int uvc_xu_ctrl_query(struct uvc_video_chain *chain, struct uvc_xu_control *xctrl, int set) { struct uvc_entity *entity; struct uvc_control *ctrl = NULL; unsigned int i, found = 0; + int restore = 0; __u8 *data; int ret; @@ -1185,13 +1320,10 @@ int uvc_xu_ctrl_query(struct uvc_video_chain *chain, return -EINVAL; } - /* Find the control. */ + /* Find the control and perform delayed initialization if needed. */ for (i = 0; i < entity->ncontrols; ++i) { ctrl = &entity->controls[i]; - if (ctrl->info == NULL) - continue; - - if (ctrl->info->selector == xctrl->selector) { + if (ctrl->index == xctrl->selector - 1) { found = 1; break; } @@ -1203,40 +1335,48 @@ int uvc_xu_ctrl_query(struct uvc_video_chain *chain, return -EINVAL; } - /* Validate control data size. */ - if (ctrl->info->size != xctrl->size) - return -EINVAL; - - if ((set && !(ctrl->info->flags & UVC_CONTROL_SET_CUR)) || - (!set && !(ctrl->info->flags & UVC_CONTROL_GET_CUR))) - return -EINVAL; - if (mutex_lock_interruptible(&chain->ctrl_mutex)) return -ERESTARTSYS; + ret = uvc_ctrl_init_xu_ctrl(chain->dev, ctrl); + if (ret < 0) { + ret = -ENOENT; + goto done; + } + + /* Validate control data size. */ + if (ctrl->info.size != xctrl->size) { + ret = -EINVAL; + goto done; + } + + if ((set && !(ctrl->info.flags & UVC_CONTROL_SET_CUR)) || + (!set && !(ctrl->info.flags & UVC_CONTROL_GET_CUR))) { + ret = -EINVAL; + goto done; + } + memcpy(uvc_ctrl_data(ctrl, UVC_CTRL_DATA_BACKUP), uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT), - xctrl->size); + ctrl->info.size); data = uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT); + restore = set; if (set && copy_from_user(data, xctrl->data, xctrl->size)) { ret = -EFAULT; - goto out; + goto done; } ret = uvc_query_ctrl(chain->dev, set ? UVC_SET_CUR : UVC_GET_CUR, xctrl->unit, chain->dev->intfnum, xctrl->selector, data, xctrl->size); if (ret < 0) - goto out; + goto done; - if (!set && copy_to_user(xctrl->data, data, xctrl->size)) { + if (!set && copy_to_user(xctrl->data, data, xctrl->size)) ret = -EFAULT; - goto out; - } - -out: - if (ret) +done: + if (ret && restore) memcpy(uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT), uvc_ctrl_data(ctrl, UVC_CTRL_DATA_BACKUP), xctrl->size); @@ -1271,13 +1411,13 @@ int uvc_ctrl_resume_device(struct uvc_device *dev) for (i = 0; i < entity->ncontrols; ++i) { ctrl = &entity->controls[i]; - if (ctrl->info == NULL || !ctrl->modified || - (ctrl->info->flags & UVC_CONTROL_RESTORE) == 0) + if (!ctrl->initialized || !ctrl->modified || + (ctrl->info.flags & UVC_CONTROL_RESTORE) == 0) continue; printk(KERN_INFO "restoring control %pUl/%u/%u\n", - ctrl->info->entity, ctrl->info->index, - ctrl->info->selector); + ctrl->info.entity, ctrl->info.index, + ctrl->info.selector); ctrl->dirty = 1; } @@ -1293,201 +1433,150 @@ int uvc_ctrl_resume_device(struct uvc_device *dev) * Control and mapping handling */ -static int uvc_ctrl_add_ctrl(struct uvc_device *dev, - struct uvc_control_info *info) +/* + * Add control information to a given control. + */ +static int uvc_ctrl_add_info(struct uvc_device *dev, struct uvc_control *ctrl, + const struct uvc_control_info *info) { - struct uvc_entity *entity; - struct uvc_control *ctrl = NULL; - int ret = 0, found = 0; - unsigned int i; - u8 *uvc_info; - u8 *uvc_data; - - list_for_each_entry(entity, &dev->entities, list) { - if (!uvc_entity_match_guid(entity, info->entity)) - continue; - - for (i = 0; i < entity->ncontrols; ++i) { - ctrl = &entity->controls[i]; - if (ctrl->index == info->index) { - found = 1; - break; - } - } - - if (found) - break; - } - - if (!found) - return 0; - - uvc_data = kmalloc(info->size * UVC_CTRL_DATA_LAST + 1, GFP_KERNEL); - if (uvc_data == NULL) - return -ENOMEM; - - uvc_info = uvc_data + info->size * UVC_CTRL_DATA_LAST; - - if (UVC_ENTITY_TYPE(entity) == UVC_VC_EXTENSION_UNIT) { - /* Check if the device control information and length match - * the user supplied information. - */ - ret = uvc_query_ctrl(dev, UVC_GET_LEN, ctrl->entity->id, - dev->intfnum, info->selector, uvc_data, 2); - if (ret < 0) { - uvc_trace(UVC_TRACE_CONTROL, - "GET_LEN failed on control %pUl/%u (%d).\n", - info->entity, info->selector, ret); - goto done; - } - - if (info->size != le16_to_cpu(*(__le16 *)uvc_data)) { - uvc_trace(UVC_TRACE_CONTROL, "Control %pUl/%u size " - "doesn't match user supplied value.\n", - info->entity, info->selector); - ret = -EINVAL; - goto done; - } + int ret = 0; - ret = uvc_query_ctrl(dev, UVC_GET_INFO, ctrl->entity->id, - dev->intfnum, info->selector, uvc_info, 1); - if (ret < 0) { - uvc_trace(UVC_TRACE_CONTROL, - "GET_INFO failed on control %pUl/%u (%d).\n", - info->entity, info->selector, ret); - goto done; - } + memcpy(&ctrl->info, info, sizeof(*info)); + INIT_LIST_HEAD(&ctrl->info.mappings); - if (((info->flags & UVC_CONTROL_GET_CUR) && - !(*uvc_info & UVC_CONTROL_CAP_GET)) || - ((info->flags & UVC_CONTROL_SET_CUR) && - !(*uvc_info & UVC_CONTROL_CAP_SET))) { - uvc_trace(UVC_TRACE_CONTROL, "Control %pUl/%u flags " - "don't match supported operations.\n", - info->entity, info->selector); - ret = -EINVAL; - goto done; - } + /* Allocate an array to save control values (cur, def, max, etc.) */ + ctrl->uvc_data = kzalloc(ctrl->info.size * UVC_CTRL_DATA_LAST + 1, + GFP_KERNEL); + if (ctrl->uvc_data == NULL) { + ret = -ENOMEM; + goto done; } - ctrl->info = info; - ctrl->uvc_data = uvc_data; - ctrl->uvc_info = uvc_info; + ctrl->initialized = 1; uvc_trace(UVC_TRACE_CONTROL, "Added control %pUl/%u to device %s " - "entity %u\n", ctrl->info->entity, ctrl->info->selector, - dev->udev->devpath, entity->id); + "entity %u\n", ctrl->info.entity, ctrl->info.selector, + dev->udev->devpath, ctrl->entity->id); done: if (ret < 0) - kfree(uvc_data); - + kfree(ctrl->uvc_data); return ret; } /* - * Add an item to the UVC control information list, and instantiate a control - * structure for each device that supports the control. + * Add a control mapping to a given control. */ -int uvc_ctrl_add_info(struct uvc_control_info *info) +static int __uvc_ctrl_add_mapping(struct uvc_device *dev, + struct uvc_control *ctrl, const struct uvc_control_mapping *mapping) { - struct uvc_control_info *ctrl; - struct uvc_device *dev; - int ret = 0; - - /* Find matching controls by walking the devices, entities and - * controls list. - */ - mutex_lock(&uvc_driver.ctrl_mutex); + struct uvc_control_mapping *map; + unsigned int size; - /* First check if the list contains a control matching the new one. - * Bail out if it does. + /* Most mappings come from static kernel data and need to be duplicated. + * Mappings that come from userspace will be unnecessarily duplicated, + * this could be optimized. */ - list_for_each_entry(ctrl, &uvc_driver.controls, list) { - if (memcmp(ctrl->entity, info->entity, 16)) - continue; + map = kmemdup(mapping, sizeof(*mapping), GFP_KERNEL); + if (map == NULL) + return -ENOMEM; - if (ctrl->selector == info->selector) { - uvc_trace(UVC_TRACE_CONTROL, - "Control %pUl/%u is already defined.\n", - info->entity, info->selector); - ret = -EEXIST; - goto end; - } - if (ctrl->index == info->index) { - uvc_trace(UVC_TRACE_CONTROL, - "Control %pUl/%u would overwrite index %d.\n", - info->entity, info->selector, info->index); - ret = -EEXIST; - goto end; - } + size = sizeof(*mapping->menu_info) * mapping->menu_count; + map->menu_info = kmemdup(mapping->menu_info, size, GFP_KERNEL); + if (map->menu_info == NULL) { + kfree(map); + return -ENOMEM; } - list_for_each_entry(dev, &uvc_driver.devices, list) - uvc_ctrl_add_ctrl(dev, info); + if (map->get == NULL) + map->get = uvc_get_le_value; + if (map->set == NULL) + map->set = uvc_set_le_value; - INIT_LIST_HEAD(&info->mappings); - list_add_tail(&info->list, &uvc_driver.controls); -end: - mutex_unlock(&uvc_driver.ctrl_mutex); - return ret; + map->ctrl = &ctrl->info; + list_add_tail(&map->list, &ctrl->info.mappings); + uvc_trace(UVC_TRACE_CONTROL, + "Adding mapping '%s' to control %pUl/%u.\n", + map->name, ctrl->info.entity, ctrl->info.selector); + + return 0; } -int uvc_ctrl_add_mapping(struct uvc_control_mapping *mapping) +int uvc_ctrl_add_mapping(struct uvc_video_chain *chain, + const struct uvc_control_mapping *mapping) { - struct uvc_control_info *info; + struct uvc_device *dev = chain->dev; struct uvc_control_mapping *map; - int ret = -EINVAL; - - if (mapping->get == NULL) - mapping->get = uvc_get_le_value; - if (mapping->set == NULL) - mapping->set = uvc_set_le_value; + struct uvc_entity *entity; + struct uvc_control *ctrl; + int found = 0; + int ret; if (mapping->id & ~V4L2_CTRL_ID_MASK) { - uvc_trace(UVC_TRACE_CONTROL, "Can't add mapping '%s' with " - "invalid control id 0x%08x\n", mapping->name, + uvc_trace(UVC_TRACE_CONTROL, "Can't add mapping '%s', control " + "id 0x%08x is invalid.\n", mapping->name, mapping->id); return -EINVAL; } - mutex_lock(&uvc_driver.ctrl_mutex); - list_for_each_entry(info, &uvc_driver.controls, list) { - if (memcmp(info->entity, mapping->entity, 16) || - info->selector != mapping->selector) - continue; + /* Search for the matching (GUID/CS) control in the given device */ + list_for_each_entry(entity, &dev->entities, list) { + unsigned int i; - if (info->size * 8 < mapping->size + mapping->offset) { - uvc_trace(UVC_TRACE_CONTROL, - "Mapping '%s' would overflow control %pUl/%u\n", - mapping->name, info->entity, info->selector); - ret = -EOVERFLOW; - goto end; - } + if (UVC_ENTITY_TYPE(entity) != UVC_VC_EXTENSION_UNIT || + !uvc_entity_match_guid(entity, mapping->entity)) + continue; - /* Check if the list contains a mapping matching the new one. - * Bail out if it does. - */ - list_for_each_entry(map, &info->mappings, list) { - if (map->id == mapping->id) { - uvc_trace(UVC_TRACE_CONTROL, "Mapping '%s' is " - "already defined.\n", mapping->name); - ret = -EEXIST; - goto end; + for (i = 0; i < entity->ncontrols; ++i) { + ctrl = &entity->controls[i]; + if (ctrl->index == mapping->selector - 1) { + found = 1; + break; } } - mapping->ctrl = info; - list_add_tail(&mapping->list, &info->mappings); - uvc_trace(UVC_TRACE_CONTROL, - "Adding mapping %s to control %pUl/%u.\n", - mapping->name, info->entity, info->selector); + if (found) + break; + } + if (!found) + return -ENOENT; - ret = 0; - break; + if (mutex_lock_interruptible(&chain->ctrl_mutex)) + return -ERESTARTSYS; + + /* Perform delayed initialization of XU controls */ + ret = uvc_ctrl_init_xu_ctrl(dev, ctrl); + if (ret < 0) { + ret = -ENOENT; + goto done; } -end: - mutex_unlock(&uvc_driver.ctrl_mutex); + + list_for_each_entry(map, &ctrl->info.mappings, list) { + if (mapping->id == map->id) { + uvc_trace(UVC_TRACE_CONTROL, "Can't add mapping '%s', " + "control id 0x%08x already exists.\n", + mapping->name, mapping->id); + ret = -EEXIST; + goto done; + } + } + + /* Prevent excess memory consumption */ + if (atomic_inc_return(&dev->nmappings) > UVC_MAX_CONTROL_MAPPINGS) { + atomic_dec(&dev->nmappings); + uvc_trace(UVC_TRACE_CONTROL, "Can't add mapping '%s', maximum " + "mappings count (%u) exceeded.\n", mapping->name, + UVC_MAX_CONTROL_MAPPINGS); + ret = -ENOMEM; + goto done; + } + + ret = __uvc_ctrl_add_mapping(dev, ctrl, mapping); + if (ret < 0) + atomic_dec(&dev->nmappings); + +done: + mutex_unlock(&chain->ctrl_mutex); return ret; } @@ -1496,29 +1585,49 @@ end: * are currently the ones that crash the camera or unconditionally return an * error when queried. */ -static void -uvc_ctrl_prune_entity(struct uvc_device *dev, struct uvc_entity *entity) +static void uvc_ctrl_prune_entity(struct uvc_device *dev, + struct uvc_entity *entity) { - static const struct { + struct uvc_ctrl_blacklist { struct usb_device_id id; u8 index; - } blacklist[] = { + }; + + static const struct uvc_ctrl_blacklist processing_blacklist[] = { { { USB_DEVICE(0x13d3, 0x509b) }, 9 }, /* Gain */ { { USB_DEVICE(0x1c4f, 0x3000) }, 6 }, /* WB Temperature */ { { USB_DEVICE(0x5986, 0x0241) }, 2 }, /* Hue */ }; + static const struct uvc_ctrl_blacklist camera_blacklist[] = { + { { USB_DEVICE(0x06f8, 0x3005) }, 9 }, /* Zoom, Absolute */ + }; - u8 *controls; + const struct uvc_ctrl_blacklist *blacklist; unsigned int size; + unsigned int count; unsigned int i; + u8 *controls; - if (UVC_ENTITY_TYPE(entity) != UVC_VC_PROCESSING_UNIT) - return; + switch (UVC_ENTITY_TYPE(entity)) { + case UVC_VC_PROCESSING_UNIT: + blacklist = processing_blacklist; + count = ARRAY_SIZE(processing_blacklist); + controls = entity->processing.bmControls; + size = entity->processing.bControlSize; + break; - controls = entity->processing.bmControls; - size = entity->processing.bControlSize; + case UVC_ITT_CAMERA: + blacklist = camera_blacklist; + count = ARRAY_SIZE(camera_blacklist); + controls = entity->camera.bmControls; + size = entity->camera.bControlSize; + break; - for (i = 0; i < ARRAY_SIZE(blacklist); ++i) { + default: + return; + } + + for (i = 0; i < count; ++i) { if (!usb_match_one_id(dev->intf, &blacklist[i].id)) continue; @@ -1534,17 +1643,54 @@ uvc_ctrl_prune_entity(struct uvc_device *dev, struct uvc_entity *entity) } /* + * Add control information and hardcoded stock control mappings to the given + * device. + */ +static void uvc_ctrl_init_ctrl(struct uvc_device *dev, struct uvc_control *ctrl) +{ + const struct uvc_control_info *info = uvc_ctrls; + const struct uvc_control_info *iend = info + ARRAY_SIZE(uvc_ctrls); + const struct uvc_control_mapping *mapping = uvc_ctrl_mappings; + const struct uvc_control_mapping *mend = + mapping + ARRAY_SIZE(uvc_ctrl_mappings); + + /* XU controls initialization requires querying the device for control + * information. As some buggy UVC devices will crash when queried + * repeatedly in a tight loop, delay XU controls initialization until + * first use. + */ + if (UVC_ENTITY_TYPE(ctrl->entity) == UVC_VC_EXTENSION_UNIT) + return; + + for (; info < iend; ++info) { + if (uvc_entity_match_guid(ctrl->entity, info->entity) && + ctrl->index == info->index) { + uvc_ctrl_add_info(dev, ctrl, info); + break; + } + } + + if (!ctrl->initialized) + return; + + for (; mapping < mend; ++mapping) { + if (uvc_entity_match_guid(ctrl->entity, mapping->entity) && + ctrl->info.selector == mapping->selector) + __uvc_ctrl_add_mapping(dev, ctrl, mapping); + } +} + +/* * Initialize device controls. */ int uvc_ctrl_init_device(struct uvc_device *dev) { - struct uvc_control_info *info; - struct uvc_control *ctrl; struct uvc_entity *entity; unsigned int i; /* Walk the entities list and instantiate controls */ list_for_each_entry(entity, &dev->entities, list) { + struct uvc_control *ctrl; unsigned int bControlSize = 0, ncontrols = 0; __u8 *bmControls = NULL; @@ -1559,20 +1705,22 @@ int uvc_ctrl_init_device(struct uvc_device *dev) bControlSize = entity->camera.bControlSize; } + /* Remove bogus/blacklisted controls */ uvc_ctrl_prune_entity(dev, entity); + /* Count supported controls and allocate the controls array */ for (i = 0; i < bControlSize; ++i) ncontrols += hweight8(bmControls[i]); - if (ncontrols == 0) continue; - entity->controls = kzalloc(ncontrols*sizeof *ctrl, GFP_KERNEL); + entity->controls = kzalloc(ncontrols * sizeof(*ctrl), + GFP_KERNEL); if (entity->controls == NULL) return -ENOMEM; - entity->ncontrols = ncontrols; + /* Initialize all supported controls */ ctrl = entity->controls; for (i = 0; i < bControlSize * 8; ++i) { if (uvc_test_bit(bmControls, i) == 0) @@ -1580,81 +1728,47 @@ int uvc_ctrl_init_device(struct uvc_device *dev) ctrl->entity = entity; ctrl->index = i; + + uvc_ctrl_init_ctrl(dev, ctrl); ctrl++; } } - /* Walk the controls info list and associate them with the device - * controls, then add the device to the global device list. This has - * to be done while holding the controls lock, to make sure - * uvc_ctrl_add_info() will not get called in-between. - */ - mutex_lock(&uvc_driver.ctrl_mutex); - list_for_each_entry(info, &uvc_driver.controls, list) - uvc_ctrl_add_ctrl(dev, info); - - list_add_tail(&dev->list, &uvc_driver.devices); - mutex_unlock(&uvc_driver.ctrl_mutex); - return 0; } /* * Cleanup device controls. */ -void uvc_ctrl_cleanup_device(struct uvc_device *dev) +static void uvc_ctrl_cleanup_mappings(struct uvc_device *dev, + struct uvc_control *ctrl) { - struct uvc_entity *entity; - unsigned int i; + struct uvc_control_mapping *mapping, *nm; - /* Remove the device from the global devices list */ - mutex_lock(&uvc_driver.ctrl_mutex); - if (dev->list.next != NULL) - list_del(&dev->list); - mutex_unlock(&uvc_driver.ctrl_mutex); - - list_for_each_entry(entity, &dev->entities, list) { - for (i = 0; i < entity->ncontrols; ++i) - kfree(entity->controls[i].uvc_data); - - kfree(entity->controls); + list_for_each_entry_safe(mapping, nm, &ctrl->info.mappings, list) { + list_del(&mapping->list); + kfree(mapping->menu_info); + kfree(mapping); } } -void uvc_ctrl_cleanup(void) +void uvc_ctrl_cleanup_device(struct uvc_device *dev) { - struct uvc_control_info *info; - struct uvc_control_info *ni; - struct uvc_control_mapping *mapping; - struct uvc_control_mapping *nm; + struct uvc_entity *entity; + unsigned int i; - list_for_each_entry_safe(info, ni, &uvc_driver.controls, list) { - if (!(info->flags & UVC_CONTROL_EXTENSION)) - continue; + /* Free controls and control mappings for all entities. */ + list_for_each_entry(entity, &dev->entities, list) { + for (i = 0; i < entity->ncontrols; ++i) { + struct uvc_control *ctrl = &entity->controls[i]; - list_for_each_entry_safe(mapping, nm, &info->mappings, list) { - list_del(&mapping->list); - kfree(mapping->menu_info); - kfree(mapping); + if (!ctrl->initialized) + continue; + + uvc_ctrl_cleanup_mappings(dev, ctrl); + kfree(ctrl->uvc_data); } - list_del(&info->list); - kfree(info); + kfree(entity->controls); } } - -void uvc_ctrl_init(void) -{ - struct uvc_control_info *ctrl = uvc_ctrls; - struct uvc_control_info *cend = ctrl + ARRAY_SIZE(uvc_ctrls); - struct uvc_control_mapping *mapping = uvc_ctrl_mappings; - struct uvc_control_mapping *mend = - mapping + ARRAY_SIZE(uvc_ctrl_mappings); - - for (; ctrl < cend; ++ctrl) - uvc_ctrl_add_info(ctrl); - - for (; mapping < mend; ++mapping) - uvc_ctrl_add_mapping(mapping); -} - |