@@ -262,6 +262,26 @@ is called. After all subdevices have been located the .complete() callback is
called. When a subdevice is removed from the system the .unbind() method is
called. All three callbacks are optional.
+Subdevice drivers might in turn register subnotifier objects with an
+array of other subdevice descriptors that the subdevice needs for its
+own operation. Subnotifiers are an extension of the bridge drivers
+notifier to allow for a incremental registering and matching of
+subdevices. This is useful when a driver only has information about
+which subdevice is closest to itself and would require knowledge from the
+driver of that subdevice to know which other subdevice(s) lie beyond.
+By registering subnotifiers drivers can incrementally move the subdevice
+matching down the chain of drivers. This is performed using the
+:c:func:`v4l2_async_subnotifier_register` call. To unregister the
+subnotifier the driver has to call
+:c:func:`v4l2_async_subnotifier_unregister`. These functions and their
+arguments behave almost the same as the bridge driver notifiers
+described above and are treated equally by the V4L2 core when matching
+asynchronously registered subdevices. The differences are that the
+subnotifier functions act on :c:type:`v4l2_subdev` instead of
+:c:type:`v4l2_device` and that they should be called from the subdevice's
+``.registered()`` and ``.unregistered()``
+:c:type:`v4l2_subdev_internal_ops` callbacks instead of at probe time.
+
V4L2 sub-device userspace API
-----------------------------
@@ -141,8 +141,9 @@ static void v4l2_async_cleanup(struct v4l2_subdev *sd)
sd->dev = NULL;
}
-int v4l2_async_notifier_register(struct v4l2_device *v4l2_dev,
- struct v4l2_async_notifier *notifier)
+static int v4l2_async_do_notifier_register(struct v4l2_device *v4l2_dev,
+ struct v4l2_async_notifier *notifier,
+ bool subnotifier)
{
struct v4l2_subdev *sd, *tmp;
struct v4l2_async_subdev *asd;
@@ -174,8 +175,17 @@ int v4l2_async_notifier_register(struct v4l2_device *v4l2_dev,
list_add_tail(&asd->list, ¬ifier->waiting);
}
- mutex_lock(&list_lock);
+ if (subnotifier)
+ lockdep_assert_held(&list_lock);
+ else
+ mutex_lock(&list_lock);
+ /*
+ * This function can be called recursively so the list
+ * might be modified in a recursive call. Start from the
+ * top of the list each iteration.
+ */
+again:
list_for_each_entry_safe(sd, tmp, &subdev_list, async_list) {
int ret;
@@ -185,21 +195,39 @@ int v4l2_async_notifier_register(struct v4l2_device *v4l2_dev,
ret = v4l2_async_test_notify(notifier, sd, asd);
if (ret < 0) {
- mutex_unlock(&list_lock);
+ if (!subnotifier)
+ mutex_unlock(&list_lock);
return ret;
}
+ goto again;
}
/* Keep also completed notifiers on the list */
list_add(¬ifier->list, ¬ifier_list);
- mutex_unlock(&list_lock);
+ if (!subnotifier)
+ mutex_unlock(&list_lock);
return 0;
}
+
+int v4l2_async_subnotifier_register(struct v4l2_subdev *sd,
+ struct v4l2_async_notifier *notifier)
+{
+ return v4l2_async_do_notifier_register(sd->v4l2_dev, notifier, true);
+}
+EXPORT_SYMBOL(v4l2_async_subnotifier_register);
+
+int v4l2_async_notifier_register(struct v4l2_device *v4l2_dev,
+ struct v4l2_async_notifier *notifier)
+{
+ return v4l2_async_do_notifier_register(v4l2_dev, notifier, false);
+}
EXPORT_SYMBOL(v4l2_async_notifier_register);
-void v4l2_async_notifier_unregister(struct v4l2_async_notifier *notifier)
+static void
+v4l2_async_do_notifier_unregister(struct v4l2_async_notifier *notifier,
+ bool subnotifier)
{
struct v4l2_subdev *sd, *tmp;
unsigned int notif_n_subdev = notifier->num_subdevs;
@@ -216,7 +244,10 @@ void v4l2_async_notifier_unregister(struct v4l2_async_notifier *notifier)
"Failed to allocate device cache!\n");
}
- mutex_lock(&list_lock);
+ if (subnotifier)
+ lockdep_assert_held(&list_lock);
+ else
+ mutex_lock(&list_lock);
list_del(¬ifier->list);
@@ -243,15 +274,20 @@ void v4l2_async_notifier_unregister(struct v4l2_async_notifier *notifier)
put_device(d);
}
- mutex_unlock(&list_lock);
+ if (!subnotifier)
+ mutex_unlock(&list_lock);
/*
* Call device_attach() to reprobe devices
*
* NOTE: If dev allocation fails, i is 0, and the whole loop won't be
* executed.
+ * TODO: If we are unregistering a subdevice notifier we can't reprobe
+ * since the lock_list is held by the master device and attaching that
+ * device would call v4l2_async_register_subdev() and end in a deadlock
+ * on list_lock.
*/
- while (i--) {
+ while (i-- && !subnotifier) {
struct device *d = dev[i];
if (d && device_attach(d) < 0) {
@@ -275,6 +311,17 @@ void v4l2_async_notifier_unregister(struct v4l2_async_notifier *notifier)
* upon notifier registration.
*/
}
+
+void v4l2_async_subnotifier_unregister(struct v4l2_async_notifier *notifier)
+{
+ v4l2_async_do_notifier_unregister(notifier, true);
+}
+EXPORT_SYMBOL(v4l2_async_subnotifier_unregister);
+
+void v4l2_async_notifier_unregister(struct v4l2_async_notifier *notifier)
+{
+ v4l2_async_do_notifier_unregister(notifier, false);
+}
EXPORT_SYMBOL(v4l2_async_notifier_unregister);
int v4l2_async_register_subdev(struct v4l2_subdev *sd)
@@ -105,6 +105,18 @@ struct v4l2_async_notifier {
};
/**
+ * v4l2_async_notifier_register - registers a subdevice asynchronous subnotifier
+ *
+ * @sd: pointer to &struct v4l2_subdev
+ * @notifier: pointer to &struct v4l2_async_notifier
+ *
+ * This function assumes the async list_lock is already locked, allowing it to
+ * be used from the struct v4l2_subdev_internal_ops registered() callback.
+ */
+int v4l2_async_subnotifier_register(struct v4l2_subdev *sd,
+ struct v4l2_async_notifier *notifier);
+
+/**
* v4l2_async_notifier_register - registers a subdevice asynchronous notifier
*
* @v4l2_dev: pointer to &struct v4l2_device
@@ -114,6 +126,16 @@ int v4l2_async_notifier_register(struct v4l2_device *v4l2_dev,
struct v4l2_async_notifier *notifier);
/**
+ * v4l2_async_subnotifier_unregister - unregisters a asynchronous subnotifier
+ *
+ * @notifier: pointer to &struct v4l2_async_notifier
+ *
+ * This function assumes the async list_lock is already locked, allowing it to
+ * be used from the struct v4l2_subdev_internal_ops unregistered() callback.
+ */
+void v4l2_async_subnotifier_unregister(struct v4l2_async_notifier *notifier);
+
+/**
* v4l2_async_notifier_unregister - unregisters a subdevice asynchronous notifier
*
* @notifier: pointer to &struct v4l2_async_notifier