1#!/bin/bash
2# SPDX-License-Identifier: GPL-2.0
3# This validates that the kernel will load firmware out of its list of
4# firmware locations on disk. Since the user helper does similar work,
5# we reset the custom load directory to a location the user helper doesn't
6# know so we can be sure we're not accidentally testing the user helper.
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 most distros enable CONFIG_FW_LOADER_USER_HELPER but disable
16# CONFIG_FW_LOADER_USER_HELPER_FALLBACK. We use /sys/class/firmware/ as an
17# indicator for CONFIG_FW_LOADER_USER_HELPER.
18HAS_FW_LOADER_USER_HELPER=$(if [ -d /sys/class/firmware/ ]; then echo yes; else echo no; fi)
19
20if [ "$HAS_FW_LOADER_USER_HELPER" = "yes" ]; then
21	OLD_TIMEOUT=$(cat /sys/class/firmware/timeout)
22fi
23
24OLD_FWPATH=$(cat /sys/module/firmware_class/parameters/path)
25
26FWPATH=$(mktemp -d)
27FW="$FWPATH/test-firmware.bin"
28
29test_finish()
30{
31	if [ "$HAS_FW_LOADER_USER_HELPER" = "yes" ]; then
32		echo "$OLD_TIMEOUT" >/sys/class/firmware/timeout
33	fi
34	if [ "$OLD_FWPATH" = "" ]; then
35		OLD_FWPATH=" "
36	fi
37	echo -n "$OLD_FWPATH" >/sys/module/firmware_class/parameters/path
38	rm -f "$FW"
39	rmdir "$FWPATH"
40}
41
42trap "test_finish" EXIT
43
44if [ "$HAS_FW_LOADER_USER_HELPER" = "yes" ]; then
45	# Turn down the timeout so failures don't take so long.
46	echo 1 >/sys/class/firmware/timeout
47fi
48
49# Set the kernel search path.
50echo -n "$FWPATH" >/sys/module/firmware_class/parameters/path
51
52# This is an unlikely real-world firmware content. :)
53echo "ABCD0123" >"$FW"
54
55NAME=$(basename "$FW")
56
57if printf '\000' >"$DIR"/trigger_request 2> /dev/null; then
58	echo "$0: empty filename should not succeed" >&2
59	exit 1
60fi
61
62if [ ! -e "$DIR"/trigger_async_request ]; then
63	echo "$0: empty filename: async trigger not present, ignoring test" >&2
64else
65	if printf '\000' >"$DIR"/trigger_async_request 2> /dev/null; then
66		echo "$0: empty filename should not succeed (async)" >&2
67		exit 1
68	fi
69fi
70
71# Request a firmware that doesn't exist, it should fail.
72if echo -n "nope-$NAME" >"$DIR"/trigger_request 2> /dev/null; then
73	echo "$0: firmware shouldn't have loaded" >&2
74	exit 1
75fi
76if diff -q "$FW" /dev/test_firmware >/dev/null ; then
77	echo "$0: firmware was not expected to match" >&2
78	exit 1
79else
80	if [ "$HAS_FW_LOADER_USER_HELPER" = "yes" ]; then
81		echo "$0: timeout works"
82	fi
83fi
84
85# This should succeed via kernel load or will fail after 1 second after
86# being handed over to the user helper, which won't find the fw either.
87if ! echo -n "$NAME" >"$DIR"/trigger_request ; then
88	echo "$0: could not trigger request" >&2
89	exit 1
90fi
91
92# Verify the contents are what we expect.
93if ! diff -q "$FW" /dev/test_firmware >/dev/null ; then
94	echo "$0: firmware was not loaded" >&2
95	exit 1
96else
97	echo "$0: filesystem loading works"
98fi
99
100# Try the asynchronous version too
101if [ ! -e "$DIR"/trigger_async_request ]; then
102	echo "$0: firmware loading: async trigger not present, ignoring test" >&2
103else
104	if ! echo -n "$NAME" >"$DIR"/trigger_async_request ; then
105		echo "$0: could not trigger async request" >&2
106		exit 1
107	fi
108
109	# Verify the contents are what we expect.
110	if ! diff -q "$FW" /dev/test_firmware >/dev/null ; then
111		echo "$0: firmware was not loaded (async)" >&2
112		exit 1
113	else
114		echo "$0: async filesystem loading works"
115	fi
116fi
117
118### Batched requests tests
119test_config_present()
120{
121	if [ ! -f $DIR/reset ]; then
122		echo "Configuration triggers not present, ignoring test"
123		exit 0
124	fi
125}
126
127# Defaults :
128#
129# send_uevent: 1
130# sync_direct: 0
131# name: test-firmware.bin
132# num_requests: 4
133config_reset()
134{
135	echo 1 >  $DIR/reset
136}
137
138release_all_firmware()
139{
140	echo 1 >  $DIR/release_all_firmware
141}
142
143config_set_name()
144{
145	echo -n $1 >  $DIR/config_name
146}
147
148config_set_sync_direct()
149{
150	echo 1 >  $DIR/config_sync_direct
151}
152
153config_unset_sync_direct()
154{
155	echo 0 >  $DIR/config_sync_direct
156}
157
158config_set_uevent()
159{
160	echo 1 >  $DIR/config_send_uevent
161}
162
163config_unset_uevent()
164{
165	echo 0 >  $DIR/config_send_uevent
166}
167
168config_trigger_sync()
169{
170	echo -n 1 > $DIR/trigger_batched_requests 2>/dev/null
171}
172
173config_trigger_async()
174{
175	echo -n 1 > $DIR/trigger_batched_requests_async 2> /dev/null
176}
177
178config_set_read_fw_idx()
179{
180	echo -n $1 > $DIR/config_read_fw_idx 2> /dev/null
181}
182
183read_firmwares()
184{
185	for i in $(seq 0 3); do
186		config_set_read_fw_idx $i
187		# Verify the contents are what we expect.
188		# -Z required for now -- check for yourself, md5sum
189		# on $FW and DIR/read_firmware will yield the same. Even
190		# cmp agrees, so something is off.
191		if ! diff -q -Z "$FW" $DIR/read_firmware 2>/dev/null ; then
192			echo "request #$i: firmware was not loaded" >&2
193			exit 1
194		fi
195	done
196}
197
198read_firmwares_expect_nofile()
199{
200	for i in $(seq 0 3); do
201		config_set_read_fw_idx $i
202		# Ensures contents differ
203		if diff -q -Z "$FW" $DIR/read_firmware 2>/dev/null ; then
204			echo "request $i: file was not expected to match" >&2
205			exit 1
206		fi
207	done
208}
209
210test_batched_request_firmware_nofile()
211{
212	echo -n "Batched request_firmware() nofile try #$1: "
213	config_reset
214	config_set_name nope-test-firmware.bin
215	config_trigger_sync
216	read_firmwares_expect_nofile
217	release_all_firmware
218	echo "OK"
219}
220
221test_batched_request_firmware_direct_nofile()
222{
223	echo -n "Batched request_firmware_direct() nofile try #$1: "
224	config_reset
225	config_set_name nope-test-firmware.bin
226	config_set_sync_direct
227	config_trigger_sync
228	release_all_firmware
229	echo "OK"
230}
231
232test_request_firmware_nowait_uevent_nofile()
233{
234	echo -n "Batched request_firmware_nowait(uevent=true) nofile try #$1: "
235	config_reset
236	config_set_name nope-test-firmware.bin
237	config_trigger_async
238	release_all_firmware
239	echo "OK"
240}
241
242test_wait_and_cancel_custom_load()
243{
244	if [ "$HAS_FW_LOADER_USER_HELPER" != "yes" ]; then
245		return
246	fi
247	local timeout=10
248	name=$1
249	while [ ! -e "$DIR"/"$name"/loading ]; do
250		sleep 0.1
251		timeout=$(( $timeout - 1 ))
252		if [ "$timeout" -eq 0 ]; then
253			echo "firmware interface never appeared:" >&2
254			echo "$DIR/$name/loading" >&2
255			exit 1
256		fi
257	done
258	echo -1 >"$DIR"/"$name"/loading
259}
260
261test_request_firmware_nowait_custom_nofile()
262{
263	echo -n "Batched request_firmware_nowait(uevent=false) nofile try #$1: "
264	config_unset_uevent
265	config_set_name nope-test-firmware.bin
266	config_trigger_async &
267	test_wait_and_cancel_custom_load nope-test-firmware.bin
268	wait
269	release_all_firmware
270	echo "OK"
271}
272
273test_batched_request_firmware()
274{
275	echo -n "Batched request_firmware() try #$1: "
276	config_reset
277	config_trigger_sync
278	read_firmwares
279	release_all_firmware
280	echo "OK"
281}
282
283test_batched_request_firmware_direct()
284{
285	echo -n "Batched request_firmware_direct() try #$1: "
286	config_reset
287	config_set_sync_direct
288	config_trigger_sync
289	release_all_firmware
290	echo "OK"
291}
292
293test_request_firmware_nowait_uevent()
294{
295	echo -n "Batched request_firmware_nowait(uevent=true) try #$1: "
296	config_reset
297	config_trigger_async
298	release_all_firmware
299	echo "OK"
300}
301
302test_request_firmware_nowait_custom()
303{
304	echo -n "Batched request_firmware_nowait(uevent=false) try #$1: "
305	config_unset_uevent
306	config_trigger_async
307	release_all_firmware
308	echo "OK"
309}
310
311# Only continue if batched request triggers are present on the
312# test-firmware driver
313test_config_present
314
315# test with the file present
316echo
317echo "Testing with the file present..."
318for i in $(seq 1 5); do
319	test_batched_request_firmware $i
320done
321
322for i in $(seq 1 5); do
323	test_batched_request_firmware_direct $i
324done
325
326for i in $(seq 1 5); do
327	test_request_firmware_nowait_uevent $i
328done
329
330for i in $(seq 1 5); do
331	test_request_firmware_nowait_custom $i
332done
333
334# Test for file not found, errors are expected, the failure would be
335# a hung task, which would require a hard reset.
336echo
337echo "Testing with the file missing..."
338for i in $(seq 1 5); do
339	test_batched_request_firmware_nofile $i
340done
341
342for i in $(seq 1 5); do
343	test_batched_request_firmware_direct_nofile $i
344done
345
346for i in $(seq 1 5); do
347	test_request_firmware_nowait_uevent_nofile $i
348done
349
350for i in $(seq 1 5); do
351	test_request_firmware_nowait_custom_nofile $i
352done
353
354exit 0
355