forked from mirrors/linux
		
	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
					
				
					 2 changed files with 189 additions and 42 deletions
				
			
		|  | @ -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 @@ int v4l2_async_register_subdev(struct v4l2_subdev *sd) | |||
| 
 | ||||
| 	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 a new issue
	
	 Sakari Ailus
						Sakari Ailus