| /******************************************************************************* |
| * Copyright (C) 2010, Linaro Limited. |
| * |
| * This file is part of PowerDebug. |
| * |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Amit Arora <amit.arora@linaro.org> (IBM Corporation) |
| * - initial API and implementation |
| *******************************************************************************/ |
| |
| #ifndef _GNU_SOURCE |
| #define _GNU_SOURCE |
| #include <stdio.h> |
| #undef _GNU_SOURCE |
| #endif |
| #include <mntent.h> |
| #include <sys/stat.h> |
| |
| #include "powerdebug.h" |
| #include "clocks.h" |
| #include "tree.h" |
| |
| #define MAX_LINES 120 |
| |
| static char clk_dir_path[PATH_MAX]; |
| static int bold[MAX_LINES]; |
| static char clock_lines[MAX_LINES][128]; |
| static int clock_line_no; |
| static int old_clock_line_no; |
| |
| struct clock_info { |
| char name[NAME_MAX]; |
| int flags; |
| int rate; |
| int usecount; |
| int num_children; |
| int last_child; |
| int expanded; |
| int level; |
| char *prefix; |
| struct clock_info *parent; |
| struct clock_info **children; |
| } *clocks_info; |
| |
| static struct tree *clock_tree = NULL; |
| |
| static int locate_debugfs(char *clk_path) |
| { |
| const char *mtab = "/proc/mounts"; |
| struct mntent *mntent; |
| int ret = -1; |
| FILE *file = NULL; |
| |
| file = setmntent(mtab, "r"); |
| if (!file) |
| return -1; |
| |
| while ((mntent = getmntent(file))) { |
| |
| if (strcmp(mntent->mnt_type, "debugfs")) |
| continue; |
| |
| strcpy(clk_path, mntent->mnt_dir); |
| ret = 0; |
| break; |
| } |
| |
| fclose(file); |
| return ret; |
| } |
| |
| int clock_init(void) |
| { |
| if (locate_debugfs(clk_dir_path)) |
| return -1; |
| |
| sprintf(clk_dir_path, "%s/clock", clk_dir_path); |
| |
| clock_tree = tree_load(clk_dir_path, NULL); |
| if (!clock_tree) |
| return -1; |
| |
| return access(clk_dir_path, F_OK); |
| } |
| |
| /* |
| * This functions is a helper to read a specific file content and store |
| * the content inside a variable pointer passed as parameter, the format |
| * parameter gives the variable type to be read from the file. |
| * |
| * @path : directory path containing the file |
| * @name : name of the file to be read |
| * @format : the format of the format |
| * @value : a pointer to a variable to store the content of the file |
| * Returns 0 on success, -1 otherwise |
| */ |
| int file_read_value(const char *path, const char *name, |
| const char *format, void *value) |
| { |
| FILE *file; |
| char *rpath; |
| int ret; |
| |
| ret = asprintf(&rpath, "%s/%s", path, name); |
| if (ret < 0) |
| return ret; |
| |
| file = fopen(rpath, "r"); |
| if (!file) { |
| ret = -1; |
| goto out_free; |
| } |
| |
| ret = fscanf(file, format, value) == EOF ? -1 : 0; |
| |
| fclose(file); |
| out_free: |
| free(rpath); |
| return ret; |
| } |
| |
| static int file_read_from_format(const char *file, int *value, |
| const char *format) |
| { |
| FILE *f; |
| int ret; |
| |
| f = fopen(file, "r"); |
| if (!f) |
| return -1; |
| ret = fscanf(f, format, value); |
| fclose(f); |
| |
| return !ret ? -1 : 0; |
| } |
| |
| static inline int file_read_int(const char *file, int *value) |
| { |
| return file_read_from_format(file, value, "%d"); |
| } |
| |
| static inline int file_read_hex(const char *file, int *value) |
| { |
| return file_read_from_format(file, value, "%x"); |
| } |
| |
| static void dump_parent(struct clock_info *clk, int line, bool dump) |
| { |
| char *unit = "Hz"; |
| double drate; |
| static char spaces[64]; |
| char str[256]; |
| static int maxline; |
| |
| if (maxline < line) |
| maxline = line; |
| |
| if (clk && clk->parent) |
| dump_parent(clk->parent, ++line, dump); |
| |
| drate = (double)clk->rate; |
| if (drate > 1000 && drate < 1000000) { |
| unit = "KHz"; |
| drate /= 1000; |
| } |
| if (drate > 1000000) { |
| unit = "MHz"; |
| drate /= 1000000; |
| } |
| if (clk == clocks_info) { |
| line++; |
| strcpy(spaces, ""); |
| sprintf(str, "%s%s (flags:0x%x,usecount:%d,rate:%5.2f %s)\n", |
| spaces, clk->name, clk->flags, clk->usecount, drate, |
| unit); |
| } else { |
| if (!(clk->parent == clocks_info)) |
| strcat(spaces, " "); |
| sprintf(str, "%s`- %s (flags:0x%x,usecount:%d,rate:%5.2f %s)\n", |
| spaces, clk->name, clk->flags, clk->usecount, drate, |
| unit); |
| } |
| if (dump) |
| //printf("line=%d:m%d:l%d %s", maxline - line + 2, maxline, line, str); |
| printf("%s", str); |
| else |
| print_one_clock(maxline - line + 2, str, 1, 0); |
| } |
| |
| static struct clock_info *find_clock(struct clock_info *clk, char *clkarg) |
| { |
| int i; |
| struct clock_info *ret = clk; |
| |
| if (!strcmp(clk->name, clkarg)) |
| return ret; |
| |
| if (clk->children) { |
| for (i = 0; i < clk->num_children; i++) { |
| if (!strcmp(clk->children[i]->name, clkarg)) |
| return clk->children[i]; |
| } |
| for (i = 0; i < clk->num_children; i++) { |
| ret = find_clock(clk->children[i], clkarg); |
| if (ret) |
| return ret; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static void dump_all_parents(char *clkarg, bool dump) |
| { |
| struct clock_info *clk; |
| char spaces[1024]; |
| |
| strcpy(spaces, ""); |
| |
| clk = find_clock(clocks_info, clkarg); |
| |
| if (!clk) |
| printf("Clock NOT found!\n"); |
| else { |
| /* while(clk && clk != clocks_info) { */ |
| /* printf("%s\n", clk->name); */ |
| /* strcat(spaces, " "); */ |
| /* clk = clk->parent; */ |
| /* printf("%s <-- ", spaces); */ |
| /* } */ |
| /* printf(" /\n"); */ |
| dump_parent(clk, 1, dump); |
| } |
| } |
| |
| void find_parents_for_clock(char *clkname, int complete) |
| { |
| char name[256]; |
| |
| name[0] = '\0'; |
| if (!complete) { |
| char str[256]; |
| |
| strcat(name, clkname); |
| sprintf(str, "Enter Clock Name : %s\n", name); |
| print_one_clock(2, str, 1, 0); |
| return; |
| } |
| sprintf(name, "Parents for \"%s\" Clock : \n", clkname); |
| print_one_clock(0, name, 1, 1); |
| dump_all_parents(clkname, false); |
| } |
| |
| static void destroy_clocks_info_recur(struct clock_info *clock) |
| { |
| int i; |
| |
| if (clock && clock->num_children) { |
| for (i = (clock->num_children - 1); i >= 0; i--) { |
| fflush(stdin); |
| destroy_clocks_info_recur(clock->children[i]); |
| if (!i) { |
| free(clock->children); |
| clock->children = NULL; |
| clock->num_children = 0; |
| } |
| } |
| } |
| } |
| |
| static void destroy_clocks_info(void) |
| { |
| int i; |
| |
| if (!clocks_info) |
| return; |
| |
| if (clocks_info->num_children) { |
| for (i = (clocks_info->num_children - 1); i >= 0 ; i--) { |
| destroy_clocks_info_recur(clocks_info->children[i]); |
| if (!i) { |
| free(clocks_info->children); |
| clocks_info->children = NULL; |
| } |
| } |
| } |
| clocks_info->num_children = 0; |
| free(clocks_info); |
| clocks_info = NULL; |
| } |
| |
| |
| int read_and_print_clock_info(int verbose, int hrow, int selected) |
| { |
| print_one_clock(0, "Reading Clock Tree ...", 1, 1); |
| |
| if (!old_clock_line_no || selected == REFRESH_WINDOW) { |
| destroy_clocks_info(); |
| read_clock_info(clk_dir_path); |
| } |
| |
| if (!clocks_info || !clocks_info->num_children) { |
| fprintf(stderr, "powerdebug: No clocks found. Exiting..\n"); |
| exit(1); |
| } |
| |
| if (selected == CLOCK_SELECTED) |
| selected = 1; |
| else |
| selected = 0; |
| |
| print_clock_info(verbose, hrow, selected); |
| hrow = (hrow < old_clock_line_no) ? hrow : old_clock_line_no - 1; |
| |
| return hrow; |
| } |
| |
| static int calc_delta_screen_size(int hrow) |
| { |
| if (hrow >= (maxy - 3)) |
| return hrow - (maxy - 4); |
| |
| return 0; |
| } |
| |
| static void prepare_name_str(char *namestr, struct clock_info *clock) |
| { |
| int i; |
| |
| strcpy(namestr, ""); |
| if (clock->level > 1) |
| for (i = 0; i < (clock->level - 1); i++) |
| strcat(namestr, " "); |
| strcat(namestr, clock->name); |
| } |
| |
| static void collapse_all_subclocks(struct clock_info *clock) |
| { |
| int i; |
| |
| clock->expanded = 0; |
| if (clock->num_children) |
| for (i = 0; i < clock->num_children; i++) |
| collapse_all_subclocks(clock->children[i]); |
| } |
| |
| static void add_clock_details_recur(struct clock_info *clock, |
| int hrow, int selected) |
| { |
| int i; |
| char *unit = " Hz"; |
| char rate_str[64]; |
| char name_str[256]; |
| double drate = (double)clock->rate; |
| |
| if (drate > 1000 && drate < 1000000) { |
| unit = "KHz"; |
| drate /= 1000; |
| } |
| if (drate > 1000000) { |
| unit = "MHz"; |
| drate /= 1000000; |
| } |
| if (clock->usecount) |
| bold[clock_line_no] = 1; |
| else |
| bold[clock_line_no] = 0; |
| |
| sprintf(rate_str, "%.2f %s", drate, unit); |
| prepare_name_str(name_str, clock); |
| sprintf(clock_lines[clock_line_no++], "%-55s 0x%-4x %-12s %-12d %-12d", |
| name_str, clock->flags, rate_str, clock->usecount, |
| clock->num_children); |
| |
| if (selected && (hrow == (clock_line_no - 1))) { |
| if (clock->expanded) |
| collapse_all_subclocks(clock); |
| else |
| clock->expanded = 1; |
| selected = 0; |
| } |
| |
| if (clock->expanded && clock->num_children) |
| for (i = 0; i < clock->num_children; i++) |
| add_clock_details_recur(clock->children[i], |
| hrow, selected); |
| strcpy(clock_lines[clock_line_no], ""); |
| } |
| |
| void print_clock_info(int verbose, int hrow, int selected) |
| { |
| int i, count = 0, delta; |
| |
| (void)verbose; |
| |
| print_clock_header(); |
| |
| for (i = 0; i < clocks_info->num_children; i++) |
| add_clock_details_recur(clocks_info->children[i], |
| hrow, selected); |
| |
| delta = calc_delta_screen_size(hrow); |
| |
| while (clock_lines[count + delta] && |
| strcmp(clock_lines[count + delta], "")) { |
| if (count < delta) { |
| count++; |
| continue; |
| } |
| print_one_clock(count - delta, clock_lines[count + delta], |
| bold[count + delta], (hrow == (count + delta))); |
| count++; |
| } |
| |
| old_clock_line_no = clock_line_no; |
| clock_line_no = 0; |
| } |
| |
| static void insert_children(struct clock_info **parent, struct clock_info *clk) |
| { |
| if (!(*parent)->num_children || (*parent)->children == NULL) { |
| (*parent)->children = (struct clock_info **) |
| malloc(sizeof(struct clock_info *)*2); |
| (*parent)->num_children = 0; |
| } else |
| (*parent)->children = (struct clock_info **) |
| realloc((*parent)->children, |
| sizeof(struct clock_info *) * |
| ((*parent)->num_children + 2)); |
| if ((*parent)->num_children > 0) |
| (*parent)->children[(*parent)->num_children - 1]->last_child |
| = 0; |
| clk->last_child = 1; |
| (*parent)->children[(*parent)->num_children] = clk; |
| (*parent)->children[(*parent)->num_children + 1] = NULL; |
| (*parent)->num_children++; |
| } |
| |
| static struct clock_info *read_clock_info_recur(char *clkpath, int level, |
| struct clock_info *parent) |
| { |
| int ret = 0; |
| DIR *dir; |
| char filename[PATH_MAX]; |
| struct dirent *item; |
| struct clock_info *cur = NULL; |
| struct stat buf; |
| |
| dir = opendir(clkpath); |
| if (!dir) |
| return NULL; |
| |
| while ((item = readdir(dir))) { |
| struct clock_info *child; |
| /* skip hidden dirs except ".." */ |
| if (item->d_name[0] == '.' ) |
| continue; |
| |
| sprintf(filename, "%s/%s", clkpath, item->d_name); |
| |
| ret = stat(filename, &buf); |
| |
| if (ret < 0) { |
| printf("Error doing a stat on %s\n", filename); |
| exit(1); |
| } |
| |
| if (S_ISREG(buf.st_mode)) { |
| if (!strcmp(item->d_name, "flags")) |
| file_read_hex(filename, &parent->flags); |
| if (!strcmp(item->d_name, "rate")) |
| file_read_int(filename, &parent->rate); |
| if (!strcmp(item->d_name, "usecount")) |
| file_read_int(filename, &parent->usecount); |
| continue; |
| } |
| |
| if (!S_ISDIR(buf.st_mode)) |
| continue; |
| |
| cur = (struct clock_info *)malloc(sizeof(struct clock_info)); |
| memset(cur, 0, sizeof(cur)); |
| strcpy(cur->name, item->d_name); |
| cur->children = NULL; |
| cur->parent = NULL; |
| cur->num_children = 0; |
| cur->expanded = 0; |
| cur->level = level; |
| child = read_clock_info_recur(filename, level + 1, cur); |
| insert_children(&parent, cur); |
| cur->parent = parent; |
| } |
| closedir(dir); |
| |
| return cur; |
| } |
| |
| static struct clock_info *clock_alloc(const char *name) |
| { |
| struct clock_info *ci; |
| |
| ci = malloc(sizeof(*ci)); |
| if (ci) { |
| memset(ci, 0, sizeof(*ci)); |
| strcpy(ci->name, name); |
| } |
| |
| return ci; |
| } |
| |
| static int fill_clock_cb(struct tree *t, void *data) |
| { |
| struct clock_info *clkinfo; |
| |
| clkinfo = clock_alloc(t->name); |
| if (!clkinfo) |
| return -1; |
| |
| t->private = clkinfo; |
| clkinfo->level = t->depth; |
| |
| file_read_value(t->path, "flags", "%x", &clkinfo->flags); |
| file_read_value(t->path, "rate", "%d", &clkinfo->rate); |
| file_read_value(t->path, "usecount", "%d", &clkinfo->usecount); |
| |
| return 0; |
| } |
| |
| int read_clock_info(char *clkpath) |
| { |
| DIR *dir; |
| struct dirent *item; |
| char filename[NAME_MAX]; |
| struct clock_info *child; |
| struct clock_info *cur; |
| int ret = -1; |
| |
| if (tree_for_each(clock_tree, fill_clock_cb, NULL)) |
| return -1; |
| |
| dir = opendir(clkpath); |
| if (!dir) |
| return -1; |
| |
| clocks_info = clock_alloc("/"); |
| if (!clocks_info) |
| return -1; |
| |
| while ((item = readdir(dir))) { |
| |
| /* skip hidden dirs except ".." */ |
| if (item->d_name[0] == '.') |
| continue; |
| |
| sprintf(filename, "%s/%s", clkpath, item->d_name); |
| |
| cur = clock_alloc(item->d_name); |
| if (!cur) |
| goto out; |
| |
| cur->parent = clocks_info; |
| cur->num_children = 0; |
| cur->expanded = 0; |
| cur->level = 1; |
| insert_children(&clocks_info, cur); |
| child = read_clock_info_recur(filename, 2, cur); |
| } |
| |
| ret = 0; |
| |
| out: |
| closedir(dir); |
| |
| return ret; |
| } |
| |
| void read_and_dump_clock_info_one(char *clk, bool dump) |
| { |
| printf("\nParents for \"%s\" Clock :\n\n", clk); |
| read_clock_info(clk_dir_path); |
| dump_all_parents(clk, dump); |
| printf("\n\n"); |
| } |
| |
| static inline const char *clock_rate(int *rate) |
| { |
| int r; |
| |
| /* GHZ */ |
| r = *rate >> 30; |
| if (r) { |
| *rate = r; |
| return "GHZ"; |
| } |
| |
| /* MHZ */ |
| r = *rate >> 20; |
| if (r) { |
| *rate = r; |
| return "MHZ"; |
| } |
| |
| /* KHZ */ |
| r = *rate >> 10; |
| if (r) { |
| *rate = r; |
| return "KHZ"; |
| } |
| |
| return ""; |
| } |
| |
| static int dump_clock_cb(struct tree *t, void *data) |
| { |
| struct clock_info *clk = t->private; |
| struct clock_info *pclk; |
| const char *unit; |
| int ret = 0; |
| int rate = clk->rate; |
| |
| if (!t->parent) { |
| printf("/\n"); |
| clk->prefix = ""; |
| return 0; |
| } |
| |
| pclk = t->parent->private; |
| |
| if (!clk->prefix) |
| ret = asprintf(&clk->prefix, "%s%s%s", pclk->prefix, |
| t->depth > 1 ? " ": "", t->next ? "|" : " "); |
| if (ret < 0) |
| return -1; |
| |
| unit = clock_rate(&rate); |
| |
| printf("%s%s-- %s (flags:0x%x, usecount:%d, rate: %d %s)\n", |
| clk->prefix, !t->next ? "`" : "", t->name, clk->flags, |
| clk->usecount, rate, unit); |
| |
| return 0; |
| } |
| |
| int dump_clock_info(void) |
| { |
| return tree_for_each(clock_tree, dump_clock_cb, NULL); |
| } |
| |
| void read_and_dump_clock_info(int verbose) |
| { |
| (void)verbose; |
| printf("\nClock Tree :\n"); |
| printf("**********\n"); |
| read_clock_info(clk_dir_path); |
| dump_clock_info(); |
| printf("\n\n"); |
| } |