All Virtuozzo development lists (kernel + QEMU)
 help / color / mirror / Atom feed
From: Konstantin Khorenko <khorenko@virtuozzo.com>
To: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>,
	Vasileios Almpanis <vasileios.almpanis@virtuozzo.com>
Cc: devel@openvz.org
Subject: [Devel] [PATCH hci-8.1 v2 1/5] selftests/ve: check CPUID override emulation for VE tasks
Date: Tue, 06 Oct 2026 19:19:10 +0200	[thread overview]
Message-ID: <20261006-b4-prep-khorenko-vstor-148656-cpuid-v2-1-f6d1192c7d60@virtuozzo.com> (raw)
In-Reply-To: <20261006-b4-prep-khorenko-vstor-148656-cpuid-v2-0-f6d1192c7d60@virtuozzo.com>

Write a one-entry /proc/vz/cpuid_override table that clears one leaf 1
feature bit and compare what a task in a fresh VE and the host see:
the VE task must get the table values, the host the hardware ones. A
second test flushes the table after the task was armed and expects the
hardware values again, since faulting stays on for the task and the
emulation has to fall back to the real CPUID.

The child stops itself with SIGSTOP after entering the VE and the
parent resumes it before the CPUID: faulting is switched on for a task
at its next context switch, not when the flag is set, and a stopped
task is off the CPU for sure. For the second test the table is flushed
while the child stands still.

Without the fix both children die with SIGSEGV. The tests skip on CPUs
without CPUID faulting and restore the previous table on exit. The
teardown runs in the parent because the harness skips a child-side
teardown once an ASSERT has fired. The test is built on x86 only.

https://virtuozzo.atlassian.net/browse/VSTOR-148843

Feature: x86: cpu pools (cpuid override)
Signed-off-by: Konstantin Khorenko <khorenko@virtuozzo.com>
---
 tools/testing/selftests/ve/.gitignore              |   1 +
 tools/testing/selftests/ve/Makefile                |   7 +
 .../testing/selftests/ve/ve_cpuid_override_test.c  | 236 +++++++++++++++++++++
 3 files changed, 244 insertions(+)

diff --git a/tools/testing/selftests/ve/.gitignore b/tools/testing/selftests/ve/.gitignore
index 3df4d05888dce..bdc322c645d95 100644
--- a/tools/testing/selftests/ve/.gitignore
+++ b/tools/testing/selftests/ve/.gitignore
@@ -1,3 +1,4 @@
 ve_ns_owner_test
 ve_perms_test
 ve_mount_accounting_test
+ve_cpuid_override_test
diff --git a/tools/testing/selftests/ve/Makefile b/tools/testing/selftests/ve/Makefile
index c6efe7c4b4fba..cd50a86fdb226 100644
--- a/tools/testing/selftests/ve/Makefile
+++ b/tools/testing/selftests/ve/Makefile
@@ -6,4 +6,11 @@ TEST_GEN_PROGS += ve_ns_owner_test
 TEST_GEN_PROGS += ve_perms_test
 TEST_GEN_PROGS += ve_mount_accounting_test
 
+ifeq ($(ARCH),)
+ARCH := $(shell uname -m 2>/dev/null || echo not)
+endif
+ifeq ($(ARCH),$(filter $(ARCH),x86 x86_64))
+TEST_GEN_PROGS += ve_cpuid_override_test
+endif
+
 include ../lib.mk
