Skip to content

Commit 477906e

Browse files
committed
Add Idle HLT Intercept testcase
Introduces a test case to verify the Idle HLT Intercept feature in QEMU, using cpuid to check support and ftrace to monitor idle-halt exits. Supports secure guest types (SEV, SEV-ES, SNP) with configurable parameters. Signed-off-by: Srikanth Aithal <srikanth.aithal@amd.com>
1 parent aa12759 commit 477906e

2 files changed

Lines changed: 227 additions & 0 deletions

File tree

qemu/tests/cfg/idlehlt.cfg

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
- idlehlt:
2+
type = idlehlt
3+
only Linux
4+
only HostCpuVendor.amd
5+
kill_vm = yes
6+
login_timeout = 240
7+
start_vm = no
8+
image_snapshot = yes
9+
mem = 8192
10+
smp = 8
11+
virtio_dev_iommu_platform = on
12+
virtio_dev_disable_legacy = on
13+
trace_dir = "/sys/kernel/tracing"
14+
hlt_exit_reason = "0x0a6"
15+
url_cpuid_tool = "http://www.etallen.com/cpuid/cpuid-20250513.src.tar.gz"
16+
bios_path = /usr/share/ovmf/OVMF.fd
17+
module_status = Y y 1
18+
variants:
19+
- svm:
20+
- cvm:
21+
vm_sev_reduced_phys_bits = 1
22+
variants:
23+
- sev:
24+
vm_secure_guest_type = sev
25+
required_qemu = [2.12, )
26+
sev_module_path = "/sys/module/kvm_amd/parameters/sev"
27+
sev_guest_check = "journalctl | grep -i -w sev"
28+
vm_sev_policy = 3
29+
- seves:
30+
vm_secure_guest_type = sev
31+
required_qemu = [6.0, )
32+
sev_module_path = "/sys/module/kvm_amd/parameters/sev_es"
33+
sev_guest_check = "journalctl | grep -i -w sev-es"
34+
vm_sev_policy = 7
35+
- snp:
36+
vm_secure_guest_type = snp
37+
required_qemu = [9.1.0, )
38+
snp_module_path = "/sys/module/kvm_amd/parameters/sev_snp"
39+
snp_guest_check = "journalctl | grep -i -w snp"
40+
vm_sev_policy = 196608
41+
vm_mem_backend = memory-backend-memfd

