linux-embedded.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Francesco Valla <francesco@valla.it>
To: linux-embedded@vger.kernel.org
Subject: [PATCH 1/1] drivers: misc: add driver for bootstage stash
Date: Fri, 23 May 2025 00:42:24 +0200	[thread overview]
Message-ID: <20250522224223.358881-3-francesco@valla.it> (raw)
In-Reply-To: <20250522224223.358881-2-francesco@valla.it>

Add support for bootstage stash areas containing boot time data
created by some bootloader (e.g. U-Boot). The driver provides generic
time information through sysfs and platform-specific one through
debugfs.

Signed-off-by: Francesco Valla <francesco@valla.it>
---
 .../bindings/reserved-memory/bootstage.yaml   |  44 +++
 Documentation/misc-devices/bootstage.rst      |  53 ++++
 Documentation/misc-devices/index.rst          |   1 +
 MAINTAINERS                                   |   7 +
 drivers/misc/Kconfig                          |  10 +
 drivers/misc/Makefile                         |   1 +
 drivers/misc/bootstage.c                      | 292 ++++++++++++++++++
 drivers/of/platform.c                         |   1 +
 8 files changed, 409 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/reserved-memory/bootstage.yaml
 create mode 100644 Documentation/misc-devices/bootstage.rst
 create mode 100644 drivers/misc/bootstage.c

diff --git a/Documentation/devicetree/bindings/reserved-memory/bootstage.yaml b/Documentation/devicetree/bindings/reserved-memory/bootstage.yaml
new file mode 100644
index 000000000000..e71d85c5c2ce
--- /dev/null
+++ b/Documentation/devicetree/bindings/reserved-memory/bootstage.yaml
@@ -0,0 +1,44 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/reserved-memory/bootstage.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Bootstage stash
+
+description: |
+  This binding represents a reserved memory region containing bootstage stash
+  data generated by a previous bootloader stage.
+
+maintainers:
+  - Francesco Valla <francesco@valla.it>
+
+allOf:
+  - $ref: reserved-memory.yaml
+
+properties:
+  compatible:
+    const: bootstage
+
+  reg:
+    description: page-aligned region of memory containing bootstage stash data
+
+required:
+  - compatible
+  - reg
+  - no-map
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    reserved-memory {
+        #address-cells = <2>;
+        #size-cells = <1>;
+
+        bootstage: bootstage@12340000 {
+            compatible = "bootstage";
+            reg = <0x00 0x12340000 0x2000>;
+            no-map;
+        };
+    };
diff --git a/Documentation/misc-devices/bootstage.rst b/Documentation/misc-devices/bootstage.rst
new file mode 100644
index 000000000000..2e1bbd31aab8
--- /dev/null
+++ b/Documentation/misc-devices/bootstage.rst
@@ -0,0 +1,53 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+================
+Bootstage driver
+================
+
+The bootstage driver exports interfaces to read from a bootstage stash area
+saved by a bootloader (e.g.: U-Boot) that ran before the Linux kernel.
+
+Two kind of interfaces are exported:
+
+- a sysfs interface for bootloader- and platform-agnostic data
+- a debugfs interface for bootloader- and platform-specific data
+
+
+The sysfs interface
+-------------------
+
+Following sysfs attributes can be found at /sys/devices/platform/<device-name>/:
+
+- start_time_us: bootloader start time in microseconds
+- end_time_us: bootloader end time in microseconds
+
+
+The debugfs interface
+---------------------
+
+Following debugfs interfaces can be found at
+/sys/kernel/debug/bootstage/<device-name>/:
+
+- stages: details on staged bootloader stages, with start time and duration.
+  Example output::
+
+    Mark (us)  Elapsed (us)  Stage
+            0             0  reset
+       183689        183689  SPL
+       489247        305558  end phase
+       506987         17740  board_init_f
+      1257880        750893  board_init_r
+      1622303        364423  eth_common_init
+      1888033        265730  eth_initialize
+      1893077          5044  main_loop
+      4204282       2311205  cli_loop
+
+- accumulated_time: time accumulated during certain bootloader stages.
+  Example output::
+
+    Time (us)  Stage
+         4902  dm_spl
+       322719  dm_f
+         9527  dm_r
+
+The number and type of staged stages are bootloader- and platform-specific.
diff --git a/Documentation/misc-devices/index.rst b/Documentation/misc-devices/index.rst
index 8c5b226d8313..c5ebb3d44505 100644
--- a/Documentation/misc-devices/index.rst
+++ b/Documentation/misc-devices/index.rst
@@ -28,3 +28,4 @@ fit into other categories.
    tps6594-pfsm
    uacce
    xilinx_sdfec
