[RFC PATCH 5/9] dt: deps: register drivers based on the initialization order based on DT

Grant Likely grant.likely at linaro.org
Wed May 14 07:13:00 PDT 2014


On Mon, 12 May 2014 18:47:56 +0200, Alexander Holler <holler at ahsoftware.de> wrote:
> The init system currently calls unknown functions with almost unknown
> functionality in an almost random order.

Correct, we've got a module system. Some would say that is a strength!
:-) That said, I don't object to optimizing to the optimal order when
possible.

> Fixing this is on a short-term basis is a bit tricky.
> 
> In order to register drivers with a deterministic order, a list of all
> available in-kernel drivers is needed. Unfortunately such a list doesn't
> exist, just a list of initcalls does exist.
> 
> The trick now is to first call special annotated initcalls (I call those
> "well done") for platform drivers, but not actualy starting those drivers
> by calling their probe function, but just collectiong their meta datas
> (struct platform_driver). After all those informations were collected,
> available the drivers will be started according to the previously
> determined order.

Why does the initcall level matter? Why not just let the initcalls
happen, capture the calls that register a driver, and then use that list
later?

> 
> The annotation of such platform drivers is necessary because it must be
> made sure that those drivers don't care if the probe is actually called in
> their initcall or later.
> 
> That means that all platform drivers which already do use
> 
> 	module_platform_driver() or
> 	module_platform_driver_probe()
> 
> don't need any modification because their initcall is known and already well
> done. But all platform drivers which do use
> 
> 	module_init() or
> 	*_initcall()
> 
> have to be reviewed if they are "well done". If they are, they need a change
> like
> 
> 	-module_init(foo_init);
> 	+well_done_platform_module_init(foo_init);
> 
> or
> 
> 	-subsys_initcall(foo_init);
> 	+well_done_platform_initcall(subsys, foo_init);
> 
> to become included in the deterministic order in which platform drivers
> will be initialized.
> 
> All other platform drivers will still be initialized in random order before
> platform drivers included in the deterministic order will be initialized.
> "Well done" drivers which don't appear in the order (because they don't appear
> in the DT) will be initialized after those which do appear in the order.
> 
> If CONFIG_OF_DEPENDENCIES is disabled, nothing is changed at all.
> 
> The long range target to fix the problem should be to include a list (array)
> of struct platform_driver in the kernel for all in-kernel platform drivers,
> instead of just initcalls. This will be easy if all platform drivers have
> become "well done".

How will that list be constructed? How will it account for multiple
platforms, each requiring a different init order?

