diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 10:05:51 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 10:05:51 +0000 |
commit | 5d1646d90e1f2cceb9f0828f4b28318cd0ec7744 (patch) | |
tree | a94efe259b9009378be6d90eb30d2b019d95c194 /tools/gpio | |
parent | Initial commit. (diff) | |
download | linux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.tar.xz linux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.zip |
Adding upstream version 5.10.209.upstream/5.10.209upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tools/gpio')
-rw-r--r-- | tools/gpio/.gitignore | 6 | ||||
-rw-r--r-- | tools/gpio/Build | 5 | ||||
-rw-r--r-- | tools/gpio/Makefile | 91 | ||||
-rw-r--r-- | tools/gpio/gpio-event-mon.c | 233 | ||||
-rw-r--r-- | tools/gpio/gpio-hammer.c | 176 | ||||
-rw-r--r-- | tools/gpio/gpio-utils.c | 373 | ||||
-rw-r--r-- | tools/gpio/gpio-utils.h | 79 | ||||
-rw-r--r-- | tools/gpio/gpio-watch.c | 100 | ||||
-rw-r--r-- | tools/gpio/lsgpio.c | 221 |
9 files changed, 1284 insertions, 0 deletions
diff --git a/tools/gpio/.gitignore b/tools/gpio/.gitignore new file mode 100644 index 000000000..a00d60402 --- /dev/null +++ b/tools/gpio/.gitignore @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only +gpio-event-mon +gpio-hammer +gpio-watch +lsgpio +include/linux/gpio.h diff --git a/tools/gpio/Build b/tools/gpio/Build new file mode 100644 index 000000000..67c7b7f6a --- /dev/null +++ b/tools/gpio/Build @@ -0,0 +1,5 @@ +gpio-utils-y += gpio-utils.o +lsgpio-y += lsgpio.o gpio-utils.o +gpio-hammer-y += gpio-hammer.o gpio-utils.o +gpio-event-mon-y += gpio-event-mon.o gpio-utils.o +gpio-watch-y += gpio-watch.o diff --git a/tools/gpio/Makefile b/tools/gpio/Makefile new file mode 100644 index 000000000..440434027 --- /dev/null +++ b/tools/gpio/Makefile @@ -0,0 +1,91 @@ +# SPDX-License-Identifier: GPL-2.0 +include ../scripts/Makefile.include + +bindir ?= /usr/bin + +# This will work when gpio is built in tools env. where srctree +# isn't set and when invoked from selftests build, where srctree +# is set to ".". building_out_of_srctree is undefined for in srctree +# builds +ifndef building_out_of_srctree +srctree := $(patsubst %/,%,$(dir $(CURDIR))) +srctree := $(patsubst %/,%,$(dir $(srctree))) +endif + +# Do not use make's built-in rules +# (this improves performance and avoids hard-to-debug behaviour); +MAKEFLAGS += -r + +override CFLAGS += -O2 -Wall -g -D_GNU_SOURCE -I$(OUTPUT)include + +ALL_TARGETS := lsgpio gpio-hammer gpio-event-mon gpio-watch +ALL_PROGRAMS := $(patsubst %,$(OUTPUT)%,$(ALL_TARGETS)) + +all: $(ALL_PROGRAMS) + +export srctree OUTPUT CC LD CFLAGS +include $(srctree)/tools/build/Makefile.include + +# +# We need the following to be outside of kernel tree +# +$(OUTPUT)include/linux/gpio.h: ../../include/uapi/linux/gpio.h + mkdir -p $(OUTPUT)include/linux 2>&1 || true + ln -sf $(CURDIR)/../../include/uapi/linux/gpio.h $@ + +prepare: $(OUTPUT)include/linux/gpio.h + +GPIO_UTILS_IN := $(OUTPUT)gpio-utils-in.o +$(GPIO_UTILS_IN): prepare FORCE + $(Q)$(MAKE) $(build)=gpio-utils + +# +# lsgpio +# +LSGPIO_IN := $(OUTPUT)lsgpio-in.o +$(LSGPIO_IN): prepare FORCE $(OUTPUT)gpio-utils-in.o + $(Q)$(MAKE) $(build)=lsgpio +$(OUTPUT)lsgpio: $(LSGPIO_IN) + $(QUIET_LINK)$(CC) $(CFLAGS) $(LDFLAGS) $< -o $@ + +# +# gpio-hammer +# +GPIO_HAMMER_IN := $(OUTPUT)gpio-hammer-in.o +$(GPIO_HAMMER_IN): prepare FORCE $(OUTPUT)gpio-utils-in.o + $(Q)$(MAKE) $(build)=gpio-hammer +$(OUTPUT)gpio-hammer: $(GPIO_HAMMER_IN) + $(QUIET_LINK)$(CC) $(CFLAGS) $(LDFLAGS) $< -o $@ + +# +# gpio-event-mon +# +GPIO_EVENT_MON_IN := $(OUTPUT)gpio-event-mon-in.o +$(GPIO_EVENT_MON_IN): prepare FORCE $(OUTPUT)gpio-utils-in.o + $(Q)$(MAKE) $(build)=gpio-event-mon +$(OUTPUT)gpio-event-mon: $(GPIO_EVENT_MON_IN) + $(QUIET_LINK)$(CC) $(CFLAGS) $(LDFLAGS) $< -o $@ + +# +# gpio-watch +# +GPIO_WATCH_IN := $(OUTPUT)gpio-watch-in.o +$(GPIO_WATCH_IN): prepare FORCE + $(Q)$(MAKE) $(build)=gpio-watch +$(OUTPUT)gpio-watch: $(GPIO_WATCH_IN) + $(QUIET_LINK)$(CC) $(CFLAGS) $(LDFLAGS) $< -o $@ + +clean: + rm -f $(ALL_PROGRAMS) + rm -f $(OUTPUT)include/linux/gpio.h + find $(if $(OUTPUT),$(OUTPUT),.) -name '*.o' -delete -o -name '\.*.d' -delete + +install: $(ALL_PROGRAMS) + install -d -m 755 $(DESTDIR)$(bindir); \ + for program in $(ALL_PROGRAMS); do \ + install $$program $(DESTDIR)$(bindir); \ + done + +FORCE: + +.PHONY: all install clean FORCE prepare diff --git a/tools/gpio/gpio-event-mon.c b/tools/gpio/gpio-event-mon.c new file mode 100644 index 000000000..367c10636 --- /dev/null +++ b/tools/gpio/gpio-event-mon.c @@ -0,0 +1,233 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * gpio-event-mon - monitor GPIO line events from userspace + * + * Copyright (C) 2016 Linus Walleij + * + * Usage: + * gpio-event-mon -n <device-name> -o <offset> + */ + +#include <unistd.h> +#include <stdlib.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <dirent.h> +#include <errno.h> +#include <string.h> +#include <poll.h> +#include <fcntl.h> +#include <getopt.h> +#include <inttypes.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <linux/gpio.h> +#include "gpio-utils.h" + +int monitor_device(const char *device_name, + unsigned int *lines, + unsigned int num_lines, + struct gpio_v2_line_config *config, + unsigned int loops) +{ + struct gpio_v2_line_values values; + char *chrdev_name; + int cfd, lfd; + int ret; + int i = 0; + + ret = asprintf(&chrdev_name, "/dev/%s", device_name); + if (ret < 0) + return -ENOMEM; + + cfd = open(chrdev_name, 0); + if (cfd == -1) { + ret = -errno; + fprintf(stderr, "Failed to open %s\n", chrdev_name); + goto exit_free_name; + } + + ret = gpiotools_request_line(device_name, lines, num_lines, config, + "gpio-event-mon"); + if (ret < 0) + goto exit_device_close; + else + lfd = ret; + + /* Read initial states */ + values.mask = 0; + values.bits = 0; + for (i = 0; i < num_lines; i++) + gpiotools_set_bit(&values.mask, i); + ret = gpiotools_get_values(lfd, &values); + if (ret < 0) { + fprintf(stderr, + "Failed to issue GPIO LINE GET VALUES IOCTL (%d)\n", + ret); + goto exit_line_close; + } + + if (num_lines == 1) { + fprintf(stdout, "Monitoring line %d on %s\n", lines[0], device_name); + fprintf(stdout, "Initial line value: %d\n", + gpiotools_test_bit(values.bits, 0)); + } else { + fprintf(stdout, "Monitoring lines %d", lines[0]); + for (i = 1; i < num_lines - 1; i++) + fprintf(stdout, ", %d", lines[i]); + fprintf(stdout, " and %d on %s\n", lines[i], device_name); + fprintf(stdout, "Initial line values: %d", + gpiotools_test_bit(values.bits, 0)); + for (i = 1; i < num_lines - 1; i++) + fprintf(stdout, ", %d", + gpiotools_test_bit(values.bits, i)); + fprintf(stdout, " and %d\n", + gpiotools_test_bit(values.bits, i)); + } + + i = 0; + while (1) { + struct gpio_v2_line_event event; + + ret = read(lfd, &event, sizeof(event)); + if (ret == -1) { + if (errno == -EAGAIN) { + fprintf(stderr, "nothing available\n"); + continue; + } else { + ret = -errno; + fprintf(stderr, "Failed to read event (%d)\n", + ret); + break; + } + } + + if (ret != sizeof(event)) { + fprintf(stderr, "Reading event failed\n"); + ret = -EIO; + break; + } + fprintf(stdout, "GPIO EVENT at %" PRIu64 " on line %d (%d|%d) ", + (uint64_t)event.timestamp_ns, event.offset, event.line_seqno, + event.seqno); + switch (event.id) { + case GPIO_V2_LINE_EVENT_RISING_EDGE: + fprintf(stdout, "rising edge"); + break; + case GPIO_V2_LINE_EVENT_FALLING_EDGE: + fprintf(stdout, "falling edge"); + break; + default: + fprintf(stdout, "unknown event"); + } + fprintf(stdout, "\n"); + + i++; + if (i == loops) + break; + } + +exit_line_close: + if (close(lfd) == -1) + perror("Failed to close line file"); +exit_device_close: + if (close(cfd) == -1) + perror("Failed to close GPIO character device file"); +exit_free_name: + free(chrdev_name); + return ret; +} + +void print_usage(void) +{ + fprintf(stderr, "Usage: gpio-event-mon [options]...\n" + "Listen to events on GPIO lines, 0->1 1->0\n" + " -n <name> Listen on GPIOs on a named device (must be stated)\n" + " -o <n> Offset of line to monitor (may be repeated)\n" + " -d Set line as open drain\n" + " -s Set line as open source\n" + " -r Listen for rising edges\n" + " -f Listen for falling edges\n" + " -b <n> Debounce the line with period n microseconds\n" + " [-c <n>] Do <n> loops (optional, infinite loop if not stated)\n" + " -? This helptext\n" + "\n" + "Example:\n" + "gpio-event-mon -n gpiochip0 -o 4 -r -f -b 10000\n" + ); +} + +#define EDGE_FLAGS \ + (GPIO_V2_LINE_FLAG_EDGE_RISING | \ + GPIO_V2_LINE_FLAG_EDGE_FALLING) + +int main(int argc, char **argv) +{ + const char *device_name = NULL; + unsigned int lines[GPIO_V2_LINES_MAX]; + unsigned int num_lines = 0; + unsigned int loops = 0; + struct gpio_v2_line_config config; + int c, attr, i; + unsigned long debounce_period_us = 0; + + memset(&config, 0, sizeof(config)); + config.flags = GPIO_V2_LINE_FLAG_INPUT; + while ((c = getopt(argc, argv, "c:n:o:b:dsrf?")) != -1) { + switch (c) { + case 'c': + loops = strtoul(optarg, NULL, 10); + break; + case 'n': + device_name = optarg; + break; + case 'o': + if (num_lines >= GPIO_V2_LINES_MAX) { + print_usage(); + return -1; + } + lines[num_lines] = strtoul(optarg, NULL, 10); + num_lines++; + break; + case 'b': + debounce_period_us = strtoul(optarg, NULL, 10); + break; + case 'd': + config.flags |= GPIO_V2_LINE_FLAG_OPEN_DRAIN; + break; + case 's': + config.flags |= GPIO_V2_LINE_FLAG_OPEN_SOURCE; + break; + case 'r': + config.flags |= GPIO_V2_LINE_FLAG_EDGE_RISING; + break; + case 'f': + config.flags |= GPIO_V2_LINE_FLAG_EDGE_FALLING; + break; + case '?': + print_usage(); + return -1; + } + } + + if (debounce_period_us) { + attr = config.num_attrs; + config.num_attrs++; + for (i = 0; i < num_lines; i++) + gpiotools_set_bit(&config.attrs[attr].mask, i); + config.attrs[attr].attr.id = GPIO_V2_LINE_ATTR_ID_DEBOUNCE; + config.attrs[attr].attr.debounce_period_us = debounce_period_us; + } + + if (!device_name || num_lines == 0) { + print_usage(); + return -1; + } + if (!(config.flags & EDGE_FLAGS)) { + printf("No flags specified, listening on both rising and " + "falling edges\n"); + config.flags |= EDGE_FLAGS; + } + return monitor_device(device_name, lines, num_lines, &config, loops); +} diff --git a/tools/gpio/gpio-hammer.c b/tools/gpio/gpio-hammer.c new file mode 100644 index 000000000..54fdf59dd --- /dev/null +++ b/tools/gpio/gpio-hammer.c @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * gpio-hammer - example swiss army knife to shake GPIO lines on a system + * + * Copyright (C) 2016 Linus Walleij + * + * Usage: + * gpio-hammer -n <device-name> -o <offset1> -o <offset2> + */ + +#include <unistd.h> +#include <stdlib.h> +#include <stdbool.h> +#include <stdio.h> +#include <dirent.h> +#include <errno.h> +#include <string.h> +#include <poll.h> +#include <fcntl.h> +#include <getopt.h> +#include <sys/ioctl.h> +#include <linux/gpio.h> +#include "gpio-utils.h" + +int hammer_device(const char *device_name, unsigned int *lines, int num_lines, + unsigned int loops) +{ + struct gpio_v2_line_values values; + struct gpio_v2_line_config config; + char swirr[] = "-\\|/"; + int fd; + int ret; + int i, j; + unsigned int iteration = 0; + + memset(&config, 0, sizeof(config)); + config.flags = GPIO_V2_LINE_FLAG_OUTPUT; + + ret = gpiotools_request_line(device_name, lines, num_lines, + &config, "gpio-hammer"); + if (ret < 0) + goto exit_error; + else + fd = ret; + + values.mask = 0; + values.bits = 0; + for (i = 0; i < num_lines; i++) + gpiotools_set_bit(&values.mask, i); + + ret = gpiotools_get_values(fd, &values); + if (ret < 0) + goto exit_close_error; + + fprintf(stdout, "Hammer lines ["); + for (i = 0; i < num_lines; i++) { + fprintf(stdout, "%d", lines[i]); + if (i != (num_lines - 1)) + fprintf(stdout, ", "); + } + fprintf(stdout, "] on %s, initial states: [", device_name); + for (i = 0; i < num_lines; i++) { + fprintf(stdout, "%d", gpiotools_test_bit(values.bits, i)); + if (i != (num_lines - 1)) + fprintf(stdout, ", "); + } + fprintf(stdout, "]\n"); + + /* Hammertime! */ + j = 0; + while (1) { + /* Invert all lines so we blink */ + for (i = 0; i < num_lines; i++) + gpiotools_change_bit(&values.bits, i); + + ret = gpiotools_set_values(fd, &values); + if (ret < 0) + goto exit_close_error; + + /* Re-read values to get status */ + ret = gpiotools_get_values(fd, &values); + if (ret < 0) + goto exit_close_error; + + fprintf(stdout, "[%c] ", swirr[j]); + j++; + if (j == sizeof(swirr) - 1) + j = 0; + + fprintf(stdout, "["); + for (i = 0; i < num_lines; i++) { + fprintf(stdout, "%d: %d", lines[i], + gpiotools_test_bit(values.bits, i)); + if (i != (num_lines - 1)) + fprintf(stdout, ", "); + } + fprintf(stdout, "]\r"); + fflush(stdout); + sleep(1); + iteration++; + if (loops && iteration == loops) + break; + } + fprintf(stdout, "\n"); + ret = 0; + +exit_close_error: + gpiotools_release_line(fd); +exit_error: + return ret; +} + +void print_usage(void) +{ + fprintf(stderr, "Usage: gpio-hammer [options]...\n" + "Hammer GPIO lines, 0->1->0->1...\n" + " -n <name> Hammer GPIOs on a named device (must be stated)\n" + " -o <n> Offset[s] to hammer, at least one, several can be stated\n" + " [-c <n>] Do <n> loops (optional, infinite loop if not stated)\n" + " -? This helptext\n" + "\n" + "Example:\n" + "gpio-hammer -n gpiochip0 -o 4\n" + ); +} + +int main(int argc, char **argv) +{ + const char *device_name = NULL; + unsigned int lines[GPIOHANDLES_MAX]; + unsigned int loops = 0; + int num_lines; + int c; + int i; + + i = 0; + while ((c = getopt(argc, argv, "c:n:o:?")) != -1) { + switch (c) { + case 'c': + loops = strtoul(optarg, NULL, 10); + break; + case 'n': + device_name = optarg; + break; + case 'o': + /* + * Avoid overflow. Do not immediately error, we want to + * be able to accurately report on the amount of times + * '-o' was given to give an accurate error message + */ + if (i < GPIOHANDLES_MAX) + lines[i] = strtoul(optarg, NULL, 10); + + i++; + break; + case '?': + print_usage(); + return -1; + } + } + + if (i >= GPIOHANDLES_MAX) { + fprintf(stderr, + "Only %d occurrences of '-o' are allowed, %d were found\n", + GPIOHANDLES_MAX, i + 1); + return -1; + } + + num_lines = i; + + if (!device_name || !num_lines) { + print_usage(); + return -1; + } + return hammer_device(device_name, lines, num_lines, loops); +} diff --git a/tools/gpio/gpio-utils.c b/tools/gpio/gpio-utils.c new file mode 100644 index 000000000..37187e056 --- /dev/null +++ b/tools/gpio/gpio-utils.c @@ -0,0 +1,373 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * GPIO tools - helpers library for the GPIO tools + * + * Copyright (C) 2015 Linus Walleij + * Copyright (C) 2016 Bamvor Jian Zhang + */ + +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <fcntl.h> +#include <getopt.h> +#include <sys/ioctl.h> +#include <linux/gpio.h> +#include "gpio-utils.h" + +#define CONSUMER "gpio-utils" + +/** + * doc: Operation of gpio + * + * Provide the api of gpiochip for chardev interface. There are two + * types of api. The first one provide as same function as each + * ioctl, including request and release for lines of gpio, read/write + * the value of gpio. If the user want to do lots of read and write of + * lines of gpio, user should use this type of api. + * + * The second one provide the easy to use api for user. Each of the + * following api will request gpio lines, do the operation and then + * release these lines. + */ +/** + * gpiotools_request_linehandle() - request gpio lines in a gpiochip + * @device_name: The name of gpiochip without prefix "/dev/", + * such as "gpiochip0" + * @lines: An array desired lines, specified by offset + * index for the associated GPIO device. + * @num_lines: The number of lines to request. + * @flag: The new flag for requsted gpio. Reference + * "linux/gpio.h" for the meaning of flag. + * @data: Default value will be set to gpio when flag is + * GPIOHANDLE_REQUEST_OUTPUT. + * @consumer_label: The name of consumer, such as "sysfs", + * "powerkey". This is useful for other users to + * know who is using. + * + * Request gpio lines through the ioctl provided by chardev. User + * could call gpiotools_set_values() and gpiotools_get_values() to + * read and write respectively through the returned fd. Call + * gpiotools_release_linehandle() to release these lines after that. + * + * Return: On success return the fd; + * On failure return the errno. + */ +int gpiotools_request_linehandle(const char *device_name, unsigned int *lines, + unsigned int num_lines, unsigned int flag, + struct gpiohandle_data *data, + const char *consumer_label) +{ + struct gpiohandle_request req; + char *chrdev_name; + int fd; + int i; + int ret; + + ret = asprintf(&chrdev_name, "/dev/%s", device_name); + if (ret < 0) + return -ENOMEM; + + fd = open(chrdev_name, 0); + if (fd == -1) { + ret = -errno; + fprintf(stderr, "Failed to open %s, %s\n", + chrdev_name, strerror(errno)); + goto exit_free_name; + } + + for (i = 0; i < num_lines; i++) + req.lineoffsets[i] = lines[i]; + + req.flags = flag; + strcpy(req.consumer_label, consumer_label); + req.lines = num_lines; + if (flag & GPIOHANDLE_REQUEST_OUTPUT) + memcpy(req.default_values, data, sizeof(req.default_values)); + + ret = ioctl(fd, GPIO_GET_LINEHANDLE_IOCTL, &req); + if (ret == -1) { + ret = -errno; + fprintf(stderr, "Failed to issue %s (%d), %s\n", + "GPIO_GET_LINEHANDLE_IOCTL", ret, strerror(errno)); + } + + if (close(fd) == -1) + perror("Failed to close GPIO character device file"); +exit_free_name: + free(chrdev_name); + return ret < 0 ? ret : req.fd; +} + +/** + * gpiotools_request_line() - request gpio lines in a gpiochip + * @device_name: The name of gpiochip without prefix "/dev/", + * such as "gpiochip0" + * @lines: An array desired lines, specified by offset + * index for the associated GPIO device. + * @num_lines: The number of lines to request. + * @config: The new config for requested gpio. Reference + * "linux/gpio.h" for config details. + * @consumer: The name of consumer, such as "sysfs", + * "powerkey". This is useful for other users to + * know who is using. + * + * Request gpio lines through the ioctl provided by chardev. User + * could call gpiotools_set_values() and gpiotools_get_values() to + * read and write respectively through the returned fd. Call + * gpiotools_release_line() to release these lines after that. + * + * Return: On success return the fd; + * On failure return the errno. + */ +int gpiotools_request_line(const char *device_name, unsigned int *lines, + unsigned int num_lines, + struct gpio_v2_line_config *config, + const char *consumer) +{ + struct gpio_v2_line_request req; + char *chrdev_name; + int fd; + int i; + int ret; + + ret = asprintf(&chrdev_name, "/dev/%s", device_name); + if (ret < 0) + return -ENOMEM; + + fd = open(chrdev_name, 0); + if (fd == -1) { + ret = -errno; + fprintf(stderr, "Failed to open %s, %s\n", + chrdev_name, strerror(errno)); + goto exit_free_name; + } + + memset(&req, 0, sizeof(req)); + for (i = 0; i < num_lines; i++) + req.offsets[i] = lines[i]; + + req.config = *config; + strcpy(req.consumer, consumer); + req.num_lines = num_lines; + + ret = ioctl(fd, GPIO_V2_GET_LINE_IOCTL, &req); + if (ret == -1) { + ret = -errno; + fprintf(stderr, "Failed to issue %s (%d), %s\n", + "GPIO_GET_LINE_IOCTL", ret, strerror(errno)); + } + + if (close(fd) == -1) + perror("Failed to close GPIO character device file"); +exit_free_name: + free(chrdev_name); + return ret < 0 ? ret : req.fd; +} + +/** + * gpiotools_set_values(): Set the value of gpio(s) + * @fd: The fd returned by + * gpiotools_request_line(). + * @values: The array of values want to set. + * + * Return: On success return 0; + * On failure return the errno. + */ +int gpiotools_set_values(const int fd, struct gpio_v2_line_values *values) +{ + int ret; + + ret = ioctl(fd, GPIO_V2_LINE_SET_VALUES_IOCTL, values); + if (ret == -1) { + ret = -errno; + fprintf(stderr, "Failed to issue %s (%d), %s\n", + "GPIOHANDLE_SET_LINE_VALUES_IOCTL", ret, + strerror(errno)); + } + + return ret; +} + +/** + * gpiotools_get_values(): Get the value of gpio(s) + * @fd: The fd returned by + * gpiotools_request_line(). + * @values: The array of values get from hardware. + * + * Return: On success return 0; + * On failure return the errno. + */ +int gpiotools_get_values(const int fd, struct gpio_v2_line_values *values) +{ + int ret; + + ret = ioctl(fd, GPIO_V2_LINE_GET_VALUES_IOCTL, values); + if (ret == -1) { + ret = -errno; + fprintf(stderr, "Failed to issue %s (%d), %s\n", + "GPIOHANDLE_GET_LINE_VALUES_IOCTL", ret, + strerror(errno)); + } + + return ret; +} + +/** + * gpiotools_release_linehandle(): Release the line(s) of gpiochip + * @fd: The fd returned by + * gpiotools_request_linehandle(). + * + * Return: On success return 0; + * On failure return the errno. + */ +int gpiotools_release_linehandle(const int fd) +{ + int ret; + + ret = close(fd); + if (ret == -1) { + perror("Failed to close GPIO LINEHANDLE device file"); + ret = -errno; + } + + return ret; +} + +/** + * gpiotools_release_line(): Release the line(s) of gpiochip + * @fd: The fd returned by + * gpiotools_request_line(). + * + * Return: On success return 0; + * On failure return the errno. + */ +int gpiotools_release_line(const int fd) +{ + int ret; + + ret = close(fd); + if (ret == -1) { + perror("Failed to close GPIO LINE device file"); + ret = -errno; + } + + return ret; +} + +/** + * gpiotools_get(): Get value from specific line + * @device_name: The name of gpiochip without prefix "/dev/", + * such as "gpiochip0" + * @line: number of line, such as 2. + * + * Return: On success return 0; + * On failure return the errno. + */ +int gpiotools_get(const char *device_name, unsigned int line) +{ + int ret; + unsigned int value; + unsigned int lines[] = {line}; + + ret = gpiotools_gets(device_name, lines, 1, &value); + if (ret) + return ret; + return value; +} + + +/** + * gpiotools_gets(): Get values from specific lines. + * @device_name: The name of gpiochip without prefix "/dev/", + * such as "gpiochip0". + * @lines: An array desired lines, specified by offset + * index for the associated GPIO device. + * @num_lines: The number of lines to request. + * @values: The array of values get from gpiochip. + * + * Return: On success return 0; + * On failure return the errno. + */ +int gpiotools_gets(const char *device_name, unsigned int *lines, + unsigned int num_lines, unsigned int *values) +{ + int fd, i; + int ret; + int ret_close; + struct gpio_v2_line_config config; + struct gpio_v2_line_values lv; + + memset(&config, 0, sizeof(config)); + config.flags = GPIO_V2_LINE_FLAG_INPUT; + ret = gpiotools_request_line(device_name, lines, num_lines, + &config, CONSUMER); + if (ret < 0) + return ret; + + fd = ret; + for (i = 0; i < num_lines; i++) + gpiotools_set_bit(&lv.mask, i); + ret = gpiotools_get_values(fd, &lv); + if (!ret) + for (i = 0; i < num_lines; i++) + values[i] = gpiotools_test_bit(lv.bits, i); + ret_close = gpiotools_release_line(fd); + return ret < 0 ? ret : ret_close; +} + +/** + * gpiotools_set(): Set value to specific line + * @device_name: The name of gpiochip without prefix "/dev/", + * such as "gpiochip0" + * @line: number of line, such as 2. + * @value: The value of gpio, must be 0(low) or 1(high). + * + * Return: On success return 0; + * On failure return the errno. + */ +int gpiotools_set(const char *device_name, unsigned int line, + unsigned int value) +{ + unsigned int lines[] = {line}; + + return gpiotools_sets(device_name, lines, 1, &value); +} + +/** + * gpiotools_sets(): Set values to specific lines. + * @device_name: The name of gpiochip without prefix "/dev/", + * such as "gpiochip0". + * @lines: An array desired lines, specified by offset + * index for the associated GPIO device. + * @num_lines: The number of lines to request. + * @value: The array of values set to gpiochip, must be + * 0(low) or 1(high). + * + * Return: On success return 0; + * On failure return the errno. + */ +int gpiotools_sets(const char *device_name, unsigned int *lines, + unsigned int num_lines, unsigned int *values) +{ + int ret, i; + struct gpio_v2_line_config config; + + memset(&config, 0, sizeof(config)); + config.flags = GPIO_V2_LINE_FLAG_OUTPUT; + config.num_attrs = 1; + config.attrs[0].attr.id = GPIO_V2_LINE_ATTR_ID_OUTPUT_VALUES; + for (i = 0; i < num_lines; i++) { + gpiotools_set_bit(&config.attrs[0].mask, i); + gpiotools_assign_bit(&config.attrs[0].attr.values, + i, values[i]); + } + ret = gpiotools_request_line(device_name, lines, num_lines, + &config, CONSUMER); + if (ret < 0) + return ret; + + return gpiotools_release_line(ret); +} diff --git a/tools/gpio/gpio-utils.h b/tools/gpio/gpio-utils.h new file mode 100644 index 000000000..6c69a9f1c --- /dev/null +++ b/tools/gpio/gpio-utils.h @@ -0,0 +1,79 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * GPIO tools - utility helpers library for the GPIO tools + * + * Copyright (C) 2015 Linus Walleij + * + * Portions copied from iio_utils and lssio: + * Copyright (c) 2010 Manuel Stahl <manuel.stahl@iis.fraunhofer.de> + * Copyright (c) 2008 Jonathan Cameron + * * + */ +#ifndef _GPIO_UTILS_H_ +#define _GPIO_UTILS_H_ + +#include <stdbool.h> +#include <string.h> +#include <linux/types.h> + +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) + +static inline int check_prefix(const char *str, const char *prefix) +{ + return strlen(str) > strlen(prefix) && + strncmp(str, prefix, strlen(prefix)) == 0; +} + +int gpiotools_request_linehandle(const char *device_name, unsigned int *lines, + unsigned int num_lines, unsigned int flag, + struct gpiohandle_data *data, + const char *consumer_label); +int gpiotools_release_linehandle(const int fd); + +int gpiotools_request_line(const char *device_name, + unsigned int *lines, + unsigned int num_lines, + struct gpio_v2_line_config *config, + const char *consumer); +int gpiotools_set_values(const int fd, struct gpio_v2_line_values *values); +int gpiotools_get_values(const int fd, struct gpio_v2_line_values *values); +int gpiotools_release_line(const int fd); + +int gpiotools_get(const char *device_name, unsigned int line); +int gpiotools_gets(const char *device_name, unsigned int *lines, + unsigned int num_lines, unsigned int *values); +int gpiotools_set(const char *device_name, unsigned int line, + unsigned int value); +int gpiotools_sets(const char *device_name, unsigned int *lines, + unsigned int num_lines, unsigned int *values); + +/* helper functions for gpio_v2_line_values bits */ +static inline void gpiotools_set_bit(__u64 *b, int n) +{ + *b |= _BITULL(n); +} + +static inline void gpiotools_change_bit(__u64 *b, int n) +{ + *b ^= _BITULL(n); +} + +static inline void gpiotools_clear_bit(__u64 *b, int n) +{ + *b &= ~_BITULL(n); +} + +static inline int gpiotools_test_bit(__u64 b, int n) +{ + return !!(b & _BITULL(n)); +} + +static inline void gpiotools_assign_bit(__u64 *b, int n, bool value) +{ + if (value) + gpiotools_set_bit(b, n); + else + gpiotools_clear_bit(b, n); +} + +#endif /* _GPIO_UTILS_H_ */ diff --git a/tools/gpio/gpio-watch.c b/tools/gpio/gpio-watch.c new file mode 100644 index 000000000..41e76d244 --- /dev/null +++ b/tools/gpio/gpio-watch.c @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * gpio-watch - monitor unrequested lines for property changes using the + * character device + * + * Copyright (C) 2019 BayLibre SAS + * Author: Bartosz Golaszewski <bgolaszewski@baylibre.com> + */ + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <linux/gpio.h> +#include <poll.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <unistd.h> + +int main(int argc, char **argv) +{ + struct gpio_v2_line_info_changed chg; + struct gpio_v2_line_info req; + struct pollfd pfd; + int fd, i, j, ret; + char *event, *end; + ssize_t rd; + + if (argc < 3) + goto err_usage; + + fd = open(argv[1], O_RDWR | O_CLOEXEC); + if (fd < 0) { + perror("unable to open gpiochip"); + return EXIT_FAILURE; + } + + for (i = 0, j = 2; i < argc - 2; i++, j++) { + memset(&req, 0, sizeof(req)); + + req.offset = strtoul(argv[j], &end, 0); + if (*end != '\0') + goto err_usage; + + ret = ioctl(fd, GPIO_V2_GET_LINEINFO_WATCH_IOCTL, &req); + if (ret) { + perror("unable to set up line watch"); + return EXIT_FAILURE; + } + } + + pfd.fd = fd; + pfd.events = POLLIN | POLLPRI; + + for (;;) { + ret = poll(&pfd, 1, 5000); + if (ret < 0) { + perror("error polling the linechanged fd"); + return EXIT_FAILURE; + } else if (ret > 0) { + memset(&chg, 0, sizeof(chg)); + rd = read(pfd.fd, &chg, sizeof(chg)); + if (rd < 0 || rd != sizeof(chg)) { + if (rd != sizeof(chg)) + errno = EIO; + + perror("error reading line change event"); + return EXIT_FAILURE; + } + + switch (chg.event_type) { + case GPIO_V2_LINE_CHANGED_REQUESTED: + event = "requested"; + break; + case GPIO_V2_LINE_CHANGED_RELEASED: + event = "released"; + break; + case GPIO_V2_LINE_CHANGED_CONFIG: + event = "config changed"; + break; + default: + fprintf(stderr, + "invalid event type received from the kernel\n"); + return EXIT_FAILURE; + } + + printf("line %u: %s at %" PRIu64 "\n", + chg.info.offset, event, (uint64_t)chg.timestamp_ns); + } + } + + return 0; + +err_usage: + printf("%s: <gpiochip> <line0> <line1> ...\n", argv[0]); + return EXIT_FAILURE; +} diff --git a/tools/gpio/lsgpio.c b/tools/gpio/lsgpio.c new file mode 100644 index 000000000..85a2aa292 --- /dev/null +++ b/tools/gpio/lsgpio.c @@ -0,0 +1,221 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * lsgpio - example on how to list the GPIO lines on a system + * + * Copyright (C) 2015 Linus Walleij + * + * Usage: + * lsgpio <-n device-name> + */ + +#include <unistd.h> +#include <stdlib.h> +#include <stdbool.h> +#include <stdio.h> +#include <dirent.h> +#include <errno.h> +#include <string.h> +#include <poll.h> +#include <fcntl.h> +#include <getopt.h> +#include <sys/ioctl.h> +#include <linux/gpio.h> + +#include "gpio-utils.h" + +struct gpio_flag { + char *name; + unsigned long long mask; +}; + +struct gpio_flag flagnames[] = { + { + .name = "used", + .mask = GPIO_V2_LINE_FLAG_USED, + }, + { + .name = "input", + .mask = GPIO_V2_LINE_FLAG_INPUT, + }, + { + .name = "output", + .mask = GPIO_V2_LINE_FLAG_OUTPUT, + }, + { + .name = "active-low", + .mask = GPIO_V2_LINE_FLAG_ACTIVE_LOW, + }, + { + .name = "open-drain", + .mask = GPIO_V2_LINE_FLAG_OPEN_DRAIN, + }, + { + .name = "open-source", + .mask = GPIO_V2_LINE_FLAG_OPEN_SOURCE, + }, + { + .name = "pull-up", + .mask = GPIO_V2_LINE_FLAG_BIAS_PULL_UP, + }, + { + .name = "pull-down", + .mask = GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN, + }, + { + .name = "bias-disabled", + .mask = GPIO_V2_LINE_FLAG_BIAS_DISABLED, + }, +}; + +static void print_attributes(struct gpio_v2_line_info *info) +{ + int i; + const char *field_format = "%s"; + + for (i = 0; i < ARRAY_SIZE(flagnames); i++) { + if (info->flags & flagnames[i].mask) { + fprintf(stdout, field_format, flagnames[i].name); + field_format = ", %s"; + } + } + + if ((info->flags & GPIO_V2_LINE_FLAG_EDGE_RISING) && + (info->flags & GPIO_V2_LINE_FLAG_EDGE_FALLING)) + fprintf(stdout, field_format, "both-edges"); + else if (info->flags & GPIO_V2_LINE_FLAG_EDGE_RISING) + fprintf(stdout, field_format, "rising-edge"); + else if (info->flags & GPIO_V2_LINE_FLAG_EDGE_FALLING) + fprintf(stdout, field_format, "falling-edge"); + + for (i = 0; i < info->num_attrs; i++) { + if (info->attrs[i].id == GPIO_V2_LINE_ATTR_ID_DEBOUNCE) + fprintf(stdout, ", debounce_period=%dusec", + info->attrs[i].debounce_period_us); + } +} + +int list_device(const char *device_name) +{ + struct gpiochip_info cinfo; + char *chrdev_name; + int fd; + int ret; + int i; + + ret = asprintf(&chrdev_name, "/dev/%s", device_name); + if (ret < 0) + return -ENOMEM; + + fd = open(chrdev_name, 0); + if (fd == -1) { + ret = -errno; + fprintf(stderr, "Failed to open %s\n", chrdev_name); + goto exit_free_name; + } + + /* Inspect this GPIO chip */ + ret = ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &cinfo); + if (ret == -1) { + ret = -errno; + perror("Failed to issue CHIPINFO IOCTL\n"); + goto exit_close_error; + } + fprintf(stdout, "GPIO chip: %s, \"%s\", %u GPIO lines\n", + cinfo.name, cinfo.label, cinfo.lines); + + /* Loop over the lines and print info */ + for (i = 0; i < cinfo.lines; i++) { + struct gpio_v2_line_info linfo; + + memset(&linfo, 0, sizeof(linfo)); + linfo.offset = i; + + ret = ioctl(fd, GPIO_V2_GET_LINEINFO_IOCTL, &linfo); + if (ret == -1) { + ret = -errno; + perror("Failed to issue LINEINFO IOCTL\n"); + goto exit_close_error; + } + fprintf(stdout, "\tline %2d:", linfo.offset); + if (linfo.name[0]) + fprintf(stdout, " \"%s\"", linfo.name); + else + fprintf(stdout, " unnamed"); + if (linfo.consumer[0]) + fprintf(stdout, " \"%s\"", linfo.consumer); + else + fprintf(stdout, " unused"); + if (linfo.flags) { + fprintf(stdout, " ["); + print_attributes(&linfo); + fprintf(stdout, "]"); + } + fprintf(stdout, "\n"); + + } + +exit_close_error: + if (close(fd) == -1) + perror("Failed to close GPIO character device file"); +exit_free_name: + free(chrdev_name); + return ret; +} + +void print_usage(void) +{ + fprintf(stderr, "Usage: lsgpio [options]...\n" + "List GPIO chips, lines and states\n" + " -n <name> List GPIOs on a named device\n" + " -? This helptext\n" + ); +} + +int main(int argc, char **argv) +{ + const char *device_name = NULL; + int ret; + int c; + + while ((c = getopt(argc, argv, "n:")) != -1) { + switch (c) { + case 'n': + device_name = optarg; + break; + case '?': + print_usage(); + return -1; + } + } + + if (device_name) + ret = list_device(device_name); + else { + const struct dirent *ent; + DIR *dp; + + /* List all GPIO devices one at a time */ + dp = opendir("/dev"); + if (!dp) { + ret = -errno; + goto error_out; + } + + ret = -ENOENT; + while (ent = readdir(dp), ent) { + if (check_prefix(ent->d_name, "gpiochip")) { + ret = list_device(ent->d_name); + if (ret) + break; + } + } + + ret = 0; + if (closedir(dp) == -1) { + perror("scanning devices: Failed to close directory"); + ret = -errno; + } + } +error_out: + return ret; +} |