+   bootstage
diff --git a/MAINTAINERS b/MAINTAINERS
index f21f1dabb5fe..0bdecd07023a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4203,6 +4203,13 @@ F:	Documentation/ABI/stable/sysfs-class-bluetooth
 F:	include/net/bluetooth/
 F:	net/bluetooth/
 
+BOOTSTAGE DRIVER
+M:	Francesco Valla <francesco@valla.it>
+L:	linux-embedded@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/reserved-memory/bootstage.yaml
+F:	drivers/misc/bootstage.c
+
 BONDING DRIVER
 M:	Jay Vosburgh <jv@jvosburgh.net>
 L:	netdev@vger.kernel.org
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 6b37d61150ee..97cdfa241c0c 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -632,6 +632,16 @@ config MCHP_LAN966X_PCI
 	    - lan966x-miim (MDIO_MSCC_MIIM)
 	    - lan966x-switch (LAN966X_SWITCH)
 
+config BOOTSTAGE
+	tristate "Bootstage stash support"
+	depends on OF_RESERVED_MEM
+	help
+	  This enables the support for a bootstage stash.
+
+	  A bootstage stash can be created by some bootloaders (e.g.: U-Boot) to
+	  store information on its boot timings. This driver provides access to
+	  these information through sysfs and debugsfs interfaces.
+
 source "drivers/misc/c2port/Kconfig"
 source "drivers/misc/eeprom/Kconfig"
 source "drivers/misc/cb710/Kconfig"
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index d6c917229c45..3562c1bf701f 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -74,3 +74,4 @@ lan966x-pci-objs		:= lan966x_pci.o
 lan966x-pci-objs		+= lan966x_pci.dtbo.o
 obj-$(CONFIG_MCHP_LAN966X_PCI)	+= lan966x-pci.o
 obj-y				+= keba/
