1#!/bin/sh
2# This validates that the kernel will fall back to using the fallback mechanism
3# to load firmware it can't find on disk itself. We must request a firmware
4# that the kernel won't find, and any installed helper (e.g. udev) also
5# won't find so that we can do the load ourself manually.
6set -e
7
8modprobe test_firmware
9
10DIR=/sys/devices/virtual/misc/test_firmware
11
12# CONFIG_FW_LOADER_USER_HELPER has a sysfs class under /sys/class/firmware/
13# These days no one enables CONFIG_FW_LOADER_USER_HELPER so check for that
14# as an indicator for CONFIG_FW_LOADER_USER_HELPER.
15HAS_FW_LOADER_USER_HELPER=$(if [ -d /sys/class/firmware/ ]; then echo yes; else echo no; fi)
16
17if [ "$HAS_FW_LOADER_USER_HELPER" = "yes" ]; then
18       OLD_TIMEOUT=$(cat /sys/class/firmware/timeout)
19else
20	echo "usermode helper disabled so ignoring test"
21	exit 0
22fi
23
24FWPATH=$(mktemp -d)
25FW="$FWPATH/test-firmware.bin"
26
27test_finish()
28{
29	echo "$OLD_TIMEOUT" >/sys/class/firmware/timeout
30	rm -f "$FW"
31	rmdir "$FWPATH"
32}
33
34load_fw()
35{
36	local name="$1"
37	local file="$2"
38
39	# This will block until our load (below) has finished.
40	echo -n "$name" >"$DIR"/trigger_request &
41
42	# Give kernel a chance to react.
43	local timeout=10
44	while [ ! -e "$DIR"/"$name"/loading ]; do
45		sleep 0.1
46		timeout=$(( $timeout - 1 ))
47		if [ "$timeout" -eq 0 ]; then
48			echo "$0: firmware interface never appeared" >&2
49			exit 1
50		fi
51	done
52
53	echo 1 >"$DIR"/"$name"/loading
54	cat "$file" >"$DIR"/"$name"/data
55	echo 0 >"$DIR"/"$name"/loading
56
57	# Wait for request to finish.
58	wait
59}
60
61load_fw_cancel()
62{
63	local name="$1"
64	local file="$2"
65
66	# This will block until our load (below) has finished.
67	echo -n "$name" >"$DIR"/trigger_request 2>/dev/null &
68
69	# Give kernel a chance to react.
70	local timeout=10
71	while [ ! -e "$DIR"/"$name"/loading ]; do
72		sleep 0.1
73		timeout=$(( $timeout - 1 ))
74		if [ "$timeout" -eq 0 ]; then
75			echo "$0: firmware interface never appeared" >&2
76			exit 1
77		fi
78	done
79
80	echo -1 >"$DIR"/"$name"/loading
81
82	# Wait for request to finish.
83	wait
84}
85
86load_fw_custom()
87{
88	local name="$1"
89	local file="$2"
90
91	echo -n "$name" >"$DIR"/trigger_custom_fallback 2>/dev/null &
92
93	# Give kernel a chance to react.
94	local timeout=10
95	while [ ! -e "$DIR"/"$name"/loading ]; do
96		sleep 0.1
97		timeout=$(( $timeout - 1 ))
98		if [ "$timeout" -eq 0 ]; then
99			echo "$0: firmware interface never appeared" >&2
100			exit 1
101		fi
102	done
103
104	echo 1 >"$DIR"/"$name"/loading
105	cat "$file" >"$DIR"/"$name"/data
106	echo 0 >"$DIR"/"$name"/loading
107
108	# Wait for request to finish.
109	wait
110}
111
112
113load_fw_custom_cancel()
114{
115	local name="$1"
116	local file="$2"
117
118	echo -n "$name" >"$DIR"/trigger_custom_fallback 2>/dev/null &
119
120	# Give kernel a chance to react.
121	local timeout=10
122	while [ ! -e "$DIR"/"$name"/loading ]; do
123		sleep 0.1
124		timeout=$(( $timeout - 1 ))
125		if [ "$timeout" -eq 0 ]; then
126			echo "$0: firmware interface never appeared" >&2
127			exit 1
128		fi
129	done
130
131	echo -1 >"$DIR"/"$name"/loading
132
133	# Wait for request to finish.
134	wait
135}
136
137load_fw_fallback_with_child()
138{
139	local name="$1"
140	local file="$2"
141
142	# This is the value already set but we want to be explicit
143	echo 4 >/sys/class/firmware/timeout
144
145	sleep 1 &
146	SECONDS_BEFORE=$(date +%s)
147	echo -n "$name" >"$DIR"/trigger_request 2>/dev/null
148	SECONDS_AFTER=$(date +%s)
149	SECONDS_DELTA=$(($SECONDS_AFTER - $SECONDS_BEFORE))
150	if [ "$SECONDS_DELTA" -lt 4 ]; then
151		RET=1
152	else
153		RET=0
154	fi
155	wait
156	return $RET
157}
158
159trap "test_finish" EXIT
160
161# This is an unlikely real-world firmware content. :)
162echo "ABCD0123" >"$FW"
163NAME=$(basename "$FW")
164
165DEVPATH="$DIR"/"nope-$NAME"/loading
166
167# Test failure when doing nothing (timeout works).
168echo -n 2 >/sys/class/firmware/timeout
169echo -n "nope-$NAME" >"$DIR"/trigger_request 2>/dev/null &
170
171# Give the kernel some time to load the loading file, must be less
172# than the timeout above.
173sleep 1
174if [ ! -f $DEVPATH ]; then
175	echo "$0: fallback mechanism immediately cancelled"
176	echo ""
177	echo "The file never appeared: $DEVPATH"
178	echo ""
179	echo "This might be a distribution udev rule setup by your distribution"
180	echo "to immediately cancel all fallback requests, this must be"
181	echo "removed before running these tests. To confirm look for"
182	echo "a firmware rule like /lib/udev/rules.d/50-firmware.rules"
183	echo "and see if you have something like this:"
184	echo ""
185	echo "SUBSYSTEM==\"firmware\", ACTION==\"add\", ATTR{loading}=\"-1\""
186	echo ""
187	echo "If you do remove this file or comment out this line before"
188	echo "proceeding with these tests."
189	exit 1
190fi
191
192if diff -q "$FW" /dev/test_firmware >/dev/null ; then
193	echo "$0: firmware was not expected to match" >&2
194	exit 1
195else
196	echo "$0: timeout works"
197fi
198
199# Put timeout high enough for us to do work but not so long that failures
200# slow down this test too much.
201echo 4 >/sys/class/firmware/timeout
202
203# Load this script instead of the desired firmware.
204load_fw "$NAME" "$0"
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: firmware comparison works"
210fi
211
212# Do a proper load, which should work correctly.
213load_fw "$NAME" "$FW"
214if ! diff -q "$FW" /dev/test_firmware >/dev/null ; then
215	echo "$0: firmware was not loaded" >&2
216	exit 1
217else
218	echo "$0: fallback mechanism works"
219fi
220
221load_fw_cancel "nope-$NAME" "$FW"
222if diff -q "$FW" /dev/test_firmware >/dev/null ; then
223	echo "$0: firmware was expected to be cancelled" >&2
224	exit 1
225else
226	echo "$0: cancelling fallback mechanism works"
227fi
228
229load_fw_custom "$NAME" "$FW"
230if ! diff -q "$FW" /dev/test_firmware >/dev/null ; then
231	echo "$0: firmware was not loaded" >&2
232	exit 1
233else
234	echo "$0: custom fallback loading mechanism works"
235fi
236
237load_fw_custom_cancel "nope-$NAME" "$FW"
238if diff -q "$FW" /dev/test_firmware >/dev/null ; then
239	echo "$0: firmware was expected to be cancelled" >&2
240	exit 1
241else
242	echo "$0: cancelling custom fallback mechanism works"
243fi
244
245set +e
246load_fw_fallback_with_child "nope-signal-$NAME" "$FW"
247if [ "$?" -eq 0 ]; then
248	echo "$0: SIGCHLD on sync ignored as expected" >&2
249else
250	echo "$0: error - sync firmware request cancelled due to SIGCHLD" >&2
251	exit 1
252fi
253set -e
254
255exit 0
256