1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
|
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2024 Intel Corporation
*
* Verify KVM correctly emulates the APIC bus frequency when the VMM configures
* the frequency via KVM_CAP_X86_APIC_BUS_CYCLES_NS. Start the APIC timer by
* programming TMICT (timer initial count) to the largest value possible (so
* that the timer will not expire during the test). Then, after an arbitrary
* amount of time has elapsed, verify TMCCT (timer current count) is within 1%
* of the expected value based on the time elapsed, the APIC bus frequency, and
* the programmed TDCR (timer divide configuration register).
*/
#include "apic.h"
#include "test_util.h"
/*
* Possible TDCR values with matching divide count. Used to modify APIC
* timer frequency.
*/
static const struct {
const u32 tdcr;
const u32 divide_count;
} tdcrs[] = {
{0x0, 2},
{0x1, 4},
{0x2, 8},
{0x3, 16},
{0x8, 32},
{0x9, 64},
{0xa, 128},
{0xb, 1},
};
static bool is_x2apic;
static void apic_enable(void)
{
if (is_x2apic)
x2apic_enable();
else
xapic_enable();
}
static u32 apic_read_reg(unsigned int reg)
{
return is_x2apic ? x2apic_read_reg(reg) : xapic_read_reg(reg);
}
static void apic_write_reg(unsigned int reg, u32 val)
{
if (is_x2apic)
x2apic_write_reg(reg, val);
else
xapic_write_reg(reg, val);
}
static void apic_guest_code(u64 apic_hz, u64 delay_ms)
{
u64 tsc_hz = guest_tsc_khz * 1000;
const u32 tmict = ~0u;
u64 tsc0, tsc1, freq;
u32 tmcct;
int i;
apic_enable();
/*
* Setup one-shot timer. The vector does not matter because the
* interrupt should not fire.
*/
apic_write_reg(APIC_LVTT, APIC_LVT_TIMER_ONESHOT | APIC_LVT_MASKED);
for (i = 0; i < ARRAY_SIZE(tdcrs); i++) {
apic_write_reg(APIC_TDCR, tdcrs[i].tdcr);
apic_write_reg(APIC_TMICT, tmict);
tsc0 = rdtsc();
udelay(delay_ms * 1000);
tmcct = apic_read_reg(APIC_TMCCT);
tsc1 = rdtsc();
/*
* Stop the timer _after_ reading the current, final count, as
* writing the initial counter also modifies the current count.
*/
apic_write_reg(APIC_TMICT, 0);
freq = (tmict - tmcct) * tdcrs[i].divide_count * tsc_hz / (tsc1 - tsc0);
/* Check if measured frequency is within 5% of configured frequency. */
__GUEST_ASSERT(freq < apic_hz * 105 / 100 && freq > apic_hz * 95 / 100,
"Frequency = %lu (wanted %lu - %lu), bus = %lu, div = %u, tsc = %lu",
freq, apic_hz * 95 / 100, apic_hz * 105 / 100,
apic_hz, tdcrs[i].divide_count, tsc_hz);
}
GUEST_DONE();
}
static void test_apic_bus_clock(struct kvm_vcpu *vcpu)
{
bool done = false;
struct ucall uc;
while (!done) {
vcpu_run(vcpu);
TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);
switch (get_ucall(vcpu, &uc)) {
case UCALL_DONE:
done = true;
break;
case UCALL_ABORT:
REPORT_GUEST_ASSERT(uc);
break;
default:
TEST_FAIL("Unknown ucall %lu", uc.cmd);
break;
}
}
}
static void run_apic_bus_clock_test(u64 apic_hz, u64 delay_ms,
bool x2apic)
{
struct kvm_vcpu *vcpu;
struct kvm_vm *vm;
int ret;
is_x2apic = x2apic;
vm = vm_create(1);
sync_global_to_guest(vm, is_x2apic);
vm_enable_cap(vm, KVM_CAP_X86_APIC_BUS_CYCLES_NS,
NSEC_PER_SEC / apic_hz);
vcpu = vm_vcpu_add(vm, 0, apic_guest_code);
vcpu_args_set(vcpu, 2, apic_hz, delay_ms);
ret = __vm_enable_cap(vm, KVM_CAP_X86_APIC_BUS_CYCLES_NS,
NSEC_PER_SEC / apic_hz);
TEST_ASSERT(ret < 0 && errno == EINVAL,
"Setting of APIC bus frequency after vCPU is created should fail.");
if (!is_x2apic)
virt_pg_map(vm, APIC_DEFAULT_GPA, APIC_DEFAULT_GPA);
test_apic_bus_clock(vcpu);
kvm_vm_free(vm);
}
static void help(char *name)
{
puts("");
printf("usage: %s [-h] [-d delay] [-f APIC bus freq]\n", name);
puts("");
printf("-d: Delay (in msec) guest uses to measure APIC bus frequency.\n");
printf("-f: The APIC bus frequency (in MHz) to be configured for the guest.\n");
puts("");
}
int main(int argc, char *argv[])
{
/*
* Arbitrarilty default to 25MHz for the APIC bus frequency, which is
* different enough from the default 1GHz to be interesting.
*/
u64 apic_hz = 25 * 1000 * 1000;
u64 delay_ms = 100;
int opt;
TEST_REQUIRE(kvm_has_cap(KVM_CAP_X86_APIC_BUS_CYCLES_NS));
while ((opt = getopt(argc, argv, "d:f:h")) != -1) {
switch (opt) {
case 'f':
apic_hz = atoi_positive("APIC bus frequency", optarg) * 1000 * 1000;
break;
case 'd':
delay_ms = atoi_positive("Delay in milliseconds", optarg);
break;
case 'h':
default:
help(argv[0]);
exit(KSFT_SKIP);
}
}
run_apic_bus_clock_test(apic_hz, delay_ms, false);
run_apic_bus_clock_test(apic_hz, delay_ms, true);
}
|