| /* |
| * Powerdebug : power debugging tool |
| * |
| * Copyright (C) 2016, Linaro Limited. |
| * |
| * Author: |
| * Thara Gopinath <thara.gopinath@linaro.org> |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| */ |
| #define DEBUGFS_GENPD "/sys/kernel/debug/pm_genpd" |
| #define NAME_MAX 16 |
| #define DEVICE_NAME_MAX 256 |
| |
| #define _GNU_SOURCE |
| #include <stdio.h> |
| #undef _GNU_SOURCE |
| #include <sys/types.h> |
| #include <stdbool.h> |
| #include <dirent.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| |
| #include "display.h" |
| #include "powerdebug.h" |
| #include "tree.h" |
| #include "utils.h" |
| |
| struct genpd_idle_state { |
| char name[NAME_MAX]; |
| long long idle_time; |
| }; |
| |
| struct genpd_info { |
| long long active_time; |
| long long total_idle_time; |
| char current_state[NAME_MAX]; |
| struct genpd_idle_state *idle_states; |
| char (*devices)[DEVICE_NAME_MAX]; |
| char (*sub_domains)[NAME_MAX]; |
| int nr_devices; |
| int nr_subdomains; |
| int nr_states; |
| }; |
| |
| static struct tree *genpd_tree; |
| |
| static struct genpd_info *genpd_alloc(void) |
| { |
| struct genpd_info *genpd; |
| |
| genpd = malloc(sizeof(*genpd)); |
| if (genpd) |
| memset(genpd, 0, sizeof(*genpd)); |
| |
| return genpd; |
| } |
| |
| static int genpd_filter_cb(const char *name) |
| { |
| /* Ignore the summary directory */ |
| if (!strcmp(name, "pm_genpd_summary")) |
| return -1; |
| if (!strcmp(name, "pm_genpd")) |
| return -1; |
| |
| return 0; |
| } |
| |
| static int genpd_dump_cb(struct tree *t, void *data) |
| { |
| struct genpd_info *genpd = t->private; |
| int i; |
| |
| if (!t->parent) |
| return 0; |
| |
| printf("\n%s:\n", t->name); |
| printf("current_state: %s\n", genpd->current_state); |
| printf("active_time: %lld ms\n", genpd->active_time); |
| printf("total_idle_time: %lld ms\n", genpd->total_idle_time); |
| printf("Idle States:\n"); |
| for (i = 0; i < genpd->nr_states; i++) { |
| struct genpd_idle_state state = genpd->idle_states[i]; |
| |
| if (!i) |
| printf("%*s State %*s Time\n", 12, "", 10, ""); |
| printf("%*s %-16s %lld\n", 12, "", state.name, |
| state.idle_time); |
| } |
| printf("Devices:\n"); |
| for (i = 0; i < genpd->nr_devices; i++) |
| printf("%*s %s\n", 8, "", genpd->devices[i]); |
| printf("Subdomains:\n"); |
| for (i = 0; i < genpd->nr_subdomains; i++) |
| printf("%*s %s\n", 11, "", genpd->sub_domains[i]); |
| |
| return 0; |
| } |
| |
| static int genpd_display_cb(struct tree *t, void *data) |
| { |
| struct genpd_info *genpd = t->private; |
| int *line = data; |
| int nr_states = 0, nr_devices = 0, nr_domains = 0, i = 0; |
| char *buf; |
| |
| if (!t->parent) |
| return 0; |
| |
| while (1) { |
| char *state_buf, *device_buf, *domain_buf; |
| |
| if ((i) && (nr_states == genpd->nr_states) && |
| (nr_devices == genpd->nr_devices) && |
| (nr_domains == genpd->nr_subdomains)) { |
| display_print_line(GENPD, *line, " ", 1, t); |
| (*line)++; |
| break; |
| } |
| |
| if (nr_states < genpd->nr_states) { |
| if (asprintf(&state_buf, "%-10s %lld", |
| genpd->idle_states[nr_states].name, |
| genpd->idle_states[nr_states].idle_time) < 0) |
| return -1; |
| nr_states++; |
| } else { |
| if (asprintf(&state_buf, "%s", "") < 0) |
| return -1; |
| } |
| |
| if (nr_devices < genpd->nr_devices) { |
| if (asprintf(&device_buf, "%s", |
| genpd->devices[nr_devices]) < 0) |
| return -1; |
| nr_devices++; |
| } else { |
| if (asprintf(&device_buf, "%s", "") < 0) |
| return -1; |
| } |
| |
| if (nr_domains < genpd->nr_subdomains) { |
| if (asprintf(&domain_buf, "%s", |
| genpd->sub_domains[nr_domains]) < 0) |
| return -1; |
| nr_domains++; |
| } else { |
| if (asprintf(&domain_buf, "%s", "") < 0) |
| return -1; |
| } |
| |
| if (!i) { |
| if (asprintf(&buf, "%-9s %-18s %-20lld %-24lld %-34s " |
| "%-50s %-15s", t->name, genpd->current_state, |
| genpd->active_time, genpd->total_idle_time, |
| state_buf, device_buf, domain_buf) < 0) |
| return -1; |
| } else { |
| if (asprintf(&buf, "%-74s %-34s %-50s %-15s", |
| "", state_buf, device_buf, domain_buf) < 0) |
| return -1; |
| } |
| |
| display_print_line(GENPD, *line, buf, 1, t); |
| (*line)++; |
| free(buf); |
| free(state_buf); |
| free(device_buf); |
| free(domain_buf); |
| i++; |
| } |
| |
| return 0; |
| } |
| |
| static int genpd_print_header(void) |
| { |
| char *buf; |
| int ret; |
| |
| if (asprintf(&buf, "%-9s %-18s %-20s %-24s %-34s %-50s %-15s", "Name", |
| "Current State", "Active Time(ms)", "Total Idle Time(ms)", |
| "Idle States(State,Time ms)", "Devices", "Subdomains") < 0) |
| return -1; |
| |
| ret = display_column_name(buf); |
| free(buf); |
| |
| return ret; |
| } |
| |
| static int genpd_print_info(struct tree *t) |
| { |
| int ret, line = 0; |
| |
| display_reset_cursor(GENPD); |
| |
| genpd_print_header(); |
| |
| ret = tree_for_each(t, genpd_display_cb, &line); |
| |
| display_refresh_pad(GENPD); |
| |
| return ret; |
| } |
| |
| static int read_genpd_cb(struct tree *t, void *data) |
| { |
| struct genpd_info *genpd = t->private; |
| FILE *fp; |
| char line[256]; |
| int nr_states = 0, nr_devices = 0, nr_sub_domains = 0; |
| |
| file_read_value(t->path, "active_time", "%lld", &genpd->active_time); |
| file_read_value(t->path, "total_idle_time", "%lld", |
| &genpd->total_idle_time); |
| file_read_value(t->path, "current_state", "%s", &genpd->current_state); |
| file_open(&fp, t->path, "idle_states", "r"); |
| while (!(file_read_line(&fp, line, sizeof(line)))) { |
| if (!strncmp(line, "State", 5)) |
| continue; |
| |
| genpd->idle_states = realloc(genpd->idle_states, |
| sizeof(struct genpd_idle_state) * (nr_states + 1)); |
| if (!genpd->idle_states) |
| continue; |
| sscanf(line, "%s %lld", genpd->idle_states[nr_states].name, |
| &(genpd->idle_states[nr_states].idle_time)); |
| |
| nr_states++; |
| } |
| file_close(&fp); |
| |
| file_open(&fp, t->path, "devices", "r"); |
| while (!(file_read_line(&fp, line, sizeof(line)))) { |
| int len; |
| |
| genpd->devices = realloc(genpd->devices, |
| sizeof(*(genpd->devices)) * (nr_devices + 1)); |
| if (!genpd->devices) |
| continue; |
| |
| len = strlen(line); |
| line[len - 1] = '\0'; |
| strcpy(genpd->devices[nr_devices], line); |
| nr_devices++; |
| } |
| file_close(&fp); |
| |
| file_open(&fp, t->path, "sub_domains", "r"); |
| while (!(file_read_line(&fp, line, sizeof(line)))) { |
| int len; |
| |
| genpd->sub_domains = realloc(genpd->sub_domains, |
| sizeof(*(genpd->sub_domains)) * (nr_sub_domains + 1)); |
| if (!genpd->sub_domains) |
| continue; |
| |
| len = strlen(line); |
| line[len - 1] = '\0'; |
| strcpy(genpd->sub_domains[nr_sub_domains], line); |
| nr_sub_domains++; |
| } |
| |
| genpd->nr_states = nr_states; |
| genpd->nr_devices = nr_devices; |
| genpd->nr_subdomains = nr_sub_domains; |
| |
| return 0; |
| } |
| |
| static int read_genpd_info(struct tree *t) |
| { |
| return tree_for_each(t, read_genpd_cb, NULL); |
| } |
| |
| static int fill_genpd_cb(struct tree *t, void *data) |
| { |
| struct genpd_info *genpd; |
| |
| genpd = genpd_alloc(); |
| if (!genpd) { |
| printf("error: unable to allocate memory for genpd\n"); |
| return -1; |
| } |
| |
| t->private = genpd; |
| |
| return read_genpd_cb(t, data); |
| } |
| |
| static int fill_genpd_tree(void) |
| { |
| return tree_for_each(genpd_tree, fill_genpd_cb, NULL); |
| } |
| |
| static int genpd_info_load(void) |
| { |
| if (genpd_tree) |
| return 0; |
| |
| genpd_tree = tree_load(DEBUGFS_GENPD, genpd_filter_cb, false); |
| if (!genpd_tree) |
| return -1; |
| |
| if (fill_genpd_tree()) |
| return -1; |
| |
| return 0; |
| } |
| |
| int genpd_dump(void) |
| { |
| if (!genpd_tree) |
| genpd_info_load(); |
| else |
| read_genpd_info(genpd_tree); |
| |
| return tree_for_each(genpd_tree, genpd_dump_cb, NULL); |
| |
| return 0; |
| } |
| |
| static int genpd_display(bool refresh) |
| { |
| if (genpd_info_load()) { |
| display_print_error(GENPD, 0, "Failed to read genpd info"); |
| return 0; |
| } |
| |
| if (refresh && read_genpd_info(genpd_tree)) |
| return -1; |
| |
| return genpd_print_info(genpd_tree); |
| } |
| |
| static struct display_ops genpd_ops = { |
| .display = genpd_display, |
| }; |
| |
| int genpd_init(struct powerdebug_options *options) |
| { |
| if (!(options->flags & GENPD_OPTION)) |
| return 0; |
| |
| return display_register(GENPD, &genpd_ops); |
| } |
| |