new file mode 100644
@@ -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
@@ -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
}
@@ -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 \
@@ -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 \
new file mode 100644
@@ -0,0 +1 @@
+#include <odp/api/spec/power.h>
new file mode 100644
@@ -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);
+}
+
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