123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234 |
- // SPDX-License-Identifier: GPL-2.0
- /*
- * sec_pm_log.c - SAMSUNG Power/Thermal logging.
- *
- */
- #include <linux/module.h>
- #include <linux/device.h>
- #include <linux/err.h>
- #include <linux/slab.h>
- #include <linux/uaccess.h>
- #include <linux/proc_fs.h>
- #include <linux/time.h>
- #include <linux/rtc.h>
- #include <linux/suspend.h>
- #include <linux/sched/clock.h>
- #include <linux/sec_pm_log.h>
- /* log for pm team */
- #define PROC_PM_DIR "sec_pm_log"
- static struct proc_dir_entry *procfs_pm_dir;
- /* power/thermal log */
- #define POWER_LOG_NAME "power_log"
- #define THERMAL_LOG_NAME "thermal_log"
- #define PM_LOG_BUF_SIZE SZ_128K
- #define MAX_STR_LEN 256
- static bool proc_init_done __read_mostly;
- /* monitoring log */
- static struct delayed_work power_log_work;
- static DEFINE_MUTEX(power_log_lock);
- /* proc log implementation */
- #define SEC_PM_LOG(name) \
- static char name##_log_buf[PM_LOG_BUF_SIZE]; \
- static unsigned int name##_buf_pos; \
- static unsigned int name##_buf_full; \
- \
- void ss_##name##_print(const char *fmt, ...) \
- { \
- char buf[MAX_STR_LEN] = {0, }; \
- unsigned int len = 0; \
- va_list args; \
- u64 time; \
- unsigned long nsec; \
- \
- if (!proc_init_done) \
- return; \
- \
- /* timestamp */ \
- time = local_clock(); \
- nsec = do_div(time, NSEC_PER_SEC); \
- len = snprintf(buf, sizeof(buf), "[%6lu.%06ld] ", \
- (unsigned long)time, nsec / NSEC_PER_USEC); \
- \
- /* append log */ \
- va_start(args, fmt); \
- len += vsnprintf(buf + len, MAX_STR_LEN - len, fmt, args); \
- va_end(args); \
- \
- /* buffer full, reset position */ \
- if (name##_buf_pos + len + 1 > PM_LOG_BUF_SIZE) { \
- name##_buf_pos = 0; \
- name##_buf_full++; \
- } \
- \
- name##_buf_pos += scnprintf(&name##_log_buf[name##_buf_pos], \
- len + 1, "%s", buf); \
- } \
- EXPORT_SYMBOL(ss_##name##_print); \
- \
- static ssize_t ss_##name##_log_read(struct file *file, char __user *buf, \
- size_t len, loff_t *offset) \
- { \
- loff_t pos = *offset; \
- ssize_t count = 0; \
- size_t size = (name##_buf_full > 0) ? PM_LOG_BUF_SIZE : (size_t)name##_buf_pos; \
- \
- pr_debug("%s: pos(%d), full(%d), size(%d)\n", __func__, \
- name##_buf_pos, name##_buf_full, size); \
- \
- if (pos >= size) \
- return 0; \
- \
- count = min(len, size); \
- \
- if ((pos + count) > size) \
- count = size - pos; \
- \
- if (copy_to_user(buf, name##_log_buf + pos, count)) \
- return -EFAULT; \
- \
- *offset += count; \
- \
- pr_info("%s: done\n", __func__); \
- \
- return count; \
- } \
- \
- static const struct proc_ops proc_ss_##name##_log_ops = { \
- .proc_read = ss_##name##_log_read, \
- };
- /* all-in-one define */
- SEC_PM_LOG(power);
- SEC_PM_LOG(thermal);
- /* monitoring log implementation */
- #define POLLING_DELAY (HZ * 5)
- static void __ref power_log_print(struct work_struct *work)
- {
- char power_log[MAX_STR_LEN];
- mutex_lock(&power_log_lock);
- pm_get_active_wakeup_sources(power_log, MAX_STR_LEN);
- mutex_unlock(&power_log_lock);
- ss_power_print("pmon: %s\n", power_log);
- schedule_delayed_work(&power_log_work, POLLING_DELAY);
- }
- void sec_pm_proc_log_init(void)
- {
- struct proc_dir_entry *entry;
- procfs_pm_dir = proc_mkdir(PROC_PM_DIR, NULL);
- if (unlikely(!procfs_pm_dir)) {
- pr_err("%s: failed to make %s\n", __func__, PROC_PM_DIR);
- return;
- }
- /* proc power */
- entry = proc_create(POWER_LOG_NAME, 0444,
- procfs_pm_dir, &proc_ss_power_log_ops);
- if (unlikely(!entry)) {
- pr_err("%s: proc power log fail\n", __func__);
- } else {
- proc_set_size(entry, PM_LOG_BUF_SIZE);
- ss_power_print("%s done\n", __func__);
- proc_init_done = true;
- }
- /* proc thermal */
- entry = proc_create(THERMAL_LOG_NAME, 0444,
- procfs_pm_dir,
- &proc_ss_thermal_log_ops);
- if (unlikely(!entry)) {
- pr_err("%s: proc thermal log fail\n", __func__);
- } else {
- proc_set_size(entry, PM_LOG_BUF_SIZE);
- ss_thermal_print("%s done\n", __func__);
- proc_init_done = true;
- }
- if (!proc_init_done)
- remove_proc_subtree(PROC_PM_DIR, NULL);
- else
- pr_info("%s: proc init done\n", __func__);
- return;
- }
- static int sec_pm_log_notify(struct notifier_block *nb,
- unsigned long mode, void *_unused)
- {
- struct timespec64 ts;
- struct rtc_time tm;
- ktime_get_real_ts64(&ts);
- rtc_time64_to_tm(ts.tv_sec, &tm);
- switch (mode) {
- case PM_SUSPEND_PREPARE:
- ss_power_print("soc: suspend(%d-%02d-%02d %02d:%02d:%02d.%03lu)\n",
- tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
- tm.tm_hour, tm.tm_min, tm.tm_sec, ts.tv_nsec);
- cancel_delayed_work(&power_log_work);
- break;
- case PM_POST_SUSPEND:
- schedule_delayed_work(&power_log_work, 0);
- ss_power_print("soc: resume(%d-%02d-%02d %02d:%02d:%02d.%03lu)\n",
- tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
- tm.tm_hour, tm.tm_min, tm.tm_sec, ts.tv_nsec);
- break;
- default:
- break;
- }
- return 0;
- }
- static struct notifier_block sec_pm_log_nb = {
- .notifier_call = sec_pm_log_notify,
- };
- static int __init sec_pm_log_init(void)
- {
- int ret = 0;
- pr_info("%s\n", __func__);
- /* proc log */
- sec_pm_proc_log_init();
- /* monitoring log */
- INIT_DELAYED_WORK(&power_log_work, power_log_print);
- schedule_delayed_work(&power_log_work, 0);
- pr_info("%s: monitor init done\n", __func__);
- /* pm notifier */
- ret = register_pm_notifier(&sec_pm_log_nb);
- if (ret)
- pr_err("sec_pm_log: pm notifier register fail(%d).\n", ret);
- return 0;
- }
- static void __exit sec_pm_log_exit(void)
- {
- remove_proc_subtree(POWER_LOG_NAME, procfs_pm_dir);
- remove_proc_subtree(THERMAL_LOG_NAME, procfs_pm_dir);
- remove_proc_subtree(PROC_PM_DIR, NULL);
- }
- module_init(sec_pm_log_init);
- module_exit(sec_pm_log_exit);
- MODULE_ALIAS("platform:sec_pm_log");
- MODULE_DESCRIPTION("Samsung Power/Thermal Log driver");
- MODULE_LICENSE("GPL v2");
|