From: Bartosz Golaszewski <bgolaszewski@xxxxxxxxxxxx> This introduces the core part of support for early platform drivers and devices. Signed-off-by: Bartosz Golaszewski <bgolaszewski@xxxxxxxxxxxx> --- drivers/base/Kconfig | 3 + drivers/base/Makefile | 1 + drivers/base/early.c | 332 +++++++++++++++++++++++++++++++++ drivers/misc/Makefile | 1 + include/linux/early_platform.h | 75 ++++++++ 5 files changed, 412 insertions(+) create mode 100644 drivers/base/early.c create mode 100644 include/linux/early_platform.h diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig index 29b0eb452b3a..ea648daecec1 100644 --- a/drivers/base/Kconfig +++ b/drivers/base/Kconfig @@ -205,6 +205,9 @@ config DEBUG_TEST_DRIVER_REMOVE unusable. You should say N here unless you are explicitly looking to test this functionality. +config EARLY_PLATFORM + def_bool n + source "drivers/base/test/Kconfig" config SYS_HYPERVISOR diff --git a/drivers/base/Makefile b/drivers/base/Makefile index 7845c95ee1b2..3998ad6719f1 100644 --- a/drivers/base/Makefile +++ b/drivers/base/Makefile @@ -7,6 +7,7 @@ obj-y := component.o core.o bus.o dd.o syscore.o \ attribute_container.o transport_class.o \ topology.o container.o property.o cacheinfo.o \ devcon.o +obj-$(CONFIG_EARLY_PLATFORM) += early.o obj-$(CONFIG_DEVTMPFS) += devtmpfs.o obj-$(CONFIG_DMA_CMA) += dma-contiguous.o obj-y += power/ diff --git a/drivers/base/early.c b/drivers/base/early.c new file mode 100644 index 000000000000..7d0b7fb85f58 --- /dev/null +++ b/drivers/base/early.c @@ -0,0 +1,332 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018 Texas Instruments, Inc. + * + * Author: + * Bartosz Golaszewski <bgolaszewski@xxxxxxxxxxxx> + */ + +#include <linux/early_platform.h> +#include <linux/init.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/slab.h> + +#include "base.h" + +extern struct early_platform_driver *__early_platform_drivers_table[]; +extern struct early_platform_driver *__early_platform_drivers_table_end[]; + +static bool early_platform_done; + +static LIST_HEAD(early_platform_drivers); +static LIST_HEAD(early_platform_devices); + +static int early_platform_device_set_name(struct early_platform_device *edev) +{ + switch (edev->pdev.id) { + case PLATFORM_DEVID_AUTO: + pr_warn("auto device ID not supported in early platform devices\n"); + /* fallthrough */ + case PLATFORM_DEVID_NONE: + edev->pdev.dev.init_name = kasprintf(GFP_KERNEL, + "%s", edev->pdev.name); + break; + default: + edev->pdev.dev.init_name = kasprintf(GFP_KERNEL, "%s.%d", + edev->pdev.name, + edev->pdev.id); + break; + } + + if (!edev->pdev.dev.init_name) + return -ENOMEM; + + return 0; +} + +static void early_platform_device_add(struct early_platform_device *edev) +{ + edev->pdev.dev.early = true; + INIT_LIST_HEAD(&edev->list); + list_add_tail(&edev->list, &early_platform_devices); +} + +static void early_platform_probe_deferred(void) +{ + struct early_platform_device *edev; + int rv; + + list_for_each_entry(edev, &early_platform_devices, list) { + if (!edev->deferred || !edev->deferred_drv->early_probe) + continue; + + rv = edev->deferred_drv->early_probe(&edev->pdev); + if (rv && rv != -EPROBE_DEFER) { + dev_err(&edev->pdev.dev, + "early platform driver probe failed: %d\n", + rv); + } + } +} + +static void early_platform_try_probe(struct early_platform_driver *edrv, + struct early_platform_device *edev) +{ + int rv; + + rv = early_platform_device_set_name(edev); + if (rv) + pr_warn("unable to set the early platform device name\n"); + + if (edrv->early_probe) { + rv = edrv->early_probe(&edev->pdev); + if (rv && rv != -EPROBE_DEFER && + rv != -ENODEV && rv != -ENXIO) { + dev_err(&edev->pdev.dev, + "early platform driver probe failed: %d\n", + rv); + return; + } else if (rv == -EPROBE_DEFER) { + edev->deferred = true; + edev->deferred_drv = edrv; + } else { + early_platform_probe_deferred(); + } + } +} + +/** + * of_early_to_platform_device - return the platform device with which this + * device node is associated + * @np - device node to look up + * + * If a device node was populated early, the corresponding platform device + * already exists. Instead of allocating a new object, we need to retrieve + * the previous one. This routine enables it. + */ +struct platform_device *of_early_to_platform_device(struct device_node *np) +{ + struct early_platform_device *edev; + + list_for_each_entry(edev, &early_platform_devices, list) { + if (np == edev->pdev.dev.of_node) + return &edev->pdev; + } + + return ERR_PTR(-ENOENT); +} + +static int of_early_platform_device_create(struct device_node *node, + struct early_platform_driver *edrv) +{ + struct early_platform_device *edev; + int rc; + + edev = kzalloc(sizeof(*edev), GFP_KERNEL); + if (!edev) + return -ENOMEM; + + platform_device_init(&edev->pdev, "", PLATFORM_DEVID_NONE); + /* + * We can safely use platform_device_release since the platform_device + * struct is the first member of early_platform_device. + */ + edev->pdev.dev.release = platform_device_release; + + rc = of_device_init_resources(&edev->pdev, node); + if (rc) { + kfree(edev); + return rc; + } + + of_node_set_flag(node, OF_POPULATED_EARLY); + edev->pdev.name = edrv->pdrv.driver.name; + edev->pdev.dev.of_node = of_node_get(node); + edev->pdev.dev.fwnode = &node->fwnode; + early_platform_device_add(edev); + early_platform_try_probe(edrv, edev); + + return 0; +} + +static int of_early_platform_populate(struct device_node *root) +{ + struct early_platform_driver *edrv; + const struct of_device_id *match; + struct device_node *child; + int rv; + + if (!root) + return 0; + + list_for_each_entry(edrv, &early_platform_drivers, list) { + if (!edrv->pdrv.driver.of_match_table) + continue; + + match = of_match_node(edrv->pdrv.driver.of_match_table, root); + if (!match) + continue; + + rv = of_early_platform_device_create(root, edrv); + if (rv) + return rv; + } + + for_each_child_of_node(root, child) { + rv = of_early_platform_populate(child); + if (rv) { + of_node_put(child); + return rv; + } + } + + return 0; +} + +/** + * early_platform_start - start handling early devices + * + * This should be called by the architecture code early in the boot sequence + * to register all early platform drivers, populate the early devices from DT + * and start matching platform devices specified in machine code. + */ +void early_platform_start(void) +{ + struct early_platform_driver **edrv; + struct device_node *root; + int rv; + + WARN_ONCE(!slab_is_available(), "slab is required for early devices\n"); + + pr_debug("%s(): registering pending early platform drivers\n", + __func__); + + for (edrv = __early_platform_drivers_table; + edrv < __early_platform_drivers_table_end; edrv++) { + rv = early_platform_driver_register(*edrv); + if (rv) + pr_warn("error registering early platform driver: %d\n", + rv); + } + + if (of_have_populated_dt()) { + pr_debug("%s(): populating early_platform devices from DT\n", + __func__); + + root = of_find_node_by_path("/"); + + rv = of_early_platform_populate(root); + if (rv) + pr_warn("error populating early devices from DT: %d\n", + rv); + + of_node_put(root); + } +} +EXPORT_SYMBOL_GPL(early_platform_start); + +/** + * early_platform_driver_register - register an early platform driver + * @edrv: early platform driver to register + * + * If we're past postcore initcall, this works exactly as + * platform_device_register(). + */ +int early_platform_driver_register(struct early_platform_driver *edrv) +{ + struct early_platform_device *edev; + + if (early_platform_done) + return platform_driver_register(&edrv->pdrv); + + INIT_LIST_HEAD(&edrv->list); + list_add_tail(&edrv->list, &early_platform_drivers); + + list_for_each_entry(edev, &early_platform_devices, list) { + if (platform_match(&edev->pdev.dev, &edrv->pdrv.driver)) { + early_platform_try_probe(edrv, edev); + break; + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(early_platform_driver_register); + +/** + * early_platform_device_register - register an early platform device + * @edev: early platform device to register + * + * If we're past postcore initcall, this works exactly as + * platform_device_register(). + */ +int early_platform_device_register(struct early_platform_device *edev) +{ + struct early_platform_driver *edrv; + + if (early_platform_done) + return platform_device_register(&edev->pdev); + + device_initialize(&edev->pdev.dev); + early_platform_device_add(edev); + + list_for_each_entry(edrv, &early_platform_drivers, list) { + if (platform_match(&edev->pdev.dev, &edrv->pdrv.driver)) { + early_platform_try_probe(edrv, edev); + break; + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(early_platform_device_register); + +/* + * This is called once the entire device model infrastructure is in place to + * seamlessly convert all early platform devices & drivers to regular ones. + * + * From this point forward all early platform devices work exactly like normal + * platform devices. + */ +static int early_platform_finalize(void) +{ + struct early_platform_driver *edrv; + struct early_platform_device *edev; + int rv; + + early_platform_done = true; + + pr_debug("%s(): converting early platform drivers to real platform drivers\n", + __func__); + + list_for_each_entry(edrv, &early_platform_drivers, list) { + rv = platform_driver_register(&edrv->pdrv); + if (rv) + pr_warn("%s: error converting early platform driver to real platform driver\n", + edrv->pdrv.driver.name); + } + + pr_debug("%s(): converting early platform devices to real platform devices\n", + __func__); + + list_for_each_entry(edev, &early_platform_devices, list) { + if (edev->pdev.dev.of_node) + /* This will be handled by of_platform_populate(). */ + continue; + + kfree(edev->pdev.dev.init_name); + + /* + * We don't want to reinitialize the associated struct device + * so we must not call platform_device_register(). + */ + rv = platform_device_add(&edev->pdev); + if (rv) + pr_warn("%s: error converting early platform device to real platform device\n", + dev_name(&edev->pdev.dev)); + } + + return 0; +} +postcore_initcall(early_platform_finalize); diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 20be70c3f118..d0a8788d5151 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -57,3 +57,4 @@ obj-$(CONFIG_ASPEED_LPC_SNOOP) += aspeed-lpc-snoop.o obj-$(CONFIG_PCI_ENDPOINT_TEST) += pci_endpoint_test.o obj-$(CONFIG_OCXL) += ocxl/ obj-$(CONFIG_MISC_RTSX) += cardreader/ +CFLAGS_dummy-early.o := -DDEBUG diff --git a/include/linux/early_platform.h b/include/linux/early_platform.h new file mode 100644 index 000000000000..fd3fd4db8322 --- /dev/null +++ b/include/linux/early_platform.h @@ -0,0 +1,75 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2018 Texas Instruments, Inc. + * + * Author: + * Bartosz Golaszewski <bgolaszewski@xxxxxxxxxxxx> + */ + +#ifndef __EARLY_PLATFORM_H__ +#define __EARLY_PLATFORM_H__ + +#include <linux/platform_device.h> +#include <linux/types.h> + +/** + * struct early_platform_driver + * + * @pdrv: real platform driver associated with this early platform driver + * @list: list head for the list of early platform drivers + * @early_probe: early probe callback + */ +struct early_platform_driver { + struct platform_driver pdrv; + struct list_head list; + int (*early_probe)(struct platform_device *); +}; + +/** + * struct early_platform_device + * + * @pdev: real platform device associated with this early platform device + * @list: list head for the list of early platform devices + * @deferred: true if this device's early probe was deferred + * @deferred_drv: early platform driver with which this device was matched + */ +struct early_platform_device { + struct platform_device pdev; + struct list_head list; + bool deferred; + struct early_platform_driver *deferred_drv; +}; + +#ifdef CONFIG_EARLY_PLATFORM +extern void early_platform_start(void); +extern int early_platform_driver_register(struct early_platform_driver *edrv); +extern int early_platform_device_register(struct early_platform_device *edev); +#else /* CONFIG_EARLY_PLATFORM */ +static inline void early_platform_start(void) {} +static int void +early_platform_driver_register(struct early_platform_driver *edrv) {} +static int void +early_platform_device_register(struct early_platform_device *edev) {} +#endif /* CONFIG_EARLY_PLATFORM */ + +#if defined(CONFIG_EARLY_PLATFORM) && defined(CONFIG_OF) +extern struct platform_device * +of_early_to_platform_device(struct device_node *np); +#else +static inline struct platform_device * +of_early_to_platform_device(struct device_node *np) +{ + return ERR_PTR(-ENOSYS); +} +#endif /* defined(CONFIG_EARLY_PLATFORM) && defined(CONFIG_OF) */ + +#ifdef CONFIG_EARLY_PLATFORM +#define module_early_platform_driver(_edrv) \ + static const struct early_platform_driver *__##_edrv##_entry \ + __used __section(__early_platform_drivers_table) \ + = &(_edrv) +#else /* CONFIG_EARLY_PLATFORM */ +#define module_early_platform_driver(_edrv) +#endif /* CONFIG_EARLY_PLATFORM */ + +#endif /* __EARLY_PLATFORM_H__ */ -- 2.17.0 -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html