// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski // SPDX-FileCopyrightText: 2022 Kent Gibson #include #include #include #include #include #include #include "tools-common.h" struct config { bool by_name; bool strict; bool unquoted_strings; const char *chip_id; }; static void print_help(void) { printf("Usage: %s [OPTIONS] [line]...\n", get_prog_name()); printf("\n"); printf("Print information about GPIO lines.\n"); printf("\n"); printf("Lines are specified by name, or optionally by offset if the chip option\n"); printf("is provided.\n"); printf("\n"); printf("If no lines are specified then all lines are displayed.\n"); printf("\n"); printf("Options:\n"); printf(" --by-name\t\ttreat lines as names even if they would parse as an offset\n"); printf(" -c, --chip \trestrict scope to a particular chip\n"); printf(" -h, --help\t\tdisplay this help and exit\n"); printf(" -s, --strict\t\tcheck all lines - don't assume line names are unique\n"); printf(" --unquoted\tdon't quote line or consumer names\n"); printf(" -v, --version\t\toutput version information and exit\n"); print_chip_help(); } static int parse_config(int argc, char **argv, struct config *cfg) { static const struct option longopts[] = { { "by-name", no_argument, NULL, 'B' }, { "chip", required_argument, NULL, 'c' }, { "help", no_argument, NULL, 'h' }, { "strict", no_argument, NULL, 's' }, { "unquoted", no_argument, NULL, 'Q' }, { "version", no_argument, NULL, 'v' }, { GETOPT_NULL_LONGOPT }, }; static const char *const shortopts = "+c:hsv"; int opti, optc; memset(cfg, 0, sizeof(*cfg)); for (;;) { optc = getopt_long(argc, argv, shortopts, longopts, &opti); if (optc < 0) break; switch (optc) { case 'B': cfg->by_name = true; break; case 'c': cfg->chip_id = optarg; break; case 's': cfg->strict = true; break; case 'h': print_help(); exit(EXIT_SUCCESS); case 'Q': cfg->unquoted_strings = true; break; case 'v': print_version(); exit(EXIT_SUCCESS); case '?': die("try %s --help", get_prog_name()); case 0: break; default: abort(); } } return optind; } /* * Minimal version similar to tools-common that indicates if a line should be * printed rather than storing details into the resolver. * Does not die on non-unique lines. */ static bool resolve_line(struct line_resolver *resolver, struct gpiod_line_info *info, int chip_num) { struct resolved_line *line; bool resolved = false; unsigned int offset; const char *name; int i; offset = gpiod_line_info_get_offset(info); for (i = 0; i < resolver->num_lines; i++) { line = &resolver->lines[i]; /* already resolved by offset? */ if (line->resolved && (line->offset == offset) && (line->chip_num == chip_num)) { resolved = true; } if (line->resolved && !resolver->strict) continue; /* else resolve by name */ name = gpiod_line_info_get_name(info); if (name && (strcmp(line->id, name) == 0)) { line->resolved = true; line->offset = offset; line->chip_num = chip_num; resolved = true; } } return resolved; } static void print_line_info(struct gpiod_line_info *info, bool unquoted_strings) { bool unquoted_name = unquoted_strings; char quoted_name[17]; const char *name; int len; name = gpiod_line_info_get_name(info); if (!name) { name = "unnamed"; unquoted_name = true; } if (unquoted_name) { printf("%-16s\t", name); } else { len = strlen(name); if (len <= 14) { quoted_name[0] = '"'; memcpy("ed_name[1], name, len); quoted_name[len + 1] = '"'; quoted_name[len + 2] = '\0'; printf("%-16s\t", quoted_name); } else { printf("\"%s\"\t", name); } } print_line_attributes(info, unquoted_strings); } /* * based on resolve_lines, but prints lines immediately rather than collecting * details in the resolver. */ static void list_lines(struct line_resolver *resolver, struct gpiod_chip *chip, int chip_num, struct config *cfg) { struct gpiod_chip_info *chip_info; struct gpiod_line_info *info; int offset, num_lines; chip_info = gpiod_chip_get_info(chip); if (!chip_info) die_perror("unable to read info from chip %s", gpiod_chip_get_path(chip)); num_lines = gpiod_chip_info_get_num_lines(chip_info); if ((chip_num == 0) && (cfg->chip_id && !cfg->by_name)) resolve_lines_by_offset(resolver, num_lines); for (offset = 0; ((offset < num_lines) && !(resolver->num_lines && resolve_done(resolver))); offset++) { info = gpiod_chip_get_line_info(chip, offset); if (!info) die_perror("unable to read info for line %d from %s", offset, gpiod_chip_info_get_name(chip_info)); if (resolver->num_lines && !resolve_line(resolver, info, chip_num)) { gpiod_line_info_free(info); continue; } if (resolver->num_lines) { printf("%s %u", gpiod_chip_info_get_name(chip_info), offset); } else { if (offset == 0) printf("%s - %u lines:\n", gpiod_chip_info_get_name(chip_info), num_lines); printf("\tline %3u:", offset); } fputc('\t', stdout); print_line_info(info, cfg->unquoted_strings); fputc('\n', stdout); gpiod_line_info_free(info); resolver->num_found++; } gpiod_chip_info_free(chip_info); } int main(int argc, char **argv) { struct line_resolver *resolver = NULL; int num_chips, i, ret = EXIT_SUCCESS; struct gpiod_chip *chip; struct config cfg; char **paths; set_prog_name(argv[0]); i = parse_config(argc, argv, &cfg); argc -= i; argv += i; if (!cfg.chip_id) cfg.by_name = true; num_chips = chip_paths(cfg.chip_id, &paths); if (cfg.chip_id && (num_chips == 0)) die("cannot find GPIO chip character device '%s'", cfg.chip_id); resolver = resolver_init(argc, argv, num_chips, cfg.strict, cfg.by_name); for (i = 0; i < num_chips; i++) { chip = gpiod_chip_open(paths[i]); if (chip) { list_lines(resolver, chip, i, &cfg); gpiod_chip_close(chip); } else { print_perror("unable to open chip '%s'", paths[i]); if (cfg.chip_id) return EXIT_FAILURE; ret = EXIT_FAILURE; } free(paths[i]); } free(paths); validate_resolution(resolver, cfg.chip_id); if (argc && resolver->num_found != argc) ret = EXIT_FAILURE; free(resolver); return ret; }