Signed-off-by: Steve Longerbeam <steve_longerbeam@xxxxxxxxxx>
---
Changes since v2:
- add a NULL asd pointer check to v4l2_async_notifier_asd_valid().
Changes since v1:
- none
---
drivers/media/v4l2-core/v4l2-async.c | 206 +++++++++++++++++++++++++++--------
include/media/v4l2-async.h | 22 ++++
2 files changed, 184 insertions(+), 44 deletions(-)
diff --git a/drivers/media/v4l2-core/v4l2-async.c b/drivers/media/v4l2-core/v4l2-async.c
index b59bbac..7b7f7e2 100644
--- a/drivers/media/v4l2-core/v4l2-async.c
+++ b/drivers/media/v4l2-core/v4l2-async.c
@@ -366,16 +366,26 @@ static bool v4l2_async_notifier_has_async_subdev(
struct v4l2_async_notifier *notifier, struct v4l2_async_subdev *asd,
unsigned int this_index)
{
+ struct v4l2_async_subdev *asd_y;
unsigned int j;
lockdep_assert_held(&list_lock);
/* Check that an asd is not being added more than once. */
- for (j = 0; j < this_index; j++) {
- struct v4l2_async_subdev *asd_y = notifier->subdevs[j];
-
- if (asd_equal(asd, asd_y))
- return true;
+ if (notifier->subdevs) {
+ for (j = 0; j < this_index; j++) {
+ asd_y = notifier->subdevs[j];
+ if (asd_equal(asd, asd_y))
+ return true;
+ }
+ } else {
+ j = 0;
+ list_for_each_entry(asd_y, ¬ifier->asd_list, asd_list) {
+ if (j++ >= this_index)
+ break;
+ if (asd_equal(asd, asd_y))
+ return true;
+ }
}
/* Check that an asd does not exist in other notifiers. */
@@ -386,10 +396,46 @@ static bool v4l2_async_notifier_has_async_subdev(
return false;
}
-static int __v4l2_async_notifier_register(struct v4l2_async_notifier *notifier)
+static int v4l2_async_notifier_asd_valid(struct v4l2_async_notifier *notifier,
+ struct v4l2_async_subdev *asd,
+ unsigned int this_index)
{
struct device *dev =
notifier->v4l2_dev ? notifier->v4l2_dev->dev : NULL;
+
+ if (!asd)
+ return -EINVAL;
+
+ switch (asd->match_type) {
+ case V4L2_ASYNC_MATCH_CUSTOM:
+ case V4L2_ASYNC_MATCH_DEVNAME:
+ case V4L2_ASYNC_MATCH_I2C:
+ case V4L2_ASYNC_MATCH_FWNODE:
+ if (v4l2_async_notifier_has_async_subdev(notifier, asd,
+ this_index))
+ return -EEXIST;
+ break;
+ default:
+ dev_err(dev, "Invalid match type %u on %p\n",
+ asd->match_type, asd);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void __v4l2_async_notifier_init(struct v4l2_async_notifier *notifier)
+{
+ lockdep_assert_held(&list_lock);
+
+ INIT_LIST_HEAD(¬ifier->asd_list);
+ INIT_LIST_HEAD(¬ifier->waiting);
+ INIT_LIST_HEAD(¬ifier->done);
+ notifier->lists_initialized = true;
+}
+
+static int __v4l2_async_notifier_register(struct v4l2_async_notifier *notifier)
+{
struct v4l2_async_subdev *asd;
int ret;
int i;
@@ -397,34 +443,40 @@ static int __v4l2_async_notifier_register(struct v4l2_async_notifier *notifier)
if (notifier->num_subdevs > V4L2_MAX_SUBDEVS)
return -EINVAL;
- INIT_LIST_HEAD(¬ifier->waiting);
- INIT_LIST_HEAD(¬ifier->done);
-
mutex_lock(&list_lock);
- for (i = 0; i < notifier->num_subdevs; i++) {
- asd = notifier->subdevs[i];
+ if (!notifier->lists_initialized)
+ __v4l2_async_notifier_init(notifier);
- switch (asd->match_type) {
- case V4L2_ASYNC_MATCH_CUSTOM:
- case V4L2_ASYNC_MATCH_DEVNAME:
- case V4L2_ASYNC_MATCH_I2C:
- case V4L2_ASYNC_MATCH_FWNODE:
- if (v4l2_async_notifier_has_async_subdev(
- notifier, asd, i)) {
- dev_err(dev,
- "asd has already been registered or in notifier's subdev list\n");
- ret = -EEXIST;
- goto err_unlock;
- }
- break;
- default:
- dev_err(dev, "Invalid match type %u on %p\n",
- asd->match_type, asd);
+ if (!list_empty(¬ifier->asd_list)) {
+ /*
+ * Caller must have either used v4l2_async_notifier_add_subdev
+ * to add asd's to notifier->asd_list, or provided the
+ * notifier->subdevs array, but not both.
+ */
+ if (WARN_ON(notifier->subdevs)) {
ret = -EINVAL;
goto err_unlock;
}
- list_add_tail(&asd->list, ¬ifier->waiting);
+
+ i = 0;
+ list_for_each_entry(asd, ¬ifier->asd_list, asd_list) {
+ ret = v4l2_async_notifier_asd_valid(notifier, asd, i++);
+ if (ret)
+ goto err_unlock;
+
+ list_add_tail(&asd->list, ¬ifier->waiting);
+ }
+ } else if (notifier->subdevs) {
+ for (i = 0; i < notifier->num_subdevs; i++) {
+ asd = notifier->subdevs[i];
+
+ ret = v4l2_async_notifier_asd_valid(notifier, asd, i);
+ if (ret)
+ goto err_unlock;
+
+ list_add_tail(&asd->list, ¬ifier->waiting);
+ }
}
ret = v4l2_async_notifier_try_all_subdevs(notifier);
@@ -514,36 +566,102 @@ void v4l2_async_notifier_unregister(struct v4l2_async_notifier *notifier)
}
EXPORT_SYMBOL(v4l2_async_notifier_unregister);
-void v4l2_async_notifier_cleanup(struct v4l2_async_notifier *notifier)
+static void __v4l2_async_notifier_cleanup(struct v4l2_async_notifier *notifier)
{
+ struct v4l2_async_subdev *asd, *tmp;
unsigned int i;
- if (!notifier || !notifier->max_subdevs)
+ if (!notifier)
return;
- for (i = 0; i < notifier->num_subdevs; i++) {
- struct v4l2_async_subdev *asd = notifier->subdevs[i];
+ if (notifier->subdevs) {
+ if (!notifier->max_subdevs)
+ return;
- switch (asd->match_type) {
- case V4L2_ASYNC_MATCH_FWNODE:
- fwnode_handle_put(asd->match.fwnode);
- break;
- default:
- WARN_ON_ONCE(true);
- break;
+ for (i = 0; i < notifier->num_subdevs; i++) {
+ asd = notifier->subdevs[i];
+
+ switch (asd->match_type) {
+ case V4L2_ASYNC_MATCH_FWNODE:
+ fwnode_handle_put(asd->match.fwnode);
+ break;
+ default:
+ break;
+ }
+
+ kfree(asd);
}
- kfree(asd);
+ notifier->max_subdevs = 0;
+ kvfree(notifier->subdevs);
+ notifier->subdevs = NULL;
+ } else if (notifier->lists_initialized) {
+ list_for_each_entry_safe(asd, tmp,
+ ¬ifier->asd_list, asd_list) {
+ switch (asd->match_type) {
+ case V4L2_ASYNC_MATCH_FWNODE:
+ fwnode_handle_put(asd->match.fwnode);
+ break;
+ default:
+ break;
+ }
+
+ list_del(&asd->asd_list);
+ kfree(asd);
+ }
}
- notifier->max_subdevs = 0;
notifier->num_subdevs = 0;
+}
- kvfree(notifier->subdevs);
- notifier->subdevs = NULL;
+void v4l2_async_notifier_cleanup(struct v4l2_async_notifier *notifier)
+{
+ mutex_lock(&list_lock);
+
+ __v4l2_async_notifier_cleanup(notifier);
+
+ mutex_unlock(&list_lock);
}
EXPORT_SYMBOL_GPL(v4l2_async_notifier_cleanup);
+int v4l2_async_notifier_add_subdev(struct v4l2_async_notifier *notifier,
+ struct v4l2_async_subdev *asd)
+{
+ int ret = 0;
+
+ mutex_lock(&list_lock);
+
+ if (notifier->num_subdevs >= V4L2_MAX_SUBDEVS) {
+ ret = -EINVAL;
+ goto unlock;
+ }
+
+ if (!notifier->lists_initialized)
+ __v4l2_async_notifier_init(notifier);
+
+ /*
+ * If caller uses this function, it cannot also allocate and
+ * place asd's in the notifier->subdevs array.
+ */
+ if (WARN_ON(notifier->subdevs)) {
+ ret = -EINVAL;
+ goto unlock;
+ }
+
+ ret = v4l2_async_notifier_asd_valid(notifier, asd,
+ notifier->num_subdevs);
+ if (ret)
+ goto unlock;
+
+ list_add_tail(&asd->asd_list, ¬ifier->asd_list);
+ notifier->num_subdevs++;
+
+unlock:
+ mutex_unlock(&list_lock);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(v4l2_async_notifier_add_subdev);
+
int v4l2_async_register_subdev(struct v4l2_subdev *sd)
{
struct v4l2_async_notifier *subdev_notifier;
@@ -617,7 +735,7 @@ void v4l2_async_unregister_subdev(struct v4l2_subdev *sd)
mutex_lock(&list_lock);
__v4l2_async_notifier_unregister(sd->subdev_notifier);
- v4l2_async_notifier_cleanup(sd->subdev_notifier);
+ __v4l2_async_notifier_cleanup(sd->subdev_notifier);
kfree(sd->subdev_notifier);
sd->subdev_notifier = NULL;
diff --git a/include/media/v4l2-async.h b/include/media/v4l2-async.h
index 1592d32..fa05905 100644
--- a/include/media/v4l2-async.h
+++ b/include/media/v4l2-async.h
@@ -73,6 +73,8 @@ enum v4l2_async_match_type {
* @match.custom.priv:
* Driver-specific private struct with match parameters
* to be used if %V4L2_ASYNC_MATCH_CUSTOM.
+ * @asd_list: used to add struct v4l2_async_subdev objects to the
+ * master notifier->asd_list
* @list: used to link struct v4l2_async_subdev objects, waiting to be
* probed, to a notifier->waiting list
*
@@ -98,6 +100,7 @@ struct v4l2_async_subdev {
/* v4l2-async core private: not to be used by drivers */
struct list_head list;
+ struct list_head asd_list;
};
/**
@@ -127,9 +130,11 @@ struct v4l2_async_notifier_operations {
* @v4l2_dev: v4l2_device of the root notifier, NULL otherwise
* @sd: sub-device that registered the notifier, NULL otherwise
* @parent: parent notifier
+ * @asd_list: master list of struct v4l2_async_subdev, replaces @subdevs
* @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
+ * @lists_initialized: list_head's have been initialized
*/
struct v4l2_async_notifier {
const struct v4l2_async_notifier_operations *ops;
@@ -139,12 +144,29 @@ struct v4l2_async_notifier {
struct v4l2_device *v4l2_dev;
struct v4l2_subdev *sd;
struct v4l2_async_notifier *parent;
+ struct list_head asd_list;
struct list_head waiting;
struct list_head done;
struct list_head list;
+ bool lists_initialized;
};
/**
+ * v4l2_async_notifier_add_subdev - Add an async subdev to the
+ * notifier's master asd_list.
+ *
+ * @notifier: pointer to &struct v4l2_async_notifier
+ * @asd: pointer to &struct v4l2_async_subdev
+ *
+ * This can be used before registering a notifier to add an
+ * asd to the notifiers master asd_list. If the caller uses
+ * this method to compose an asd list, it must never allocate
+ * or place asd's in the @subdevs array.
+ */
+int v4l2_async_notifier_add_subdev(struct v4l2_async_notifier *notifier,
+ struct v4l2_async_subdev *asd);
+
+/**
* v4l2_async_notifier_register - registers a subdevice asynchronous notifier
*
* @v4l2_dev: pointer to &struct v4l2_device
--
2.7.4