diff mbox series

[API-NEXT,1/5] power: added API for managing CPU DVFS

Message ID 1484670521-28503-2-git-send-email-sergei.trofimov@arm.com
State New
Headers show
Series power management api | expand

Commit Message

Sergei Trofimov Jan. 17, 2017, 4:28 p.m. UTC
Added APIs for quiering available CPU power/frequency domains and
setting their frequencies. Current implementation assumes that Linux is
using userspace cpufreq governor.

Signed-off-by: Sergei Trofimov <sergei.trofimov@arm.com>

---
 include/odp/api/spec/power.h                   | 116 ++++++++++
 include/odp_api.h                              |   1 +
 platform/Makefile.inc                          |   1 +
 platform/linux-generic/Makefile.am             |   2 +
 platform/linux-generic/include/odp/api/power.h |   1 +
 platform/linux-generic/odp_power.c             | 294 +++++++++++++++++++++++++
 6 files changed, 415 insertions(+)
 create mode 100644 include/odp/api/spec/power.h
 create mode 100644 platform/linux-generic/include/odp/api/power.h
 create mode 100644 platform/linux-generic/odp_power.c

-- 
1.9.1
diff mbox series

Patch

diff --git a/include/odp/api/spec/power.h b/include/odp/api/spec/power.h
new file mode 100644
index 0000000..26dd64e
--- /dev/null
+++ b/include/odp/api/spec/power.h
@@ -0,0 +1,116 @@ 
+/* Copyright (c) 2016, ARM Limited
+ * All rights reserved.
+ *
+ * SPDX-License-Identifier:     BSD-3-Clause
+ */
+
+
+/**
+ * @file
+ *
+ * CPU power management
+ */
+
+#ifndef ODP_POWER_H
+#define ODP_POWER_H
+
+#include <stdint.h>
+#include <limits.h>
+
+#include <odp/api/visibility_begin.h>
+#include <odp/api/cpumask.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/**
+ * CPU power domain description.
+ *
+ */
+typedef struct odp_power_domain_s{
+	/** Number of possible performance levels in the domain. */
+	int num_perf_levels;
+	/** Valid performance levels for the domain. Higher numeric values
+	 * represent more performance. The units for performance levels are
+	 * platform-specific. */
+	int *perf_levels;
+	/** CPUs in this power domain */
+	odp_cpumask_t cpus;
+} odp_power_domain_t;
+
+
+/**
+ * Information about the discovered power domains.
+ *
+ */
+typedef struct odp_power_domain_info_s {
+	/** Number of discovered power domains */
+	int num_domains;
+	/** Discovered power domains */
+	odp_power_domain_t *domains;
+} odp_power_domain_info_t;
+
+/**
+ * Populate power domains info for this platform.
+ *
+ * Discover power domains available on the platform and populate the
+ * domain info object with them.
+ *
+ *
+ * @param info 	power domains info to be initialized
+ *
+ * @return      zero on success or negative error value on error
+ *
+ */
+int  odp_power_domain_info_populate(odp_power_domain_info_t *info);
+
+/**
+ * Destroy a previously init'd info struct, releasing associated resources.
+ *
+ *
+ * @param info 	power domains info to be terminated
+ *
+ */
+void odp_power_domain_info_destroy(odp_power_domain_info_t *info);
+
+/**
+ * Return the power domain associated with the specified cpu
+ *
+ * @param info 	contains information about power domains
+ * @param cpu	CPU whose power domain will be returned
+ *
+ * @return  a pointer to the power domain or NULL on error
+ *
+ */
+odp_power_domain_t *odp_power_domain_for_cpu(odp_power_domain_info_t *info, int cpu);
+
+/**
+ * Set performance level of the specified domain
+ *
+ * @param domain 	domain for which frequency will be set
+ * @param level		the performance level to set
+ *
+ * @return 	zero on success or negative error value on error
+ *
+ */
+int odp_power_domain_set_perf_level(odp_power_domain_t *domain, int level);
+
+
+/**
+ * Return the current performance level of the specified domain
+ *
+ * @param domain 	domain for which performance level will be set
+ *
+ * @return 	performance level
+ *
+ */
+uint64_t odp_power_domain_get_perf_level(odp_power_domain_t *domain);
+
+#ifdef __cplusplus
+}
+#endif
+
+#include <odp/api/visibility_end.h>
+#endif  // ODP_POWER_H
diff --git a/include/odp_api.h b/include/odp_api.h
index 73e5309..983e44f 100644
--- a/include/odp_api.h
+++ b/include/odp_api.h
@@ -58,6 +58,7 @@  extern "C" {
 #include <odp/api/rwlock_recursive.h>
 #include <odp/api/std_clib.h>
 #include <odp/api/ipsec.h>
+#include <odp/api/power.h>
 
 #ifdef __cplusplus
 }
