近来研究了HPET的规范以及在xen中HPET模拟和时钟流程的源码。由于今天把BIOS给刷坏了,暂时不能工作,只能写点东西。对近来的提交的时钟patch有了些疑问,希望大家耐心看完后提出宝贵意见。欢迎大家一起讨论:
1,patch: fix.tsc.sync.patch
patch内容:
diff -r bac5837c1689 xen/arch/x86/hvm/vpt.c
--- a/xen/arch/x86/hvm/vpt.c Wed May 16 04:50:40 2007 +0800
+++ b/xen/arch/x86/hvm/vpt.c Wed May 16 04:51:01 2007 +0800
@@ -69,7 +69,7 @@ void pt_thaw_time(struct vcpu *v)
if ( v->arch.hvm_vcpu.guest_time )
{
- hvm_set_guest_time(v, v->arch.hvm_vcpu.guest_time);
+ // hvm_set_guest_time(v, v->arch.hvm_vcpu.guest_time);
list_for_each( list, head )
{
@@ -169,8 +169,8 @@ void pt_intr_post(struct vcpu *v, int ve
pt->pending_intr_nr--;
pt->last_plt_gtime += pt->period_cycles;
- if ( hvm_get_guest_time(pt->vcpu) < pt->last_plt_gtime )
- hvm_set_guest_time(pt->vcpu, pt->last_plt_gtime);
+ //if ( hvm_get_guest_time(pt->vcpu) < pt->last_plt_gtime )
+ // hvm_set_guest_time(pt->vcpu, pt->last_plt_gtime);
if ( pt->cb != NULL )
pt->cb(pt->vcpu, pt->priv);
要说明白这个patch的问题,首先要说一下HPET的工作机制:
HPET(高精度时钟) 不是通过pci的CF8, CFC来访问其配置空间的。而是通过mmio映射的方式来访问其配置寄存器的。具体HPET映射到内存中的地址是通过ACPI table告诉给OS的(在xen中其实就是HPET TABLE 代码在/tools/firmware/hvmloader/acpi/build.c)。其具体资源会在ACPI dsdt(/tools/firmware/hvmloader/acpi/dsdt.asl)表中会有描述,这是由BIOS提供的。(在我们目前的G33平台,老版本的BIOS没有报告主板上有HPET,所以在native 的vista中可以看到资源管理器中,时钟设备用的是RTC)。
HPET的配置寄存器占用内存1K的大小。一个block可以最大支持32个Timer。目前在在xen中最大支持3个timer,每个Timer可以单独配置成one-shot 和 period模式。one-shot 就是只对comparator写入一个初值在maincounter里面的值和comparator里一样发生一次中断后,不再发生中断(除非roll over and maincouter wraps around。period模式是当maincounter里面的值和comparator里一样发生一次中断后,会把comparator里的值加上上一次写入comparator
值最为下一次比较点。依次循环下去。
对于one-short模式的Timer,period模式的timer。上面那个patch都会引起中断混乱的问题,还有可能会丢弃OS对HPET的配置。为什么呢:
1,在hpet初始化的hpet_init(xen/arch/x86/hvm/hpet.c)函数中,
h->hpet.capability = 0x8086A201ULL; 这个和acpi 里面的HPET table里面的值是一至的。这个值是什么含义呢,我们看规范:
其实就是3个timer,64bit的maincounter,LegacyReplacement 寄存器等于一。
对于LegacyReplacement 我想补充说一点,LegacyReplacement =1 就是timer 0,取代PIT(8254)。timer 1,取代(RTC)。在这种模式下8254,RTC是不发中断的。
继续看代码:
init_timer(&h->timers[i], hpet_timer_fn, &h->timer_fn_info[i],
v->processor);
给每个hpet的timer注册了一个hpet_timer_fn(定时器的回调函数)
在hpet_timer_fn函数中我们看到这行代码:
uint64_t mc = hpet_read_maincounter(h);
然而:
static inline uint64_t hpet_read_maincounter(HPETState *h)
{
if ( hpet_enabled(h) )
return guest_time_hpet(h->vcpu) + h->mc_offset;
else
return h->hpet.mc64;
}
/* Frequency_of_TSC / frequency_of_HPET = 32 */
#define TSC_PER_HPET_TICK 32
#define guest_time_hpet(v) (hvm_get_guest_time(v) / TSC_PER_HPET_TICK)
uint64 hvm_get_guest_time(struct vcpu *v)
{
u64 host_tsc;
rdtscll(host_tsc);
return host_tsc + v->arch.hvm_vcpu.cache_tsc_offset;
}
void hvm_set_guest_time(struct vcpu *v, u64 gtime)
{
u64 host_tsc;
rdtscll(host_tsc);
v->arch.hvm_vcpu.cache_tsc_offset = gtime - host_tsc;
hvm_funcs.set_tsc_offset(v, v->arch.hvm_vcpu.cache_tsc_offset);
}
根据以上的函数我们发现xen读虚拟hpet的maincounter的值并不像硬件那样直接从寄存器中读,而是对真实的cpu上TSC加上guest的tsc对正式tsc的偏移量。
下面我再说一下上面的patch:
pt_thaw_time()函数是在VCPU被调度出,重新调度得到时间片是执行的,这个函数的原意是在重新得到时间片时更新这个VCPU的cache_tsc_offset,但是patch中把这段给注释了,且不说这么做的好处。如果注释了这个,VCPU的cache_tsc_offset得不到更新,每次HPET 的timer 发完时钟中后更新main counter都会不准。造成没次时钟中断的间隔不一样。从而造成Guest Os系统时间不准,更有可能变慢。