1#!/bin/bash
2# SPDX-License-Identifier: GPL-2.0
3
4set -u
5set -e
6
7# This script currently only works for x86_64
8ARCH="$(uname -m)"
9case "${ARCH}" in
10x86_64)
11	QEMU_BINARY=qemu-system-x86_64
12	BZIMAGE="arch/x86/boot/bzImage"
13	;;
14*)
15	echo "Unsupported architecture"
16	exit 1
17	;;
18esac
19SCRIPT_DIR="$(dirname $(realpath $0))"
20OUTPUT_DIR="$SCRIPT_DIR/results"
21KCONFIG_REL_PATHS=("${SCRIPT_DIR}/config" "${SCRIPT_DIR}/config.common" "${SCRIPT_DIR}/config.${ARCH}")
22B2C_URL="https://gitlab.freedesktop.org/gfx-ci/boot2container/-/raw/main/vm2c.py"
23NUM_COMPILE_JOBS="$(nproc)"
24LOG_FILE_BASE="$(date +"hid_selftests.%Y-%m-%d_%H-%M-%S")"
25LOG_FILE="${LOG_FILE_BASE}.log"
26EXIT_STATUS_FILE="${LOG_FILE_BASE}.exit_status"
27CONTAINER_IMAGE="registry.freedesktop.org/bentiss/hid/fedora/39:2023-11-22.1"
28
29TARGETS="${TARGETS:=$(basename ${SCRIPT_DIR})}"
30DEFAULT_COMMAND="pip3 install hid-tools; make -C tools/testing/selftests TARGETS=${TARGETS} run_tests"
31
32usage()
33{
34	cat <<EOF
35Usage: $0 [-j N] [-s] [-b] [-d <output_dir>] -- [<command>]
36
37<command> is the command you would normally run when you are in
38the source kernel direcory. e.g:
39
40	$0 -- ./tools/testing/selftests/hid/hid_bpf
41
42If no command is specified and a debug shell (-s) is not requested,
43"${DEFAULT_COMMAND}" will be run by default.
44
45If you build your kernel using KBUILD_OUTPUT= or O= options, these
46can be passed as environment variables to the script:
47
48  O=<kernel_build_path> $0 -- ./tools/testing/selftests/hid/hid_bpf
49
50or
51
52  KBUILD_OUTPUT=<kernel_build_path> $0 -- ./tools/testing/selftests/hid/hid_bpf
53
54Options:
55
56	-u)		Update the boot2container script to a newer version.
57	-d)		Update the output directory (default: ${OUTPUT_DIR})
58	-b)		Run only the build steps for the kernel and the selftests
59	-j)		Number of jobs for compilation, similar to -j in make
60			(default: ${NUM_COMPILE_JOBS})
61	-s)		Instead of powering off the VM, start an interactive
62			shell. If <command> is specified, the shell runs after
63			the command finishes executing
64EOF
65}
66
67download()
68{
69	local file="$1"
70
71	echo "Downloading $file..." >&2
72	curl -Lsf "$file" -o "${@:2}"
73}
74
75recompile_kernel()
76{
77	local kernel_checkout="$1"
78	local make_command="$2"
79
80	cd "${kernel_checkout}"
81
82	${make_command} olddefconfig
83	${make_command} headers
84	${make_command}
85}
86
87update_selftests()
88{
89	local kernel_checkout="$1"
90	local selftests_dir="${kernel_checkout}/tools/testing/selftests/hid"
91
92	cd "${selftests_dir}"
93	${make_command}
94}
95
96run_vm()
97{
98	local run_dir="$1"
99	local b2c="$2"
100	local kernel_bzimage="$3"
101	local command="$4"
102	local post_command=""
103
104	cd "${run_dir}"
105
106	if ! which "${QEMU_BINARY}" &> /dev/null; then
107		cat <<EOF
108Could not find ${QEMU_BINARY}
109Please install qemu or set the QEMU_BINARY environment variable.
110EOF
111		exit 1
112	fi
113
114	# alpine (used in post-container requires the PATH to have /bin
115	export PATH=$PATH:/bin
116
117	if [[ "${debug_shell}" != "yes" ]]
118	then
119		touch ${OUTPUT_DIR}/${LOG_FILE}
120		command="mount bpffs -t bpf /sys/fs/bpf/; set -o pipefail ; ${command} 2>&1 | tee ${OUTPUT_DIR}/${LOG_FILE}"
121		post_command="cat ${OUTPUT_DIR}/${LOG_FILE}"
122	else
123		command="mount bpffs -t bpf /sys/fs/bpf/; ${command}"
124	fi
125
126	set +e
127	$b2c --command "${command}" \
128	     --kernel ${kernel_bzimage} \
129	     --workdir ${OUTPUT_DIR} \
130	     --image ${CONTAINER_IMAGE}
131
132	echo $? > ${OUTPUT_DIR}/${EXIT_STATUS_FILE}
133
134	set -e
135
136	${post_command}
137}
138
139is_rel_path()
140{
141	local path="$1"
142
143	[[ ${path:0:1} != "/" ]]
144}
145
146do_update_kconfig()
147{
148	local kernel_checkout="$1"
149	local kconfig_file="$2"
150
151	rm -f "$kconfig_file" 2> /dev/null
152
153	for config in "${KCONFIG_REL_PATHS[@]}"; do
154		local kconfig_src="${config}"
155		cat "$kconfig_src" >> "$kconfig_file"
156	done
157}
158
159update_kconfig()
160{
161	local kernel_checkout="$1"
162	local kconfig_file="$2"
163
164	if [[ -f "${kconfig_file}" ]]; then
165		local local_modified="$(stat -c %Y "${kconfig_file}")"
166
167		for config in "${KCONFIG_REL_PATHS[@]}"; do
168			local kconfig_src="${config}"
169			local src_modified="$(stat -c %Y "${kconfig_src}")"
170			# Only update the config if it has been updated after the
171			# previously cached config was created. This avoids
172			# unnecessarily compiling the kernel and selftests.
173			if [[ "${src_modified}" -gt "${local_modified}" ]]; then
174				do_update_kconfig "$kernel_checkout" "$kconfig_file"
175				# Once we have found one outdated configuration
176				# there is no need to check other ones.
177				break
178			fi
179		done
180	else
181		do_update_kconfig "$kernel_checkout" "$kconfig_file"
182	fi
183}
184
185main()
186{
187	local script_dir="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
188	local kernel_checkout=$(realpath "${script_dir}"/../../../../)
189	# By default the script searches for the kernel in the checkout directory but
190	# it also obeys environment variables O= and KBUILD_OUTPUT=
191	local kernel_bzimage="${kernel_checkout}/${BZIMAGE}"
192	local command="${DEFAULT_COMMAND}"
193	local update_b2c="no"
194	local debug_shell="no"
195	local build_only="no"
196
197	while getopts ':hsud:j:b' opt; do
198		case ${opt} in
199		u)
200			update_b2c="yes"
201			;;
202		d)
203			OUTPUT_DIR="$OPTARG"
204			;;
205		j)
206			NUM_COMPILE_JOBS="$OPTARG"
207			;;
208		s)
209			command="/bin/sh"
210			debug_shell="yes"
211			;;
212		b)
213			build_only="yes"
214			;;
215		h)
216			usage
217			exit 0
218			;;
219		\? )
220			echo "Invalid Option: -$OPTARG"
221			usage
222			exit 1
223			;;
224		: )
225			echo "Invalid Option: -$OPTARG requires an argument"
226			usage
227			exit 1
228			;;
229		esac
230	done
231	shift $((OPTIND -1))
232
233	# trap 'catch "$?"' EXIT
234	if [[ "${build_only}" == "no" && "${debug_shell}" == "no" ]]; then
235		if [[ $# -eq 0 ]]; then
236			echo "No command specified, will run ${DEFAULT_COMMAND} in the vm"
237		else
238			command="$@"
239
240			if [[ "${command}" == "/bin/bash" || "${command}" == "bash" ]]
241			then
242				debug_shell="yes"
243			fi
244		fi
245	fi
246
247	local kconfig_file="${OUTPUT_DIR}/latest.config"
248	local make_command="make -j ${NUM_COMPILE_JOBS} KCONFIG_CONFIG=${kconfig_file}"
249
250	# Figure out where the kernel is being built.
251	# O takes precedence over KBUILD_OUTPUT.
252	if [[ "${O:=""}" != "" ]]; then
253		if is_rel_path "${O}"; then
254			O="$(realpath "${PWD}/${O}")"
255		fi
256		kernel_bzimage="${O}/${BZIMAGE}"
257		make_command="${make_command} O=${O}"
258	elif [[ "${KBUILD_OUTPUT:=""}" != "" ]]; then
259		if is_rel_path "${KBUILD_OUTPUT}"; then
260			KBUILD_OUTPUT="$(realpath "${PWD}/${KBUILD_OUTPUT}")"
261		fi
262		kernel_bzimage="${KBUILD_OUTPUT}/${BZIMAGE}"
263		make_command="${make_command} KBUILD_OUTPUT=${KBUILD_OUTPUT}"
264	fi
265
266	local b2c="${OUTPUT_DIR}/vm2c.py"
267
268	echo "Output directory: ${OUTPUT_DIR}"
269
270	mkdir -p "${OUTPUT_DIR}"
271	update_kconfig "${kernel_checkout}" "${kconfig_file}"
272
273	recompile_kernel "${kernel_checkout}" "${make_command}"
274	update_selftests "${kernel_checkout}" "${make_command}"
275
276	if [[ "${build_only}" == "no" ]]; then
277		if [[ "${update_b2c}" == "no" && ! -f "${b2c}" ]]; then
278			echo "vm2c script not found in ${b2c}"
279			update_b2c="yes"
280		fi
281
282		if [[ "${update_b2c}" == "yes" ]]; then
283			download $B2C_URL $b2c
284			chmod +x $b2c
285		fi
286
287		run_vm "${kernel_checkout}" $b2c "${kernel_bzimage}" "${command}"
288		if [[ "${debug_shell}" != "yes" ]]; then
289			echo "Logs saved in ${OUTPUT_DIR}/${LOG_FILE}"
290		fi
291
292		exit $(cat ${OUTPUT_DIR}/${EXIT_STATUS_FILE})
293	fi
294}
295
296main "$@"
297