| #!/bin/bash | |
| # SPDX-License-Identifier: GPL-2.0 | |
| set -u | |
| set -e | |
| # This script currently only works for x86_64 | |
| ARCH="$(uname -m)" | |
| case "${ARCH}" in | |
| x86_64) | |
| QEMU_BINARY=qemu-system-x86_64 | |
| BZIMAGE="arch/x86/boot/bzImage" | |
| ;; | |
| *) | |
| echo "Unsupported architecture" | |
| exit 1 | |
| ;; | |
| esac | |
| SCRIPT_DIR="$(dirname $(realpath $0))" | |
| OUTPUT_DIR="$SCRIPT_DIR/results" | |
| KCONFIG_REL_PATHS=("${SCRIPT_DIR}/config" "${SCRIPT_DIR}/config.common" "${SCRIPT_DIR}/config.${ARCH}") | |
| B2C_URL="https://gitlab.freedesktop.org/gfx-ci/boot2container/-/raw/main/vm2c.py" | |
| NUM_COMPILE_JOBS="$(nproc)" | |
| LOG_FILE_BASE="$(date +"hid_selftests.%Y-%m-%d_%H-%M-%S")" | |
| LOG_FILE="${LOG_FILE_BASE}.log" | |
| EXIT_STATUS_FILE="${LOG_FILE_BASE}.exit_status" | |
| CONTAINER_IMAGE="registry.freedesktop.org/bentiss/hid/fedora/39:2023-11-22.1" | |
| TARGETS="${TARGETS:=$(basename ${SCRIPT_DIR})}" | |
| DEFAULT_COMMAND="pip3 install hid-tools; make -C tools/testing/selftests TARGETS=${TARGETS} run_tests" | |
| usage() | |
| { | |
| cat <<EOF | |
| Usage: $0 [-j N] [-s] [-b] [-d <output_dir>] -- [<command>] | |
| <command> is the command you would normally run when you are in | |
| the source kernel direcory. e.g: | |
| $0 -- ./tools/testing/selftests/hid/hid_bpf | |
| If no command is specified and a debug shell (-s) is not requested, | |
| "${DEFAULT_COMMAND}" will be run by default. | |
| If you build your kernel using KBUILD_OUTPUT= or O= options, these | |
| can be passed as environment variables to the script: | |
| O=<kernel_build_path> $0 -- ./tools/testing/selftests/hid/hid_bpf | |
| or | |
| KBUILD_OUTPUT=<kernel_build_path> $0 -- ./tools/testing/selftests/hid/hid_bpf | |
| Options: | |
| -u) Update the boot2container script to a newer version. | |
| -d) Update the output directory (default: ${OUTPUT_DIR}) | |
| -b) Run only the build steps for the kernel and the selftests | |
| -j) Number of jobs for compilation, similar to -j in make | |
| (default: ${NUM_COMPILE_JOBS}) | |
| -s) Instead of powering off the VM, start an interactive | |
| shell. If <command> is specified, the shell runs after | |
| the command finishes executing | |
| EOF | |
| } | |
| download() | |
| { | |
| local file="$1" | |
| echo "Downloading $file..." >&2 | |
| curl -Lsf "$file" -o "${@:2}" | |
| } | |
| recompile_kernel() | |
| { | |
| local kernel_checkout="$1" | |
| local make_command="$2" | |
| cd "${kernel_checkout}" | |
| ${make_command} olddefconfig | |
| ${make_command} headers | |
| ${make_command} | |
| } | |
| update_selftests() | |
| { | |
| local kernel_checkout="$1" | |
| local selftests_dir="${kernel_checkout}/tools/testing/selftests/hid" | |
| cd "${selftests_dir}" | |
| ${make_command} | |
| } | |
| run_vm() | |
| { | |
| local run_dir="$1" | |
| local b2c="$2" | |
| local kernel_bzimage="$3" | |
| local command="$4" | |
| local post_command="" | |
| cd "${run_dir}" | |
| if ! which "${QEMU_BINARY}" &> /dev/null; then | |
| cat <<EOF | |
| Could not find ${QEMU_BINARY} | |
| Please install qemu or set the QEMU_BINARY environment variable. | |
| EOF | |
| exit 1 | |
| fi | |
| # alpine (used in post-container requires the PATH to have /bin | |
| export PATH=$PATH:/bin | |
| if [[ "${debug_shell}" != "yes" ]] | |
| then | |
| touch ${OUTPUT_DIR}/${LOG_FILE} | |
| command="mount bpffs -t bpf /sys/fs/bpf/; set -o pipefail ; ${command} 2>&1 | tee ${OUTPUT_DIR}/${LOG_FILE}" | |
| post_command="cat ${OUTPUT_DIR}/${LOG_FILE}" | |
| else | |
| command="mount bpffs -t bpf /sys/fs/bpf/; ${command}" | |
| fi | |
| set +e | |
| $b2c --command "${command}" \ | |
| --kernel ${kernel_bzimage} \ | |
| --workdir ${OUTPUT_DIR} \ | |
| --image ${CONTAINER_IMAGE} | |
| echo $? > ${OUTPUT_DIR}/${EXIT_STATUS_FILE} | |
| set -e | |
| ${post_command} | |
| } | |
| is_rel_path() | |
| { | |
| local path="$1" | |
| [[ ${path:0:1} != "/" ]] | |
| } | |
| do_update_kconfig() | |
| { | |
| local kernel_checkout="$1" | |
| local kconfig_file="$2" | |
| rm -f "$kconfig_file" 2> /dev/null | |
| for config in "${KCONFIG_REL_PATHS[@]}"; do | |
| local kconfig_src="${config}" | |
| cat "$kconfig_src" >> "$kconfig_file" | |
| done | |
| } | |
| update_kconfig() | |
| { | |
| local kernel_checkout="$1" | |
| local kconfig_file="$2" | |
| if [[ -f "${kconfig_file}" ]]; then | |
| local local_modified="$(stat -c %Y "${kconfig_file}")" | |
| for config in "${KCONFIG_REL_PATHS[@]}"; do | |
| local kconfig_src="${config}" | |
| local src_modified="$(stat -c %Y "${kconfig_src}")" | |
| # Only update the config if it has been updated after the | |
| # previously cached config was created. This avoids | |
| # unnecessarily compiling the kernel and selftests. | |
| if [[ "${src_modified}" -gt "${local_modified}" ]]; then | |
| do_update_kconfig "$kernel_checkout" "$kconfig_file" | |
| # Once we have found one outdated configuration | |
| # there is no need to check other ones. | |
| break | |
| fi | |
| done | |
| else | |
| do_update_kconfig "$kernel_checkout" "$kconfig_file" | |
| fi | |
| } | |
| main() | |
| { | |
| local script_dir="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)" | |
| local kernel_checkout=$(realpath "${script_dir}"/../../../../) | |
| # By default the script searches for the kernel in the checkout directory but | |
| # it also obeys environment variables O= and KBUILD_OUTPUT= | |
| local kernel_bzimage="${kernel_checkout}/${BZIMAGE}" | |
| local command="${DEFAULT_COMMAND}" | |
| local update_b2c="no" | |
| local debug_shell="no" | |
| local build_only="no" | |
| while getopts ':hsud:j:b' opt; do | |
| case ${opt} in | |
| u) | |
| update_b2c="yes" | |
| ;; | |
| d) | |
| OUTPUT_DIR="$OPTARG" | |
| ;; | |
| j) | |
| NUM_COMPILE_JOBS="$OPTARG" | |
| ;; | |
| s) | |
| command="/bin/sh" | |
| debug_shell="yes" | |
| ;; | |
| b) | |
| build_only="yes" | |
| ;; | |
| h) | |
| usage | |
| exit 0 | |
| ;; | |
| \? ) | |
| echo "Invalid Option: -$OPTARG" | |
| usage | |
| exit 1 | |
| ;; | |
| : ) | |
| echo "Invalid Option: -$OPTARG requires an argument" | |
| usage | |
| exit 1 | |
| ;; | |
| esac | |
| done | |
| shift $((OPTIND -1)) | |
| # trap 'catch "$?"' EXIT | |
| if [[ "${build_only}" == "no" && "${debug_shell}" == "no" ]]; then | |
| if [[ $# -eq 0 ]]; then | |
| echo "No command specified, will run ${DEFAULT_COMMAND} in the vm" | |
| else | |
| command="$@" | |
| if [[ "${command}" == "/bin/bash" || "${command}" == "bash" ]] | |
| then | |
| debug_shell="yes" | |
| fi | |
| fi | |
| fi | |
| local kconfig_file="${OUTPUT_DIR}/latest.config" | |
| local make_command="make -j ${NUM_COMPILE_JOBS} KCONFIG_CONFIG=${kconfig_file}" | |
| # Figure out where the kernel is being built. | |
| # O takes precedence over KBUILD_OUTPUT. | |
| if [[ "${O:=""}" != "" ]]; then | |
| if is_rel_path "${O}"; then | |
| O="$(realpath "${PWD}/${O}")" | |
| fi | |
| kernel_bzimage="${O}/${BZIMAGE}" | |
| make_command="${make_command} O=${O}" | |
| elif [[ "${KBUILD_OUTPUT:=""}" != "" ]]; then | |
| if is_rel_path "${KBUILD_OUTPUT}"; then | |
| KBUILD_OUTPUT="$(realpath "${PWD}/${KBUILD_OUTPUT}")" | |
| fi | |
| kernel_bzimage="${KBUILD_OUTPUT}/${BZIMAGE}" | |
| make_command="${make_command} KBUILD_OUTPUT=${KBUILD_OUTPUT}" | |
| fi | |
| local b2c="${OUTPUT_DIR}/vm2c.py" | |
| echo "Output directory: ${OUTPUT_DIR}" | |
| mkdir -p "${OUTPUT_DIR}" | |
| update_kconfig "${kernel_checkout}" "${kconfig_file}" | |
| recompile_kernel "${kernel_checkout}" "${make_command}" | |
| update_selftests "${kernel_checkout}" "${make_command}" | |
| if [[ "${build_only}" == "no" ]]; then | |
| if [[ "${update_b2c}" == "no" && ! -f "${b2c}" ]]; then | |
| echo "vm2c script not found in ${b2c}" | |
| update_b2c="yes" | |
| fi | |
| if [[ "${update_b2c}" == "yes" ]]; then | |
| download $B2C_URL $b2c | |
| chmod +x $b2c | |
| fi | |
| run_vm "${kernel_checkout}" $b2c "${kernel_bzimage}" "${command}" | |
| if [[ "${debug_shell}" != "yes" ]]; then | |
| echo "Logs saved in ${OUTPUT_DIR}/${LOG_FILE}" | |
| fi | |
| exit $(cat ${OUTPUT_DIR}/${EXIT_STATUS_FILE}) | |
| fi | |
| } | |
| main "$@" |