// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2020 MediaTek Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include /* Define */ #define BOOT_STR_SIZE 256 #define BUF_COUNT 12 #define LOGS_PER_BUF 80 #define MSG_SIZE 128 #ifdef CONFIG_BOOTPROF_THRESHOLD_MS #define BOOTPROF_THRESHOLD (CONFIG_BOOTPROF_THRESHOLD_MS*1000000) #else #define BOOTPROF_THRESHOLD 15000000 #endif struct log_t { /* task cmdline for first 16 bytes * and boot event for the rest */ char *comm_event; pid_t pid; u64 timestamp; }; /** * Data structures to store tracepoints information */ struct tracepoints_table { const char *name; void *func; struct tracepoint *tp; void *data; bool init; }; /** * Data structures to store initcall start time info */ struct initcall_list_t { pid_t pid; pid_t tid; u64 timestamp; struct list_head dev_entry; }; static void tp_deinit(void); /* Parameters */ static struct log_t *bootprof[BUF_COUNT]; static unsigned long log_count; static DEFINE_SPINLOCK(bootprof_lock); static bool enabled; static int bf_lk_t, bf_pl_t, bf_logo_t; static int bf_bl2ext_t, bf_gz_t, bf_tfa_t, bf_sec_os_t; static u64 timestamp_on, timestamp_off; static bool boot_finish; static struct list_head initcall_list; static DEFINE_SPINLOCK(initcall_lock); atomic_t initcall_num = ATOMIC_INIT(0); /*Get info form cmdline*/ module_param_named(pl_t, bf_pl_t, int, 0644); module_param_named(lk_t, bf_lk_t, int, 0644); module_param_named(logo_t, bf_logo_t, int, 0644); bool mt_boot_finish(void) { return boot_finish; } EXPORT_SYMBOL_GPL(mt_boot_finish); static long long msec_high(unsigned long long nsec) { if ((long long)nsec < 0) { nsec = -nsec; do_div(nsec, 1000000); return -nsec; } do_div(nsec, 1000000); return nsec; } static unsigned long msec_low(unsigned long long nsec) { if ((long long)nsec < 0) nsec = -nsec; return do_div(nsec, 1000000); } void bootprof_log_boot(char *str) { unsigned long long ts; struct log_t *p = NULL; size_t n; int err = 0; if (!str) { return; } n = strlen(str) + 1; ts = sched_clock(); spin_lock(&bootprof_lock); if (!enabled) { spin_unlock(&bootprof_lock); return; } if (log_count >= (LOGS_PER_BUF * BUF_COUNT)) { enabled = false; err = 1; goto out; } else if (log_count && !(log_count % LOGS_PER_BUF)) { bootprof[log_count / LOGS_PER_BUF] = kcalloc(LOGS_PER_BUF, sizeof(struct log_t), GFP_ATOMIC | __GFP_NORETRY | __GFP_NOWARN); } if (!bootprof[log_count / LOGS_PER_BUF]) { err = 2; goto out; } p = &bootprof[log_count / LOGS_PER_BUF][log_count % LOGS_PER_BUF]; p->timestamp = ts; p->pid = current->pid; n += TASK_COMM_LEN; p->comm_event = kzalloc(n, GFP_ATOMIC | __GFP_NORETRY | __GFP_NOWARN); if (!p->comm_event) { enabled = false; err = 3; goto out; } memcpy(p->comm_event, current->comm, TASK_COMM_LEN); memcpy(p->comm_event + TASK_COMM_LEN, str, n - TASK_COMM_LEN); log_count++; out: spin_unlock(&bootprof_lock); pr_info("BOOTPROF:%10lld.%06ld:%s\n", msec_high(ts), msec_low(ts), str); if (err > 0) pr_info("[BOOTPROF] Error(Ret:%d): Skip log.\n", err); } EXPORT_SYMBOL_GPL(bootprof_log_boot); static void bootprof_bootloader(void) { struct device_node *node; node = of_find_node_by_name(NULL, "bootprof"); if (node) { of_property_read_s32(node, "pl_t", &bf_pl_t); of_property_read_s32(node, "lk_t", &bf_lk_t); if (of_property_read_s32(node, "logo_t", &bf_logo_t)) of_property_read_s32(node, "lk_logo_t", &bf_logo_t); of_property_read_s32(node, "logo_t", &bf_logo_t); of_property_read_s32(node, "bl2_ext_t", &bf_bl2ext_t); of_property_read_s32(node, "tfa_t", &bf_tfa_t); of_property_read_s32(node, "sec_os_t", &bf_sec_os_t); of_property_read_s32(node, "gz_t", &bf_gz_t); pr_info("BOOTPROF: pl=%d, bl2ext=%d ,lk=%d, logo=%d, tfa=%d, sec_os=%d, gz=%d\n", bf_pl_t, bf_bl2ext_t, bf_lk_t, bf_logo_t, bf_tfa_t, bf_sec_os_t, bf_gz_t); } } void bootprof_initcall(initcall_t fn, unsigned long long ts) { /* log more than threshold initcalls */ unsigned long msec_rem; char msgbuf[MSG_SIZE]; int len; atomic_inc(&initcall_num); if (ts > BOOTPROF_THRESHOLD) { msec_rem = do_div(ts, NSEC_PER_MSEC); len = scnprintf(msgbuf, sizeof(msgbuf), "initcall: %ps %5llu.%06lums", fn, ts, msec_rem); if (len < 0) pr_info("BOOTPROF: initcall - Invalid argument.\n"); bootprof_log_boot(msgbuf); } } #ifndef MODULE /*Build-in*/ void bootprof_probe(unsigned long long ts, struct device *dev, struct device_driver *drv, unsigned long probe) { /* log more than threshold probes*/ unsigned long msec_rem; char msgbuf[MSG_SIZE]; int pos, len; if (ts <= BOOTPROF_THRESHOLD) return; msec_rem = do_div(ts, NSEC_PER_MSEC); pos = scnprintf(msgbuf, sizeof(msgbuf), "probe: probe=%ps", (void *)probe); if (pos < 0) pos = 0; if (drv) { len = scnprintf(msgbuf + pos, sizeof(msgbuf) - pos, " drv=%s(%ps)", drv->name ? drv->name : "", (void *)drv); if (len >= 0) pos += len; } if (dev && dev->init_name) { len = scnprintf(msgbuf + pos, sizeof(msgbuf) - pos, " dev=%s(%ps)", dev->init_name, (void *)dev); if (len >= 0) pos += len; } scnprintf(msgbuf + pos, sizeof(msgbuf) - pos, " %5llu.%06lums", ts, msec_rem); bootprof_log_boot(msgbuf); } EXPORT_SYMBOL_GPL(bootprof_probe); void bootprof_pdev_register(unsigned long long ts, struct platform_device *pdev) { /* log more than threshold register*/ unsigned long msec_rem; char msgbuf[MSG_SIZE]; int len; if (ts <= BOOTPROF_THRESHOLD || !pdev) return; msec_rem = do_div(ts, NSEC_PER_MSEC); len = scnprintf(msgbuf, sizeof(msgbuf), "probe: pdev=%s(%ps) %5llu.%06lums", pdev->name, (void *)pdev, ts, msec_rem); if (len < 0) pr_info("BOOTPROF: pdev - Invalid argument.\n"); bootprof_log_boot(msgbuf); } EXPORT_SYMBOL_GPL(bootprof_pdev_register); #endif /*MODULE END*/ /* initcalls tracepoint cb while initcall_debug=1 */ static __init_or_module void tp_initcall_start_cb(void *data, initcall_t fn) { struct initcall_list_t *obj; struct initcall_list_t *pos, *next; struct list_head err_list; INIT_LIST_HEAD(&err_list); obj = kzalloc(sizeof(struct initcall_list_t), GFP_ATOMIC | __GFP_NORETRY | __GFP_NOWARN); if (!obj) return; obj->pid = task_pid_nr(current); obj->tid = task_pid_vnr(current); obj->timestamp = sched_clock(); /*Check if there is duplicated enrty.*/ spin_lock(&initcall_lock); if (!list_empty(&initcall_list)) { list_for_each_entry_safe(pos, next, &initcall_list, dev_entry) { if ((pos->pid == obj->pid) && (pos->tid == obj->tid)) { list_del(&pos->dev_entry); /*Add duplicated enrty into err list*/ list_add_tail(&pos->dev_entry, &err_list); } } } list_add_tail(&obj->dev_entry, &initcall_list); spin_unlock(&initcall_lock); /*release entry of err list*/ if (!list_empty(&err_list)) { list_for_each_entry_safe(pos, next, &err_list, dev_entry) { pr_info("[BOOTPROF] Warn:duplicated entry.(pid:%d, tid:%d)\n", pos->pid, pos->tid); list_del(&pos->dev_entry); kfree(pos); } } } static __init_or_module void tp_initcall_finish_cb(void *data, initcall_t fn, int ret) { struct initcall_list_t *pos, *next; unsigned long long start_ts = 0; struct list_head memfree_list; unsigned long long end_ts = sched_clock(); unsigned long long duration; INIT_LIST_HEAD(&memfree_list); spin_lock(&initcall_lock); list_for_each_entry_safe(pos, next, &initcall_list, dev_entry) { if ((pos->pid == task_pid_nr(current)) && (pos->tid == task_pid_vnr(current))) { start_ts = pos->timestamp; list_del(&pos->dev_entry); list_add_tail(&pos->dev_entry, &memfree_list); break; } } spin_unlock(&initcall_lock); /*release entry*/ if (!list_empty(&memfree_list)) { list_for_each_entry_safe(pos, next, &memfree_list, dev_entry) { list_del(&pos->dev_entry); kfree(pos); } } /* start time of current module is 0.*/ if (start_ts == 0) { #ifdef MODULE /* if bootprof is first loading module.*/ bootprof_log_boot("Kernel_init_done"); #endif return; } duration = end_ts - start_ts; bootprof_initcall(fn, duration); } static struct tracepoints_table interests[] = { {.name = "initcall_start", .func = tp_initcall_start_cb}, {.name = "initcall_finish", .func = tp_initcall_finish_cb}, }; #define FOR_EACH_INTEREST(i) \ for (i = 0; i < sizeof(interests) / \ sizeof(struct tracepoints_table); i++) /* Find the struct tracepoint associated */ /* with a given tracepointname. */ static void tp_lookup(struct tracepoint *tp, void *ignore) { unsigned int i; if (!tp || !tp->name) return; FOR_EACH_INTEREST(i) { if (strcmp(interests[i].name, tp->name) == 0) interests[i].tp = tp; } } /* Unregister initcalls tracepoints */ static void tp_deinit(void) { unsigned int i; struct initcall_list_t *pos, *next; FOR_EACH_INTEREST(i) { if (interests[i].init) { tracepoint_probe_unregister(interests[i].tp, interests[i].func, interests[i].data); interests[i].init = false; } } spin_lock(&initcall_lock); if (!list_empty(&initcall_list)) { list_for_each_entry_safe(pos, next, &initcall_list, dev_entry) { if (pos) { list_del(&pos->dev_entry); kfree(pos); } } } spin_unlock(&initcall_lock); pr_info("BOOTPROF: Unregister initcalls tracepoint.\n"); } /* Register initcalls tracepoints */ static void tp_init(void) { unsigned int i; INIT_LIST_HEAD(&initcall_list); /* Install the tracepoints */ for_each_kernel_tracepoint(tp_lookup, NULL); FOR_EACH_INTEREST(i) { if (!interests[i].tp) { pr_info("[BOOTPROF]TP: %s not found\n", interests[i].name); /* Unload previously loaded */ tp_deinit(); return; } tracepoint_probe_register(interests[i].tp, interests[i].func, interests[i].data); interests[i].init = true; } } static void mt_bootprof_switch(int on) { bool tmp; unsigned long long ts = sched_clock(); spin_lock(&bootprof_lock); tmp = enabled ^ on; if (tmp) { if (on) enabled = 1; else enabled = 0; } spin_unlock(&bootprof_lock); if (tmp) { pr_info("BOOTPROF:%10lld.%06ld: %s%lld)\n", msec_high(ts), msec_low(ts), on ? "ON (TH:" : "OFF (KO:", on ? msec_high(BOOTPROF_THRESHOLD) : (long long)atomic_read(&initcall_num)); if (on) { timestamp_on = ts; } else { timestamp_off = ts; if (!boot_finish) { boot_finish = true; /* Unregister Initcall tracepointsk while boot finish */ tp_deinit(); } } } } static ssize_t mt_bootprof_write(struct file *filp, const char *ubuf, size_t cnt, loff_t *data) { char buf[BOOT_STR_SIZE]; size_t copy_size = cnt; if (cnt >= sizeof(buf)) copy_size = BOOT_STR_SIZE - 1; if (copy_from_user(&buf, ubuf, copy_size)) return -EFAULT; if (cnt == 1 && buf[0] == '1') { mt_bootprof_switch(1); return 1; } else if (cnt == 1 && buf[0] == '0') { mt_bootprof_switch(0); return 1; } buf[copy_size] = 0; bootprof_log_boot(buf); return cnt; } static int mt_bootprof_show(struct seq_file *m, void *v) { unsigned long i; struct log_t *p; if (!m) { pr_info("seq_file is Null.\n"); return 0; } seq_puts(m, "----------------------------------------\n"); seq_printf(m, "%-10d BOOT PROF (unit:msec)\n", enabled); seq_printf(m, "%-10d Kernel Module Total\n", atomic_read(&initcall_num)); seq_puts(m, "----------------------------------------\n"); seq_printf(m, "%10d : %s\n", bf_pl_t, "preloader"); if (bf_bl2ext_t > 0) { seq_printf(m, "%10d : %s (%s: %d)\n", bf_bl2ext_t, "bl2_ext", "Start->Show logo", bf_logo_t); if (bf_tfa_t > 0) seq_printf(m, "%10d : %s\n", bf_tfa_t, "tfa"); if (bf_sec_os_t > 0) seq_printf(m, "%10d : %s\n", bf_sec_os_t, "sec_os"); if (bf_gz_t > 0) seq_printf(m, "%10d : %s\n", bf_gz_t, "gz"); seq_printf(m, "%10d : %s\n", bf_lk_t, "lk"); } else { seq_printf(m, "%10d : %s (%s: %d)\n", bf_lk_t, "lk", "Start->Show logo", bf_logo_t); } seq_puts(m, "----------------------------------------\n"); seq_printf(m, "%10lld.%06ld : ON (Threshold:%5lldms)\n", msec_high(timestamp_on), msec_low(timestamp_on), msec_high(BOOTPROF_THRESHOLD)); for (i = 0; i < log_count; i++) { p = &bootprof[i / LOGS_PER_BUF][i % LOGS_PER_BUF]; if (!p->comm_event) continue; seq_printf(m, "%10llu.%06lu :%5d-%-16s: %s\n", msec_high(p->timestamp), msec_low(p->timestamp), p->pid, p->comm_event, p->comm_event + TASK_COMM_LEN); } seq_printf(m, "%10lld.%06ld : OFF\n", msec_high(timestamp_off), msec_low(timestamp_off)); seq_puts(m, "----------------------------------------\n"); return 0; } /*** Seq operation of mtprof ****/ static int mt_bootprof_open(struct inode *inode, struct file *file) { return single_open(file, mt_bootprof_show, inode->i_private); } static const struct proc_ops mt_bootprof_fops = { .proc_open = mt_bootprof_open, .proc_write = mt_bootprof_write, .proc_read = seq_read, .proc_lseek = seq_lseek, .proc_release = single_release, }; static int __init bootprof_init(void) { struct proc_dir_entry *pe; memset(bootprof, 0, sizeof(struct log_t *) * BUF_COUNT); bootprof[0] = kcalloc(LOGS_PER_BUF, sizeof(struct log_t), GFP_ATOMIC | __GFP_NORETRY | __GFP_NOWARN); if (!bootprof[0]) { pr_info("[BOOTPROF] fail to allocate memory\n"); return -ENOMEM; } pe = proc_create("bootprof", 0664, NULL, &mt_bootprof_fops); if (!pe) { pr_info("[BOOTPROF] fail to create file node\n"); return -ENOMEM; } bootprof_bootloader(); tp_init(); mt_bootprof_switch(1); return 0; } static void __exit bootprof_exit(void) { struct log_t *p = NULL; unsigned int i; tp_deinit(); if (log_count > 0) { spin_lock(&bootprof_lock); enabled = 0; for (i = 0; i < log_count; i++) { p = &bootprof[i / LOGS_PER_BUF][i % LOGS_PER_BUF]; kfree(p->comm_event); } for (i = 0; i < ((log_count / LOGS_PER_BUF) + 1); i++) kfree(bootprof[i]); spin_unlock(&bootprof_lock); } remove_proc_entry("bootprof", NULL); pr_info("bootprof module exit.\n"); } early_initcall(bootprof_init); module_exit(bootprof_exit); MODULE_DESCRIPTION("MEDIATEK BOOT TIME PROFILING"); MODULE_LICENSE("GPL v2");