diff --git a/platform/Makefile.inc b/platform/Makefile.inc
index aefbf9a..8427117 100644
--- a/platform/Makefile.inc
+++ b/platform/Makefile.inc
@@ -40,6 +40,7 @@  odpapispecinclude_HEADERS = \
 		  $(top_srcdir)/include/odp/api/spec/packet_io.h \
 		  $(top_srcdir)/include/odp/api/spec/packet_io_stats.h \
 		  $(top_srcdir)/include/odp/api/spec/pool.h \
+		  $(top_srcdir)/include/odp/api/spec/power.h \
 		  $(top_srcdir)/include/odp/api/spec/queue.h \
 		  $(top_srcdir)/include/odp/api/spec/random.h \
 		  $(top_srcdir)/include/odp/api/spec/rwlock.h \
diff --git a/platform/linux-generic/Makefile.am b/platform/linux-generic/Makefile.am
index 6bbe775..738c582 100644
--- a/platform/linux-generic/Makefile.am
+++ b/platform/linux-generic/Makefile.am
@@ -43,6 +43,7 @@  odpapiinclude_HEADERS = \
 		  $(srcdir)/include/odp/api/packet_io.h \
 		  $(srcdir)/include/odp/api/packet_io_stats.h \
 		  $(srcdir)/include/odp/api/pool.h \
+		  $(srcdir)/include/odp/api/power.h \
 		  $(srcdir)/include/odp/api/queue.h \
 		  $(srcdir)/include/odp/api/random.h \
 		  $(srcdir)/include/odp/api/rwlock.h \
@@ -205,6 +206,7 @@  __LIB__libodp_linux_la_SOURCES = \
 			   pktio/ring.c \
 			   odp_pkt_queue.c \
 			   odp_pool.c \
+			   odp_power.c \
 			   odp_queue.c \
 			   odp_rwlock.c \
 			   odp_rwlock_recursive.c \
