Praveen Chidambaram | 3895bde | 2012-05-14 19:42:40 +0530 | [diff] [blame] | 1 | /* Copyright (c) 2012, Code Aurora Forum. All rights reserved. |
| 2 | * |
| 3 | * This program is free software; you can redistribute it and/or modify |
| 4 | * it under the terms of the GNU General Public License version 2 and |
| 5 | * only version 2 as published by the Free Software Foundation. |
| 6 | * |
| 7 | * This program is distributed in the hope that it will be useful, |
| 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 10 | * GNU General Public License for more details. |
| 11 | * |
| 12 | */ |
| 13 | |
| 14 | #include <linux/module.h> |
| 15 | #include <linux/kernel.h> |
| 16 | #include <linux/init.h> |
| 17 | #include <linux/spinlock.h> |
| 18 | #include <linux/uaccess.h> |
| 19 | #include <linux/proc_fs.h> |
| 20 | |
| 21 | #include "pm.h" |
| 22 | |
| 23 | struct msm_pm_time_stats { |
| 24 | const char *name; |
| 25 | int64_t first_bucket_time; |
| 26 | int bucket[CONFIG_MSM_IDLE_STATS_BUCKET_COUNT]; |
| 27 | int64_t min_time[CONFIG_MSM_IDLE_STATS_BUCKET_COUNT]; |
| 28 | int64_t max_time[CONFIG_MSM_IDLE_STATS_BUCKET_COUNT]; |
| 29 | int count; |
| 30 | int64_t total_time; |
| 31 | bool enabled; |
| 32 | }; |
| 33 | |
| 34 | struct msm_pm_cpu_time_stats { |
| 35 | struct msm_pm_time_stats stats[MSM_PM_STAT_COUNT]; |
| 36 | }; |
| 37 | |
| 38 | static DEFINE_SPINLOCK(msm_pm_stats_lock); |
| 39 | static DEFINE_PER_CPU_SHARED_ALIGNED( |
| 40 | struct msm_pm_cpu_time_stats, msm_pm_stats); |
| 41 | |
| 42 | /* |
| 43 | * Add the given time data to the statistics collection. |
| 44 | */ |
| 45 | void msm_pm_add_stat(enum msm_pm_time_stats_id id, int64_t t) |
| 46 | { |
| 47 | unsigned long flags; |
| 48 | struct msm_pm_time_stats *stats; |
| 49 | int64_t bt; |
| 50 | int i; |
| 51 | |
| 52 | spin_lock_irqsave(&msm_pm_stats_lock, flags); |
| 53 | stats = __get_cpu_var(msm_pm_stats).stats; |
| 54 | |
| 55 | if (!stats[id].enabled) |
| 56 | goto add_bail; |
| 57 | |
| 58 | stats[id].total_time += t; |
| 59 | stats[id].count++; |
| 60 | |
| 61 | bt = t; |
| 62 | do_div(bt, stats[id].first_bucket_time); |
| 63 | |
| 64 | if (bt < 1ULL << (CONFIG_MSM_IDLE_STATS_BUCKET_SHIFT * |
| 65 | (CONFIG_MSM_IDLE_STATS_BUCKET_COUNT - 1))) |
| 66 | i = DIV_ROUND_UP(fls((uint32_t)bt), |
| 67 | CONFIG_MSM_IDLE_STATS_BUCKET_SHIFT); |
| 68 | else |
| 69 | i = CONFIG_MSM_IDLE_STATS_BUCKET_COUNT - 1; |
| 70 | |
| 71 | if (i >= CONFIG_MSM_IDLE_STATS_BUCKET_COUNT) |
| 72 | i = CONFIG_MSM_IDLE_STATS_BUCKET_COUNT - 1; |
| 73 | |
| 74 | stats[id].bucket[i]++; |
| 75 | |
| 76 | if (t < stats[id].min_time[i] || !stats[id].max_time[i]) |
| 77 | stats[id].min_time[i] = t; |
| 78 | if (t > stats[id].max_time[i]) |
| 79 | stats[id].max_time[i] = t; |
| 80 | |
| 81 | add_bail: |
| 82 | spin_unlock_irqrestore(&msm_pm_stats_lock, flags); |
| 83 | } |
| 84 | |
| 85 | /* |
| 86 | * Helper function of snprintf where buf is auto-incremented, size is auto- |
| 87 | * decremented, and there is no return value. |
| 88 | * |
| 89 | * NOTE: buf and size must be l-values (e.g. variables) |
| 90 | */ |
| 91 | #define SNPRINTF(buf, size, format, ...) \ |
| 92 | do { \ |
| 93 | if (size > 0) { \ |
| 94 | int ret; \ |
| 95 | ret = snprintf(buf, size, format, ## __VA_ARGS__); \ |
| 96 | if (ret > size) { \ |
| 97 | buf += size; \ |
| 98 | size = 0; \ |
| 99 | } else { \ |
| 100 | buf += ret; \ |
| 101 | size -= ret; \ |
| 102 | } \ |
| 103 | } \ |
| 104 | } while (0) |
| 105 | |
| 106 | /* |
| 107 | * Write out the power management statistics. |
| 108 | */ |
| 109 | static int msm_pm_read_proc |
| 110 | (char *page, char **start, off_t off, int count, int *eof, void *data) |
| 111 | { |
| 112 | unsigned int cpu = off / MSM_PM_STAT_COUNT; |
| 113 | int id = off % MSM_PM_STAT_COUNT; |
| 114 | char *p = page; |
| 115 | |
| 116 | if (count < 1024) { |
| 117 | *start = (char *) 0; |
| 118 | *eof = 0; |
| 119 | return 0; |
| 120 | } |
| 121 | |
| 122 | if (cpu < num_possible_cpus()) { |
| 123 | unsigned long flags; |
| 124 | struct msm_pm_time_stats *stats; |
| 125 | int i; |
| 126 | int64_t bucket_time; |
| 127 | int64_t s; |
| 128 | uint32_t ns; |
| 129 | |
| 130 | spin_lock_irqsave(&msm_pm_stats_lock, flags); |
| 131 | stats = per_cpu(msm_pm_stats, cpu).stats; |
| 132 | |
| 133 | /* Skip the disabled ones */ |
| 134 | if (!stats[id].enabled) { |
| 135 | *p = '\0'; |
| 136 | p++; |
| 137 | goto again; |
| 138 | } |
| 139 | |
| 140 | s = stats[id].total_time; |
| 141 | ns = do_div(s, NSEC_PER_SEC); |
| 142 | SNPRINTF(p, count, |
| 143 | "[cpu %u] %s:\n" |
| 144 | " count: %7d\n" |
| 145 | " total_time: %lld.%09u\n", |
| 146 | cpu, stats[id].name, |
| 147 | stats[id].count, |
| 148 | s, ns); |
| 149 | |
| 150 | bucket_time = stats[id].first_bucket_time; |
| 151 | for (i = 0; i < CONFIG_MSM_IDLE_STATS_BUCKET_COUNT - 1; i++) { |
| 152 | s = bucket_time; |
| 153 | ns = do_div(s, NSEC_PER_SEC); |
| 154 | SNPRINTF(p, count, |
| 155 | " <%6lld.%09u: %7d (%lld-%lld)\n", |
| 156 | s, ns, stats[id].bucket[i], |
| 157 | stats[id].min_time[i], |
| 158 | stats[id].max_time[i]); |
| 159 | |
| 160 | bucket_time <<= CONFIG_MSM_IDLE_STATS_BUCKET_SHIFT; |
| 161 | } |
| 162 | |
| 163 | SNPRINTF(p, count, " >=%6lld.%09u: %7d (%lld-%lld)\n", |
| 164 | s, ns, stats[id].bucket[i], |
| 165 | stats[id].min_time[i], |
| 166 | stats[id].max_time[i]); |
| 167 | |
| 168 | again: |
| 169 | *start = (char *) 1; |
| 170 | *eof = (off + 1 >= MSM_PM_STAT_COUNT * num_possible_cpus()); |
| 171 | |
| 172 | spin_unlock_irqrestore(&msm_pm_stats_lock, flags); |
| 173 | } |
| 174 | |
| 175 | return p - page; |
| 176 | } |
| 177 | #undef SNPRINTF |
| 178 | |
| 179 | #define MSM_PM_STATS_RESET "reset" |
| 180 | |
| 181 | /* |
| 182 | * Reset the power management statistics values. |
| 183 | */ |
| 184 | static int msm_pm_write_proc(struct file *file, const char __user *buffer, |
| 185 | unsigned long count, void *data) |
| 186 | { |
| 187 | char buf[sizeof(MSM_PM_STATS_RESET)]; |
| 188 | int ret; |
| 189 | unsigned long flags; |
| 190 | unsigned int cpu; |
| 191 | |
| 192 | if (count < sizeof(MSM_PM_STATS_RESET)) { |
| 193 | ret = -EINVAL; |
| 194 | goto write_proc_failed; |
| 195 | } |
| 196 | |
| 197 | if (copy_from_user(buf, buffer, sizeof(MSM_PM_STATS_RESET))) { |
| 198 | ret = -EFAULT; |
| 199 | goto write_proc_failed; |
| 200 | } |
| 201 | |
| 202 | if (memcmp(buf, MSM_PM_STATS_RESET, sizeof(MSM_PM_STATS_RESET))) { |
| 203 | ret = -EINVAL; |
| 204 | goto write_proc_failed; |
| 205 | } |
| 206 | |
| 207 | spin_lock_irqsave(&msm_pm_stats_lock, flags); |
| 208 | for_each_possible_cpu(cpu) { |
| 209 | struct msm_pm_time_stats *stats; |
| 210 | int i; |
| 211 | |
| 212 | stats = per_cpu(msm_pm_stats, cpu).stats; |
| 213 | for (i = 0; i < MSM_PM_STAT_COUNT; i++) { |
| 214 | memset(stats[i].bucket, |
| 215 | 0, sizeof(stats[i].bucket)); |
| 216 | memset(stats[i].min_time, |
| 217 | 0, sizeof(stats[i].min_time)); |
| 218 | memset(stats[i].max_time, |
| 219 | 0, sizeof(stats[i].max_time)); |
| 220 | stats[i].count = 0; |
| 221 | stats[i].total_time = 0; |
| 222 | } |
| 223 | } |
| 224 | |
| 225 | spin_unlock_irqrestore(&msm_pm_stats_lock, flags); |
| 226 | return count; |
| 227 | |
| 228 | write_proc_failed: |
| 229 | return ret; |
| 230 | } |
| 231 | #undef MSM_PM_STATS_RESET |
| 232 | |
| 233 | void msm_pm_add_stats(enum msm_pm_time_stats_id *enable_stats, int size) |
| 234 | { |
| 235 | unsigned int cpu; |
| 236 | struct proc_dir_entry *d_entry; |
| 237 | int i = 0; |
| 238 | |
| 239 | for_each_possible_cpu(cpu) { |
| 240 | struct msm_pm_time_stats *stats = |
| 241 | per_cpu(msm_pm_stats, cpu).stats; |
| 242 | |
| 243 | stats[MSM_PM_STAT_REQUESTED_IDLE].name = "idle-request"; |
| 244 | stats[MSM_PM_STAT_REQUESTED_IDLE].first_bucket_time = |
| 245 | CONFIG_MSM_IDLE_STATS_FIRST_BUCKET; |
| 246 | |
| 247 | stats[MSM_PM_STAT_IDLE_SPIN].name = "idle-spin"; |
| 248 | stats[MSM_PM_STAT_IDLE_SPIN].first_bucket_time = |
| 249 | CONFIG_MSM_IDLE_STATS_FIRST_BUCKET; |
| 250 | |
| 251 | stats[MSM_PM_STAT_IDLE_WFI].name = "idle-wfi"; |
| 252 | stats[MSM_PM_STAT_IDLE_WFI].first_bucket_time = |
| 253 | CONFIG_MSM_IDLE_STATS_FIRST_BUCKET; |
| 254 | |
| 255 | stats[MSM_PM_STAT_RETENTION].name = "retention"; |
| 256 | stats[MSM_PM_STAT_RETENTION].first_bucket_time = |
| 257 | CONFIG_MSM_IDLE_STATS_FIRST_BUCKET; |
| 258 | |
| 259 | stats[MSM_PM_STAT_IDLE_STANDALONE_POWER_COLLAPSE].name = |
| 260 | "idle-standalone-power-collapse"; |
| 261 | stats[MSM_PM_STAT_IDLE_STANDALONE_POWER_COLLAPSE]. |
| 262 | first_bucket_time = CONFIG_MSM_IDLE_STATS_FIRST_BUCKET; |
| 263 | |
| 264 | stats[MSM_PM_STAT_IDLE_FAILED_STANDALONE_POWER_COLLAPSE].name = |
| 265 | "idle-failed-standalone-power-collapse"; |
| 266 | stats[MSM_PM_STAT_IDLE_FAILED_STANDALONE_POWER_COLLAPSE]. |
| 267 | first_bucket_time = |
| 268 | CONFIG_MSM_IDLE_STATS_FIRST_BUCKET; |
| 269 | |
| 270 | stats[MSM_PM_STAT_IDLE_POWER_COLLAPSE].name = |
| 271 | "idle-power-collapse"; |
| 272 | stats[MSM_PM_STAT_IDLE_POWER_COLLAPSE].first_bucket_time = |
| 273 | CONFIG_MSM_IDLE_STATS_FIRST_BUCKET; |
| 274 | |
| 275 | stats[MSM_PM_STAT_IDLE_FAILED_POWER_COLLAPSE].name = |
| 276 | "idle-failed-power-collapse"; |
| 277 | stats[MSM_PM_STAT_IDLE_FAILED_POWER_COLLAPSE]. |
| 278 | first_bucket_time = |
| 279 | CONFIG_MSM_IDLE_STATS_FIRST_BUCKET; |
| 280 | |
| 281 | stats[MSM_PM_STAT_SUSPEND].name = "suspend"; |
| 282 | stats[MSM_PM_STAT_SUSPEND].first_bucket_time = |
| 283 | CONFIG_MSM_SUSPEND_STATS_FIRST_BUCKET; |
| 284 | |
| 285 | stats[MSM_PM_STAT_FAILED_SUSPEND].name = "failed-suspend"; |
| 286 | stats[MSM_PM_STAT_FAILED_SUSPEND].first_bucket_time = |
| 287 | CONFIG_MSM_IDLE_STATS_FIRST_BUCKET; |
| 288 | |
| 289 | stats[MSM_PM_STAT_NOT_IDLE].name = "not-idle"; |
| 290 | stats[MSM_PM_STAT_NOT_IDLE].first_bucket_time = |
| 291 | CONFIG_MSM_IDLE_STATS_FIRST_BUCKET; |
| 292 | |
| 293 | for (i = 0; i < size; i++) |
| 294 | stats[enable_stats[i]].enabled = true; |
| 295 | |
| 296 | } |
| 297 | |
| 298 | d_entry = create_proc_entry("msm_pm_stats", |
| 299 | S_IRUGO | S_IWUSR | S_IWGRP, NULL); |
| 300 | if (d_entry) { |
| 301 | d_entry->read_proc = msm_pm_read_proc; |
| 302 | d_entry->write_proc = msm_pm_write_proc; |
| 303 | d_entry->data = NULL; |
| 304 | } |
| 305 | } |