diff --git a/tools/testing/selftests/ve/ve_cpuid_override_test.c b/tools/testing/selftests/ve/ve_cpuid_override_test.c
new file mode 100644
index 0000000000000..291b88404a53e
--- /dev/null
+++ b/tools/testing/selftests/ve/ve_cpuid_override_test.c
@@ -0,0 +1,236 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * /proc/vz/cpuid_override selftests
+ *
+ * A non-empty override table turns CPUID faulting on for every task that
+ * enters a VE, and the #GP handler has to emulate the instruction for
+ * them. The tests check that a task in a VE sees the table values, that
+ * the host keeps seeing the hardware values, and that a task which was
+ * armed while the table was set falls back to the hardware once the
+ * table is flushed. A kernel that does not emulate the fault kills the
+ * child with SIGSEGV instead.
+ */
+#define _GNU_SOURCE
+#include <sched.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/wait.h>
+
+#include "../kselftest_harness.h"
+#include "ve_selftest.h"
+
+#define CPUID_OVERRIDE_PATH	"/proc/vz/cpuid_override"
+#define CPUID_LEAF		1
+#define CPUID_TABLE_LEN		128
+/* 128 entries of up to 67 characters each, as the kernel prints them. */
+#define CPUID_TABLE_MAX		16384
+
+struct cpuid_regs {
+	unsigned int eax, ebx, ecx, edx;
+};
+
+/* x86_64 only, so ebx is not the PIC base and "=b" is safe. */
+static void do_cpuid(unsigned int leaf, struct cpuid_regs *r)
+{
+	asm volatile("cpuid"
+		     : "=a" (r->eax), "=b" (r->ebx), "=c" (r->ecx), "=d" (r->edx)
+		     : "a" (leaf), "c" (0));
+}
+
+static int write_cpuid_override(const char *table)
+{
+	int fd, ret;
+
+	fd = open(CPUID_OVERRIDE_PATH, O_WRONLY);
+	if (fd < 0)
+		return -1;
+	ret = write(fd, table, strlen(table));
+	close(fd);
+	return ret == (int)strlen(table) ? 0 : -1;
+}
+
+/*
+ * The child enters a fresh VE, so it is armed with the table state of that
+ * moment, then reports its view of the leaf to the parent over a pipe. It
+ * stops itself in between: a stopped task is off the CPU, so the resume is
+ * a context switch, and the parent may change the table while the child
+ * stands still.
+ */
+struct child_ctx {
+	int result_pipe[2];
+};
+
+static int cpuid_child(struct child_ctx *ctx)
+{
+	struct cpuid_regs r;
+
+	close(ctx->result_pipe[0]);
+
+	if (unshare(CLONE_NEWVE) < 0)
+		return 21;
+	if (raise(SIGSTOP))
+		return 22;
+
+	do_cpuid(CPUID_LEAF, &r);
+
+	if (write(ctx->result_pipe[1], &r, sizeof(r)) != sizeof(r))
+		return 24;
+	return 0;
+}
+
+FIXTURE(ve_cpuid_override)
+{
+	int cgv2_fd;
+	int ctid;
+	struct cpuid_regs host;
+	unsigned int bit;
+	char table[CPUID_TABLE_LEN];
+	char saved[CPUID_TABLE_MAX];
+};
+
+FIXTURE_SETUP(ve_cpuid_override)
+{
+	int fd, len;
+
+	fd = open(CPUID_OVERRIDE_PATH, O_RDONLY);
+	if (fd < 0)
+		SKIP(return, "%s: %s", CPUID_OVERRIDE_PATH, strerror(errno));
+	close(fd);
+
+	len = read_file_at(AT_FDCWD, CPUID_OVERRIDE_PATH, self->saved,
+			   sizeof(self->saved));
+	ASSERT_GE(len, 0);
+	ASSERT_LT(len, sizeof(self->saved) - 1);
+
+	/* Mask one feature bit the host has; SSE2 when present. */
+	do_cpuid(CPUID_LEAF, &self->host);
+	self->bit = 1u << 26;
+	if (!(self->host.edx & self->bit))
+		self->bit = self->host.edx & -self->host.edx;
+	ASSERT_NE(self->bit, 0);
+
+	snprintf(self->table, sizeof(self->table),
+		 "0x%08x: 0x%08x 0x%08x 0x%08x 0x%08x\n", CPUID_LEAF,
+		 self->host.eax, self->host.ebx, self->host.ecx,
+		 self->host.edx & ~self->bit);
+
+	/* Last, so that a failed ASSERT above leaves no VE cgroup behind. */
+	self->cgv2_fd = mount_cg2_fd();
+	ASSERT_GE(self->cgv2_fd, 0);
+	ASSERT_EQ(write_file_at(self->cgv2_fd, "cgroup.subtree_control",
+				VE_CONTROLLERS), 0);
+	self->ctid = make_ve(self->cgv2_fd, CTID_MIN);
+	ASSERT_GE(self->ctid, 0);
+}
+
+/*
+ * Runs in the parent: a child-side teardown is skipped when an ASSERT fires,
+ * and the table must be restored even then. The fd from setup belongs to the
+ * child, so the cgroup2 tree is mounted again here.
+ */
+FIXTURE_TEARDOWN_PARENT(ve_cpuid_override)
+{
+	int cgv2_fd;
+
+	if (write_cpuid_override(self->saved[0] ? self->saved : "\n"))
+		TH_LOG("could not restore %s: %s", CPUID_OVERRIDE_PATH,
+		       strerror(errno));
+
+	cgv2_fd = mount_cg2_fd();
+	if (cgv2_fd < 0)
+		return;
+	destroy_ve(cgv2_fd, self->ctid);
+	close(cgv2_fd);
+}
+
+/* Fork the child into the VE and return once it stands still there. */
+static pid_t spawn_cpuid_child(struct __test_metadata *_metadata,
+			       int cgv2_fd, int ctid, struct child_ctx *ctx)
+{
+	int status;
+	pid_t pid;
+
+	ASSERT_EQ(pipe(ctx->result_pipe), 0);
+	ASSERT_GE(enter_cgroup(cgv2_fd, ctid), 0);
+
+	pid = fork();
+	ASSERT_GE(pid, 0);
+	if (pid == 0)
+		_exit(cpuid_child(ctx));
+
+	ASSERT_GE(enter_cgroup(cgv2_fd, 0), 0);
+	close(ctx->result_pipe[1]);
+
+	ASSERT_GE(waitpid(pid, &status, WUNTRACED), 0);
+	if (WIFEXITED(status))
+		TH_LOG("child exited with %d before stopping",
+		       WEXITSTATUS(status));
+	ASSERT_TRUE(WIFSTOPPED(status));
+	return pid;
+}
+
+static void resume_cpuid_child(struct __test_metadata *_metadata, pid_t pid)
+{
+	ASSERT_EQ(kill(pid, SIGCONT), 0);
+}
+
+static void collect_cpuid_child(struct __test_metadata *_metadata, pid_t pid,
+				struct child_ctx *ctx, struct cpuid_regs *r)
+{
+	int status, len;
+
+	len = read(ctx->result_pipe[0], r, sizeof(*r));
+	ASSERT_GE(waitpid(pid, &status, 0), 0);
+	if (WIFSIGNALED(status))
+		TH_LOG("child killed by signal %d", WTERMSIG(status));
+	ASSERT_TRUE(WIFEXITED(status));
+	ASSERT_EQ(WEXITSTATUS(status), 0);
+	ASSERT_EQ(len, sizeof(*r));
+
+	close(ctx->result_pipe[0]);
+}
+
+TEST_F(ve_cpuid_override, ve_sees_table_host_sees_hardware)
+{
+	struct child_ctx ctx;
+	struct cpuid_regs r;
+	pid_t pid;
+
+	ASSERT_EQ(write_cpuid_override(self->table), 0);
+
+	pid = spawn_cpuid_child(_metadata, self->cgv2_fd, self->ctid, &ctx);
+	resume_cpuid_child(_metadata, pid);
+	collect_cpuid_child(_metadata, pid, &ctx, &r);
+
+	EXPECT_EQ(r.eax, self->host.eax);
+	EXPECT_EQ(r.ebx, self->host.ebx);
+	EXPECT_EQ(r.ecx, self->host.ecx);
+	EXPECT_EQ(r.edx, self->host.edx & ~self->bit);
+
+	do_cpuid(CPUID_LEAF, &r);
+	EXPECT_NE(r.edx & self->bit, 0);
+}
+
+TEST_F(ve_cpuid_override, flushed_table_falls_back_to_hardware)
+{
+	struct child_ctx ctx;
+	struct cpuid_regs r;
+	pid_t pid;
+
+	ASSERT_EQ(write_cpuid_override(self->table), 0);
+
+	pid = spawn_cpuid_child(_metadata, self->cgv2_fd, self->ctid, &ctx);
+
+	ASSERT_EQ(write_cpuid_override("\n"), 0);
+	resume_cpuid_child(_metadata, pid);
+	collect_cpuid_child(_metadata, pid, &ctx, &r);
+
+	EXPECT_NE(r.edx & self->bit, 0);
+}
+
+TEST_HARNESS_MAIN

