[PATCH 10/12] platform/early: implement support for early platform drivers

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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



[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]


  Powered by Linux