| /******************************************************************************* |
| * 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 <string.h> |
| #include <stdbool.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <sys/param.h> |
| #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; |
| int preparecount; |
| int enablecount; |
| int notifiercount; |
| } *clocks_info; |
| |
| enum clock_fw_type{ |
| CCF, /* common clock framework */ |
| OCF, /* old clock framework */ |
| MAX, |
| }; |
| |
| static struct tree *clock_tree = NULL; |
| static int clock_fw; |
| |
| static int locate_debugfs(char *clk_path) |
| { |
| strcpy(clk_path, "/sys/kernel/debug"); |
| return 0; |
| } |
| |
| 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); |
| } |
| |
| static inline int read_clock_cb(struct tree *t, void *data) |
| { |
| struct clock_info *clk = t->private; |
| |
| if(clock_fw == CCF) { |
| file_read_value(t->path, "clk_flags", "%x", &clk->flags); |
| file_read_value(t->path, "clk_rate", "%d", &clk->rate); |
| file_read_value(t->path, "clk_prepare_count", "%d", &clk->preparecount); |
| file_read_value(t->path, "clk_enable_count", "%d", &clk->enablecount); |
| file_read_value(t->path, "clk_notifier_count", "%d", &clk->notifiercount); |
| } |
| else { |
| 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(struct tree *tree) |
| { |
| return tree_for_each(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(clock_fw == CCF) { |
| if (asprintf(&clkline, "%-35s 0x%-8x %-12s %-10d %-11d %-15d %-14d %-10d", |
| clkname, clk->flags, clkrate, clk->usecount, t->nrchild, |
| clk->preparecount, clk->enablecount, clk->notifiercount) < 0) |
| goto free_clkrate; |
| } |
| else { |
| 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; |
| |
| 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_cb(struct tree *t, void *data) |
| { |
| /* 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; |
| |
| return _clock_print_info_cb(t, data); |
| } |
| |
| static int clock_print_header(void) |
| { |
| char *buf; |
| int ret; |
| |
| if(clock_fw == CCF) { |
| if (asprintf(&buf, "%-35s %-10s %-12s %-10s %-11s %-15s %-14s %-14s", |
| "Name", "Flags", "Rate", "Usecount", "Children", "Prepare_Count", |
| "Enable_Count", "Notifier_Count") < 0) |
| return -1; |
| } |
| else { |
| if (asprintf(&buf, "%-55s %-16s %-12s %-9s %-8s", |
| "Name", "Flags", "Rate", "Usecount", "Children") < 0) |
| return -1; |
| } |
| |
| ret = display_column_name(buf); |
| |
| free(buf); |
| |
| return ret; |
| } |
| |
| static int clock_print_info(struct tree *tree) |
| { |
| int ret, line = 0; |
| |
| display_reset_cursor(CLOCK); |
| |
| clock_print_header(); |
| |
| ret = tree_for_each(tree, clock_print_info_cb, &line); |
| |
| display_refresh_pad(CLOCK); |
| |
| return ret; |
| } |
| |
| static int clock_select(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 |
| */ |
| static int clock_display(bool refresh) |
| { |
| if (refresh && read_clock_info(clock_tree)) |
| return -1; |
| |
| return clock_print_info(clock_tree); |
| } |
| |
| static int clock_find(const char *name) |
| { |
| struct tree **ptree = NULL; |
| int i, nr, line = 0, ret = 0; |
| |
| nr = tree_finds(clock_tree, name, &ptree); |
| |
| display_reset_cursor(CLOCK); |
| |
| for (i = 0; i < nr; i++) { |
| |
| ret = _clock_print_info_cb(ptree[i], &line); |
| if (ret) |
| break; |
| |
| } |
| |
| display_refresh_pad(CLOCK); |
| |
| free(ptree); |
| |
| return ret; |
| } |
| |
| static int clock_selectf(void) |
| { |
| struct tree *t = display_get_row_data(CLOCK); |
| int line = 0; |
| |
| display_reset_cursor(CLOCK); |
| |
| if (tree_for_each_parent(t, _clock_print_info_cb, &line)) |
| return -1; |
| |
| return display_refresh_pad(CLOCK); |
| } |
| |
| /* |
| * 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(clock_tree)) |
| 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_select, |
| .find = clock_find, |
| .selectf = clock_selectf, |
| }; |
| |
| /* |
| * Initialize the clock framework |
| */ |
| int clock_init(void) |
| { |
| char clk_dir_path[MAX+1][PATH_MAX]; |
| |
| if (locate_debugfs(clk_dir_path[CCF]) || locate_debugfs(clk_dir_path[OCF])) |
| return -1; |
| |
| sprintf(clk_dir_path[CCF], "%s/clk", clk_dir_path[CCF]); |
| sprintf(clk_dir_path[OCF], "%s/clock", clk_dir_path[OCF]); |
| if (!access(clk_dir_path[CCF], F_OK)) { |
| clock_fw = CCF; |
| strcpy(clk_dir_path[MAX],clk_dir_path[CCF]); |
| } |
| else if(!access(clk_dir_path[OCF], F_OK)) { |
| clock_fw = OCF; |
| strcpy(clk_dir_path[MAX],clk_dir_path[OCF]); |
| } |
| else |
| return -1; |
| |
| clock_tree = tree_load(clk_dir_path[MAX], NULL, false); |
| if (!clock_tree) |
| return -1; |
| |
| if (fill_clock_tree()) |
| return -1; |
| |
| return display_register(CLOCK, &clock_ops); |
| } |