diff options
author | Sakari Ailus <sakari.ailus@linux.intel.com> | 2017-09-24 20:54:31 -0400 |
---|---|---|
committer | Mauro Carvalho Chehab <mchehab@s-opensource.com> | 2017-10-31 13:56:24 -0400 |
commit | 2cab00bb076b9f0e8442e3d72425843d2b441143 (patch) | |
tree | b53cb0ed70297f6608433b7569702e0d1163e8c6 /drivers/media | |
parent | a3620cb48d303f07160694c00d9c1c8f0ea96690 (diff) | |
download | linux-2cab00bb076b9f0e8442e3d72425843d2b441143.tar.gz linux-2cab00bb076b9f0e8442e3d72425843d2b441143.tar.xz |
media: v4l: async: Allow binding notifiers to sub-devices
Registering a notifier has required the knowledge of struct v4l2_device
for the reason that sub-devices generally are registered to the
v4l2_device (as well as the media device, also available through
v4l2_device).
This information is not available for sub-device drivers at probe time.
What this patch does is that it allows registering notifiers without
having v4l2_device around. Instead the sub-device pointer is stored in the
notifier. Once the sub-device of the driver that registered the notifier
is registered, the notifier will gain the knowledge of the v4l2_device,
and the binding of async sub-devices from the sub-device driver's notifier
may proceed.
The complete callback of the root notifier will be called only when the
v4l2_device is available and no notifier has pending sub-devices to bind.
No complete callbacks are supported for sub-device notifiers.
Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com>
Acked-by: Hans Verkuil <hans.verkuil@cisco.com>
Acked-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.co.uk>
Signed-off-by: Mauro Carvalho Chehab <mchehab@s-opensource.com>
Diffstat (limited to 'drivers/media')
-rw-r--r-- | drivers/media/v4l2-core/v4l2-async.c | 212 |
1 files changed, 172 insertions, 40 deletions
diff --git a/drivers/media/v4l2-core/v4l2-async.c b/drivers/media/v4l2-core/v4l2-async.c index 6265717769d2..ed539c4fd5dc 100644 --- a/drivers/media/v4l2-core/v4l2-async.c +++ b/drivers/media/v4l2-core/v4l2-async.c @@ -124,11 +124,87 @@ static struct v4l2_async_subdev *v4l2_async_find_match( return NULL; } +/* Find the sub-device notifier registered by a sub-device driver. */ +static struct v4l2_async_notifier *v4l2_async_find_subdev_notifier( + struct v4l2_subdev *sd) +{ + struct v4l2_async_notifier *n; + + list_for_each_entry(n, ¬ifier_list, list) + if (n->sd == sd) + return n; + + return NULL; +} + +/* Get v4l2_device related to the notifier if one can be found. */ +static struct v4l2_device *v4l2_async_notifier_find_v4l2_dev( + struct v4l2_async_notifier *notifier) +{ + while (notifier->parent) + notifier = notifier->parent; + + return notifier->v4l2_dev; +} + +/* + * Return true if all child sub-device notifiers are complete, false otherwise. + */ +static bool v4l2_async_notifier_can_complete( + struct v4l2_async_notifier *notifier) +{ + struct v4l2_subdev *sd; + + if (!list_empty(¬ifier->waiting)) + return false; + + list_for_each_entry(sd, ¬ifier->done, async_list) { + struct v4l2_async_notifier *subdev_notifier = + v4l2_async_find_subdev_notifier(sd); + + if (subdev_notifier && + !v4l2_async_notifier_can_complete(subdev_notifier)) + return false; + } + + return true; +} + +/* + * Complete the master notifier if possible. This is done when all async + * sub-devices have been bound; v4l2_device is also available then. + */ +static int v4l2_async_notifier_try_complete( + struct v4l2_async_notifier *notifier) +{ + /* Quick check whether there are still more sub-devices here. */ + if (!list_empty(¬ifier->waiting)) + return 0; + + /* Check the entire notifier tree; find the root notifier first. */ + while (notifier->parent) + notifier = notifier->parent; + + /* This is root if it has v4l2_dev. */ + if (!notifier->v4l2_dev) + return 0; + + /* Is everything ready? */ + if (!v4l2_async_notifier_can_complete(notifier)) + return 0; + + return v4l2_async_notifier_call_complete(notifier); +} + +static int v4l2_async_notifier_try_all_subdevs( + struct v4l2_async_notifier *notifier); + static int v4l2_async_match_notify(struct v4l2_async_notifier *notifier, struct v4l2_device *v4l2_dev, struct v4l2_subdev *sd, struct v4l2_async_subdev *asd) { + struct v4l2_async_notifier *subdev_notifier; int ret; ret = v4l2_device_register_subdev(v4l2_dev, sd); @@ -149,17 +225,36 @@ static int v4l2_async_match_notify(struct v4l2_async_notifier *notifier, /* Move from the global subdevice list to notifier's done */ list_move(&sd->async_list, ¬ifier->done); - return 0; + /* + * See if the sub-device has a notifier. If not, return here. + */ + subdev_notifier = v4l2_async_find_subdev_notifier(sd); + if (!subdev_notifier || subdev_notifier->parent) + return 0; + + /* + * Proceed with checking for the sub-device notifier's async + * sub-devices, and return the result. The error will be handled by the + * caller. + */ + subdev_notifier->parent = notifier; + + return v4l2_async_notifier_try_all_subdevs(subdev_notifier); } /* Test all async sub-devices in a notifier for a match. */ static int v4l2_async_notifier_try_all_subdevs( struct v4l2_async_notifier *notifier) { - struct v4l2_device *v4l2_dev = notifier->v4l2_dev; - struct v4l2_subdev *sd, *tmp; + struct v4l2_device *v4l2_dev = + v4l2_async_notifier_find_v4l2_dev(notifier); + struct v4l2_subdev *sd; + + if (!v4l2_dev) + return 0; - list_for_each_entry_safe(sd, tmp, &subdev_list, async_list) { +again: + list_for_each_entry(sd, &subdev_list, async_list) { struct v4l2_async_subdev *asd; int ret; @@ -170,6 +265,14 @@ static int v4l2_async_notifier_try_all_subdevs( ret = v4l2_async_match_notify(notifier, v4l2_dev, sd, asd); if (ret < 0) return ret; + + /* + * v4l2_async_match_notify() may lead to registering a + * new notifier and thus changing the async subdevs + * list. In order to proceed safely from here, restart + * parsing the list from the beginning. + */ + goto again; } return 0; @@ -183,17 +286,26 @@ static void v4l2_async_cleanup(struct v4l2_subdev *sd) sd->asd = NULL; } +/* Unbind all sub-devices in the notifier tree. */ static void v4l2_async_notifier_unbind_all_subdevs( struct v4l2_async_notifier *notifier) { struct v4l2_subdev *sd, *tmp; list_for_each_entry_safe(sd, tmp, ¬ifier->done, async_list) { + struct v4l2_async_notifier *subdev_notifier = + v4l2_async_find_subdev_notifier(sd); + + if (subdev_notifier) + v4l2_async_notifier_unbind_all_subdevs(subdev_notifier); + v4l2_async_notifier_call_unbind(notifier, sd, sd->asd); v4l2_async_cleanup(sd); list_move(&sd->async_list, &subdev_list); } + + notifier->parent = NULL; } static int __v4l2_async_notifier_register(struct v4l2_async_notifier *notifier) @@ -208,15 +320,6 @@ static int __v4l2_async_notifier_register(struct v4l2_async_notifier *notifier) INIT_LIST_HEAD(¬ifier->waiting); INIT_LIST_HEAD(¬ifier->done); - if (!notifier->num_subdevs) { - int ret; - - ret = v4l2_async_notifier_call_complete(notifier); - notifier->v4l2_dev = NULL; - - return ret; - } - for (i = 0; i < notifier->num_subdevs; i++) { asd = notifier->subdevs[i]; @@ -238,16 +341,12 @@ static int __v4l2_async_notifier_register(struct v4l2_async_notifier *notifier) mutex_lock(&list_lock); ret = v4l2_async_notifier_try_all_subdevs(notifier); - if (ret) { - mutex_unlock(&list_lock); - return ret; - } + if (ret) + goto err_unbind; - if (list_empty(¬ifier->waiting)) { - ret = v4l2_async_notifier_call_complete(notifier); - if (ret) - goto err_complete; - } + ret = v4l2_async_notifier_try_complete(notifier); + if (ret) + goto err_unbind; /* Keep also completed notifiers on the list */ list_add(¬ifier->list, ¬ifier_list); @@ -256,7 +355,10 @@ static int __v4l2_async_notifier_register(struct v4l2_async_notifier *notifier) return 0; -err_complete: +err_unbind: + /* + * On failure, unbind all sub-devices registered through this notifier. + */ v4l2_async_notifier_unbind_all_subdevs(notifier); mutex_unlock(&list_lock); @@ -269,7 +371,7 @@ int v4l2_async_notifier_register(struct v4l2_device *v4l2_dev, { int ret; - if (WARN_ON(!v4l2_dev)) + if (WARN_ON(!v4l2_dev || notifier->sd)) return -EINVAL; notifier->v4l2_dev = v4l2_dev; @@ -282,20 +384,39 @@ int v4l2_async_notifier_register(struct v4l2_device *v4l2_dev, } EXPORT_SYMBOL(v4l2_async_notifier_register); +int v4l2_async_subdev_notifier_register(struct v4l2_subdev *sd, + struct v4l2_async_notifier *notifier) +{ + int ret; + + if (WARN_ON(!sd || notifier->v4l2_dev)) + return -EINVAL; + + notifier->sd = sd; + + ret = __v4l2_async_notifier_register(notifier); + if (ret) + notifier->sd = NULL; + + return ret; +} +EXPORT_SYMBOL(v4l2_async_subdev_notifier_register); + void v4l2_async_notifier_unregister(struct v4l2_async_notifier *notifier) { - if (!notifier->v4l2_dev) + if (!notifier->v4l2_dev && !notifier->sd) return; mutex_lock(&list_lock); - list_del(¬ifier->list); - v4l2_async_notifier_unbind_all_subdevs(notifier); - mutex_unlock(&list_lock); - + notifier->sd = NULL; notifier->v4l2_dev = NULL; + + list_del(¬ifier->list); + + mutex_unlock(&list_lock); } EXPORT_SYMBOL(v4l2_async_notifier_unregister); @@ -331,6 +452,7 @@ EXPORT_SYMBOL_GPL(v4l2_async_notifier_cleanup); int v4l2_async_register_subdev(struct v4l2_subdev *sd) { + struct v4l2_async_notifier *subdev_notifier; struct v4l2_async_notifier *notifier; int ret; @@ -347,24 +469,26 @@ int v4l2_async_register_subdev(struct v4l2_subdev *sd) INIT_LIST_HEAD(&sd->async_list); list_for_each_entry(notifier, ¬ifier_list, list) { - struct v4l2_async_subdev *asd = v4l2_async_find_match(notifier, - sd); + struct v4l2_device *v4l2_dev = + v4l2_async_notifier_find_v4l2_dev(notifier); + struct v4l2_async_subdev *asd; int ret; + if (!v4l2_dev) + continue; + + asd = v4l2_async_find_match(notifier, sd); if (!asd) continue; ret = v4l2_async_match_notify(notifier, notifier->v4l2_dev, sd, asd); if (ret) - goto err_unlock; - - if (!list_empty(¬ifier->waiting)) - goto out_unlock; + goto err_unbind; - ret = v4l2_async_notifier_call_complete(notifier); + ret = v4l2_async_notifier_try_complete(notifier); if (ret) - goto err_cleanup; + goto err_unbind; goto out_unlock; } @@ -377,11 +501,19 @@ out_unlock: return 0; -err_cleanup: - v4l2_async_notifier_call_unbind(notifier, sd, sd->asd); +err_unbind: + /* + * Complete failed. Unbind the sub-devices bound through registering + * this async sub-device. + */ + subdev_notifier = v4l2_async_find_subdev_notifier(sd); + if (subdev_notifier) + v4l2_async_notifier_unbind_all_subdevs(subdev_notifier); + + if (sd->asd) + v4l2_async_notifier_call_unbind(notifier, sd, sd->asd); v4l2_async_cleanup(sd); -err_unlock: mutex_unlock(&list_lock); return ret; |