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