GNU Linux-libre 4.14.266-gnu1
[releases.git] / tools / testing / selftests / firmware / fw_fallback.sh
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.
7 set -e
8
9 modprobe test_firmware
10
11 DIR=/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.
16 HAS_FW_LOADER_USER_HELPER=$(if [ -d /sys/class/firmware/ ]; then echo yes; else echo no; fi)
17
18 if [ "$HAS_FW_LOADER_USER_HELPER" = "yes" ]; then
19        OLD_TIMEOUT=$(cat /sys/class/firmware/timeout)
20 else
21         echo "usermode helper disabled so ignoring test"
22         exit 0
23 fi
24
25 FWPATH=$(mktemp -d)
26 FW="$FWPATH/test-firmware.bin"
27
28 test_finish()
29 {
30         echo "$OLD_TIMEOUT" >/sys/class/firmware/timeout
31         rm -f "$FW"
32         rmdir "$FWPATH"
33 }
34
35 load_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
62 load_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
87 load_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
114 load_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
138 load_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
160 trap "test_finish" EXIT
161
162 # This is an unlikely real-world firmware content. :)
163 echo "ABCD0123" >"$FW"
164 NAME=$(basename "$FW")
165
166 DEVPATH="$DIR"/"nope-$NAME"/loading
167
168 # Test failure when doing nothing (timeout works).
169 echo -n 2 >/sys/class/firmware/timeout
170 echo -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.
174 sleep 1
175 if [ ! -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
191 fi
192
193 if diff -q "$FW" /dev/test_firmware >/dev/null ; then
194         echo "$0: firmware was not expected to match" >&2
195         exit 1
196 else
197         echo "$0: timeout works"
198 fi
199
200 # Put timeout high enough for us to do work but not so long that failures
201 # slow down this test too much.
202 echo 4 >/sys/class/firmware/timeout
203
204 # Load this script instead of the desired firmware.
205 load_fw "$NAME" "$0"
206 if diff -q "$FW" /dev/test_firmware >/dev/null ; then
207         echo "$0: firmware was not expected to match" >&2
208         exit 1
209 else
210         echo "$0: firmware comparison works"
211 fi
212
213 # Do a proper load, which should work correctly.
214 load_fw "$NAME" "$FW"
215 if ! diff -q "$FW" /dev/test_firmware >/dev/null ; then
216         echo "$0: firmware was not loaded" >&2
217         exit 1
218 else
219         echo "$0: fallback mechanism works"
220 fi
221
222 load_fw_cancel "nope-$NAME" "$FW"
223 if diff -q "$FW" /dev/test_firmware >/dev/null ; then
224         echo "$0: firmware was expected to be cancelled" >&2
225         exit 1
226 else
227         echo "$0: cancelling fallback mechanism works"
228 fi
229
230 load_fw_custom "$NAME" "$FW"
231 if ! diff -q "$FW" /dev/test_firmware >/dev/null ; then
232         echo "$0: firmware was not loaded" >&2
233         exit 1
234 else
235         echo "$0: custom fallback loading mechanism works"
236 fi
237
238 load_fw_custom_cancel "nope-$NAME" "$FW"
239 if diff -q "$FW" /dev/test_firmware >/dev/null ; then
240         echo "$0: firmware was expected to be cancelled" >&2
241         exit 1
242 else
243         echo "$0: cancelling custom fallback mechanism works"
244 fi
245
246 set +e
247 load_fw_fallback_with_child "nope-signal-$NAME" "$FW"
248 if [ "$?" -eq 0 ]; then
249         echo "$0: SIGCHLD on sync ignored as expected" >&2
250 else
251         echo "$0: error - sync firmware request cancelled due to SIGCHLD" >&2
252         exit 1
253 fi
254 set -e
255
256 exit 0