linux/drivers/cpufreq/cpufreq_stats.c
<<
>>
Prefs
   1/*
   2 *  drivers/cpufreq/cpufreq_stats.c
   3 *
   4 *  Copyright (C) 2003-2004 Venkatesh Pallipadi <venkatesh.pallipadi@intel.com>.
   5 *  (C) 2004 Zou Nan hai <nanhai.zou@intel.com>.
   6 *
   7 * This program is free software; you can redistribute it and/or modify
   8 * it under the terms of the GNU General Public License version 2 as
   9 * published by the Free Software Foundation.
  10 */
  11
  12#include <linux/kernel.h>
  13#include <linux/sysdev.h>
  14#include <linux/cpu.h>
  15#include <linux/sysfs.h>
  16#include <linux/cpufreq.h>
  17#include <linux/jiffies.h>
  18#include <linux/percpu.h>
  19#include <linux/kobject.h>
  20#include <linux/spinlock.h>
  21#include <linux/notifier.h>
  22#include <asm/cputime.h>
  23
  24static spinlock_t cpufreq_stats_lock;
  25
  26#define CPUFREQ_STATDEVICE_ATTR(_name, _mode, _show) \
  27static struct freq_attr _attr_##_name = {\
  28        .attr = {.name = __stringify(_name), .mode = _mode, }, \
  29        .show = _show,\
  30};
  31
  32struct cpufreq_stats {
  33        unsigned int cpu;
  34        unsigned int total_trans;
  35        unsigned long long  last_time;
  36        unsigned int max_state;
  37        unsigned int state_num;
  38        unsigned int last_index;
  39        cputime64_t *time_in_state;
  40        unsigned int *freq_table;
  41#ifdef CONFIG_CPU_FREQ_STAT_DETAILS
  42        unsigned int *trans_table;
  43#endif
  44};
  45
  46static DEFINE_PER_CPU(struct cpufreq_stats *, cpufreq_stats_table);
  47
  48struct cpufreq_stats_attribute {
  49        struct attribute attr;
  50        ssize_t(*show) (struct cpufreq_stats *, char *);
  51};
  52
  53static int cpufreq_stats_update(unsigned int cpu)
  54{
  55        struct cpufreq_stats *stat;
  56        unsigned long long cur_time;
  57
  58        cur_time = get_jiffies_64();
  59        spin_lock(&cpufreq_stats_lock);
  60        stat = per_cpu(cpufreq_stats_table, cpu);
  61        if (stat->time_in_state)
  62                stat->time_in_state[stat->last_index] =
  63                        cputime64_add(stat->time_in_state[stat->last_index],
  64                                      cputime_sub(cur_time, stat->last_time));
  65        stat->last_time = cur_time;
  66        spin_unlock(&cpufreq_stats_lock);
  67        return 0;
  68}
  69
  70static ssize_t show_total_trans(struct cpufreq_policy *policy, char *buf)
  71{
  72        struct cpufreq_stats *stat = per_cpu(cpufreq_stats_table, policy->cpu);
  73        if (!stat)
  74                return 0;
  75        return sprintf(buf, "%d\n",
  76                        per_cpu(cpufreq_stats_table, stat->cpu)->total_trans);
  77}
  78
  79static ssize_t show_time_in_state(struct cpufreq_policy *policy, char *buf)
  80{
  81        ssize_t len = 0;
  82        int i;
  83        struct cpufreq_stats *stat = per_cpu(cpufreq_stats_table, policy->cpu);
  84        if (!stat)
  85                return 0;
  86        cpufreq_stats_update(stat->cpu);
  87        for (i = 0; i < stat->state_num; i++) {
  88                len += sprintf(buf + len, "%u %llu\n", stat->freq_table[i],
  89                        (unsigned long long)
  90                        cputime64_to_clock_t(stat->time_in_state[i]));
  91        }
  92        return len;
  93}
  94
  95#ifdef CONFIG_CPU_FREQ_STAT_DETAILS
  96static ssize_t show_trans_table(struct cpufreq_policy *policy, char *buf)
  97{
  98        ssize_t len = 0;
  99        int i, j;
 100
 101        struct cpufreq_stats *stat = per_cpu(cpufreq_stats_table, policy->cpu);
 102        if (!stat)
 103                return 0;
 104        cpufreq_stats_update(stat->cpu);
 105        len += snprintf(buf + len, PAGE_SIZE - len, "   From  :    To\n");
 106        len += snprintf(buf + len, PAGE_SIZE - len, "         : ");
 107        for (i = 0; i < stat->state_num; i++) {
 108                if (len >= PAGE_SIZE)
 109                        break;
 110                len += snprintf(buf + len, PAGE_SIZE - len, "%9u ",
 111                                stat->freq_table[i]);
 112        }
 113        if (len >= PAGE_SIZE)
 114                return PAGE_SIZE;
 115
 116        len += snprintf(buf + len, PAGE_SIZE - len, "\n");
 117
 118        for (i = 0; i < stat->state_num; i++) {
 119                if (len >= PAGE_SIZE)
 120                        break;
 121
 122                len += snprintf(buf + len, PAGE_SIZE - len, "%9u: ",
 123                                stat->freq_table[i]);
 124
 125                for (j = 0; j < stat->state_num; j++)   {
 126                        if (len >= PAGE_SIZE)
 127                                break;
 128                        len += snprintf(buf + len, PAGE_SIZE - len, "%9u ",
 129                                        stat->trans_table[i*stat->max_state+j]);
 130                }
 131                if (len >= PAGE_SIZE)
 132                        break;
 133                len += snprintf(buf + len, PAGE_SIZE - len, "\n");
 134        }
 135        if (len >= PAGE_SIZE)
 136                return PAGE_SIZE;
 137        return len;
 138}
 139CPUFREQ_STATDEVICE_ATTR(trans_table, 0444, show_trans_table);
 140#endif
 141
 142CPUFREQ_STATDEVICE_ATTR(total_trans, 0444, show_total_trans);
 143CPUFREQ_STATDEVICE_ATTR(time_in_state, 0444, show_time_in_state);
 144
 145static struct attribute *default_attrs[] = {
 146        &_attr_total_trans.attr,
 147        &_attr_time_in_state.attr,
 148#ifdef CONFIG_CPU_FREQ_STAT_DETAILS
 149        &_attr_trans_table.attr,
 150#endif
 151        NULL
 152};
 153static struct attribute_group stats_attr_group = {
 154        .attrs = default_attrs,
 155        .name = "stats"
 156};
 157
 158static int freq_table_get_index(struct cpufreq_stats *stat, unsigned int freq)
 159{
 160        int index;
 161        for (index = 0; index < stat->max_state; index++)
 162                if (stat->freq_table[index] == freq)
 163                        return index;
 164        return -1;
 165}
 166
 167static void cpufreq_stats_free_table(unsigned int cpu)
 168{
 169        struct cpufreq_stats *stat = per_cpu(cpufreq_stats_table, cpu);
 170        struct cpufreq_policy *policy = cpufreq_cpu_get(cpu);
 171        if (policy && policy->cpu == cpu)
 172                sysfs_remove_group(&policy->kobj, &stats_attr_group);
 173        if (stat) {
 174                kfree(stat->time_in_state);
 175                kfree(stat);
 176        }
 177        per_cpu(cpufreq_stats_table, cpu) = NULL;
 178        if (policy)
 179                cpufreq_cpu_put(policy);
 180}
 181
 182static int cpufreq_stats_create_table(struct cpufreq_policy *policy,
 183                struct cpufreq_frequency_table *table)
 184{
 185        unsigned int i, j, count = 0, ret = 0;
 186        struct cpufreq_stats *stat;
 187        struct cpufreq_policy *data;
 188        unsigned int alloc_size;
 189        unsigned int cpu = policy->cpu;
 190        if (per_cpu(cpufreq_stats_table, cpu))
 191                return -EBUSY;
 192        stat = kzalloc(sizeof(struct cpufreq_stats), GFP_KERNEL);
 193        if ((stat) == NULL)
 194                return -ENOMEM;
 195
 196        data = cpufreq_cpu_get(cpu);
 197        if (data == NULL) {
 198                ret = -EINVAL;
 199                goto error_get_fail;
 200        }
 201
 202        ret = sysfs_create_group(&data->kobj, &stats_attr_group);
 203        if (ret)
 204                goto error_out;
 205
 206        stat->cpu = cpu;
 207        per_cpu(cpufreq_stats_table, cpu) = stat;
 208
 209        for (i = 0; table[i].frequency != CPUFREQ_TABLE_END; i++) {
 210                unsigned int freq = table[i].frequency;
 211                if (freq == CPUFREQ_ENTRY_INVALID)
 212                        continue;
 213                count++;
 214        }
 215
 216        alloc_size = count * sizeof(int) + count * sizeof(cputime64_t);
 217
 218#ifdef CONFIG_CPU_FREQ_STAT_DETAILS
 219        alloc_size += count * count * sizeof(int);
 220#endif
 221        stat->max_state = count;
 222        stat->time_in_state = kzalloc(alloc_size, GFP_KERNEL);
 223        if (!stat->time_in_state) {
 224                ret = -ENOMEM;
 225                goto error_out;
 226        }
 227        stat->freq_table = (unsigned int *)(stat->time_in_state + count);
 228
 229#ifdef CONFIG_CPU_FREQ_STAT_DETAILS
 230        stat->trans_table = stat->freq_table + count;
 231#endif
 232        j = 0;
 233        for (i = 0; table[i].frequency != CPUFREQ_TABLE_END; i++) {
 234                unsigned int freq = table[i].frequency;
 235                if (freq == CPUFREQ_ENTRY_INVALID)
 236                        continue;
 237                if (freq_table_get_index(stat, freq) == -1)
 238                        stat->freq_table[j++] = freq;
 239        }
 240        stat->state_num = j;
 241        spin_lock(&cpufreq_stats_lock);
 242        stat->last_time = get_jiffies_64();
 243        stat->last_index = freq_table_get_index(stat, policy->cur);
 244        spin_unlock(&cpufreq_stats_lock);
 245        cpufreq_cpu_put(data);
 246        return 0;
 247error_out:
 248        cpufreq_cpu_put(data);
 249error_get_fail:
 250        kfree(stat);
 251        per_cpu(cpufreq_stats_table, cpu) = NULL;
 252        return ret;
 253}
 254
 255static int cpufreq_stat_notifier_policy(struct notifier_block *nb,
 256                unsigned long val, void *data)
 257{
 258        int ret;
 259        struct cpufreq_policy *policy = data;
 260        struct cpufreq_frequency_table *table;
 261        unsigned int cpu = policy->cpu;
 262        if (val != CPUFREQ_NOTIFY)
 263                return 0;
 264        table = cpufreq_frequency_get_table(cpu);
 265        if (!table)
 266                return 0;
 267        ret = cpufreq_stats_create_table(policy, table);
 268        if (ret)
 269                return ret;
 270        return 0;
 271}
 272
 273static int cpufreq_stat_notifier_trans(struct notifier_block *nb,
 274                unsigned long val, void *data)
 275{
 276        struct cpufreq_freqs *freq = data;
 277        struct cpufreq_stats *stat;
 278        int old_index, new_index;
 279
 280        if (val != CPUFREQ_POSTCHANGE)
 281                return 0;
 282
 283        stat = per_cpu(cpufreq_stats_table, freq->cpu);
 284        if (!stat)
 285                return 0;
 286
 287        old_index = stat->last_index;
 288        new_index = freq_table_get_index(stat, freq->new);
 289
 290        cpufreq_stats_update(freq->cpu);
 291        if (old_index == new_index)
 292                return 0;
 293
 294        if (old_index == -1 || new_index == -1)
 295                return 0;
 296
 297        spin_lock(&cpufreq_stats_lock);
 298        stat->last_index = new_index;
 299#ifdef CONFIG_CPU_FREQ_STAT_DETAILS
 300        stat->trans_table[old_index * stat->max_state + new_index]++;
 301#endif
 302        stat->total_trans++;
 303        spin_unlock(&cpufreq_stats_lock);
 304        return 0;
 305}
 306
 307static int __cpuinit cpufreq_stat_cpu_callback(struct notifier_block *nfb,
 308                                               unsigned long action,
 309                                               void *hcpu)
 310{
 311        unsigned int cpu = (unsigned long)hcpu;
 312
 313        switch (action) {
 314        case CPU_ONLINE:
 315        case CPU_ONLINE_FROZEN:
 316                cpufreq_update_policy(cpu);
 317                break;
 318        case CPU_DEAD:
 319        case CPU_DEAD_FROZEN:
 320                cpufreq_stats_free_table(cpu);
 321                break;
 322        }
 323        return NOTIFY_OK;
 324}
 325
 326static struct notifier_block cpufreq_stat_cpu_notifier __refdata =
 327{
 328        .notifier_call = cpufreq_stat_cpu_callback,
 329};
 330
 331static struct notifier_block notifier_policy_block = {
 332        .notifier_call = cpufreq_stat_notifier_policy
 333};
 334
 335static struct notifier_block notifier_trans_block = {
 336        .notifier_call = cpufreq_stat_notifier_trans
 337};
 338
 339static int __init cpufreq_stats_init(void)
 340{
 341        int ret;
 342        unsigned int cpu;
 343
 344        spin_lock_init(&cpufreq_stats_lock);
 345        ret = cpufreq_register_notifier(&notifier_policy_block,
 346                                CPUFREQ_POLICY_NOTIFIER);
 347        if (ret)
 348                return ret;
 349
 350        ret = cpufreq_register_notifier(&notifier_trans_block,
 351                                CPUFREQ_TRANSITION_NOTIFIER);
 352        if (ret) {
 353                cpufreq_unregister_notifier(&notifier_policy_block,
 354                                CPUFREQ_POLICY_NOTIFIER);
 355                return ret;
 356        }
 357
 358        register_hotcpu_notifier(&cpufreq_stat_cpu_notifier);
 359        for_each_online_cpu(cpu) {
 360                cpufreq_update_policy(cpu);
 361        }
 362        return 0;
 363}
 364static void __exit cpufreq_stats_exit(void)
 365{
 366        unsigned int cpu;
 367
 368        cpufreq_unregister_notifier(&notifier_policy_block,
 369                        CPUFREQ_POLICY_NOTIFIER);
 370        cpufreq_unregister_notifier(&notifier_trans_block,
 371                        CPUFREQ_TRANSITION_NOTIFIER);
 372        unregister_hotcpu_notifier(&cpufreq_stat_cpu_notifier);
 373        for_each_online_cpu(cpu) {
 374                cpufreq_stats_free_table(cpu);
 375        }
 376}
 377
 378MODULE_AUTHOR("Zou Nan hai <nanhai.zou@intel.com>");
 379MODULE_DESCRIPTION("'cpufreq_stats' - A driver to export cpufreq stats "
 380                                "through sysfs filesystem");
 381MODULE_LICENSE("GPL");
 382
 383module_init(cpufreq_stats_init);
 384module_exit(cpufreq_stats_exit);
 385