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 178DEVPATH="$DIR"/"nope-$NAME"/loading 179 180# Test failure when doing nothing (timeout works). 181echo -n 2 >/sys/class/firmware/timeout 182echo -n "nope-$NAME" >"$DIR"/trigger_request 2>/dev/null & 183 184# Give the kernel some time to load the loading file, must be less 185# than the timeout above. 186sleep 1 187if [ ! -f $DEVPATH ]; then 188 echo "$0: fallback mechanism immediately cancelled" 189 echo "" 190 echo "The file never appeared: $DEVPATH" 191 echo "" 192 echo "This might be a distribution udev rule setup by your distribution" 193 echo "to immediately cancel all fallback requests, this must be" 194 echo "removed before running these tests. To confirm look for" 195 echo "a firmware rule like /lib/udev/rules.d/50-firmware.rules" 196 echo "and see if you have something like this:" 197 echo "" 198 echo "SUBSYSTEM==\"firmware\", ACTION==\"add\", ATTR{loading}=\"-1\"" 199 echo "" 200 echo "If you do remove this file or comment out this line before" 201 echo "proceeding with these tests." 202 exit 1 203fi 204 205if diff -q "$FW" /dev/test_firmware >/dev/null ; then 206 echo "$0: firmware was not expected to match" >&2 207 exit 1 208else 209 echo "$0: timeout works" 210fi 211 212# Put timeout high enough for us to do work but not so long that failures 213# slow down this test too much. 214echo 4 >/sys/class/firmware/timeout 215 216# Load this script instead of the desired firmware. 217load_fw "$NAME" "$0" 218if diff -q "$FW" /dev/test_firmware >/dev/null ; then 219 echo "$0: firmware was not expected to match" >&2 220 exit 1 221else 222 echo "$0: firmware comparison works" 223fi 224 225# Do a proper load, which should work correctly. 226load_fw "$NAME" "$FW" 227if ! diff -q "$FW" /dev/test_firmware >/dev/null ; then 228 echo "$0: firmware was not loaded" >&2 229 exit 1 230else 231 echo "$0: fallback mechanism works" 232fi 233 234load_fw_cancel "nope-$NAME" "$FW" 235if diff -q "$FW" /dev/test_firmware >/dev/null ; then 236 echo "$0: firmware was expected to be cancelled" >&2 237 exit 1 238else 239 echo "$0: cancelling fallback mechanism works" 240fi 241 242if load_fw_custom "$NAME" "$FW" ; then 243 if ! diff -q "$FW" /dev/test_firmware >/dev/null ; then 244 echo "$0: firmware was not loaded" >&2 245 exit 1 246 else 247 echo "$0: custom fallback loading mechanism works" 248 fi 249fi 250 251if load_fw_custom_cancel "nope-$NAME" "$FW" ; then 252 if diff -q "$FW" /dev/test_firmware >/dev/null ; then 253 echo "$0: firmware was expected to be cancelled" >&2 254 exit 1 255 else 256 echo "$0: cancelling custom fallback mechanism works" 257 fi 258fi 259 260set +e 261load_fw_fallback_with_child "nope-signal-$NAME" "$FW" 262if [ "$?" -eq 0 ]; then 263 echo "$0: SIGCHLD on sync ignored as expected" >&2 264else 265 echo "$0: error - sync firmware request cancelled due to SIGCHLD" >&2 266 exit 1 267fi 268set -e 269 270exit 0 271