From 2cab00bb076b9f0e8442e3d72425843d2b441143 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Sun, 24 Sep 2017 20:54:31 -0400 Subject: media: v4l: async: Allow binding notifiers to sub-devices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Acked-by: Hans Verkuil Acked-by: Niklas Söderlund Reviewed-by: Sebastian Reichel Signed-off-by: Mauro Carvalho Chehab --- drivers/media/v4l2-core/v4l2-async.c | 212 ++++++++++++++++++++++++++++------- 1 file changed, 172 insertions(+), 40 deletions(-) (limited to 'drivers/media/v4l2-core') 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; -- cgit v1.2.3