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