| /* |
| * 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 <stdio.h> |
| #undef _GNU_SOURCE |
| #endif |
| #include <mntent.h> |
| #include <string.h> |
| #include <stdbool.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <sys/param.h> |
| #include <sys/stat.h> |
| |
| #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); |
| } |