/* * Power debug tool (powerdebug) * * Copyright (C) 2016, Linaro Limited. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #ifndef _GNU_SOURCE #define _GNU_SOURCE #include #undef _GNU_SOURCE #endif #include #include #include #include #include #include #include #include "powerdebug.h" #include "display.h" #include "tree.h" #include "utils.h" #define SYSFS_GPIO "/sys/class/gpio" #define MAX_VALUE_BYTE 10 struct gpio_info { bool expanded; int active_low; int value; char direction[MAX_VALUE_BYTE]; char edge[MAX_VALUE_BYTE]; char *prefix; } *gpios_info; static struct tree *gpio_tree = NULL; static struct gpio_info *gpio_alloc(void) { struct gpio_info *gi; gi = malloc(sizeof(*gi)); if (gi) { memset(gi, -1, sizeof(*gi)); memset(gi->direction, 0, MAX_VALUE_BYTE); memset(gi->edge, 0, MAX_VALUE_BYTE); gi->prefix = NULL; } return gi; } static int gpio_filter_cb(const char *name) { /* let's ignore some directories in order to avoid to be * pulled inside the sysfs circular symlinks mess/hell * (choose the word which fit better) */ if (!strcmp(name, "device")) return 1; if (!strcmp(name, "subsystem")) return 1; if (!strcmp(name, "driver")) return 1; /* we want to ignore the gpio chips */ if (strstr(name, "chip")) return 1; /* we are not interested by the power value */ if (!strcmp(name, "power")) return 1; return 0; } static inline int read_gpio_cb(struct tree *t, void *data) { struct gpio_info *gpio = t->private; file_read_value(t->path, "active_low", "%d", &gpio->active_low); file_read_value(t->path, "value", "%d", &gpio->value); file_read_value(t->path, "edge", "%8s", &gpio->edge); file_read_value(t->path, "direction", "%4s", &gpio->direction); return 0; } static int read_gpio_info(struct tree *tree) { return tree_for_each(tree, read_gpio_cb, NULL); } static int fill_gpio_cb(struct tree *t, void *data) { struct gpio_info *gpio; gpio = gpio_alloc(); if (!gpio) return -1; t->private = gpio; /* we skip the root node but we set it expanded for its children */ if (!t->parent) { gpio->expanded = true; return 0; } return read_gpio_cb(t, data); } static int fill_gpio_tree(void) { return tree_for_each(gpio_tree, fill_gpio_cb, NULL); } static int dump_gpio_cb(struct tree *t, void *data) { struct gpio_info *gpio = t->private; struct gpio_info *pgpio; if (!t->parent) { printf("/\n"); gpio->prefix = ""; return 0; } pgpio = t->parent->private; if (!gpio->prefix) if (asprintf(&gpio->prefix, "%s%s%s", pgpio->prefix, t->depth > 1 ? " ": "", t->next ? "|" : " ") < 0) return -1; printf("%s%s-- %s (", gpio->prefix, !t->next ? "`" : "", t->name); if (gpio->active_low != -1) printf(" active_low:%d", gpio->active_low); if (gpio->value != -1) printf(", value:%d", gpio->value); if (gpio->edge[0] != 0) printf(", edge:%s", gpio->edge); if (gpio->direction[0] != 0) printf(", direction:%s", gpio->direction); printf(" )\n"); return 0; } int dump_gpio_info(void) { return tree_for_each(gpio_tree, dump_gpio_cb, NULL); } static char *gpio_line(struct tree *t) { struct gpio_info *gpio = t->private; char *gpioline; if (asprintf(&gpioline, "%-20s %-10d %-10d %-10s %-10s", t->name, gpio->value, gpio->active_low, gpio->edge, gpio->direction) < 0) return NULL; return gpioline; } static int _gpio_print_info_cb(struct tree *t, void *data) { int *line = data; char *buffer; /* we skip the root node of the tree */ if (!t->parent) return 0; buffer = gpio_line(t); if (!buffer) return -1; display_print_line(GPIO, *line, buffer, 0, t); (*line)++; free(buffer); return 0; } static int gpio_print_info_cb(struct tree *t, void *data) { /* we skip the root node of the tree */ if (!t->parent) return 0; return _gpio_print_info_cb(t, data); } static int gpio_print_header(void) { char *buf; int ret; if (asprintf(&buf, "%-20s %-10s %-10s %-10s %-10s", "Name", "Value", "Active_low", "Edge", "Direction") < 0) return -1; ret = display_column_name(buf); free(buf); return ret; } static int gpio_print_info(struct tree *tree) { int ret, line = 0; display_reset_cursor(GPIO); gpio_print_header(); ret = tree_for_each(tree, gpio_print_info_cb, &line); display_refresh_pad(GPIO); return ret; } void export_free_gpios(void) { FILE *fgpio, *fgpio_export; int i, gpio_max = 0; char *line = NULL; ssize_t nrread; size_t len = 0; fgpio = fopen("/sys/kernel/debug/gpio", "r"); if (!fgpio) { printf("failed to read debugfs gpio file\n"); return; } fgpio_export = fopen("/sys/class/gpio/export", "w"); if (!fgpio_export) { printf("failed to write open gpio-export file\n"); goto out; } /* export the gpios */ while ((nrread = getline(&line, &len, fgpio)) != -1) { if (strstr(line, "GPIOs")) sscanf(line, "%*[^-]-%d", &gpio_max); } printf("log: total gpios = %d\n", gpio_max); for (i = 0 ; i <= gpio_max ; i++) { char command[50] = ""; sprintf(command, "echo %d > /sys/class/gpio/export", i); if (system(command) < 0) printf("error: failed to export gpio-%d\n", i); } free(line); if (fgpio) fclose(fgpio); out: if (fgpio_export) fclose(fgpio_export); return; } static int gpio_load_info(void) { if (gpio_tree) return 0; export_free_gpios(); gpio_tree = tree_load(SYSFS_GPIO, gpio_filter_cb, false); if (!gpio_tree) return -1; if (fill_gpio_tree()) return -1; return 0; } int gpio_dump(void) { int ret; if (gpio_load_info()) return -1; printf("\nGpio Tree :\n"); printf("***********\n"); ret = dump_gpio_info(); printf("\n\n"); return ret; } static int gpio_display(bool refresh) { if (gpio_load_info()) { display_print_error(GPIO, 0, "Failed to read gpio info"); return 0; /* we don't want this to be a critical error */ } if (refresh && read_gpio_info(gpio_tree)) return -1; return gpio_print_info(gpio_tree); } static int gpio_change(int keyvalue) { struct tree *t = display_get_row_data(GPIO); struct gpio_info *gpio = t->private; if (!t || !gpio) return -1; switch (keyvalue) { case 'D': /* Only change direction when gpio interrupt not set.*/ if (!strstr(gpio->edge, "none")) return 0; if (strstr(gpio->direction, "in")) strcpy(gpio->direction, "out"); else if (strstr(gpio->direction, "out")) strcpy(gpio->direction, "in"); file_write_value(t->path, "direction", "%s", &gpio->direction); file_read_value(t->path, "direction", "%s", &gpio->direction); file_read_value(t->path, "value", "%d", &gpio->value); break; case 'V': /* Only change value when gpio direction is out. */ if (!strstr(gpio->edge, "none") || !strstr(gpio->direction, "out")) return 0; if (gpio->value) file_write_value(t->path, "direction", "%s", &"low"); else file_write_value(t->path, "direction", "%s", &"high"); file_read_value(t->path, "value", "%d", &gpio->value); break; default: return -1; } return 0; } static struct display_ops gpio_ops = { .display = gpio_display, .change = gpio_change, }; /* * Initialize the gpio framework */ int gpio_init(struct powerdebug_options *options) { if (!(options->flags & GPIO_OPTION)) return 0; return display_register(GPIO, &gpio_ops); }