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