| #!/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 no lines defined" |
| insmod_test "0,0" |
| if [ "$full_test" ]; then |
| echo "2.2 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" |