[PATCH 3/3] selftests: cgroup: Add vmtest-dmem runner based on hid vmtest

From: Albert Esteve

Date: Fri Mar 27 2026 - 04:54:38 EST


Currently, test_dmem relies on the dmem_selftest helper module
and a VM setup that may not have the helper preinstalled.
This makes automated coverage of dmem charge paths harder in
virtme-based runs.

Add tools/testing/selftests/cgroup/vmtest-dmem.sh, modeled
after the existing selftests vmtest runners
(notably tools/testing/selftests/hid/vmtest.sh),
to provide a repeatable VM workflow for dmem tests.

The script boots a virtme-ng guest, validates dmem
controller availability, ensures the dmem helper path is
present, and runs tools/testing/selftests/cgroup/test_dmem.
If the helper is not available as a loaded module, it
attempts module build/load for the running guest kernel
before executing the test binary.

The runner also supports interactive shell mode and
reuses the same verbosity and exit-code conventions
used by other vmtest scripts, so it integrates with existing
kselftest workflows.

Signed-off-by: Albert Esteve <aesteve@xxxxxxxxxx>
---
tools/testing/selftests/cgroup/vmtest-dmem.sh | 189 ++++++++++++++++++++++++++
1 file changed, 189 insertions(+)

diff --git a/tools/testing/selftests/cgroup/vmtest-dmem.sh b/tools/testing/selftests/cgroup/vmtest-dmem.sh
new file mode 100755
index 0000000000000..e481d3b2cdf8f
--- /dev/null
+++ b/tools/testing/selftests/cgroup/vmtest-dmem.sh
@@ -0,0 +1,189 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (c) 2026 Red Hat, Inc.
+#
+# Run cgroup test_dmem inside a virtme-ng VM.
+# Dependencies:
+# * virtme-ng
+# * busybox-static (used by virtme-ng)
+# * qemu (used by virtme-ng)
+
+set -euo pipefail
+
+readonly SCRIPT_DIR="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
+readonly KERNEL_CHECKOUT="$(realpath "${SCRIPT_DIR}"/../../../../)"
+
+source "${SCRIPT_DIR}"/../kselftest/ktap_helpers.sh
+
+readonly SSH_GUEST_PORT="${SSH_GUEST_PORT:-22}"
+readonly WAIT_PERIOD=3
+readonly WAIT_PERIOD_MAX=80
+readonly WAIT_TOTAL=$((WAIT_PERIOD * WAIT_PERIOD_MAX))
+readonly QEMU_PIDFILE="$(mktemp /tmp/qemu_dmem_vmtest_XXXX.pid)"
+readonly QEMU_OPTS=" --pidfile ${QEMU_PIDFILE} "
+
+QEMU="qemu-system-$(uname -m)"
+VERBOSE=0
+SHELL_MODE=0
+GUEST_TREE="${GUEST_TREE:-$KERNEL_CHECKOUT}"
+
+usage() {
+ echo
+ echo "$0 [OPTIONS]"
+ echo " -q <qemu> QEMU binary/path (default: ${QEMU})"
+ echo " -s Start interactive shell in VM"
+ echo " -v Verbose output (use -vv for vng boot logs)"
+ echo
+}
+
+die() {
+ echo "$*" >&2
+ exit "${KSFT_FAIL}"
+}
+
+cleanup() {
+ if [[ -s "${QEMU_PIDFILE}" ]]; then
+ pkill -SIGTERM -F "${QEMU_PIDFILE}" >/dev/null 2>&1 || true
+ fi
+ rm -f "${QEMU_PIDFILE}"
+}
+
+vm_ssh() {
+ stdbuf -oL ssh -q \
+ -F "${HOME}/.cache/virtme-ng/.ssh/virtme-ng-ssh.conf" \
+ -l root "virtme-ng%${SSH_GUEST_PORT}" \
+ "$@"
+}
+
+check_deps() {
+ for dep in vng "${QEMU}" busybox pkill ssh; do
+ if ! command -v "${dep}" >/dev/null 2>&1; then
+ echo "skip: dependency ${dep} not found"
+ exit "${KSFT_SKIP}"
+ fi
+ done
+}
+
+vm_start() {
+ local logfile=/dev/null
+ local verbose_opt=""
+
+ if [[ "${VERBOSE}" -eq 2 ]]; then
+ verbose_opt="--verbose"
+ logfile=/dev/stdout
+ fi
+
+ vng \
+ --run \
+ ${verbose_opt} \
+ --qemu-opts="${QEMU_OPTS}" \
+ --qemu="$(command -v "${QEMU}")" \
+ --user root \
+ --ssh "${SSH_GUEST_PORT}" \
+ --rw &>"${logfile}" &
+
+ local vng_pid=$!
+ local elapsed=0
+
+ while [[ ! -s "${QEMU_PIDFILE}" ]]; do
+ kill -0 "${vng_pid}" 2>/dev/null || die "vng exited early; failed to boot VM"
+ [[ "${elapsed}" -ge "${WAIT_TOTAL}" ]] && die "timed out waiting for VM boot"
+ sleep 1
+ elapsed=$((elapsed + 1))
+ done
+}
+
+vm_wait_for_ssh() {
+ local i=0
+ while true; do
+ vm_ssh -- true && break
+ i=$((i + 1))
+ [[ "${i}" -gt "${WAIT_PERIOD_MAX}" ]] && die "timed out waiting for guest ssh"
+ sleep "${WAIT_PERIOD}"
+ done
+}
+
+check_guest_requirements() {
+ local cfg_ok
+ cfg_ok="$(vm_ssh -- "cfg=/boot/config-\$(uname -r); \
+ if [[ -r \"\$cfg\" ]]; then grep -Eq '^CONFIG_CGROUP_DMEM=(y|m)$' \"\$cfg\"; \
+ elif [[ -r /proc/config.gz ]]; then \
+ zgrep -Eq '^CONFIG_CGROUP_DMEM=(y|m)$' /proc/config.gz; \
+ else false; fi; echo \$?")"
+ [[ "${cfg_ok}" == "0" ]] || die "guest kernel missing CONFIG_CGROUP_DMEM"
+}
+
+setup_guest_dmem_helper() {
+ local kdir
+
+ vm_ssh -- "mountpoint -q /sys/kernel/debug || \
+ mount -t debugfs none /sys/kernel/debug" || true
+
+ # Already available (built-in or loaded).
+ if vm_ssh -- "[[ -e /sys/kernel/debug/dmem_selftest/charge ]]"; then
+ echo "dmem_selftest ready"
+ return 0
+ fi
+
+ # Fast path: try installed module.
+ vm_ssh -- "modprobe -q dmem_selftest 2>/dev/null || true"
+ if vm_ssh -- "[[ -e /sys/kernel/debug/dmem_selftest/charge ]]"; then
+ echo "dmem_selftest ready"
+ return 0
+ fi
+
+ # Fallback: build only this module against running guest kernel,
+ # then insert it.
+ kdir="$(vm_ssh -- "echo /lib/modules/\$(uname -r)/build")"
+ if vm_ssh -- "[[ -d '${kdir}' ]]"; then
+ echo "Building dmem_selftest.ko against running guest kernel..."
+ vm_ssh -- "make -C '${kdir}' \
+ M='${GUEST_TREE}/kernel/cgroup' \
+ CONFIG_DMEM_SELFTEST=m modules"
+ vm_ssh -- "insmod '${GUEST_TREE}/kernel/cgroup/dmem_selftest.ko' \
+ 2>/dev/null || modprobe -q dmem_selftest 2>/dev/null || true"
+ fi
+
+ if vm_ssh -- "[[ -e /sys/kernel/debug/dmem_selftest/charge ]]"; then
+ echo "dmem_selftest ready"
+ return 0
+ fi
+
+ die "dmem_selftest unavailable (modprobe/build+insmod failed)"
+}
+
+run_test() {
+ vm_ssh -- "cd '${GUEST_TREE}' && make -C tools/testing/selftests TARGETS=cgroup"
+ vm_ssh -- "cd '${GUEST_TREE}' && ./tools/testing/selftests/cgroup/test_dmem"
+}
+
+while getopts ":hvq:s" o; do
+ case "${o}" in
+ v) VERBOSE=$((VERBOSE + 1)) ;;
+ q) QEMU="${OPTARG}" ;;
+ s) SHELL_MODE=1 ;;
+ h|*) usage ;;
+ esac
+done
+
+trap cleanup EXIT
+
+check_deps
+echo "Booting virtme-ng VM..."
+vm_start
+vm_wait_for_ssh
+echo "VM is reachable via SSH."
+check_guest_requirements
+setup_guest_dmem_helper
+
+if [[ "${SHELL_MODE}" -eq 1 ]]; then
+ echo "Starting interactive shell in VM. Exit to stop VM."
+ vm_ssh -t -- "cd '${GUEST_TREE}' && exec bash --noprofile --norc"
+ exit "${KSFT_PASS}"
+fi
+
+echo "Running cgroup/test_dmem in VM..."
+run_test
+echo "PASS: test_dmem completed"
+exit "${KSFT_PASS}"

--
2.52.0