/* Copyright (c) 2016, 2019, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MAX_STRING_LEN 256 #define BOOT_MARKER_MAX_LEN 40 #define MSM_ARCH_TIMER_FREQ 19200000 struct boot_marker { char marker_name[BOOT_MARKER_MAX_LEN]; unsigned long long int timer_value; struct list_head list; spinlock_t slock; }; static struct dentry *dent_bkpi, *dent_bkpi_status, *dent_mpm_timer; static struct boot_marker boot_marker_list; 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 int 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 set_bootloader_stats(void) { _create_boot_marker("M - ABL Start - ", readl_relaxed(&boot_stats->bootloader_start)); _create_boot_marker("M - ABL End - ", readl_relaxed(&boot_stats->bootloader_end)); } 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 inline u64 get_time_in_msec(u64 counter) { counter *= MSEC_PER_SEC; do_div(counter, MSM_ARCH_TIMER_FREQ); return counter; } void measure_wake_up_time(void) { u64 wake_up_time, deep_sleep_exit_time, current_time; char wakeup_marker[50] = {0,}; current_time = arch_counter_get_cntvct(); deep_sleep_exit_time = get_sleep_exit_time(); if (deep_sleep_exit_time) { wake_up_time = current_time - deep_sleep_exit_time; wake_up_time = get_time_in_msec(wake_up_time); pr_debug("Current= %llu, wakeup=%llu, kpi=%llu msec\n", current_time, deep_sleep_exit_time, wake_up_time); snprintf(wakeup_marker, sizeof(wakeup_marker), "M - STR Wakeup : %llu ms", wake_up_time); destroy_marker("M - STR Wakeup"); place_marker(wakeup_marker); } else destroy_marker("M - STR Wakeup"); } EXPORT_SYMBOL(measure_wake_up_time); static ssize_t bootkpi_reader(struct file *fp, char __user *user_buffer, size_t count, loff_t *position) { int rc = 0; char *buf; int temp = 0; struct boot_marker *marker; buf = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!buf) return -ENOMEM; spin_lock(&boot_marker_list.slock); list_for_each_entry(marker, &boot_marker_list.list, list) { temp += scnprintf(buf + temp, PAGE_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 = simple_read_from_buffer(user_buffer, count, position, buf, temp); kfree(buf); return rc; } static ssize_t bootkpi_writer(struct file *fp, const char __user *user_buffer, size_t count, loff_t *position) { int rc = 0; char buf[MAX_STRING_LEN]; if (count > MAX_STRING_LEN) return -EINVAL; rc = simple_write_to_buffer(buf, sizeof(buf) - 1, position, user_buffer, count); if (rc < 0) return rc; buf[rc] = '\0'; place_marker(buf); return rc; } static int bootkpi_open(struct inode *inode, struct file *file) { return 0; } static const struct file_operations fops_bkpi = { .owner = THIS_MODULE, .open = bootkpi_open, .read = bootkpi_reader, .write = bootkpi_writer, }; static ssize_t mpm_timer_read(struct file *fp, char __user *user_buffer, size_t count, loff_t *position) { unsigned long long int timer_value; int rc = 0; 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)); rc = simple_read_from_buffer(user_buffer, count, position, buf, temp); return rc; } static int mpm_timer_open(struct inode *inode, struct file *file) { return 0; } static int mpm_timer_mmap(struct file *file, struct vm_area_struct *vma) { phys_addr_t addr = msm_timer_get_pa(); if (vma->vm_flags & VM_WRITE) return -EPERM; vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); return vm_iomap_memory(vma, addr, PAGE_SIZE); } static const struct file_operations fops_mpm_timer = { .owner = THIS_MODULE, .open = mpm_timer_open, .read = mpm_timer_read, .mmap = mpm_timer_mmap, }; static int __init init_bootkpi(void) { dent_bkpi = debugfs_create_dir("bootkpi", NULL); if (IS_ERR_OR_NULL(dent_bkpi)) return -ENODEV; dent_bkpi_status = debugfs_create_file_unsafe("kpi_values", 0666, dent_bkpi, NULL, &fops_bkpi); if (IS_ERR_OR_NULL(dent_bkpi_status)) { debugfs_remove(dent_bkpi); dent_bkpi = NULL; pr_err("boot_marker: Could not create 'kpi_values' debugfs file\n"); return -ENODEV; } dent_mpm_timer = debugfs_create_file("mpm_timer", 0444, dent_bkpi, NULL, &fops_mpm_timer); if (IS_ERR_OR_NULL(dent_mpm_timer)) { debugfs_remove(dent_bkpi_status); dent_bkpi_status = NULL; debugfs_remove(dent_bkpi); dent_bkpi = NULL; pr_err("boot_marker: Could not create 'mpm_timer' debugfs file\n"); return -ENODEV; } INIT_LIST_HEAD(&boot_marker_list.list); spin_lock_init(&boot_marker_list.slock); set_bootloader_stats(); return 0; } subsys_initcall(init_bootkpi); static void __exit exit_bootkpi(void) { struct boot_marker *marker; struct boot_marker *temp_addr; debugfs_remove_recursive(dent_bkpi); 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); boot_stats_exit(); } module_exit(exit_bootkpi); MODULE_DESCRIPTION("MSM boot key performance indicators"); MODULE_LICENSE("GPL v2");