2 # SPDX-License-Identifier: GPL-2.0
6 # This script currently only works for the following platforms,
7 # as it is based on the VM image used by the BPF CI, which is
8 # available only for these architectures. We can also specify
9 # the local rootfs image generated by the following script:
10 # https://github.com/libbpf/ci/blob/main/rootfs/mkrootfs_debian.sh
11 PLATFORM="${PLATFORM:-$(uname -m)}"
14 QEMU_BINARY=qemu-system-s390x
16 HOST_FLAGS=(-smp 2 -enable-kvm)
18 BZIMAGE="arch/s390/boot/vmlinux"
22 QEMU_BINARY=qemu-system-x86_64
23 QEMU_CONSOLE="ttyS0,115200"
24 HOST_FLAGS=(-cpu host -enable-kvm -smp 8)
26 BZIMAGE="arch/x86/boot/bzImage"
30 QEMU_BINARY=qemu-system-aarch64
31 QEMU_CONSOLE="ttyAMA0,115200"
32 HOST_FLAGS=(-M virt,gic-version=3 -cpu host -enable-kvm -smp 8)
33 CROSS_FLAGS=(-M virt,gic-version=3 -cpu cortex-a76 -smp 8)
34 BZIMAGE="arch/arm64/boot/Image"
38 # required qemu version v7.2.0+
39 QEMU_BINARY=qemu-system-riscv64
40 QEMU_CONSOLE="ttyS0,115200"
41 HOST_FLAGS=(-M virt -cpu host -enable-kvm -smp 8)
42 CROSS_FLAGS=(-M virt -cpu rv64,sscofpmf=true -smp 8)
43 BZIMAGE="arch/riscv/boot/Image"
47 echo "Unsupported architecture"
51 DEFAULT_COMMAND="./test_progs"
54 ROOTFS_IMAGE="root.img"
55 OUTPUT_DIR="$HOME/.bpf_selftests"
56 KCONFIG_REL_PATHS=("tools/testing/selftests/bpf/config"
57 "tools/testing/selftests/bpf/config.vm"
58 "tools/testing/selftests/bpf/config.${PLATFORM}")
59 INDEX_URL="https://raw.githubusercontent.com/libbpf/ci/master/INDEX"
60 NUM_COMPILE_JOBS="$(nproc)"
61 LOG_FILE_BASE="$(date +"bpf_selftests.%Y-%m-%d_%H-%M-%S")"
62 LOG_FILE="${LOG_FILE_BASE}.log"
63 EXIT_STATUS_FILE="${LOG_FILE_BASE}.exit_status"
68 Usage: $0 [-i] [-s] [-d <output_dir>] -- [<command>]
70 <command> is the command you would normally run when you are in
71 tools/testing/selftests/bpf. e.g:
73 $0 -- ./test_progs -t test_lsm
75 If no command is specified and a debug shell (-s) is not requested,
76 "${DEFAULT_COMMAND}" will be run by default.
78 Using PLATFORM= and CROSS_COMPILE= options will enable cross platform testing:
80 PLATFORM=<platform> CROSS_COMPILE=<toolchain> $0 -- ./test_progs -t test_lsm
82 If you build your kernel using KBUILD_OUTPUT= or O= options, these
83 can be passed as environment variables to the script:
85 O=<kernel_build_path> $0 -- ./test_progs -t test_lsm
89 KBUILD_OUTPUT=<kernel_build_path> $0 -- ./test_progs -t test_lsm
93 -l) Specify the path to the local rootfs image.
94 -i) Update the rootfs image with a newer version.
95 -d) Update the output directory (default: ${OUTPUT_DIR})
96 -j) Number of jobs for compilation, similar to -j in make
97 (default: ${NUM_COMPILE_JOBS})
98 -s) Instead of powering off the VM, start an interactive
99 shell. If <command> is specified, the shell runs after
100 the command finishes executing
107 if ! declare -p URLS &> /dev/null; then
108 # URLS contain the mapping from file names to URLs where
109 # those files can be downloaded from.
111 while IFS=$'\t' read -r name url; do
113 done < <(curl -Lsf ${INDEX_URL})
117 newest_rootfs_version()
120 for file in "${!URLS[@]}"; do
121 if [[ $file =~ ^"${PLATFORM}"/libbpf-vmtest-rootfs-(.*)\.tar\.zst$ ]]; then
122 echo "${BASH_REMATCH[1]}"
125 } | sort -rV | head -1
132 local rootfsversion="$(newest_rootfs_version)"
133 local file="${PLATFORM}/libbpf-vmtest-rootfs-$rootfsversion.tar.zst"
135 if [[ ! -v URLS[$file] ]]; then
136 echo "$file not found" >&2
140 echo "Downloading $file..." >&2
141 curl -Lsf "${URLS[$file]}" "${@:2}"
148 if ! which zstd &> /dev/null; then
149 echo 'Could not find "zstd" on the system, please install zstd'
153 if [[ -n "${LOCAL_ROOTFS_IMAGE}" ]]; then
154 cat "${LOCAL_ROOTFS_IMAGE}" | zstd -d | sudo tar -C "$dir" -x
156 download_rootfs | zstd -d | sudo tar -C "$dir" -x
162 local kernel_checkout="$1"
163 local make_command="$2"
165 cd "${kernel_checkout}"
167 ${make_command} olddefconfig
173 local rootfs_img="${OUTPUT_DIR}/${ROOTFS_IMAGE}"
174 local mount_dir="${OUTPUT_DIR}/${MOUNT_DIR}"
176 sudo mount -o loop "${rootfs_img}" "${mount_dir}"
181 local mount_dir="${OUTPUT_DIR}/${MOUNT_DIR}"
183 sudo umount "${mount_dir}" &> /dev/null
188 local kernel_checkout="$1"
189 local selftests_dir="${kernel_checkout}/tools/testing/selftests/bpf"
191 cd "${selftests_dir}"
194 # Mount the image and copy the selftests to the image.
196 sudo rm -rf "${mount_dir}/root/bpf"
197 sudo cp -r "${selftests_dir}" "${mount_dir}/root"
203 local init_script_dir="${OUTPUT_DIR}/${MOUNT_DIR}/etc/rcS.d"
204 local init_script="${init_script_dir}/S50-startup"
206 local exit_command="$2"
210 if [[ ! -d "${init_script_dir}" ]]; then
212 Could not find ${init_script_dir} in the mounted image.
213 This likely indicates a bad rootfs image, Please download
214 a new image by passing "-i" to the script
220 sudo bash -c "echo '#!/bin/bash' > ${init_script}"
222 if [[ "${command}" != "" ]]; then
223 sudo bash -c "cat >>${init_script}" <<EOF
224 # Have a default value in the exit status file
225 # incase the VM is forcefully stopped.
226 echo "130" > "/root/${EXIT_STATUS_FILE}"
231 stdbuf -oL -eL ${command}
232 echo "\$?" > "/root/${EXIT_STATUS_FILE}"
233 } 2>&1 | tee "/root/${LOG_FILE}"
234 # Ensure that the logs are written to disk
239 sudo bash -c "echo ${exit_command} >> ${init_script}"
240 sudo chmod a+x "${init_script}"
246 local rootfs_img="${OUTPUT_DIR}/${ROOTFS_IMAGE}"
247 local mount_dir="${OUTPUT_DIR}/${MOUNT_DIR}"
249 rm -rf "${rootfs_img}"
250 touch "${rootfs_img}"
251 chattr +C "${rootfs_img}" >/dev/null 2>&1 || true
253 truncate -s 2G "${rootfs_img}"
254 mkfs.ext4 -q "${rootfs_img}"
257 load_rootfs "${mount_dir}"
263 local kernel_bzimage="$1"
264 local rootfs_img="${OUTPUT_DIR}/${ROOTFS_IMAGE}"
266 if ! which "${QEMU_BINARY}" &> /dev/null; then
268 Could not find ${QEMU_BINARY}
269 Please install qemu or set the QEMU_BINARY environment variable.
274 if [[ "${PLATFORM}" != "$(uname -m)" ]]; then
275 QEMU_FLAGS=("${CROSS_FLAGS[@]}")
277 QEMU_FLAGS=("${HOST_FLAGS[@]}")
286 -drive file="${rootfs_img}",format=raw,index=1,media=disk,if=virtio,cache=none \
287 -kernel "${kernel_bzimage}" \
288 -append "root=/dev/vda rw console=${QEMU_CONSOLE}"
293 local mount_dir="${OUTPUT_DIR}/${MOUNT_DIR}"
294 local log_file="${mount_dir}/root/${LOG_FILE}"
295 local exit_status_file="${mount_dir}/root/${EXIT_STATUS_FILE}"
298 sudo cp ${log_file} "${OUTPUT_DIR}"
299 sudo cp ${exit_status_file} "${OUTPUT_DIR}"
300 sudo rm -f ${log_file}
308 [[ ${path:0:1} != "/" ]]
313 local kernel_checkout="$1"
314 local kconfig_file="$2"
316 rm -f "$kconfig_file" 2> /dev/null
318 for config in "${KCONFIG_REL_PATHS[@]}"; do
319 local kconfig_src="${kernel_checkout}/${config}"
320 cat "$kconfig_src" >> "$kconfig_file"
326 local kernel_checkout="$1"
327 local kconfig_file="$2"
329 if [[ -f "${kconfig_file}" ]]; then
330 local local_modified="$(stat -c %Y "${kconfig_file}")"
332 for config in "${KCONFIG_REL_PATHS[@]}"; do
333 local kconfig_src="${kernel_checkout}/${config}"
334 local src_modified="$(stat -c %Y "${kconfig_src}")"
335 # Only update the config if it has been updated after the
336 # previously cached config was created. This avoids
337 # unnecessarily compiling the kernel and selftests.
338 if [[ "${src_modified}" -gt "${local_modified}" ]]; then
339 do_update_kconfig "$kernel_checkout" "$kconfig_file"
340 # Once we have found one outdated configuration
341 # there is no need to check other ones.
346 do_update_kconfig "$kernel_checkout" "$kconfig_file"
353 local exit_status_file="${OUTPUT_DIR}/${EXIT_STATUS_FILE}"
354 # This is just a cleanup and the directory may
355 # have already been unmounted. So, don't let this
356 # clobber the error code we intend to return.
357 unmount_image || true
358 if [[ -f "${exit_status_file}" ]]; then
359 exit_code="$(cat ${exit_status_file})"
366 local script_dir="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
367 local kernel_checkout=$(realpath "${script_dir}"/../../../../)
368 # By default the script searches for the kernel in the checkout directory but
369 # it also obeys environment variables O= and KBUILD_OUTPUT=
370 local kernel_bzimage="${kernel_checkout}/${BZIMAGE}"
371 local command="${DEFAULT_COMMAND}"
372 local update_image="no"
373 local exit_command="poweroff -f"
374 local debug_shell="no"
376 while getopts ':hskl:id:j:' opt; do
379 LOCAL_ROOTFS_IMAGE="$OPTARG"
388 NUM_COMPILE_JOBS="$OPTARG"
400 echo "Invalid Option: -$OPTARG"
405 echo "Invalid Option: -$OPTARG requires an argument"
413 trap 'catch "$?"' EXIT
415 if [[ "${PLATFORM}" != "$(uname -m)" ]] && [[ -z "${CROSS_COMPILE}" ]]; then
416 echo "Cross-platform testing needs to specify CROSS_COMPILE"
420 if [[ $# -eq 0 && "${debug_shell}" == "no" ]]; then
421 echo "No command specified, will run ${DEFAULT_COMMAND} in the vm"
426 local kconfig_file="${OUTPUT_DIR}/latest.config"
427 local make_command="make ARCH=${ARCH} CROSS_COMPILE=${CROSS_COMPILE} \
428 -j ${NUM_COMPILE_JOBS} KCONFIG_CONFIG=${kconfig_file}"
430 # Figure out where the kernel is being built.
431 # O takes precedence over KBUILD_OUTPUT.
432 if [[ "${O:=""}" != "" ]]; then
433 if is_rel_path "${O}"; then
434 O="$(realpath "${PWD}/${O}")"
436 kernel_bzimage="${O}/${BZIMAGE}"
437 make_command="${make_command} O=${O}"
438 elif [[ "${KBUILD_OUTPUT:=""}" != "" ]]; then
439 if is_rel_path "${KBUILD_OUTPUT}"; then
440 KBUILD_OUTPUT="$(realpath "${PWD}/${KBUILD_OUTPUT}")"
442 kernel_bzimage="${KBUILD_OUTPUT}/${BZIMAGE}"
443 make_command="${make_command} KBUILD_OUTPUT=${KBUILD_OUTPUT}"
446 local rootfs_img="${OUTPUT_DIR}/${ROOTFS_IMAGE}"
447 local mount_dir="${OUTPUT_DIR}/${MOUNT_DIR}"
449 echo "Output directory: ${OUTPUT_DIR}"
451 mkdir -p "${OUTPUT_DIR}"
452 mkdir -p "${mount_dir}"
453 update_kconfig "${kernel_checkout}" "${kconfig_file}"
455 recompile_kernel "${kernel_checkout}" "${make_command}"
457 if [[ "${update_image}" == "no" && ! -f "${rootfs_img}" ]]; then
458 echo "rootfs image not found in ${rootfs_img}"
462 if [[ "${update_image}" == "yes" ]]; then
466 update_selftests "${kernel_checkout}" "${make_command}"
467 update_init_script "${command}" "${exit_command}"
468 run_vm "${kernel_bzimage}"
469 if [[ "${command}" != "" ]]; then
471 echo "Logs saved in ${OUTPUT_DIR}/${LOG_FILE}"