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>
This commit is contained in:
parent
a3620cb48d
commit
2cab00bb07
|
@ -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;
|
||||
|
||||
list_for_each_entry_safe(sd, tmp, &subdev_list, async_list) {
|
||||
if (!v4l2_dev)
|
||||
return 0;
|
||||
|
||||
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;
|
||||
goto err_unbind;
|
||||
|
||||
if (!list_empty(¬ifier->waiting))
|
||||
goto out_unlock;
|
||||
|
||||
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;
|
||||
|
|
|
@ -82,7 +82,8 @@ struct v4l2_async_subdev {
|
|||
/**
|
||||
* struct v4l2_async_notifier_operations - Asynchronous V4L2 notifier operations
|
||||
* @bound: a subdevice driver has successfully probed one of the subdevices
|
||||
* @complete: all subdevices have been probed successfully
|
||||
* @complete: All subdevices have been probed successfully. The complete
|
||||
* callback is only executed for the root notifier.
|
||||
* @unbind: a subdevice is leaving
|
||||
*/
|
||||
struct v4l2_async_notifier_operations {
|
||||
|
@ -102,7 +103,9 @@ struct v4l2_async_notifier_operations {
|
|||
* @num_subdevs: number of subdevices used in the subdevs array
|
||||
* @max_subdevs: number of subdevices allocated in the subdevs array
|
||||
* @subdevs: array of pointers to subdevice descriptors
|
||||
* @v4l2_dev: pointer to struct v4l2_device
|
||||
* @v4l2_dev: v4l2_device of the root notifier, NULL otherwise
|
||||
* @sd: sub-device that registered the notifier, NULL otherwise
|
||||
* @parent: parent notifier
|
||||
* @waiting: list of struct v4l2_async_subdev, waiting for their drivers
|
||||
* @done: list of struct v4l2_subdev, already probed
|
||||
* @list: member in a global list of notifiers
|
||||
|
@ -113,6 +116,8 @@ struct v4l2_async_notifier {
|
|||
unsigned int max_subdevs;
|
||||
struct v4l2_async_subdev **subdevs;
|
||||
struct v4l2_device *v4l2_dev;
|
||||
struct v4l2_subdev *sd;
|
||||
struct v4l2_async_notifier *parent;
|
||||
struct list_head waiting;
|
||||
struct list_head done;
|
||||
struct list_head list;
|
||||
|
@ -127,6 +132,16 @@ struct v4l2_async_notifier {
|
|||
int v4l2_async_notifier_register(struct v4l2_device *v4l2_dev,
|
||||
struct v4l2_async_notifier *notifier);
|
||||
|
||||
/**
|
||||
* v4l2_async_subdev_notifier_register - registers a subdevice asynchronous
|
||||
* notifier for a sub-device
|
||||
*
|
||||
* @sd: pointer to &struct v4l2_subdev
|
||||
* @notifier: pointer to &struct v4l2_async_notifier
|
||||
*/
|
||||
int v4l2_async_subdev_notifier_register(struct v4l2_subdev *sd,
|
||||
struct v4l2_async_notifier *notifier);
|
||||
|
||||
/**
|
||||
* v4l2_async_notifier_unregister - unregisters a subdevice asynchronous notifier
|
||||
*
|
||||
|
|
Loading…
Reference in New Issue