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

From: Grant Likely
Date: Wed May 14 2014 - 10:13:13 EST


On Mon, 12 May 2014 18:47:56 +0200, Alexander Holler <holler@xxxxxxxxxxxxx> 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@xxxxxxxxxxxxx>
> ---
> 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@xxxxxxxxxxxxxxx
> More majordomo info at http://vger.kernel.org/majordomo-info.html
> Please read the FAQ at http://www.tux.org/lkml/

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/