root/tools/power/cpupower/utils/idle_monitor/cpuidle_sysfs.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 *  (C) 2010,2011       Thomas Renninger <trenn@suse.de>, Novell Inc
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <limits.h>
#include <cpuidle.h>

#include "helpers/helpers.h"
#include "idle_monitor/cpupower-monitor.h"

#define CPUIDLE_STATES_MAX 10
static cstate_t cpuidle_cstates[CPUIDLE_STATES_MAX];
struct cpuidle_monitor cpuidle_sysfs_monitor;

static unsigned long long **previous_count;
static unsigned long long **current_count;
static struct timespec start_time;
static unsigned long long timediff;

static int cpuidle_get_count_percent(unsigned int id, double *percent,
                                     unsigned int cpu)
{
        unsigned long long statediff = current_count[cpu][id]
                - previous_count[cpu][id];
        dprint("%s: - diff: %llu - percent: %f (%u)\n",
               cpuidle_cstates[id].name, timediff, *percent, cpu);

        if (timediff == 0)
                *percent = 0.0;
        else
                *percent = ((100.0 * statediff) / timediff);

        dprint("%s: - timediff: %llu - statediff: %llu - percent: %f (%u)\n",
               cpuidle_cstates[id].name, timediff, statediff, *percent, cpu);

        return 0;
}

static int cpuidle_start(void)
{
        int cpu, state;
        clock_gettime(CLOCK_REALTIME, &start_time);
        for (cpu = 0; cpu < cpu_count; cpu++) {
                for (state = 0; state < cpuidle_sysfs_monitor.hw_states_num;
                     state++) {
                        previous_count[cpu][state] =
                                cpuidle_state_time(cpu, state);
                        dprint("CPU %d - State: %d - Val: %llu\n",
                               cpu, state, previous_count[cpu][state]);
                }
        }
        return 0;
}

static int cpuidle_stop(void)
{
        int cpu, state;
        struct timespec end_time;
        clock_gettime(CLOCK_REALTIME, &end_time);
        timediff = timespec_diff_us(start_time, end_time);

        for (cpu = 0; cpu < cpu_count; cpu++) {
                for (state = 0; state < cpuidle_sysfs_monitor.hw_states_num;
                     state++) {
                        current_count[cpu][state] =
                                cpuidle_state_time(cpu, state);
                        dprint("CPU %d - State: %d - Val: %llu\n",
                               cpu, state, current_count[cpu][state]);
                }
        }
        return 0;
}

void fix_up_intel_idle_driver_name(char *tmp, int num)
{
        /* fix up cpuidle name for intel idle driver */
        if (!strncmp(tmp, "NHM-", 4)) {
                switch (num) {
                case 1:
                        strcpy(tmp, "C1");
                        break;
                case 2:
                        strcpy(tmp, "C3");
                        break;
                case 3:
                        strcpy(tmp, "C6");
                        break;
                }
        } else if (!strncmp(tmp, "SNB-", 4)) {
                switch (num) {
                case 1:
                        strcpy(tmp, "C1");
                        break;
                case 2:
                        strcpy(tmp, "C3");
                        break;
                case 3:
                        strcpy(tmp, "C6");
                        break;
                case 4:
                        strcpy(tmp, "C7");
                        break;
                }
        } else if (!strncmp(tmp, "ATM-", 4)) {
                switch (num) {
                case 1:
                        strcpy(tmp, "C1");
                        break;
                case 2:
                        strcpy(tmp, "C2");
                        break;
                case 3:
                        strcpy(tmp, "C4");
                        break;
                case 4:
                        strcpy(tmp, "C6");
                        break;
                }
        }
}

#ifdef __powerpc__
void map_power_idle_state_name(char *tmp)
{
        if (!strncmp(tmp, "stop0_lite", CSTATE_NAME_LEN))
                strcpy(tmp, "stop0L");
        else if (!strncmp(tmp, "stop1_lite", CSTATE_NAME_LEN))
                strcpy(tmp, "stop1L");
        else if (!strncmp(tmp, "stop2_lite", CSTATE_NAME_LEN))
                strcpy(tmp, "stop2L");
}
#else
void map_power_idle_state_name(char *tmp) { }
#endif

static struct cpuidle_monitor *cpuidle_register(void)
{
        int num;
        char *tmp;
        int this_cpu;

        this_cpu = sched_getcpu();

        /* Assume idle state count is the same for all CPUs */
        cpuidle_sysfs_monitor.hw_states_num = cpuidle_state_count(this_cpu);

        if (cpuidle_sysfs_monitor.hw_states_num <= 0)
                return NULL;

        for (num = 0; num < cpuidle_sysfs_monitor.hw_states_num; num++) {
                tmp = cpuidle_state_name(this_cpu, num);
                if (tmp == NULL)
                        continue;

                map_power_idle_state_name(tmp);
                fix_up_intel_idle_driver_name(tmp, num);
                strncpy(cpuidle_cstates[num].name, tmp, CSTATE_NAME_LEN - 1);
                free(tmp);

                tmp = cpuidle_state_desc(this_cpu, num);
                if (tmp == NULL)
                        continue;
                strncpy(cpuidle_cstates[num].desc, tmp, CSTATE_DESC_LEN - 1);
                free(tmp);

                cpuidle_cstates[num].range = RANGE_THREAD;
                cpuidle_cstates[num].id = num;
                cpuidle_cstates[num].get_count_percent =
                        cpuidle_get_count_percent;
        }

        /* Free this at program termination */
        previous_count = malloc(sizeof(long long *) * cpu_count);
        current_count = malloc(sizeof(long long *) * cpu_count);
        for (num = 0; num < cpu_count; num++) {
                previous_count[num] = malloc(sizeof(long long) *
                                        cpuidle_sysfs_monitor.hw_states_num);
                current_count[num] = malloc(sizeof(long long) *
                                        cpuidle_sysfs_monitor.hw_states_num);
        }

        cpuidle_sysfs_monitor.name_len = strlen(cpuidle_sysfs_monitor.name);
        return &cpuidle_sysfs_monitor;
}

void cpuidle_unregister(void)
{
        int num;

        for (num = 0; num < cpu_count; num++) {
                free(previous_count[num]);
                free(current_count[num]);
        }
        free(previous_count);
        free(current_count);
}

struct cpuidle_monitor cpuidle_sysfs_monitor = {
        .name                   = "Idle_Stats",
        .hw_states              = cpuidle_cstates,
        .start                  = cpuidle_start,
        .stop                   = cpuidle_stop,
        .do_register            = cpuidle_register,
        .unregister             = cpuidle_unregister,
        .flags.needs_root       = 0,
        .overflow_s             = UINT_MAX,
};