Commit 17da79e0 authored by Andrew Jones's avatar Andrew Jones Committed by Anup Patel
Browse files

KVM: arm64: selftests: Split get-reg-list test code



Split the arch-neutral test code out of aarch64/get-reg-list.c into
get-reg-list.c. To do this we invent a new make variable
$(SPLIT_TESTS) which expects common parts to be in the KVM selftests
root and the counterparts to have the same name, but be in
$(ARCH_DIR).

There's still some work to be done to de-aarch64 the common
get-reg-list.c, but we leave that to the next patch to avoid
modifying too much code while moving it.

Signed-off-by: default avatarAndrew Jones <ajones@ventanamicro.com>
Signed-off-by: default avatarHaibo Xu <haibo1.xu@intel.com>
Signed-off-by: default avatarAnup Patel <anup@brainfault.org>
parent 0ace6bda
Loading
Loading
Loading
Loading
+10 −2
Original line number Diff line number Diff line
@@ -140,7 +140,6 @@ TEST_GEN_PROGS_EXTENDED_x86_64 += x86_64/nx_huge_pages_test
TEST_GEN_PROGS_aarch64 += aarch64/aarch32_id_regs
TEST_GEN_PROGS_aarch64 += aarch64/arch_timer
TEST_GEN_PROGS_aarch64 += aarch64/debug-exceptions
TEST_GEN_PROGS_aarch64 += aarch64/get-reg-list
TEST_GEN_PROGS_aarch64 += aarch64/hypercalls
TEST_GEN_PROGS_aarch64 += aarch64/page_fault_test
TEST_GEN_PROGS_aarch64 += aarch64/psci_test
@@ -152,6 +151,7 @@ TEST_GEN_PROGS_aarch64 += access_tracking_perf_test
TEST_GEN_PROGS_aarch64 += demand_paging_test
TEST_GEN_PROGS_aarch64 += dirty_log_test
TEST_GEN_PROGS_aarch64 += dirty_log_perf_test
TEST_GEN_PROGS_aarch64 += get-reg-list
TEST_GEN_PROGS_aarch64 += kvm_create_max_vcpus
TEST_GEN_PROGS_aarch64 += kvm_page_table_test
TEST_GEN_PROGS_aarch64 += memslot_modification_stress_test
@@ -181,6 +181,8 @@ TEST_GEN_PROGS_riscv += kvm_page_table_test
TEST_GEN_PROGS_riscv += set_memory_region_test
TEST_GEN_PROGS_riscv += kvm_binary_stats_test

SPLIT_TESTS += get-reg-list

TEST_PROGS += $(TEST_PROGS_$(ARCH_DIR))
TEST_GEN_PROGS += $(TEST_GEN_PROGS_$(ARCH_DIR))
TEST_GEN_PROGS_EXTENDED += $(TEST_GEN_PROGS_EXTENDED_$(ARCH_DIR))
@@ -228,11 +230,14 @@ LIBKVM_C_OBJ := $(patsubst %.c, $(OUTPUT)/%.o, $(LIBKVM_C))
LIBKVM_S_OBJ := $(patsubst %.S, $(OUTPUT)/%.o, $(LIBKVM_S))
LIBKVM_STRING_OBJ := $(patsubst %.c, $(OUTPUT)/%.o, $(LIBKVM_STRING))
LIBKVM_OBJS = $(LIBKVM_C_OBJ) $(LIBKVM_S_OBJ) $(LIBKVM_STRING_OBJ)
SPLIT_TESTS_TARGETS := $(patsubst %, $(OUTPUT)/%, $(SPLIT_TESTS))
SPLIT_TESTS_OBJS := $(patsubst %, $(ARCH_DIR)/%.o, $(SPLIT_TESTS))

TEST_GEN_OBJ = $(patsubst %, %.o, $(TEST_GEN_PROGS))
TEST_GEN_OBJ += $(patsubst %, %.o, $(TEST_GEN_PROGS_EXTENDED))
TEST_DEP_FILES = $(patsubst %.o, %.d, $(TEST_GEN_OBJ))
TEST_DEP_FILES += $(patsubst %.o, %.d, $(LIBKVM_OBJS))
TEST_DEP_FILES += $(patsubst %.o, %.d, $(SPLIT_TESTS_OBJS))
-include $(TEST_DEP_FILES)

