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