| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405 |
- // 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;
- if (len)
- c->name[len++] = '+';
- strcpy(c->name + len, s->name);
- len += strlen(s->name);
- }
- c->name[len] = '\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 kvm_vm *vm, struct vcpu_reg_list *c,
- struct kvm_vcpu_init *init)
- {
- struct vcpu_reg_sublist *s;
- vm_ioctl(vm, KVM_ARM_PREFERRED_TARGET, init);
- 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;
- struct kvm_vcpu *vcpu;
- prepare_vcpu_init(vm, 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",
- 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;
- }
|