Loading drivers/soc/qcom/Kconfig +9 −0 Original line number Diff line number Diff line Loading @@ -375,6 +375,15 @@ config MSM_BOOT_STATS This figures are reported in mpm sleep clock cycles and have a resolution of 31 bits as 1 bit is used as an overflow check. config MSM_BOOT_TIME_MARKER bool "Use MSM boot time marker reporting" depends on MSM_BOOT_STATS help Use this to mark msm boot kpi for measurement. An instrumentation for boot time measurement. To create an entry, call "place_marker" function. At userspace, write marker name to "/sys/kernel/boot_kpi/kpi_values" config QCOM_QMI_HELPERS tristate depends on ARCH_QCOM || COMPILE_TEST Loading drivers/soc/qcom/boot_stats.c +268 −5 Original line number Diff line number Diff line // SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2013-2019, The Linux Foundation. All rights reserved. * Copyright (c) 2013-2019, 2021 The Linux Foundation. All rights reserved. */ #include <linux/kernel.h> Loading @@ -15,6 +15,15 @@ #include <linux/sched.h> #include <linux/of.h> #include <linux/of_address.h> #include <linux/slab.h> #include <linux/uaccess.h> #include <soc/qcom/boot_stats.h> #define MAX_STRING_LEN 256 #define BOOT_MARKER_MAX_LEN 50 #define MSM_ARCH_TIMER_FREQ 19200000 #define BOOTKPI_BUF_SIZE (2 * PAGE_SIZE) #define TIMER_KHZ 32768 struct boot_stats { uint32_t bootloader_start; Loading @@ -27,6 +36,248 @@ static void __iomem *mpm_counter_base; static uint32_t mpm_counter_freq; static struct boot_stats __iomem *boot_stats; #ifdef CONFIG_MSM_BOOT_TIME_MARKER struct boot_marker { char marker_name[BOOT_MARKER_MAX_LEN]; unsigned long long timer_value; struct list_head list; spinlock_t slock; }; static struct boot_marker boot_marker_list; static struct kobject *bootkpi_obj; static struct attribute_group *attr_grp; unsigned long long msm_timer_get_sclk_ticks(void) { unsigned long long t1, t2; int loop_count = 10; int loop_zero_count = 3; u64 tmp = USEC_PER_SEC; void __iomem *sclk_tick; do_div(tmp, TIMER_KHZ); tmp /= (loop_zero_count-1); sclk_tick = mpm_counter_base; if (!sclk_tick) return -EINVAL; while (loop_zero_count--) { t1 = readl_no_log(sclk_tick); do { udelay(1); t2 = t1; t1 = readl_no_log(sclk_tick); } while ((t2 != t1) && --loop_count); if (!loop_count) { pr_err("boot_stats: SCLK did not stabilize\n"); return 0; } if (t1) break; udelay(tmp); } if (!loop_zero_count) { pr_err("boot_stats: SCLK reads zero\n"); return 0; } return t1; } static void _destroy_boot_marker(const char *name) { struct boot_marker *marker; struct boot_marker *temp_addr; spin_lock(&boot_marker_list.slock); list_for_each_entry_safe(marker, temp_addr, &boot_marker_list.list, list) { if (strnstr(marker->marker_name, name, strlen(marker->marker_name))) { list_del(&marker->list); kfree(marker); } } spin_unlock(&boot_marker_list.slock); } static void _create_boot_marker(const char *name, unsigned long long timer_value) { struct boot_marker *new_boot_marker; pr_debug("%-41s:%llu.%03llu seconds\n", name, timer_value/TIMER_KHZ, ((timer_value % TIMER_KHZ) * 1000) / TIMER_KHZ); new_boot_marker = kmalloc(sizeof(*new_boot_marker), GFP_ATOMIC); if (!new_boot_marker) return; strlcpy(new_boot_marker->marker_name, name, sizeof(new_boot_marker->marker_name)); new_boot_marker->timer_value = timer_value; spin_lock(&boot_marker_list.slock); list_add_tail(&(new_boot_marker->list), &(boot_marker_list.list)); spin_unlock(&boot_marker_list.slock); } static void boot_marker_cleanup(void) { struct boot_marker *marker; struct boot_marker *temp_addr; spin_lock(&boot_marker_list.slock); list_for_each_entry_safe(marker, temp_addr, &boot_marker_list.list, list) { list_del(&marker->list); kfree(marker); } spin_unlock(&boot_marker_list.slock); } void place_marker(const char *name) { _create_boot_marker((char *)name, msm_timer_get_sclk_ticks()); } EXPORT_SYMBOL(place_marker); void destroy_marker(const char *name) { _destroy_boot_marker((char *) name); } EXPORT_SYMBOL(destroy_marker); static ssize_t bootkpi_reader(struct kobject *obj, struct kobj_attribute *attr, char *user_buffer) { int rc = 0; char *buf; int temp = 0; struct boot_marker *marker; buf = kmalloc(BOOTKPI_BUF_SIZE, GFP_KERNEL); if (!buf) return -ENOMEM; spin_lock(&boot_marker_list.slock); list_for_each_entry(marker, &boot_marker_list.list, list) { WARN_ON((BOOTKPI_BUF_SIZE - temp) <= 0); temp += scnprintf(buf + temp, BOOTKPI_BUF_SIZE - temp, "%-41s:%llu.%03llu seconds\n", marker->marker_name, marker->timer_value/TIMER_KHZ, (((marker->timer_value % TIMER_KHZ) * 1000) / TIMER_KHZ)); } spin_unlock(&boot_marker_list.slock); rc = scnprintf(user_buffer, temp + 1, "%s\n", buf); kfree(buf); return rc; } static ssize_t bootkpi_writer(struct kobject *obj, struct kobj_attribute *attr, const char *user_buffer, size_t count) { int rc = 0; char buf[MAX_STRING_LEN]; if (count >= MAX_STRING_LEN) return -EINVAL; rc = scnprintf(buf, count, "%s", user_buffer); if (rc < 0) return rc; buf[rc] = '\0'; place_marker(buf); return rc; } static ssize_t mpm_timer_read(struct kobject *obj, struct kobj_attribute *attr, char *user_buffer) { unsigned long long timer_value; char buf[100]; int temp = 0; timer_value = msm_timer_get_sclk_ticks(); temp = scnprintf(buf, sizeof(buf), "%llu.%03llu seconds\n", timer_value/TIMER_KHZ, (((timer_value % TIMER_KHZ) * 1000) / TIMER_KHZ)); return scnprintf(user_buffer, temp + 1, "%s\n", buf); } static struct kobj_attribute kpi_values_attribute = __ATTR(kpi_values, 0644, bootkpi_reader, bootkpi_writer); static struct kobj_attribute mpm_timer_attribute = __ATTR(mpm_timer, 0444, mpm_timer_read, NULL); static struct attribute *attrs[] = { &kpi_values_attribute.attr, &mpm_timer_attribute.attr, NULL, }; static int bootkpi_sysfs_init(void) { int ret; attr_grp = kzalloc(sizeof(struct attribute_group), GFP_KERNEL); if (!attr_grp) return -ENOMEM; bootkpi_obj = kobject_create_and_add("boot_kpi", kernel_kobj); if (!bootkpi_obj) { pr_err("boot_marker: Could not create kobject\n"); ret = -ENOMEM; goto kobj_err; } attr_grp->attrs = attrs; ret = sysfs_create_group(bootkpi_obj, attr_grp); if (ret) { pr_err("boot_marker: Could not create sysfs group\n"); goto err; } return 0; err: kobject_del(bootkpi_obj); kobj_err: kfree(attr_grp); return ret; } static int init_bootkpi(void) { int ret = 0; ret = bootkpi_sysfs_init(); if (ret) return ret; INIT_LIST_HEAD(&boot_marker_list.list); spin_lock_init(&boot_marker_list.slock); return 0; } static void exit_bootkpi(void) { boot_marker_cleanup(); sysfs_remove_group(bootkpi_obj, attr_grp); kobject_del(bootkpi_obj); kfree(attr_grp); } #endif static int mpm_parse_dt(void) { struct device_node *np_imem, *np_mpm2; Loading Loading @@ -96,16 +347,28 @@ static int __init boot_stats_init(void) return -ENODEV; print_boot_stats(); if (boot_marker_enabled()) { ret = init_bootkpi(); if (ret) { pr_err("boot_stats: BootKPI init failed %d\n"); return ret; } } else { iounmap(boot_stats); iounmap(mpm_counter_base); } return 0; } module_init(boot_stats_init); subsys_initcall(boot_stats_init); static void __exit boot_stats_exit(void) { if (boot_marker_enabled()) { exit_bootkpi(); iounmap(boot_stats); iounmap(mpm_counter_base); } } module_exit(boot_stats_exit) Loading include/soc/qcom/boot_stats.h 0 → 100644 +15 −0 Original line number Diff line number Diff line /* SPDX-License-Identifier: GPL-2.0-only * Copyright (c) 2011-2021, The Linux Foundation. All rights reserved. */ #ifdef CONFIG_MSM_BOOT_TIME_MARKER void place_marker(const char *name); void destroy_marker(const char *name); int boot_marker_enabled(void) { return 1; } #else static int init_bootkpi(void) { return 0; } static inline void exit_bootkpi(void) { }; static inline void place_marker(char *name) { }; static inline void destroy_marker(const char *name) { }; static int boot_marker_enabled(void) { return 0; } #endif Loading
drivers/soc/qcom/Kconfig +9 −0 Original line number Diff line number Diff line Loading @@ -375,6 +375,15 @@ config MSM_BOOT_STATS This figures are reported in mpm sleep clock cycles and have a resolution of 31 bits as 1 bit is used as an overflow check. config MSM_BOOT_TIME_MARKER bool "Use MSM boot time marker reporting" depends on MSM_BOOT_STATS help Use this to mark msm boot kpi for measurement. An instrumentation for boot time measurement. To create an entry, call "place_marker" function. At userspace, write marker name to "/sys/kernel/boot_kpi/kpi_values" config QCOM_QMI_HELPERS tristate depends on ARCH_QCOM || COMPILE_TEST Loading
drivers/soc/qcom/boot_stats.c +268 −5 Original line number Diff line number Diff line // SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2013-2019, The Linux Foundation. All rights reserved. * Copyright (c) 2013-2019, 2021 The Linux Foundation. All rights reserved. */ #include <linux/kernel.h> Loading @@ -15,6 +15,15 @@ #include <linux/sched.h> #include <linux/of.h> #include <linux/of_address.h> #include <linux/slab.h> #include <linux/uaccess.h> #include <soc/qcom/boot_stats.h> #define MAX_STRING_LEN 256 #define BOOT_MARKER_MAX_LEN 50 #define MSM_ARCH_TIMER_FREQ 19200000 #define BOOTKPI_BUF_SIZE (2 * PAGE_SIZE) #define TIMER_KHZ 32768 struct boot_stats { uint32_t bootloader_start; Loading @@ -27,6 +36,248 @@ static void __iomem *mpm_counter_base; static uint32_t mpm_counter_freq; static struct boot_stats __iomem *boot_stats; #ifdef CONFIG_MSM_BOOT_TIME_MARKER struct boot_marker { char marker_name[BOOT_MARKER_MAX_LEN]; unsigned long long timer_value; struct list_head list; spinlock_t slock; }; static struct boot_marker boot_marker_list; static struct kobject *bootkpi_obj; static struct attribute_group *attr_grp; unsigned long long msm_timer_get_sclk_ticks(void) { unsigned long long t1, t2; int loop_count = 10; int loop_zero_count = 3; u64 tmp = USEC_PER_SEC; void __iomem *sclk_tick; do_div(tmp, TIMER_KHZ); tmp /= (loop_zero_count-1); sclk_tick = mpm_counter_base; if (!sclk_tick) return -EINVAL; while (loop_zero_count--) { t1 = readl_no_log(sclk_tick); do { udelay(1); t2 = t1; t1 = readl_no_log(sclk_tick); } while ((t2 != t1) && --loop_count); if (!loop_count) { pr_err("boot_stats: SCLK did not stabilize\n"); return 0; } if (t1) break; udelay(tmp); } if (!loop_zero_count) { pr_err("boot_stats: SCLK reads zero\n"); return 0; } return t1; } static void _destroy_boot_marker(const char *name) { struct boot_marker *marker; struct boot_marker *temp_addr; spin_lock(&boot_marker_list.slock); list_for_each_entry_safe(marker, temp_addr, &boot_marker_list.list, list) { if (strnstr(marker->marker_name, name, strlen(marker->marker_name))) { list_del(&marker->list); kfree(marker); } } spin_unlock(&boot_marker_list.slock); } static void _create_boot_marker(const char *name, unsigned long long timer_value) { struct boot_marker *new_boot_marker; pr_debug("%-41s:%llu.%03llu seconds\n", name, timer_value/TIMER_KHZ, ((timer_value % TIMER_KHZ) * 1000) / TIMER_KHZ); new_boot_marker = kmalloc(sizeof(*new_boot_marker), GFP_ATOMIC); if (!new_boot_marker) return; strlcpy(new_boot_marker->marker_name, name, sizeof(new_boot_marker->marker_name)); new_boot_marker->timer_value = timer_value; spin_lock(&boot_marker_list.slock); list_add_tail(&(new_boot_marker->list), &(boot_marker_list.list)); spin_unlock(&boot_marker_list.slock); } static void boot_marker_cleanup(void) { struct boot_marker *marker; struct boot_marker *temp_addr; spin_lock(&boot_marker_list.slock); list_for_each_entry_safe(marker, temp_addr, &boot_marker_list.list, list) { list_del(&marker->list); kfree(marker); } spin_unlock(&boot_marker_list.slock); } void place_marker(const char *name) { _create_boot_marker((char *)name, msm_timer_get_sclk_ticks()); } EXPORT_SYMBOL(place_marker); void destroy_marker(const char *name) { _destroy_boot_marker((char *) name); } EXPORT_SYMBOL(destroy_marker); static ssize_t bootkpi_reader(struct kobject *obj, struct kobj_attribute *attr, char *user_buffer) { int rc = 0; char *buf; int temp = 0; struct boot_marker *marker; buf = kmalloc(BOOTKPI_BUF_SIZE, GFP_KERNEL); if (!buf) return -ENOMEM; spin_lock(&boot_marker_list.slock); list_for_each_entry(marker, &boot_marker_list.list, list) { WARN_ON((BOOTKPI_BUF_SIZE - temp) <= 0); temp += scnprintf(buf + temp, BOOTKPI_BUF_SIZE - temp, "%-41s:%llu.%03llu seconds\n", marker->marker_name, marker->timer_value/TIMER_KHZ, (((marker->timer_value % TIMER_KHZ) * 1000) / TIMER_KHZ)); } spin_unlock(&boot_marker_list.slock); rc = scnprintf(user_buffer, temp + 1, "%s\n", buf); kfree(buf); return rc; } static ssize_t bootkpi_writer(struct kobject *obj, struct kobj_attribute *attr, const char *user_buffer, size_t count) { int rc = 0; char buf[MAX_STRING_LEN]; if (count >= MAX_STRING_LEN) return -EINVAL; rc = scnprintf(buf, count, "%s", user_buffer); if (rc < 0) return rc; buf[rc] = '\0'; place_marker(buf); return rc; } static ssize_t mpm_timer_read(struct kobject *obj, struct kobj_attribute *attr, char *user_buffer) { unsigned long long timer_value; char buf[100]; int temp = 0; timer_value = msm_timer_get_sclk_ticks(); temp = scnprintf(buf, sizeof(buf), "%llu.%03llu seconds\n", timer_value/TIMER_KHZ, (((timer_value % TIMER_KHZ) * 1000) / TIMER_KHZ)); return scnprintf(user_buffer, temp + 1, "%s\n", buf); } static struct kobj_attribute kpi_values_attribute = __ATTR(kpi_values, 0644, bootkpi_reader, bootkpi_writer); static struct kobj_attribute mpm_timer_attribute = __ATTR(mpm_timer, 0444, mpm_timer_read, NULL); static struct attribute *attrs[] = { &kpi_values_attribute.attr, &mpm_timer_attribute.attr, NULL, }; static int bootkpi_sysfs_init(void) { int ret; attr_grp = kzalloc(sizeof(struct attribute_group), GFP_KERNEL); if (!attr_grp) return -ENOMEM; bootkpi_obj = kobject_create_and_add("boot_kpi", kernel_kobj); if (!bootkpi_obj) { pr_err("boot_marker: Could not create kobject\n"); ret = -ENOMEM; goto kobj_err; } attr_grp->attrs = attrs; ret = sysfs_create_group(bootkpi_obj, attr_grp); if (ret) { pr_err("boot_marker: Could not create sysfs group\n"); goto err; } return 0; err: kobject_del(bootkpi_obj); kobj_err: kfree(attr_grp); return ret; } static int init_bootkpi(void) { int ret = 0; ret = bootkpi_sysfs_init(); if (ret) return ret; INIT_LIST_HEAD(&boot_marker_list.list); spin_lock_init(&boot_marker_list.slock); return 0; } static void exit_bootkpi(void) { boot_marker_cleanup(); sysfs_remove_group(bootkpi_obj, attr_grp); kobject_del(bootkpi_obj); kfree(attr_grp); } #endif static int mpm_parse_dt(void) { struct device_node *np_imem, *np_mpm2; Loading Loading @@ -96,16 +347,28 @@ static int __init boot_stats_init(void) return -ENODEV; print_boot_stats(); if (boot_marker_enabled()) { ret = init_bootkpi(); if (ret) { pr_err("boot_stats: BootKPI init failed %d\n"); return ret; } } else { iounmap(boot_stats); iounmap(mpm_counter_base); } return 0; } module_init(boot_stats_init); subsys_initcall(boot_stats_init); static void __exit boot_stats_exit(void) { if (boot_marker_enabled()) { exit_bootkpi(); iounmap(boot_stats); iounmap(mpm_counter_base); } } module_exit(boot_stats_exit) Loading
include/soc/qcom/boot_stats.h 0 → 100644 +15 −0 Original line number Diff line number Diff line /* SPDX-License-Identifier: GPL-2.0-only * Copyright (c) 2011-2021, The Linux Foundation. All rights reserved. */ #ifdef CONFIG_MSM_BOOT_TIME_MARKER void place_marker(const char *name); void destroy_marker(const char *name); int boot_marker_enabled(void) { return 1; } #else static int init_bootkpi(void) { return 0; } static inline void exit_bootkpi(void) { }; static inline void place_marker(char *name) { }; static inline void destroy_marker(const char *name) { }; static int boot_marker_enabled(void) { return 0; } #endif