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