root/tools/power/cpupower/lib/powercap.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 *  (C) 2016 SUSE Software Solutions GmbH
 *           Thomas Renninger <trenn@suse.de>
 */

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

#include "powercap.h"

static 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;
}

static int sysfs_get_enabled(char *path, int *mode)
{
        int fd;
        char yes_no;
        int ret = 0;

        *mode = 0;

        fd = open(path, O_RDONLY);
        if (fd == -1) {
                ret = -1;
                goto out;
        }

        if (read(fd, &yes_no, 1) != 1) {
                ret = -1;
                goto out_close;
        }

        if (yes_no == '1') {
                *mode = 1;
                goto out_close;
        } else if (yes_no == '0') {
                goto out_close;
        } else {
                ret = -1;
                goto out_close;
        }
out_close:
        close(fd);
out:
        return ret;
}

int powercap_get_enabled(int *mode)
{
        char path[SYSFS_PATH_MAX] = PATH_TO_POWERCAP "/intel-rapl/enabled";

        return sysfs_get_enabled(path, mode);
}

/*
 * TODO: implement function. Returns dummy 0 for now.
 */
int powercap_set_enabled(int mode)
{
        return 0;
}

/*
 * Hardcoded, because rapl is the only powercap implementation
- * this needs to get more generic if more powercap implementations
 * should show up
 */
int powercap_get_driver(char *driver, int buflen)
{
        char file[SYSFS_PATH_MAX] = PATH_TO_RAPL;

        struct stat statbuf;

        if (stat(file, &statbuf) != 0 || !S_ISDIR(statbuf.st_mode)) {
                driver = "";
                return -1;
        } else if (buflen > 10) {
                strcpy(driver, "intel-rapl");
                return 0;
        } else
                return -1;
}

enum powercap_get64 {
        GET_ENERGY_UJ,
        GET_MAX_ENERGY_RANGE_UJ,
        GET_POWER_UW,
        GET_MAX_POWER_RANGE_UW,
        MAX_GET_64_FILES
};

static const char *powercap_get64_files[MAX_GET_64_FILES] = {
        [GET_POWER_UW] = "power_uw",
        [GET_MAX_POWER_RANGE_UW] = "max_power_range_uw",
        [GET_ENERGY_UJ] = "energy_uj",
        [GET_MAX_ENERGY_RANGE_UJ] = "max_energy_range_uj",
};

static int sysfs_powercap_get64_val(struct powercap_zone *zone,
                                      enum powercap_get64 which,
                                      uint64_t *val)
{
        char file[SYSFS_PATH_MAX] = PATH_TO_POWERCAP "/";
        int ret;
        char buf[MAX_LINE_LEN];

        strcat(file, zone->sys_name);
        strcat(file, "/");
        strcat(file, powercap_get64_files[which]);

        ret = sysfs_read_file(file, buf, MAX_LINE_LEN);
        if (ret < 0)
                return ret;
        if (ret == 0)
                return -1;

        *val = strtoll(buf, NULL, 10);
        return 0;
}

int powercap_get_max_energy_range_uj(struct powercap_zone *zone, uint64_t *val)
{
        return sysfs_powercap_get64_val(zone, GET_MAX_ENERGY_RANGE_UJ, val);
}

int powercap_get_energy_uj(struct powercap_zone *zone, uint64_t *val)
{
        return sysfs_powercap_get64_val(zone, GET_ENERGY_UJ, val);
}

int powercap_get_max_power_range_uw(struct powercap_zone *zone, uint64_t *val)
{
        return sysfs_powercap_get64_val(zone, GET_MAX_POWER_RANGE_UW, val);
}

int powercap_get_power_uw(struct powercap_zone *zone, uint64_t *val)
{
        return sysfs_powercap_get64_val(zone, GET_POWER_UW, val);
}

int powercap_zone_get_enabled(struct powercap_zone *zone, int *mode)
{
        char path[SYSFS_PATH_MAX] = PATH_TO_POWERCAP;

        if ((strlen(PATH_TO_POWERCAP) + strlen(zone->sys_name)) +
            strlen("/enabled") + 1 >= SYSFS_PATH_MAX)
                return -1;

        strcat(path, "/");
        strcat(path, zone->sys_name);
        strcat(path, "/enabled");

        return sysfs_get_enabled(path, mode);
}