> 
> Unfortunately there are some drivers which will need quiet some changes
> to become "well done". As an example for such an initcall look e.g. at
> drivers/tty/serial/8250/8250_core.c.
> 
> Signed-off-by: Alexander Holler <holler at ahsoftware.de>
> ---
>  drivers/base/platform.c           | 13 +++++++
>  drivers/of/of_dependencies.c      | 79 +++++++++++++++++++++++++++++++++++++++
>  include/asm-generic/vmlinux.lds.h |  1 +
>  include/linux/init.h              | 19 ++++++++++
>  include/linux/of_dependencies.h   |  5 +++
>  include/linux/platform_device.h   | 16 ++++++--
>  init/main.c                       | 17 ++++++++-
>  7 files changed, 145 insertions(+), 5 deletions(-)
> 
> diff --git a/drivers/base/platform.c b/drivers/base/platform.c
> index bc78848..b9c9b33 100644
> --- a/drivers/base/platform.c
> +++ b/drivers/base/platform.c
> @@ -13,6 +13,7 @@
>  #include <linux/string.h>
>  #include <linux/platform_device.h>
>  #include <linux/of_device.h>
> +#include <linux/of_dependencies.h>
>  #include <linux/module.h>
>  #include <linux/init.h>
>  #include <linux/dma-mapping.h>
> @@ -541,6 +542,12 @@ int __platform_driver_register(struct platform_driver *drv,
>  	if (drv->shutdown)
>  		drv->driver.shutdown = platform_drv_shutdown;
>  
> +#ifdef CONFIG_OF_DEPENDENCIES
> +	if (of_init_is_recording())
> +		/* Just record the driver */
> +		return of_init_register_platform_driver(drv);
> +	else
> +#endif
>  	return driver_register(&drv->driver);
>  }
>  EXPORT_SYMBOL_GPL(__platform_driver_register);
> @@ -590,8 +597,14 @@ int __init_or_module platform_driver_probe(struct platform_driver *drv,
>  
>  	/* temporary section violation during probe() */
>  	drv->probe = probe;
> +
>  	retval = code = platform_driver_register(drv);
>  
> +#ifdef CONFIG_OF_DEPENDENCIES
> +	if (of_init_is_recording())
> +		/* Just record the driver */
> +		return retval;
> +#endif
>  	/*
>  	 * Fixup that section violation, being paranoid about code scanning
>  	 * the list of drivers in order to probe new devices.  Check to see
> diff --git a/drivers/of/of_dependencies.c b/drivers/of/of_dependencies.c
> index 7905172..4af62d5 100644
> --- a/drivers/of/of_dependencies.c
> +++ b/drivers/of/of_dependencies.c
> @@ -46,9 +46,12 @@ struct init_order {
>  	/* Used to keep track of parent devices in regard to the DT */
>  	uint32_t parent_by_phandle[MAX_DT_NODES+1];
>  	struct device *device_by_phandle[MAX_DT_NODES+1];
> +	struct platform_driver *platform_drivers[MAX_DT_NODES+1];
> +	unsigned count_drivers;
>  };
>  static struct init_order order __initdata;
>  
> +static bool is_recording;
>  
>  /* Copied from drivers/of/base.c (because it's lockless). */
>  static struct property * __init __of_find_property(const struct device_node *np,
> @@ -401,3 +404,79 @@ void __init of_init_free_order(void)
>  	order.count = 0;
>  	/* remove_new_phandles(); */
>  }
> +
> +void __init of_init_set_recording(bool recording)
> +{
> +	is_recording = recording;
> +}
> +
> +bool of_init_is_recording(void)
> +{
> +	return is_recording;
> +}
> +
> +int of_init_register_platform_driver(struct platform_driver *drv)
> +{
> +	BUG_ON(!is_recording);
> +	order.platform_drivers[order.count_drivers++] = drv;
> +	return 0;
> +}
> +
> +void __init of_init_register_drivers(void)
> +{
> +	unsigned i, j;
> +	int rc __maybe_unused;
> +
> +	BUG_ON(is_recording);
> +	/*
> +	 * Because we already have a list of devices and drivers together
> +	 * with their compatible strings, the below code could be speed up
> +	 * by replacing the functions which are walking through lists with
> +	 * something which uses trees or hashes to compare/search strings.
> +	 * These are of_driver_match_device() and driver_find() (the latter
> +	 * is called again in driver_register().
> +	 */
> +	for (i = 0; i < order.count; ++i) {
> +		struct device_node *node = order.order[i];
> +		struct device *dev = order.device_by_phandle[node->phandle];
> +
> +		for (j = 0; j < order.count_drivers; ++j) {
> +			struct platform_driver *drv = order.platform_drivers[j];
> +
> +			if (of_driver_match_device(dev, &drv->driver)) {
> +				if (!driver_find(drv->driver.name,
> +				    drv->driver.bus))
> +					platform_driver_register(drv);
> +				if (dev->parent)
> +					device_lock(dev->parent);
> +				rc = device_attach(dev);
> +				if (dev->parent)
> +					device_unlock(dev->parent);
> +				break;
> +			}
> +		}
> +		if (j >= order.count_drivers) {
> +			/*
> +			 * No driver in the initialization order matched,
> +			 * try to attach the device, maybe a driver already
> +			 * exists (e.g. loaded pre-smp).
> +			 */
> +			if (dev->parent)
> +				device_lock(dev->parent);
> +			rc = device_attach(dev);
> +			if (dev->parent)
> +				device_unlock(dev->parent);
> +		}
> +	}
> +	/*
> +	 * Now just register all drivers, including those not used through
> +	 * the initialization order (well-done drivers which aren't listed
> +	 * in the DT or blacklisted through of_init_create_devices()).
> +	 */
> +	for (j = 0; j < order.count_drivers; ++j) {
> +		struct platform_driver *drv = order.platform_drivers[j];
> +
> +		if (!driver_find(drv->driver.name, drv->driver.bus))
> +			platform_driver_register(drv);
> +	}
> +}
> diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h
> index bc2121f..cedb3b0 100644
> --- a/include/asm-generic/vmlinux.lds.h
> +++ b/include/asm-generic/vmlinux.lds.h
> @@ -633,6 +633,7 @@
>  		INIT_CALLS_LEVEL(rootfs)				\
>  		INIT_CALLS_LEVEL(6)					\
>  		INIT_CALLS_LEVEL(7)					\
> +		INIT_CALLS_LEVEL(8)					\
>  		VMLINUX_SYMBOL(__initcall_end) = .;
>  
>  #define CON_INITCALL							\
> diff --git a/include/linux/init.h b/include/linux/init.h
> index e168880..acb7dfa 100644
> --- a/include/linux/init.h
> +++ b/include/linux/init.h
> @@ -209,6 +209,23 @@ extern bool initcall_debug;
>  #define late_initcall(fn)		__define_initcall(fn, 7)
>  #define late_initcall_sync(fn)		__define_initcall(fn, 7s)
>  
> +/*
> + * A well_done_platform_module_init or well_done_platform_initcall
> + * only calls platform_driver_register() or platform_driver_probe()
> + * and ignores the return code. This is necessary because the
> + * actual calls to platform_driver_register() or platform_driver_probe()
> + * will be delayed when CONFIG_OF_DEPENDENCIES is enabled. This is done
> + * to sort those calls based on the dependencies in the DT (matched to the
> + * platform driver data).
> + */
> +#ifdef CONFIG_OF_DEPENDENCIES
> +#define well_done_platform_module_init(fn)	__define_initcall(fn, 8)
> +#define well_done_platform_initcall(leve, fn)	__define_initcall(fn, 8)
> +#else
> +#define well_done_platform_module_init(fn)	module_init(fn)
> +#define well_done_platform_initcall(level, fn)	level ## _initcall(fn)
> +#endif
> +
>  #define __initcall(fn) device_initcall(fn)
>  
>  #define __exitcall(fn) \
> @@ -289,6 +306,8 @@ void __init parse_early_options(char *cmdline);
>  #define rootfs_initcall(fn)		module_init(fn)
>  #define device_initcall(fn)		module_init(fn)
>  #define late_initcall(fn)		module_init(fn)
> +#define well_done_platform_initcall(fn)	module_init(fn)
> +#define well_done_platform_module_init(fn)	module_init(fn)
>  
>  #define console_initcall(fn)		module_init(fn)
>  #define security_initcall(fn)		module_init(fn)
> diff --git a/include/linux/of_dependencies.h b/include/linux/of_dependencies.h
> index e046ce2..8869162 100644
> --- a/include/linux/of_dependencies.h
> +++ b/include/linux/of_dependencies.h
> @@ -58,4 +58,9 @@ extern void of_init_create_devices(const struct of_device_id *matches,
>   */
>  extern void of_init_free_order(void);
>  
> +void of_init_set_recording(bool recording);
> +bool of_init_is_recording(void);
> +int of_init_register_platform_driver(struct platform_driver *drv);
> +void of_init_register_drivers(void);
> +
>  #endif	/* _LINUX_OF_DEPENDENCIES_H */
> diff --git a/include/linux/platform_device.h b/include/linux/platform_device.h
> index 16f6654..b8559d9 100644
> --- a/include/linux/platform_device.h
> +++ b/include/linux/platform_device.h
> @@ -215,9 +215,17 @@ static inline void platform_set_drvdata(struct platform_device *pdev,
>   * boilerplate.  Each module may only use this macro once, and
>   * calling it replaces module_init() and module_exit()
>   */
> -#define module_platform_driver(__platform_driver) \
> -	module_driver(__platform_driver, platform_driver_register, \
> -			platform_driver_unregister)
> +#define module_platform_driver(__driver) \
> +static int __init __driver##_init(void) \
> +{ \
> +	return platform_driver_register(&(__driver)); \
> +} \
> +well_done_platform_module_init(__driver##_init); \
> +static void __exit __driver##_exit(void) \
> +{ \
> +	platform_driver_unregister(&(__driver)); \
> +} \
> +module_exit(__driver##_exit);
>  
>  /* module_platform_driver_probe() - Helper macro for drivers that don't do
>   * anything special in module init/exit.  This eliminates a lot of
> @@ -230,7 +238,7 @@ static int __init __platform_driver##_init(void) \
>  	return platform_driver_probe(&(__platform_driver), \
>  				     __platform_probe);    \
>  } \
> -module_init(__platform_driver##_init); \
> +well_done_platform_module_init(__platform_driver##_init); \
>  static void __exit __platform_driver##_exit(void) \
>  { \
>  	platform_driver_unregister(&(__platform_driver)); \
> diff --git a/init/main.c b/init/main.c
> index 9c7fd4c..7591cd1 100644
> --- a/init/main.c
> +++ b/init/main.c
> @@ -77,6 +77,7 @@
>  #include <linux/sched_clock.h>
>  #include <linux/context_tracking.h>
>  #include <linux/random.h>
> +#include <linux/of_dependencies.h>
>  
>  #include <asm/io.h>
>  #include <asm/bugs.h>
> @@ -720,6 +721,7 @@ extern initcall_t __initcall4_start[];
>  extern initcall_t __initcall5_start[];
>  extern initcall_t __initcall6_start[];
>  extern initcall_t __initcall7_start[];
> +extern initcall_t __initcall8_start[];
>  extern initcall_t __initcall_end[];
>  
>  static initcall_t *initcall_levels[] __initdata = {
> @@ -731,6 +733,7 @@ static initcall_t *initcall_levels[] __initdata = {
>  	__initcall5_start,
>  	__initcall6_start,
>  	__initcall7_start,
> +	__initcall8_start,
>  	__initcall_end,
>  };
>  
> @@ -744,6 +747,8 @@ static char *initcall_level_names[] __initdata = {
>  	"fs",
>  	"device",
>  	"late",
> +	/* must be the last level to become excluded in do_initcalls() */
> +	"well-done-platform-driver",
>  };
>  
>  static void __init do_initcall_level(int level)
> @@ -766,7 +771,7 @@ static void __init do_initcalls(void)
>  {
>  	int level;
>  
> -	for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
> +	for (level = 0; level < ARRAY_SIZE(initcall_levels) - 3; level++)
>  		do_initcall_level(level);
>  }
>  
> @@ -787,6 +792,16 @@ static void __init do_basic_setup(void)
>  	do_ctors();
>  	usermodehelper_enable();
>  	do_initcalls();
> +#ifdef CONFIG_OF_DEPENDENCIES
> +	/* collect a list of available platform drivers */
> +	of_init_set_recording(true);
> +	do_initcall_level(ARRAY_SIZE(initcall_levels) - 2);
> +	of_init_set_recording(false);
> +	/* probe available platform drivers with deterministic order */
> +	of_init_register_drivers();
> +	/* register late drivers */
> +	do_initcall_level(ARRAY_SIZE(initcall_levels) - 3);
> +#endif
>  	random_int_secret_init();
>  }
>  
> -- 
> 1.8.3.1
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> the body of a message to majordomo at vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> Please read the FAQ at  http://www.tux.org/lkml/




More information about the linux-arm-kernel mailing list