diff options
Diffstat (limited to 'tools/testing/selftests/gpio')
-rw-r--r-- | tools/testing/selftests/gpio/.gitignore | 4 | ||||
-rw-r--r-- | tools/testing/selftests/gpio/Makefile | 8 | ||||
-rw-r--r-- | tools/testing/selftests/gpio/config | 4 | ||||
-rw-r--r-- | tools/testing/selftests/gpio/gpio-chip-info.c | 57 | ||||
-rw-r--r-- | tools/testing/selftests/gpio/gpio-line-name.c | 55 | ||||
-rw-r--r-- | tools/testing/selftests/gpio/gpio-mockup-cdev.c | 198 | ||||
-rwxr-xr-x | tools/testing/selftests/gpio/gpio-mockup-sysfs.sh | 77 | ||||
-rwxr-xr-x | tools/testing/selftests/gpio/gpio-mockup.sh | 403 | ||||
-rwxr-xr-x | tools/testing/selftests/gpio/gpio-sim.sh | 399 |
9 files changed, 1205 insertions, 0 deletions
diff --git a/tools/testing/selftests/gpio/.gitignore b/tools/testing/selftests/gpio/.gitignore new file mode 100644 index 000000000..ededb077a --- /dev/null +++ b/tools/testing/selftests/gpio/.gitignore @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +gpio-mockup-cdev +gpio-chip-info +gpio-line-name diff --git a/tools/testing/selftests/gpio/Makefile b/tools/testing/selftests/gpio/Makefile new file mode 100644 index 000000000..e08843904 --- /dev/null +++ b/tools/testing/selftests/gpio/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 + +TEST_PROGS := gpio-mockup.sh gpio-sim.sh +TEST_FILES := gpio-mockup-sysfs.sh +TEST_GEN_PROGS_EXTENDED := gpio-mockup-cdev gpio-chip-info gpio-line-name +CFLAGS += -O2 -g -Wall $(KHDR_INCLUDES) + +include ../lib.mk diff --git a/tools/testing/selftests/gpio/config b/tools/testing/selftests/gpio/config new file mode 100644 index 000000000..409a8532f --- /dev/null +++ b/tools/testing/selftests/gpio/config @@ -0,0 +1,4 @@ +CONFIG_GPIOLIB=y +CONFIG_GPIO_CDEV=y +CONFIG_GPIO_MOCKUP=m +CONFIG_GPIO_SIM=m diff --git a/tools/testing/selftests/gpio/gpio-chip-info.c b/tools/testing/selftests/gpio/gpio-chip-info.c new file mode 100644 index 000000000..fdc07e742 --- /dev/null +++ b/tools/testing/selftests/gpio/gpio-chip-info.c @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * GPIO character device helper for reading chip information. + * + * Copyright (C) 2021 Bartosz Golaszewski <brgl@bgdev.pl> + */ + +#include <fcntl.h> +#include <linux/gpio.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/types.h> + +static void print_usage(void) +{ + printf("usage:\n"); + printf(" gpio-chip-info <chip path> [name|label|num-lines]\n"); +} + +int main(int argc, char **argv) +{ + struct gpiochip_info info; + int fd, ret; + + if (argc != 3) { + print_usage(); + return EXIT_FAILURE; + } + + fd = open(argv[1], O_RDWR); + if (fd < 0) { + perror("unable to open the GPIO chip"); + return EXIT_FAILURE; + } + + memset(&info, 0, sizeof(info)); + ret = ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &info); + if (ret) { + perror("chip info ioctl failed"); + return EXIT_FAILURE; + } + + if (strcmp(argv[2], "name") == 0) { + printf("%s\n", info.name); + } else if (strcmp(argv[2], "label") == 0) { + printf("%s\n", info.label); + } else if (strcmp(argv[2], "num-lines") == 0) { + printf("%u\n", info.lines); + } else { + fprintf(stderr, "unknown command: %s\n", argv[2]); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/tools/testing/selftests/gpio/gpio-line-name.c b/tools/testing/selftests/gpio/gpio-line-name.c new file mode 100644 index 000000000..e635cfadb --- /dev/null +++ b/tools/testing/selftests/gpio/gpio-line-name.c @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * GPIO character device helper for reading line names. + * + * Copyright (C) 2021 Bartosz Golaszewski <brgl@bgdev.pl> + */ + +#include <fcntl.h> +#include <linux/gpio.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/types.h> + +static void print_usage(void) +{ + printf("usage:\n"); + printf(" gpio-line-name <chip path> <line offset>\n"); +} + +int main(int argc, char **argv) +{ + struct gpio_v2_line_info info; + int fd, ret; + char *endp; + + if (argc != 3) { + print_usage(); + return EXIT_FAILURE; + } + + fd = open(argv[1], O_RDWR); + if (fd < 0) { + perror("unable to open the GPIO chip"); + return EXIT_FAILURE; + } + + memset(&info, 0, sizeof(info)); + info.offset = strtoul(argv[2], &endp, 10); + if (*endp != '\0') { + print_usage(); + return EXIT_FAILURE; + } + + ret = ioctl(fd, GPIO_V2_GET_LINEINFO_IOCTL, &info); + if (ret) { + perror("line info ioctl failed"); + return EXIT_FAILURE; + } + + printf("%s\n", info.name); + + return EXIT_SUCCESS; +} diff --git a/tools/testing/selftests/gpio/gpio-mockup-cdev.c b/tools/testing/selftests/gpio/gpio-mockup-cdev.c new file mode 100644 index 000000000..d1640f44f --- /dev/null +++ b/tools/testing/selftests/gpio/gpio-mockup-cdev.c @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * GPIO mockup cdev test helper + * + * Copyright (C) 2020 Kent Gibson + */ + +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include <linux/gpio.h> + +#define CONSUMER "gpio-mockup-cdev" + +static int request_line_v2(int cfd, unsigned int offset, + uint64_t flags, unsigned int val) +{ + struct gpio_v2_line_request req; + int ret; + + memset(&req, 0, sizeof(req)); + req.num_lines = 1; + req.offsets[0] = offset; + req.config.flags = flags; + strcpy(req.consumer, CONSUMER); + if (flags & GPIO_V2_LINE_FLAG_OUTPUT) { + req.config.num_attrs = 1; + req.config.attrs[0].mask = 1; + req.config.attrs[0].attr.id = GPIO_V2_LINE_ATTR_ID_OUTPUT_VALUES; + if (val) + req.config.attrs[0].attr.values = 1; + } + ret = ioctl(cfd, GPIO_V2_GET_LINE_IOCTL, &req); + if (ret == -1) + return -errno; + return req.fd; +} + + +static int get_value_v2(int lfd) +{ + struct gpio_v2_line_values vals; + int ret; + + memset(&vals, 0, sizeof(vals)); + vals.mask = 1; + ret = ioctl(lfd, GPIO_V2_LINE_GET_VALUES_IOCTL, &vals); + if (ret == -1) + return -errno; + return vals.bits & 0x1; +} + +static int request_line_v1(int cfd, unsigned int offset, + uint32_t flags, unsigned int val) +{ + struct gpiohandle_request req; + int ret; + + memset(&req, 0, sizeof(req)); + req.lines = 1; + req.lineoffsets[0] = offset; + req.flags = flags; + strcpy(req.consumer_label, CONSUMER); + if (flags & GPIOHANDLE_REQUEST_OUTPUT) + req.default_values[0] = val; + + ret = ioctl(cfd, GPIO_GET_LINEHANDLE_IOCTL, &req); + if (ret == -1) + return -errno; + return req.fd; +} + +static int get_value_v1(int lfd) +{ + struct gpiohandle_data vals; + int ret; + + memset(&vals, 0, sizeof(vals)); + ret = ioctl(lfd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &vals); + if (ret == -1) + return -errno; + return vals.values[0]; +} + +static void usage(char *prog) +{ + printf("Usage: %s [-l] [-b <bias>] [-s <value>] [-u <uAPI>] <gpiochip> <offset>\n", prog); + printf(" -b: set line bias to one of pull-down, pull-up, disabled\n"); + printf(" (default is to leave bias unchanged):\n"); + printf(" -l: set line active low (default is active high)\n"); + printf(" -s: set line value (default is to get line value)\n"); + printf(" -u: uAPI version to use (default is 2)\n"); + exit(-1); +} + +static int wait_signal(void) +{ + int sig; + sigset_t wset; + + sigemptyset(&wset); + sigaddset(&wset, SIGHUP); + sigaddset(&wset, SIGINT); + sigaddset(&wset, SIGTERM); + sigwait(&wset, &sig); + + return sig; +} + +int main(int argc, char *argv[]) +{ + char *chip; + int opt, ret, cfd, lfd; + unsigned int offset, val = 0, abiv; + uint32_t flags_v1; + uint64_t flags_v2; + + abiv = 2; + ret = 0; + flags_v1 = GPIOHANDLE_REQUEST_INPUT; + flags_v2 = GPIO_V2_LINE_FLAG_INPUT; + + while ((opt = getopt(argc, argv, "lb:s:u:")) != -1) { + switch (opt) { + case 'l': + flags_v1 |= GPIOHANDLE_REQUEST_ACTIVE_LOW; + flags_v2 |= GPIO_V2_LINE_FLAG_ACTIVE_LOW; + break; + case 'b': + if (strcmp("pull-up", optarg) == 0) { + flags_v1 |= GPIOHANDLE_REQUEST_BIAS_PULL_UP; + flags_v2 |= GPIO_V2_LINE_FLAG_BIAS_PULL_UP; + } else if (strcmp("pull-down", optarg) == 0) { + flags_v1 |= GPIOHANDLE_REQUEST_BIAS_PULL_DOWN; + flags_v2 |= GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN; + } else if (strcmp("disabled", optarg) == 0) { + flags_v1 |= GPIOHANDLE_REQUEST_BIAS_DISABLE; + flags_v2 |= GPIO_V2_LINE_FLAG_BIAS_DISABLED; + } + break; + case 's': + val = atoi(optarg); + flags_v1 &= ~GPIOHANDLE_REQUEST_INPUT; + flags_v1 |= GPIOHANDLE_REQUEST_OUTPUT; + flags_v2 &= ~GPIO_V2_LINE_FLAG_INPUT; + flags_v2 |= GPIO_V2_LINE_FLAG_OUTPUT; + break; + case 'u': + abiv = atoi(optarg); + break; + default: + usage(argv[0]); + } + } + + if (argc < optind + 2) + usage(argv[0]); + + chip = argv[optind]; + offset = atoi(argv[optind + 1]); + + cfd = open(chip, 0); + if (cfd == -1) { + fprintf(stderr, "Failed to open %s: %s\n", chip, strerror(errno)); + return -errno; + } + + if (abiv == 1) + lfd = request_line_v1(cfd, offset, flags_v1, val); + else + lfd = request_line_v2(cfd, offset, flags_v2, val); + + close(cfd); + + if (lfd < 0) { + fprintf(stderr, "Failed to request %s:%d: %s\n", chip, offset, strerror(-lfd)); + return lfd; + } + + if (flags_v2 & GPIO_V2_LINE_FLAG_OUTPUT) { + wait_signal(); + } else { + if (abiv == 1) + ret = get_value_v1(lfd); + else + ret = get_value_v2(lfd); + } + + close(lfd); + + return ret; +} diff --git a/tools/testing/selftests/gpio/gpio-mockup-sysfs.sh b/tools/testing/selftests/gpio/gpio-mockup-sysfs.sh new file mode 100755 index 000000000..2d2e5d876 --- /dev/null +++ b/tools/testing/selftests/gpio/gpio-mockup-sysfs.sh @@ -0,0 +1,77 @@ + +# SPDX-License-Identifier: GPL-2.0 + +# Overrides functions in gpio-mockup.sh to test using the GPIO SYSFS uAPI + +SYSFS=`grep -w sysfs /proc/mounts | cut -f2 -d' '` +[ -d "$SYSFS" ] || skip "sysfs is not mounted" + +GPIO_SYSFS="${SYSFS}/class/gpio" +[ -d "$GPIO_SYSFS" ] || skip "CONFIG_GPIO_SYSFS is not selected" + +PLATFORM_SYSFS=$SYSFS/devices/platform + +sysfs_nr= +sysfs_ldir= + +# determine the sysfs GPIO number given the $chip and $offset +# e.g. gpiochip1:32 +find_sysfs_nr() +{ + # e.g. /sys/devices/platform/gpio-mockup.1/gpiochip1 + local platform=$(find $PLATFORM_SYSFS -mindepth 2 -maxdepth 2 -type d -name $chip) + [ "$platform" ] || fail "can't find platform of $chip" + # e.g. /sys/devices/platform/gpio-mockup.1/gpio/gpiochip508/base + local base=$(find ${platform%/*}/gpio/ -mindepth 2 -maxdepth 2 -type f -name base) + [ "$base" ] || fail "can't find base of $chip" + sysfs_nr=$(($(< "$base") + $offset)) + sysfs_ldir="$GPIO_SYSFS/gpio$sysfs_nr" +} + +acquire_line() +{ + [ "$sysfs_nr" ] && return + find_sysfs_nr + echo "$sysfs_nr" > "$GPIO_SYSFS/export" +} + +# The helpers being overridden... +get_line() +{ + [ -e "$sysfs_ldir/value" ] && echo $(< "$sysfs_ldir/value") +} + +set_line() +{ + acquire_line + + for option in $*; do + case $option in + active-high) + echo 0 > "$sysfs_ldir/active_low" + ;; + active-low) + echo 1 > "$sysfs_ldir/active_low" + ;; + input) + echo "in" > "$sysfs_ldir/direction" + ;; + 0) + echo "out" > "$sysfs_ldir/direction" + echo 0 > "$sysfs_ldir/value" + ;; + 1) + echo "out" > "$sysfs_ldir/direction" + echo 1 > "$sysfs_ldir/value" + ;; + esac + done +} + +release_line() +{ + [ "$sysfs_nr" ] || return 0 + echo "$sysfs_nr" > "$GPIO_SYSFS/unexport" + sysfs_nr= + sysfs_ldir= +} diff --git a/tools/testing/selftests/gpio/gpio-mockup.sh b/tools/testing/selftests/gpio/gpio-mockup.sh new file mode 100755 index 000000000..0d6c5f7f9 --- /dev/null +++ b/tools/testing/selftests/gpio/gpio-mockup.sh @@ -0,0 +1,403 @@ +#!/bin/bash -efu +# SPDX-License-Identifier: GPL-2.0 + +#exit status +#0: success +#1: fail +#4: skip test - including run as non-root user + +BASE=${0%/*} +DEBUGFS= +GPIO_DEBUGFS= +dev_type="cdev" +module="gpio-mockup" +verbose= +full_test= +random= +uapi_opt= +active_opt= +bias_opt= +line_set_pid= + +# Kselftest return codes +ksft_fail=1 +ksft_skip=4 + +usage() +{ + echo "Usage:" + echo "$0 [-frv] [-t type]" + echo "-f: full test (minimal set run by default)" + echo "-r: test random lines as well as fence posts" + echo "-t: interface type:" + echo " cdev (character device ABI) - default" + echo " cdev_v1 (deprecated character device ABI)" + echo " sysfs (deprecated SYSFS ABI)" + echo "-v: verbose progress reporting" + exit $ksft_fail +} + +skip() +{ + echo "$*" >&2 + echo "GPIO $module test SKIP" + exit $ksft_skip +} + +prerequisite() +{ + [ $(id -u) -eq 0 ] || skip "must be run as root" + + DEBUGFS=$(grep -w debugfs /proc/mounts | cut -f2 -d' ') + [ -d "$DEBUGFS" ] || skip "debugfs is not mounted" + + GPIO_DEBUGFS=$DEBUGFS/$module +} + +remove_module() +{ + modprobe -r -q $module +} + +cleanup() +{ + set +e + release_line + remove_module + jobs -p | xargs -r kill > /dev/null 2>&1 +} + +fail() +{ + echo "test failed: $*" >&2 + echo "GPIO $module test FAIL" + exit $ksft_fail +} + +try_insert_module() +{ + modprobe -q $module "$1" || fail "insert $module failed with error $?" +} + +log() +{ + [ -z "$verbose" ] || echo "$*" +} + +# The following line helpers, release_Line, get_line and set_line, all +# make use of the global $chip and $offset variables. +# +# This implementation drives the GPIO character device (cdev) uAPI. +# Other implementations may override these to test different uAPIs. + +# Release any resources related to the line +release_line() +{ + [ "$line_set_pid" ] && kill $line_set_pid && wait $line_set_pid || true + line_set_pid= +} + +# Read the current value of the line +get_line() +{ + release_line + + local cdev_opts=${uapi_opt}${active_opt} + $BASE/gpio-mockup-cdev $cdev_opts /dev/$chip $offset + echo $? +} + +# Set the state of the line +# +# Changes to line configuration are provided as parameters. +# The line is assumed to be an output if the line value 0 or 1 is +# specified, else an input. +set_line() +{ + local val= + + release_line + + # parse config options... + for option in $*; do + case $option in + active-low) + active_opt="-l " + ;; + active-high) + active_opt= + ;; + bias-none) + bias_opt= + ;; + pull-down) + bias_opt="-bpull-down " + ;; + pull-up) + bias_opt="-bpull-up " + ;; + 0) + val=0 + ;; + 1) + val=1 + ;; + esac + done + + local cdev_opts=${uapi_opt}${active_opt} + if [ "$val" ]; then + $BASE/gpio-mockup-cdev $cdev_opts -s$val /dev/$chip $offset & + # failure to set is detected by reading mockup and toggling values + line_set_pid=$! + # allow for gpio-mockup-cdev to launch and request line + # (there is limited value in checking if line has been requested) + sleep 0.01 + elif [ "$bias_opt" ]; then + cdev_opts=${cdev_opts}${bias_opt} + $BASE/gpio-mockup-cdev $cdev_opts /dev/$chip $offset || true + fi +} + +assert_line() +{ + local val + # don't need any retry here as set_mock allows for propagation + val=$(get_line) + [ "$val" = "$1" ] || fail "line value is ${val:-empty} when $1 was expected" +} + +# The following mockup helpers all make use of the $mock_line +assert_mock() +{ + local backoff_wait=10 + local retry=0 + local val + # retry allows for set propagation from uAPI to mockup + while true; do + val=$(< $mock_line) + [ "$val" = "$1" ] && break + retry=$((retry + 1)) + [ $retry -lt 5 ] || fail "mockup $mock_line value ${val:-empty} when $1 expected" + sleep $(printf "%0.2f" $((backoff_wait))e-3) + backoff_wait=$((backoff_wait * 2)) + done +} + +set_mock() +{ + echo "$1" > $mock_line + # allow for set propagation - so we won't be in a race with set_line + assert_mock "$1" +} + +# test the functionality of a line +# +# The line is set from the mockup side and is read from the userspace side +# (input), and is set from the userspace side and is read from the mockup side +# (output). +# +# Setting the mockup pull using the userspace interface bias settings is +# tested where supported by the userspace interface (cdev). +test_line() +{ + chip=$1 + offset=$2 + log "test_line $chip $offset" + mock_line=$GPIO_DEBUGFS/$chip/$offset + [ -e "$mock_line" ] || fail "missing line $chip:$offset" + + # test input active-high + set_mock 1 + set_line input active-high + assert_line 1 + set_mock 0 + assert_line 0 + set_mock 1 + assert_line 1 + + if [ "$full_test" ]; then + if [ "$dev_type" != "sysfs" ]; then + # test pulls + set_mock 0 + set_line input pull-up + assert_line 1 + set_mock 0 + assert_line 0 + + set_mock 1 + set_line input pull-down + assert_line 0 + set_mock 1 + assert_line 1 + + set_line bias-none + fi + + # test input active-low + set_mock 0 + set_line active-low + assert_line 1 + set_mock 1 + assert_line 0 + set_mock 0 + assert_line 1 + + # test output active-high + set_mock 1 + set_line active-high 0 + assert_mock 0 + set_line 1 + assert_mock 1 + set_line 0 + assert_mock 0 + fi + + # test output active-low + set_mock 0 + set_line active-low 0 + assert_mock 1 + set_line 1 + assert_mock 0 + set_line 0 + assert_mock 1 + + release_line +} + +test_no_line() +{ + log test_no_line "$*" + [ ! -e "$GPIO_DEBUGFS/$1/$2" ] || fail "unexpected line $1:$2" +} + +# Load the module and check that the expected number of gpiochips, with the +# expected number of lines, are created and are functional. +# +# $1 is the gpio_mockup_ranges parameter for the module +# The remaining parameters are the number of lines, n, expected for each of +# the gpiochips expected to be created. +# +# For each gpiochip the fence post lines, 0 and n-1, are tested, and the +# line on the far side of the fence post, n, is tested to not exist. +# +# If the $random flag is set then a random line in the middle of the +# gpiochip is tested as well. +insmod_test() +{ + local ranges= + local gc= + local width= + + [ "${1:-}" ] || fail "missing ranges" + ranges=$1 ; shift + try_insert_module "gpio_mockup_ranges=$ranges" + log "GPIO $module test with ranges: <$ranges>:" + # e.g. /sys/kernel/debug/gpio-mockup/gpiochip1 + gpiochip=$(find "$DEBUGFS/$module/" -name gpiochip* -type d | sort) + for chip in $gpiochip; do + gc=${chip##*/} + [ "${1:-}" ] || fail "unexpected chip - $gc" + width=$1 ; shift + test_line $gc 0 + if [ "$random" -a $width -gt 2 ]; then + test_line $gc $((RANDOM % ($width - 2) + 1)) + fi + test_line $gc $(($width - 1)) + test_no_line $gc $width + done + [ "${1:-}" ] && fail "missing expected chip of width $1" + remove_module || fail "failed to remove module with error $?" +} + +while getopts ":frvt:" opt; do + case $opt in + f) + full_test=true + ;; + r) + random=true + ;; + t) + dev_type=$OPTARG + ;; + v) + verbose=true + ;; + *) + usage + ;; + esac +done +shift $((OPTIND - 1)) + +[ "${1:-}" ] && fail "unknown argument '$1'" + +prerequisite + +trap 'exit $ksft_fail' SIGTERM SIGINT +trap cleanup EXIT + +case "$dev_type" in +sysfs) + source $BASE/gpio-mockup-sysfs.sh + echo "WARNING: gpio sysfs ABI is deprecated." + ;; +cdev_v1) + echo "WARNING: gpio cdev ABI v1 is deprecated." + uapi_opt="-u1 " + ;; +cdev) + ;; +*) + fail "unknown interface type: $dev_type" + ;; +esac + +remove_module || fail "can't remove existing $module module" + +# manual gpio allocation tests fail if a physical chip already exists +[ "$full_test" -a -e "/dev/gpiochip0" ] && skip "full tests conflict with gpiochip0" + +echo "1. Module load tests" +echo "1.1. dynamic allocation of gpio" +insmod_test "-1,32" 32 +insmod_test "-1,23,-1,32" 23 32 +insmod_test "-1,23,-1,26,-1,32" 23 26 32 +if [ "$full_test" ]; then + echo "1.2. manual allocation of gpio" + insmod_test "0,32" 32 + insmod_test "0,32,32,60" 32 28 + insmod_test "0,32,40,64,64,96" 32 24 32 + echo "1.3. dynamic and manual allocation of gpio" + insmod_test "-1,32,32,62" 32 30 + insmod_test "-1,22,-1,23,0,24,32,64" 22 23 24 32 + insmod_test "-1,32,32,60,-1,29" 32 28 29 + insmod_test "-1,32,40,64,-1,5" 32 24 5 + insmod_test "0,32,32,44,-1,22,-1,31" 32 12 22 31 +fi +echo "2. Module load error tests" +echo "2.1 gpio overflow" +# Currently: The max number of gpio(1024) is defined in arm architecture. +insmod_test "-1,1024" +if [ "$full_test" ]; then + echo "2.2 no lines defined" + insmod_test "0,0" + echo "2.3 ignore range overlap" + insmod_test "0,32,0,1" 32 + insmod_test "0,32,1,5" 32 + insmod_test "0,32,30,35" 32 + insmod_test "0,32,31,32" 32 + insmod_test "10,32,30,35" 22 + insmod_test "10,32,9,14" 22 + insmod_test "0,32,20,21,40,56" 32 16 + insmod_test "0,32,32,64,32,40" 32 32 + insmod_test "0,32,32,64,36,37" 32 32 + insmod_test "0,32,35,64,34,36" 32 29 + insmod_test "0,30,35,64,35,45" 30 29 + insmod_test "0,32,40,56,30,33" 32 16 + insmod_test "0,32,40,56,30,41" 32 16 + insmod_test "0,32,40,56,39,45" 32 16 +fi + +echo "GPIO $module test PASS" diff --git a/tools/testing/selftests/gpio/gpio-sim.sh b/tools/testing/selftests/gpio/gpio-sim.sh new file mode 100755 index 000000000..bf67b23ed --- /dev/null +++ b/tools/testing/selftests/gpio/gpio-sim.sh @@ -0,0 +1,399 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2021 Bartosz Golaszewski <brgl@bgdev.pl> + +BASE_DIR=`dirname $0` +CONFIGFS_DIR="/sys/kernel/config/gpio-sim" +MODULE="gpio-sim" + +fail() { + echo "$*" >&2 + echo "GPIO $MODULE test FAIL" + exit 1 +} + +skip() { + echo "$*" >&2 + echo "GPIO $MODULE test SKIP" + exit 4 +} + +remove_chip() { + local CHIP=$1 + + for FILE in $CONFIGFS_DIR/$CHIP/*; do + BANK=`basename $FILE` + if [ "$BANK" = "live" -o "$BANK" = "dev_name" ]; then + continue + fi + + LINES=`ls $CONFIGFS_DIR/$CHIP/$BANK/ | egrep ^line` + if [ "$?" = 0 ]; then + for LINE in $LINES; do + if [ -e $CONFIGFS_DIR/$CHIP/$BANK/$LINE/hog ]; then + rmdir $CONFIGFS_DIR/$CHIP/$BANK/$LINE/hog || \ + fail "Unable to remove the hog" + fi + + rmdir $CONFIGFS_DIR/$CHIP/$BANK/$LINE || \ + fail "Unable to remove the line" + done + fi + + rmdir $CONFIGFS_DIR/$CHIP/$BANK + done + + rmdir $CONFIGFS_DIR/$CHIP || fail "Unable to remove the chip" +} + +configfs_cleanup() { + for CHIP in `ls $CONFIGFS_DIR/`; do + remove_chip $CHIP + done +} + +create_chip() { + local CHIP=$1 + + mkdir $CONFIGFS_DIR/$CHIP +} + +create_bank() { + local CHIP=$1 + local BANK=$2 + + mkdir $CONFIGFS_DIR/$CHIP/$BANK +} + +set_label() { + local CHIP=$1 + local BANK=$2 + local LABEL=$3 + + echo $LABEL > $CONFIGFS_DIR/$CHIP/$BANK/label || fail "Unable to set the chip label" +} + +set_num_lines() { + local CHIP=$1 + local BANK=$2 + local NUM_LINES=$3 + + echo $NUM_LINES > $CONFIGFS_DIR/$CHIP/$BANK/num_lines || \ + fail "Unable to set the number of lines" +} + +set_line_name() { + local CHIP=$1 + local BANK=$2 + local OFFSET=$3 + local NAME=$4 + local LINE_DIR=$CONFIGFS_DIR/$CHIP/$BANK/line$OFFSET + + test -d $LINE_DIR || mkdir $LINE_DIR + echo $NAME > $LINE_DIR/name || fail "Unable to set the line name" +} + +enable_chip() { + local CHIP=$1 + + echo 1 > $CONFIGFS_DIR/$CHIP/live || fail "Unable to enable the chip" +} + +disable_chip() { + local CHIP=$1 + + echo 0 > $CONFIGFS_DIR/$CHIP/live || fail "Unable to disable the chip" +} + +configfs_chip_name() { + local CHIP=$1 + local BANK=$2 + + cat $CONFIGFS_DIR/$CHIP/$BANK/chip_name 2> /dev/null || \ + fail "unable to read the chip name from configfs" +} + +configfs_dev_name() { + local CHIP=$1 + + cat $CONFIGFS_DIR/$CHIP/dev_name 2> /dev/null || \ + fail "unable to read the device name from configfs" +} + +get_chip_num_lines() { + local CHIP=$1 + local BANK=$2 + + $BASE_DIR/gpio-chip-info /dev/`configfs_chip_name $CHIP $BANK` num-lines || \ + fail "unable to read the number of lines from the character device" +} + +get_chip_label() { + local CHIP=$1 + local BANK=$2 + + $BASE_DIR/gpio-chip-info /dev/`configfs_chip_name $CHIP $BANK` label || \ + fail "unable to read the chip label from the character device" +} + +get_line_name() { + local CHIP=$1 + local BANK=$2 + local OFFSET=$3 + + $BASE_DIR/gpio-line-name /dev/`configfs_chip_name $CHIP $BANK` $OFFSET || \ + fail "unable to read the line name from the character device" +} + +sysfs_set_pull() { + local DEV=$1 + local BANK=$2 + local OFFSET=$3 + local PULL=$4 + local DEVNAME=`configfs_dev_name $DEV` + local CHIPNAME=`configfs_chip_name $DEV $BANK` + local SYSFSPATH="/sys/devices/platform/$DEVNAME/$CHIPNAME/sim_gpio$OFFSET/pull" + + echo $PULL > $SYSFSPATH || fail "Unable to set line pull in sysfs" +} + +# Load the gpio-sim module. This will pull in configfs if needed too. +modprobe gpio-sim || skip "unable to load the gpio-sim module" +# Make sure configfs is mounted at /sys/kernel/config. Wait a bit if needed. +for IDX in `seq 5`; do + if [ "$IDX" -eq "5" ]; then + skip "configfs not mounted at /sys/kernel/config" + fi + + mountpoint -q /sys/kernel/config && break + sleep 0.1 +done +# If the module was already loaded: remove all previous chips +configfs_cleanup + +trap "exit 1" SIGTERM SIGINT +trap configfs_cleanup EXIT + +echo "1. chip_name and dev_name attributes" + +echo "1.1. Chip name is communicated to user" +create_chip chip +create_bank chip bank +enable_chip chip +test -n `cat $CONFIGFS_DIR/chip/bank/chip_name` || fail "chip_name doesn't work" +remove_chip chip + +echo "1.2. chip_name returns 'none' if the chip is still pending" +create_chip chip +create_bank chip bank +test "`cat $CONFIGFS_DIR/chip/bank/chip_name`" = "none" || \ + fail "chip_name doesn't return 'none' for a pending chip" +remove_chip chip + +echo "1.3. Device name is communicated to user" +create_chip chip +create_bank chip bank +enable_chip chip +test -n `cat $CONFIGFS_DIR/chip/dev_name` || fail "dev_name doesn't work" +remove_chip chip + +echo "2. Creating and configuring simulated chips" + +echo "2.1. Default number of lines is 1" +create_chip chip +create_bank chip bank +enable_chip chip +test "`get_chip_num_lines chip bank`" = "1" || fail "default number of lines is not 1" +remove_chip chip + +echo "2.2. Number of lines can be specified" +create_chip chip +create_bank chip bank +set_num_lines chip bank 16 +enable_chip chip +test "`get_chip_num_lines chip bank`" = "16" || fail "number of lines is not 16" +remove_chip chip + +echo "2.3. Label can be set" +create_chip chip +create_bank chip bank +set_label chip bank foobar +enable_chip chip +test "`get_chip_label chip bank`" = "foobar" || fail "label is incorrect" +remove_chip chip + +echo "2.4. Label can be left empty" +create_chip chip +create_bank chip bank +enable_chip chip +test -z "`cat $CONFIGFS_DIR/chip/bank/label`" || fail "label is not empty" +remove_chip chip + +echo "2.5. Line names can be configured" +create_chip chip +create_bank chip bank +set_num_lines chip bank 16 +set_line_name chip bank 0 foo +set_line_name chip bank 2 bar +enable_chip chip +test "`get_line_name chip bank 0`" = "foo" || fail "line name is incorrect" +test "`get_line_name chip bank 2`" = "bar" || fail "line name is incorrect" +remove_chip chip + +echo "2.6. Line config can remain unused if offset is greater than number of lines" +create_chip chip +create_bank chip bank +set_num_lines chip bank 2 +set_line_name chip bank 5 foobar +enable_chip chip +test "`get_line_name chip bank 0`" = "" || fail "line name is incorrect" +test "`get_line_name chip bank 1`" = "" || fail "line name is incorrect" +remove_chip chip + +echo "2.7. Line configfs directory names are sanitized" +create_chip chip +create_bank chip bank +mkdir $CONFIGFS_DIR/chip/bank/line12foobar 2> /dev/null && \ + fail "invalid configfs line name accepted" +mkdir $CONFIGFS_DIR/chip/bank/line_no_offset 2> /dev/null && \ + fail "invalid configfs line name accepted" +remove_chip chip + +echo "2.8. Multiple chips can be created" +CHIPS="chip0 chip1 chip2" +for CHIP in $CHIPS; do + create_chip $CHIP + create_bank $CHIP bank + enable_chip $CHIP +done +for CHIP in $CHIPS; do + remove_chip $CHIP +done + +echo "2.9. Can't modify settings when chip is live" +create_chip chip +create_bank chip bank +enable_chip chip +echo foobar > $CONFIGFS_DIR/chip/bank/label 2> /dev/null && \ + fail "Setting label of a live chip should fail" +echo 8 > $CONFIGFS_DIR/chip/bank/num_lines 2> /dev/null && \ + fail "Setting number of lines of a live chip should fail" +remove_chip chip + +echo "2.10. Can't create line items when chip is live" +create_chip chip +create_bank chip bank +enable_chip chip +mkdir $CONFIGFS_DIR/chip/bank/line0 2> /dev/null && fail "Creating line item should fail" +remove_chip chip + +echo "2.11. Probe errors are propagated to user-space" +create_chip chip +create_bank chip bank +set_num_lines chip bank 99999 +echo 1 > $CONFIGFS_DIR/chip/live 2> /dev/null && fail "Probe error was not propagated" +remove_chip chip + +echo "2.12. Cannot enable a chip without any GPIO banks" +create_chip chip +echo 1 > $CONFIGFS_DIR/chip/live 2> /dev/null && fail "Chip enabled without any GPIO banks" +remove_chip chip + +echo "2.13. Duplicate chip labels are not allowed" +create_chip chip +create_bank chip bank0 +set_label chip bank0 foobar +create_bank chip bank1 +set_label chip bank1 foobar +echo 1 > $CONFIGFS_DIR/chip/live 2> /dev/null && fail "Duplicate chip labels were not rejected" +remove_chip chip + +echo "2.14. Lines can be hogged" +create_chip chip +create_bank chip bank +set_num_lines chip bank 8 +mkdir -p $CONFIGFS_DIR/chip/bank/line4/hog +enable_chip chip +$BASE_DIR/gpio-mockup-cdev -s 1 /dev/`configfs_chip_name chip bank` 4 2> /dev/null && \ + fail "Setting the value of a hogged line shouldn't succeed" +remove_chip chip + +echo "3. Controlling simulated chips" + +echo "3.1. Pull can be set over sysfs" +create_chip chip +create_bank chip bank +set_num_lines chip bank 8 +enable_chip chip +sysfs_set_pull chip bank 0 pull-up +$BASE_DIR/gpio-mockup-cdev /dev/`configfs_chip_name chip bank` 0 +test "$?" = "1" || fail "pull set incorrectly" +sysfs_set_pull chip bank 0 pull-down +$BASE_DIR/gpio-mockup-cdev /dev/`configfs_chip_name chip bank` 1 +test "$?" = "0" || fail "pull set incorrectly" +remove_chip chip + +echo "3.2. Pull can be read from sysfs" +create_chip chip +create_bank chip bank +set_num_lines chip bank 8 +enable_chip chip +DEVNAME=`configfs_dev_name chip` +CHIPNAME=`configfs_chip_name chip bank` +SYSFS_PATH=/sys/devices/platform/$DEVNAME/$CHIPNAME/sim_gpio0/pull +test `cat $SYSFS_PATH` = "pull-down" || fail "reading the pull failed" +sysfs_set_pull chip bank 0 pull-up +test `cat $SYSFS_PATH` = "pull-up" || fail "reading the pull failed" +remove_chip chip + +echo "3.3. Incorrect input in sysfs is rejected" +create_chip chip +create_bank chip bank +set_num_lines chip bank 8 +enable_chip chip +DEVNAME=`configfs_dev_name chip` +CHIPNAME=`configfs_chip_name chip bank` +SYSFS_PATH="/sys/devices/platform/$DEVNAME/$CHIPNAME/sim_gpio0/pull" +echo foobar > $SYSFS_PATH 2> /dev/null && fail "invalid input not detected" +remove_chip chip + +echo "3.4. Can't write to value" +create_chip chip +create_bank chip bank +enable_chip chip +DEVNAME=`configfs_dev_name chip` +CHIPNAME=`configfs_chip_name chip bank` +SYSFS_PATH="/sys/devices/platform/$DEVNAME/$CHIPNAME/sim_gpio0/value" +echo 1 > $SYSFS_PATH 2> /dev/null && fail "writing to 'value' succeeded unexpectedly" +remove_chip chip + +echo "4. Simulated GPIO chips are functional" + +echo "4.1. Values can be read from sysfs" +create_chip chip +create_bank chip bank +set_num_lines chip bank 8 +enable_chip chip +DEVNAME=`configfs_dev_name chip` +CHIPNAME=`configfs_chip_name chip bank` +SYSFS_PATH="/sys/devices/platform/$DEVNAME/$CHIPNAME/sim_gpio0/value" +test `cat $SYSFS_PATH` = "0" || fail "incorrect value read from sysfs" +$BASE_DIR/gpio-mockup-cdev -s 1 /dev/`configfs_chip_name chip bank` 0 & +sleep 0.1 # FIXME Any better way? +test `cat $SYSFS_PATH` = "1" || fail "incorrect value read from sysfs" +kill $! +remove_chip chip + +echo "4.2. Bias settings work correctly" +create_chip chip +create_bank chip bank +set_num_lines chip bank 8 +enable_chip chip +DEVNAME=`configfs_dev_name chip` +CHIPNAME=`configfs_chip_name chip bank` +SYSFS_PATH="/sys/devices/platform/$DEVNAME/$CHIPNAME/sim_gpio0/value" +$BASE_DIR/gpio-mockup-cdev -b pull-up /dev/`configfs_chip_name chip bank` 0 +test `cat $SYSFS_PATH` = "1" || fail "bias setting does not work" +remove_chip chip + +echo "GPIO $MODULE test PASS" |