// 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 #include #include #include #include #include #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=] [--list] [--list-filtered]\n\n" " --config= Used to select a specific vcpu configuration for the test/listing\n" " '' 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; }