xref: /openbmc/linux/tools/testing/selftests/firmware/fw_fallback.sh (revision 9cc853203c3bf4d3b0c466c5525750c071624b05)
1#!/bin/bash
2# SPDX-License-Identifier: GPL-2.0
3# This validates that the kernel will fall back to using the fallback mechanism
4# to load firmware it can't find on disk itself. We must request a firmware
5# that the kernel won't find, and any installed helper (e.g. udev) also
6# won't find so that we can do the load ourself manually.
7set -e
8
9TEST_DIR=$(dirname $0)
10source $TEST_DIR/fw_lib.sh
11
12check_mods
13
14HAS_FW_LOADER_USER_HELPER=$(kconfig_has CONFIG_FW_LOADER_USER_HELPER=y)
15HAS_FW_LOADER_USER_HELPER_FALLBACK=$(kconfig_has CONFIG_FW_LOADER_USER_HELPER_FALLBACK=y)
16
17if [ "$HAS_FW_LOADER_USER_HELPER" = "yes" ]; then
18       OLD_TIMEOUT=$(cat /sys/class/firmware/timeout)
19else
20	echo "usermode helper disabled so ignoring test"
21	exit 0
22fi
23
24FWPATH=$(mktemp -d)
25FW="$FWPATH/test-firmware.bin"
26
27test_finish()
28{
29	echo "$OLD_TIMEOUT" >/sys/class/firmware/timeout
30	rm -f "$FW"
31	rmdir "$FWPATH"
32}
33
34load_fw()
35{
36	local name="$1"
37	local file="$2"
38
39	# This will block until our load (below) has finished.
40	echo -n "$name" >"$DIR"/trigger_request &
41
42	# Give kernel a chance to react.
43	local timeout=10
44	while [ ! -e "$DIR"/"$name"/loading ]; do
45		sleep 0.1
46		timeout=$(( $timeout - 1 ))
47		if [ "$timeout" -eq 0 ]; then
48			echo "$0: firmware interface never appeared" >&2
49			exit 1
50		fi
51	done
52
53	echo 1 >"$DIR"/"$name"/loading
54	cat "$file" >"$DIR"/"$name"/data
55	echo 0 >"$DIR"/"$name"/loading
56
57	# Wait for request to finish.
58	wait
59}
60
61load_fw_cancel()
62{
63	local name="$1"
64	local file="$2"
65
66	# This will block until our load (below) has finished.
67	echo -n "$name" >"$DIR"/trigger_request 2>/dev/null &
68
69	# Give kernel a chance to react.
70	local timeout=10
71	while [ ! -e "$DIR"/"$name"/loading ]; do
72		sleep 0.1
73		timeout=$(( $timeout - 1 ))
74		if [ "$timeout" -eq 0 ]; then
75			echo "$0: firmware interface never appeared" >&2
76			exit 1
77		fi
78	done
79
80	echo -1 >"$DIR"/"$name"/loading
81
82	# Wait for request to finish.
83	wait
84}
85
86load_fw_custom()
87{
88	if [ ! -e "$DIR"/trigger_custom_fallback ]; then
89		echo "$0: custom fallback trigger not present, ignoring test" >&2
90		return 1
91	fi
92
93	local name="$1"
94	local file="$2"
95
96	echo -n "$name" >"$DIR"/trigger_custom_fallback 2>/dev/null &
97
98	# Give kernel a chance to react.
99	local timeout=10
100	while [ ! -e "$DIR"/"$name"/loading ]; do
101		sleep 0.1
102		timeout=$(( $timeout - 1 ))
103		if [ "$timeout" -eq 0 ]; then
104			echo "$0: firmware interface never appeared" >&2
105			exit 1
106		fi
107	done
108
109	echo 1 >"$DIR"/"$name"/loading
110	cat "$file" >"$DIR"/"$name"/data
111	echo 0 >"$DIR"/"$name"/loading
112
113	# Wait for request to finish.
114	wait
115	return 0
116}
117
118
119load_fw_custom_cancel()
120{
121	if [ ! -e "$DIR"/trigger_custom_fallback ]; then
122		echo "$0: canceling custom fallback trigger not present, ignoring test" >&2
123		return 1
124	fi
125
126	local name="$1"
127	local file="$2"
128
129	echo -n "$name" >"$DIR"/trigger_custom_fallback 2>/dev/null &
130
131	# Give kernel a chance to react.
132	local timeout=10
133	while [ ! -e "$DIR"/"$name"/loading ]; do
134		sleep 0.1
135		timeout=$(( $timeout - 1 ))
136		if [ "$timeout" -eq 0 ]; then
137			echo "$0: firmware interface never appeared" >&2
138			exit 1
139		fi
140	done
141
142	echo -1 >"$DIR"/"$name"/loading
143
144	# Wait for request to finish.
145	wait
146	return 0
147}
148
149load_fw_fallback_with_child()
150{
151	local name="$1"
152	local file="$2"
153
154	# This is the value already set but we want to be explicit
155	echo 4 >/sys/class/firmware/timeout
156
157	sleep 1 &
158	SECONDS_BEFORE=$(date +%s)
159	echo -n "$name" >"$DIR"/trigger_request 2>/dev/null
160	SECONDS_AFTER=$(date +%s)
161	SECONDS_DELTA=$(($SECONDS_AFTER - $SECONDS_BEFORE))
162	if [ "$SECONDS_DELTA" -lt 4 ]; then
163		RET=1
164	else
165		RET=0
166	fi
167	wait
168	return $RET
169}
170
171trap "test_finish" EXIT
172
173# This is an unlikely real-world firmware content. :)
174echo "ABCD0123" >"$FW"
175NAME=$(basename "$FW")
176
177test_syfs_timeout()
178{
179	DEVPATH="$DIR"/"nope-$NAME"/loading
180
181	# Test failure when doing nothing (timeout works).
182	echo -n 2 >/sys/class/firmware/timeout
183	echo -n "nope-$NAME" >"$DIR"/trigger_request 2>/dev/null &
184
185	# Give the kernel some time to load the loading file, must be less
186	# than the timeout above.
187	sleep 1
188	if [ ! -f $DEVPATH ]; then
189		echo "$0: fallback mechanism immediately cancelled"
190		echo ""
191		echo "The file never appeared: $DEVPATH"
192		echo ""
193		echo "This might be a distribution udev rule setup by your distribution"
194		echo "to immediately cancel all fallback requests, this must be"
195		echo "removed before running these tests. To confirm look for"
196		echo "a firmware rule like /lib/udev/rules.d/50-firmware.rules"
197		echo "and see if you have something like this:"
198		echo ""
199		echo "SUBSYSTEM==\"firmware\", ACTION==\"add\", ATTR{loading}=\"-1\""
200		echo ""
201		echo "If you do remove this file or comment out this line before"
202		echo "proceeding with these tests."
203		exit 1
204	fi
205
206	if diff -q "$FW" /dev/test_firmware >/dev/null ; then
207		echo "$0: firmware was not expected to match" >&2
208		exit 1
209	else
210		echo "$0: timeout works"
211	fi
212}
213
214run_sysfs_main_tests()
215{
216	test_syfs_timeout
217	# Put timeout high enough for us to do work but not so long that failures
218	# slow down this test too much.
219	echo 4 >/sys/class/firmware/timeout
220
221	# Load this script instead of the desired firmware.
222	load_fw "$NAME" "$0"
223	if diff -q "$FW" /dev/test_firmware >/dev/null ; then
224		echo "$0: firmware was not expected to match" >&2
225		exit 1
226	else
227		echo "$0: firmware comparison works"
228	fi
229
230	# Do a proper load, which should work correctly.
231	load_fw "$NAME" "$FW"
232	if ! diff -q "$FW" /dev/test_firmware >/dev/null ; then
233		echo "$0: firmware was not loaded" >&2
234		exit 1
235	else
236		echo "$0: fallback mechanism works"
237	fi
238
239	load_fw_cancel "nope-$NAME" "$FW"
240	if diff -q "$FW" /dev/test_firmware >/dev/null ; then
241		echo "$0: firmware was expected to be cancelled" >&2
242		exit 1
243	else
244		echo "$0: cancelling fallback mechanism works"
245	fi
246
247	set +e
248	load_fw_fallback_with_child "nope-signal-$NAME" "$FW"
249	if [ "$?" -eq 0 ]; then
250		echo "$0: SIGCHLD on sync ignored as expected" >&2
251	else
252		echo "$0: error - sync firmware request cancelled due to SIGCHLD" >&2
253		exit 1
254	fi
255	set -e
256}
257
258run_sysfs_custom_load_tests()
259{
260	if load_fw_custom "$NAME" "$FW" ; then
261		if ! diff -q "$FW" /dev/test_firmware >/dev/null ; then
262			echo "$0: firmware was not loaded" >&2
263			exit 1
264		else
265			echo "$0: custom fallback loading mechanism works"
266		fi
267	fi
268
269	if load_fw_custom "$NAME" "$FW" ; then
270		if ! diff -q "$FW" /dev/test_firmware >/dev/null ; then
271			echo "$0: firmware was not loaded" >&2
272			exit 1
273		else
274			echo "$0: custom fallback loading mechanism works"
275		fi
276	fi
277
278	if load_fw_custom_cancel "nope-$NAME" "$FW" ; then
279		if diff -q "$FW" /dev/test_firmware >/dev/null ; then
280			echo "$0: firmware was expected to be cancelled" >&2
281			exit 1
282		else
283			echo "$0: cancelling custom fallback mechanism works"
284		fi
285	fi
286}
287
288if [ "$HAS_FW_LOADER_USER_HELPER_FALLBACK" = "yes" ]; then
289	run_sysfs_main_tests
290fi
291
292run_sysfs_custom_load_tests
293
294exit 0
295