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 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 114load_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 138load_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 160trap "test_finish" EXIT 161 162# This is an unlikely real-world firmware content. :) 163echo "ABCD0123" >"$FW" 164NAME=$(basename "$FW") 165 166DEVPATH="$DIR"/"nope-$NAME"/loading 167 168# Test failure when doing nothing (timeout works). 169echo -n 2 >/sys/class/firmware/timeout 170echo -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. 174sleep 1 175if [ ! -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 191fi 192 193if diff -q "$FW" /dev/test_firmware >/dev/null ; then 194 echo "$0: firmware was not expected to match" >&2 195 exit 1 196else 197 echo "$0: timeout works" 198fi 199 200# Put timeout high enough for us to do work but not so long that failures 201# slow down this test too much. 202echo 4 >/sys/class/firmware/timeout 203 204# Load this script instead of the desired firmware. 205load_fw "$NAME" "$0" 206if diff -q "$FW" /dev/test_firmware >/dev/null ; then 207 echo "$0: firmware was not expected to match" >&2 208 exit 1 209else 210 echo "$0: firmware comparison works" 211fi 212 213# Do a proper load, which should work correctly. 214load_fw "$NAME" "$FW" 215if ! diff -q "$FW" /dev/test_firmware >/dev/null ; then 216 echo "$0: firmware was not loaded" >&2 217 exit 1 218else 219 echo "$0: fallback mechanism works" 220fi 221 222load_fw_cancel "nope-$NAME" "$FW" 223if diff -q "$FW" /dev/test_firmware >/dev/null ; then 224 echo "$0: firmware was expected to be cancelled" >&2 225 exit 1 226else 227 echo "$0: cancelling fallback mechanism works" 228fi 229 230load_fw_custom "$NAME" "$FW" 231if ! diff -q "$FW" /dev/test_firmware >/dev/null ; then 232 echo "$0: firmware was not loaded" >&2 233 exit 1 234else 235 echo "$0: custom fallback loading mechanism works" 236fi 237 238load_fw_custom_cancel "nope-$NAME" "$FW" 239if diff -q "$FW" /dev/test_firmware >/dev/null ; then 240 echo "$0: firmware was expected to be cancelled" >&2 241 exit 1 242else 243 echo "$0: cancelling custom fallback mechanism works" 244fi 245 246set +e 247load_fw_fallback_with_child "nope-signal-$NAME" "$FW" 248if [ "$?" -eq 0 ]; then 249 echo "$0: SIGCHLD on sync ignored as expected" >&2 250else 251 echo "$0: error - sync firmware request cancelled due to SIGCHLD" >&2 252 exit 1 253fi 254set -e 255 256exit 0 257