qemu/tests/idlehlt.py

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
import os
2+
import re
3+
import shutil
4+
import time
5+
6+
from avocado.utils import archive, build, process
7+
from virttest import error_context
8+
from virttest.utils_misc import verify_dmesg, verify_secure_guest, verify_secure_host
9+
10+
11+
@error_context.context_aware
12+
def run(test, params, env):
13+
"""
14+
QEMU test case to verify the Idle HLT Intercept feature and
15+
monitor idle-halt exits using ftrace.
16+
17+
:param test: QEMU test object for logging and test control.
18+
:param params: Dictionary with test parameters (e.g., vm_secure_guest_type,
19+
url_cpuid_tool).
20+
:param env: Dictionary with test environment, including VM configuration.
21+
"""
22+
23+
def setup_ftrace(hlt_exit_reason):
24+
# Set up ftrace
25+
error_context.context("Configuring ftrace for kvm:kvm_exit", test.log.info)
26+
if not os.path.exists(trace_dir):
27+
test.cancel("ftrace not available at {}".format(trace_dir))
28+
29+
try:
30+
with open(os.path.join(trace_dir, "tracing_on"), "w") as f:
31+
f.write("0")
32+
with open(os.path.join(trace_dir, "trace"), "w") as f:
33+
f.write("")
34+
with open(os.path.join(trace_dir, "events/kvm/kvm_exit/enable"), "w") as f:
35+
f.write("1")
36+
filter_text = "exit_reason == " + hlt_exit_reason
37+
with open(os.path.join(trace_dir, "events/kvm/kvm_exit/filter"), "w") as f:
38+
f.write(filter_text)
39+
test.log.info(
40+
"ftrace configured for kvm:kvm_exit with exit_reason == %s.",
41+
hlt_exit_reason,
42+
)
43+
except (IOError, PermissionError) as e:
44+
test.cancel("Failed to configure ftrace: {}".format(e))
45+
46+
def cpuid_tool_build():
47+
"""
48+
Build and install cpuid from source tarball if not already installed.
49+
"""
50+
error_context.context("Building cpuid tool from source", test.log.info)
51+
test.log.info("Using cpuid source URL: %s", url_cpuid_tool)
52+
53+
# Check for build tools
54+
for tool in ["make", "gcc", "tar"]:
55+
if not shutil.which(tool):
56+
test.cancel(
57+
f"Build tool {tool} not found. Please install it "
58+
f"(e.g., 'sudo apt install build-essential')."
59+
)
60+
61+
try:
62+
# Download the tarball
63+
tarball = test.fetch_asset(url_cpuid_tool)
64+
test.log.info("Downloaded cpuid source: %s", tarball)
65+
66+
# Extract tarball
67+
source_dir_name = os.path.basename(tarball).split(".src.tar.")[0]
68+
sourcedir = os.path.join(test.teststmpdir, source_dir_name)
69+
archive.extract(tarball, test.teststmpdir)
70+
test.log.info("Extracted cpuid source to %s", sourcedir)
71+
72+
# Build and install (use sudo for make install)
73+
build.make(sourcedir, extra_args="install", ignore_status=False)
74+
test.log.info("Successfully built and installed cpuid")
75+
76+
# Verify installation
77+
cpuid_path = shutil.which("cpuid")
78+
if not cpuid_path:
79+
test.fail("cpuid binary not found in PATH after installation")
80+
81+
# Verify cpuid works
82+
result = process.run("cpuid --version", shell=True, ignore_status=True)
83+
if result.exit_status != 0:
84+
test.fail(
85+
"Installed cpuid tool failed to execute: {}".format(
86+
result.stderr.decode()
87+
)
88+
)
89+
test.log.info(
90+
"cpuid tool installed and verified: %s", result.stdout.decode().strip()
91+
)
92+
93+
except Exception as e:
94+
test.cancel("Failed to build/install test prerequisite: {}".format(e))
95+
96+
if params.get("vm_secure_guest_type"):
97+
secure_guest_type = params.get("vm_secure_guest_type")
98+
supported_secureguest = ["sev", "snp"]
99+
if secure_guest_type not in supported_secureguest:
100+
test.cancel(
101+
"Testcase does not support vm_secure_guest_type %s" % secure_guest_type
102+
)
103+
# Check host kernel sev support
104+
verify_secure_host(params)
105+
error_context.context("Setting up test environment", test.log.info)
106+
timeout = params.get_numeric("login_timeout", 240)
107+
trace_dir = params.get("trace_dir", "/sys/kernel/tracing")
108+
hlt_exit_reason = params.get("hlt_exit_reason", "0x0a6")
109+
url_cpuid_tool = params.get(
110+
"url_cpuid_tool",
111+
default="http://www.etallen.com/cpuid/cpuid-20250513.src.tar.gz",
112+
)
113+
# Check if cpuid is installed; build if not
114+
if not shutil.which("cpuid"):
115+
test.log.info("cpuid tool not found, attempting to build from source")
116+
cpuid_tool_build()
117+
118+
# Check Idle HLT Intercept feature
119+
error_context.context("Checking Idle HLT Intercept feature", test.log.info)
120+
try:
121+
result = process.run(
122+
"cpuid -1 -r -l 0x8000000A", shell=True, ignore_status=True
123+
)
124+
if result.exit_status != 0:
125+
test.cancel(
126+
"Failed to execute cpuid command: {}".format(result.stderr.decode())
127+
)
128+
output = result.stdout.decode()
129+
edx_match = re.search(r"edx\s*=\s*0x([0-9a-fA-F]+)", output)
130+
if not edx_match:
131+
test.cancel("Could not parse EDX from cpuid output.")
132+
edx = int(edx_match.group(1), 16)
133+
if not (edx & (1 << 30)):
134+
test.cancel("Idle HLT Intercept feature is not supported on this platform.")
135+
test.log.info("Idle HLT Intercept feature is supported.")
136+
137+
except process.CmdError as e:
138+
test.cancel("Error executing cpuid: {}".format(e))
139+
# Set up ftrace
140+
setup_ftrace(hlt_exit_reason)
141+
try:
142+
# Enable ftrace
143+
with open(os.path.join(trace_dir, "tracing_on"), "w") as f:
144+
f.write("1")
145+
vm_name = params["main_vm"]
146+
vm = env.get_vm(vm_name)
147+
vm.create()
148+
vm.verify_alive()
149+
session = vm.wait_for_login(timeout=timeout)
150+
verify_dmesg()
151+
if "secure_guest_type" in locals() and secure_guest_type:
152+
verify_secure_guest(session, params, vm)
153+
time.sleep(5)
154+
with open(os.path.join(trace_dir, "tracing_on"), "w") as f:
155+
f.write("0")
156+
with open(os.path.join(trace_dir, "trace"), "r") as f:
157+
trace_output = f.read()
158+
if "idle-halt" not in trace_output:
159+
test.fail("No idle-halt exits detected in ftrace output.")
160+
else:
161+
test.log.info(
162+
"Idle-halt exits detected in ftrace output:\n%s", trace_output
163+
)
164+
except Exception as e:
165+
test.fail("Test failed: %s" % str(e))
166+
finally:
167+
try:
168+
if os.path.exists(os.path.join(trace_dir, "tracing_on")):
169+
with open(os.path.join(trace_dir, "tracing_on"), "w") as f:
170+
f.write("0")
171+
with open(
172+
os.path.join(trace_dir, "events/kvm/kvm_exit/enable"), "w"
173+
) as f:
174+
f.write("0")
175+
with open(
176+
os.path.join(trace_dir, "events/kvm/kvm_exit/filter"), "w"
177+
) as f:
178+
f.write("0")
179+
with open(os.path.join(trace_dir, "trace"), "w") as f:
180+
f.write("")
181+
test.log.info("ftrace cleaned up.")
182+
except (IOError, PermissionError) as e:
183+
test.log.warning("Failed to clean up ftrace: %s", e)
184+
if "session" in locals() and session:
185+
session.close()
186+
vm.destroy()

0 commit comments

Comments
 (0)