This adds support for pipelines that fork into branches consisting of more than one entity, creating a chain for each fork and putting common entities on all chains that share them. This requires us to share the ctrl_mutex across forked chains. Whenever we discover a shared part of a chain, we assign the pointer to an existing mutex to the sharing chain instead of creating a new one. The "forward scan" is not needed anymore, as after scanning back from OTs, we go over all entities which are not on a path from an OT and accept single-XU branches, adding them to the existing chains. Also extract control locking into __uvc_ctrl_{lock,unlock} functions. Signed-off-by: Pawel Osciak <posciak@xxxxxxxxxxxx> --- drivers/media/usb/uvc/uvc_ctrl.c | 70 ++++++++----- drivers/media/usb/uvc/uvc_driver.c | 210 +++++++++++++++++++++---------------- drivers/media/usb/uvc/uvc_entity.c | 15 ++- drivers/media/usb/uvc/uvc_v4l2.c | 9 +- drivers/media/usb/uvc/uvcvideo.h | 20 +++- 5 files changed, 199 insertions(+), 125 deletions(-) diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c index a2f4501..ba159a4 100644 --- a/drivers/media/usb/uvc/uvc_ctrl.c +++ b/drivers/media/usb/uvc/uvc_ctrl.c @@ -841,6 +841,7 @@ static struct uvc_control *uvc_find_control(struct uvc_video_chain *chain, { struct uvc_control *ctrl = NULL; struct uvc_entity *entity; + struct uvc_chain_entry *entry; int next = v4l2_id & V4L2_CTRL_FLAG_NEXT_CTRL; *mapping = NULL; @@ -849,7 +850,8 @@ static struct uvc_control *uvc_find_control(struct uvc_video_chain *chain, v4l2_id &= V4L2_CTRL_ID_MASK; /* Find the control. */ - list_for_each_entry(entity, &chain->entities, chain) { + list_for_each_entry(entry, &chain->entities, chain_entry) { + entity = entry->entity; __uvc_find_control(entity, v4l2_id, mapping, &ctrl, next); if (ctrl && !next) return ctrl; @@ -1048,6 +1050,16 @@ static int __uvc_query_v4l2_ctrl(struct uvc_video_chain *chain, return 0; } +int __uvc_ctrl_lock(struct uvc_video_chain *chain) +{ + return mutex_lock_interruptible(&chain->pipeline->ctrl_mutex) ? + -ERESTARTSYS : 0; +} +void __uvc_ctrl_unlock(struct uvc_video_chain *chain) +{ + mutex_unlock(&chain->pipeline->ctrl_mutex); +} + int uvc_query_v4l2_ctrl(struct uvc_video_chain *chain, struct v4l2_queryctrl *v4l2_ctrl) { @@ -1055,9 +1067,9 @@ int uvc_query_v4l2_ctrl(struct uvc_video_chain *chain, struct uvc_control_mapping *mapping; int ret; - ret = mutex_lock_interruptible(&chain->ctrl_mutex); + ret = __uvc_ctrl_lock(chain); if (ret < 0) - return -ERESTARTSYS; + return ret; ctrl = uvc_find_control(chain, v4l2_ctrl->id, &mapping); if (ctrl == NULL) { @@ -1067,7 +1079,7 @@ int uvc_query_v4l2_ctrl(struct uvc_video_chain *chain, ret = __uvc_query_v4l2_ctrl(chain, ctrl, mapping, v4l2_ctrl); done: - mutex_unlock(&chain->ctrl_mutex); + __uvc_ctrl_unlock(chain); return ret; } @@ -1094,9 +1106,9 @@ int uvc_query_v4l2_menu(struct uvc_video_chain *chain, query_menu->id = id; query_menu->index = index; - ret = mutex_lock_interruptible(&chain->ctrl_mutex); + ret = __uvc_ctrl_lock(chain); if (ret < 0) - return -ERESTARTSYS; + return ret; ctrl = uvc_find_control(chain, query_menu->id, &mapping); if (ctrl == NULL || mapping->v4l2_type != V4L2_CTRL_TYPE_MENU) { @@ -1132,7 +1144,7 @@ int uvc_query_v4l2_menu(struct uvc_video_chain *chain, strlcpy(query_menu->name, menu_info->name, sizeof query_menu->name); done: - mutex_unlock(&chain->ctrl_mutex); + __uvc_ctrl_unlock(chain); return ret; } @@ -1257,9 +1269,9 @@ static int uvc_ctrl_add_event(struct v4l2_subscribed_event *sev, unsigned elems) struct uvc_control *ctrl; int ret; - ret = mutex_lock_interruptible(&handle->chain->ctrl_mutex); + ret = __uvc_ctrl_lock(handle->chain); if (ret < 0) - return -ERESTARTSYS; + return ret; ctrl = uvc_find_control(handle->chain, sev->id, &mapping); if (ctrl == NULL) { @@ -1285,7 +1297,7 @@ static int uvc_ctrl_add_event(struct v4l2_subscribed_event *sev, unsigned elems) } done: - mutex_unlock(&handle->chain->ctrl_mutex); + __uvc_ctrl_unlock(handle->chain); return ret; } @@ -1293,9 +1305,9 @@ static void uvc_ctrl_del_event(struct v4l2_subscribed_event *sev) { struct uvc_fh *handle = container_of(sev->fh, struct uvc_fh, vfh); - mutex_lock(&handle->chain->ctrl_mutex); + __uvc_ctrl_lock(handle->chain); list_del(&sev->node); - mutex_unlock(&handle->chain->ctrl_mutex); + __uvc_ctrl_unlock(handle->chain); } const struct v4l2_subscribed_event_ops uvc_ctrl_sub_ev_ops = { @@ -1331,7 +1343,7 @@ const struct v4l2_subscribed_event_ops uvc_ctrl_sub_ev_ops = { */ int uvc_ctrl_begin(struct uvc_video_chain *chain) { - return mutex_lock_interruptible(&chain->ctrl_mutex) ? -ERESTARTSYS : 0; + return __uvc_ctrl_lock(chain); } static int uvc_ctrl_commit_entity(struct uvc_device *dev, @@ -1390,10 +1402,12 @@ int __uvc_ctrl_commit(struct uvc_fh *handle, int rollback, { struct uvc_video_chain *chain = handle->chain; struct uvc_entity *entity; + struct uvc_chain_entry *entry; int ret = 0; /* Find the control. */ - list_for_each_entry(entity, &chain->entities, chain) { + list_for_each_entry(entry, &chain->entities, chain_entry) { + entity = entry->entity; ret = uvc_ctrl_commit_entity(chain->dev, entity, rollback); if (ret < 0) goto done; @@ -1402,7 +1416,7 @@ int __uvc_ctrl_commit(struct uvc_fh *handle, int rollback, if (!rollback) uvc_ctrl_send_events(handle, xctrls, xctrls_count); done: - mutex_unlock(&chain->ctrl_mutex); + __uvc_ctrl_unlock(chain); return ret; } @@ -1667,7 +1681,8 @@ static int uvc_ctrl_init_xu_ctrl(struct uvc_device *dev, int uvc_xu_ctrl_query(struct uvc_video_chain *chain, struct uvc_xu_control_query *xqry) { - struct uvc_entity *entity; + struct uvc_entity *entity = NULL; + struct uvc_chain_entry *entry; struct uvc_control *ctrl; unsigned int i, found = 0; __u32 reqflags; @@ -1676,13 +1691,14 @@ int uvc_xu_ctrl_query(struct uvc_video_chain *chain, int ret; /* Find the extension unit. */ - list_for_each_entry(entity, &chain->entities, chain) { + list_for_each_entry(entry, &chain->entities, chain_entry) { + entity = entry->entity; if (UVC_ENTITY_TYPE(entity) == UVC_VC_EXTENSION_UNIT && entity->id == xqry->unit) break; } - if (entity->id != xqry->unit) { + if (!entity || entity->id != xqry->unit) { uvc_trace(UVC_TRACE_CONTROL, "Extension unit %u not found.\n", xqry->unit); return -ENOENT; @@ -1703,8 +1719,9 @@ int uvc_xu_ctrl_query(struct uvc_video_chain *chain, return -ENOENT; } - if (mutex_lock_interruptible(&chain->ctrl_mutex)) - return -ERESTARTSYS; + ret = __uvc_ctrl_lock(chain); + if (ret < 0) + return ret; ret = uvc_ctrl_init_xu_ctrl(chain->dev, ctrl); if (ret < 0) { @@ -1778,7 +1795,7 @@ int uvc_xu_ctrl_query(struct uvc_video_chain *chain, ret = -EFAULT; done: kfree(data); - mutex_unlock(&chain->ctrl_mutex); + __uvc_ctrl_unlock(chain); return ret; } @@ -1904,6 +1921,7 @@ int uvc_ctrl_add_mapping(struct uvc_video_chain *chain, const struct uvc_control_mapping *mapping) { struct uvc_device *dev = chain->dev; + struct uvc_chain_entry *entry; struct uvc_control_mapping *map; struct uvc_entity *entity; struct uvc_control *ctrl; @@ -1918,8 +1936,9 @@ int uvc_ctrl_add_mapping(struct uvc_video_chain *chain, } /* Search for the matching (GUID/CS) control on the current chain */ - list_for_each_entry(entity, &chain->entities, chain) { + list_for_each_entry(entry, &chain->entities, chain_entry) { unsigned int i; + entity = entry->entity; if (UVC_ENTITY_TYPE(entity) != UVC_VC_EXTENSION_UNIT || !uvc_entity_match_guid(entity, mapping->entity)) @@ -1939,8 +1958,9 @@ int uvc_ctrl_add_mapping(struct uvc_video_chain *chain, if (!found) return -ENOENT; - if (mutex_lock_interruptible(&chain->ctrl_mutex)) - return -ERESTARTSYS; + ret = __uvc_ctrl_lock(chain); + if (ret < 0) + return ret; /* Perform delayed initialization of XU controls */ ret = uvc_ctrl_init_xu_ctrl(dev, ctrl); @@ -1974,7 +1994,7 @@ int uvc_ctrl_add_mapping(struct uvc_video_chain *chain, atomic_dec(&dev->nmappings); done: - mutex_unlock(&chain->ctrl_mutex); + __uvc_ctrl_unlock(chain); return ret; } diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c index 81695d4..d7ff707 100644 --- a/drivers/media/usb/uvc/uvc_driver.c +++ b/drivers/media/usb/uvc/uvc_driver.c @@ -1215,6 +1215,36 @@ next_descriptor: * UVC device scan */ +static int uvc_add_chain_entry(struct uvc_video_chain *chain, + struct uvc_entity *entity) +{ + struct uvc_chain_entry *chain_entry; + + chain_entry = kzalloc(sizeof(struct uvc_chain_entry), GFP_KERNEL); + if (!chain_entry) + return -ENOMEM; + + chain_entry->entity = entity; + list_add_tail(&chain_entry->chain_entry, &chain->entities); + if (!entity->chain) + entity->chain = chain; + + return 0; +} + +static void uvc_delete_chain(struct uvc_video_chain *chain) +{ + struct list_head *p, *n; + struct uvc_chain_entry *entry; + + list_for_each_safe(p, n, &chain->entities) { + entry = list_entry(p, struct uvc_chain_entry, chain_entry); + kfree(entry); + } + + kfree(chain); +} + /* * Scan the UVC descriptors to locate a chain starting at an Output Terminal * and containing the following units: @@ -1320,72 +1350,7 @@ static int uvc_scan_chain_entity(struct uvc_video_chain *chain, return -1; } - list_add_tail(&entity->chain, &chain->entities); - return 0; -} - -static int uvc_scan_chain_forward(struct uvc_video_chain *chain, - struct uvc_entity *entity, struct uvc_entity *prev) -{ - struct uvc_entity *forward; - int found; - - /* Forward scan */ - forward = NULL; - found = 0; - - while (1) { - forward = uvc_entity_by_reference(chain->dev, entity->id, - forward); - if (forward == NULL) - break; - if (forward == prev) - continue; - - switch (UVC_ENTITY_TYPE(forward)) { - case UVC_VC_EXTENSION_UNIT: - if (forward->bNrInPins != 1) { - uvc_trace(UVC_TRACE_DESCR, "Extension unit %d " - "has more than 1 input pin.\n", - entity->id); - return -EINVAL; - } - - list_add_tail(&forward->chain, &chain->entities); - if (uvc_trace_param & UVC_TRACE_PROBE) { - if (!found) - printk(" (->"); - - printk(" XU %d", forward->id); - found = 1; - } - break; - - case UVC_OTT_VENDOR_SPECIFIC: - case UVC_OTT_DISPLAY: - case UVC_OTT_MEDIA_TRANSPORT_OUTPUT: - case UVC_TT_STREAMING: - if (UVC_ENTITY_IS_ITERM(forward)) { - uvc_trace(UVC_TRACE_DESCR, "Unsupported input " - "terminal %u.\n", forward->id); - return -EINVAL; - } - - list_add_tail(&forward->chain, &chain->entities); - if (uvc_trace_param & UVC_TRACE_PROBE) { - if (!found) - printk(" (->"); - - printk(" OT %d", forward->id); - found = 1; - } - break; - } - } - if (found) - printk(")"); - - return 0; + return uvc_add_chain_entry(chain, entity); } static int uvc_scan_chain_backward(struct uvc_video_chain *chain, @@ -1394,6 +1359,7 @@ static int uvc_scan_chain_backward(struct uvc_video_chain *chain, struct uvc_entity *entity = *_entity; struct uvc_entity *term; int id = -EINVAL, i; + int ret; switch (UVC_ENTITY_TYPE(entity)) { case UVC_VC_EXTENSION_UNIT: @@ -1425,8 +1391,9 @@ static int uvc_scan_chain_backward(struct uvc_video_chain *chain, if (uvc_trace_param & UVC_TRACE_PROBE) printk(" %d", term->id); - list_add_tail(&term->chain, &chain->entities); - uvc_scan_chain_forward(chain, term, entity); + ret = uvc_add_chain_entry(chain, term); + if (ret) + return ret; } if (uvc_trace_param & UVC_TRACE_PROBE) @@ -1473,23 +1440,23 @@ static int uvc_scan_chain(struct uvc_video_chain *chain, prev = NULL; while (entity != NULL) { - /* Entity must not be part of an existing chain */ - if (entity->chain.next || entity->chain.prev) { - uvc_trace(UVC_TRACE_DESCR, "Found reference to " - "entity %d already in chain.\n", entity->id); + if (entity->chain == chain) { + uvc_trace(UVC_TRACE_DESCR, "Found a cycle in the " + "chain"); return -EINVAL; } + /* If this entity is a part of an existing chain, the + * current chain belongs to the same pipeline. + */ + if (entity->chain) + chain->pipeline = entity->chain->pipeline; + /* Process entity */ if (uvc_scan_chain_entity(chain, entity) < 0) return -EINVAL; - /* Forward scan */ - if (uvc_scan_chain_forward(chain, entity, prev) < 0) - return -EINVAL; - /* Backward scan */ - prev = entity; if (uvc_scan_chain_backward(chain, &entity) < 0) return -EINVAL; } @@ -1501,10 +1468,12 @@ static unsigned int uvc_print_terms(struct list_head *terms, u16 dir, char *buffer) { struct uvc_entity *term; + struct uvc_chain_entry *entry; unsigned int nterms = 0; char *p = buffer; - list_for_each_entry(term, terms, chain) { + list_for_each_entry(entry, terms, chain_entry) { + term = entry->entity; if (!UVC_ENTITY_IS_TERM(term) || UVC_TERM_DIRECTION(term) != dir) continue; @@ -1541,39 +1510,58 @@ static const char *uvc_print_chain(struct uvc_video_chain *chain) static int uvc_scan_device(struct uvc_device *dev) { struct uvc_video_chain *chain; - struct uvc_entity *term; + struct uvc_entity *entity, *source; + int ret; - list_for_each_entry(term, &dev->entities, list) { - if (!UVC_ENTITY_IS_OTERM(term)) + list_for_each_entry(entity, &dev->entities, list) { + if (!UVC_ENTITY_IS_OTERM(entity)) continue; - /* If the terminal is already included in a chain, skip it. - * This can happen for chains that have multiple output - * terminals, where all output terminals beside the first one - * will be inserted in the chain in forward scans. + /* Allow single-unit branches of Output Terminals to reside + * on the existing chains. */ - if (term->chain.next || term->chain.prev) - continue; + source = uvc_entity_by_id(dev, entity->baSourceID[0]); + if (entity == NULL) { + uvc_trace(UVC_TRACE_DESCR, "Found reference to " + "unknown entity %d.\n", entity->baSourceID[0]); + return -EINVAL; + } chain = kzalloc(sizeof(*chain), GFP_KERNEL); if (chain == NULL) return -ENOMEM; INIT_LIST_HEAD(&chain->entities); - mutex_init(&chain->ctrl_mutex); chain->dev = dev; v4l2_prio_init(&chain->prio); - term->flags |= UVC_ENTITY_FLAG_DEFAULT; + entity->flags |= UVC_ENTITY_FLAG_DEFAULT; - if (uvc_scan_chain(chain, term) < 0) { - kfree(chain); + if (uvc_scan_chain(chain, entity) < 0) { + uvc_delete_chain(chain); continue; } uvc_trace(UVC_TRACE_PROBE, "Found a valid video chain (%s).\n", uvc_print_chain(chain)); + /* + * If none of the entities are shared, allocate a new pipeline. + * Otherwise, the shared pipeline is already set up. + */ + if (!chain->pipeline) { + chain->pipeline = kzalloc(sizeof(*chain->pipeline), + GFP_KERNEL); + if (!chain->pipeline) { + uvc_delete_chain(chain); + return -ENOMEM; + } + mutex_init(&chain->pipeline->ctrl_mutex); + atomic_set(&chain->pipeline->num_chains, 1); + } else { + atomic_inc(&chain->pipeline->num_chains); + } + list_add_tail(&chain->list, &dev->chains); } @@ -1582,6 +1570,38 @@ static int uvc_scan_device(struct uvc_device *dev) return -1; } + /* Find branches with no OTERMs (if any) by looking for entities not + * on any chain. Accept only branches with a single Extension Unit. + */ + list_for_each_entry(entity, &dev->entities, list) { + if (entity->chain) + continue; + + if (UVC_ENTITY_TYPE(entity) != UVC_VC_EXTENSION_UNIT + || entity->bNrInPins != 1 + || uvc_entity_by_reference(dev, entity->id, NULL)) { + uvc_printk(KERN_INFO, "Found an invalid branch " + "starting at entity id %d.\n", entity->id); + return -1; + } + + /* Single-unit XU branch. */ + source = uvc_entity_by_id(dev, entity->baSourceID[0]); + if (source == NULL) { + uvc_trace(UVC_TRACE_DESCR, "Found reference to " + "unknown entity %d.\n", entity->baSourceID[0]); + return -EINVAL; + } + if (!source->chain) + continue; + + ret = uvc_add_chain_entry(source->chain, entity); + if (ret) + return ret; + uvc_trace(UVC_TRACE_DESCR, "XU %d <- (%d)\n", + entity->id, source->id); + } + return 0; } @@ -1619,7 +1639,9 @@ static void uvc_delete(struct uvc_device *dev) list_for_each_safe(p, n, &dev->chains) { struct uvc_video_chain *chain; chain = list_entry(p, struct uvc_video_chain, list); - kfree(chain); + if (atomic_dec_and_test(&chain->pipeline->num_chains)) + kfree(chain->pipeline); + uvc_delete_chain(chain); } list_for_each_safe(p, n, &dev->entities) { @@ -1763,9 +1785,11 @@ static int uvc_register_terms(struct uvc_device *dev, { struct uvc_streaming *stream; struct uvc_entity *term; + struct uvc_chain_entry *entry; int ret; - list_for_each_entry(term, &chain->entities, chain) { + list_for_each_entry(entry, &chain->entities, chain_entry) { + term = entry->entity; if (UVC_ENTITY_TYPE(term) != UVC_TT_STREAMING) continue; diff --git a/drivers/media/usb/uvc/uvc_entity.c b/drivers/media/usb/uvc/uvc_entity.c index dc56a59..657f49a 100644 --- a/drivers/media/usb/uvc/uvc_entity.c +++ b/drivers/media/usb/uvc/uvc_entity.c @@ -104,9 +104,14 @@ static int uvc_mc_init_entity(struct uvc_entity *entity) int uvc_mc_register_entities(struct uvc_video_chain *chain) { struct uvc_entity *entity; + struct uvc_chain_entry *entry; int ret; - list_for_each_entry(entity, &chain->entities, chain) { + list_for_each_entry(entry, &chain->entities, chain_entry) { + entity = entry->entity; + if (entity->registered) + continue; + ret = uvc_mc_init_entity(entity); if (ret < 0) { uvc_printk(KERN_INFO, "Failed to initialize entity for " @@ -115,13 +120,19 @@ int uvc_mc_register_entities(struct uvc_video_chain *chain) } } - list_for_each_entry(entity, &chain->entities, chain) { + list_for_each_entry(entry, &chain->entities, chain_entry) { + entity = entry->entity; + if (entity->registered) + continue; + ret = uvc_mc_register_entity(chain, entity); if (ret < 0) { uvc_printk(KERN_INFO, "Failed to register entity for " "entity %u\n", entity->id); return ret; } + + entity->registered = true; } return 0; diff --git a/drivers/media/usb/uvc/uvc_v4l2.c b/drivers/media/usb/uvc/uvc_v4l2.c index 3afff92..a899159 100644 --- a/drivers/media/usb/uvc/uvc_v4l2.c +++ b/drivers/media/usb/uvc/uvc_v4l2.c @@ -713,6 +713,7 @@ static long uvc_v4l2_do_ioctl(struct file *file, unsigned int cmd, void *arg) const struct uvc_entity *selector = chain->selector; struct v4l2_input *input = arg; struct uvc_entity *iterm = NULL; + struct uvc_chain_entry *entry; u32 index = input->index; int pin = 0; @@ -720,14 +721,18 @@ static long uvc_v4l2_do_ioctl(struct file *file, unsigned int cmd, void *arg) (chain->dev->quirks & UVC_QUIRK_IGNORE_SELECTOR_UNIT)) { if (index != 0) return -EINVAL; - list_for_each_entry(iterm, &chain->entities, chain) { + list_for_each_entry(entry, &chain->entities, + chain_entry) { + iterm = entry->entity; if (UVC_ENTITY_IS_ITERM(iterm)) break; } pin = iterm->id; } else if (index < selector->bNrInPins) { pin = selector->baSourceID[index]; - list_for_each_entry(iterm, &chain->entities, chain) { + list_for_each_entry(entry, &chain->entities, + chain_entry) { + iterm = entry->entity; if (!UVC_ENTITY_IS_ITERM(iterm)) continue; if (iterm->id == pin) diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h index 75e0153..731b378 100644 --- a/drivers/media/usb/uvc/uvcvideo.h +++ b/drivers/media/usb/uvc/uvcvideo.h @@ -229,8 +229,8 @@ struct uvc_format_desc { struct uvc_entity { struct list_head list; /* Entity as part of a UVC device. */ - struct list_head chain; /* Entity as part of a video device - * chain. */ + struct uvc_video_chain *chain; /* Entity as a part of a video device + chain. */ unsigned int flags; __u8 id; @@ -243,6 +243,7 @@ struct uvc_entity { unsigned int num_pads; unsigned int num_links; struct media_pad *pads; + bool registered; /* True if already registered with MC */ union { struct { @@ -289,6 +290,12 @@ struct uvc_entity { struct uvc_control *controls; }; +struct uvc_chain_entry { + struct list_head chain_entry; + struct uvc_entity *entity; + struct uvc_video_chain *chain; +}; + struct uvc_frame { __u8 bFrameIndex; __u8 bmCapabilities; @@ -366,6 +373,12 @@ struct uvc_video_queue { struct list_head irqqueue; }; +struct uvc_video_pipeline { + struct mutex ctrl_mutex; /* Protects controls in all + chains of this pipeline */ + atomic_t num_chains; +}; + struct uvc_video_chain { struct uvc_device *dev; struct list_head list; @@ -374,7 +387,8 @@ struct uvc_video_chain { struct uvc_entity *processing; /* Processing unit */ struct uvc_entity *selector; /* Selector unit */ - struct mutex ctrl_mutex; /* Protects ctrl.info */ + struct uvc_video_pipeline *pipeline; /* Pipeline this chain + belongs to */ struct v4l2_prio_state prio; /* V4L2 priority state */ u32 caps; /* V4L2 chain-wide caps */ -- 1.8.4 -- To unsubscribe from this list: send the line "unsubscribe linux-media" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html