lkml.org 
[lkml]   [2017]   [Dec]   [14]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    From
    Subject[PATCH V5 08/13] boot_constraint: Manage deferrable constraints
    Date
    It is possible that some of the resources aren't available at the time
    constraints are getting set and the boot constraints core will return
    -EPROBE_DEFER for them. In order to retry adding the constraints at a
    later point of time (after the resource is added and before any of its
    users come up), this patch proposes two things:

    - Each constraint is represented by a virtual platform device, so that
    it is re-probed again until the time all the dependencies aren't met.
    The platform device is removed along with the constraint, with help of
    the free_resources() callback.

    - Enable early defer probing support by calling
    driver_enable_deferred_probe(), so that the core retries probing
    deferred devices every time any device is bound to a driver. This
    makes sure that the constraint is set before any of the users of the
    resources come up.

    This is tested on ARM64 Hikey board where probe was deferred for a
    device.

    Tested-by: Rajendra Nayak <rnayak@codeaurora.org>
    Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>
    ---
    drivers/base/dd.c | 12 ++
    drivers/boot_constraint/Makefile | 2 +-
    drivers/boot_constraint/deferrable_dev.c | 241 +++++++++++++++++++++++++++++++
    include/linux/boot_constraint.h | 27 ++++
    4 files changed, 281 insertions(+), 1 deletion(-)
    create mode 100644 drivers/boot_constraint/deferrable_dev.c

    diff --git a/drivers/base/dd.c b/drivers/base/dd.c
    index dc89f98a2487..48a9945ff206 100644
    --- a/drivers/base/dd.c
    +++ b/drivers/base/dd.c
    @@ -227,6 +227,18 @@ void device_unblock_probing(void)
    driver_deferred_probe_trigger();
    }

    +/**
    + * driver_enable_deferred_probe() - Enable probing of deferred devices
    + *
    + * We don't want to get in the way when the bulk of drivers are getting probed
    + * and so deferred probe is disabled in the beginning. Enable it now because we
    + * need it.
    + */
    +void driver_enable_deferred_probe(void)
    +{
    + driver_deferred_probe_enable = true;
    +}
    +
    /**
    * deferred_probe_initcall() - Enable probing of deferred devices
    *
    diff --git a/drivers/boot_constraint/Makefile b/drivers/boot_constraint/Makefile
    index b7ade1a7afb5..a765094623a3 100644
    --- a/drivers/boot_constraint/Makefile
    +++ b/drivers/boot_constraint/Makefile
    @@ -1,3 +1,3 @@
    # Makefile for device boot constraints

    -obj-y := clk.o core.o pm.o supply.o
    +obj-y := clk.o deferrable_dev.o core.o pm.o supply.o
    diff --git a/drivers/boot_constraint/deferrable_dev.c b/drivers/boot_constraint/deferrable_dev.c
    new file mode 100644
    index 000000000000..158c04d41e09
    --- /dev/null
    +++ b/drivers/boot_constraint/deferrable_dev.c
    @@ -0,0 +1,241 @@
    +// SPDX-License-Identifier: GPL-2.0
    +/*
    + * Copyright (C) 2017 Linaro.
    + * Viresh Kumar <viresh.kumar@linaro.org>
    + */
    +
    +#include <linux/err.h>
    +#include <linux/idr.h>
    +#include <linux/of.h>
    +#include <linux/of_platform.h>
    +#include <linux/platform_device.h>
    +#include <linux/slab.h>
    +
    +#include "core.h"
    +
    +static DEFINE_IDA(pdev_index);
    +
    +void driver_enable_deferred_probe(void);
    +
    +struct boot_constraint_pdata {
    + struct device *dev;
    + struct dev_boot_constraint constraint;
    + int probe_failed;
    + int index;
    +};
    +
    +static void boot_constraint_remove(void *data)
    +{
    + struct platform_device *pdev = data;
    + struct boot_constraint_pdata *pdata = dev_get_platdata(&pdev->dev);
    +
    + ida_simple_remove(&pdev_index, pdata->index);
    + kfree(pdata->constraint.data);
    + platform_device_unregister(pdev);
    +}
    +
    +/*
    + * A platform device is added for each and every constraint, to handle
    + * -EPROBE_DEFER properly.
    + */
    +static int boot_constraint_probe(struct platform_device *pdev)
    +{
    + struct boot_constraint_pdata *pdata = dev_get_platdata(&pdev->dev);
    + struct dev_boot_constraint_info info;
    + int ret;
    +
    + if (WARN_ON(!pdata))
    + return -EINVAL;
    +
    + info.constraint = pdata->constraint;
    + info.free_resources = boot_constraint_remove;
    + info.free_resources_data = pdev;
    +
    + ret = dev_boot_constraint_add(pdata->dev, &info);
    + if (ret) {
    + if (ret == -EPROBE_DEFER)
    + driver_enable_deferred_probe();
    + else
    + pdata->probe_failed = ret;
    + }
    +
    + return ret;
    +}
    +
    +static struct platform_driver boot_constraint_driver = {
    + .driver = {
    + .name = "boot-constraints-dev",
    + },
    + .probe = boot_constraint_probe,
    +};
    +
    +static int __init boot_constraint_init(void)
    +{
    + return platform_driver_register(&boot_constraint_driver);
    +}
    +core_initcall(boot_constraint_init);
    +
    +static int boot_constraint_add_dev(struct device *dev,
    + struct dev_boot_constraint *constraint)
    +{
    + struct boot_constraint_pdata pdata = {
    + .dev = dev,
    + .constraint.type = constraint->type,
    + };
    + struct platform_device *pdev;
    + struct boot_constraint_pdata *pdev_pdata;
    + int size, ret;
    +
    + switch (constraint->type) {
    + case DEV_BOOT_CONSTRAINT_CLK:
    + size = sizeof(struct dev_boot_constraint_clk_info);
    + break;
    + case DEV_BOOT_CONSTRAINT_PM:
    + size = 0;
    + break;
    + case DEV_BOOT_CONSTRAINT_SUPPLY:
    + size = sizeof(struct dev_boot_constraint_supply_info);
    + break;
    + default:
    + dev_err(dev, "%s: Constraint type (%d) not supported\n",
    + __func__, constraint->type);
    + return -EINVAL;
    + }
    +
    + /* Will be freed from boot_constraint_remove() */
    + pdata.constraint.data = kmemdup(constraint->data, size, GFP_KERNEL);
    + if (!pdata.constraint.data)
    + return -ENOMEM;
    +
    + ret = ida_simple_get(&pdev_index, 0, 256, GFP_KERNEL);
    + if (ret < 0) {
    + dev_err(dev, "failed to allocate index (%d)\n", ret);
    + goto free;
    + }
    +
    + pdata.index = ret;
    +
    + pdev = platform_device_register_data(NULL, "boot-constraints-dev", ret,
    + &pdata, sizeof(pdata));
    + if (IS_ERR(pdev)) {
    + dev_err(dev, "%s: Failed to create pdev (%ld)\n", __func__,
    + PTR_ERR(pdev));
    + ret = PTR_ERR(pdev);
    + goto ida_remove;
    + }
    +
    + /* Release resources if probe has failed */
    + pdev_pdata = dev_get_platdata(&pdev->dev);
    + if (pdev_pdata->probe_failed) {
    + ret = pdev_pdata->probe_failed;
    + goto remove_pdev;
    + }
    +
    + return 0;
    +
    +remove_pdev:
    + platform_device_unregister(pdev);
    +ida_remove:
    + ida_simple_remove(&pdev_index, pdata.index);
    +free:
    + kfree(pdata.constraint.data);
    +
    + return ret;
    +}
    +
    +static int dev_boot_constraint_add_deferrable(struct device *dev,
    + struct dev_boot_constraint *constraints, int count)
    +{
    + int ret, i;
    +
    + for (i = 0; i < count; i++) {
    + ret = boot_constraint_add_dev(dev, &constraints[i]);
    + if (ret)
    + return ret;
    + }
    +
    + return 0;
    +}
    +
    +/* This only creates platform devices for now */
    +static void add_deferrable_of_single(struct device_node *np,
    + struct dev_boot_constraint *constraints,
    + int count)
    +{
    + struct device *dev;
    + int ret;
    +
    + if (!of_device_is_available(np))
    + return;
    +
    + ret = of_platform_bus_create(np, NULL, NULL, NULL, false);
    + if (ret)
    + return;
    +
    + dev = of_find_any_device_by_node(np);
    + if (!dev) {
    + pr_err("Boot Constraints: Failed to find dev: %pOF\n", np);
    + return;
    + }
    +
    + ret = dev_boot_constraint_add_deferrable(dev, constraints, count);
    + if (ret)
    + dev_err(dev, "Failed to add boot constraint (%d)\n", ret);
    +}
    +
    +/* Not all compatible device nodes may have boot constraints */
    +static bool node_has_boot_constraints(struct device_node *np,
    + struct dev_boot_constraint_of *oconst)
    +{
    + int i;
    +
    + if (!oconst->dev_names)
    + return true;
    +
    + for (i = 0; i < oconst->dev_names_count; i++) {
    + if (!strcmp(oconst->dev_names[i], kbasename(np->full_name)))
    + return true;
    + }
    +
    + return false;
    +}
    +
    +/**
    + * dev_boot_constraint_add_deferrable_of: Adds all constraints for a platform.
    + *
    + * @oconst: This is an array of 'struct dev_boot_constraint_of', where each
    + * entry of the array is used to add one or more boot constraints across one or
    + * more devices having the same compatibility in the device tree.
    + * @count: Size of the 'oconst' array.
    + *
    + * This helper routine provides an easy way to add all boot constraints for a
    + * machine or platform. Just like dev_boot_constraint_add(), this must be called
    + * before the devices (to which we want to add constraints) are probed by their
    + * drivers, otherwise the boot constraint will never get removed for those
    + * devices and may result in unwanted behavior of the hardware. The boot
    + * constraints are removed by the driver core automatically after the devices
    + * are probed (successfully or unsuccessfully).
    + *
    + * This adds the boot constraints in a deferrable way and the caller need not
    + * worry about the availability of the resources required by the constraint.
    + * This routine will return successfully and the constraint will be added by the
    + * boot constraint core as soon as the resource is available at a later point in
    + * time.
    + */
    +void dev_boot_constraint_add_deferrable_of(struct dev_boot_constraint_of *oconst,
    + int count)
    +{
    + struct device_node *np;
    + int i;
    +
    + for (i = 0; i < count; i++) {
    + for_each_compatible_node(np, NULL, oconst[i].compat) {
    + if (!node_has_boot_constraints(np, &oconst[i]))
    + continue;
    +
    + add_deferrable_of_single(np, oconst[i].constraints,
    + oconst[i].count);
    + }
    + }
    +}
    +EXPORT_SYMBOL_GPL(dev_boot_constraint_add_deferrable_of);
    diff --git a/include/linux/boot_constraint.h b/include/linux/boot_constraint.h
    index 12fac0d565b0..ab752e19bfd0 100644
    --- a/include/linux/boot_constraint.h
    +++ b/include/linux/boot_constraint.h
    @@ -79,16 +79,43 @@ struct dev_boot_constraint_info {
    void *free_resources_data;
    };

    +/**
    + * struct dev_boot_constraint_of - This is used to add one or more boot
    + * constraints across one or more devices having the same compatibility in the
    + * device tree.
    + *
    + * @compat: This must match the compatible string of the devices to which we
    + * want to apply constraints.
    + * @constraints: This points to one or more boot constraints.
    + * @count: This contains the number of boot constraints pointed by the
    + * 'constraints' field.
    + * @dev_names: This is used to limit the application of boot constraints to only
    + * a subset of devices with matching compatibility.
    + * @dev_names_count: This is the number of devices pointed by the 'dev_names'
    + * array.
    + */
    +struct dev_boot_constraint_of {
    + const char *compat;
    + struct dev_boot_constraint *constraints;
    + unsigned int count;
    +
    + const char * const *dev_names;
    + unsigned int dev_names_count;
    +};
    +
    #ifdef CONFIG_DEV_BOOT_CONSTRAINT
    int dev_boot_constraint_add(struct device *dev,
    struct dev_boot_constraint_info *info);
    void dev_boot_constraints_remove(struct device *dev);
    +void dev_boot_constraint_add_deferrable_of(struct dev_boot_constraint_of *oconst,
    + int count);
    #else
    static inline
    int dev_boot_constraint_add(struct device *dev,
    struct dev_boot_constraint_info *info)
    { return 0; }
    static inline void dev_boot_constraints_remove(struct device *dev) {}
    +static inline void dev_boot_constraint_add_deferrable_of(struct dev_boot_constraint_of *oconst, int count) {}
    #endif /* CONFIG_DEV_BOOT_CONSTRAINT */

    #endif /* _LINUX_BOOT_CONSTRAINT_H */
    --
    2.15.0.194.g9af6a3dea062
    \
     
     \ /
      Last update: 2017-12-14 16:37    [W:4.516 / U:24.040 seconds]
    ©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site