Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,2 @@
# SPDX-License-Identifier: GPL-2.0-only
fw_namespace
@@ -0,0 +1,10 @@
# SPDX-License-Identifier: GPL-2.0-only
# Makefile for firmware loading selftests
CFLAGS = -Wall \
-O2
TEST_PROGS := fw_run_tests.sh
TEST_FILES := fw_fallback.sh fw_filesystem.sh fw_upload.sh fw_lib.sh
TEST_GEN_FILES := fw_namespace
include ../lib.mk
@@ -0,0 +1,6 @@
CONFIG_TEST_FIRMWARE=y
CONFIG_FW_LOADER=y
CONFIG_FW_LOADER_USER_HELPER=y
CONFIG_IKCONFIG=y
CONFIG_IKCONFIG_PROC=y
CONFIG_FW_UPLOAD=y
+283
View File
@@ -0,0 +1,283 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
# This validates that the kernel will fall back to using the fallback mechanism
# to load firmware it can't find on disk itself. We must request a firmware
# that the kernel won't find, and any installed helper (e.g. udev) also
# won't find so that we can do the load ourself manually.
set -e
TEST_REQS_FW_SYSFS_FALLBACK="yes"
TEST_REQS_FW_SET_CUSTOM_PATH="no"
TEST_DIR=$(dirname $0)
source $TEST_DIR/fw_lib.sh
check_mods
check_setup
verify_reqs
setup_tmp_file
trap "test_finish" EXIT
load_fw()
{
local name="$1"
local file="$2"
# This will block until our load (below) has finished.
echo -n "$name" >"$DIR"/trigger_request &
# Give kernel a chance to react.
local timeout=10
while [ ! -e "$DIR"/"$name"/loading ]; do
sleep 0.1
timeout=$(( $timeout - 1 ))
if [ "$timeout" -eq 0 ]; then
echo "$0: firmware interface never appeared" >&2
exit 1
fi
done
echo 1 >"$DIR"/"$name"/loading
cat "$file" >"$DIR"/"$name"/data
echo 0 >"$DIR"/"$name"/loading
# Wait for request to finish.
wait
}
load_fw_cancel()
{
local name="$1"
local file="$2"
# This will block until our load (below) has finished.
echo -n "$name" >"$DIR"/trigger_request 2>/dev/null &
# Give kernel a chance to react.
local timeout=10
while [ ! -e "$DIR"/"$name"/loading ]; do
sleep 0.1
timeout=$(( $timeout - 1 ))
if [ "$timeout" -eq 0 ]; then
echo "$0: firmware interface never appeared" >&2
exit 1
fi
done
echo -1 >"$DIR"/"$name"/loading
# Wait for request to finish.
wait
}
load_fw_custom()
{
if [ ! -e "$DIR"/trigger_custom_fallback ]; then
echo "$0: custom fallback trigger not present, ignoring test" >&2
exit $ksft_skip
fi
local name="$1"
local file="$2"
echo -n "$name" >"$DIR"/trigger_custom_fallback 2>/dev/null &
# Give kernel a chance to react.
local timeout=10
while [ ! -e "$DIR"/"$name"/loading ]; do
sleep 0.1
timeout=$(( $timeout - 1 ))
if [ "$timeout" -eq 0 ]; then
echo "$0: firmware interface never appeared" >&2
exit 1
fi
done
echo 1 >"$DIR"/"$name"/loading
cat "$file" >"$DIR"/"$name"/data
echo 0 >"$DIR"/"$name"/loading
# Wait for request to finish.
wait
return 0
}
load_fw_custom_cancel()
{
if [ ! -e "$DIR"/trigger_custom_fallback ]; then
echo "$0: canceling custom fallback trigger not present, ignoring test" >&2
exit $ksft_skip
fi
local name="$1"
local file="$2"
echo -n "$name" >"$DIR"/trigger_custom_fallback 2>/dev/null &
# Give kernel a chance to react.
local timeout=10
while [ ! -e "$DIR"/"$name"/loading ]; do
sleep 0.1
timeout=$(( $timeout - 1 ))
if [ "$timeout" -eq 0 ]; then
echo "$0: firmware interface never appeared" >&2
exit 1
fi
done
echo -1 >"$DIR"/"$name"/loading
# Wait for request to finish.
wait
return 0
}
load_fw_fallback_with_child()
{
local name="$1"
local file="$2"
# This is the value already set but we want to be explicit
echo 4 >/sys/class/firmware/timeout
sleep 1 &
SECONDS_BEFORE=$(date +%s)
echo -n "$name" >"$DIR"/trigger_request 2>/dev/null
SECONDS_AFTER=$(date +%s)
SECONDS_DELTA=$(($SECONDS_AFTER - $SECONDS_BEFORE))
if [ "$SECONDS_DELTA" -lt 4 ]; then
RET=1
else
RET=0
fi
wait
return $RET
}
test_syfs_timeout()
{
DEVPATH="$DIR"/"nope-$NAME"/loading
# Test failure when doing nothing (timeout works).
echo -n 2 >/sys/class/firmware/timeout
echo -n "nope-$NAME" >"$DIR"/trigger_request 2>/dev/null &
# Give the kernel some time to load the loading file, must be less
# than the timeout above.
sleep 1
if [ ! -f $DEVPATH ]; then
echo "$0: fallback mechanism immediately cancelled"
echo ""
echo "The file never appeared: $DEVPATH"
echo ""
echo "This might be a distribution udev rule setup by your distribution"
echo "to immediately cancel all fallback requests, this must be"
echo "removed before running these tests. To confirm look for"
echo "a firmware rule like /lib/udev/rules.d/50-firmware.rules"
echo "and see if you have something like this:"
echo ""
echo "SUBSYSTEM==\"firmware\", ACTION==\"add\", ATTR{loading}=\"-1\""
echo ""
echo "If you do remove this file or comment out this line before"
echo "proceeding with these tests."
exit 1
fi
if diff -q "$FW" /dev/test_firmware >/dev/null ; then
echo "$0: firmware was not expected to match" >&2
exit 1
else
echo "$0: timeout works"
fi
}
run_sysfs_main_tests()
{
test_syfs_timeout
# Put timeout high enough for us to do work but not so long that failures
# slow down this test too much.
echo 4 >/sys/class/firmware/timeout
# Load this script instead of the desired firmware.
load_fw "$NAME" "$0"
if diff -q "$FW" /dev/test_firmware >/dev/null ; then
echo "$0: firmware was not expected to match" >&2
exit 1
else
echo "$0: firmware comparison works"
fi
# Do a proper load, which should work correctly.
load_fw "$NAME" "$FW"
if ! diff -q "$FW" /dev/test_firmware >/dev/null ; then
echo "$0: firmware was not loaded" >&2
exit 1
else
echo "$0: fallback mechanism works"
fi
load_fw_cancel "nope-$NAME" "$FW"
if diff -q "$FW" /dev/test_firmware >/dev/null ; then
echo "$0: firmware was expected to be cancelled" >&2
exit 1
else
echo "$0: cancelling fallback mechanism works"
fi
set +e
load_fw_fallback_with_child "nope-signal-$NAME" "$FW"
if [ "$?" -eq 0 ]; then
echo "$0: SIGCHLD on sync ignored as expected" >&2
else
echo "$0: error - sync firmware request cancelled due to SIGCHLD" >&2
exit 1
fi
set -e
}
run_sysfs_custom_load_tests()
{
RANDOM_FILE_PATH=$(setup_random_file)
RANDOM_FILE="$(basename $RANDOM_FILE_PATH)"
if load_fw_custom "$RANDOM_FILE" "$RANDOM_FILE_PATH" ; then
if ! diff -q "$RANDOM_FILE_PATH" /dev/test_firmware >/dev/null ; then
echo "$0: firmware was not loaded" >&2
exit 1
else
echo "$0: custom fallback loading mechanism works"
fi
fi
RANDOM_FILE_PATH=$(setup_random_file)
RANDOM_FILE="$(basename $RANDOM_FILE_PATH)"
if load_fw_custom "$RANDOM_FILE" "$RANDOM_FILE_PATH" ; then
if ! diff -q "$RANDOM_FILE_PATH" /dev/test_firmware >/dev/null ; then
echo "$0: firmware was not loaded" >&2
exit 1
else
echo "$0: custom fallback loading mechanism works"
fi
fi
RANDOM_FILE_REAL="$RANDOM_FILE_PATH"
FAKE_RANDOM_FILE_PATH=$(setup_random_file_fake)
FAKE_RANDOM_FILE="$(basename $FAKE_RANDOM_FILE_PATH)"
if load_fw_custom_cancel "$FAKE_RANDOM_FILE" "$RANDOM_FILE_REAL" ; then
if diff -q "$RANDOM_FILE_PATH" /dev/test_firmware >/dev/null ; then
echo "$0: firmware was expected to be cancelled" >&2
exit 1
else
echo "$0: cancelling custom fallback mechanism works"
fi
fi
}
if [ "$HAS_FW_LOADER_USER_HELPER_FALLBACK" = "yes" ]; then
run_sysfs_main_tests
fi
run_sysfs_custom_load_tests
exit 0
@@ -0,0 +1,546 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
# This validates that the kernel will load firmware out of its list of
# firmware locations on disk. Since the user helper does similar work,
# we reset the custom load directory to a location the user helper doesn't
# know so we can be sure we're not accidentally testing the user helper.
set -e
TEST_REQS_FW_SYSFS_FALLBACK="no"
TEST_REQS_FW_SET_CUSTOM_PATH="yes"
TEST_DIR=$(dirname $0)
source $TEST_DIR/fw_lib.sh
RUN_XZ="xz -C crc32 --lzma2=dict=2MiB"
RUN_ZSTD="zstd -q"
check_mods
check_setup
verify_reqs
setup_tmp_file
trap "test_finish" EXIT
if [ "$HAS_FW_LOADER_USER_HELPER" = "yes" ]; then
# Turn down the timeout so failures don't take so long.
echo 1 >/sys/class/firmware/timeout
fi
if printf '\000' >"$DIR"/trigger_request 2> /dev/null; then
echo "$0: empty filename should not succeed" >&2
exit 1
fi
if [ ! -e "$DIR"/trigger_async_request ]; then
echo "$0: empty filename: async trigger not present, ignoring test" >&2
exit $ksft_skip
else
if printf '\000' >"$DIR"/trigger_async_request 2> /dev/null; then
echo "$0: empty filename should not succeed (async)" >&2
exit 1
fi
fi
# Request a firmware that doesn't exist, it should fail.
if echo -n "nope-$NAME" >"$DIR"/trigger_request 2> /dev/null; then
echo "$0: firmware shouldn't have loaded" >&2
exit 1
fi
if diff -q "$FW" /dev/test_firmware >/dev/null ; then
echo "$0: firmware was not expected to match" >&2
exit 1
else
if [ "$HAS_FW_LOADER_USER_HELPER" = "yes" ]; then
echo "$0: timeout works"
fi
fi
# This should succeed via kernel load or will fail after 1 second after
# being handed over to the user helper, which won't find the fw either.
if ! echo -n "$NAME" >"$DIR"/trigger_request ; then
echo "$0: could not trigger request" >&2
exit 1
fi
# Verify the contents are what we expect.
if ! diff -q "$FW" /dev/test_firmware >/dev/null ; then
echo "$0: firmware was not loaded" >&2
exit 1
else
echo "$0: filesystem loading works"
fi
# Try the asynchronous version too
if [ ! -e "$DIR"/trigger_async_request ]; then
echo "$0: firmware loading: async trigger not present, ignoring test" >&2
exit $ksft_skip
else
if ! echo -n "$NAME" >"$DIR"/trigger_async_request ; then
echo "$0: could not trigger async request" >&2
exit 1
fi
# Verify the contents are what we expect.
if ! diff -q "$FW" /dev/test_firmware >/dev/null ; then
echo "$0: firmware was not loaded (async)" >&2
exit 1
else
echo "$0: async filesystem loading works"
fi
fi
# Try platform (EFI embedded fw) loading too
if [ ! -e "$DIR"/trigger_request_platform ]; then
echo "$0: firmware loading: platform trigger not present, ignoring test" >&2
else
if printf '\000' >"$DIR"/trigger_request_platform 2> /dev/null; then
echo "$0: empty filename should not succeed (platform)" >&2
exit 1
fi
# Note we echo a non-existing name, since files on the file-system
# are preferred over firmware embedded inside the platform's firmware
# The test adds a fake entry with the requested name to the platform's
# fw list, so the name does not matter as long as it does not exist
if ! echo -n "nope-$NAME" >"$DIR"/trigger_request_platform ; then
echo "$0: could not trigger request platform" >&2
exit 1
fi
# The test verifies itself that the loaded firmware contents matches
# the contents for the fake platform fw entry it added.
echo "$0: platform loading works"
fi
### Batched requests tests
test_config_present()
{
if [ ! -f $DIR/reset ]; then
echo "Configuration triggers not present, ignoring test"
exit $ksft_skip
fi
}
# Defaults :
#
# send_uevent: 1
# sync_direct: 0
# name: test-firmware.bin
# num_requests: 4
config_reset()
{
echo 1 > $DIR/reset
}
release_all_firmware()
{
echo 1 > $DIR/release_all_firmware
}
config_set_name()
{
echo -n $1 > $DIR/config_name
}
config_set_into_buf()
{
echo 1 > $DIR/config_into_buf
}
config_unset_into_buf()
{
echo 0 > $DIR/config_into_buf
}
config_set_buf_size()
{
echo $1 > $DIR/config_buf_size
}
config_set_file_offset()
{
echo $1 > $DIR/config_file_offset
}
config_set_partial()
{
echo 1 > $DIR/config_partial
}
config_unset_partial()
{
echo 0 > $DIR/config_partial
}
config_set_sync_direct()
{
echo 1 > $DIR/config_sync_direct
}
config_unset_sync_direct()
{
echo 0 > $DIR/config_sync_direct
}
config_set_uevent()
{
echo 1 > $DIR/config_send_uevent
}
config_unset_uevent()
{
echo 0 > $DIR/config_send_uevent
}
config_trigger_sync()
{
echo -n 1 > $DIR/trigger_batched_requests 2>/dev/null
}
config_trigger_async()
{
echo -n 1 > $DIR/trigger_batched_requests_async 2> /dev/null
}
config_set_read_fw_idx()
{
echo -n $1 > $DIR/config_read_fw_idx 2> /dev/null
}
read_firmwares()
{
if [ "$(cat $DIR/config_into_buf)" == "1" ]; then
fwfile="$FW_INTO_BUF"
else
fwfile="$FW"
fi
if [ "$1" = "componly" ]; then
fwfile="${fwfile}-orig"
fi
for i in $(seq 0 3); do
config_set_read_fw_idx $i
# Verify the contents are what we expect.
# -Z required for now -- check for yourself, md5sum
# on $FW and DIR/read_firmware will yield the same. Even
# cmp agrees, so something is off.
if ! diff -q -Z "$fwfile" $DIR/read_firmware 2>/dev/null ; then
echo "request #$i: firmware was not loaded" >&2
exit 1
fi
done
}
read_partial_firmwares()
{
if [ "$(cat $DIR/config_into_buf)" == "1" ]; then
fwfile="${FW_INTO_BUF}"
else
fwfile="${FW}"
fi
if [ "$1" = "componly" ]; then
fwfile="${fwfile}-orig"
fi
# Strip fwfile down to match partial offset and length
partial_data="$(cat $fwfile)"
partial_data="${partial_data:$2:$3}"
for i in $(seq 0 3); do
config_set_read_fw_idx $i
read_firmware="$(cat $DIR/read_firmware)"
# Verify the contents are what we expect.
if [ $read_firmware != $partial_data ]; then
echo "request #$i: partial firmware was not loaded" >&2
exit 1
fi
done
}
read_firmwares_expect_nofile()
{
for i in $(seq 0 3); do
config_set_read_fw_idx $i
# Ensures contents differ
if diff -q -Z "$FW" $DIR/read_firmware 2>/dev/null ; then
echo "request $i: file was not expected to match" >&2
exit 1
fi
done
}
test_batched_request_firmware_nofile()
{
echo -n "Batched request_firmware() nofile try #$1: "
config_reset
config_set_name nope-test-firmware.bin
config_trigger_sync
read_firmwares_expect_nofile
release_all_firmware
echo "OK"
}
test_batched_request_firmware_into_buf_nofile()
{
echo -n "Batched request_firmware_into_buf() nofile try #$1: "
config_reset
config_set_name nope-test-firmware.bin
config_set_into_buf
config_trigger_sync
read_firmwares_expect_nofile
release_all_firmware
echo "OK"
}
test_request_partial_firmware_into_buf_nofile()
{
echo -n "Test request_partial_firmware_into_buf() off=$1 size=$2 nofile: "
config_reset
config_set_name nope-test-firmware.bin
config_set_into_buf
config_set_partial
config_set_buf_size $2
config_set_file_offset $1
config_trigger_sync
read_firmwares_expect_nofile
release_all_firmware
echo "OK"
}
test_batched_request_firmware_direct_nofile()
{
echo -n "Batched request_firmware_direct() nofile try #$1: "
config_reset
config_set_name nope-test-firmware.bin
config_set_sync_direct
config_trigger_sync
release_all_firmware
echo "OK"
}
test_request_firmware_nowait_uevent_nofile()
{
echo -n "Batched request_firmware_nowait(uevent=true) nofile try #$1: "
config_reset
config_set_name nope-test-firmware.bin
config_trigger_async
release_all_firmware
echo "OK"
}
test_wait_and_cancel_custom_load()
{
if [ "$HAS_FW_LOADER_USER_HELPER" != "yes" ]; then
return
fi
local timeout=10
name=$1
while [ ! -e "$DIR"/"$name"/loading ]; do
sleep 0.1
timeout=$(( $timeout - 1 ))
if [ "$timeout" -eq 0 ]; then
echo "firmware interface never appeared:" >&2
echo "$DIR/$name/loading" >&2
exit 1
fi
done
echo -1 >"$DIR"/"$name"/loading
}
test_request_firmware_nowait_custom_nofile()
{
echo -n "Batched request_firmware_nowait(uevent=false) nofile try #$1: "
config_reset
config_unset_uevent
RANDOM_FILE_PATH=$(setup_random_file_fake)
RANDOM_FILE="$(basename $RANDOM_FILE_PATH)"
config_set_name $RANDOM_FILE
config_trigger_async &
test_wait_and_cancel_custom_load $RANDOM_FILE
wait
release_all_firmware
echo "OK"
}
test_batched_request_firmware()
{
echo -n "Batched request_firmware() $2 try #$1: "
config_reset
config_trigger_sync
read_firmwares $2
release_all_firmware
echo "OK"
}
test_batched_request_firmware_into_buf()
{
echo -n "Batched request_firmware_into_buf() $2 try #$1: "
config_reset
config_set_name $TEST_FIRMWARE_INTO_BUF_FILENAME
config_set_into_buf
config_trigger_sync
read_firmwares $2
release_all_firmware
echo "OK"
}
test_batched_request_firmware_direct()
{
echo -n "Batched request_firmware_direct() $2 try #$1: "
config_reset
config_set_sync_direct
config_trigger_sync
release_all_firmware
echo "OK"
}
test_request_firmware_nowait_uevent()
{
echo -n "Batched request_firmware_nowait(uevent=true) $2 try #$1: "
config_reset
config_trigger_async
release_all_firmware
echo "OK"
}
test_request_firmware_nowait_custom()
{
echo -n "Batched request_firmware_nowait(uevent=false) $2 try #$1: "
config_reset
config_unset_uevent
RANDOM_FILE_PATH=$(setup_random_file)
RANDOM_FILE="$(basename $RANDOM_FILE_PATH)"
if [ -n "$2" -a "$2" != "normal" ]; then
compress_"$2"_"$COMPRESS_FORMAT" $RANDOM_FILE_PATH
fi
config_set_name $RANDOM_FILE
config_trigger_async
release_all_firmware
echo "OK"
}
test_request_partial_firmware_into_buf()
{
echo -n "Test request_partial_firmware_into_buf() off=$1 size=$2: "
config_reset
config_set_name $TEST_FIRMWARE_INTO_BUF_FILENAME
config_set_into_buf
config_set_partial
config_set_buf_size $2
config_set_file_offset $1
config_trigger_sync
read_partial_firmwares normal $1 $2
release_all_firmware
echo "OK"
}
do_tests ()
{
mode="$1"
suffix="$2"
for i in $(seq 1 5); do
test_batched_request_firmware$suffix $i $mode
done
for i in $(seq 1 5); do
test_batched_request_firmware_into_buf$suffix $i $mode
done
for i in $(seq 1 5); do
test_batched_request_firmware_direct$suffix $i $mode
done
for i in $(seq 1 5); do
test_request_firmware_nowait_uevent$suffix $i $mode
done
for i in $(seq 1 5); do
test_request_firmware_nowait_custom$suffix $i $mode
done
}
# Only continue if batched request triggers are present on the
# test-firmware driver
test_config_present
# test with the file present
echo
echo "Testing with the file present..."
do_tests normal
# Partial loads cannot use fallback, so do not repeat tests.
test_request_partial_firmware_into_buf 0 10
test_request_partial_firmware_into_buf 0 5
test_request_partial_firmware_into_buf 1 6
test_request_partial_firmware_into_buf 2 10
# Test for file not found, errors are expected, the failure would be
# a hung task, which would require a hard reset.
echo
echo "Testing with the file missing..."
do_tests nofile _nofile
# Partial loads cannot use fallback, so do not repeat tests.
test_request_partial_firmware_into_buf_nofile 0 10
test_request_partial_firmware_into_buf_nofile 0 5
test_request_partial_firmware_into_buf_nofile 1 6
test_request_partial_firmware_into_buf_nofile 2 10
test_request_firmware_compressed ()
{
export COMPRESS_FORMAT="$1"
# test with both files present
compress_both_"$COMPRESS_FORMAT" $FW
compress_both_"$COMPRESS_FORMAT" $FW_INTO_BUF
config_set_name $NAME
echo
echo "Testing with both plain and $COMPRESS_FORMAT files present..."
do_tests both
# test with only compressed file present
mv "$FW" "${FW}-orig"
mv "$FW_INTO_BUF" "${FW_INTO_BUF}-orig"
config_set_name $NAME
echo
echo "Testing with only $COMPRESS_FORMAT file present..."
do_tests componly
mv "${FW}-orig" "$FW"
mv "${FW_INTO_BUF}-orig" "$FW_INTO_BUF"
}
compress_both_XZ ()
{
$RUN_XZ -k "$@"
}
compress_componly_XZ ()
{
$RUN_XZ "$@"
}
compress_both_ZSTD ()
{
$RUN_ZSTD -k "$@"
}
compress_componly_ZSTD ()
{
$RUN_ZSTD --rm "$@"
}
if test "$HAS_FW_LOADER_COMPRESS_XZ" = "yes"; then
test_request_firmware_compressed XZ
fi
if test "$HAS_FW_LOADER_COMPRESS_ZSTD" = "yes"; then
test_request_firmware_compressed ZSTD
fi
exit 0
+236
View File
@@ -0,0 +1,236 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
# Library of helpers for test scripts.
set -e
DIR=/sys/devices/virtual/misc/test_firmware
PROC_CONFIG="/proc/config.gz"
TEST_DIR=$(dirname $0)
# We need to load a different file to test request_firmware_into_buf
# I believe the issue is firmware loaded cached vs. non-cached
# with same filename is bungled.
# To reproduce rename this to test-firmware.bin
TEST_FIRMWARE_INTO_BUF_FILENAME=test-firmware-into-buf.bin
# Kselftest framework requirement - SKIP code is 4.
ksft_skip=4
print_reqs_exit()
{
echo "You must have the following enabled in your kernel:" >&2
cat $TEST_DIR/config >&2
exit $ksft_skip
}
test_modprobe()
{
if [ ! -d $DIR ]; then
print_reqs_exit
fi
}
check_mods()
{
local uid=$(id -u)
if [ $uid -ne 0 ]; then
echo "skip all tests: must be run as root" >&2
exit $ksft_skip
fi
trap "test_modprobe" EXIT
if [ ! -d $DIR ]; then
modprobe test_firmware
fi
if [ ! -f $PROC_CONFIG ]; then
if modprobe configs 2>/dev/null; then
echo "Loaded configs module"
if [ ! -f $PROC_CONFIG ]; then
echo "You must have the following enabled in your kernel:" >&2
cat $TEST_DIR/config >&2
echo "Resorting to old heuristics" >&2
fi
else
echo "Failed to load configs module, using old heuristics" >&2
fi
fi
}
check_setup()
{
HAS_FW_LOADER_USER_HELPER="$(kconfig_has CONFIG_FW_LOADER_USER_HELPER=y)"
HAS_FW_LOADER_USER_HELPER_FALLBACK="$(kconfig_has CONFIG_FW_LOADER_USER_HELPER_FALLBACK=y)"
HAS_FW_LOADER_COMPRESS_XZ="$(kconfig_has CONFIG_FW_LOADER_COMPRESS_XZ=y)"
HAS_FW_LOADER_COMPRESS_ZSTD="$(kconfig_has CONFIG_FW_LOADER_COMPRESS_ZSTD=y)"
HAS_FW_UPLOAD="$(kconfig_has CONFIG_FW_UPLOAD=y)"
PROC_FW_IGNORE_SYSFS_FALLBACK="0"
PROC_FW_FORCE_SYSFS_FALLBACK="0"
if [ -z $PROC_SYS_DIR ]; then
PROC_SYS_DIR="/proc/sys/kernel"
fi
FW_PROC="${PROC_SYS_DIR}/firmware_config"
FW_FORCE_SYSFS_FALLBACK="$FW_PROC/force_sysfs_fallback"
FW_IGNORE_SYSFS_FALLBACK="$FW_PROC/ignore_sysfs_fallback"
if [ -f $FW_FORCE_SYSFS_FALLBACK ]; then
PROC_FW_FORCE_SYSFS_FALLBACK="$(cat $FW_FORCE_SYSFS_FALLBACK)"
fi
if [ -f $FW_IGNORE_SYSFS_FALLBACK ]; then
PROC_FW_IGNORE_SYSFS_FALLBACK="$(cat $FW_IGNORE_SYSFS_FALLBACK)"
fi
if [ "$PROC_FW_FORCE_SYSFS_FALLBACK" = "1" ]; then
HAS_FW_LOADER_USER_HELPER="yes"
HAS_FW_LOADER_USER_HELPER_FALLBACK="yes"
fi
if [ "$PROC_FW_IGNORE_SYSFS_FALLBACK" = "1" ]; then
HAS_FW_LOADER_USER_HELPER_FALLBACK="no"
HAS_FW_LOADER_USER_HELPER="no"
fi
if [ "$HAS_FW_LOADER_USER_HELPER" = "yes" ]; then
OLD_TIMEOUT="$(cat /sys/class/firmware/timeout)"
fi
OLD_FWPATH="$(cat /sys/module/firmware_class/parameters/path)"
if [ "$HAS_FW_LOADER_COMPRESS_XZ" = "yes" ]; then
if ! which xz 2> /dev/null > /dev/null; then
HAS_FW_LOADER_COMPRESS_XZ=""
fi
fi
if [ "$HAS_FW_LOADER_COMPRESS_ZSTD" = "yes" ]; then
if ! which zstd 2> /dev/null > /dev/null; then
HAS_FW_LOADER_COMPRESS_ZSTD=""
fi
fi
}
verify_reqs()
{
if [ "$TEST_REQS_FW_SYSFS_FALLBACK" = "yes" ]; then
if [ ! "$HAS_FW_LOADER_USER_HELPER" = "yes" ]; then
echo "usermode helper disabled so ignoring test"
exit 0
fi
fi
if [ "$TEST_REQS_FW_UPLOAD" = "yes" ]; then
if [ ! "$HAS_FW_UPLOAD" = "yes" ]; then
echo "firmware upload disabled so ignoring test"
exit 0
fi
fi
}
setup_tmp_file()
{
FWPATH=$(mktemp -d)
FW="$FWPATH/test-firmware.bin"
echo "ABCD0123" >"$FW"
FW_INTO_BUF="$FWPATH/$TEST_FIRMWARE_INTO_BUF_FILENAME"
echo "EFGH4567" >"$FW_INTO_BUF"
NAME=$(basename "$FW")
if [ "$TEST_REQS_FW_SET_CUSTOM_PATH" = "yes" ]; then
echo -n "$FWPATH" >/sys/module/firmware_class/parameters/path
fi
}
__setup_random_file()
{
RANDOM_FILE_PATH="$(mktemp -p $FWPATH)"
# mktemp says dry-run -n is unsafe, so...
if [[ "$1" = "fake" ]]; then
rm -rf $RANDOM_FILE_PATH
sync
else
echo "ABCD0123" >"$RANDOM_FILE_PATH"
fi
echo $RANDOM_FILE_PATH
}
setup_random_file()
{
echo $(__setup_random_file)
}
setup_random_file_fake()
{
echo $(__setup_random_file fake)
}
proc_set_force_sysfs_fallback()
{
if [ -f $FW_FORCE_SYSFS_FALLBACK ]; then
echo -n $1 > $FW_FORCE_SYSFS_FALLBACK
check_setup
fi
}
proc_set_ignore_sysfs_fallback()
{
if [ -f $FW_IGNORE_SYSFS_FALLBACK ]; then
echo -n $1 > $FW_IGNORE_SYSFS_FALLBACK
check_setup
fi
}
proc_restore_defaults()
{
proc_set_force_sysfs_fallback 0
proc_set_ignore_sysfs_fallback 0
}
test_finish()
{
if [ "$HAS_FW_LOADER_USER_HELPER" = "yes" ]; then
echo "$OLD_TIMEOUT" >/sys/class/firmware/timeout
fi
if [ "$TEST_REQS_FW_SET_CUSTOM_PATH" = "yes" ]; then
if [ "$OLD_FWPATH" = "" ]; then
# A zero-length write won't work; write a null byte
printf '\000' >/sys/module/firmware_class/parameters/path
else
echo -n "$OLD_FWPATH" >/sys/module/firmware_class/parameters/path
fi
fi
if [ -f $FW ]; then
rm -f "$FW"
fi
if [ -f $FW_INTO_BUF ]; then
rm -f "$FW_INTO_BUF"
fi
if [ -d $FWPATH ]; then
rm -rf "$FWPATH"
fi
proc_restore_defaults
}
kconfig_has()
{
if [ -f $PROC_CONFIG ]; then
if zgrep -q $1 $PROC_CONFIG 2>/dev/null; then
echo "yes"
else
echo "no"
fi
else
# We currently don't have easy heuristics to infer this
# so best we can do is just try to use the kernel assuming
# you had enabled it. This matches the old behaviour.
if [ "$1" = "CONFIG_FW_LOADER_USER_HELPER_FALLBACK=y" ]; then
echo "yes"
elif [ "$1" = "CONFIG_FW_LOADER_USER_HELPER=y" ]; then
if [ -d /sys/class/firmware/ ]; then
echo yes
else
echo no
fi
fi
fi
}
@@ -0,0 +1,148 @@
// SPDX-License-Identifier: GPL-2.0
/* Test triggering of loading of firmware from different mount
* namespaces. Expect firmware to be always loaded from the mount
* namespace of PID 1. */
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <sched.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
static char *fw_path = NULL;
static void die(char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
if (fw_path)
unlink(fw_path);
umount("/lib/firmware");
exit(EXIT_FAILURE);
}
static void trigger_fw(const char *fw_name, const char *sys_path)
{
int fd;
fd = open(sys_path, O_WRONLY);
if (fd < 0)
die("open failed: %s\n",
strerror(errno));
if (write(fd, fw_name, strlen(fw_name)) != strlen(fw_name))
exit(EXIT_FAILURE);
close(fd);
}
static void setup_fw(const char *fw_path)
{
int fd;
const char fw[] = "ABCD0123";
fd = open(fw_path, O_WRONLY | O_CREAT, 0600);
if (fd < 0)
die("open failed: %s\n",
strerror(errno));
if (write(fd, fw, sizeof(fw) -1) != sizeof(fw) -1)
die("write failed: %s\n",
strerror(errno));
close(fd);
}
static bool test_fw_in_ns(const char *fw_name, const char *sys_path, bool block_fw_in_parent_ns)
{
pid_t child;
if (block_fw_in_parent_ns)
if (mount("test", "/lib/firmware", "tmpfs", MS_RDONLY, NULL) == -1)
die("blocking firmware in parent ns failed\n");
child = fork();
if (child == -1) {
die("fork failed: %s\n",
strerror(errno));
}
if (child != 0) { /* parent */
pid_t pid;
int status;
pid = waitpid(child, &status, 0);
if (pid == -1) {
die("waitpid failed: %s\n",
strerror(errno));
}
if (pid != child) {
die("waited for %d got %d\n",
child, pid);
}
if (!WIFEXITED(status)) {
die("child did not terminate cleanly\n");
}
if (block_fw_in_parent_ns)
umount("/lib/firmware");
return WEXITSTATUS(status) == EXIT_SUCCESS;
}
if (unshare(CLONE_NEWNS) != 0) {
die("unshare(CLONE_NEWNS) failed: %s\n",
strerror(errno));
}
if (mount(NULL, "/", NULL, MS_SLAVE|MS_REC, NULL) == -1)
die("remount root in child ns failed\n");
if (!block_fw_in_parent_ns) {
if (mount("test", "/lib/firmware", "tmpfs", MS_RDONLY, NULL) == -1)
die("blocking firmware in child ns failed\n");
} else
umount("/lib/firmware");
trigger_fw(fw_name, sys_path);
exit(EXIT_SUCCESS);
}
int main(int argc, char **argv)
{
const char *fw_name = "test-firmware.bin";
char *sys_path;
if (argc != 2)
die("usage: %s sys_path\n", argv[0]);
/* Mount tmpfs to /lib/firmware so we don't have to assume
that it is writable for us.*/
if (mount("test", "/lib/firmware", "tmpfs", 0, NULL) == -1)
die("mounting tmpfs to /lib/firmware failed\n");
sys_path = argv[1];
if (asprintf(&fw_path, "/lib/firmware/%s", fw_name) < 0)
die("error: failed to build full fw_path\n");
setup_fw(fw_path);
setvbuf(stdout, NULL, _IONBF, 0);
/* Positive case: firmware in PID1 mount namespace */
printf("Testing with firmware in parent namespace (assumed to be same file system as PID1)\n");
if (!test_fw_in_ns(fw_name, sys_path, false))
die("error: failed to access firmware\n");
/* Negative case: firmware in child mount namespace, expected to fail */
printf("Testing with firmware in child namespace\n");
if (test_fw_in_ns(fw_name, sys_path, true))
die("error: firmware access did not fail\n");
unlink(fw_path);
free(fw_path);
umount("/lib/firmware");
exit(EXIT_SUCCESS);
}
@@ -0,0 +1,79 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
# This runs all known tests across all known possible configurations we could
# emulate in one run.
set -e
TEST_DIR=$(dirname $0)
source $TEST_DIR/fw_lib.sh
export HAS_FW_LOADER_USER_HELPER=""
export HAS_FW_LOADER_USER_HELPER_FALLBACK=""
export HAS_FW_LOADER_COMPRESS=""
run_tests()
{
proc_set_force_sysfs_fallback $1
proc_set_ignore_sysfs_fallback $2
$TEST_DIR/fw_filesystem.sh
proc_set_force_sysfs_fallback $1
proc_set_ignore_sysfs_fallback $2
$TEST_DIR/fw_fallback.sh
proc_set_force_sysfs_fallback $1
proc_set_ignore_sysfs_fallback $2
$TEST_DIR/fw_upload.sh
}
run_test_config_0001()
{
echo "-----------------------------------------------------"
echo "Running kernel configuration test 1 -- rare"
echo "Emulates:"
echo "CONFIG_FW_LOADER=y"
echo "CONFIG_FW_LOADER_USER_HELPER=n"
echo "CONFIG_FW_LOADER_USER_HELPER_FALLBACK=n"
run_tests 0 1
}
run_test_config_0002()
{
echo "-----------------------------------------------------"
echo "Running kernel configuration test 2 -- distro"
echo "Emulates:"
echo "CONFIG_FW_LOADER=y"
echo "CONFIG_FW_LOADER_USER_HELPER=y"
echo "CONFIG_FW_LOADER_USER_HELPER_FALLBACK=n"
proc_set_ignore_sysfs_fallback 0
run_tests 0 0
}
run_test_config_0003()
{
echo "-----------------------------------------------------"
echo "Running kernel configuration test 3 -- android"
echo "Emulates:"
echo "CONFIG_FW_LOADER=y"
echo "CONFIG_FW_LOADER_USER_HELPER=y"
echo "CONFIG_FW_LOADER_USER_HELPER_FALLBACK=y"
run_tests 1 0
}
check_mods
check_setup
echo "Running namespace test: "
$TEST_DIR/fw_namespace $DIR/trigger_request
echo "OK"
if [ -f $FW_FORCE_SYSFS_FALLBACK ]; then
run_test_config_0001
run_test_config_0002
run_test_config_0003
else
echo "Running basic kernel configuration, working with your config"
run_tests
fi
+214
View File
@@ -0,0 +1,214 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
# This validates the user-initiated fw upload mechanism of the firmware
# loader. It verifies that one or more firmware devices can be created
# for a device driver. It also verifies the data transfer, the
# cancellation support, and the error flows.
set -e
TEST_REQS_FW_UPLOAD="yes"
TEST_DIR=$(dirname $0)
progress_states="preparing transferring programming"
errors="hw-error
timeout
device-busy
invalid-file-size
read-write-error
flash-wearout"
error_abort="user-abort"
fwname1=fw1
fwname2=fw2
fwname3=fw3
source $TEST_DIR/fw_lib.sh
check_mods
check_setup
verify_reqs
trap "upload_finish" EXIT
upload_finish() {
local fwdevs="$fwname1 $fwname2 $fwname3"
for name in $fwdevs; do
if [ -e "$DIR/$name" ]; then
echo -n "$name" > "$DIR"/upload_unregister
fi
done
}
upload_fw() {
local name="$1"
local file="$2"
echo 1 > "$DIR"/"$name"/loading
cat "$file" > "$DIR"/"$name"/data
echo 0 > "$DIR"/"$name"/loading
}
verify_fw() {
local name="$1"
local file="$2"
echo -n "$name" > "$DIR"/config_upload_name
if ! cmp "$file" "$DIR"/upload_read > /dev/null 2>&1; then
echo "$0: firmware compare for $name did not match" >&2
exit 1
fi
echo "$0: firmware upload for $name works" >&2
return 0
}
inject_error() {
local name="$1"
local status="$2"
local error="$3"
echo 1 > "$DIR"/"$name"/loading
echo -n "inject":"$status":"$error" > "$DIR"/"$name"/data
echo 0 > "$DIR"/"$name"/loading
}
await_status() {
local name="$1"
local expected="$2"
local status
local i
let i=0
while [ $i -lt 50 ]; do
status=$(cat "$DIR"/"$name"/status)
if [ "$status" = "$expected" ]; then
return 0;
fi
sleep 1e-03
let i=$i+1
done
echo "$0: Invalid status: Expected $expected, Actual $status" >&2
return 1;
}
await_idle() {
local name="$1"
await_status "$name" "idle"
return $?
}
expect_error() {
local name="$1"
local expected="$2"
local error=$(cat "$DIR"/"$name"/error)
if [ "$error" != "$expected" ]; then
echo "Invalid error: Expected $expected, Actual $error" >&2
return 1
fi
return 0
}
random_firmware() {
local bs="$1"
local count="$2"
local file=$(mktemp -p /tmp uploadfwXXX.bin)
dd if=/dev/urandom of="$file" bs="$bs" count="$count" > /dev/null 2>&1
echo "$file"
}
test_upload_cancel() {
local name="$1"
local status
for status in $progress_states; do
inject_error $name $status $error_abort
if ! await_status $name $status; then
exit 1
fi
echo 1 > "$DIR"/"$name"/cancel
if ! await_idle $name; then
exit 1
fi
if ! expect_error $name "$status":"$error_abort"; then
exit 1
fi
done
echo "$0: firmware upload cancellation works"
return 0
}
test_error_handling() {
local name=$1
local status
local error
for status in $progress_states; do
for error in $errors; do
inject_error $name $status $error
if ! await_idle $name; then
exit 1
fi
if ! expect_error $name "$status":"$error"; then
exit 1
fi
done
done
echo "$0: firmware upload error handling works"
}
test_fw_too_big() {
local name=$1
local fw_too_big=`random_firmware 512 5`
local expected="preparing:invalid-file-size"
upload_fw $name $fw_too_big
rm -f $fw_too_big
if ! await_idle $name; then
exit 1
fi
if ! expect_error $name $expected; then
exit 1
fi
echo "$0: oversized firmware error handling works"
}
echo -n "$fwname1" > "$DIR"/upload_register
echo -n "$fwname2" > "$DIR"/upload_register
echo -n "$fwname3" > "$DIR"/upload_register
test_upload_cancel $fwname1
test_error_handling $fwname1
test_fw_too_big $fwname1
fw_file1=`random_firmware 512 4`
fw_file2=`random_firmware 512 3`
fw_file3=`random_firmware 512 2`
upload_fw $fwname1 $fw_file1
upload_fw $fwname2 $fw_file2
upload_fw $fwname3 $fw_file3
verify_fw ${fwname1} ${fw_file1}
verify_fw ${fwname2} ${fw_file2}
verify_fw ${fwname3} ${fw_file3}
echo -n "$fwname1" > "$DIR"/upload_unregister
echo -n "$fwname2" > "$DIR"/upload_unregister
echo -n "$fwname3" > "$DIR"/upload_unregister
exit 0
@@ -0,0 +1,8 @@
# The async firmware timeout is set to 1 second (but ends up being effectively
# 2 seconds). There are 3 test configs, each done with and without firmware
# present, each with 2 "nowait" functions tested 5 times. Expected time for a
# normal execution should be 2 * 3 * 2 * 2 * 5 = 120 seconds for those alone.
# Additionally, fw_fallback may take 5 seconds for internal timeouts in each
# of the 3 configs, so at least another 15 seconds are needed. Add another
# 10 seconds for each testing config: 120 + 15 + 30
timeout=165