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