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