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/mupuf/boot2container/-/raw/master/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/libevdev/hid-tools/fedora/37:2023-02-17.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 [-i] [-s] [-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	-j)		Number of jobs for compilation, similar to -j in make
59			(default: ${NUM_COMPILE_JOBS})
60	-s)		Instead of powering off the VM, start an interactive
61			shell. If <command> is specified, the shell runs after
62			the command finishes executing
63EOF
64}
65
66download()
67{
68	local file="$1"
69
70	echo "Downloading $file..." >&2
71	curl -Lsf "$file" -o "${@:2}"
72}
73
74recompile_kernel()
75{
76	local kernel_checkout="$1"
77	local make_command="$2"
78
79	cd "${kernel_checkout}"
80
81	${make_command} olddefconfig
82	${make_command} headers
83	${make_command}
84}
85
86update_selftests()
87{
88	local kernel_checkout="$1"
89	local selftests_dir="${kernel_checkout}/tools/testing/selftests/hid"
90
91	cd "${selftests_dir}"
92	${make_command}
93}
94
95run_vm()
96{
97	local run_dir="$1"
98	local b2c="$2"
99	local kernel_bzimage="$3"
100	local command="$4"
101	local post_command=""
102
103	cd "${run_dir}"
104
105	if ! which "${QEMU_BINARY}" &> /dev/null; then
106		cat <<EOF
107Could not find ${QEMU_BINARY}
108Please install qemu or set the QEMU_BINARY environment variable.
109EOF
110		exit 1
111	fi
112
113	# alpine (used in post-container requires the PATH to have /bin
114	export PATH=$PATH:/bin
115
116	if [[ "${debug_shell}" != "yes" ]]
117	then
118		touch ${OUTPUT_DIR}/${LOG_FILE}
119		command="mount bpffs -t bpf /sys/fs/bpf/; set -o pipefail ; ${command} 2>&1 | tee ${OUTPUT_DIR}/${LOG_FILE}"
120		post_command="cat ${OUTPUT_DIR}/${LOG_FILE}"
121	else
122		command="mount bpffs -t bpf /sys/fs/bpf/; ${command}"
123	fi
124
125	set +e
126	$b2c --command "${command}" \
127	     --kernel ${kernel_bzimage} \
128	     --workdir ${OUTPUT_DIR} \
129	     --image ${CONTAINER_IMAGE}
130
131	echo $? > ${OUTPUT_DIR}/${EXIT_STATUS_FILE}
132
133	set -e
134
135	${post_command}
136}
137
138is_rel_path()
139{
140	local path="$1"
141
142	[[ ${path:0:1} != "/" ]]
143}
144
145do_update_kconfig()
146{
147	local kernel_checkout="$1"
148	local kconfig_file="$2"
149
150	rm -f "$kconfig_file" 2> /dev/null
151
152	for config in "${KCONFIG_REL_PATHS[@]}"; do
153		local kconfig_src="${config}"
154		cat "$kconfig_src" >> "$kconfig_file"
155	done
156}
157
158update_kconfig()
159{
160	local kernel_checkout="$1"
161	local kconfig_file="$2"
162
163	if [[ -f "${kconfig_file}" ]]; then
164		local local_modified="$(stat -c %Y "${kconfig_file}")"
165
166		for config in "${KCONFIG_REL_PATHS[@]}"; do
167			local kconfig_src="${config}"
168			local src_modified="$(stat -c %Y "${kconfig_src}")"
169			# Only update the config if it has been updated after the
170			# previously cached config was created. This avoids
171			# unnecessarily compiling the kernel and selftests.
172			if [[ "${src_modified}" -gt "${local_modified}" ]]; then
173				do_update_kconfig "$kernel_checkout" "$kconfig_file"
174				# Once we have found one outdated configuration
175				# there is no need to check other ones.
176				break
177			fi
178		done
179	else
180		do_update_kconfig "$kernel_checkout" "$kconfig_file"
181	fi
182}
183
184main()
185{
186	local script_dir="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
187	local kernel_checkout=$(realpath "${script_dir}"/../../../../)
188	# By default the script searches for the kernel in the checkout directory but
189	# it also obeys environment variables O= and KBUILD_OUTPUT=
190	local kernel_bzimage="${kernel_checkout}/${BZIMAGE}"
191	local command="${DEFAULT_COMMAND}"
192	local update_b2c="no"
193	local debug_shell="no"
194
195	while getopts ':hsud:j:' opt; do
196		case ${opt} in
197		u)
198			update_b2c="yes"
199			;;
200		d)
201			OUTPUT_DIR="$OPTARG"
202			;;
203		j)
204			NUM_COMPILE_JOBS="$OPTARG"
205			;;
206		s)
207			command="/bin/sh"
208			debug_shell="yes"
209			;;
210		h)
211			usage
212			exit 0
213			;;
214		\? )
215			echo "Invalid Option: -$OPTARG"
216			usage
217			exit 1
218			;;
219		: )
220			echo "Invalid Option: -$OPTARG requires an argument"
221			usage
222			exit 1
223			;;
224		esac
225	done
226	shift $((OPTIND -1))
227
228	# trap 'catch "$?"' EXIT
229
230	if [[ "${debug_shell}" == "no" ]]; then
231		if [[ $# -eq 0 ]]; then
232			echo "No command specified, will run ${DEFAULT_COMMAND} in the vm"
233		else
234			command="$@"
235
236			if [[ "${command}" == "/bin/bash" || "${command}" == "bash" ]]
237			then
238				debug_shell="yes"
239			fi
240		fi
241	fi
242
243	local kconfig_file="${OUTPUT_DIR}/latest.config"
244	local make_command="make -j ${NUM_COMPILE_JOBS} KCONFIG_CONFIG=${kconfig_file}"
245
246	# Figure out where the kernel is being built.
247	# O takes precedence over KBUILD_OUTPUT.
248	if [[ "${O:=""}" != "" ]]; then
249		if is_rel_path "${O}"; then
250			O="$(realpath "${PWD}/${O}")"
251		fi
252		kernel_bzimage="${O}/${BZIMAGE}"
253		make_command="${make_command} O=${O}"
254	elif [[ "${KBUILD_OUTPUT:=""}" != "" ]]; then
255		if is_rel_path "${KBUILD_OUTPUT}"; then
256			KBUILD_OUTPUT="$(realpath "${PWD}/${KBUILD_OUTPUT}")"
257		fi
258		kernel_bzimage="${KBUILD_OUTPUT}/${BZIMAGE}"
259		make_command="${make_command} KBUILD_OUTPUT=${KBUILD_OUTPUT}"
260	fi
261
262	local b2c="${OUTPUT_DIR}/vm2c.py"
263
264	echo "Output directory: ${OUTPUT_DIR}"
265
266	mkdir -p "${OUTPUT_DIR}"
267	update_kconfig "${kernel_checkout}" "${kconfig_file}"
268
269	recompile_kernel "${kernel_checkout}" "${make_command}"
270
271	if [[ "${update_b2c}" == "no" && ! -f "${b2c}" ]]; then
272		echo "vm2c script not found in ${b2c}"
273		update_b2c="yes"
274	fi
275
276	if [[ "${update_b2c}" == "yes" ]]; then
277		download $B2C_URL $b2c
278		chmod +x $b2c
279	fi
280
281	update_selftests "${kernel_checkout}" "${make_command}"
282	run_vm "${kernel_checkout}" $b2c "${kernel_bzimage}" "${command}"
283	if [[ "${debug_shell}" != "yes" ]]; then
284		echo "Logs saved in ${OUTPUT_DIR}/${LOG_FILE}"
285	fi
286
287	exit $(cat ${OUTPUT_DIR}/${EXIT_STATUS_FILE})
288}
289
290main "$@"
291