Palacios Public Git Repository

To checkout Palacios execute

  git clone http://v3vee.org/palacios/palacios.web/palacios.git
This will give you the master branch. You probably want the devel branch or one of the release branches. To switch to the devel branch, simply execute
  cd palacios
  git checkout --track -b devel origin/devel
The other branches are similar.


Cleaned up time management stuff, being more careful on signs of various time computa...
[palacios.git] / palacios / src / palacios / vmm_time.c
1 /* 
2  * This file is part of the Palacios Virtual Machine Monitor developed
3  * by the V3VEE Project with funding from the United States National 
4  * Science Foundation and the Department of Energy.  
5  *
6  * The V3VEE Project is a joint project between Northwestern University
7  * and the University of New Mexico.  You can find out more at 
8  * http://www.v3vee.org
9  *
10  * Copyright (c) 2008, Jack Lange <jarusl@cs.northwestern.edu> 
11  * Copyright (c) 2008, The V3VEE Project <http://www.v3vee.org> 
12  * All rights reserved.
13  *
14  * Author: Jack Lange <jarusl@cs.northwestern.edu>
15  *         Patrick G. Bridges <bridges@cs.unm.edu>
16  *
17  * This is free software.  You are permitted to use,
18  * redistribute, and modify it as specified in the file "V3VEE_LICENSE".
19  */
20
21 #include <palacios/vmm.h>
22 #include <palacios/vmm_time.h>
23 #include <palacios/vm_guest.h>
24
25 #ifndef V3_CONFIG_DEBUG_TIME
26 #undef PrintDebug
27 #define PrintDebug(fmt, args...)
28 #endif
29
30 /* Overview 
31  *
32  * Time handling in VMMs is challenging, and Palacios uses the highest 
33  * resolution, lowest overhead timer on modern CPUs that it can - the 
34  * processor timestamp counter (TSC). Note that on somewhat old processors
35  * this can be problematic; in particular, older AMD processors did not 
36  * have a constant rate timestamp counter in the face of power management
37  * events. However, the latest Intel and AMD CPUs all do (should...) have a 
38  * constant rate TSC, and Palacios relies on this fact.
39  * 
40  * Basically, Palacios keeps track of three quantities as it runs to manage
41  * the passage of time:
42  * (1) The host timestamp counter - read directly from HW and never written
43  * (2) A monotonic guest timestamp counter used to measure the progression of
44  *     time in the guest. This is computed using an offsets from (1) above.
45  * (3) The actual guest timestamp counter (which can be written by
46  *     writing to the guest TSC MSR - MSR 0x10) from the monotonic guest TSC.
47  *     This is also computed as an offset from (2) above when the TSC and
48  *     this offset is updated when the TSC MSR is written.
49  *
50  * The value used to offset the guest TSC from the host TSC is the *sum* of all
51  * of these offsets (2 and 3) above
52  * 
53  * Because all other devices are slaved off of the passage of time in the guest,
54  * it is (2) above that drives the firing of other timers in the guest, 
55  * including timer devices such as the Programmable Interrupt Timer (PIT).
56  *
57  * Future additions:
58  * (1) Add support for temporarily skewing guest time off of where it should
59  *     be to support slack simulation of guests. The idea is that simulators
60  *     set this skew to be the difference between how much time passed for a 
61  *     simulated feature and a real implementation of that feature, making 
62  *     pass at a different rate from real time on this core. The VMM will then
63  *     attempt to move this skew back towards 0 subject to resolution/accuracy
64  *     constraints from various system timers.
65  *   
66  *     The main effort in doing this will be to get accuracy/resolution 
67  *     information from each local timer and to use this to bound how much skew
68  *     is removed on each exit.
69  */
70
71
72 static int handle_cpufreq_hcall(struct guest_info * info, uint_t hcall_id, void * priv_data) {
73     struct vm_time * time_state = &(info->time_state);
74
75     info->vm_regs.rbx = time_state->guest_cpu_freq;
76
77     PrintDebug("Guest request cpu frequency: return %ld\n", (long)info->vm_regs.rbx);
78     
79     return 0;
80 }
81
82
83
84 int v3_start_time(struct guest_info * info) {
85     /* We start running with guest_time == host_time */
86     uint64_t t = v3_get_host_time(&info->time_state); 
87
88     PrintDebug("Starting initial guest time as %llu\n", t);
89
90     info->time_state.enter_time = 0;
91     info->time_state.exit_time = t; 
92     info->time_state.last_update = t;
93     info->time_state.initial_time = t;
94     info->yield_start_cycle = t;
95
96     return 0;
97 }
98
99 int v3_offset_time( struct guest_info * info, sint64_t offset )
100 {
101     struct vm_time * time_state = &(info->time_state);
102     PrintDebug("Adding additional offset of %lld to guest time.\n", offset);
103     time_state->guest_host_offset += offset;
104     return 0;
105 }
106
107 #ifdef V3_CONFIG_TIME_DILATION
108 static uint64_t compute_target_host_time(struct guest_info * info, uint64_t guest_time)
109 {
110     struct vm_time * time_state = &(info->time_state);
111     uint64_t guest_elapsed, desired_elapsed;
112     
113     guest_elapsed = (guest_time - time_state->initial_time);
114     desired_elapsed = (guest_elapsed * time_state->host_cpu_freq) / time_state->guest_cpu_freq;
115     return time_state->initial_time + desired_elapsed;
116 }
117
118 static uint64_t compute_target_guest_time(struct guest_info *info)
119 {
120     struct vm_time * time_state = &(info->time_state);
121     uint64_t host_elapsed, desired_elapsed;
122
123     host_elapsed = v3_get_host_time(time_state) - time_state->initial_time;
124     desired_elapsed = (host_elapsed * time_state->guest_cpu_freq) / time_state->host_cpu_freq;
125
126     return time_state->initial_time + desired_elapsed;
127
128
129
130 /* Yield time in the host to deal with a guest that wants to run slower than 
131  * the native host cycle frequency */
132 static int yield_host_time(struct guest_info * info) {
133     struct vm_time * time_state = &(info->time_state);
134     uint64_t host_time, target_host_time;
135     uint64_t guest_time, old_guest_time;
136
137     /* Now, let the host run while the guest is stopped to make the two
138      * sync up. Note that this doesn't assume that guest time is stopped;
139      * the offsetting in the next step will change add an offset to guest
140      * time to account for the time paused even if the geust isn't 
141      * usually paused in the VMM. */
142     host_time = v3_get_host_time(time_state);
143     old_guest_time = v3_compute_guest_time(time_state, host_time);
144     target_host_time = compute_target_host_time(info, old_guest_time);
145
146     while (target_host_time > host_time) {
147         v3_yield(info);
148         host_time = v3_get_host_time(time_state);
149     }
150
151     guest_time = v3_compute_guest_time(time_state, host_time);
152
153     /* We do *not* assume the guest timer was paused in the VM. If it was
154      * this offseting is 0. If it wasn't, we need this. */
155     v3_offset_time(info, (sint64_t)(old_guest_time - guest_time));
156
157     return 0;
158 }
159
160 static int skew_guest_time(struct guest_info * info) {
161     struct vm_time * time_state = &(info->time_state);
162     uint64_t target_guest_time, guest_time;
163     /* Now the host may have gotten ahead of the guest because
164      * yielding is a coarse grained thing. Figure out what guest time
165      * we want to be at, and use the use the offsetting mechanism in 
166      * the VMM to make the guest run forward. We limit *how* much we skew 
167      * it forward to prevent the guest time making large jumps, 
168      * however. */
169     target_guest_time = compute_target_guest_time(info);
170     guest_time = v3_get_guest_time(time_state);
171
172     if (guest_time < target_guest_time) {
173         sint64_t max_skew, desired_skew, skew;
174
175         if (time_state->enter_time) {
176             /* Limit forward skew to 10% of the amount the guest has
177              * run since we last could skew time */
178             max_skew = (sint64_t)(guest_time - time_state->enter_time) / 10.0;
179         } else {
180             max_skew = 0;
181         }
182
183         desired_skew = (sint64_t)(target_guest_time - guest_time);
184         skew = desired_skew > max_skew ? max_skew : desired_skew;
185         PrintDebug("Guest %lld cycles behind where it should be.\n",
186                    desired_skew);
187         PrintDebug("Limit on forward skew is %lld. Skewing forward %lld.\n",
188                    max_skew, skew); 
189         
190         v3_offset_time(info, skew);
191     }
192
193     return 0;
194 }
195 #endif /* V3_CONFIG_TIME_DILATION */
196
197 // Control guest time in relation to host time so that the two stay 
198 // appropriately synchronized to the extent possible. 
199 int v3_adjust_time(struct guest_info * info) {
200
201 #ifdef V3_CONFIG_TIME_DILATION
202     /* First deal with yielding if we want to slow down the guest */
203     yield_host_time(info);
204
205     /* Now, if the guest is too slow, (either from excess yielding above,
206      * or because the VMM is doing something that takes a long time to emulate)
207      * allow guest time to jump forward a bit */
208     skew_guest_time(info);
209 #endif
210     return 0;
211 }
212
213 /* Called immediately upon entry in the the VMM */
214 int 
215 v3_time_exit_vm( struct guest_info * info ) 
216 {
217     struct vm_time * time_state = &(info->time_state);
218     
219     time_state->exit_time = v3_get_host_time(time_state);
220
221     return 0;
222 }
223
224 /* Called immediately prior to entry to the VM */
225 int 
226 v3_time_enter_vm( struct guest_info * info )
227 {
228     struct vm_time * time_state = &(info->time_state);
229     uint64_t host_time;
230
231     host_time = v3_get_host_time(time_state);
232     time_state->enter_time = host_time;
233 #ifdef V3_CONFIG_TIME_DILATION
234     { 
235         uint64_t guest_time;
236         sint64_t offset;
237         guest_time = v3_compute_guest_time(time_state, host_time);
238         // XXX we probably want to use an inline function to do these
239         // time differences to deal with sign and overflow carefully
240         offset = (sint64_t)guest_time - (sint64_t)host_time;
241         PrintDebug("v3_time_enter_vm: guest time offset %lld from host time.\n", offset);
242         time_state->guest_host_offset = offset;
243     }
244 #else
245     time_state->guest_host_offset = 0;
246 #endif
247
248     return 0;
249 }
250         
251
252            
253 struct v3_timer * v3_add_timer(struct guest_info * info, 
254                                struct v3_timer_ops * ops, 
255                                void * private_data) {
256     struct v3_timer * timer = NULL;
257     timer = (struct v3_timer *)V3_Malloc(sizeof(struct v3_timer));
258     V3_ASSERT(timer != NULL);
259
260     timer->ops = ops;
261     timer->private_data = private_data;
262
263     list_add(&(timer->timer_link), &(info->time_state.timers));
264     info->time_state.num_timers++;
265
266     return timer;
267 }
268
269 int v3_remove_timer(struct guest_info * info, struct v3_timer * timer) {
270     list_del(&(timer->timer_link));
271     info->time_state.num_timers--;
272
273     V3_Free(timer);
274     return 0;
275 }
276
277 void v3_update_timers(struct guest_info * info) {
278     struct vm_time *time_state = &info->time_state;
279     struct v3_timer * tmp_timer;
280     sint64_t cycles;
281     uint64_t old_time = info->time_state.last_update;
282
283     time_state->last_update = v3_get_guest_time(time_state);
284     cycles = (sint64_t)(time_state->last_update - old_time);
285     V3_ASSERT(cycles >= 0);
286
287     //    V3_Print("Updating timers with %lld elapsed cycles.\n", cycles);
288     list_for_each_entry(tmp_timer, &(time_state->timers), timer_link) {
289         tmp_timer->ops->update_timer(info, cycles, time_state->guest_cpu_freq, tmp_timer->private_data);
290     }
291 }
292
293 /* 
294  * Handle full virtualization of the time stamp counter.  As noted
295  * above, we don't store the actual value of the TSC, only the guest's
296  * offset from monotonic guest's time. If the guest writes to the TSC, we
297  * handle this by changing that offset.
298  *
299  * Possible TODO: Proper hooking of TSC read/writes?
300  */ 
301
302 int v3_rdtsc(struct guest_info * info) {
303     uint64_t tscval = v3_get_guest_tsc(&info->time_state);
304
305     info->vm_regs.rdx = tscval >> 32;
306     info->vm_regs.rax = tscval & 0xffffffffLL;
307
308     return 0;
309 }
310
311 int v3_handle_rdtsc(struct guest_info * info) {
312     v3_rdtsc(info);
313     
314     info->vm_regs.rax &= 0x00000000ffffffffLL;
315     info->vm_regs.rdx &= 0x00000000ffffffffLL;
316
317     info->rip += 2;
318     
319     return 0;
320 }
321
322 int v3_rdtscp(struct guest_info * info) {
323     int ret;
324     /* First get the MSR value that we need. It's safe to futz with
325      * ra/c/dx here since they're modified by this instruction anyway. */
326     info->vm_regs.rcx = TSC_AUX_MSR; 
327     ret = v3_handle_msr_read(info);
328
329     if (ret != 0) {
330         return ret;
331     }
332
333     info->vm_regs.rcx = info->vm_regs.rax;
334
335     /* Now do the TSC half of the instruction */
336     ret = v3_rdtsc(info);
337
338     if (ret != 0) {
339         return ret;
340     }
341
342     return 0;
343 }
344
345
346 int v3_handle_rdtscp(struct guest_info * info) {
347   PrintDebug("Handling virtual RDTSCP call.\n");
348
349     v3_rdtscp(info);
350
351     info->vm_regs.rax &= 0x00000000ffffffffLL;
352     info->vm_regs.rcx &= 0x00000000ffffffffLL;
353     info->vm_regs.rdx &= 0x00000000ffffffffLL;
354
355     info->rip += 3;
356     
357     return 0;
358 }
359
360 static int tsc_aux_msr_read_hook(struct guest_info *info, uint_t msr_num, 
361                                  struct v3_msr *msr_val, void *priv) {
362     struct vm_time * time_state = &(info->time_state);
363
364     V3_ASSERT(msr_num == TSC_AUX_MSR);
365
366     msr_val->lo = time_state->tsc_aux.lo;
367     msr_val->hi = time_state->tsc_aux.hi;
368
369     return 0;
370 }
371
372 static int tsc_aux_msr_write_hook(struct guest_info *info, uint_t msr_num, 
373                               struct v3_msr msr_val, void *priv) {
374     struct vm_time * time_state = &(info->time_state);
375
376     V3_ASSERT(msr_num == TSC_AUX_MSR);
377
378     time_state->tsc_aux.lo = msr_val.lo;
379     time_state->tsc_aux.hi = msr_val.hi;
380
381     return 0;
382 }
383
384 static int tsc_msr_read_hook(struct guest_info *info, uint_t msr_num,
385                              struct v3_msr *msr_val, void *priv) {
386     uint64_t time = v3_get_guest_tsc(&info->time_state);
387
388     V3_ASSERT(msr_num == TSC_MSR);
389
390     msr_val->hi = time >> 32;
391     msr_val->lo = time & 0xffffffffLL;
392     
393     return 0;
394 }
395
396 static int tsc_msr_write_hook(struct guest_info *info, uint_t msr_num,
397                              struct v3_msr msr_val, void *priv) {
398     struct vm_time * time_state = &(info->time_state);
399     uint64_t guest_time, new_tsc;
400
401     V3_ASSERT(msr_num == TSC_MSR);
402
403     new_tsc = (((uint64_t)msr_val.hi) << 32) | (uint64_t)msr_val.lo;
404     guest_time = v3_get_guest_time(time_state);
405     time_state->tsc_guest_offset = (sint64_t)new_tsc - (sint64_t)guest_time; 
406
407     return 0;
408 }
409
410
411 int v3_init_time_vm(struct v3_vm_info * vm) {
412     int ret;
413
414     PrintDebug("Installing TSC MSR hook.\n");
415     ret = v3_hook_msr(vm, TSC_MSR, 
416                       tsc_msr_read_hook, tsc_msr_write_hook, NULL);
417
418     if (ret != 0) {
419         return ret;
420     }
421
422     PrintDebug("Installing TSC_AUX MSR hook.\n");
423     ret = v3_hook_msr(vm, TSC_AUX_MSR, tsc_aux_msr_read_hook, 
424                       tsc_aux_msr_write_hook, NULL);
425
426     if (ret != 0) {
427         return ret;
428     }
429
430     PrintDebug("Registering TIME_CPUFREQ hypercall.\n");
431     ret = v3_register_hypercall(vm, TIME_CPUFREQ_HCALL, 
432                                 handle_cpufreq_hcall, NULL);
433
434     return ret;
435 }
436
437 void v3_deinit_time_vm(struct v3_vm_info * vm) {
438     v3_unhook_msr(vm, TSC_MSR);
439     v3_unhook_msr(vm, TSC_AUX_MSR);
440
441     v3_remove_hypercall(vm, TIME_CPUFREQ_HCALL);
442 }
443
444 void v3_init_time_core(struct guest_info * info) {
445     struct vm_time * time_state = &(info->time_state);
446     v3_cfg_tree_t * cfg_tree = info->core_cfg_data;
447     char * khz = NULL;
448
449     time_state->host_cpu_freq = V3_CPU_KHZ();
450     khz = v3_cfg_val(cfg_tree, "khz");
451
452     if (khz) {
453         time_state->guest_cpu_freq = atoi(khz);
454         PrintDebug("Logical Core %d (vcpu=%d) CPU frequency requested at %d khz.\n", 
455                    info->pcpu_id, info->vcpu_id, time_state->guest_cpu_freq);
456     } 
457     
458     if ( (khz == NULL) || 
459          (time_state->guest_cpu_freq <= 0)  || 
460          (time_state->guest_cpu_freq > time_state->host_cpu_freq) ) {
461
462         time_state->guest_cpu_freq = time_state->host_cpu_freq;
463     }
464
465     PrintDebug("Logical Core %d (vcpu=%d) CPU frequency set to %d KHz (host CPU frequency = %d KHz).\n", 
466                info->pcpu_id, info->vcpu_id,
467                time_state->guest_cpu_freq, 
468                time_state->host_cpu_freq);
469
470     time_state->initial_time = 0;
471     time_state->last_update = 0;
472     time_state->guest_host_offset = 0;
473     time_state->tsc_guest_offset = 0;
474
475     INIT_LIST_HEAD(&(time_state->timers));
476     time_state->num_timers = 0;
477     
478     time_state->tsc_aux.lo = 0;
479     time_state->tsc_aux.hi = 0;
480 }
481
482
483 void v3_deinit_time_core(struct guest_info * core) {
484     struct vm_time * time_state = &(core->time_state);
485     struct v3_timer * tmr = NULL;
486     struct v3_timer * tmp = NULL;
487
488     list_for_each_entry_safe(tmr, tmp, &(time_state->timers), timer_link) {
489         v3_remove_timer(core, tmr);
490     }
491
492 }