+obj-$(CONFIG_BOOTSTAGE)		+= bootstage.o
diff --git a/drivers/misc/bootstage.c b/drivers/misc/bootstage.c
new file mode 100644
index 000000000000..a106410a56ee
--- /dev/null
+++ b/drivers/misc/bootstage.c
@@ -0,0 +1,292 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025 - Francesco Valla <francesco@valla.it>
+ *
+ * Driver for bootstage stash.
+ *
+ * This driver exposes bootstage stash generated by bootloader and/or firmware
+ * stages that run before the Linux kernel.
+ *
+ */
+
+#include <linux/debugfs.h>
+#include <linux/io.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/platform_device.h>
+#include <linux/seq_file.h>
+
+#define BOOTSTAGE_MAGIC		0xb00757a3
+#define BOOTSTAGE_MAX_VERSION	0
+
+enum bootstage_id {
+	BOOTSTAGE_ID_START = 0,
+};
+
+enum bootstage_flags {
+	BOOTSTAGEF_ERROR	= 1 << 0,	/* Error record */
+	BOOTSTAGEF_ALLOC	= 1 << 1,	/* Allocate an id */
+};
+
+struct bootstage_record {
+	ulong time_us;
+	u32 start_us;
+	const char *name;
+	int flags;
+	enum bootstage_id id;
+};
+
+struct bootstage_hdr {
+	u32 version;		/* Boostage stash version */
+	u32 count;		/* Number of records */
+	u32 size;		/* Total data size (non-zero if valid) */
+	u32 magic;		/* Magic number */
+	u32 next_id;		/* Next ID to use for bootstage */
+};
+
+struct bootstage_drvdata {
+	struct bootstage_hdr *hdr;
+	struct bootstage_record *records;
+
+	u32 start_time_us;
+	u32 end_time_us;
+
+	struct dentry *debugfs_dir;
+};
+
+static struct dentry *bootstage_debugfs_dir;
+
+static int stages_show(struct seq_file *m, void *d)
+{
+	struct bootstage_drvdata *drvdata = m->private;
+	struct bootstage_hdr *hdr = drvdata->hdr;
+	struct bootstage_record *rec;
+	u32 prev = 0;
+	int i;
+
+	seq_printf(m, "%13s %13s  %s\n", "Mark (us)", "Elapsed (us)", "Stage");
+
+	for (i = 0, rec = drvdata->records; i < hdr->count; i++, rec++) {
+		if ((rec->id && !rec->start_us) || (i == 0)) {
+			if (prev > rec->time_us)
+				prev = 0;
+			seq_printf(m, "%13lu %13lu  %s\n", rec->time_us,
+				   rec->time_us - prev, rec->name);
+			prev = rec->time_us;
+		}
+	}
+
+	return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(stages);
+
+static int accumulated_time_show(struct seq_file *m, void *d)
+{
+	struct bootstage_drvdata *drvdata = m->private;
+	struct bootstage_hdr *hdr = drvdata->hdr;
+	struct bootstage_record *rec;
+	int i;
+
+	seq_printf(m, "%13s  %s\n", "Time (us)", "Stage");
+
+	for (i = 0, rec = drvdata->records; i < hdr->count; i++, rec++) {
+		if (rec->start_us)
+			seq_printf(m, "%13lu  %s\n", rec->time_us, rec->name);
+	}
+
+	return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(accumulated_time);
+
+static void bootstage_debugfs_init(struct device *dev, struct bootstage_drvdata *drvdata)
+{
+	drvdata->debugfs_dir = debugfs_create_dir(dev_name(dev), bootstage_debugfs_dir);
+	if (IS_ERR(drvdata->debugfs_dir))
+		return;
+
+	debugfs_create_file("stages", 0444, drvdata->debugfs_dir, drvdata, &stages_fops);
+	debugfs_create_file("accumulated_time", 0444, drvdata->debugfs_dir, drvdata,
+			    &accumulated_time_fops);
+}
+
+static void bootstage_debugfs_exit(struct device *dev, struct bootstage_drvdata *drvdata)
+{
+	debugfs_remove_recursive(drvdata->debugfs_dir);
+}
+
+static ssize_t start_time_us_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct bootstage_drvdata *drvdata = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%u\n", drvdata->start_time_us);
+}
+static DEVICE_ATTR_RO(start_time_us);
+
+static ssize_t end_time_us_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct bootstage_drvdata *drvdata = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%u\n", drvdata->end_time_us);
+}
+static DEVICE_ATTR_RO(end_time_us);
+
+static struct attribute *bootstage_attrs[] = {
+	&dev_attr_start_time_us.attr,
+	&dev_attr_end_time_us.attr,
+	NULL,
+};
+
+static const struct attribute_group bootstage_attr_group = {
+	.attrs = bootstage_attrs,
+};
+
+static int bootstage_parse(struct device *dev, struct bootstage_drvdata *drvdata,
+			   resource_size_t size)
+{
+	const char *str_ptr = (const char *)(drvdata->records + drvdata->hdr->count);
+	const resource_size_t calc_size = (resource_size_t)((void *)str_ptr - (void *)drvdata->hdr);
+	struct bootstage_record *rec;
+	u32 r;
+
+	// Sanity checks on bootstage header
+	if (drvdata->hdr->magic != BOOTSTAGE_MAGIC) {
+		dev_err(dev, "wrong bootstage magic number %08Xh\n", drvdata->hdr->magic);
+		return -EINVAL;
+	} else if (drvdata->hdr->version > BOOTSTAGE_MAX_VERSION) {
+		dev_err(dev, "bootstage version %u not supported\n", drvdata->hdr->version);
+		return -EOPNOTSUPP;
+	} else if (drvdata->hdr->size == 0) {
+		dev_err(dev, "invalid bootstage stash (declared size is zero)\n");
+		return -EINVAL;
+	} else if (drvdata->hdr->size > size) {
+		dev_err(dev, "invalid declared stash size %u (expected: <= %llu)\n",
+			drvdata->hdr->size, size);
+		return -EINVAL;
+	} else if (calc_size > size) {
+		dev_err(dev, "invalid calculated stash size %llu (expected: <= %llu)\n",
+			calc_size, size);
+		return -EINVAL;
+	} else if (drvdata->hdr->count == 0) {
+		dev_info(dev, "bootstage stash has no records\n");
+		return 0;
+	}
+
+	// Set start time to invalid
+	drvdata->start_time_us = 0xFFFFFFFF;
+
+	// Associate names to records, which are placed at the end of the record area
+	for (r = 0, rec = drvdata->records; r < drvdata->hdr->count; r++, rec++) {
+		// Save minimum time, will be used as bootloader enter time
+		if (rec->start_us < drvdata->start_time_us)
+			drvdata->start_time_us = rec->time_us;
+
+		// Save maximum time, will be used as bootloader exit time
+		if (rec->time_us > drvdata->end_time_us)
+			drvdata->end_time_us = rec->time_us;
+
+		if (str_ptr > ((const char *)drvdata->hdr + size)) {
+			dev_err(dev, "name for record %u is corrupted\n", r);
+			return -ENODATA;
+		}
+
+		rec->name = str_ptr;
+		str_ptr += strlen(rec->name) + 1;
+	}
+
+	return 0;
+}
+
+static int bootstage_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct reserved_mem *rmem;
+	struct bootstage_drvdata *drvdata;
+	int ret;
+
+	rmem = of_reserved_mem_lookup(dev->of_node);
+	if (!rmem) {
+		dev_err(dev, "failed to lookup reserved memory\n");
+		return -EINVAL;
+	}
+
+	if (!rmem->size || (rmem->size > ULONG_MAX) ||
+	    (rmem->size < sizeof(struct bootstage_hdr))) {
+		dev_err(dev, "invalid memory region size\n");
+		return -EINVAL;
+	}
+
+	if (!PAGE_ALIGNED(rmem->base) || !PAGE_ALIGNED(rmem->size)) {
+		dev_err(dev, "memory region must be page-aligned\n");
+		return -EINVAL;
+	}
+
+	drvdata = devm_kmalloc(dev, sizeof(*drvdata), GFP_KERNEL);
+	if (!drvdata)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, drvdata);
+
+	drvdata->hdr = devm_memremap(dev, rmem->base, rmem->size, MEMREMAP_WB);
+	if (IS_ERR(drvdata->hdr)) {
+		dev_err(dev, "failed to remap bootstage region\n");
+		return PTR_ERR(drvdata->hdr);
+	}
+
+	drvdata->records =
+		(struct bootstage_record *)((void *)drvdata->hdr + sizeof(struct bootstage_hdr));
+
+	ret = bootstage_parse(dev, drvdata, rmem->size);
+	if (ret)
+		return ret;
+
+	ret = sysfs_create_group(&pdev->dev.kobj, &bootstage_attr_group);
+	if (ret) {
+		dev_err(dev, "failed to create sysfs group\n");
+		return ret;
+	}
+
+	bootstage_debugfs_init(dev, drvdata);
+
+	return 0;
+}
+
+static void bootstage_remove(struct platform_device *pdev)
+{
+	struct bootstage_drvdata *drvdata = platform_get_drvdata(pdev);
+
+	bootstage_debugfs_exit(&pdev->dev, drvdata);
+	sysfs_remove_group(&pdev->dev.kobj, &bootstage_attr_group);
+}
+
+static const struct of_device_id bootstage_of_match[] = {
+	{ .compatible = "bootstage" },
+	{},
+};
+
+static struct platform_driver bootstage_driver = {
+	.probe = bootstage_probe,
+	.remove = bootstage_remove,
+	.driver = {
+		.name = "bootstage",
+		.of_match_table = bootstage_of_match,
+	},
+};
+
+static int __init bootstage_init(void)
+{
+	bootstage_debugfs_dir = debugfs_create_dir(bootstage_driver.driver.name, NULL);
+	return platform_driver_register(&bootstage_driver);
+}
+arch_initcall(bootstage_init);
+
+static void __exit bootstage_exit(void)
+{
+	debugfs_remove_recursive(bootstage_debugfs_dir);
+	platform_driver_unregister(&bootstage_driver);
+}
+module_exit(bootstage_exit)
+
+MODULE_DESCRIPTION("Driver for Bootstage stash.");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Francesco Valla <francesco@valla.it>");
diff --git a/drivers/of/platform.c b/drivers/of/platform.c
index f77cb19973a5..e19b04733584 100644
--- a/drivers/of/platform.c
+++ b/drivers/of/platform.c
@@ -496,6 +496,7 @@ static const struct of_device_id reserved_mem_matches[] = {
 	{ .compatible = "ramoops" },
 	{ .compatible = "nvmem-rmem" },
 	{ .compatible = "google,open-dice" },
+	{ .compatible = "bootstage" },
 	{}
 };
 
