xref: /linux/tools/testing/selftests/firmware/fw_fallback.sh (revision 33dea5aae0320345af26ae9aba0894a930e0d4ec)
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	local name="$1"
90	local file="$2"
91
92	echo -n "$name" >"$DIR"/trigger_custom_fallback 2>/dev/null &
93
94	# Give kernel a chance to react.
95	local timeout=10
96	while [ ! -e "$DIR"/"$name"/loading ]; do
97		sleep 0.1
98		timeout=$(( $timeout - 1 ))
99		if [ "$timeout" -eq 0 ]; then
100			echo "$0: firmware interface never appeared" >&2
101			exit 1
102		fi
103	done
104
105	echo 1 >"$DIR"/"$name"/loading
106	cat "$file" >"$DIR"/"$name"/data
107	echo 0 >"$DIR"/"$name"/loading
108
109	# Wait for request to finish.
110	wait
111}
112
113
114load_fw_custom_cancel()
115{
116	local name="$1"
117	local file="$2"
118
119	echo -n "$name" >"$DIR"/trigger_custom_fallback 2>/dev/null &
120
121	# Give kernel a chance to react.
122	local timeout=10
123	while [ ! -e "$DIR"/"$name"/loading ]; do
124		sleep 0.1
125		timeout=$(( $timeout - 1 ))
126		if [ "$timeout" -eq 0 ]; then
127			echo "$0: firmware interface never appeared" >&2
128			exit 1
129		fi
130	done
131
132	echo -1 >"$DIR"/"$name"/loading
133
134	# Wait for request to finish.
135	wait
136}
137
138load_fw_fallback_with_child()
139{
140	local name="$1"
141	local file="$2"
142
143	# This is the value already set but we want to be explicit
144	echo 4 >/sys/class/firmware/timeout
145
146	sleep 1 &
147	SECONDS_BEFORE=$(date +%s)
148	echo -n "$name" >"$DIR"/trigger_request 2>/dev/null
149	SECONDS_AFTER=$(date +%s)
150	SECONDS_DELTA=$(($SECONDS_AFTER - $SECONDS_BEFORE))
151	if [ "$SECONDS_DELTA" -lt 4 ]; then
152		RET=1
153	else
154		RET=0
155	fi
156	wait
157	return $RET
158}
159
160trap "test_finish" EXIT
161
162# This is an unlikely real-world firmware content. :)
163echo "ABCD0123" >"$FW"
164NAME=$(basename "$FW")
165
166DEVPATH="$DIR"/"nope-$NAME"/loading
167
168# Test failure when doing nothing (timeout works).
169echo -n 2 >/sys/class/firmware/timeout
170echo -n "nope-$NAME" >"$DIR"/trigger_request 2>/dev/null &
171
172# Give the kernel some time to load the loading file, must be less
173# than the timeout above.
174sleep 1
175if [ ! -f $DEVPATH ]; then
176	echo "$0: fallback mechanism immediately cancelled"
177	echo ""
178	echo "The file never appeared: $DEVPATH"
179	echo ""
180	echo "This might be a distribution udev rule setup by your distribution"
181	echo "to immediately cancel all fallback requests, this must be"
182	echo "removed before running these tests. To confirm look for"
183	echo "a firmware rule like /lib/udev/rules.d/50-firmware.rules"
184	echo "and see if you have something like this:"
185	echo ""
186	echo "SUBSYSTEM==\"firmware\", ACTION==\"add\", ATTR{loading}=\"-1\""
187	echo ""
188	echo "If you do remove this file or comment out this line before"
189	echo "proceeding with these tests."
190	exit 1
191fi
192
193if diff -q "$FW" /dev/test_firmware >/dev/null ; then
194	echo "$0: firmware was not expected to match" >&2
195	exit 1
196else
197	echo "$0: timeout works"
198fi
199
200# Put timeout high enough for us to do work but not so long that failures
201# slow down this test too much.
202echo 4 >/sys/class/firmware/timeout
203
204# Load this script instead of the desired firmware.
205load_fw "$NAME" "$0"
206if diff -q "$FW" /dev/test_firmware >/dev/null ; then
207	echo "$0: firmware was not expected to match" >&2
208	exit 1
209else
210	echo "$0: firmware comparison works"
211fi
212
213# Do a proper load, which should work correctly.
214load_fw "$NAME" "$FW"
215if ! diff -q "$FW" /dev/test_firmware >/dev/null ; then
216	echo "$0: firmware was not loaded" >&2
217	exit 1
218else
219	echo "$0: fallback mechanism works"
220fi
221
222load_fw_cancel "nope-$NAME" "$FW"
223if diff -q "$FW" /dev/test_firmware >/dev/null ; then
224	echo "$0: firmware was expected to be cancelled" >&2
225	exit 1
226else
227	echo "$0: cancelling fallback mechanism works"
228fi
229
230load_fw_custom "$NAME" "$FW"
231if ! diff -q "$FW" /dev/test_firmware >/dev/null ; then
232	echo "$0: firmware was not loaded" >&2
233	exit 1
234else
235	echo "$0: custom fallback loading mechanism works"
236fi
237
238load_fw_custom_cancel "nope-$NAME" "$FW"
239if diff -q "$FW" /dev/test_firmware >/dev/null ; then
240	echo "$0: firmware was expected to be cancelled" >&2
241	exit 1
242else
243	echo "$0: cancelling custom fallback mechanism works"
244fi
245
246set +e
247load_fw_fallback_with_child "nope-signal-$NAME" "$FW"
248if [ "$?" -eq 0 ]; then
249	echo "$0: SIGCHLD on sync ignored as expected" >&2
250else
251	echo "$0: error - sync firmware request cancelled due to SIGCHLD" >&2
252	exit 1
253fi
254set -e
255
256exit 0
257