diff options
Diffstat (limited to 'tools/testing/selftests/kvm/get-reg-list.c')
| -rw-r--r-- | tools/testing/selftests/kvm/get-reg-list.c | 401 | 
1 files changed, 401 insertions, 0 deletions
diff --git a/tools/testing/selftests/kvm/get-reg-list.c b/tools/testing/selftests/kvm/get-reg-list.c new file mode 100644 index 000000000000..be7bf5224434 --- /dev/null +++ b/tools/testing/selftests/kvm/get-reg-list.c @@ -0,0 +1,401 @@ +// 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_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])) + +#define for_each_present_blessed_reg(i)						\ +	for_each_reg(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); +} + +bool __weak check_reject_set(int err) +{ +	return true; +} + +void __weak finalize_vcpu(struct kvm_vcpu *vcpu, struct vcpu_reg_list *c) +{ +} + +#ifdef __aarch64__ +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 struct kvm_vcpu *vcpu_config_get_vcpu(struct vcpu_reg_list *c, struct kvm_vm *vm) +{ +	struct kvm_vcpu_init init = { .target = -1, }; +	struct kvm_vcpu *vcpu; + +	prepare_vcpu_init(c, &init); +	vcpu = __vm_vcpu_add(vm, 0); +	aarch64_vcpu_setup(vcpu, &init); + +	return vcpu; +} +#else +static struct kvm_vcpu *vcpu_config_get_vcpu(struct vcpu_reg_list *c, struct kvm_vm *vm) +{ +	return __vm_vcpu_add(vm, 0); +} +#endif + +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) +{ +	int new_regs = 0, missing_regs = 0, i, n; +	int failed_get = 0, failed_set = 0, failed_reject = 0; +	int skipped_set = 0; +	struct kvm_vcpu *vcpu; +	struct kvm_vm *vm; +	struct vcpu_reg_sublist *s; + +	check_supported(c); + +	vm = vm_create_barebones(); +	vcpu = vcpu_config_get_vcpu(c, vm); +	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; +	} + +	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]; +	} + +	/* +	 * 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. +	 * +	 * Only do the get/set tests on present, blessed list registers, +	 * since we don't know the capabilities of any new registers. +	 */ +	for_each_present_blessed_reg(i) { +		uint8_t addr[2048 / 8]; +		struct kvm_one_reg reg = { +			.id = reg_list->reg[i], +			.addr = (__u64)&addr, +		}; +		bool reject_reg = false, skip_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; +		} + +		for_each_sublist(c, s) { +			/* rejects_set registers are rejected for set operation */ +			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, ®); +				if (ret != -1 || !check_reject_set(errno)) { +					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; +			} + +			/* skips_set registers are skipped for set operation */ +			if (s->skips_set && find_reg(s->skips_set, s->skips_set_n, reg.id)) { +				skip_reg = true; +				++skipped_set; +				break; +			} +		} + +		if (!reject_reg && !skip_reg) { +			ret = __vcpu_ioctl(vcpu, KVM_SET_ONE_REG, ®); +			if (ret) { +				printf("%s: Failed to set ", config_name(c)); +				print_reg(config_name(c), reg.id); +				putchar('\n'); +				++failed_set; +			} +		} +	} + +	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; %d registers skipped set", +		    config_name(c), missing_regs, failed_get, failed_set, failed_reject, skipped_set); + +	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; +}  |