1// SPDX-License-Identifier: GPL-2.0
2/*
3 * Test that KVM_SET_BOOT_CPU_ID works as intended
4 *
5 * Copyright (C) 2020, Red Hat, Inc.
6 */
7#define _GNU_SOURCE /* for program_invocation_name */
8#include <fcntl.h>
9#include <stdio.h>
10#include <stdlib.h>
11#include <string.h>
12#include <sys/ioctl.h>
13
14#include "test_util.h"
15#include "kvm_util.h"
16#include "processor.h"
17#include "apic.h"
18
19static void guest_bsp_vcpu(void *arg)
20{
21	GUEST_SYNC(1);
22
23	GUEST_ASSERT_NE(get_bsp_flag(), 0);
24
25	GUEST_DONE();
26}
27
28static void guest_not_bsp_vcpu(void *arg)
29{
30	GUEST_SYNC(1);
31
32	GUEST_ASSERT_EQ(get_bsp_flag(), 0);
33
34	GUEST_DONE();
35}
36
37static void test_set_bsp_busy(struct kvm_vcpu *vcpu, const char *msg)
38{
39	int r = __vm_ioctl(vcpu->vm, KVM_SET_BOOT_CPU_ID,
40			   (void *)(unsigned long)vcpu->id);
41
42	TEST_ASSERT(r == -1 && errno == EBUSY, "KVM_SET_BOOT_CPU_ID set %s", msg);
43}
44
45static void run_vcpu(struct kvm_vcpu *vcpu)
46{
47	struct ucall uc;
48	int stage;
49
50	for (stage = 0; stage < 2; stage++) {
51
52		vcpu_run(vcpu);
53
54		switch (get_ucall(vcpu, &uc)) {
55		case UCALL_SYNC:
56			TEST_ASSERT(!strcmp((const char *)uc.args[0], "hello") &&
57					uc.args[1] == stage + 1,
58					"Stage %d: Unexpected register values vmexit, got %lx",
59					stage + 1, (ulong)uc.args[1]);
60			test_set_bsp_busy(vcpu, "while running vm");
61			break;
62		case UCALL_DONE:
63			TEST_ASSERT(stage == 1,
64					"Expected GUEST_DONE in stage 2, got stage %d",
65					stage);
66			break;
67		case UCALL_ABORT:
68			REPORT_GUEST_ASSERT(uc);
69		default:
70			TEST_ASSERT(false, "Unexpected exit: %s",
71				    exit_reason_str(vcpu->run->exit_reason));
72		}
73	}
74}
75
76static struct kvm_vm *create_vm(uint32_t nr_vcpus, uint32_t bsp_vcpu_id,
77				struct kvm_vcpu *vcpus[])
78{
79	struct kvm_vm *vm;
80	uint32_t i;
81
82	vm = vm_create(nr_vcpus);
83
84	vm_ioctl(vm, KVM_SET_BOOT_CPU_ID, (void *)(unsigned long)bsp_vcpu_id);
85
86	for (i = 0; i < nr_vcpus; i++)
87		vcpus[i] = vm_vcpu_add(vm, i, i == bsp_vcpu_id ? guest_bsp_vcpu :
88								 guest_not_bsp_vcpu);
89	return vm;
90}
91
92static void run_vm_bsp(uint32_t bsp_vcpu_id)
93{
94	struct kvm_vcpu *vcpus[2];
95	struct kvm_vm *vm;
96
97	vm = create_vm(ARRAY_SIZE(vcpus), bsp_vcpu_id, vcpus);
98
99	run_vcpu(vcpus[0]);
100	run_vcpu(vcpus[1]);
101
102	kvm_vm_free(vm);
103}
104
105static void check_set_bsp_busy(void)
106{
107	struct kvm_vcpu *vcpus[2];
108	struct kvm_vm *vm;
109
110	vm = create_vm(ARRAY_SIZE(vcpus), 0, vcpus);
111
112	test_set_bsp_busy(vcpus[1], "after adding vcpu");
113
114	run_vcpu(vcpus[0]);
115	run_vcpu(vcpus[1]);
116
117	test_set_bsp_busy(vcpus[1], "to a terminated vcpu");
118
119	kvm_vm_free(vm);
120}
121
122int main(int argc, char *argv[])
123{
124	TEST_REQUIRE(kvm_has_cap(KVM_CAP_SET_BOOT_CPU_ID));
125
126	run_vm_bsp(0);
127	run_vm_bsp(1);
128	run_vm_bsp(0);
129
130	check_set_bsp_busy();
131}
132