| /******************************************************************************* |
| * 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 |
| * |
| * Daniel Lezcano <daniel.lezcano@linaro.org> (IBM Corporation) |
| * - Rewrote code and API |
| * |
| *******************************************************************************/ |
| |
| #ifndef _GNU_SOURCE |
| #define _GNU_SOURCE |
| #include <stdio.h> |
| #undef _GNU_SOURCE |
| #endif |
| #include <mntent.h> |
| #include <sys/stat.h> |
| |
| #include "powerdebug.h" |
| #include "display.h" |
| #include "clocks.h" |
| #include "tree.h" |
| #include "utils.h" |
| |
| struct clock_info { |
| int flags; |
| int rate; |
| int usecount; |
| bool expanded; |
| char *prefix; |
| } *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; |
| } |
| |
| static struct clock_info *clock_alloc(void) |
| { |
| struct clock_info *ci; |
| |
| ci = malloc(sizeof(*ci)); |
| if (ci) |
| memset(ci, 0, sizeof(*ci)); |
| |
| return ci; |
| } |
| |
| 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); |
| } |
| |
| static int dump_all_parents(char *clkarg) |
| { |
| struct tree *tree; |
| |
| tree = tree_find(clock_tree, clkarg); |
| if (!tree) { |
| printf("Clock NOT found!\n"); |
| return -1; |
| } |
| |
| return tree_for_each_parent(tree, dump_clock_cb, NULL); |
| } |
| |
| 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); |
| display_reset_cursor(CLOCK); |
| display_print_line(CLOCK, 0, str, 1, NULL); |
| display_refresh_pad(CLOCK); |
| return; |
| } |
| sprintf(name, "Parents for \"%s\" Clock : \n", clkname); |
| display_reset_cursor(CLOCK); |
| display_print_line(CLOCK, 0, name, 1, NULL); |
| display_refresh_pad(CLOCK); |
| dump_all_parents(clkname); |
| } |
| |
| static inline int read_clock_cb(struct tree *t, void *data) |
| { |
| struct clock_info *clk = t->private; |
| |
| file_read_value(t->path, "flags", "%x", &clk->flags); |
| file_read_value(t->path, "rate", "%d", &clk->rate); |
| file_read_value(t->path, "usecount", "%d", &clk->usecount); |
| |
| return 0; |
| } |
| |
| static int read_clock_info(void) |
| { |
| return tree_for_each(clock_tree, read_clock_cb, NULL); |
| } |
| |
| static int fill_clock_cb(struct tree *t, void *data) |
| { |
| struct clock_info *clk; |
| |
| clk = clock_alloc(); |
| if (!clk) |
| return -1; |
| t->private = clk; |
| |
| /* we skip the root node but we set it expanded for its children */ |
| if (!t->parent) { |
| clk->expanded = true; |
| return 0; |
| } |
| |
| return read_clock_cb(t, data); |
| } |
| |
| static int fill_clock_tree(void) |
| { |
| return tree_for_each(clock_tree, fill_clock_cb, NULL); |
| } |
| |
| static int is_collapsed(struct tree *t, void *data) |
| { |
| struct clock_info *clk = t->private; |
| |
| if (!clk->expanded) |
| return 1; |
| |
| return 0; |
| } |
| |
| static char *clock_line(struct tree *t) |
| { |
| struct clock_info *clk; |
| int rate; |
| const char *clkunit; |
| char *clkrate, *clkname, *clkline = NULL; |
| |
| clk = t->private; |
| rate = clk->rate; |
| clkunit = clock_rate(&rate); |
| |
| if (asprintf(&clkname, "%*s%s", (t->depth - 1) * 2, "", t->name) < 0) |
| return NULL; |
| |
| if (asprintf(&clkrate, "%d%s", rate, clkunit) < 0) |
| goto free_clkname; |
| |
| if (asprintf(&clkline, "%-55s 0x%-16x %-12s %-9d %-8d", clkname, |
| clk->flags, clkrate, clk->usecount, t->nrchild) < 0) |
| goto free_clkrate; |
| |
| free_clkrate: |
| free(clkrate); |
| free_clkname: |
| free(clkname); |
| |
| return clkline; |
| } |
| |
| static int clock_print_info_cb(struct tree *t, void *data) |
| { |
| struct clock_info *clock = t->private; |
| int *line = data; |
| char *buffer; |
| |
| /* we skip the root node of the tree */ |
| if (!t->parent) |
| return 0; |
| |
| /* show the clock when *all* its parent is expanded */ |
| if (tree_for_each_parent(t->parent, is_collapsed, NULL)) |
| return 0; |
| |
| buffer = clock_line(t); |
| if (!buffer) |
| return -1; |
| |
| display_print_line(CLOCK, *line, buffer, clock->usecount, t); |
| |
| (*line)++; |
| |
| free(buffer); |
| |
| return 0; |
| } |
| |
| static int clock_print_info(void) |
| { |
| int ret, line = 0; |
| |
| print_clock_header(); |
| |
| display_reset_cursor(CLOCK); |
| |
| ret = tree_for_each(clock_tree, clock_print_info_cb, &line); |
| |
| display_refresh_pad(CLOCK); |
| |
| return ret; |
| } |
| |
| int clock_toggle_expanded(void) |
| { |
| struct tree *t = display_get_row_data(CLOCK); |
| struct clock_info *clk = t->private; |
| |
| clk->expanded = !clk->expanded; |
| |
| return 0; |
| } |
| |
| /* |
| * Read the clock information and fill the tree with the information |
| * found in the files. Then print the result to the text based interface |
| * Return 0 on success, < 0 otherwise |
| */ |
| int clock_display(void) |
| { |
| if (read_clock_info()) |
| return -1; |
| |
| return clock_print_info(); |
| } |
| |
| /* |
| * Read the clock information and fill the tree with the information |
| * found in the files. Then dump to stdout a formatted result. |
| * @clk : a name for a specific clock we want to show |
| * Return 0 on success, < 0 otherwise |
| */ |
| int clock_dump(char *clk) |
| { |
| int ret; |
| |
| if (read_clock_info()) |
| return -1; |
| |
| if (clk) { |
| printf("\nParents for \"%s\" Clock :\n\n", clk); |
| ret = dump_all_parents(clk); |
| printf("\n\n"); |
| } else { |
| printf("\nClock Tree :\n"); |
| printf("**********\n"); |
| ret = dump_clock_info(); |
| printf("\n\n"); |
| } |
| |
| return ret; |
| } |
| |
| static struct display_ops clock_ops = { |
| .display = clock_display, |
| .select = clock_toggle_expanded, |
| }; |
| |
| /* |
| * Initialize the clock framework |
| */ |
| int clock_init(void) |
| { |
| char clk_dir_path[PATH_MAX]; |
| |
| if (display_register(CLOCK, &clock_ops)) |
| return -1; |
| |
| if (locate_debugfs(clk_dir_path)) |
| return -1; |
| |
| sprintf(clk_dir_path, "%s/clock", clk_dir_path); |
| |
| if (access(clk_dir_path, F_OK)) |
| return -1; |
| |
| clock_tree = tree_load(clk_dir_path, NULL); |
| if (!clock_tree) |
| return -1; |
| |
| return fill_clock_tree(); |
| } |