Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ea26407c authored by qctecmdr's avatar qctecmdr Committed by Gerrit - the friendly Code Review server
Browse files

Merge "drivers: qcom: Add support for kernel boot markers"

parents 73b6f2e1 a7785a89
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -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
+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>
@@ -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();

	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 −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