1#!/bin/bash -efu
2# SPDX-License-Identifier: GPL-2.0
3
4#exit status
5#0: success
6#1: fail
7#4: skip test - including run as non-root user
8
9BASE=${0%/*}
10DEBUGFS=
11GPIO_DEBUGFS=
12dev_type="cdev"
13module="gpio-mockup"
14verbose=
15full_test=
16random=
17uapi_opt=
18active_opt=
19bias_opt=
20line_set_pid=
21
22# Kselftest return codes
23ksft_fail=1
24ksft_skip=4
25
26usage()
27{
28	echo "Usage:"
29	echo "$0 [-frv] [-t type]"
30	echo "-f:  full test (minimal set run by default)"
31	echo "-r:  test random lines as well as fence posts"
32	echo "-t:  interface type:"
33	echo "      cdev (character device ABI) - default"
34	echo "      cdev_v1 (deprecated character device ABI)"
35	echo "      sysfs (deprecated SYSFS ABI)"
36	echo "-v:  verbose progress reporting"
37	exit $ksft_fail
38}
39
40skip()
41{
42	echo "$*" >&2
43	echo "GPIO $module test SKIP"
44	exit $ksft_skip
45}
46
47prerequisite()
48{
49	[ $(id -u) -eq 0 ] || skip "must be run as root"
50
51	DEBUGFS=$(grep -w debugfs /proc/mounts | cut -f2 -d' ')
52	[ -d "$DEBUGFS" ] || skip "debugfs is not mounted"
53
54	GPIO_DEBUGFS=$DEBUGFS/$module
55}
56
57remove_module()
58{
59	modprobe -r -q $module
60}
61
62cleanup()
63{
64	set +e
65	release_line
66	remove_module
67	jobs -p | xargs -r kill > /dev/null 2>&1
68}
69
70fail()
71{
72	echo "test failed: $*" >&2
73	echo "GPIO $module test FAIL"
74	exit $ksft_fail
75}
76
77try_insert_module()
78{
79	modprobe -q $module "$1" || fail "insert $module failed with error $?"
80}
81
82log()
83{
84	[ -z "$verbose" ] || echo "$*"
85}
86
87# The following line helpers, release_Line, get_line and set_line, all
88# make use of the global $chip and $offset variables.
89#
90# This implementation drives the GPIO character device (cdev) uAPI.
91# Other implementations may override these to test different uAPIs.
92
93# Release any resources related to the line
94release_line()
95{
96	[ "$line_set_pid" ] && kill $line_set_pid && wait $line_set_pid || true
97	line_set_pid=
98}
99
100# Read the current value of the line
101get_line()
102{
103	release_line
104
105	local cdev_opts=${uapi_opt}${active_opt}
106	$BASE/gpio-mockup-cdev $cdev_opts /dev/$chip $offset
107	echo $?
108}
109
110# Set the state of the line
111#
112# Changes to line configuration are provided as parameters.
113# The line is assumed to be an output if the line value 0 or 1 is
114# specified, else an input.
115set_line()
116{
117	local val=
118
119	release_line
120
121	# parse config options...
122	for option in $*; do
123		case $option in
124		active-low)
125			active_opt="-l "
126			;;
127		active-high)
128			active_opt=
129			;;
130		bias-none)
131			bias_opt=
132			;;
133		pull-down)
134			bias_opt="-bpull-down "
135			;;
136		pull-up)
137			bias_opt="-bpull-up "
138			;;
139		0)
140			val=0
141			;;
142		1)
143			val=1
144			;;
145		esac
146	done
147
148	local cdev_opts=${uapi_opt}${active_opt}
149	if [ "$val" ]; then
150		$BASE/gpio-mockup-cdev $cdev_opts -s$val /dev/$chip $offset &
151		# failure to set is detected by reading mockup and toggling values
152		line_set_pid=$!
153		# allow for gpio-mockup-cdev to launch and request line
154		# (there is limited value in checking if line has been requested)
155		sleep 0.01
156	elif [ "$bias_opt" ]; then
157		cdev_opts=${cdev_opts}${bias_opt}
158		$BASE/gpio-mockup-cdev $cdev_opts /dev/$chip $offset || true
159	fi
160}
161
162assert_line()
163{
164	local val
165	# don't need any retry here as set_mock allows for propagation
166	val=$(get_line)
167	[ "$val" = "$1" ] || fail "line value is ${val:-empty} when $1 was expected"
168}
169
170# The following mockup helpers all make use of the $mock_line
171assert_mock()
172{
173	local backoff_wait=10
174	local retry=0
175	local val
176	# retry allows for set propagation from uAPI to mockup
177	while true; do
178		val=$(< $mock_line)
179		[ "$val" = "$1" ] && break
180		retry=$((retry + 1))
181		[ $retry -lt 5 ] || fail "mockup $mock_line value ${val:-empty} when $1 expected"
182		sleep $(printf "%0.2f" $((backoff_wait))e-3)
183		backoff_wait=$((backoff_wait * 2))
184	done
185}
186
187set_mock()
188{
189	echo "$1" > $mock_line
190	# allow for set propagation - so we won't be in a race with set_line
191	assert_mock "$1"
192}
193
194# test the functionality of a line
195#
196# The line is set from the mockup side and is read from the userspace side
197# (input), and is set from the userspace side and is read from the mockup side
198# (output).
199#
200# Setting the mockup pull using the userspace interface bias settings is
201# tested where supported by the userspace interface (cdev).
202test_line()
203{
204	chip=$1
205	offset=$2
206	log "test_line $chip $offset"
207	mock_line=$GPIO_DEBUGFS/$chip/$offset
208	[ -e "$mock_line" ] || fail "missing line $chip:$offset"
209
210	# test input active-high
211	set_mock 1
212	set_line input active-high
213	assert_line 1
214	set_mock 0
215	assert_line 0
216	set_mock 1
217	assert_line 1
218
219	if [ "$full_test" ]; then
220		if [ "$dev_type" != "sysfs" ]; then
221			# test pulls
222			set_mock 0
223			set_line input pull-up
224			assert_line 1
225			set_mock 0
226			assert_line 0
227
228			set_mock 1
229			set_line input pull-down
230			assert_line 0
231			set_mock 1
232			assert_line 1
233
234			set_line bias-none
235		fi
236
237		# test input active-low
238		set_mock 0
239		set_line active-low
240		assert_line 1
241		set_mock 1
242		assert_line 0
243		set_mock 0
244		assert_line 1
245
246		# test output active-high
247		set_mock 1
248		set_line active-high 0
249		assert_mock 0
250		set_line 1
251		assert_mock 1
252		set_line 0
253		assert_mock 0
254	fi
255
256	# test output active-low
257	set_mock 0
258	set_line active-low 0
259	assert_mock 1
260	set_line 1
261	assert_mock 0
262	set_line 0
263	assert_mock 1
264
265	release_line
266}
267
268test_no_line()
269{
270	log test_no_line "$*"
271	[ ! -e "$GPIO_DEBUGFS/$1/$2" ] || fail "unexpected line $1:$2"
272}
273
274# Load the module and check that the expected number of gpiochips, with the
275# expected number of lines, are created and are functional.
276#
277# $1 is the gpio_mockup_ranges parameter for the module
278# The remaining parameters are the number of lines, n, expected for each of
279# the gpiochips expected to be created.
280#
281# For each gpiochip the fence post lines, 0 and n-1, are tested, and the
282# line on the far side of the fence post, n, is tested to not exist.
283#
284# If the $random flag is set then a random line in the middle of the
285# gpiochip is tested as well.
286insmod_test()
287{
288	local ranges=
289	local gc=
290	local width=
291
292	[ "${1:-}" ] || fail "missing ranges"
293	ranges=$1 ; shift
294	try_insert_module "gpio_mockup_ranges=$ranges"
295	log "GPIO $module test with ranges: <$ranges>:"
296	# e.g. /sys/kernel/debug/gpio-mockup/gpiochip1
297	gpiochip=$(find "$DEBUGFS/$module/" -name gpiochip* -type d | sort)
298	for chip in $gpiochip; do
299		gc=${chip##*/}
300		[ "${1:-}" ] || fail "unexpected chip - $gc"
301		width=$1 ; shift
302		test_line $gc 0
303		if [ "$random" -a $width -gt 2 ]; then
304			test_line $gc $((RANDOM % ($width - 2) + 1))
305		fi
306		test_line $gc $(($width - 1))
307		test_no_line $gc $width
308	done
309	[ "${1:-}" ] && fail "missing expected chip of width $1"
310	remove_module || fail "failed to remove module with error $?"
311}
312
313while getopts ":frvt:" opt; do
314	case $opt in
315	f)
316		full_test=true
317		;;
318	r)
319		random=true
320		;;
321	t)
322		dev_type=$OPTARG
323		;;
324	v)
325		verbose=true
326		;;
327	*)
328		usage
329		;;
330	esac
331done
332shift $((OPTIND - 1))
333
334[ "${1:-}" ] && fail "unknown argument '$1'"
335
336prerequisite
337
338trap 'exit $ksft_fail' SIGTERM SIGINT
339trap cleanup EXIT
340
341case "$dev_type" in
342sysfs)
343	source $BASE/gpio-mockup-sysfs.sh
344	echo "WARNING: gpio sysfs ABI is deprecated."
345	;;
346cdev_v1)
347	echo "WARNING: gpio cdev ABI v1 is deprecated."
348	uapi_opt="-u1 "
349	;;
350cdev)
351	;;
352*)
353	fail "unknown interface type: $dev_type"
354	;;
355esac
356
357remove_module || fail "can't remove existing $module module"
358
359# manual gpio allocation tests fail if a physical chip already exists
360[ "$full_test" -a -e "/dev/gpiochip0" ] && skip "full tests conflict with gpiochip0"
361
362echo "1.  Module load tests"
363echo "1.1.  dynamic allocation of gpio"
364insmod_test "-1,32" 32
365insmod_test "-1,23,-1,32" 23 32
366insmod_test "-1,23,-1,26,-1,32" 23 26 32
367if [ "$full_test" ]; then
368	echo "1.2.  manual allocation of gpio"
369	insmod_test "0,32" 32
370	insmod_test "0,32,32,60" 32 28
371	insmod_test "0,32,40,64,64,96" 32 24 32
372	echo "1.3.  dynamic and manual allocation of gpio"
373	insmod_test "-1,32,32,62" 32 30
374	insmod_test "-1,22,-1,23,0,24,32,64" 22 23 24 32
375	insmod_test "-1,32,32,60,-1,29" 32 28 29
376	insmod_test "-1,32,40,64,-1,5" 32 24 5
377	insmod_test "0,32,32,44,-1,22,-1,31" 32 12 22 31
378fi
379echo "2.  Module load error tests"
380echo "2.1 no lines defined"
381insmod_test "0,0"
382if [ "$full_test" ]; then
383	echo "2.2 ignore range overlap"
384	insmod_test "0,32,0,1" 32
385	insmod_test "0,32,1,5" 32
386	insmod_test "0,32,30,35" 32
387	insmod_test "0,32,31,32" 32
388	insmod_test "10,32,30,35" 22
389	insmod_test "10,32,9,14" 22
390	insmod_test "0,32,20,21,40,56" 32 16
391	insmod_test "0,32,32,64,32,40" 32 32
392	insmod_test "0,32,32,64,36,37" 32 32
393	insmod_test "0,32,35,64,34,36" 32 29
394	insmod_test "0,30,35,64,35,45" 30 29
395	insmod_test "0,32,40,56,30,33" 32 16
396	insmod_test "0,32,40,56,30,41" 32 16
397	insmod_test "0,32,40,56,39,45" 32 16
398fi
399
400echo "GPIO $module test PASS"
401