/* Copyright (c) 2014-2015, 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 "soc/qcom/msm-core.h" #define MAX_PSTATES 50 enum arg_offset { CPU_OFFSET, FREQ_OFFSET, POWER_OFFSET, }; struct core_debug { int cpu; struct cpu_pstate_pwr *head; int enabled; int len; struct cpu_pwr_stats *ptr; struct cpu_pstate_pwr *driver_data; int driver_len; }; static DEFINE_PER_CPU(struct core_debug, c_dgfs); static struct cpu_pwr_stats *msm_core_data; static struct debugfs_blob_wrapper help_msg = { .data = "MSM CORE Debug-FS Support\n" "\n" "Hierarchy schema\n" "/sys/kernel/debug/msm_core\n" " /help - Static help text\n" " /ptable - write to p-state table\n" " /enable - enable the written p-state table\n" " /ptable_dump - Dump the debug ptable\n" "\n" "Usage\n" " Input test frequency and power information in ptable:\n" " echo \"0 300000 120\" > ptable\n" " format: \n" "\n" " Enable the ptable for the cpu:\n" " echo \"0 1\" > enable\n" " format: <1 to enable, 0 to disable>\n" " Note: Writing 0 to disable will reset/clear the ptable\n" "\n" " Dump the entire ptable:\n" " cat ptable\n" " ----- CPU0 - Enabled ---------\n" " Freq Power\n" " 700000 120\n" "----- CPU0 - Live numbers -----\n" " Freq Power\n" " 300000 218\n" " ----- CPU1 - Written ---------\n" " Freq Power\n" " 700000 120\n" " Ptable dump will dump the status of the table as well\n" " It shows:\n" " Enabled -> for a cpu that debug ptable enabled\n" " Written -> for a cpu that has debug ptable values written\n" " but not enabled\n" "\n", }; static void add_to_ptable(uint64_t *arg) { struct core_debug *node; int i, cpu = arg[CPU_OFFSET]; if (!cpu_possible(cpu)) return; node = &per_cpu(c_dgfs, cpu); if (!node->head) { node->head = kzalloc(sizeof(struct cpu_pstate_pwr) * (MAX_PSTATES + 1), GFP_KERNEL); if (!node->head) return; } for (i = 0; i < MAX_PSTATES; i++) { if (node->head[i].freq == arg[FREQ_OFFSET]) { node->head[i].power = arg[POWER_OFFSET]; return; } if (node->head[i].freq == 0) break; } if (i == MAX_PSTATES) { pr_warn("Dropped ptable update - no space left.\n"); return; } /* Insert a new frequency (may need to move things around to keep in ascending order). */ for (i = MAX_PSTATES - 1; i > 0; i--) { if (node->head[i-1].freq > arg[FREQ_OFFSET]) { node->head[i].freq = node->head[i-1].freq; node->head[i].power = node->head[i-1].power; } else if (node->head[i-1].freq != 0) { break; } } node->head[i].freq = arg[FREQ_OFFSET]; node->head[i].power = arg[POWER_OFFSET]; node->len++; if (node->ptr) node->ptr->len = node->len; } static int split_ptable_args(char *line, uint64_t *arg) { char *args; int i; int ret = 0; for (i = 0; line; i++) { args = strsep(&line, " "); ret = kstrtoull(args, 10, &arg[i]); } return ret; } static ssize_t msm_core_ptable_write(struct file *file, const char __user *ubuf, size_t len, loff_t *offp) { char *kbuf; int ret; uint64_t arg[3]; if (len == 0) return 0; kbuf = kzalloc(len + 1, GFP_KERNEL); if (!kbuf) return -ENOMEM; if (copy_from_user(kbuf, ubuf, len)) { ret = -EFAULT; goto done; } kbuf[len] = '\0'; ret = split_ptable_args(kbuf, arg); if (!ret) { add_to_ptable(arg); ret = len; } done: kfree(kbuf); return ret; } static void print_table(struct seq_file *m, struct cpu_pstate_pwr *c_n, int len) { int i; seq_puts(m, " Freq Power\n"); for (i = 0; i < len; i++) seq_printf(m, " %d %u\n", c_n[i].freq, c_n[i].power); } static int msm_core_ptable_read(struct seq_file *m, void *data) { int cpu; struct core_debug *node; for_each_possible_cpu(cpu) { node = &per_cpu(c_dgfs, cpu); if (node->head) { seq_printf(m, "----- CPU%d - %s - Debug -------\n", cpu, node->enabled == 1 ? "Enabled" : "Written"); print_table(m, node->head, node->len); } if (msm_core_data[cpu].ptable) { seq_printf(m, "--- CPU%d - Live numbers at %ldC---\n", cpu, node->ptr->temp); print_table(m, msm_core_data[cpu].ptable, msm_core_data[cpu].len); } } return 0; } static ssize_t msm_core_enable_write(struct file *file, const char __user *ubuf, size_t len, loff_t *offp) { char *kbuf; int ret; uint64_t arg[3]; int cpu; if (len == 0) return 0; kbuf = kzalloc(len + 1, GFP_KERNEL); if (!kbuf) return -ENOMEM; if (copy_from_user(kbuf, ubuf, len)) { ret = -EFAULT; goto done; } kbuf[len] = '\0'; ret = split_ptable_args(kbuf, arg); if (ret) goto done; cpu = arg[CPU_OFFSET]; if (cpu_possible(cpu)) { struct core_debug *node = &per_cpu(c_dgfs, cpu); if (arg[FREQ_OFFSET]) { msm_core_data[cpu].ptable = node->head; msm_core_data[cpu].len = node->len; } else { msm_core_data[cpu].ptable = node->driver_data; msm_core_data[cpu].len = node->driver_len; node->len = 0; } node->enabled = arg[FREQ_OFFSET]; } ret = len; blocking_notifier_call_chain( get_power_update_notifier(), cpu, NULL); done: kfree(kbuf); return ret; } static const struct file_operations msm_core_enable_ops = { .write = msm_core_enable_write, }; static int msm_core_dump_open(struct inode *inode, struct file *file) { return single_open(file, msm_core_ptable_read, inode->i_private); } static const struct file_operations msm_core_ptable_ops = { .open = msm_core_dump_open, .read = seq_read, .write = msm_core_ptable_write, .llseek = seq_lseek, .release = single_release, }; int msm_core_debug_init(void) { struct dentry *dir; struct dentry *file; int i; msm_core_data = get_cpu_pwr_stats(); if (!msm_core_data) goto fail; dir = debugfs_create_dir("msm_core", NULL); if (IS_ERR_OR_NULL(dir)) return PTR_ERR(dir); file = debugfs_create_file("enable", S_IRUSR|S_IRGRP|S_IWUSR|S_IWGRP, dir, NULL, &msm_core_enable_ops); if (IS_ERR_OR_NULL(file)) goto fail; file = debugfs_create_file("ptable", S_IRUSR|S_IRGRP|S_IWUSR|S_IWGRP, dir, NULL, &msm_core_ptable_ops); if (IS_ERR_OR_NULL(file)) goto fail; help_msg.size = strlen(help_msg.data); file = debugfs_create_blob("help", S_IRUGO, dir, &help_msg); if (IS_ERR_OR_NULL(file)) goto fail; for (i = 0; i < num_possible_cpus(); i++) { per_cpu(c_dgfs, i).ptr = &msm_core_data[i]; per_cpu(c_dgfs, i).driver_data = msm_core_data[i].ptable; per_cpu(c_dgfs, i).driver_len = msm_core_data[i].len; } return 0; fail: debugfs_remove(dir); return PTR_ERR(file); } late_initcall(msm_core_debug_init);