[PATCH 1/1] drivers: misc: add driver for bootstage stash
Francesco Valla <[email protected]> Fri, 23 May 2025 00:42:24 +0200
| Newsgroups | org.kernel.vger.linux-embedded |
|---|---|
| Message-ID | <[email protected]> |
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 <[email protected]> --- .../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 <[email protected]> + +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 <[email protected]> +L: [email protected] +S: Maintained +F: Documentation/devicetree/bindings/reserved-memory/bootstage.yaml +F: drivers/misc/bootstage.c + BONDING DRIVER M: Jay Vosburgh <[email protected]> L: [email protected] 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 <[email protected]> + * + * 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 <[email protected]>"); 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