diff options
author | Daniel Lezcano <daniel.lezcano@linaro.org> | 2020-07-20 16:52:57 +0200 |
---|---|---|
committer | Daniel Lezcano <daniel.lezcano@linaro.org> | 2020-07-20 16:52:57 +0200 |
commit | bf64bbf588ef9ee23fcde8fa5b5a9942d2d40b35 (patch) | |
tree | aee694128e19f57eadfbfaffc1f6a84a6c33dc2f |
Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | powerem.c | 518 |
2 files changed, 520 insertions, 0 deletions
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a4ada7b --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +all: + gcc -g -Wall powerem.c -o powerem -lreadline diff --git a/powerem.c b/powerem.c new file mode 100644 index 0000000..6971b3a --- /dev/null +++ b/powerem.c @@ -0,0 +1,518 @@ +/* + * Power Energy Model : protocode + * + * Rebalancing power + * + * Copyright: Linaro Ltd, Daniel Lezcano <daniel.lezcano@linaro.org> + */ +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <readline/readline.h> +#include <readline/history.h> + +#include <linux/errno.h> + +typedef unsigned long long u64; + +#define BUG abort +#define MAX_CHILDREN 8 + +struct powercap_em { + struct powercap_em *parent; + struct powercap_em *children[MAX_CHILDREN]; + const char *name; + int weight; + int nr_children; + u64 min_power; + u64 max_power; + u64 cur_power; + u64 power_limit; + u64 static_power_limit; + u64 dynamic_power_limit; +}; + +struct powercap_em soc = { + .name = "soc" +}; + +struct powercap_em package = { + .name = "package" +}; + +struct powercap_em big = { + .name = "big", + .min_power = 113, + .max_power = 1130, + .cur_power = 113, + }; + +struct powercap_em little = { + .name = "little", + .min_power = 26, + .max_power = 179, + .cur_power = 150, +}; + +struct powercap_em memory = { + .name = "memory", + .min_power = 400, + .max_power = 700, + .cur_power = 700, +}; + +u64 powercap_em_get_power(struct powercap_em *pcem) +{ + u64 power = 0; + int i; + + /* + * Only the leaves of the tree can return the real power, + * otherwise we return the sum of the children power. + */ + if (!pcem->nr_children) + return pcem->cur_power; + + for (i = 0; i < pcem->nr_children; i++) + power += powercap_em_get_power(pcem->children[i]); + + return power; +} + +int powercap_em_rebalance_weight(struct powercap_em *pcem) +{ + struct powercap_em *child; + int i; + + for (i = 0; i < pcem->nr_children; i++) { + child = pcem->children[i]; + child->weight = (child->max_power * 1024) / pcem->max_power; + } + + return 0; +} + +int powercap_em_set_static_power_limit(struct powercap_em *pcem, u64 power); + +int powercap_em_rebalance_limit(struct powercap_em *pcem) +{ + if (!pcem) + BUG(); + + if (pcem->power_limit) + return powercap_em_set_static_power_limit(pcem, pcem->power_limit); + + return powercap_em_rebalance_limit(pcem->parent); +} + +int powercap_em_rebalance_power(struct powercap_em *pcem) +{ + struct powercap_em *donees[MAX_CHILDREN]; + struct powercap_em *donors[MAX_CHILDREN]; + int nr_donee, nr_donor, sum_weight; + u64 power, power_free; + int i, j; + +again: + nr_donee = nr_donor = sum_weight = 0; + + /* + * This first pass build the list of the donees and the sum of + * their weights. + */ + for (i = 0; i < pcem->nr_children; i++) { + struct powercap_em *child; + + child = pcem->children[i]; + power = powercap_em_get_power(child); + + if (power > child->dynamic_power_limit) { + + /* + * We have one device which is consuming more + * energy than allocated but actually it + * donated the free power it had to another + * device. Now it is requesting more power, so + * it must get back the power it gave. Reset + * the dynamic power limit to the static power + * limit and do the rebalancing again. + */ + if (power < child->static_power_limit) { + powercap_em_rebalance_limit(pcem); + goto again; + } + + donees[nr_donee++] = child; + sum_weight += child->weight; + continue; + } + + if (power < child->dynamic_power_limit) + donors[nr_donor++] = child; + } + + /* + * There are no donees, so no need to rebalance the power + * across the actors, this is done, bail out. + */ + if (!nr_donee) + return 0; + + /* + * We are in a situation where there is no more free power for + * the donees, so we need to reduce their power. + */ + if (!nr_donor) { + + /* + * We are in the situation where all the power was + * rebalanced along the actors but more power is + * consumed, in this case we need to cap their power. + */ + for (i = 0; i < nr_donee; i++) + donees[i]->cur_power = + donees[i]->dynamic_power_limit; + + return 0; + } + + /* + * One or more actors are consuming more than the allocated + * power. Let's distribute the free power not used by the + * other actors which are below their limit. + */ + for (i = 0; i < nr_donor; i++) { + + struct powercap_em *donor = donors[i]; + + power = powercap_em_get_power(donor); + + /* + * Compute the unused power ... + */ + power_free = donor->dynamic_power_limit - power; + + /* + * ... and as it will be given to the donees, + * remove this power from the allocated budget from + * the donor. + */ + donor->dynamic_power_limit = power; + + /* + * Redistribute the unused power to the donees. + */ + for (j = 0; j < nr_donee; j++) { + donees[j]->dynamic_power_limit += + (power_free * donees[j]->weight) / sum_weight; + } + } + + /* + * Let's find out new donees and new donors + */ + goto again; + + /* Never reached */ + return -EINVAL; +} + +int powercap_em_set_power(struct powercap_em *pcem, u64 power) +{ + /* + * This function can only be called by the leaves of the + * hierarchy tree which represent the devices on the + * system. Other nodes in the tree are aggregating the + * constraints and power information of the children. + */ + if (pcem->nr_children) + return -EINVAL; + + if (power < pcem->min_power || power > pcem->max_power) + return -EINVAL; + + pcem->cur_power = power; + + /* + * The power of this device changed, let's check if there is a + * limit to rebalance the power. + */ + while (pcem) { + + if (pcem->power_limit) + return powercap_em_rebalance_power(pcem); + + pcem = pcem->parent; + } + + return 0; +} + +int powercap_em_set_static_power_limit(struct powercap_em *pcem, u64 power_limit) +{ + struct powercap_em *child; + u64 static_power_limit; + int i, ret; + + if (!pcem->nr_children) { + pcem->dynamic_power_limit = power_limit; + pcem->static_power_limit = power_limit; + return 0; + } + + for (i = 0; i < pcem->nr_children; i++) { + child = pcem->children[i]; + static_power_limit = (power_limit * child->weight) / 1024; + + ret = powercap_em_set_static_power_limit(child, static_power_limit); + if (ret) + return ret; + } + + return 0; +} + +int powercap_em_set_power_limit(struct powercap_em *pcem, u64 power_limit) +{ + int ret; + + /* + * A power limit set to zero means we remove the constraint + */ + if (power_limit && (power_limit < pcem->min_power || + power_limit > pcem->max_power)) + return -EINVAL; + + ret = powercap_em_set_static_power_limit(pcem, power_limit); + if (ret) + return ret; + + ret = powercap_em_rebalance_power(pcem); + if (ret) + goto out_undo; + + pcem->power_limit = power_limit; + + return 0; + +out_undo: + powercap_em_set_static_power_limit(pcem, 0); + + return ret; +} + +int powercap_em_add_child(struct powercap_em *parent, struct powercap_em *child) +{ + if (parent->nr_children == MAX_CHILDREN) + return -ENOMEM; + + child->parent = parent; + parent->children[parent->nr_children] = child; + parent->nr_children++; + parent->max_power += child->max_power; + parent->min_power += child->min_power; + parent->cur_power += child->cur_power; + + return powercap_em_rebalance_weight(parent); +} + +int __powercap_em_dump(struct powercap_em *pcem, int indent) +{ + int i; + int step = 5; + printf("%*c", indent, ' '); + printf("name: %s\n", pcem->name); + printf("%*c", indent + step, ' '); + printf("| weight: %d\n", pcem->weight); + printf("%*c", indent + step, ' '); + printf("| max power: %llu\n", pcem->max_power); + printf("%*c", indent + step, ' '); + printf("| min power: %llu\n", pcem->min_power); + printf("%*c", indent + step, ' '); + printf("| cur power: %llu\n", powercap_em_get_power(pcem)); + printf("%*c", indent + step, ' '); + printf("| power limit: %llu\n", pcem->power_limit); + printf("%*c", indent + step, ' '); + printf("| static power limit: %llu\n", pcem->static_power_limit); + printf("%*c", indent + step, ' '); + printf("` dynamic power limit: %llu\n", pcem->dynamic_power_limit); + + for (i = 0; i < pcem->nr_children; i++) + __powercap_em_dump(pcem->children[i], indent + step); + + return 0; +} + +int powercap_em_dump(struct powercap_em *pcem) +{ + if (!pcem) + return -EINVAL; + + return __powercap_em_dump(pcem, 0); +} + +/****************************** readline ***************************/ + +struct powercap_em *powercap_em_lookup(struct powercap_em *pcem, + const char *name) +{ + int i; + + if (!strcmp(name, pcem->name)) + return pcem; + + for (i = 0; i < pcem->nr_children; i++) { + struct powercap_em *match; + + match = powercap_em_lookup(pcem->children[i], name); + if (match) + return match; + } + + return NULL; +} + +static int command_set_power_limit(char *argv[]) +{ + struct powercap_em *pcem; + + if (!argv[0] || !argv[1]) + return -EINVAL; + + pcem = powercap_em_lookup(&soc, argv[0]); + if (!pcem) + return -EINVAL; + + return powercap_em_set_power_limit(pcem, atoi(argv[1])); +} + +static int command_set_power(char *argv[]) +{ + struct powercap_em *pcem; + + if (!argv[0] || !argv[1]) + return -EINVAL; + + pcem = powercap_em_lookup(&soc, argv[0]); + if (!pcem) + return -EINVAL; + + return powercap_em_set_power(pcem, atoi(argv[1])); +} + +struct cmd_callback { + const char *cmd; + int (*callback)(char *argv[]); +}; + + +static struct cmd_callback set_commands[] = { + { "power", command_set_power }, + { "power_limit", command_set_power_limit }, +}; + +static int command_set(char *argv[]) +{ + int i; + + if (!argv[0]) + return -EINVAL; + + for (i = 0; i < sizeof(set_commands) / sizeof(set_commands[0]); i++) + if (!strcmp(set_commands[i].cmd, argv[0])) + return set_commands[i].callback(&argv[1]); + + return -EINVAL; +} + +static int command_dump(char *argv[]) +{ + struct powercap_em *pcem; + if (!argv[0]) + return powercap_em_dump(&soc); + + pcem = powercap_em_lookup(&soc, argv[0]); + printf("Found pcem %s\n", pcem->name); + + return powercap_em_dump(powercap_em_lookup(&soc, argv[0])); +} + +static struct cmd_callback commands[] = { + { "set", command_set }, + { "dump", command_dump }, +}; + +static int mainloop(void) +{ + char *argv[32]; + char* buf; + + read_history(".powerem_history"); + + for (;;) { + + int i = 0; + + buf = readline("powerem:$ "); + if (!buf) + break; + + if (strlen(buf) > 0) + add_history(buf); + + argv[i] = strtok(buf," "); + + while (argv[i]) + argv[++i] = strtok(NULL, " "); + + if (argv[0]) { + for (i = 0; i < (sizeof(commands) / sizeof(commands[0])); i++) { + if (strcmp(commands[i].cmd, argv[0])) + continue; + + if (commands[i].callback(&argv[1])) + fprintf(stderr, "Command failed\n"); + } + } + + free(buf); + } + + write_history(".powerem_history"); + + return 0; +} + +int main(int argc, char *argv[]) +{ + if (powercap_em_add_child(&package, &big)) { + fprintf(stderr, "Failed to add child '%s' to '%s'", + big.name, package.name); + return -1; + } + + if (powercap_em_add_child(&package, &little)) { + fprintf(stderr, "Failed to add child '%s' to '%s'", + little.name, package.name); + return -1; + } + + if (powercap_em_add_child(&package, &memory)) { + fprintf(stderr, "Failed to add child '%s' to '%s'", + memory.name, package.name); + return -1; + } + + if (powercap_em_add_child(&soc, &package)) { + fprintf(stderr, "Failed to add child '%s' to '%s'", + big.name, package.name); + return -1; + } + + powercap_em_dump(&soc); + + mainloop(); + + return 0; +} |