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