drivers: qcom: Add support for kernel boot markers
The existing boot_stats module captures the timestamps of various events before the kernel start, like bootloader start, kernel load time, etc. Extend support to capture timestamps of events after kernel starts execution. Users can call place_marker from different points of execution to capture the timestamp. Expose a sysfs node to read the complete list of events captured by the boot_stats module. Change-Id: Icbc2341b34fe7e2559a37c092a5ab74919703184 Signed-off-by: Shreyas K K <shrekk@codeaurora.org>
This commit is contained in:
parent
5b540967b7
commit
a7785a897c
@ -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
|
||||
|
@ -1,6 +1,6 @@
|
||||
// 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>
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -96,16 +347,28 @@ static int __init boot_stats_init(void)
|
||||
return -ENODEV;
|
||||
|
||||
print_boot_stats();
|
||||
|
||||
iounmap(boot_stats);
|
||||
iounmap(mpm_counter_base);
|
||||
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)
|
||||
|
||||
|
15
include/soc/qcom/boot_stats.h
Normal file
15
include/soc/qcom/boot_stats.h
Normal file
@ -0,0 +1,15 @@
|
||||
/* 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…
Reference in New Issue
Block a user