root/tools/power/cpupower/utils/helpers/sysfs.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 *  (C) 2004-2009  Dominik Brodowski <linux@dominikbrodowski.de>
 *  (C) 2011       Thomas Renninger <trenn@novell.com> Novell Inc.
 */

#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#include "helpers/sysfs.h"

unsigned int sysfs_read_file(const char *path, char *buf, size_t buflen)
{
        int fd;
        ssize_t numread;

        fd = open(path, O_RDONLY);
        if (fd == -1)
                return 0;

        numread = read(fd, buf, buflen - 1);
        if (numread < 1) {
                close(fd);
                return 0;
        }

        buf[numread] = '\0';
        close(fd);

        return (unsigned int) numread;
}

/*
 * Detect whether a CPU is online
 *
 * Returns:
 *     1 -> if CPU is online
 *     0 -> if CPU is offline
 *     negative errno values in error case
 */
int sysfs_is_cpu_online(unsigned int cpu)
{
        char path[SYSFS_PATH_MAX];
        int fd;
        ssize_t numread;
        unsigned long long value;
        char linebuf[MAX_LINE_LEN];
        char *endp;
        struct stat statbuf;

        snprintf(path, sizeof(path), PATH_TO_CPU "cpu%u", cpu);

        if (stat(path, &statbuf) != 0)
                return 0;

        /*
         * kernel without CONFIG_HOTPLUG_CPU
         * -> cpuX directory exists, but not cpuX/online file
         */
        snprintf(path, sizeof(path), PATH_TO_CPU "cpu%u/online", cpu);
        if (stat(path, &statbuf) != 0)
                return 1;

        fd = open(path, O_RDONLY);
        if (fd == -1)
                return -errno;

        numread = read(fd, linebuf, MAX_LINE_LEN - 1);
        if (numread < 1) {
                close(fd);
                return -EIO;
        }
        linebuf[numread] = '\0';
        close(fd);

        value = strtoull(linebuf, &endp, 0);
        if (value > 1)
                return -EINVAL;

        return value;
}

/* CPUidle idlestate specific /sys/devices/system/cpu/cpuX/cpuidle/ access */


/* CPUidle idlestate specific /sys/devices/system/cpu/cpuX/cpuidle/ access */

/*
 * helper function to check whether a file under "../cpuX/cpuidle/stateX/" dir
 * exists.
 * For example the functionality to disable c-states was introduced in later
 * kernel versions, this function can be used to explicitly check for this
 * feature.
 *
 * returns 1 if the file exists, 0 otherwise.
 */
unsigned int sysfs_idlestate_file_exists(unsigned int cpu,
                                         unsigned int idlestate,
                                         const char *fname)
{
        char path[SYSFS_PATH_MAX];
        struct stat statbuf;


        snprintf(path, sizeof(path), PATH_TO_CPU "cpu%u/cpuidle/state%u/%s",
                 cpu, idlestate, fname);
        if (stat(path, &statbuf) != 0)
                return 0;
        return 1;
}

/*
 * helper function to read file from /sys into given buffer
 * fname is a relative path under "cpuX/cpuidle/stateX/" dir
 * cstates starting with 0, C0 is not counted as cstate.
 * This means if you want C1 info, pass 0 as idlestate param
 */
unsigned int sysfs_idlestate_read_file(unsigned int cpu, unsigned int idlestate,
                             const char *fname, char *buf, size_t buflen)
{
        char path[SYSFS_PATH_MAX];
        int fd;
        ssize_t numread;

        snprintf(path, sizeof(path), PATH_TO_CPU "cpu%u/cpuidle/state%u/%s",
                 cpu, idlestate, fname);

        fd = open(path, O_RDONLY);
        if (fd == -1)
                return 0;

        numread = read(fd, buf, buflen - 1);
        if (numread < 1) {
                close(fd);
                return 0;
        }

        buf[numread] = '\0';
        close(fd);

        return (unsigned int) numread;
}

/* 
 * helper function to write a new value to a /sys file
 * fname is a relative path under "../cpuX/cpuidle/cstateY/" dir
 *
 * Returns the number of bytes written or 0 on error
 */
static
unsigned int sysfs_idlestate_write_file(unsigned int cpu,
                                        unsigned int idlestate,
                                        const char *fname,
                                        const char *value, size_t len)
{
        char path[SYSFS_PATH_MAX];
        int fd;
        ssize_t numwrite;

        snprintf(path, sizeof(path), PATH_TO_CPU "cpu%u/cpuidle/state%u/%s",
                 cpu, idlestate, fname);

        fd = open(path, O_WRONLY);
        if (fd == -1)
                return 0;

        numwrite = write(fd, value, len);
        if (numwrite < 1) {
                close(fd);
                return 0;
        }

        close(fd);

        return (unsigned int) numwrite;
}

