/* sec_bootstat.c * * Copyright (C) 2014 Samsung Electronics * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include #include #include #include #include #include #include #include #include #include #include "sec_bootstat.h" extern void register_hook_bootstat(void (*func)(const char *buf)); extern void sec_bootstat_get_cpuinfo(int *freq, int *online); extern void sec_bootstat_get_thermal(int *temp); extern u64 exynos_get_mct_start(void); static u32 mct_start; /* timestamps at /proc/boot_stat * freq[3] : cluster0 / cluster1 / cluster2 * temp[6] : cluster0 / cluster1 / cluster2 / g3d / isp / npu */ #if IS_BUILTIN(CONFIG_SEC_BOOTSTAT) static struct boot_event sec_boot_initcall[] = { {"pure",}, {"core",}, {"postcore",}, {"arch",}, {"subsys",}, {"fs",}, {"device",}, {"late",}, }; #endif static struct boot_event boot_events[] = { {"!@Boot: start init process",}, {"!@Boot: Begin of preload()",}, {"!@Boot: End of preload()",}, {"!@Boot: Entered the Android system server!",}, {"!@Boot: Start PackageManagerService",}, {"!@Boot: End PackageManagerService",}, {"!@Boot: Loop forever",}, {"!@Boot: performEnableScreen",}, {"!@Boot: Enabling Screen!",}, {"!@Boot: bootcomplete", true,}, {"!@Boot: Voice SVC is acquired",}, {"!@Boot: Data SVC is acquired",}, {"!@Boot_SVC : PhoneApp OnCrate",}, {"!@Boot_DEBUG: start networkManagement",}, {"!@Boot_DEBUG: end networkManagement",}, {"!@Boot_SVC : RIL_UNSOL_RIL_CONNECTED",}, {"!@Boot_SVC : setRadioPower on",}, {"!@Boot_SVC : setUiccSubscription",}, {"!@Boot_SVC : SIM onAllRecordsLoaded",}, {"!@Boot_SVC : RUIM onAllRecordsLoaded",}, {"!@Boot_SVC : setupDataCall",}, {"!@Boot_SVC : Response setupDataCall",}, {"!@Boot_SVC : onDataConnectionAttached",}, {"!@Boot_SVC : IMSI Ready",}, {"!@Boot_SVC : completeConnection",}, {"!@Boot_DEBUG: finishUserUnlockedCompleted",}, {"!@Boot: setIconVisibility: ims_volte: [SHOW]",}, {"!@Boot_DEBUG: Launcher.onCreate()",}, {"!@Boot_DEBUG: Launcher.onResume()",}, {"!@Boot_DEBUG: Launcher.LoaderTask.run() start",}, {"!@Boot_DEBUG: Launcher - FinishFirstBind",}, #ifdef CONFIG_SEC_FACTORY {"!@Boot: Factory Process [Boot Completed]",}, #endif /* CONFIG_SEC_FACTORY */ }; #define MAX_LENGTH_OF_BOOTING_LOG 90 #define DELAY_TIME_EBS 10000 #define MAX_EVENTS_EBS 100 struct enhanced_boot_time { struct list_head next; char buf[MAX_LENGTH_OF_BOOTING_LOG]; unsigned int time; int freq[3]; int online; }; #define MAX_LENGTH_OF_SYSTEMSERVER_LOG 90 struct systemserver_init_time_entry { struct list_head next; char buf[MAX_LENGTH_OF_SYSTEMSERVER_LOG]; }; static bool bootcompleted; static bool ebs_finished; unsigned long long boot_complete_time; static int events_ebs; #if IS_BUILTIN(CONFIG_SEC_BOOTSTAT) LIST_HEAD(device_init_time_list); #endif LIST_HEAD(systemserver_init_time_list); LIST_HEAD(enhanced_boot_time_list); #if IS_BUILTIN(CONFIG_SEC_BOOTSTAT) void sec_bootstat_add_initcall(const char *s) { size_t i = 0; unsigned long long t = 0; for (i = 0; i < ARRAY_SIZE(sec_boot_initcall); i++) { if (!strcmp(s, sec_boot_initcall[i].string)) { t = local_clock(); do_div(t, 1000000); sec_boot_initcall[i].time = (unsigned int)t; break; } } } #endif static DEFINE_RAW_SPINLOCK(ebs_list_lock); void sec_enhanced_boot_stat_record(const char *buf) { unsigned long long t = 0; struct enhanced_boot_time *entry; unsigned long flags; entry = kmalloc(sizeof(*entry), GFP_KERNEL); if (!entry) return; strncpy(entry->buf, buf, MAX_LENGTH_OF_BOOTING_LOG); entry->buf[MAX_LENGTH_OF_BOOTING_LOG-1] = '\0'; t = local_clock(); do_div(t, 1000000); entry->time = (unsigned int)t; sec_bootstat_get_cpuinfo(entry->freq, &entry->online); raw_spin_lock_irqsave(&ebs_list_lock, flags); list_add(&entry->next, &enhanced_boot_time_list); events_ebs++; raw_spin_unlock_irqrestore(&ebs_list_lock, flags); } static int prev; void sec_bootstat_add(const char *c) { size_t i = 0; unsigned long long t = 0; if (bootcompleted && !ebs_finished) { t = local_clock(); do_div(t, 1000000); if ((t - boot_complete_time) >= DELAY_TIME_EBS) ebs_finished = true; } // Collect Boot_EBS from java side if (!ebs_finished && events_ebs < MAX_EVENTS_EBS) { if (!strncmp(c, "!@Boot_EBS: ", 12)) { sec_enhanced_boot_stat_record(c + 12); return; } else if (!strncmp(c, "!@Boot_EBS_", 11)) { sec_enhanced_boot_stat_record(c); return; } } if (!bootcompleted && !strncmp(c, "!@Boot_SystemServer: ", 21)) { struct systemserver_init_time_entry *entry; entry = kmalloc(sizeof(*entry), GFP_KERNEL); if (!entry) return; strncpy(entry->buf, c + 21, MAX_LENGTH_OF_SYSTEMSERVER_LOG); entry->buf[MAX_LENGTH_OF_SYSTEMSERVER_LOG-1] = '\0'; list_add(&entry->next, &systemserver_init_time_list); return; } for (i = 0; i < ARRAY_SIZE(boot_events); i++) { if (!strncmp(c, boot_events[i].string, strlen(boot_events[i].string))) { if (boot_events[i].time == 0) { boot_events[prev].order = i; prev = i; t = local_clock(); do_div(t, 1000000); boot_events[i].time = (unsigned int)t; sec_bootstat_get_cpuinfo(boot_events[i].freq, &boot_events[i].online); sec_bootstat_get_thermal(boot_events[i].temp); } // careful check bootcomplete message index 9 if (i == 9) { bootcompleted = true; boot_complete_time = local_clock(); do_div(boot_complete_time, 1000000); } break; } } } void print_format(struct boot_event *data, struct seq_file *m, int index, int delta) { seq_printf(m, "%-50s %6u %6u %6d %4d %4d %4d L%d%d%d%d M%d%d%d B%d %2d %2d %2d %2d %2d %2d\n", data[index].string, data[index].time + mct_start, data[index].time, delta, data[index].freq[0] / 1000, data[index].freq[1] / 1000, data[index].freq[2] / 1000, data[index].online & 1, (data[index].online >> 1) & 1, (data[index].online >> 2) & 1, (data[index].online >> 3) & 1, (data[index].online >> 4) & 1, (data[index].online >> 5) & 1, (data[index].online >> 6) & 1, (data[index].online >> 7) & 1, data[index].temp[0], data[index].temp[1], data[index].temp[2], data[index].temp[3], data[index].temp[4], data[index].temp[5] ); } static int sec_boot_stat_proc_show(struct seq_file *m, void *v) { size_t i = 0; unsigned int last_time = 0; #if IS_BUILTIN(CONFIG_SEC_BOOTSTAT) struct device_init_time_entry *entry; #endif struct systemserver_init_time_entry *systemserver_entry; seq_puts(m, "boot event time ktime delta f_c0 f_c1 f_c2 online_mask B M L G I N\n"); seq_puts(m, "----------------------------------------------------------------------------------------------------------------------\n"); seq_puts(m, "BOOTLOADER - KERNEL\n"); seq_puts(m, "----------------------------------------------------------------------------------------------------------------------\n"); seq_printf(m, "MCT is initialized in bl2 %6u %6u %6u\n", 0, 0, 0); seq_printf(m, "start kernel timer %6u %6u %6u\n", mct_start, 0, mct_start); #if IS_BUILTIN(CONFIG_SEC_BOOTSTAT) for (i = 0; i < ARRAY_SIZE(sec_boot_initcall); i++) { print_format(sec_boot_initcall, m, i, sec_boot_initcall[i].time - last_time); last_time = sec_boot_initcall[i].time; } #endif seq_puts(m, "----------------------------------------------------------------------------------------------------------------------\n"); seq_puts(m, "FRAMEWORK\n"); seq_puts(m, "----------------------------------------------------------------------------------------------------------------------\n"); i = 0; do { if (boot_events[i].time != 0) { print_format(boot_events, m, i, boot_events[i].time - last_time); last_time = boot_events[i].time; } if (i != boot_events[i].order) i = boot_events[i].order; else break; } while (i > 0 && i < ARRAY_SIZE(boot_events)); #if IS_BUILTIN(CONFIG_SEC_BOOTSTAT) seq_puts(m, "----------------------------------------------------------------------------------------------------------------------\n"); seq_printf(m, "device init time over %d ms\n\n", DEVICE_INIT_TIME_100MS / 1000); list_for_each_entry (entry, &device_init_time_list, next) seq_printf(m, "%-20s : %lld usces\n", entry->buf, entry->duration); #endif seq_puts(m, "----------------------------------------------------------------------------------------------------------------------\n"); seq_puts(m, "SystemServer services that took long time\n\n"); list_for_each_entry (systemserver_entry, &systemserver_init_time_list, next) seq_printf(m, "%s\n", systemserver_entry->buf); return 0; } int __read_mostly boot_time_bl1; int __read_mostly boot_time_bl2; int __read_mostly boot_time_bl3; module_param(boot_time_bl1, int, 0440); module_param(boot_time_bl2, int, 0440); module_param(boot_time_bl3, int, 0440); static int sec_enhanced_boot_stat_proc_show(struct seq_file *m, void *v) { #if IS_BUILTIN(CONFIG_SEC_BOOTSTAT) size_t i = 0; #endif unsigned int last_time = 0; struct enhanced_boot_time *entry; seq_printf(m, "%-90s %6s %6s %6s %4s %4s %4s\n", "Boot Events", "time", "ktime", "delta", "f_c0", "f_c1", "f_c2"); seq_puts(m, "------------------------------------------------------------------------------------------------------------------------------\n"); seq_puts(m, "BOOTLOADER - KERNEL\n"); seq_puts(m, "------------------------------------------------------------------------------------------------------------------------------\n"); seq_printf(m, "%-90s %6u %6u %6u %4d %4d %4d\n", "!@Boot_EBS_B: MCT_is_initialized", 0, 0, 0, 0, 0, 0); seq_printf(m, "%-90s %6u %6u %6u %4d %4d %4d\n", "!@Boot_EBS_B: boot_time_bl1", boot_time_bl1, 0, boot_time_bl1, 0, 0, 0); seq_printf(m, "%-90s %6u %6u %6u %4d %4d %4d\n", "!@Boot_EBS_B: boot_time_bl2", boot_time_bl2, 0, boot_time_bl2 - boot_time_bl1, 0, 0, 0); seq_printf(m, "%-90s %6u %6u %6u %4d %4d %4d\n", "!@Boot_EBS_B: boot_time_bl3", boot_time_bl3, 0, boot_time_bl3 - boot_time_bl2, 0, 0, 0); seq_printf(m, "%-90s %6u %6u %6u %4d %4d %4d\n", "!@Boot_EBS_B: start_kernel_timer", mct_start, 0, mct_start - boot_time_bl3, 0, 0, 0); #if IS_BUILTIN(CONFIG_SEC_BOOTSTAT) for (i = 0; i < ARRAY_SIZE(boot_initcall); i++) { seq_printf(m, "%-90s %6u %6u %6u %4d %4d\n", boot_initcall[i].string, boot_initcall[i].time + mct_start, boot_initcall[i].time, boot_initcall[i].time - last_time, boot_initcall[i].freq[0] / 1000, boot_initcall[i].freq[1] / 1000); last_time = boot_initcall[i].time; } #endif seq_puts(m, "------------------------------------------------------------------------------------------------------------------------------\n"); seq_puts(m, "FRAMEWORK\n"); seq_puts(m, "------------------------------------------------------------------------------------------------------------------------------\n"); list_for_each_entry_reverse (entry, &enhanced_boot_time_list, next) { if (entry->buf[0] == '!') { seq_printf(m, "%-90s %6u %6u %6u %4d %4d %4d\n", entry->buf, entry->time + mct_start, entry->time, entry->time - last_time, entry->freq[0] / 1000, entry->freq[1] / 1000, entry->freq[2] / 1000); last_time = entry->time; } else { seq_printf(m, "%-90s %6u %6u %11d %4d %4d\n", entry->buf, entry->time + mct_start, entry->time, entry->freq[0] / 1000, entry->freq[1] / 1000, entry->freq[2] / 1000); } } return 0; } static int sec_boot_stat_proc_open(struct inode *inode, struct file *file) { return single_open(file, sec_boot_stat_proc_show, NULL); } static int sec_enhanced_boot_stat_proc_open(struct inode *inode, struct file *file) { return single_open(file, sec_enhanced_boot_stat_proc_show, NULL); } static const struct proc_ops sec_boot_stat_proc_fops = { .proc_open = sec_boot_stat_proc_open, .proc_read = seq_read, .proc_lseek = seq_lseek, .proc_release = single_release, }; static const struct proc_ops sec_enhanced_boot_stat_proc_fops = { .proc_open = sec_enhanced_boot_stat_proc_open, .proc_read = seq_read, .proc_lseek = seq_lseek, .proc_release = single_release, }; static ssize_t store_boot_stat(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { unsigned long long t = 0; // Collect Boot_EBS from native side if (!ebs_finished && events_ebs < MAX_EVENTS_EBS) { if (!strncmp(buf, "!@Boot_EBS: ", 12)) { sec_enhanced_boot_stat_record(buf + 12); return count; } else if (!strncmp(buf, "!@Boot_EBS_", 11)) { sec_enhanced_boot_stat_record(buf); return count; } } if (!strncmp(buf, "!@Boot: start init process", 26)) { t = local_clock(); do_div(t, 1000000); boot_events[0].time = (unsigned int)t; sec_bootstat_get_cpuinfo(boot_events[0].freq, &boot_events[0].online); sec_bootstat_get_thermal(boot_events[0].temp); } return count; } static DEVICE_ATTR(boot_stat, 0220, NULL, store_boot_stat); static int __init_or_module sec_bootstat_init(void) { struct proc_dir_entry *entry; struct proc_dir_entry *enhanced_entry; struct device *dev; mct_start = (u32)(exynos_get_mct_start() & 0xFFFFFFFF); // proc entry = proc_create("boot_stat", S_IRUGO, NULL, &sec_boot_stat_proc_fops); if (!entry) return -ENOMEM; enhanced_entry = proc_create("enhanced_boot_stat", S_IRUGO, NULL, &sec_enhanced_boot_stat_proc_fops); if (!enhanced_entry) return -ENOMEM; // sysfs dev = sec_device_create(NULL, "bsp"); BUG_ON(!dev); if (IS_ERR(dev)) pr_err("%s:Failed to create devce\n", __func__); if (device_create_file(dev, &dev_attr_boot_stat) < 0) pr_err("%s: Failed to create device file\n", __func__); register_hook_bootstat(sec_bootstat_add); return 0; } module_init(sec_bootstat_init); MODULE_DESCRIPTION("Samsung boot-stat driver"); MODULE_LICENSE("GPL v2");