Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/tools/testing/selftests/hid/vmtest.sh
29524 views
1
#!/bin/bash
2
# SPDX-License-Identifier: GPL-2.0
3
#
4
# Copyright (c) 2025 Red Hat
5
# Copyright (c) 2025 Meta Platforms, Inc. and affiliates
6
#
7
# Dependencies:
8
# * virtme-ng
9
# * busybox-static (used by virtme-ng)
10
# * qemu (used by virtme-ng)
11
12
readonly SCRIPT_DIR="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
13
readonly KERNEL_CHECKOUT=$(realpath "${SCRIPT_DIR}"/../../../../)
14
15
source "${SCRIPT_DIR}"/../kselftest/ktap_helpers.sh
16
17
readonly HID_BPF_TEST="${SCRIPT_DIR}"/hid_bpf
18
readonly HIDRAW_TEST="${SCRIPT_DIR}"/hidraw
19
readonly HID_BPF_PROGS="${KERNEL_CHECKOUT}/drivers/hid/bpf/progs"
20
readonly SSH_GUEST_PORT=22
21
readonly WAIT_PERIOD=3
22
readonly WAIT_PERIOD_MAX=60
23
readonly WAIT_TOTAL=$(( WAIT_PERIOD * WAIT_PERIOD_MAX ))
24
readonly QEMU_PIDFILE=$(mktemp /tmp/qemu_hid_vmtest_XXXX.pid)
25
26
readonly QEMU_OPTS="\
27
--pidfile ${QEMU_PIDFILE} \
28
"
29
readonly KERNEL_CMDLINE=""
30
readonly LOG=$(mktemp /tmp/hid_vmtest_XXXX.log)
31
readonly TEST_NAMES=(vm_hid_bpf vm_hidraw vm_pytest)
32
readonly TEST_DESCS=(
33
"Run hid_bpf tests in the VM."
34
"Run hidraw tests in the VM."
35
"Run the hid-tools test-suite in the VM."
36
)
37
38
VERBOSE=0
39
SHELL_MODE=0
40
BUILD_HOST=""
41
BUILD_HOST_PODMAN_CONTAINER_NAME=""
42
43
usage() {
44
local name
45
local desc
46
local i
47
48
echo
49
echo "$0 [OPTIONS] [TEST]... [-- tests-args]"
50
echo "If no TEST argument is given, all tests will be run."
51
echo
52
echo "Options"
53
echo " -b: build the kernel from the current source tree and use it for guest VMs"
54
echo " -H: hostname for remote build host (used with -b)"
55
echo " -p: podman container name for remote build host (used with -b)"
56
echo " Example: -H beefyserver -p vng"
57
echo " -q: set the path to or name of qemu binary"
58
echo " -s: start a shell in the VM instead of running tests"
59
echo " -v: more verbose output (can be repeated multiple times)"
60
echo
61
echo "Available tests"
62
63
for ((i = 0; i < ${#TEST_NAMES[@]}; i++)); do
64
name=${TEST_NAMES[${i}]}
65
desc=${TEST_DESCS[${i}]}
66
printf "\t%-35s%-35s\n" "${name}" "${desc}"
67
done
68
echo
69
70
exit 1
71
}
72
73
die() {
74
echo "$*" >&2
75
exit "${KSFT_FAIL}"
76
}
77
78
vm_ssh() {
79
# vng --ssh-client keeps shouting "Warning: Permanently added 'virtme-ng%22'
80
# (ED25519) to the list of known hosts.",
81
# So replace the command with what's actually called and add the "-q" option
82
stdbuf -oL ssh -q \
83
-F ${HOME}/.cache/virtme-ng/.ssh/virtme-ng-ssh.conf \
84
-l root virtme-ng%${SSH_GUEST_PORT} \
85
"$@"
86
return $?
87
}
88
89
cleanup() {
90
if [[ -s "${QEMU_PIDFILE}" ]]; then
91
pkill -SIGTERM -F "${QEMU_PIDFILE}" > /dev/null 2>&1
92
fi
93
94
# If failure occurred during or before qemu start up, then we need
95
# to clean this up ourselves.
96
if [[ -e "${QEMU_PIDFILE}" ]]; then
97
rm "${QEMU_PIDFILE}"
98
fi
99
}
100
101
check_args() {
102
local found
103
104
for arg in "$@"; do
105
found=0
106
for name in "${TEST_NAMES[@]}"; do
107
if [[ "${name}" = "${arg}" ]]; then
108
found=1
109
break
110
fi
111
done
112
113
if [[ "${found}" -eq 0 ]]; then
114
echo "${arg} is not an available test" >&2
115
usage
116
fi
117
done
118
119
for arg in "$@"; do
120
if ! command -v > /dev/null "test_${arg}"; then
121
echo "Test ${arg} not found" >&2
122
usage
123
fi
124
done
125
}
126
127
check_deps() {
128
for dep in vng ${QEMU} busybox pkill ssh pytest; do
129
if [[ ! -x $(command -v "${dep}") ]]; then
130
echo -e "skip: dependency ${dep} not found!\n"
131
exit "${KSFT_SKIP}"
132
fi
133
done
134
135
if [[ ! -x $(command -v "${HID_BPF_TEST}") ]]; then
136
printf "skip: %s not found!" "${HID_BPF_TEST}"
137
printf " Please build the kselftest hid_bpf target.\n"
138
exit "${KSFT_SKIP}"
139
fi
140
141
if [[ ! -x $(command -v "${HIDRAW_TEST}") ]]; then
142
printf "skip: %s not found!" "${HIDRAW_TEST}"
143
printf " Please build the kselftest hidraw target.\n"
144
exit "${KSFT_SKIP}"
145
fi
146
}
147
148
check_vng() {
149
local tested_versions
150
local version
151
local ok
152
153
tested_versions=("1.36" "1.37")
154
version="$(vng --version)"
155
156
ok=0
157
for tv in "${tested_versions[@]}"; do
158
if [[ "${version}" == *"${tv}"* ]]; then
159
ok=1
160
break
161
fi
162
done
163
164
if [[ ! "${ok}" -eq 1 ]]; then
165
printf "warning: vng version '%s' has not been tested and may " "${version}" >&2
166
printf "not function properly.\n\tThe following versions have been tested: " >&2
167
echo "${tested_versions[@]}" >&2
168
fi
169
}
170
171
handle_build() {
172
if [[ ! "${BUILD}" -eq 1 ]]; then
173
return
174
fi
175
176
if [[ ! -d "${KERNEL_CHECKOUT}" ]]; then
177
echo "-b requires vmtest.sh called from the kernel source tree" >&2
178
exit 1
179
fi
180
181
pushd "${KERNEL_CHECKOUT}" &>/dev/null
182
183
if ! vng --kconfig --config "${SCRIPT_DIR}"/config; then
184
die "failed to generate .config for kernel source tree (${KERNEL_CHECKOUT})"
185
fi
186
187
local vng_args=("-v" "--config" "${SCRIPT_DIR}/config" "--build")
188
189
if [[ -n "${BUILD_HOST}" ]]; then
190
vng_args+=("--build-host" "${BUILD_HOST}")
191
fi
192
193
if [[ -n "${BUILD_HOST_PODMAN_CONTAINER_NAME}" ]]; then
194
vng_args+=("--build-host-exec-prefix" \
195
"podman exec -ti ${BUILD_HOST_PODMAN_CONTAINER_NAME}")
196
fi
197
198
if ! vng "${vng_args[@]}"; then
199
die "failed to build kernel from source tree (${KERNEL_CHECKOUT})"
200
fi
201
202
if ! make -j$(nproc) -C "${HID_BPF_PROGS}"; then
203
die "failed to build HID bpf objects from source tree (${HID_BPF_PROGS})"
204
fi
205
206
if ! make -j$(nproc) -C "${SCRIPT_DIR}"; then
207
die "failed to build HID selftests from source tree (${SCRIPT_DIR})"
208
fi
209
210
popd &>/dev/null
211
}
212
213
vm_start() {
214
local logfile=/dev/null
215
local verbose_opt=""
216
local kernel_opt=""
217
local qemu
218
219
qemu=$(command -v "${QEMU}")
220
221
if [[ "${VERBOSE}" -eq 2 ]]; then
222
verbose_opt="--verbose"
223
logfile=/dev/stdout
224
fi
225
226
# If we are running from within the kernel source tree, use the kernel source tree
227
# as the kernel to boot, otherwise use the currently running kernel.
228
if [[ "$(realpath "$(pwd)")" == "${KERNEL_CHECKOUT}"* ]]; then
229
kernel_opt="${KERNEL_CHECKOUT}"
230
fi
231
232
vng \
233
--run \
234
${kernel_opt} \
235
${verbose_opt} \
236
--qemu-opts="${QEMU_OPTS}" \
237
--qemu="${qemu}" \
238
--user root \
239
--append "${KERNEL_CMDLINE}" \
240
--ssh "${SSH_GUEST_PORT}" \
241
--rw &> ${logfile} &
242
243
local vng_pid=$!
244
local elapsed=0
245
246
while [[ ! -s "${QEMU_PIDFILE}" ]]; do
247
if ! kill -0 "${vng_pid}" 2>/dev/null; then
248
echo "vng process (PID ${vng_pid}) exited early, check logs for details" >&2
249
die "failed to boot VM"
250
fi
251
252
if [[ ${elapsed} -ge ${WAIT_TOTAL} ]]; then
253
echo "Timed out after ${WAIT_TOTAL} seconds waiting for VM to boot" >&2
254
die "failed to boot VM"
255
fi
256
257
sleep 1
258
elapsed=$((elapsed + 1))
259
done
260
}
261
262
vm_wait_for_ssh() {
263
local i
264
265
i=0
266
while true; do
267
if [[ ${i} -gt ${WAIT_PERIOD_MAX} ]]; then
268
die "Timed out waiting for guest ssh"
269
fi
270
if vm_ssh -- true; then
271
break
272
fi
273
i=$(( i + 1 ))
274
sleep ${WAIT_PERIOD}
275
done
276
}
277
278
vm_mount_bpffs() {
279
vm_ssh -- mount bpffs -t bpf /sys/fs/bpf
280
}
281
282
__log_stdin() {
283
stdbuf -oL awk '{ printf "%s:\t%s\n","'"${prefix}"'", $0; fflush() }'
284
}
285
286
__log_args() {
287
echo "$*" | awk '{ printf "%s:\t%s\n","'"${prefix}"'", $0 }'
288
}
289
290
log() {
291
local verbose="$1"
292
shift
293
294
local prefix="$1"
295
296
shift
297
local redirect=
298
if [[ ${verbose} -le 0 ]]; then
299
redirect=/dev/null
300
else
301
redirect=/dev/stdout
302
fi
303
304
if [[ "$#" -eq 0 ]]; then
305
__log_stdin | tee -a "${LOG}" > ${redirect}
306
else
307
__log_args "$@" | tee -a "${LOG}" > ${redirect}
308
fi
309
}
310
311
log_setup() {
312
log $((VERBOSE-1)) "setup" "$@"
313
}
314
315
log_host() {
316
local testname=$1
317
318
shift
319
log $((VERBOSE-1)) "test:${testname}:host" "$@"
320
}
321
322
log_guest() {
323
local testname=$1
324
325
shift
326
log ${VERBOSE} "# test:${testname}" "$@"
327
}
328
329
test_vm_hid_bpf() {
330
local testname="${FUNCNAME[0]#test_}"
331
332
vm_ssh -- "${HID_BPF_TEST}" \
333
2>&1 | log_guest "${testname}"
334
335
return ${PIPESTATUS[0]}
336
}
337
338
test_vm_hidraw() {
339
local testname="${FUNCNAME[0]#test_}"
340
341
vm_ssh -- "${HIDRAW_TEST}" \
342
2>&1 | log_guest "${testname}"
343
344
return ${PIPESTATUS[0]}
345
}
346
347
test_vm_pytest() {
348
local testname="${FUNCNAME[0]#test_}"
349
350
shift
351
352
vm_ssh -- pytest ${SCRIPT_DIR}/tests --color=yes "$@" \
353
2>&1 | log_guest "${testname}"
354
355
return ${PIPESTATUS[0]}
356
}
357
358
run_test() {
359
local vm_oops_cnt_before
360
local vm_warn_cnt_before
361
local vm_oops_cnt_after
362
local vm_warn_cnt_after
363
local name
364
local rc
365
366
vm_oops_cnt_before=$(vm_ssh -- dmesg | grep -c -i 'Oops')
367
vm_error_cnt_before=$(vm_ssh -- dmesg --level=err | wc -l)
368
369
name=$(echo "${1}" | awk '{ print $1 }')
370
eval test_"${name}" "$@"
371
rc=$?
372
373
vm_oops_cnt_after=$(vm_ssh -- dmesg | grep -i 'Oops' | wc -l)
374
if [[ ${vm_oops_cnt_after} -gt ${vm_oops_cnt_before} ]]; then
375
echo "FAIL: kernel oops detected on vm" | log_host "${name}"
376
rc=$KSFT_FAIL
377
fi
378
379
vm_error_cnt_after=$(vm_ssh -- dmesg --level=err | wc -l)
380
if [[ ${vm_error_cnt_after} -gt ${vm_error_cnt_before} ]]; then
381
echo "FAIL: kernel error detected on vm" | log_host "${name}"
382
vm_ssh -- dmesg --level=err | log_host "${name}"
383
rc=$KSFT_FAIL
384
fi
385
386
return "${rc}"
387
}
388
389
QEMU="qemu-system-$(uname -m)"
390
391
while getopts :hvsbq:H:p: o
392
do
393
case $o in
394
v) VERBOSE=$((VERBOSE+1));;
395
s) SHELL_MODE=1;;
396
b) BUILD=1;;
397
q) QEMU=$OPTARG;;
398
H) BUILD_HOST=$OPTARG;;
399
p) BUILD_HOST_PODMAN_CONTAINER_NAME=$OPTARG;;
400
h|*) usage;;
401
esac
402
done
403
shift $((OPTIND-1))
404
405
trap cleanup EXIT
406
407
PARAMS=""
408
409
if [[ ${#} -eq 0 ]]; then
410
ARGS=("${TEST_NAMES[@]}")
411
else
412
ARGS=()
413
COUNT=0
414
for arg in $@; do
415
COUNT=$((COUNT+1))
416
if [[ x"$arg" == x"--" ]]; then
417
break
418
fi
419
ARGS+=($arg)
420
done
421
shift $COUNT
422
PARAMS="$@"
423
fi
424
425
if [[ "${SHELL_MODE}" -eq 0 ]]; then
426
check_args "${ARGS[@]}"
427
echo "1..${#ARGS[@]}"
428
fi
429
check_deps
430
check_vng
431
handle_build
432
433
log_setup "Booting up VM"
434
vm_start
435
vm_wait_for_ssh
436
vm_mount_bpffs
437
log_setup "VM booted up"
438
439
if [[ "${SHELL_MODE}" -eq 1 ]]; then
440
log_setup "Starting interactive shell in VM"
441
echo "Starting shell in VM. Use 'exit' to quit and shutdown the VM."
442
CURRENT_DIR="$(pwd)"
443
vm_ssh -t -- "cd '${CURRENT_DIR}' && exec bash -l"
444
exit "$KSFT_PASS"
445
fi
446
447
cnt_pass=0
448
cnt_fail=0
449
cnt_skip=0
450
cnt_total=0
451
for arg in "${ARGS[@]}"; do
452
run_test "${arg}" "${PARAMS}"
453
rc=$?
454
if [[ ${rc} -eq $KSFT_PASS ]]; then
455
cnt_pass=$(( cnt_pass + 1 ))
456
echo "ok ${cnt_total} ${arg}"
457
elif [[ ${rc} -eq $KSFT_SKIP ]]; then
458
cnt_skip=$(( cnt_skip + 1 ))
459
echo "ok ${cnt_total} ${arg} # SKIP"
460
elif [[ ${rc} -eq $KSFT_FAIL ]]; then
461
cnt_fail=$(( cnt_fail + 1 ))
462
echo "not ok ${cnt_total} ${arg} # exit=$rc"
463
fi
464
cnt_total=$(( cnt_total + 1 ))
465
done
466
467
echo "SUMMARY: PASS=${cnt_pass} SKIP=${cnt_skip} FAIL=${cnt_fail}"
468
echo "Log: ${LOG}"
469
470
if [ $((cnt_pass + cnt_skip)) -eq ${cnt_total} ]; then
471
exit "$KSFT_PASS"
472
else
473
exit "$KSFT_FAIL"
474
fi
475
476