/* read access to files which contain one numeric value */

enum idlestate_value {
        IDLESTATE_USAGE,
        IDLESTATE_POWER,
        IDLESTATE_LATENCY,
        IDLESTATE_TIME,
        IDLESTATE_DISABLE,
        MAX_IDLESTATE_VALUE_FILES
};

static const char *idlestate_value_files[MAX_IDLESTATE_VALUE_FILES] = {
        [IDLESTATE_USAGE] = "usage",
        [IDLESTATE_POWER] = "power",
        [IDLESTATE_LATENCY] = "latency",
        [IDLESTATE_TIME]  = "time",
        [IDLESTATE_DISABLE]  = "disable",
};

static unsigned long long sysfs_idlestate_get_one_value(unsigned int cpu,
                                                     unsigned int idlestate,
                                                     enum idlestate_value which)
{
        unsigned long long value;
        unsigned int len;
        char linebuf[MAX_LINE_LEN];
        char *endp;

        if (which >= MAX_IDLESTATE_VALUE_FILES)
                return 0;

        len = sysfs_idlestate_read_file(cpu, idlestate,
                                        idlestate_value_files[which],
                                        linebuf, sizeof(linebuf));
        if (len == 0)
                return 0;

        value = strtoull(linebuf, &endp, 0);

        if (endp == linebuf || errno == ERANGE)
                return 0;

        return value;
}

/* read access to files which contain one string */

enum idlestate_string {
        IDLESTATE_DESC,
        IDLESTATE_NAME,
        MAX_IDLESTATE_STRING_FILES
};

static const char *idlestate_string_files[MAX_IDLESTATE_STRING_FILES] = {
        [IDLESTATE_DESC] = "desc",
        [IDLESTATE_NAME] = "name",
};


static char *sysfs_idlestate_get_one_string(unsigned int cpu,
                                        unsigned int idlestate,
                                        enum idlestate_string which)
{
        char linebuf[MAX_LINE_LEN];
        char *result;
        unsigned int len;

        if (which >= MAX_IDLESTATE_STRING_FILES)
                return NULL;

        len = sysfs_idlestate_read_file(cpu, idlestate,
                                        idlestate_string_files[which],
                                        linebuf, sizeof(linebuf));
        if (len == 0)
                return NULL;

        result = strdup(linebuf);
        if (result == NULL)
                return NULL;

        if (result[strlen(result) - 1] == '\n')
                result[strlen(result) - 1] = '\0';

        return result;
}

/*
 * Returns:
 *    1  if disabled
 *    0  if enabled
 *    -1 if idlestate is not available
 *    -2 if disabling is not supported by the kernel
 */
int sysfs_is_idlestate_disabled(unsigned int cpu,
                                unsigned int idlestate)
{
        if (sysfs_get_idlestate_count(cpu) <= idlestate)
                return -1;

        if (!sysfs_idlestate_file_exists(cpu, idlestate,
                                 idlestate_value_files[IDLESTATE_DISABLE]))
                return -2;
        return sysfs_idlestate_get_one_value(cpu, idlestate, IDLESTATE_DISABLE);
}

/*
 * Pass 1 as last argument to disable or 0 to enable the state
 * Returns:
 *    0  on success
 *    negative values on error, for example:
 *      -1 if idlestate is not available
 *      -2 if disabling is not supported by the kernel
 *      -3 No write access to disable/enable C-states
 */
int sysfs_idlestate_disable(unsigned int cpu,
                            unsigned int idlestate,
                            unsigned int disable)
{
        char value[SYSFS_PATH_MAX];
        int bytes_written;

        if (sysfs_get_idlestate_count(cpu) <= idlestate)
                return -1;

        if (!sysfs_idlestate_file_exists(cpu, idlestate,
                                 idlestate_value_files[IDLESTATE_DISABLE]))
                return -2;

        snprintf(value, SYSFS_PATH_MAX, "%u", disable);

        bytes_written = sysfs_idlestate_write_file(cpu, idlestate, "disable",
                                                   value, sizeof(disable));
        if (bytes_written)
                return 0;
        return -3;
}

unsigned long sysfs_get_idlestate_latency(unsigned int cpu,
                                          unsigned int idlestate)
{
        return sysfs_idlestate_get_one_value(cpu, idlestate, IDLESTATE_LATENCY);
}

