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