diff --git a/platform/linux-generic/include/odp/api/power.h b/platform/linux-generic/include/odp/api/power.h
new file mode 100644
index 0000000..f012bf5
--- /dev/null
+++ b/platform/linux-generic/include/odp/api/power.h
@@ -0,0 +1 @@ 
+#include <odp/api/spec/power.h>
diff --git a/platform/linux-generic/odp_power.c b/platform/linux-generic/odp_power.c
new file mode 100644
index 0000000..df41655
--- /dev/null
+++ b/platform/linux-generic/odp_power.c
@@ -0,0 +1,294 @@ 
+#include <odp/api/spec/power.h>
+#include <odp/api/spec/cpumask.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <inttypes.h>
+#include <string.h>
+#include <errno.h>
+#include <limits.h>
+
+#define MAX_PATH 255
+#define MAX_LINE MAX_PATH
+#define MAX_PERF_LEVEL INT_MAX
+#define MIN_PERF_LEVEL 0
+
+typedef enum {
+	INT,
+	/*UINT,*/
+	/*DOUBLE,*/
+	POINTER
+} ll_entry_type_t;
+
+struct ll_entry_s;
+typedef struct ll_entry_s {
+	struct ll_entry_s *next;
+	union {
+		int64_t i64;
+		uint64_t u64;
+		double d;
+		void *p;
+	} value;
+	ll_entry_type_t type;
+} ll_entry_t;
+
+typedef struct {
+	int num;
+	ll_entry_t *head;
+	ll_entry_t **ptail;
+} llist_t;
+
+#define DEFINE_LLIST_ADD(NAME, TYPE, VALNAME, TYPE_ENUM) \
+	static int llist_add_##NAME(llist_t *list, TYPE val);\
+	static int llist_add_##NAME(llist_t *list, TYPE val) {\
+		ll_entry_t *new = malloc(sizeof(ll_entry_t));\
+		if (new == NULL)\
+			return ENOMEM;\
+		new->next = NULL;\
+		new->type = TYPE_ENUM;\
+		new->value.VALNAME =  val;\
+		*(list->ptail) = new;\
+		list->ptail = &(new->next);\
+		list->num++;\
+		return 0;\
+	}
+
+DEFINE_LLIST_ADD(int,int64_t,i64,INT);
+DEFINE_LLIST_ADD(ptr,void *,p,POINTER);
+/*DEFINE_LLIST_ADD(uint,uint64_t,u64,UINT);*/
+/*DEFINE_LLIST_ADD(double,double,d,DOUBLE);*/
+
+static void llist_init(llist_t *list) {
+	list->num = 0;
+	list->head = NULL;
+	list->ptail = &list->head;
+}
+
+static void llist_fini(llist_t *list) {
+	ll_entry_t *current, *next;
+	next = list->head;
+	while (next != NULL) {
+		current = next;
+		next = next->next;
+		free(current);
+	}
+	list->num = 0;
+}
+
+static int read_int_list(char *path, int **list, int *num);
+static int int_cmp(const void *val1, const void *val2);
+static void get_related_cpus(int cpu_id, int **related_cpus, int *num);
+static inline int check_in_array(int *arr, int size, int item);
+static void get_available_cpu_frequencies(int cpu_id, int **freqs, int *num);
+
+int odp_power_domain_info_populate(odp_power_domain_info_t *info) {
+	int num_cpus = sysconf(_SC_NPROCESSORS_CONF);
+
+	info->num_domains = 0;
+
+	llist_t dom_list;
+	llist_init(&dom_list);
+	odp_cpumask_t processed;
+	odp_cpumask_zero(&processed);
+	int i;
+	for(i = 0; i < num_cpus; i++) {
+
+		if (odp_cpumask_isset(&processed, (1ULL << i))) {
+			continue;
+		}
+
+		odp_power_domain_t *dom = malloc(sizeof(odp_power_domain_t));
+		if (dom == NULL)
+			goto return_nomem;
+		odp_cpumask_zero(&dom->cpus);
+
+		int *related;
+		int num;
+		get_related_cpus(i, &related, &num);
+		get_available_cpu_frequencies(i, &(dom->perf_levels), &(dom->num_perf_levels));
+		qsort(dom->perf_levels, dom->num_perf_levels, sizeof(int), int_cmp);
+
+		int j;
+		for (j = 0; j < num; j++) {
+
+			odp_cpumask_set(&dom->cpus, related[j]);
+			odp_cpumask_set(&processed, related[j]);
+		}
+
+		llist_add_ptr(&dom_list, (void *)dom);
+	}
+
+	info->domains = malloc(sizeof(odp_power_domain_t) * dom_list.num);
+	if (info->domains == NULL)
+		goto return_nomem;
+
+	ll_entry_t *dom_entry = dom_list.head;
+	for (i = 0; i < dom_list.num; i++) {
+		memcpy(&(info->domains[i]), dom_entry->value.p, sizeof(odp_power_domain_t));
+		free(dom_entry->value.p);
+		dom_entry = dom_entry->next;
+	}
+	info->num_domains = dom_list.num;
+	llist_fini(&dom_list);
+
+	return 0;
+
+return_nomem:
+	dom_entry = dom_list.head;
+	for (i = 0; i < dom_list.num; i++) {
+		free(dom_entry->value.p);
+		dom_entry = dom_entry->next;
+	}
+	llist_fini(&dom_list);
+	return -ENOMEM;
+}
+
+void odp_power_domain_info_destroy(odp_power_domain_info_t *info)
+{
+	int i;
+	for (i = 0; i < info->num_domains; i++) {
+		free(info->domains[i].perf_levels);
+	}
+	free(info->domains);
+}
+
+odp_power_domain_t *odp_power_domain_for_cpu(odp_power_domain_info_t *info, int cpu) {
+	int i;
+	for (i = 0; i < info->num_domains; i++) {
+		odp_power_domain_t *dom = &info->domains[i];
+		if (odp_cpumask_isset(&dom->cpus, cpu))
+			return dom;
+	}
+
+	return NULL;
+}
+
+int odp_power_domain_set_perf_level(odp_power_domain_t *domain, int level) {
+	if (level == MAX_PERF_LEVEL) {
+		level = domain->perf_levels[domain->num_perf_levels - 1];
+	}
+	if (level == MIN_PERF_LEVEL) {
+		level = domain->perf_levels[0];
+	}
+	if (!check_in_array(domain->perf_levels, domain->num_perf_levels, level)) {
+		return -EINVAL;
+	}
+
+	// Note: this assumes that the current governor is userspace
+	char path[MAX_PATH];
+	int cpu = odp_cpumask_first(&domain->cpus);
+	snprintf(path, MAX_PATH, "/sys/devices/system/cpu/cpu%d/cpufreq/scaling_setspeed", cpu);
+
+	FILE *f = fopen(path, "w");
+	if (f == NULL) {
+		return errno;
+	}
+
+	int err = fprintf(f, "%d", level);
+	if (err < 0) {
+		fclose(f);
+		return err;
+	}
+
+	err = fclose(f);
+	return err;
+
+}
+
+uint64_t odp_power_domain_get_perf_level(odp_power_domain_t *domain) {
+	char path[MAX_PATH];
+	int cpu = odp_cpumask_first(&domain->cpus);
+	snprintf(path, MAX_PATH, "/sys/devices/system/cpu/cpu%d/cpufreq/scaling_cur_freq", cpu);
+
+	FILE *f = fopen(path, "r");
+	if (f == NULL) {
+		return errno;
+	}
+
+	char buffer[MAX_PATH];
+	char *ret = fgets(buffer, MAX_PATH, f);
+	fclose(f);
+	if (ret == NULL)
+		return 0;
+
+	return atoi(buffer);
+}
+
+static int read_int_list(char *path, int **list, int *num) {
+	FILE *f = fopen(path, "r");
+	char line[MAX_PATH];
+
+	char *ret = fgets(line, MAX_PATH, f);
+	fclose(f);
+	if (ret == NULL)
+		return -EIO;
+
+	llist_t llist;
+	llist_init(&llist);
+
+	*num = 0;
+	char *part = strtok(line, " ");
+	while(part != NULL) {
+		if (part[0] == '\n')
+			break;
+		llist_add_int(&llist, atoi(part));
+		part = strtok(NULL, " ");
+	}
+
+	*list = malloc(sizeof(int) * llist.num);
+	if (*list == NULL) {
+		llist_fini(&llist);
+		return -ENOMEM;
+	}
+
+	int i = 0;
+	ll_entry_t *entry = llist.head;
+	while (entry != NULL) {
+		(*list)[i++] = entry->value.i64;
+		entry = entry->next;
+	}
+	*num = llist.num;
+
+	llist_fini(&llist);
+
+	return 0;
+}
+
+static int int_cmp(const void *val1, const void *val2) {
+	int one = *((const int *)val1);
+	int two = *((const int *)val2);
+
+	if (one > two)
+		return 1;
+	if (one < two)
+		return -1;
+	return 0;
+}
+
+static inline int check_in_array(int *arr, int size, int item) {
+	int i;
+	for (i = 0; i < size; i++) {
+		if (arr[i] == item)
+			return 1;
+	}
+
+	return 0;
+}
+
+static void get_related_cpus(int cpu_id, int **related_cpus, int *num) {
+	char path[MAX_PATH];
+	snprintf(path, MAX_PATH, "/sys/devices/system/cpu/cpu%d/cpufreq/affected_cpus", cpu_id);
+	int ret = read_int_list(path, related_cpus, num);
+	if (ret)
+		fprintf(stderr, "ERROR: could not read affected_cpus for CPU %d\n", cpu_id);
+}
+
+static void get_available_cpu_frequencies(int cpu_id, int **freqs, int *num) {
+	char path[MAX_PATH];
+	snprintf(path, MAX_PATH, "/sys/devices/system/cpu/cpu%d/cpufreq/scaling_available_frequencies", cpu_id);
+	int ret = read_int_list(path, freqs, num);
+	if (ret)
+		fprintf(stderr, "ERROR: could not read avaliable frequencies for CPU %d\n", cpu_id);
+}
+