$(TEST_GEN_PROGS) $(TEST_GEN_PROGS_EXTENDED): %: %.o
@@ -240,7 +245,10 @@ $(TEST_GEN_PROGS) $(TEST_GEN_PROGS_EXTENDED): %: %.o
$(TEST_GEN_OBJ): $(OUTPUT)/%.o: %.c
	$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@

EXTRA_CLEAN += $(LIBKVM_OBJS) $(TEST_DEP_FILES) $(TEST_GEN_OBJ) cscope.*
$(SPLIT_TESTS_TARGETS): %: %.o $(SPLIT_TESTS_OBJS)
	$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH) $^ $(LDLIBS) -o $@

EXTRA_CLEAN += $(LIBKVM_OBJS) $(TEST_DEP_FILES) $(TEST_GEN_OBJ) $(SPLIT_TESTS_OBJS) cscope.*

x := $(shell mkdir -p $(sort $(dir $(LIBKVM_C_OBJ) $(LIBKVM_S_OBJ))))
$(LIBKVM_C_OBJ): $(OUTPUT)/%.o: %.c
+11 −356
Original line number Diff line number Diff line
@@ -4,37 +4,17 @@
 *
 * Copyright (C) 2020, Red Hat, Inc.
 *
 * When attempting to migrate from a host with an older kernel to a host
 * with a newer kernel we allow the newer kernel on the destination to
 * list new registers with get-reg-list. We assume they'll be unused, at
 * least until the guest reboots, and so they're relatively harmless.
 * However, if the destination host with the newer kernel is missing
 * registers which the source host with the older kernel has, then that's
 * a regression in get-reg-list. This test checks for that regression by
 * checking the current list against a blessed list. We should never have
 * missing registers, but if new ones appear then they can probably be
 * added to the blessed list. A completely new blessed list can be created
 * by running the test with the --list command line argument.
 *
 * Note, the blessed list should be created from the oldest possible
 * kernel. We can't go older than v5.2, though, because that's the first
 * While the blessed list should be created from the oldest possible
 * kernel, we can't go older than v5.2, though, because that's the first
 * release which includes df205b5c6328 ("KVM: arm64: Filter out invalid
 * core register IDs in KVM_GET_REG_LIST"). Without that commit the core
 * registers won't match expectations.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "kvm_util.h"
#include "test_util.h"
#include "processor.h"

static struct kvm_reg_list *reg_list;
static __u64 *blessed_reg, blessed_n;

struct feature_id_reg {
	__u64 reg;
	__u64 id_reg;
@@ -63,55 +43,7 @@ static struct feature_id_reg feat_id_regs[] = {
	}
};

static struct vcpu_reg_list *vcpu_configs[];
static int vcpu_configs_n;

#define for_each_sublist(c, s)							\
	for ((s) = &(c)->sublists[0]; (s)->regs; ++(s))

#define for_each_reg(i)								\
	for ((i) = 0; (i) < reg_list->n; ++(i))

#define for_each_reg_filtered(i)						\
	for_each_reg(i)								\
		if (!filter_reg(reg_list->reg[i]))

#define for_each_missing_reg(i)							\
	for ((i) = 0; (i) < blessed_n; ++(i))					\
		if (!find_reg(reg_list->reg, reg_list->n, blessed_reg[i]))	\
			if (check_supported_feat_reg(vcpu, blessed_reg[i]))

#define for_each_new_reg(i)							\
	for_each_reg_filtered(i)						\
		if (!find_reg(blessed_reg, blessed_n, reg_list->reg[i]))

static const char *config_name(struct vcpu_reg_list *c)
{
	struct vcpu_reg_sublist *s;
	int len = 0;

	if (c->name)
		return c->name;

	for_each_sublist(c, s)
		len += strlen(s->name) + 1;

	c->name = malloc(len);

	len = 0;
	for_each_sublist(c, s) {
		if (!strcmp(s->name, "base"))
			continue;
		strcat(c->name + len, s->name);
		len += strlen(s->name) + 1;
		c->name[len - 1] = '+';
	}
	c->name[len - 1] = '\0';

	return c->name;
}

static bool filter_reg(__u64 reg)
bool filter_reg(__u64 reg)
{
	/*
	 * DEMUX register presence depends on the host's CLIDR_EL1.
@@ -123,16 +55,6 @@ static bool filter_reg(__u64 reg)
	return false;
}

static bool find_reg(__u64 regs[], __u64 nr_regs, __u64 reg)
{
	int i;

	for (i = 0; i < nr_regs; ++i)
		if (reg == regs[i])
			return true;
	return false;
}

static bool check_supported_feat_reg(struct kvm_vcpu *vcpu, __u64 reg)
{
	int i, ret;
@@ -152,6 +74,11 @@ static bool check_supported_feat_reg(struct kvm_vcpu *vcpu, __u64 reg)
	return true;
}

bool check_supported_reg(struct kvm_vcpu *vcpu, __u64 reg)
{
	return check_supported_feat_reg(vcpu, reg);
}

#define REG_MASK (KVM_REG_ARCH_MASK | KVM_REG_SIZE_MASK | KVM_REG_ARM_COPROC_MASK)

#define CORE_REGS_XX_NR_WORDS	2
@@ -235,7 +162,7 @@ static const char *sve_id_to_str(const char *prefix, __u64 id)
	return NULL;
}

static void print_reg(const char *prefix, __u64 id)
void print_reg(const char *prefix, __u64 id)
{
	unsigned op0, op1, crn, crm, op2;
	const char *reg_size = NULL;
@@ -315,278 +242,6 @@ static void print_reg(const char *prefix, __u64 id)
	}
}

static void prepare_vcpu_init(struct vcpu_reg_list *c, struct kvm_vcpu_init *init)
{
	struct vcpu_reg_sublist *s;

	for_each_sublist(c, s)
		if (s->capability)
			init->features[s->feature / 32] |= 1 << (s->feature % 32);
}

static void finalize_vcpu(struct kvm_vcpu *vcpu, struct vcpu_reg_list *c)
{
	struct vcpu_reg_sublist *s;
	int feature;

	for_each_sublist(c, s) {
		if (s->finalize) {
			feature = s->feature;
			vcpu_ioctl(vcpu, KVM_ARM_VCPU_FINALIZE, &feature);
		}
	}
}

static void check_supported(struct vcpu_reg_list *c)
{
	struct vcpu_reg_sublist *s;

	for_each_sublist(c, s) {
		if (!s->capability)
			continue;

		__TEST_REQUIRE(kvm_has_cap(s->capability),
			       "%s: %s not available, skipping tests\n",
			       config_name(c), s->name);
	}
}

static bool print_list;
static bool print_filtered;

static void run_test(struct vcpu_reg_list *c)
{
	struct kvm_vcpu_init init = { .target = -1, };
	int new_regs = 0, missing_regs = 0, i, n;
	int failed_get = 0, failed_set = 0, failed_reject = 0;
	struct kvm_vcpu *vcpu;
	struct kvm_vm *vm;
	struct vcpu_reg_sublist *s;

	check_supported(c);

	vm = vm_create_barebones();
	prepare_vcpu_init(c, &init);
	vcpu = __vm_vcpu_add(vm, 0);
	aarch64_vcpu_setup(vcpu, &init);
	finalize_vcpu(vcpu, c);

	reg_list = vcpu_get_reg_list(vcpu);

	if (print_list || print_filtered) {
		putchar('\n');
		for_each_reg(i) {
			__u64 id = reg_list->reg[i];
			if ((print_list && !filter_reg(id)) ||
			    (print_filtered && filter_reg(id)))
				print_reg(config_name(c), id);
		}
		putchar('\n');
		return;
	}

	/*
	 * We only test that we can get the register and then write back the
	 * same value. Some registers may allow other values to be written
	 * back, but others only allow some bits to be changed, and at least
	 * for ID registers set will fail if the value does not exactly match
	 * what was returned by get. If registers that allow other values to
	 * be written need to have the other values tested, then we should
	 * create a new set of tests for those in a new independent test
	 * executable.
	 */
	for_each_reg(i) {
		uint8_t addr[2048 / 8];
		struct kvm_one_reg reg = {
			.id = reg_list->reg[i],
			.addr = (__u64)&addr,
		};
		bool reject_reg = false;
		int ret;

		ret = __vcpu_get_reg(vcpu, reg_list->reg[i], &addr);
		if (ret) {
			printf("%s: Failed to get ", config_name(c));
			print_reg(config_name(c), reg.id);
			putchar('\n');
			++failed_get;
		}

		/* rejects_set registers are rejected after KVM_ARM_VCPU_FINALIZE */
		for_each_sublist(c, s) {
			if (s->rejects_set && find_reg(s->rejects_set, s->rejects_set_n, reg.id)) {
				reject_reg = true;
				ret = __vcpu_ioctl(vcpu, KVM_SET_ONE_REG, &reg);
				if (ret != -1 || errno != EPERM) {
					printf("%s: Failed to reject (ret=%d, errno=%d) ", config_name(c), ret, errno);
					print_reg(config_name(c), reg.id);
					putchar('\n');
					++failed_reject;
				}
				break;
			}
		}

		if (!reject_reg) {
			ret = __vcpu_ioctl(vcpu, KVM_SET_ONE_REG, &reg);
			if (ret) {
				printf("%s: Failed to set ", config_name(c));
				print_reg(config_name(c), reg.id);
				putchar('\n');
				++failed_set;
			}
		}
	}

	for_each_sublist(c, s)
		blessed_n += s->regs_n;
	blessed_reg = calloc(blessed_n, sizeof(__u64));

	n = 0;
	for_each_sublist(c, s) {
		for (i = 0; i < s->regs_n; ++i)
			blessed_reg[n++] = s->regs[i];
	}

	for_each_new_reg(i)
		++new_regs;

	for_each_missing_reg(i)
		++missing_regs;

	if (new_regs || missing_regs) {
		n = 0;
		for_each_reg_filtered(i)
			++n;

		printf("%s: Number blessed registers: %5lld\n", config_name(c), blessed_n);
		printf("%s: Number registers:         %5lld (includes %lld filtered registers)\n",
		       config_name(c), reg_list->n, reg_list->n - n);
	}

	if (new_regs) {
		printf("\n%s: There are %d new registers.\n"
		       "Consider adding them to the blessed reg "
		       "list with the following lines:\n\n", config_name(c), new_regs);
		for_each_new_reg(i)
			print_reg(config_name(c), reg_list->reg[i]);
		putchar('\n');
	}

	if (missing_regs) {
		printf("\n%s: There are %d missing registers.\n"
		       "The following lines are missing registers:\n\n", config_name(c), missing_regs);
		for_each_missing_reg(i)
			print_reg(config_name(c), blessed_reg[i]);
		putchar('\n');
	}

	TEST_ASSERT(!missing_regs && !failed_get && !failed_set && !failed_reject,
		    "%s: There are %d missing registers; "
		    "%d registers failed get; %d registers failed set; %d registers failed reject",
		    config_name(c), missing_regs, failed_get, failed_set, failed_reject);

	pr_info("%s: PASS\n", config_name(c));
	blessed_n = 0;
	free(blessed_reg);
	free(reg_list);
	kvm_vm_free(vm);
}

static void help(void)
{
	struct vcpu_reg_list *c;
	int i;

	printf(
	"\n"
	"usage: get-reg-list [--config=<selection>] [--list] [--list-filtered]\n\n"
	" --config=<selection>        Used to select a specific vcpu configuration for the test/listing\n"
	"                             '<selection>' may be\n");

	for (i = 0; i < vcpu_configs_n; ++i) {
		c = vcpu_configs[i];
		printf(
	"                               '%s'\n", config_name(c));
	}

	printf(
	"\n"
	" --list                      Print the register list rather than test it (requires --config)\n"
	" --list-filtered             Print registers that would normally be filtered out (requires --config)\n"
	"\n"
	);
}

static struct vcpu_reg_list *parse_config(const char *config)
{
	struct vcpu_reg_list *c;
	int i;

	if (config[8] != '=')
		help(), exit(1);

	for (i = 0; i < vcpu_configs_n; ++i) {
		c = vcpu_configs[i];
		if (strcmp(config_name(c), &config[9]) == 0)
			break;
	}

	if (i == vcpu_configs_n)
		help(), exit(1);

	return c;
}

int main(int ac, char **av)
{
	struct vcpu_reg_list *c, *sel = NULL;
	int i, ret = 0;
	pid_t pid;

	for (i = 1; i < ac; ++i) {
		if (strncmp(av[i], "--config", 8) == 0)
			sel = parse_config(av[i]);
		else if (strcmp(av[i], "--list") == 0)
			print_list = true;
		else if (strcmp(av[i], "--list-filtered") == 0)
			print_filtered = true;
		else if (strcmp(av[i], "--help") == 0 || strcmp(av[1], "-h") == 0)
			help(), exit(0);
		else
			help(), exit(1);
	}

	if (print_list || print_filtered) {
		/*
		 * We only want to print the register list of a single config.
		 */
		if (!sel)
			help(), exit(1);
	}

	for (i = 0; i < vcpu_configs_n; ++i) {
		c = vcpu_configs[i];
		if (sel && c != sel)
			continue;

		pid = fork();

		if (!pid) {
			run_test(c);
			exit(0);
		} else {
			int wstatus;
			pid_t wpid = wait(&wstatus);
			TEST_ASSERT(wpid == pid && WIFEXITED(wstatus), "wait: Unexpected return");
			if (WEXITSTATUS(wstatus) && WEXITSTATUS(wstatus) != KSFT_SKIP)
				ret = KSFT_FAIL;
		}
	}

	return ret;
}

/*
 * The original blessed list was primed with the output of kernel version
 * v4.15 with --core-reg-fixup and then later updated with new registers.
@@ -1073,7 +728,7 @@ static struct vcpu_reg_list pauth_pmu_config = {
	},
};

static struct vcpu_reg_list *vcpu_configs[] = {
struct vcpu_reg_list *vcpu_configs[] = {
	&vregs_config,
	&vregs_pmu_config,
	&sve_config,
@@ -1081,4 +736,4 @@ static struct vcpu_reg_list *vcpu_configs[] = {
	&pauth_config,
	&pauth_pmu_config,
};
static int vcpu_configs_n = ARRAY_SIZE(vcpu_configs);
int vcpu_configs_n = ARRAY_SIZE(vcpu_configs);
+377 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0
/*
 * Check for KVM_GET_REG_LIST regressions.
 *
 * Copyright (C) 2020, Red Hat, Inc.
 *
 * When attempting to migrate from a host with an older kernel to a host
 * with a newer kernel we allow the newer kernel on the destination to
 * list new registers with get-reg-list. We assume they'll be unused, at
 * least until the guest reboots, and so they're relatively harmless.
 * However, if the destination host with the newer kernel is missing
 * registers which the source host with the older kernel has, then that's
 * a regression in get-reg-list. This test checks for that regression by
 * checking the current list against a blessed list. We should never have
 * missing registers, but if new ones appear then they can probably be
 * added to the blessed list. A completely new blessed list can be created
 * by running the test with the --list command line argument.
 *
 * The blessed list should be created from the oldest possible kernel.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "kvm_util.h"
#include "test_util.h"
#include "processor.h"

static struct kvm_reg_list *reg_list;
static __u64 *blessed_reg, blessed_n;

extern struct vcpu_reg_list *vcpu_configs[];
extern int vcpu_configs_n;

#define for_each_sublist(c, s)							\
	for ((s) = &(c)->sublists[0]; (s)->regs; ++(s))

#define for_each_reg(i)								\
	for ((i) = 0; (i) < reg_list->n; ++(i))

#define for_each_reg_filtered(i)						\
	for_each_reg(i)								\
		if (!filter_reg(reg_list->reg[i]))

#define for_each_missing_reg(i)							\
	for ((i) = 0; (i) < blessed_n; ++(i))					\
		if (!find_reg(reg_list->reg, reg_list->n, blessed_reg[i]))	\
			if (check_supported_reg(vcpu, blessed_reg[i]))

#define for_each_new_reg(i)							\
	for_each_reg_filtered(i)						\
		if (!find_reg(blessed_reg, blessed_n, reg_list->reg[i]))

static const char *config_name(struct vcpu_reg_list *c)
{
	struct vcpu_reg_sublist *s;
	int len = 0;

	if (c->name)
		return c->name;

	for_each_sublist(c, s)
		len += strlen(s->name) + 1;

	c->name = malloc(len);

	len = 0;
	for_each_sublist(c, s) {
		if (!strcmp(s->name, "base"))
			continue;
		strcat(c->name + len, s->name);
		len += strlen(s->name) + 1;
		c->name[len - 1] = '+';
	}
	c->name[len - 1] = '\0';

	return c->name;
}

bool __weak check_supported_reg(struct kvm_vcpu *vcpu, __u64 reg)
{
	return true;
}

bool __weak filter_reg(__u64 reg)
{
	return false;
}

static bool find_reg(__u64 regs[], __u64 nr_regs, __u64 reg)
{
	int i;

	for (i = 0; i < nr_regs; ++i)
		if (reg == regs[i])
			return true;
	return false;
}

void __weak print_reg(const char *prefix, __u64 id)
{
	printf("\t0x%llx,\n", id);
}

static void prepare_vcpu_init(struct vcpu_reg_list *c, struct kvm_vcpu_init *init)
{
	struct vcpu_reg_sublist *s;

	for_each_sublist(c, s)
		if (s->capability)
			init->features[s->feature / 32] |= 1 << (s->feature % 32);
}

static void finalize_vcpu(struct kvm_vcpu *vcpu, struct vcpu_reg_list *c)
{
	struct vcpu_reg_sublist *s;
	int feature;

	for_each_sublist(c, s) {
		if (s->finalize) {
			feature = s->feature;
			vcpu_ioctl(vcpu, KVM_ARM_VCPU_FINALIZE, &feature);
		}
	}
}

static void check_supported(struct vcpu_reg_list *c)
{
	struct vcpu_reg_sublist *s;

	for_each_sublist(c, s) {
		if (!s->capability)
			continue;

		__TEST_REQUIRE(kvm_has_cap(s->capability),
			       "%s: %s not available, skipping tests\n",
			       config_name(c), s->name);
	}
}

static bool print_list;
static bool print_filtered;

static void run_test(struct vcpu_reg_list *c)
{
	struct kvm_vcpu_init init = { .target = -1, };
	int new_regs = 0, missing_regs = 0, i, n;
	int failed_get = 0, failed_set = 0, failed_reject = 0;
	struct kvm_vcpu *vcpu;
	struct kvm_vm *vm;
	struct vcpu_reg_sublist *s;

	check_supported(c);

	vm = vm_create_barebones();
	prepare_vcpu_init(c, &init);
	vcpu = __vm_vcpu_add(vm, 0);
	aarch64_vcpu_setup(vcpu, &init);
	finalize_vcpu(vcpu, c);

	reg_list = vcpu_get_reg_list(vcpu);

	if (print_list || print_filtered) {
		putchar('\n');
		for_each_reg(i) {
			__u64 id = reg_list->reg[i];
			if ((print_list && !filter_reg(id)) ||
			    (print_filtered && filter_reg(id)))
				print_reg(config_name(c), id);
		}
		putchar('\n');
		return;
	}

	/*
	 * We only test that we can get the register and then write back the
	 * same value. Some registers may allow other values to be written
	 * back, but others only allow some bits to be changed, and at least
	 * for ID registers set will fail if the value does not exactly match
	 * what was returned by get. If registers that allow other values to
	 * be written need to have the other values tested, then we should
	 * create a new set of tests for those in a new independent test
	 * executable.
	 */
	for_each_reg(i) {
		uint8_t addr[2048 / 8];
		struct kvm_one_reg reg = {
			.id = reg_list->reg[i],
			.addr = (__u64)&addr,
		};
		bool reject_reg = false;
		int ret;

		ret = __vcpu_get_reg(vcpu, reg_list->reg[i], &addr);
		if (ret) {
			printf("%s: Failed to get ", config_name(c));
			print_reg(config_name(c), reg.id);
			putchar('\n');
			++failed_get;
		}

		/* rejects_set registers are rejected after KVM_ARM_VCPU_FINALIZE */
		for_each_sublist(c, s) {
			if (s->rejects_set && find_reg(s->rejects_set, s->rejects_set_n, reg.id)) {
				reject_reg = true;
				ret = __vcpu_ioctl(vcpu, KVM_SET_ONE_REG, &reg);
				if (ret != -1 || errno != EPERM) {
					printf("%s: Failed to reject (ret=%d, errno=%d) ", config_name(c), ret, errno);
					print_reg(config_name(c), reg.id);
					putchar('\n');
					++failed_reject;
				}
				break;
			}
		}

		if (!reject_reg) {
			ret = __vcpu_ioctl(vcpu, KVM_SET_ONE_REG, &reg);
			if (ret) {
				printf("%s: Failed to set ", config_name(c));
				print_reg(config_name(c), reg.id);
				putchar('\n');
				++failed_set;
			}
		}
	}

	for_each_sublist(c, s)
		blessed_n += s->regs_n;
	blessed_reg = calloc(blessed_n, sizeof(__u64));

	n = 0;
	for_each_sublist(c, s) {
		for (i = 0; i < s->regs_n; ++i)
			blessed_reg[n++] = s->regs[i];
	}

	for_each_new_reg(i)
		++new_regs;

	for_each_missing_reg(i)
		++missing_regs;

	if (new_regs || missing_regs) {
		n = 0;
		for_each_reg_filtered(i)
			++n;

		printf("%s: Number blessed registers: %5lld\n", config_name(c), blessed_n);
		printf("%s: Number registers:         %5lld (includes %lld filtered registers)\n",
		       config_name(c), reg_list->n, reg_list->n - n);
	}

	if (new_regs) {
		printf("\n%s: There are %d new registers.\n"
		       "Consider adding them to the blessed reg "
		       "list with the following lines:\n\n", config_name(c), new_regs);
		for_each_new_reg(i)
			print_reg(config_name(c), reg_list->reg[i]);
		putchar('\n');
	}

	if (missing_regs) {
		printf("\n%s: There are %d missing registers.\n"
		       "The following lines are missing registers:\n\n", config_name(c), missing_regs);
		for_each_missing_reg(i)
			print_reg(config_name(c), blessed_reg[i]);
		putchar('\n');
	}

	TEST_ASSERT(!missing_regs && !failed_get && !failed_set && !failed_reject,
		    "%s: There are %d missing registers; "
		    "%d registers failed get; %d registers failed set; %d registers failed reject",
		    config_name(c), missing_regs, failed_get, failed_set, failed_reject);

	pr_info("%s: PASS\n", config_name(c));
	blessed_n = 0;
	free(blessed_reg);
	free(reg_list);
	kvm_vm_free(vm);
}