-- 
2.43.0

_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel

  reply	other threads:[~2026-10-06 17:20 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-06 17:19 [Devel] [PATCH hci-8.1 v2 0/5] x86/ve: CPUID override follow-ups: arm at once, prefixes, selftests Konstantin Khorenko
2026-10-06 17:19 ` Konstantin Khorenko [this message]
2026-10-06 17:29   ` [Devel] [PATCH hci-8.1 v2 1/5] selftests/ve: check CPUID override emulation for VE tasks no-reply
2026-10-06 17:19 ` [Devel] [PATCH hci-8.1 v2 2/5] x86/ve: enable CPUID faulting at once when the current task enters a VE Konstantin Khorenko
2026-10-06 17:23   ` no-reply
2026-10-06 17:19 ` [Devel] [PATCH hci-8.1 v2 3/5] selftests/ve: check CPUID override right after entering " Konstantin Khorenko
2026-10-06 17:24   ` no-reply
2026-10-06 17:19 ` [Devel] [PATCH hci-8.1 v2 4/5] x86/traps: decode the faulting CPUID with the instruction decoder Konstantin Khorenko
2026-10-06 17:25   ` no-reply
2026-10-06 17:19 ` [Devel] [PATCH hci-8.1 v2 5/5] selftests/ve: check CPUID override emulation for a prefixed CPUID Konstantin Khorenko
2026-10-06 17:26   ` no-reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261006-b4-prep-khorenko-vstor-148656-cpuid-v2-1-f6d1192c7d60@virtuozzo.com \
    --to=khorenko@virtuozzo.com \
    --cc=devel@openvz.org \
    --cc=ptikhomirov@virtuozzo.com \
    --cc=vasileios.almpanis@virtuozzo.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.