|
@@ -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;
|