[PATCH v2 5/8] common: add initial barebox deep-probe support
Ahmad Fatoum
a.fatoum at pengutronix.de
Fri Oct 2 02:10:03 EDT 2020
On 9/30/20 10:47 AM, Marco Felsch wrote:
> The barebox 'deep probe' or 'probe on demand' mechanism is the answer of
> unwanted -EPROBE_DEFER failures. The EPROBE_DEFER error code was
> introduced by commit ab3da15bc14c ("base: Introduce deferred probing")
> and since then it causes a few problems.
>
> The error is returned if either the device is not yet present or the
> driver is not yet registered. This makes sense on linux systems where
> modules and hot-plug devices are used very often but not for barebox.
> The module support is rarely used and devices aren't hot pluggable.
>
> The current barebox behaviour populates all devices before the drivers
> are registered so all devices are present during the driver
> registration. So the driver probe() function gets called immediately
> after the driver registration and causes the -EPROBE_DEFER error if this
> driver depends on an other not yet registered driver.
>
> To get rid of the EPROBE_DEFER error code we need to reorder the device
> population and the driver registration. All drivers must be registered
> first. In an ideal world all driver can be registered by the same
> initcall level. Then devices are getting populated which causes calling
> the driver probe() function but this time resources/devices are created
> on demand if not yet available.
>
> Dependencies between devices are normally expressed as references to
> other device nodes. With deep probe barebox provides helper functions
> which take a device node and probe the device behind that node if
> necessary. This means instead of returning -EPROBE_DEFER, we can now
> make the desired resources available once we need them.
>
> If the resource can't be created we are returning -ENODEV since we are
> not supporting hot-plugging. Dropping EPROBE_DEFER is the long-term
> goal, avoid initcall shifting is the short-term goal.
>
> Call it deep-probe since the on-demand device creation can greate very
> deep stacks. This commit adds the initial support for: spi, i2c, reset,
> regulator and clk resource on-demand creation. The deep-probe mechanism
> must be enabled for each board to avoid breaking changes using
> deep_probe_enable(). This can be changed later after all boards are
> converted to the new mechanism.
>
> Signed-off-by: Marco Felsch <m.felsch at pengutronix.de>
> ---
> v2:
> - add deep-probe state to verify it once
> - use a linker list array instead of initcalls and runtime allocation
> - make of_device_create_on_demand an internal function
> - convert to of_device_ensure_probed* and of_devices_ensure_probed*
> - add missing function docs
> - force the existence of the driver to avoid EPROBE_DEFER (see long-term
> goal)
> - return int instead of device_d
> - fix stub funcs
> - add BUG_ON() to track undefined behaviour
> - add comment to of_platform_device_create() to make it clear that
> calling it again for an already populated device is not allowed
> - i2c: only set of_node->dev if it is available
>
> common/Makefile | 1 +
> common/deep-probe.c | 36 +++++++
> drivers/base/driver.c | 11 +-
> drivers/clk/clk.c | 5 +
> drivers/i2c/i2c.c | 8 ++
> drivers/of/base.c | 13 ++-
> drivers/of/platform.c | 165 ++++++++++++++++++++++++++++++
> drivers/regulator/core.c | 6 ++
> drivers/reset/core.c | 4 +
> drivers/spi/spi.c | 2 +
> include/asm-generic/barebox.lds.h | 10 +-
> include/deep-probe.h | 26 +++++
> include/of.h | 32 +++++-
> 13 files changed, 314 insertions(+), 5 deletions(-)
> create mode 100644 common/deep-probe.c
> create mode 100644 include/deep-probe.h
>
> diff --git a/common/Makefile b/common/Makefile
> index c3ae3ca1b9..8525240422 100644
> --- a/common/Makefile
> +++ b/common/Makefile
> @@ -3,6 +3,7 @@ obj-y += memory_display.o
> pbl-$(CONFIG_PBL_CONSOLE) += memory_display.o
> obj-y += clock.o
> obj-y += console_common.o
> +obj-y += deep-probe.o
> obj-y += startup.o
> obj-y += misc.o
> obj-pbl-y += memsize.o
> diff --git a/common/deep-probe.c b/common/deep-probe.c
> new file mode 100644
> index 0000000000..af53abdc74
> --- /dev/null
> +++ b/common/deep-probe.c
> @@ -0,0 +1,36 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +
> +#include <common.h>
> +#include <deep-probe.h>
> +#include <of.h>
> +
> +enum deep_probe_state {
> + DEEP_PROBE_UNKONW,
UNKNOWN*
> + DEEP_PROBE_SUPPORTED,
> + DEEP_PROBE_NOT_SUPPORTED
> +};
> +
> +static enum deep_probe_state boardstate;
> +
> +bool deep_probe_is_supported(void)
> +{
> + struct deep_probe_entry *board;
> +
> + if (boardstate == DEEP_PROBE_NOT_SUPPORTED)
> + return false;
> + else if (boardstate == DEEP_PROBE_SUPPORTED)
> + return true;
If you set UNKNOWN to -ENOSYS, SUPPORTED to 1 and NOT_SUPPORTED to 0,
you could just do if (boardstate >= 0) return boardstate; here
(Even if you want to keep it verbose, I like the enum constants having
expectable values)
> +
> + /* determine boardstate */
> + for (board = &__barebox_deep_probe_start;
> + board != &__barebox_deep_probe_end; board++) {
> + if (of_machine_is_compatible(board->compatible)) {
> + boardstate = DEEP_PROBE_SUPPORTED;
> + return true;
> + }
> + }
> +
> + boardstate = DEEP_PROBE_NOT_SUPPORTED;
> + return false;
> +}
> +EXPORT_SYMBOL_GPL(deep_probe_is_supported);
> diff --git a/drivers/base/driver.c b/drivers/base/driver.c
> index 412db6c406..b797655c15 100644
> --- a/drivers/base/driver.c
> +++ b/drivers/base/driver.c
> @@ -21,6 +21,7 @@
>
> #include <common.h>
> #include <command.h>
> +#include <deep-probe.h>
> #include <driver.h>
> #include <malloc.h>
> #include <console.h>
> @@ -95,7 +96,15 @@ int device_probe(struct device_d *dev)
> if (ret == -EPROBE_DEFER) {
> list_del(&dev->active);
> list_add(&dev->active, &deferred);
> - dev_dbg(dev, "probe deferred\n");
> +
> + /*
> + * -EPROBE_DEFER should never appear on a deep-probe machine so
> + * inform the user immediately.
> + */
> + if (deep_probe_is_supported())
> + dev_warn(dev, "probe deferred\n");
> + else
> + dev_dbg(dev, "probe deferred\n");
> return ret;
> }
>
> diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c
> index b04d44593b..e40dfae9ce 100644
> --- a/drivers/clk/clk.c
> +++ b/drivers/clk/clk.c
> @@ -439,6 +439,11 @@ struct clk *of_clk_get_from_provider(struct of_phandle_args *clkspec)
> {
> struct of_clk_provider *provider;
> struct clk *clk = ERR_PTR(-EPROBE_DEFER);
> + int ret;
> +
> + ret = of_device_ensure_probed(clkspec->np);
> + if (ret)
> + return ERR_PTR(ret);
>
> /* Check if we have such a provider in our array */
> list_for_each_entry(provider, &of_clk_providers, link) {
> diff --git a/drivers/i2c/i2c.c b/drivers/i2c/i2c.c
> index 57d8c7017f..12aac42794 100644
> --- a/drivers/i2c/i2c.c
> +++ b/drivers/i2c/i2c.c
> @@ -407,6 +407,9 @@ static struct i2c_client *i2c_new_device(struct i2c_adapter *adapter,
> }
> client->dev.info = i2c_info;
>
> + if (chip->of_node)
> + chip->of_node->dev = &client->dev;
> +
> return client;
> }
>
> @@ -548,6 +551,11 @@ struct i2c_adapter *i2c_get_adapter(int busnum)
> struct i2c_adapter *of_find_i2c_adapter_by_node(struct device_node *node)
> {
> struct i2c_adapter *adap;
> + int ret;
> +
> + ret = of_device_ensure_probed(node);
> + if (ret)
> + return ERR_PTR(ret);
>
> for_each_i2c_adapter(adap)
> if (adap->dev.device_node == node)
> diff --git a/drivers/of/base.c b/drivers/of/base.c
> index 8ba151dbde..a2f6c39ad1 100644
> --- a/drivers/of/base.c
> +++ b/drivers/of/base.c
> @@ -15,6 +15,7 @@
> * GNU General Public License for more details.
> */
> #include <common.h>
> +#include <deep-probe.h>
> #include <of.h>
> #include <of_address.h>
> #include <errno.h>
> @@ -1567,6 +1568,15 @@ int of_set_root_node(struct device_node *node)
> return 0;
> }
>
> +static int barebox_of_populate(void)
> +{
> + if (IS_ENABLED(CONFIG_OFDEVICE) && deep_probe_is_supported())
> + of_probe();
return of_probe(); ?
> +
> + return 0;
> +}
> +of_populate_initcall(barebox_of_populate);
This function's name should reflect that it's deep probe specific
> +
> void barebox_register_of(struct device_node *root)
> {
> if (root_node)
> @@ -1577,7 +1587,8 @@ void barebox_register_of(struct device_node *root)
>
> if (IS_ENABLED(CONFIG_OFDEVICE)) {
> of_clk_init(root, NULL);
> - of_probe();
> + if (!deep_probe_is_supported())
> + of_probe();
> }
> }
>
> diff --git a/drivers/of/platform.c b/drivers/of/platform.c
> index 01de6f98af..0368b1485a 100644
> --- a/drivers/of/platform.c
> +++ b/drivers/of/platform.c
> @@ -15,6 +15,7 @@
> * GNU General Public License for more details.
> */
> #include <common.h>
> +#include <deep-probe.h>
> #include <malloc.h>
> #include <of.h>
> #include <of_address.h>
> @@ -29,6 +30,12 @@
> struct device_d *of_find_device_by_node(struct device_node *np)
> {
> struct device_d *dev;
> + int ret;
> +
> + ret = of_device_ensure_probed(np);
> + if (ret)
> + return NULL;
> +
If you associate a dev with the np on deep probe, can't you just
return it deep_probe_is_supported() ?
> for_each_device(dev)
> if (dev->device_node == np)
> return dev;
> @@ -353,3 +360,161 @@ int of_platform_populate(struct device_node *root,
> return rc;
> }
> EXPORT_SYMBOL_GPL(of_platform_populate);
> +
> +static struct device_d *of_device_create_on_demand(struct device_node *np)
> +{
> + struct device_node *parent;
> + struct device_d *parent_dev, *dev;
> +
> + parent = of_get_parent(np);
> + if (!parent)
> + return NULL;
> +
> + /* Create all parent devices needed for the requested device */
> + parent_dev = parent->dev ? : of_device_create_on_demand(parent);
> + if (IS_ERR(parent_dev))
> + return parent_dev;
> +
> + /*
> + * Parent devices like i2c/spi controllers are populating their own
> + * devices. So it can be that the requested device already exist after
> + * the parent device creation.
> + */
> + if (np->dev)
> + return np->dev;
> +
> + pr_debug("%s: Create %s (%s) on demand\n", __func__,
> + np->name, np->full_name);
> +
> + if (of_device_is_compatible(np, "arm,primecell"))
> + dev = of_amba_device_create(np);
> + else
> + dev = of_platform_device_create(np, parent_dev);
> +
> + return dev ? : ERR_PTR(-ENODEV);
> +}
> +
> +/**
> + * of_device_ensure_probed() - ensures that a device is probed
> + *
> + * @np: the device_node handle which should be probed
> + *
> + * Ensures that the device is populated and probed so frameworks can make use of
> + * it.
> + *
> + * Return: %0 on success
> + * %-ENODEV if either the device can't be populated, the driver is
> + * missing or the driver probe returns an error.
> + */
> +int of_device_ensure_probed(struct device_node *np)
> +{
> + struct device_d *dev;
> +
> + if (!deep_probe_is_supported())
> + return 0;
> +
> + dev = of_device_create_on_demand(np);
> + if (IS_ERR(dev))
> + return PTR_ERR(dev);
> +
> + BUG_ON(!dev);
> +
> + /*
> + * The deep-probe mechanism relies on the fact that all necessary
> + * drivers are added before the device creation. Furthermore deep-probe
> + * is the answer of the EPROBE_DEFER errno so we must ensure that the
answer to*
> + * driver was probed succesfully after the device creation. Both
successfully
> + * requirments are fullfilled if 'dev->driver' is not NULL.
requirements, fulfilled
> + */
> + if (!dev->driver)
> + return -ENODEV;
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(of_device_ensure_probed);
> +
> +/**
> + * of_device_ensure_probed_by_alias() - ensures that a device is probed
> + *
> + * @alias: the alias string to search for a device
> + *
> + * The function search for a given alias string and ensures that the device is
> + * populated and probed if found.
> + *
> + * Return: %0 on success
> + * %-ENODEV if either the device can't be populated, the driver is
> + * missing or the driver probe returns an error
I don't think it would be nice to just pass along driver probe errors as-is.
> + * %-EINVAL if alias can't be found
> + */
> +int of_device_ensure_probed_by_alias(const char *alias)
> +{
> + struct device_node *dev_node;
> +
> + dev_node = of_find_node_by_alias(NULL, alias);
> + if (!dev_node)
> + return -EINVAL;
> +
> + return of_device_ensure_probed(dev_node);
> +}
> +EXPORT_SYMBOL_GPL(of_device_ensure_probed_by_alias);
> +
> +/**
> + * of_devices_ensure_probed_by_dev_id() - ensures that a devices are probed
> + *
> + * @np: the start point device_node handle
> + * @ids: the matching 'struct of_device_id' ids
> + *
> + * The function start searching the device tree from @np and populates and
> + * probes devices which matches @ids.
> + *
> + * Return: %0 on success
> + * %-ENODEV if either the device wasn't found, can't be populated,
> + * the driver is missing or the driver probe returns an error
Likewise
> + */
> +int of_devices_ensure_probed_by_dev_id(struct device_node *np,
> + const struct of_device_id *ids)
> +{
> + struct device_node *child;
> +
> + if (of_match_node(ids, np))
> + return of_device_ensure_probed(np);
> +
> + for_each_child_of_node(np, child) {
> + int ret;
> +
> + ret = of_devices_ensure_probed_by_dev_id(child, ids);
> + if (!ret)
> + return 0;
> + }
> +
> + return -ENODEV;
> +}
> +EXPORT_SYMBOL_GPL(of_devices_ensure_probed_by_dev_id);
> +
> +/**
> + * of_devices_ensure_probed_by_property() - ensures that a devices are probed
> + *
> + * @property_name: The property name to search for
> + *
> + * The function start searching the whole device tree and populates and probe
> + * devices which matches @property_name.
> + *
> + * Return: %0 on success
> + * %-ENODEV if either the device wasn't found, can't be populated,
> + * the driver is missing or the driver probe returns an error
Likewise
> + */
> +int of_devices_ensure_probed_by_property(const char *property_name)
> +{
> + struct device_node *node;
> +
> + for_each_node_with_property(node, property_name) {
> + int ret;
> +
> + ret = of_device_ensure_probed(node);
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(of_devices_ensure_probed_by_property);
> diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c
> index 6ea21a4609..803b66bd0c 100644
> --- a/drivers/regulator/core.c
> +++ b/drivers/regulator/core.c
> @@ -175,6 +175,7 @@ int of_regulator_register(struct regulator_dev *rd, struct device_node *node)
> return PTR_ERR(ri);
>
> ri->node = node;
> + node->dev = rd->dev;
>
> of_property_read_u32(node, "regulator-enable-ramp-delay",
> &ri->enable_time_us);
> @@ -191,6 +192,7 @@ static struct regulator_internal *of_regulator_get(struct device_d *dev, const c
> char *propname;
> struct regulator_internal *ri;
> struct device_node *node;
> + int ret;
>
> propname = basprintf("%s-supply", supply);
>
> @@ -222,6 +224,10 @@ static struct regulator_internal *of_regulator_get(struct device_d *dev, const c
> goto out;
> }
>
> + ret = of_device_ensure_probed(node);
> + if (ret)
> + return ERR_PTR(ret);
> +
> list_for_each_entry(ri, ®ulator_list, list) {
> if (ri->node == node) {
> dev_dbg(dev, "Using %s regulator from %s\n",
> diff --git a/drivers/reset/core.c b/drivers/reset/core.c
> index 99b9c80655..ee2de58c34 100644
> --- a/drivers/reset/core.c
> +++ b/drivers/reset/core.c
> @@ -168,6 +168,10 @@ static struct reset_control *of_reset_control_get(struct device_node *node,
> if (ret)
> return ERR_PTR(ret);
>
> + ret = of_device_ensure_probed(args.np);
> + if (ret)
> + return ERR_PTR(ret);
> +
> rcdev = NULL;
> list_for_each_entry(r, &reset_controller_list, list) {
> if (args.np == r->of_node) {
> diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
> index 8421d9d7c1..d1d3bdcc41 100644
> --- a/drivers/spi/spi.c
> +++ b/drivers/spi/spi.c
> @@ -107,6 +107,8 @@ struct spi_device *spi_new_device(struct spi_controller *ctrl,
> if (status)
> goto fail;
>
> + chip->device_node->dev = &proxy->dev;
> +
> return proxy;
> fail:
> free(proxy);
> diff --git a/include/asm-generic/barebox.lds.h b/include/asm-generic/barebox.lds.h
> index a7d32160d1..c5f9d97547 100644
> --- a/include/asm-generic/barebox.lds.h
> +++ b/include/asm-generic/barebox.lds.h
> @@ -114,6 +114,13 @@
> KEEP(*(.rsa_keys.rodata.*)); \
> __rsa_keys_end = .; \
>
> +#define BAREBOX_DEEP_PROBE \
> + STRUCT_ALIGN(); \
> + __barebox_deep_probe_start = .; \
> + KEEP(*(SORT_BY_NAME(.barebox_deep_probe*))) \
> + __barebox_deep_probe_end = .;
> +
> +
> #ifdef CONFIG_CONSTRUCTORS
> #define KERNEL_CTORS() . = ALIGN(8); \
> __ctors_start = .; \
> @@ -136,7 +143,8 @@
> BAREBOX_CLK_TABLE \
> BAREBOX_DTB \
> BAREBOX_RSA_KEYS \
> - BAREBOX_PCI_FIXUP
> + BAREBOX_PCI_FIXUP \
> + BAREBOX_DEEP_PROBE
>
> #if defined(CONFIG_ARCH_BAREBOX_MAX_BARE_INIT_SIZE) && \
> CONFIG_ARCH_BAREBOX_MAX_BARE_INIT_SIZE < CONFIG_BAREBOX_MAX_BARE_INIT_SIZE
> diff --git a/include/deep-probe.h b/include/deep-probe.h
> new file mode 100644
> index 0000000000..4f6673a6f1
> --- /dev/null
> +++ b/include/deep-probe.h
> @@ -0,0 +1,26 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +#ifndef __DEEP_PROBE_H
> +#define __DEEP_PROBE_H
> +
> +#include <linux/stringify.h>
> +#include <linux/types.h>
> +
> +struct deep_probe_entry {
> + const char *compatible;
> +};
> +
> +bool deep_probe_is_supported(void);
> +
> +extern struct deep_probe_entry __barebox_deep_probe_start;
> +extern struct deep_probe_entry __barebox_deep_probe_end;
> +
> +#define _DEEP_PROBE_ENTRY(_entry) \
> + __barebox_deep_probe_ ## _entry
> +
> +#define BAREBOX_DEEP_PROBE_ENABLE(_entry,_compatible) \
> + const struct deep_probe_entry _DEEP_PROBE_ENTRY(_entry) \
> + __attribute__ ((unused,section (".barebox_deep_probe_" __stringify(_entry)))) = { \
> + .compatible = _compatible, \
> + }
> +
> +#endif /* __DEEP_PROBE_H */
> diff --git a/include/of.h b/include/of.h
> index f2dad9a6c2..3042ce261d 100644
> --- a/include/of.h
> +++ b/include/of.h
> @@ -267,6 +267,12 @@ extern struct device_d *of_device_enable_and_register_by_name(const char *name);
> extern struct device_d *of_device_enable_and_register_by_alias(
> const char *alias);
>
> +extern int of_device_ensure_probed(struct device_node *np);
> +extern int of_device_ensure_probed_by_alias(const char *alias);
> +extern int of_devices_ensure_probed_by_property(const char *property_name);
> +extern int of_devices_ensure_probed_by_dev_id(struct device_node *np,
> + const struct of_device_id *ids);
> +
> struct cdev *of_parse_partition(struct cdev *cdev, struct device_node *node);
> int of_parse_partitions(struct cdev *cdev, struct device_node *node);
> int of_partitions_register_fixup(struct cdev *cdev);
> @@ -332,12 +338,34 @@ static inline int of_set_root_node(struct device_node *node)
> return -ENOSYS;
> }
>
> -static inline struct device_d *of_platform_device_create(struct device_node *np,
> - struct device_d *parent)
> +static inline struct device_d *
> +of_platform_device_create(struct device_node *np, struct device_d *parent)
Unrelated change?
> {
> return NULL;
> }
>
> +static inline int of_device_ensure_probed(struct device_node *np)
> +{
> + return 0;
> +}
> +
> +static inline int of_device_ensure_probed_by_alias(const char *alias)
> +{
> + return 0;
> +}
> +
> +static inline int of_devices_ensure_probed_by_property(const char *property_name)
> +{
> + return 0;
> +}
> +
> +static inline int
> +of_devices_ensure_probed_by_dev_id(struct device_node *np,
> + const struct of_device_id *ids)
> +{
> + return 0;
> +}
> +
> static inline int of_bus_n_addr_cells(struct device_node *np)
> {
> return 0;
>
--
Pengutronix e.K. | |
Steuerwalder Str. 21 | http://www.pengutronix.de/ |
31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |
More information about the barebox
mailing list