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_REQS_FW_SYSFS_FALLBACK="no"
10TEST_REQS_FW_SET_CUSTOM_PATH="yes"
11TEST_DIR=$(dirname $0)
12source $TEST_DIR/fw_lib.sh
13
14RUN_XZ="xz -C crc32 --lzma2=dict=2MiB"
15
16check_mods
17check_setup
18verify_reqs
19setup_tmp_file
20
21trap "test_finish" EXIT
22
23if [ "$HAS_FW_LOADER_USER_HELPER" = "yes" ]; then
24	# Turn down the timeout so failures don't take so long.
25	echo 1 >/sys/class/firmware/timeout
26fi
27
28if printf '\000' >"$DIR"/trigger_request 2> /dev/null; then
29	echo "$0: empty filename should not succeed" >&2
30	exit 1
31fi
32
33if [ ! -e "$DIR"/trigger_async_request ]; then
34	echo "$0: empty filename: async trigger not present, ignoring test" >&2
35	exit $ksft_skip
36else
37	if printf '\000' >"$DIR"/trigger_async_request 2> /dev/null; then
38		echo "$0: empty filename should not succeed (async)" >&2
39		exit 1
40	fi
41fi
42
43# Request a firmware that doesn't exist, it should fail.
44if echo -n "nope-$NAME" >"$DIR"/trigger_request 2> /dev/null; then
45	echo "$0: firmware shouldn't have loaded" >&2
46	exit 1
47fi
48if diff -q "$FW" /dev/test_firmware >/dev/null ; then
49	echo "$0: firmware was not expected to match" >&2
50	exit 1
51else
52	if [ "$HAS_FW_LOADER_USER_HELPER" = "yes" ]; then
53		echo "$0: timeout works"
54	fi
55fi
56
57# This should succeed via kernel load or will fail after 1 second after
58# being handed over to the user helper, which won't find the fw either.
59if ! echo -n "$NAME" >"$DIR"/trigger_request ; then
60	echo "$0: could not trigger request" >&2
61	exit 1
62fi
63
64# Verify the contents are what we expect.
65if ! diff -q "$FW" /dev/test_firmware >/dev/null ; then
66	echo "$0: firmware was not loaded" >&2
67	exit 1
68else
69	echo "$0: filesystem loading works"
70fi
71
72# Try the asynchronous version too
73if [ ! -e "$DIR"/trigger_async_request ]; then
74	echo "$0: firmware loading: async trigger not present, ignoring test" >&2
75	exit $ksft_skip
76else
77	if ! echo -n "$NAME" >"$DIR"/trigger_async_request ; then
78		echo "$0: could not trigger async request" >&2
79		exit 1
80	fi
81
82	# Verify the contents are what we expect.
83	if ! diff -q "$FW" /dev/test_firmware >/dev/null ; then
84		echo "$0: firmware was not loaded (async)" >&2
85		exit 1
86	else
87		echo "$0: async filesystem loading works"
88	fi
89fi
90
91# Try platform (EFI embedded fw) loading too
92if [ ! -e "$DIR"/trigger_request_platform ]; then
93	echo "$0: firmware loading: platform trigger not present, ignoring test" >&2
94else
95	if printf '\000' >"$DIR"/trigger_request_platform 2> /dev/null; then
96		echo "$0: empty filename should not succeed (platform)" >&2
97		exit 1
98	fi
99
100	# Note we echo a non-existing name, since files on the file-system
101	# are preferred over firmware embedded inside the platform's firmware
102	# The test adds a fake entry with the requested name to the platform's
103	# fw list, so the name does not matter as long as it does not exist
104	if ! echo -n "nope-$NAME" >"$DIR"/trigger_request_platform ; then
105		echo "$0: could not trigger request platform" >&2
106		exit 1
107	fi
108
109	# The test verifies itself that the loaded firmware contents matches
110	# the contents for the fake platform fw entry it added.
111	echo "$0: platform loading works"
112fi
113
114### Batched requests tests
115test_config_present()
116{
117	if [ ! -f $DIR/reset ]; then
118		echo "Configuration triggers not present, ignoring test"
119		exit $ksft_skip
120	fi
121}
122
123# Defaults :
124#
125# send_uevent: 1
126# sync_direct: 0
127# name: test-firmware.bin
128# num_requests: 4
129config_reset()
130{
131	echo 1 >  $DIR/reset
132}
133
134release_all_firmware()
135{
136	echo 1 >  $DIR/release_all_firmware
137}
138
139config_set_name()
140{
141	echo -n $1 >  $DIR/config_name
142}
143
144config_set_into_buf()
145{
146	echo 1 >  $DIR/config_into_buf
147}
148
149config_unset_into_buf()
150{
151	echo 0 >  $DIR/config_into_buf
152}
153
154config_set_buf_size()
155{
156	echo $1 >  $DIR/config_buf_size
157}
158
159config_set_file_offset()
160{
161	echo $1 >  $DIR/config_file_offset
162}
163
164config_set_partial()
165{
166	echo 1 >  $DIR/config_partial
167}
168
169config_unset_partial()
170{
171	echo 0 >  $DIR/config_partial
172}
173
174config_set_sync_direct()
175{
176	echo 1 >  $DIR/config_sync_direct
177}
178
179config_unset_sync_direct()
180{
181	echo 0 >  $DIR/config_sync_direct
182}
183
184config_set_uevent()
185{
186	echo 1 >  $DIR/config_send_uevent
187}
188
189config_unset_uevent()
190{
191	echo 0 >  $DIR/config_send_uevent
192}
193
194config_trigger_sync()
195{
196	echo -n 1 > $DIR/trigger_batched_requests 2>/dev/null
197}
198
199config_trigger_async()
200{
201	echo -n 1 > $DIR/trigger_batched_requests_async 2> /dev/null
202}
203
204config_set_read_fw_idx()
205{
206	echo -n $1 > $DIR/config_read_fw_idx 2> /dev/null
207}
208
209read_firmwares()
210{
211	if [ "$(cat $DIR/config_into_buf)" == "1" ]; then
212		fwfile="$FW_INTO_BUF"
213	else
214		fwfile="$FW"
215	fi
216	if [ "$1" = "xzonly" ]; then
217		fwfile="${fwfile}-orig"
218	fi
219	for i in $(seq 0 3); do
220		config_set_read_fw_idx $i
221		# Verify the contents are what we expect.
222		# -Z required for now -- check for yourself, md5sum
223		# on $FW and DIR/read_firmware will yield the same. Even
224		# cmp agrees, so something is off.
225		if ! diff -q -Z "$fwfile" $DIR/read_firmware 2>/dev/null ; then
226			echo "request #$i: firmware was not loaded" >&2
227			exit 1
228		fi
229	done
230}
231
232read_partial_firmwares()
233{
234	if [ "$(cat $DIR/config_into_buf)" == "1" ]; then
235		fwfile="${FW_INTO_BUF}"
236	else
237		fwfile="${FW}"
238	fi
239
240	if [ "$1" = "xzonly" ]; then
241		fwfile="${fwfile}-orig"
242	fi
243
244	# Strip fwfile down to match partial offset and length
245	partial_data="$(cat $fwfile)"
246	partial_data="${partial_data:$2:$3}"
247
248	for i in $(seq 0 3); do
249		config_set_read_fw_idx $i
250
251		read_firmware="$(cat $DIR/read_firmware)"
252
253		# Verify the contents are what we expect.
254		if [ $read_firmware != $partial_data ]; then
255			echo "request #$i: partial firmware was not loaded" >&2
256			exit 1
257		fi
258	done
259}
260
261read_firmwares_expect_nofile()
262{
263	for i in $(seq 0 3); do
264		config_set_read_fw_idx $i
265		# Ensures contents differ
266		if diff -q -Z "$FW" $DIR/read_firmware 2>/dev/null ; then
267			echo "request $i: file was not expected to match" >&2
268			exit 1
269		fi
270	done
271}
272
273test_batched_request_firmware_nofile()
274{
275	echo -n "Batched request_firmware() nofile try #$1: "
276	config_reset
277	config_set_name nope-test-firmware.bin
278	config_trigger_sync
279	read_firmwares_expect_nofile
280	release_all_firmware
281	echo "OK"
282}
283
284test_batched_request_firmware_into_buf_nofile()
285{
286	echo -n "Batched request_firmware_into_buf() nofile try #$1: "
287	config_reset
288	config_set_name nope-test-firmware.bin
289	config_set_into_buf
290	config_trigger_sync
291	read_firmwares_expect_nofile
292	release_all_firmware
293	echo "OK"
294}
295
296test_request_partial_firmware_into_buf_nofile()
297{
298	echo -n "Test request_partial_firmware_into_buf() off=$1 size=$2 nofile: "
299	config_reset
300	config_set_name nope-test-firmware.bin
301	config_set_into_buf
302	config_set_partial
303	config_set_buf_size $2
304	config_set_file_offset $1
305	config_trigger_sync
306	read_firmwares_expect_nofile
307	release_all_firmware
308	echo "OK"
309}
310
311test_batched_request_firmware_direct_nofile()
312{
313	echo -n "Batched request_firmware_direct() nofile try #$1: "
314	config_reset
315	config_set_name nope-test-firmware.bin
316	config_set_sync_direct
317	config_trigger_sync
318	release_all_firmware
319	echo "OK"
320}
321
322test_request_firmware_nowait_uevent_nofile()
323{
324	echo -n "Batched request_firmware_nowait(uevent=true) nofile try #$1: "
325	config_reset
326	config_set_name nope-test-firmware.bin
327	config_trigger_async
328	release_all_firmware
329	echo "OK"
330}
331
332test_wait_and_cancel_custom_load()
333{
334	if [ "$HAS_FW_LOADER_USER_HELPER" != "yes" ]; then
335		return
336	fi
337	local timeout=10
338	name=$1
339	while [ ! -e "$DIR"/"$name"/loading ]; do
340		sleep 0.1
341		timeout=$(( $timeout - 1 ))
342		if [ "$timeout" -eq 0 ]; then
343			echo "firmware interface never appeared:" >&2
344			echo "$DIR/$name/loading" >&2
345			exit 1
346		fi
347	done
348	echo -1 >"$DIR"/"$name"/loading
349}
350
351test_request_firmware_nowait_custom_nofile()
352{
353	echo -n "Batched request_firmware_nowait(uevent=false) nofile try #$1: "
354	config_reset
355	config_unset_uevent
356	RANDOM_FILE_PATH=$(setup_random_file_fake)
357	RANDOM_FILE="$(basename $RANDOM_FILE_PATH)"
358	config_set_name $RANDOM_FILE
359	config_trigger_async &
360	test_wait_and_cancel_custom_load $RANDOM_FILE
361	wait
362	release_all_firmware
363	echo "OK"
364}
365
366test_batched_request_firmware()
367{
368	echo -n "Batched request_firmware() $2 try #$1: "
369	config_reset
370	config_trigger_sync
371	read_firmwares $2
372	release_all_firmware
373	echo "OK"
374}
375
376test_batched_request_firmware_into_buf()
377{
378	echo -n "Batched request_firmware_into_buf() $2 try #$1: "
379	config_reset
380	config_set_name $TEST_FIRMWARE_INTO_BUF_FILENAME
381	config_set_into_buf
382	config_trigger_sync
383	read_firmwares $2
384	release_all_firmware
385	echo "OK"
386}
387
388test_batched_request_firmware_direct()
389{
390	echo -n "Batched request_firmware_direct() $2 try #$1: "
391	config_reset
392	config_set_sync_direct
393	config_trigger_sync
394	release_all_firmware
395	echo "OK"
396}
397
398test_request_firmware_nowait_uevent()
399{
400	echo -n "Batched request_firmware_nowait(uevent=true) $2 try #$1: "
401	config_reset
402	config_trigger_async
403	release_all_firmware
404	echo "OK"
405}
406
407test_request_firmware_nowait_custom()
408{
409	echo -n "Batched request_firmware_nowait(uevent=false) $2 try #$1: "
410	config_reset
411	config_unset_uevent
412	RANDOM_FILE_PATH=$(setup_random_file)
413	RANDOM_FILE="$(basename $RANDOM_FILE_PATH)"
414	if [ "$2" = "both" ]; then
415		$RUN_XZ -k $RANDOM_FILE_PATH
416	elif [ "$2" = "xzonly" ]; then
417		$RUN_XZ $RANDOM_FILE_PATH
418	fi
419	config_set_name $RANDOM_FILE
420	config_trigger_async
421	release_all_firmware
422	echo "OK"
423}
424
425test_request_partial_firmware_into_buf()
426{
427	echo -n "Test request_partial_firmware_into_buf() off=$1 size=$2: "
428	config_reset
429	config_set_name $TEST_FIRMWARE_INTO_BUF_FILENAME
430	config_set_into_buf
431	config_set_partial
432	config_set_buf_size $2
433	config_set_file_offset $1
434	config_trigger_sync
435	read_partial_firmwares normal $1 $2
436	release_all_firmware
437	echo "OK"
438}
439
440# Only continue if batched request triggers are present on the
441# test-firmware driver
442test_config_present
443
444# test with the file present
445echo
446echo "Testing with the file present..."
447for i in $(seq 1 5); do
448	test_batched_request_firmware $i normal
449done
450
451for i in $(seq 1 5); do
452	test_batched_request_firmware_into_buf $i normal
453done
454
455for i in $(seq 1 5); do
456	test_batched_request_firmware_direct $i normal
457done
458
459for i in $(seq 1 5); do
460	test_request_firmware_nowait_uevent $i normal
461done
462
463for i in $(seq 1 5); do
464	test_request_firmware_nowait_custom $i normal
465done
466
467# Partial loads cannot use fallback, so do not repeat tests.
468test_request_partial_firmware_into_buf 0 10
469test_request_partial_firmware_into_buf 0 5
470test_request_partial_firmware_into_buf 1 6
471test_request_partial_firmware_into_buf 2 10
472
473# Test for file not found, errors are expected, the failure would be
474# a hung task, which would require a hard reset.
475echo
476echo "Testing with the file missing..."
477for i in $(seq 1 5); do
478	test_batched_request_firmware_nofile $i
479done
480
481for i in $(seq 1 5); do
482	test_batched_request_firmware_into_buf_nofile $i
483done
484
485for i in $(seq 1 5); do
486	test_batched_request_firmware_direct_nofile $i
487done
488
489for i in $(seq 1 5); do
490	test_request_firmware_nowait_uevent_nofile $i
491done
492
493for i in $(seq 1 5); do
494	test_request_firmware_nowait_custom_nofile $i
495done
496
497# Partial loads cannot use fallback, so do not repeat tests.
498test_request_partial_firmware_into_buf_nofile 0 10
499test_request_partial_firmware_into_buf_nofile 0 5
500test_request_partial_firmware_into_buf_nofile 1 6
501test_request_partial_firmware_into_buf_nofile 2 10
502
503test "$HAS_FW_LOADER_COMPRESS" != "yes" && exit 0
504
505# test with both files present
506$RUN_XZ -k $FW
507config_set_name $NAME
508echo
509echo "Testing with both plain and xz files present..."
510for i in $(seq 1 5); do
511	test_batched_request_firmware $i both
512done
513
514for i in $(seq 1 5); do
515	test_batched_request_firmware_into_buf $i both
516done
517
518for i in $(seq 1 5); do
519	test_batched_request_firmware_direct $i both
520done
521
522for i in $(seq 1 5); do
523	test_request_firmware_nowait_uevent $i both
524done
525
526for i in $(seq 1 5); do
527	test_request_firmware_nowait_custom $i both
528done
529
530# test with only xz file present
531mv "$FW" "${FW}-orig"
532echo
533echo "Testing with only xz file present..."
534for i in $(seq 1 5); do
535	test_batched_request_firmware $i xzonly
536done
537
538for i in $(seq 1 5); do
539	test_batched_request_firmware_into_buf $i xzonly
540done
541
542for i in $(seq 1 5); do
543	test_batched_request_firmware_direct $i xzonly
544done
545
546for i in $(seq 1 5); do
547	test_request_firmware_nowait_uevent $i xzonly
548done
549
550for i in $(seq 1 5); do
551	test_request_firmware_nowait_custom $i xzonly
552done
553
554exit 0
555