forked from ventanamicro/kvmtool
-
Notifications
You must be signed in to change notification settings - Fork 0
/
kvm-cpu.c
335 lines (271 loc) · 7.04 KB
/
kvm-cpu.c
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
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
#include "kvm/kvm-cpu.h"
#include "kvm/symbol.h"
#include "kvm/util.h"
#include "kvm/kvm.h"
#include "kvm/virtio.h"
#include "kvm/mutex.h"
#include "kvm/barrier.h"
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/eventfd.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
extern __thread struct kvm_cpu *current_kvm_cpu;
int __attribute__((weak)) kvm_cpu__get_endianness(struct kvm_cpu *vcpu)
{
return VIRTIO_ENDIAN_HOST;
}
void kvm_cpu__enable_singlestep(struct kvm_cpu *vcpu)
{
struct kvm_guest_debug debug = {
.control = KVM_GUESTDBG_ENABLE | KVM_GUESTDBG_SINGLESTEP,
};
if (ioctl(vcpu->vcpu_fd, KVM_SET_GUEST_DEBUG, &debug) < 0)
pr_warning("KVM_SET_GUEST_DEBUG failed");
}
void kvm_cpu__run(struct kvm_cpu *vcpu)
{
int err;
if (!vcpu->is_running)
return;
err = ioctl(vcpu->vcpu_fd, KVM_RUN, 0);
if (err < 0 && (errno != EINTR && errno != EAGAIN))
die_perror("KVM_RUN failed");
}
static void kvm_cpu_signal_handler(int signum)
{
if (signum == SIGKVMEXIT) {
if (current_kvm_cpu && current_kvm_cpu->is_running)
current_kvm_cpu->is_running = false;
} else if (signum == SIGKVMPAUSE) {
if (current_kvm_cpu->paused)
die("Pause signaled for already paused CPU\n");
/* pause_lock is held by kvm__pause() */
current_kvm_cpu->paused = 1;
/*
* This is a blocking function and uses locks. It is safe
* to call it for this signal as a second pause event should
* not be send to this thread until it acquires and releases
* the pause_lock.
*/
kvm__notify_paused();
}
/* For SIGKVMTASK cpu->task is already set */
}
static void kvm_cpu__handle_coalesced_mmio(struct kvm_cpu *cpu)
{
if (cpu->ring) {
while (cpu->ring->first != cpu->ring->last) {
struct kvm_coalesced_mmio *m;
m = &cpu->ring->coalesced_mmio[cpu->ring->first];
kvm_cpu__emulate_mmio(cpu,
m->phys_addr,
m->data,
m->len,
1);
cpu->ring->first = (cpu->ring->first + 1) % KVM_COALESCED_MMIO_MAX;
}
}
}
static DEFINE_MUTEX(task_lock);
static int task_eventfd;
static void kvm_cpu__run_task(struct kvm_cpu *cpu)
{
u64 inc = 1;
pr_debug("Running task %p on cpu %lu", cpu->task, cpu->cpu_id);
/* Make sure we see the store to cpu->task */
rmb();
cpu->task->func(cpu, cpu->task->data);
/* Clear task before we signal completion */
cpu->task = NULL;
wmb();
if (write(task_eventfd, &inc, sizeof(inc)) < 0)
die("Failed notifying of completed task.");
}
void kvm_cpu__run_on_all_cpus(struct kvm *kvm, struct kvm_cpu_task *task)
{
int i, done = 0;
pr_debug("Running task %p on all cpus", task);
mutex_lock(&task_lock);
for (i = 0; i < kvm->nrcpus; i++) {
if (kvm->cpus[i]->task) {
/* Should never happen */
die("CPU %d already has a task pending!", i);
}
kvm->cpus[i]->task = task;
wmb();
if (kvm->cpus[i] == current_kvm_cpu)
kvm_cpu__run_task(current_kvm_cpu);
else
pthread_kill(kvm->cpus[i]->thread, SIGKVMTASK);
}
while (done < kvm->nrcpus) {
u64 count;
if (read(task_eventfd, &count, sizeof(count)) < 0)
die("Failed reading task eventfd");
done += count;
}
mutex_unlock(&task_lock);
}
int kvm_cpu__start(struct kvm_cpu *cpu)
{
sigset_t sigset;
sigemptyset(&sigset);
sigaddset(&sigset, SIGALRM);
pthread_sigmask(SIG_BLOCK, &sigset, NULL);
signal(SIGKVMEXIT, kvm_cpu_signal_handler);
signal(SIGKVMPAUSE, kvm_cpu_signal_handler);
signal(SIGKVMTASK, kvm_cpu_signal_handler);
kvm_cpu__reset_vcpu(cpu);
if (cpu->kvm->cfg.single_step)
kvm_cpu__enable_singlestep(cpu);
while (cpu->is_running) {
if (cpu->needs_nmi) {
kvm_cpu__arch_nmi(cpu);
cpu->needs_nmi = 0;
}
if (cpu->task)
kvm_cpu__run_task(cpu);
kvm_cpu__run(cpu);
switch (cpu->kvm_run->exit_reason) {
case KVM_EXIT_UNKNOWN:
break;
case KVM_EXIT_DEBUG:
kvm_cpu__show_registers(cpu);
kvm_cpu__show_code(cpu);
break;
case KVM_EXIT_IO: {
bool ret;
ret = kvm_cpu__emulate_io(cpu,
cpu->kvm_run->io.port,
(u8 *)cpu->kvm_run +
cpu->kvm_run->io.data_offset,
cpu->kvm_run->io.direction,
cpu->kvm_run->io.size,
cpu->kvm_run->io.count);
if (!ret)
goto panic_kvm;
break;
}
case KVM_EXIT_MMIO: {
bool ret;
/*
* If we had MMIO exit, coalesced ring should be processed
* *before* processing the exit itself
*/
kvm_cpu__handle_coalesced_mmio(cpu);
ret = kvm_cpu__emulate_mmio(cpu,
cpu->kvm_run->mmio.phys_addr,
cpu->kvm_run->mmio.data,
cpu->kvm_run->mmio.len,
cpu->kvm_run->mmio.is_write);
if (!ret)
goto panic_kvm;
break;
}
case KVM_EXIT_INTR:
if (cpu->is_running)
break;
goto exit_kvm;
case KVM_EXIT_SHUTDOWN:
goto exit_kvm;
case KVM_EXIT_SYSTEM_EVENT:
/*
* Print the type of system event and
* treat all system events as shutdown request.
*/
switch (cpu->kvm_run->system_event.type) {
default:
pr_warning("unknown system event type %d",
cpu->kvm_run->system_event.type);
/* fall through for now */
case KVM_SYSTEM_EVENT_RESET:
/* Fall through for now */
case KVM_SYSTEM_EVENT_SHUTDOWN:
/*
* Ensure that all VCPUs are torn down,
* regardless of which CPU generated the event.
*/
kvm__reboot(cpu->kvm);
goto exit_kvm;
};
break;
default: {
bool ret;
ret = kvm_cpu__handle_exit(cpu);
if (!ret)
goto panic_kvm;
break;
}
}
kvm_cpu__handle_coalesced_mmio(cpu);
}
exit_kvm:
return 0;
panic_kvm:
return 1;
}
int kvm_cpu__init(struct kvm *kvm)
{
int max_cpus, recommended_cpus, i;
max_cpus = kvm__max_cpus(kvm);
recommended_cpus = kvm__recommended_cpus(kvm);
if (kvm->cfg.nrcpus > max_cpus) {
printf(" # Limit the number of CPUs to %d\n", max_cpus);
kvm->cfg.nrcpus = max_cpus;
} else if (kvm->cfg.nrcpus > recommended_cpus) {
printf(" # Warning: The maximum recommended amount of VCPUs"
" is %d\n", recommended_cpus);
}
kvm->nrcpus = kvm->cfg.nrcpus;
task_eventfd = eventfd(0, 0);
if (task_eventfd < 0) {
pr_warning("Couldn't create task_eventfd");
return task_eventfd;
}
/* Alloc one pointer too many, so array ends up 0-terminated */
kvm->cpus = calloc(kvm->nrcpus + 1, sizeof(void *));
if (!kvm->cpus) {
pr_warning("Couldn't allocate array for %d CPUs", kvm->nrcpus);
return -ENOMEM;
}
for (i = 0; i < kvm->nrcpus; i++) {
kvm->cpus[i] = kvm_cpu__arch_init(kvm, i);
if (!kvm->cpus[i]) {
pr_warning("unable to initialize KVM VCPU");
goto fail_alloc;
}
}
return 0;
fail_alloc:
for (i = 0; i < kvm->nrcpus; i++)
free(kvm->cpus[i]);
return -ENOMEM;
}
base_init(kvm_cpu__init);
int kvm_cpu__exit(struct kvm *kvm)
{
int i, r;
void *ret = NULL;
kvm_cpu__delete(kvm->cpus[0]);
kvm->cpus[0] = NULL;
kvm__pause(kvm);
for (i = 1; i < kvm->nrcpus; i++) {
if (kvm->cpus[i]->is_running) {
pthread_kill(kvm->cpus[i]->thread, SIGKVMEXIT);
if (pthread_join(kvm->cpus[i]->thread, &ret) != 0)
die("pthread_join");
kvm_cpu__delete(kvm->cpus[i]);
}
if (ret == NULL)
r = 0;
}
kvm__continue(kvm);
free(kvm->cpus);
kvm->nrcpus = 0;
close(task_eventfd);
return r;
}