static void help(void)
{
	struct vcpu_reg_list *c;
	int i;

	printf(
	"\n"
	"usage: get-reg-list [--config=<selection>] [--list] [--list-filtered]\n\n"
	" --config=<selection>        Used to select a specific vcpu configuration for the test/listing\n"
	"                             '<selection>' may be\n");

	for (i = 0; i < vcpu_configs_n; ++i) {
		c = vcpu_configs[i];
		printf(
	"                               '%s'\n", config_name(c));
	}

	printf(
	"\n"
	" --list                      Print the register list rather than test it (requires --config)\n"
	" --list-filtered             Print registers that would normally be filtered out (requires --config)\n"
	"\n"
	);
}

static struct vcpu_reg_list *parse_config(const char *config)
{
	struct vcpu_reg_list *c = NULL;
	int i;

	if (config[8] != '=')
		help(), exit(1);

	for (i = 0; i < vcpu_configs_n; ++i) {
		c = vcpu_configs[i];
		if (strcmp(config_name(c), &config[9]) == 0)
			break;
	}

	if (i == vcpu_configs_n)
		help(), exit(1);

	return c;
}

int main(int ac, char **av)
{
	struct vcpu_reg_list *c, *sel = NULL;
	int i, ret = 0;
	pid_t pid;

	for (i = 1; i < ac; ++i) {
		if (strncmp(av[i], "--config", 8) == 0)
			sel = parse_config(av[i]);
		else if (strcmp(av[i], "--list") == 0)
			print_list = true;
		else if (strcmp(av[i], "--list-filtered") == 0)
			print_filtered = true;
		else if (strcmp(av[i], "--help") == 0 || strcmp(av[1], "-h") == 0)
			help(), exit(0);
		else
			help(), exit(1);
	}

	if (print_list || print_filtered) {
		/*
		 * We only want to print the register list of a single config.
		 */
		if (!sel)
			help(), exit(1);
	}

	for (i = 0; i < vcpu_configs_n; ++i) {
		c = vcpu_configs[i];
		if (sel && c != sel)
			continue;

		pid = fork();

		if (!pid) {
			run_test(c);
			exit(0);
		} else {
			int wstatus;
			pid_t wpid = wait(&wstatus);
			TEST_ASSERT(wpid == pid && WIFEXITED(wstatus), "wait: Unexpected return");
			if (WEXITSTATUS(wstatus) && WEXITSTATUS(wstatus) != KSFT_SKIP)
				ret = KSFT_FAIL;
		}
	}

	return ret;
}