• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
178DEVPATH="$DIR"/"nope-$NAME"/loading
179
180# Test failure when doing nothing (timeout works).
181echo -n 2 >/sys/class/firmware/timeout
182echo -n "nope-$NAME" >"$DIR"/trigger_request 2>/dev/null &
183
184# Give the kernel some time to load the loading file, must be less
185# than the timeout above.
186sleep 1
187if [ ! -f $DEVPATH ]; then
188	echo "$0: fallback mechanism immediately cancelled"
189	echo ""
190	echo "The file never appeared: $DEVPATH"
191	echo ""
192	echo "This might be a distribution udev rule setup by your distribution"
193	echo "to immediately cancel all fallback requests, this must be"
194	echo "removed before running these tests. To confirm look for"
195	echo "a firmware rule like /lib/udev/rules.d/50-firmware.rules"
196	echo "and see if you have something like this:"
197	echo ""
198	echo "SUBSYSTEM==\"firmware\", ACTION==\"add\", ATTR{loading}=\"-1\""
199	echo ""
200	echo "If you do remove this file or comment out this line before"
201	echo "proceeding with these tests."
202	exit 1
203fi
204
205if diff -q "$FW" /dev/test_firmware >/dev/null ; then
206	echo "$0: firmware was not expected to match" >&2
207	exit 1
208else
209	echo "$0: timeout works"
210fi
211
212# Put timeout high enough for us to do work but not so long that failures
213# slow down this test too much.
214echo 4 >/sys/class/firmware/timeout
215
216# Load this script instead of the desired firmware.
217load_fw "$NAME" "$0"
218if diff -q "$FW" /dev/test_firmware >/dev/null ; then
219	echo "$0: firmware was not expected to match" >&2
220	exit 1
221else
222	echo "$0: firmware comparison works"
223fi
224
225# Do a proper load, which should work correctly.
226load_fw "$NAME" "$FW"
227if ! diff -q "$FW" /dev/test_firmware >/dev/null ; then
228	echo "$0: firmware was not loaded" >&2
229	exit 1
230else
231	echo "$0: fallback mechanism works"
232fi
233
234load_fw_cancel "nope-$NAME" "$FW"
235if diff -q "$FW" /dev/test_firmware >/dev/null ; then
236	echo "$0: firmware was expected to be cancelled" >&2
237	exit 1
238else
239	echo "$0: cancelling fallback mechanism works"
240fi
241
242if load_fw_custom "$NAME" "$FW" ; then
243	if ! diff -q "$FW" /dev/test_firmware >/dev/null ; then
244		echo "$0: firmware was not loaded" >&2
245		exit 1
246	else
247		echo "$0: custom fallback loading mechanism works"
248	fi
249fi
250
251if load_fw_custom_cancel "nope-$NAME" "$FW" ; then
252	if diff -q "$FW" /dev/test_firmware >/dev/null ; then
253		echo "$0: firmware was expected to be cancelled" >&2
254		exit 1
255	else
256		echo "$0: cancelling custom fallback mechanism works"
257	fi
258fi
259
260set +e
261load_fw_fallback_with_child "nope-signal-$NAME" "$FW"
262if [ "$?" -eq 0 ]; then
263	echo "$0: SIGCHLD on sync ignored as expected" >&2
264else
265	echo "$0: error - sync firmware request cancelled due to SIGCHLD" >&2
266	exit 1
267fi
268set -e
269
270exit 0
271