-- 
2.49.0


  reply	other threads:[~2025-05-22 23:15 UTC|newest]

Thread overview: 16+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-05-22 22:42 [RFC PATCH 0/1] Add driver for bootstage stash Francesco Valla
2025-05-22 22:42 ` Francesco Valla [this message]
2025-05-23  6:29   ` [PATCH 1/1] drivers: misc: add " Krzysztof Kozlowski
2025-05-23 19:34     ` Rob Landley
2025-05-24  6:16       ` Krzysztof Kozlowski
2025-05-23 19:43     ` Francesco Valla
2025-05-24  6:15       ` Krzysztof Kozlowski
2025-05-23 23:43   ` Bird, Tim
2025-05-24  7:18   ` kernel test robot
2025-05-23  7:04 ` [RFC PATCH 0/1] Add " Geert Uytterhoeven
2025-05-23 20:06   ` Francesco Valla
     [not found] ` <PA4PR08MB604681FF6392B25A19926A11ED98A@PA4PR08MB6046.eurprd08.prod.outlook.com>
2025-05-23  7:34   ` Federico Giovanardi
2025-05-23 19:43     ` Rob Landley
2025-05-23 20:11     ` Francesco Valla
2025-05-24  0:07     ` Bird, Tim
2025-05-24  0:28       ` Bird, Tim

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20250522224223.358881-3-francesco@valla.it \
    --to=francesco@valla.it \
    --cc=linux-embedded@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).