162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *  (C) 2016 SUSE Software Solutions GmbH
462306a36Sopenharmony_ci *           Thomas Renninger <trenn@suse.de>
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci#include <sys/types.h>
862306a36Sopenharmony_ci#include <sys/stat.h>
962306a36Sopenharmony_ci#include <unistd.h>
1062306a36Sopenharmony_ci#include <stdlib.h>
1162306a36Sopenharmony_ci#include <string.h>
1262306a36Sopenharmony_ci#include <fcntl.h>
1362306a36Sopenharmony_ci#include <stdio.h>
1462306a36Sopenharmony_ci#include <dirent.h>
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#include "powercap.h"
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_cistatic unsigned int sysfs_read_file(const char *path, char *buf, size_t buflen)
1962306a36Sopenharmony_ci{
2062306a36Sopenharmony_ci	int fd;
2162306a36Sopenharmony_ci	ssize_t numread;
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci	fd = open(path, O_RDONLY);
2462306a36Sopenharmony_ci	if (fd == -1)
2562306a36Sopenharmony_ci		return 0;
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci	numread = read(fd, buf, buflen - 1);
2862306a36Sopenharmony_ci	if (numread < 1) {
2962306a36Sopenharmony_ci		close(fd);
3062306a36Sopenharmony_ci		return 0;
3162306a36Sopenharmony_ci	}
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci	buf[numread] = '\0';
3462306a36Sopenharmony_ci	close(fd);
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci	return (unsigned int) numread;
3762306a36Sopenharmony_ci}
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_cistatic int sysfs_get_enabled(char *path, int *mode)
4062306a36Sopenharmony_ci{
4162306a36Sopenharmony_ci	int fd;
4262306a36Sopenharmony_ci	char yes_no;
4362306a36Sopenharmony_ci	int ret = 0;
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci	*mode = 0;
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci	fd = open(path, O_RDONLY);
4862306a36Sopenharmony_ci	if (fd == -1) {
4962306a36Sopenharmony_ci		ret = -1;
5062306a36Sopenharmony_ci		goto out;
5162306a36Sopenharmony_ci	}
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci	if (read(fd, &yes_no, 1) != 1) {
5462306a36Sopenharmony_ci		ret = -1;
5562306a36Sopenharmony_ci		goto out_close;
5662306a36Sopenharmony_ci	}
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci	if (yes_no == '1') {
5962306a36Sopenharmony_ci		*mode = 1;
6062306a36Sopenharmony_ci		goto out_close;
6162306a36Sopenharmony_ci	} else if (yes_no == '0') {
6262306a36Sopenharmony_ci		goto out_close;
6362306a36Sopenharmony_ci	} else {
6462306a36Sopenharmony_ci		ret = -1;
6562306a36Sopenharmony_ci		goto out_close;
6662306a36Sopenharmony_ci	}
6762306a36Sopenharmony_ciout_close:
6862306a36Sopenharmony_ci	close(fd);
6962306a36Sopenharmony_ciout:
7062306a36Sopenharmony_ci	return ret;
7162306a36Sopenharmony_ci}
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ciint powercap_get_enabled(int *mode)
7462306a36Sopenharmony_ci{
7562306a36Sopenharmony_ci	char path[SYSFS_PATH_MAX] = PATH_TO_POWERCAP "/intel-rapl/enabled";
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci	return sysfs_get_enabled(path, mode);
7862306a36Sopenharmony_ci}
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci/*
8162306a36Sopenharmony_ci * Hardcoded, because rapl is the only powercap implementation
8262306a36Sopenharmony_ci- * this needs to get more generic if more powercap implementations
8362306a36Sopenharmony_ci * should show up
8462306a36Sopenharmony_ci */
8562306a36Sopenharmony_ciint powercap_get_driver(char *driver, int buflen)
8662306a36Sopenharmony_ci{
8762306a36Sopenharmony_ci	char file[SYSFS_PATH_MAX] = PATH_TO_RAPL;
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci	struct stat statbuf;
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	if (stat(file, &statbuf) != 0 || !S_ISDIR(statbuf.st_mode)) {
9262306a36Sopenharmony_ci		driver = "";
9362306a36Sopenharmony_ci		return -1;
9462306a36Sopenharmony_ci	} else if (buflen > 10) {
9562306a36Sopenharmony_ci		strcpy(driver, "intel-rapl");
9662306a36Sopenharmony_ci		return 0;
9762306a36Sopenharmony_ci	} else
9862306a36Sopenharmony_ci		return -1;
9962306a36Sopenharmony_ci}
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_cienum powercap_get64 {
10262306a36Sopenharmony_ci	GET_ENERGY_UJ,
10362306a36Sopenharmony_ci	GET_MAX_ENERGY_RANGE_UJ,
10462306a36Sopenharmony_ci	GET_POWER_UW,
10562306a36Sopenharmony_ci	GET_MAX_POWER_RANGE_UW,
10662306a36Sopenharmony_ci	MAX_GET_64_FILES
10762306a36Sopenharmony_ci};
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_cistatic const char *powercap_get64_files[MAX_GET_64_FILES] = {
11062306a36Sopenharmony_ci	[GET_POWER_UW] = "power_uw",
11162306a36Sopenharmony_ci	[GET_MAX_POWER_RANGE_UW] = "max_power_range_uw",
11262306a36Sopenharmony_ci	[GET_ENERGY_UJ] = "energy_uj",
11362306a36Sopenharmony_ci	[GET_MAX_ENERGY_RANGE_UJ] = "max_energy_range_uj",
11462306a36Sopenharmony_ci};
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_cistatic int sysfs_powercap_get64_val(struct powercap_zone *zone,
11762306a36Sopenharmony_ci				      enum powercap_get64 which,
11862306a36Sopenharmony_ci				      uint64_t *val)
11962306a36Sopenharmony_ci{
12062306a36Sopenharmony_ci	char file[SYSFS_PATH_MAX] = PATH_TO_POWERCAP "/";
12162306a36Sopenharmony_ci	int ret;
12262306a36Sopenharmony_ci	char buf[MAX_LINE_LEN];
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	strcat(file, zone->sys_name);
12562306a36Sopenharmony_ci	strcat(file, "/");
12662306a36Sopenharmony_ci	strcat(file, powercap_get64_files[which]);
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	ret = sysfs_read_file(file, buf, MAX_LINE_LEN);
12962306a36Sopenharmony_ci	if (ret < 0)
13062306a36Sopenharmony_ci		return ret;
13162306a36Sopenharmony_ci	if (ret == 0)
13262306a36Sopenharmony_ci		return -1;
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	*val = strtoll(buf, NULL, 10);
13562306a36Sopenharmony_ci	return 0;
13662306a36Sopenharmony_ci}
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ciint powercap_get_max_energy_range_uj(struct powercap_zone *zone, uint64_t *val)
13962306a36Sopenharmony_ci{
14062306a36Sopenharmony_ci	return sysfs_powercap_get64_val(zone, GET_MAX_ENERGY_RANGE_UJ, val);
14162306a36Sopenharmony_ci}
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ciint powercap_get_energy_uj(struct powercap_zone *zone, uint64_t *val)
14462306a36Sopenharmony_ci{
14562306a36Sopenharmony_ci	return sysfs_powercap_get64_val(zone, GET_ENERGY_UJ, val);
14662306a36Sopenharmony_ci}
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ciint powercap_get_max_power_range_uw(struct powercap_zone *zone, uint64_t *val)
14962306a36Sopenharmony_ci{
15062306a36Sopenharmony_ci	return sysfs_powercap_get64_val(zone, GET_MAX_POWER_RANGE_UW, val);
15162306a36Sopenharmony_ci}
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ciint powercap_get_power_uw(struct powercap_zone *zone, uint64_t *val)
15462306a36Sopenharmony_ci{
15562306a36Sopenharmony_ci	return sysfs_powercap_get64_val(zone, GET_POWER_UW, val);
15662306a36Sopenharmony_ci}
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ciint powercap_zone_get_enabled(struct powercap_zone *zone, int *mode)
15962306a36Sopenharmony_ci{
16062306a36Sopenharmony_ci	char path[SYSFS_PATH_MAX] = PATH_TO_POWERCAP;
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci	if ((strlen(PATH_TO_POWERCAP) + strlen(zone->sys_name)) +
16362306a36Sopenharmony_ci	    strlen("/enabled") + 1 >= SYSFS_PATH_MAX)
16462306a36Sopenharmony_ci		return -1;
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	strcat(path, "/");
16762306a36Sopenharmony_ci	strcat(path, zone->sys_name);
16862306a36Sopenharmony_ci	strcat(path, "/enabled");
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci	return sysfs_get_enabled(path, mode);
17162306a36Sopenharmony_ci}
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ciint powercap_zone_set_enabled(struct powercap_zone *zone, int mode)
17462306a36Sopenharmony_ci{
17562306a36Sopenharmony_ci	/* To be done if needed */
17662306a36Sopenharmony_ci	return 0;
17762306a36Sopenharmony_ci}
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ciint powercap_read_zone(struct powercap_zone *zone)
18162306a36Sopenharmony_ci{
18262306a36Sopenharmony_ci	struct dirent *dent;
18362306a36Sopenharmony_ci	DIR *zone_dir;
18462306a36Sopenharmony_ci	char sysfs_dir[SYSFS_PATH_MAX] = PATH_TO_POWERCAP;
18562306a36Sopenharmony_ci	struct powercap_zone *child_zone;
18662306a36Sopenharmony_ci	char file[SYSFS_PATH_MAX] = PATH_TO_POWERCAP;
18762306a36Sopenharmony_ci	int i, ret = 0;
18862306a36Sopenharmony_ci	uint64_t val = 0;
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci	strcat(sysfs_dir, "/");
19162306a36Sopenharmony_ci	strcat(sysfs_dir, zone->sys_name);
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	zone_dir = opendir(sysfs_dir);
19462306a36Sopenharmony_ci	if (zone_dir == NULL)
19562306a36Sopenharmony_ci		return -1;
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	strcat(file, "/");
19862306a36Sopenharmony_ci	strcat(file, zone->sys_name);
19962306a36Sopenharmony_ci	strcat(file, "/name");
20062306a36Sopenharmony_ci	sysfs_read_file(file, zone->name, MAX_LINE_LEN);
20162306a36Sopenharmony_ci	if (zone->parent)
20262306a36Sopenharmony_ci		zone->tree_depth = zone->parent->tree_depth + 1;
20362306a36Sopenharmony_ci	ret = powercap_get_energy_uj(zone, &val);
20462306a36Sopenharmony_ci	if (ret == 0)
20562306a36Sopenharmony_ci		zone->has_energy_uj = 1;
20662306a36Sopenharmony_ci	ret = powercap_get_power_uw(zone, &val);
20762306a36Sopenharmony_ci	if (ret == 0)
20862306a36Sopenharmony_ci		zone->has_power_uw = 1;
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci	while ((dent = readdir(zone_dir)) != NULL) {
21162306a36Sopenharmony_ci		struct stat st;
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci		if (strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0)
21462306a36Sopenharmony_ci			continue;
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci		if (stat(dent->d_name, &st) != 0 || !S_ISDIR(st.st_mode))
21762306a36Sopenharmony_ci			if (fstatat(dirfd(zone_dir), dent->d_name, &st, 0) < 0)
21862306a36Sopenharmony_ci				continue;
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci		if (strncmp(dent->d_name, "intel-rapl:", 11) != 0)
22162306a36Sopenharmony_ci			continue;
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_ci		child_zone = calloc(1, sizeof(struct powercap_zone));
22462306a36Sopenharmony_ci		if (child_zone == NULL)
22562306a36Sopenharmony_ci			return -1;
22662306a36Sopenharmony_ci		for (i = 0; i < POWERCAP_MAX_CHILD_ZONES; i++) {
22762306a36Sopenharmony_ci			if (zone->children[i] == NULL) {
22862306a36Sopenharmony_ci				zone->children[i] = child_zone;
22962306a36Sopenharmony_ci				break;
23062306a36Sopenharmony_ci			}
23162306a36Sopenharmony_ci			if (i == POWERCAP_MAX_CHILD_ZONES - 1) {
23262306a36Sopenharmony_ci				free(child_zone);
23362306a36Sopenharmony_ci				fprintf(stderr, "Reached POWERCAP_MAX_CHILD_ZONES %d\n",
23462306a36Sopenharmony_ci				       POWERCAP_MAX_CHILD_ZONES);
23562306a36Sopenharmony_ci				return -1;
23662306a36Sopenharmony_ci			}
23762306a36Sopenharmony_ci		}
23862306a36Sopenharmony_ci		strcpy(child_zone->sys_name, zone->sys_name);
23962306a36Sopenharmony_ci		strcat(child_zone->sys_name, "/");
24062306a36Sopenharmony_ci		strcat(child_zone->sys_name, dent->d_name);
24162306a36Sopenharmony_ci		child_zone->parent = zone;
24262306a36Sopenharmony_ci		if (zone->tree_depth >= POWERCAP_MAX_TREE_DEPTH) {
24362306a36Sopenharmony_ci			fprintf(stderr, "Maximum zone hierarchy depth[%d] reached\n",
24462306a36Sopenharmony_ci				POWERCAP_MAX_TREE_DEPTH);
24562306a36Sopenharmony_ci			ret = -1;
24662306a36Sopenharmony_ci			break;
24762306a36Sopenharmony_ci		}
24862306a36Sopenharmony_ci		powercap_read_zone(child_zone);
24962306a36Sopenharmony_ci	}
25062306a36Sopenharmony_ci	closedir(zone_dir);
25162306a36Sopenharmony_ci	return ret;
25262306a36Sopenharmony_ci}
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_cistruct powercap_zone *powercap_init_zones(void)
25562306a36Sopenharmony_ci{
25662306a36Sopenharmony_ci	int enabled;
25762306a36Sopenharmony_ci	struct powercap_zone *root_zone;
25862306a36Sopenharmony_ci	int ret;
25962306a36Sopenharmony_ci	char file[SYSFS_PATH_MAX] = PATH_TO_RAPL "/enabled";
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_ci	ret = sysfs_get_enabled(file, &enabled);
26262306a36Sopenharmony_ci
26362306a36Sopenharmony_ci	if (ret)
26462306a36Sopenharmony_ci		return NULL;
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci	if (!enabled)
26762306a36Sopenharmony_ci		return NULL;
26862306a36Sopenharmony_ci
26962306a36Sopenharmony_ci	root_zone = calloc(1, sizeof(struct powercap_zone));
27062306a36Sopenharmony_ci	if (!root_zone)
27162306a36Sopenharmony_ci		return NULL;
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_ci	strcpy(root_zone->sys_name, "intel-rapl/intel-rapl:0");
27462306a36Sopenharmony_ci
27562306a36Sopenharmony_ci	powercap_read_zone(root_zone);
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ci	return root_zone;
27862306a36Sopenharmony_ci}
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ci/* Call function *f on the passed zone and all its children */
28162306a36Sopenharmony_ci
28262306a36Sopenharmony_ciint powercap_walk_zones(struct powercap_zone *zone,
28362306a36Sopenharmony_ci			int (*f)(struct powercap_zone *zone))
28462306a36Sopenharmony_ci{
28562306a36Sopenharmony_ci	int i, ret;
28662306a36Sopenharmony_ci
28762306a36Sopenharmony_ci	if (!zone)
28862306a36Sopenharmony_ci		return -1;
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_ci	ret = f(zone);
29162306a36Sopenharmony_ci	if (ret)
29262306a36Sopenharmony_ci		return ret;
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_ci	for (i = 0; i < POWERCAP_MAX_CHILD_ZONES; i++) {
29562306a36Sopenharmony_ci		if (zone->children[i] != NULL)
29662306a36Sopenharmony_ci			powercap_walk_zones(zone->children[i], f);
29762306a36Sopenharmony_ci	}
29862306a36Sopenharmony_ci	return 0;
29962306a36Sopenharmony_ci}
300