unsigned long sysfs_get_idlestate_usage(unsigned int cpu,
                                        unsigned int idlestate)
{
        return sysfs_idlestate_get_one_value(cpu, idlestate, IDLESTATE_USAGE);
}

unsigned long long sysfs_get_idlestate_time(unsigned int cpu,
                                        unsigned int idlestate)
{
        return sysfs_idlestate_get_one_value(cpu, idlestate, IDLESTATE_TIME);
}

char *sysfs_get_idlestate_name(unsigned int cpu, unsigned int idlestate)
{
        return sysfs_idlestate_get_one_string(cpu, idlestate, IDLESTATE_NAME);
}

char *sysfs_get_idlestate_desc(unsigned int cpu, unsigned int idlestate)
{
        return sysfs_idlestate_get_one_string(cpu, idlestate, IDLESTATE_DESC);
}

/*
 * Returns number of supported C-states of CPU core cpu
 * Negativ in error case
 * Zero if cpuidle does not export any C-states
 */
unsigned int sysfs_get_idlestate_count(unsigned int cpu)
{
        char file[SYSFS_PATH_MAX];
        struct stat statbuf;
        int idlestates = 1;


        snprintf(file, SYSFS_PATH_MAX, PATH_TO_CPU "cpuidle");
        if (stat(file, &statbuf) != 0 || !S_ISDIR(statbuf.st_mode))
                return 0;

        snprintf(file, SYSFS_PATH_MAX, PATH_TO_CPU "cpu%u/cpuidle/state0", cpu);
        if (stat(file, &statbuf) != 0 || !S_ISDIR(statbuf.st_mode))
                return 0;

        while (stat(file, &statbuf) == 0 && S_ISDIR(statbuf.st_mode)) {
                snprintf(file, SYSFS_PATH_MAX, PATH_TO_CPU
                         "cpu%u/cpuidle/state%d", cpu, idlestates);
                idlestates++;
        }
        idlestates--;
        return idlestates;
}

/* CPUidle general /sys/devices/system/cpu/cpuidle/ sysfs access ********/

/*
 * helper function to read file from /sys into given buffer
 * fname is a relative path under "cpu/cpuidle/" dir
 */
static unsigned int sysfs_cpuidle_read_file(const char *fname, char *buf,
                                            size_t buflen)
{
        char path[SYSFS_PATH_MAX];

        snprintf(path, sizeof(path), PATH_TO_CPU "cpuidle/%s", fname);

        return sysfs_read_file(path, buf, buflen);
}



/* read access to files which contain one string */

enum cpuidle_string {
        CPUIDLE_GOVERNOR,
        CPUIDLE_GOVERNOR_RO,
        CPUIDLE_DRIVER,
        MAX_CPUIDLE_STRING_FILES
};

static const char *cpuidle_string_files[MAX_CPUIDLE_STRING_FILES] = {
        [CPUIDLE_GOVERNOR]      = "current_governor",
        [CPUIDLE_GOVERNOR_RO]   = "current_governor_ro",
        [CPUIDLE_DRIVER]        = "current_driver",
};


static char *sysfs_cpuidle_get_one_string(enum cpuidle_string which)
{
        char linebuf[MAX_LINE_LEN];
        char *result;
        unsigned int len;

        if (which >= MAX_CPUIDLE_STRING_FILES)
                return NULL;

        len = sysfs_cpuidle_read_file(cpuidle_string_files[which],
                                linebuf, sizeof(linebuf));
        if (len == 0)
                return NULL;

        result = strdup(linebuf);
        if (result == NULL)
                return NULL;

        if (result[strlen(result) - 1] == '\n')
                result[strlen(result) - 1] = '\0';

        return result;
}

char *sysfs_get_cpuidle_governor(void)
{
        char *tmp = sysfs_cpuidle_get_one_string(CPUIDLE_GOVERNOR_RO);
        if (!tmp)
                return sysfs_cpuidle_get_one_string(CPUIDLE_GOVERNOR);
        else
                return tmp;
}

char *sysfs_get_cpuidle_driver(void)
{
        return sysfs_cpuidle_get_one_string(CPUIDLE_DRIVER);
}
/* CPUidle idlestate specific /sys/devices/system/cpu/cpuX/cpuidle/ access */

/*
 * Get sched_mc or sched_smt settings
 * Pass "mc" or "smt" as argument
 *
 * Returns negative value on failure
 */
int sysfs_get_sched(const char *smt_mc)
{
        return -ENODEV;
}

/*
 * Get sched_mc or sched_smt settings
 * Pass "mc" or "smt" as argument
 *
 * Returns negative value on failure
 */
int sysfs_set_sched(const char *smt_mc, int val)
{
        return -ENODEV;
}