int powercap_zone_set_enabled(struct powercap_zone *zone, int mode)
{
        /* To be done if needed */
        return 0;
}


int powercap_read_zone(struct powercap_zone *zone)
{
        struct dirent *dent;
        DIR *zone_dir;
        char sysfs_dir[SYSFS_PATH_MAX] = PATH_TO_POWERCAP;
        struct powercap_zone *child_zone;
        char file[SYSFS_PATH_MAX] = PATH_TO_POWERCAP;
        int i, ret = 0;
        uint64_t val = 0;

        strcat(sysfs_dir, "/");
        strcat(sysfs_dir, zone->sys_name);

        zone_dir = opendir(sysfs_dir);
        if (zone_dir == NULL)
                return -1;

        strcat(file, "/");
        strcat(file, zone->sys_name);
        strcat(file, "/name");
        sysfs_read_file(file, zone->name, MAX_LINE_LEN);
        if (zone->parent)
                zone->tree_depth = zone->parent->tree_depth + 1;
        ret = powercap_get_energy_uj(zone, &val);
        if (ret == 0)
                zone->has_energy_uj = 1;
        ret = powercap_get_power_uw(zone, &val);
        if (ret == 0)
                zone->has_power_uw = 1;

        while ((dent = readdir(zone_dir)) != NULL) {
                struct stat st;

                if (strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0)
                        continue;

                if (stat(dent->d_name, &st) != 0 || !S_ISDIR(st.st_mode))
                        if (fstatat(dirfd(zone_dir), dent->d_name, &st, 0) < 0)
                                continue;

                if (strncmp(dent->d_name, "intel-rapl:", 11) != 0)
                        continue;

                child_zone = calloc(1, sizeof(struct powercap_zone));
                if (child_zone == NULL)
                        return -1;
                for (i = 0; i < POWERCAP_MAX_CHILD_ZONES; i++) {
                        if (zone->children[i] == NULL) {
                                zone->children[i] = child_zone;
                                break;
                        }
                        if (i == POWERCAP_MAX_CHILD_ZONES - 1) {
                                free(child_zone);
                                fprintf(stderr, "Reached POWERCAP_MAX_CHILD_ZONES %d\n",
                                       POWERCAP_MAX_CHILD_ZONES);
                                return -1;
                        }
                }
                strcpy(child_zone->sys_name, zone->sys_name);
                strcat(child_zone->sys_name, "/");
                strcat(child_zone->sys_name, dent->d_name);
                child_zone->parent = zone;
                if (zone->tree_depth >= POWERCAP_MAX_TREE_DEPTH) {
                        fprintf(stderr, "Maximum zone hierarchy depth[%d] reached\n",
                                POWERCAP_MAX_TREE_DEPTH);
                        ret = -1;
                        break;
                }
                powercap_read_zone(child_zone);
        }
        closedir(zone_dir);
        return ret;
}

struct powercap_zone *powercap_init_zones(void)
{
        int enabled;
        struct powercap_zone *root_zone;
        int ret;
        char file[SYSFS_PATH_MAX] = PATH_TO_RAPL "/enabled";

        ret = sysfs_get_enabled(file, &enabled);

        if (ret)
                return NULL;

        if (!enabled)
                return NULL;

        root_zone = calloc(1, sizeof(struct powercap_zone));
        if (!root_zone)
                return NULL;

        strcpy(root_zone->sys_name, "intel-rapl/intel-rapl:0");

        powercap_read_zone(root_zone);

        return root_zone;
}

/* Call function *f on the passed zone and all its children */

int powercap_walk_zones(struct powercap_zone *zone,
                        int (*f)(struct powercap_zone *zone))
{
        int i, ret;

        if (!zone)
                return -1;

        ret = f(zone);
        if (ret)
                return ret;

        for (i = 0; i < POWERCAP_MAX_CHILD_ZONES; i++) {
                if (zone->children[i] != NULL)
                        powercap_walk_zones(zone->children[i], f);
        }
        return 0;
}