[PATCH v6 7/9] hv: vrtc calibrate function


Zhao, Yuanyuan
 

For TSC's precision (20~100ppm) is lower than physical RTC (less than 20ppm),
vRTC need to be calibrated by physical RTC. A timer tiggers calibration for
vRTC every 3 hours. This can improve efficiency because physical RTC can be
read once and calibrate all vRTC.

Signed-off-by: Yuanyuan Zhao <yuanyuan.zhao@...>
---
hypervisor/dm/vrtc.c | 80 ++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 77 insertions(+), 3 deletions(-)

diff --git a/hypervisor/dm/vrtc.c b/hypervisor/dm/vrtc.c
index 9157a8fc2..3a21f219f 100644
--- a/hypervisor/dm/vrtc.c
+++ b/hypervisor/dm/vrtc.c
@@ -51,6 +51,8 @@ struct clktime {
uint32_t dow; /* day of week (0 - 6; 0 = Sunday) */
};

+static spinlock_t vrtc_rebase_lock = { .head = 0U, .tail = 0U };
+
#define POSIX_BASE_YEAR 1970
#define SECDAY (24 * 60 * 60)
#define SECYR (SECDAY * 365)
@@ -381,10 +383,12 @@ static time_t vrtc_get_current_time(struct acrn_vrtc *vrtc)
uint64_t offset;
time_t second = VRTC_BROKEN_TIME;

+ spinlock_obtain(&vrtc_rebase_lock);
if (vrtc->base_rtctime > 0) {
offset = (cpu_ticks() - vrtc->base_tsc) / (get_tsc_khz() * 1000U);
second = vrtc->base_rtctime + vrtc->offset_rtctime + (time_t)offset;
}
+ spinlock_release(&vrtc_rebase_lock);
return second;
}

@@ -568,7 +572,9 @@ static bool vrtc_write(struct acrn_vcpu *vcpu, uint16_t addr, size_t width,
*((uint8_t *)&vrtc->rtcdev + vrtc->addr) = (uint8_t)(value & 0xFFU);
current = vrtc_get_current_time(vrtc);
after = rtc_to_secs(vrtc);
+ spinlock_obtain(&vrtc_rebase_lock);
vrtc->offset_rtctime += after - current;
+ spinlock_release(&vrtc_rebase_lock);
break;
}
}
@@ -577,9 +583,70 @@ static bool vrtc_write(struct acrn_vcpu *vcpu, uint16_t addr, size_t width,
return true;
}

+#define CALIBRATE_PERIOD (3 * 3600 * 1000) /* By ms, totally 3 hours. */
+static struct hv_timer calibrate_timer;
+
+static time_t vrtc_get_physical_rtc_time(struct acrn_vrtc *vrtc)
+{
+ uint8_t *rtc = (uint8_t *)&vrtc->rtcdev;
+
+ *(rtc + RTC_SEC) = cmos_get_reg_val(RTC_SEC);
+ *(rtc + RTC_MIN) = cmos_get_reg_val(RTC_MIN);
+ *(rtc + RTC_HRS) = cmos_get_reg_val(RTC_HRS);
+ *(rtc + RTC_DAY) = cmos_get_reg_val(RTC_DAY);
+ *(rtc + RTC_MONTH) = cmos_get_reg_val(RTC_MONTH);
+ *(rtc + RTC_YEAR) = cmos_get_reg_val(RTC_YEAR);
+ *(rtc + RTC_CENTURY) = cmos_get_reg_val(RTC_CENTURY);
+ *(rtc + RTC_STATUSB) = cmos_get_reg_val(RTC_STATUSB);
+
+ return rtc_to_secs(vrtc);
+}
+
+static void vrtc_update_basetime(time_t physical_time, time_t offset)
+{
+ struct acrn_vm *vm;
+ uint32_t vm_id;
+
+ for (vm_id = 0U; vm_id < CONFIG_MAX_VM_NUM; vm_id++) {
+ vm = get_vm_from_vmid(vm_id);
+ if (is_rt_vm(vm) || is_prelaunched_vm(vm)) {
+ spinlock_obtain(&vrtc_rebase_lock);
+ vm->vrtc.base_tsc = cpu_ticks();
+ vm->vrtc.base_rtctime = physical_time;
+ vm->vrtc.offset_rtctime += offset;
+ spinlock_release(&vrtc_rebase_lock);
+ }
+ }
+}
+
+static void calibrate_timer_callback(__unused void *data)
+{
+ struct acrn_vrtc temp_vrtc;
+ time_t physical_time = vrtc_get_physical_rtc_time(&temp_vrtc);
+
+ vrtc_update_basetime(physical_time, 0);
+}
+
+static void calibrate_setup_timer(void)
+{
+ uint64_t period_in_cycle, fire_tsc;
+
+ period_in_cycle = TICKS_PER_MS * CALIBRATE_PERIOD;
+ fire_tsc = cpu_ticks() + period_in_cycle;
+ initialize_timer(&calibrate_timer,
+ calibrate_timer_callback, NULL,
+ fire_tsc, period_in_cycle);
+
+ /* Start an periodic timer */
+ if (add_timer(&calibrate_timer) != 0) {
+ pr_err("Failed to add calibrate timer");
+ }
+}
+
static void vrtc_set_basetime(struct acrn_vrtc *vrtc)
{
struct rtcdev *vrtcdev = &vrtc->rtcdev;
+ time_t current;

/*
* Read base time from physical rtc.
@@ -596,7 +663,10 @@ static void vrtc_set_basetime(struct acrn_vrtc *vrtc)
vrtcdev->reg_c = cmos_get_reg_val(RTC_INTR);
vrtcdev->reg_d = cmos_get_reg_val(RTC_STATUSD);

- vrtc->base_rtctime = rtc_to_secs(vrtc);
+ current = rtc_to_secs(vrtc);
+ spinlock_obtain(&vrtc_rebase_lock);
+ vrtc->base_rtctime = current;
+ spinlock_release(&vrtc_rebase_lock);
}

void vrtc_init(struct acrn_vm *vm)
@@ -610,6 +680,10 @@ void vrtc_init(struct acrn_vm *vm)
vm->vrtc.vm = vm;
register_pio_emulation_handler(vm, RTC_PIO_IDX, &range, vrtc_read, vrtc_write);

- vrtc_set_basetime(&vm->vrtc);
- vm->vrtc.base_tsc = cpu_ticks();
+ if (is_service_vm(vm)) {
+ calibrate_setup_timer();
+ } else {
+ vrtc_set_basetime(&vm->vrtc);
+ vm->vrtc.base_tsc = cpu_ticks();
+ }
}
--
2.25.1


Junjie Mao
 

Yuanyuan Zhao <yuanyuan.zhao@...> writes:

For TSC's precision (20~100ppm) is lower than physical RTC (less than 20ppm),
vRTC need to be calibrated by physical RTC. A timer tiggers calibration for
vRTC every 3 hours. This can improve efficiency because physical RTC can be
read once and calibrate all vRTC.

Signed-off-by: Yuanyuan Zhao <yuanyuan.zhao@...>
Reviewed-by: Junjie Mao <junjie.mao@...>

---
hypervisor/dm/vrtc.c | 80 ++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 77 insertions(+), 3 deletions(-)

diff --git a/hypervisor/dm/vrtc.c b/hypervisor/dm/vrtc.c
index 9157a8fc2..3a21f219f 100644
--- a/hypervisor/dm/vrtc.c
+++ b/hypervisor/dm/vrtc.c
@@ -51,6 +51,8 @@ struct clktime {
uint32_t dow; /* day of week (0 - 6; 0 = Sunday) */
};

+static spinlock_t vrtc_rebase_lock = { .head = 0U, .tail = 0U };
+
#define POSIX_BASE_YEAR 1970
#define SECDAY (24 * 60 * 60)
#define SECYR (SECDAY * 365)
@@ -381,10 +383,12 @@ static time_t vrtc_get_current_time(struct acrn_vrtc *vrtc)
uint64_t offset;
time_t second = VRTC_BROKEN_TIME;

+ spinlock_obtain(&vrtc_rebase_lock);
if (vrtc->base_rtctime > 0) {
offset = (cpu_ticks() - vrtc->base_tsc) / (get_tsc_khz() * 1000U);
second = vrtc->base_rtctime + vrtc->offset_rtctime + (time_t)offset;
}
+ spinlock_release(&vrtc_rebase_lock);
return second;
}

@@ -568,7 +572,9 @@ static bool vrtc_write(struct acrn_vcpu *vcpu, uint16_t addr, size_t width,
*((uint8_t *)&vrtc->rtcdev + vrtc->addr) = (uint8_t)(value & 0xFFU);
current = vrtc_get_current_time(vrtc);
after = rtc_to_secs(vrtc);
+ spinlock_obtain(&vrtc_rebase_lock);
vrtc->offset_rtctime += after - current;
+ spinlock_release(&vrtc_rebase_lock);
break;
}
}
@@ -577,9 +583,70 @@ static bool vrtc_write(struct acrn_vcpu *vcpu, uint16_t addr, size_t width,
return true;
}

+#define CALIBRATE_PERIOD (3 * 3600 * 1000) /* By ms, totally 3 hours. */
+static struct hv_timer calibrate_timer;
+
+static time_t vrtc_get_physical_rtc_time(struct acrn_vrtc *vrtc)
+{
+ uint8_t *rtc = (uint8_t *)&vrtc->rtcdev;
+
+ *(rtc + RTC_SEC) = cmos_get_reg_val(RTC_SEC);
+ *(rtc + RTC_MIN) = cmos_get_reg_val(RTC_MIN);
+ *(rtc + RTC_HRS) = cmos_get_reg_val(RTC_HRS);
+ *(rtc + RTC_DAY) = cmos_get_reg_val(RTC_DAY);
+ *(rtc + RTC_MONTH) = cmos_get_reg_val(RTC_MONTH);
+ *(rtc + RTC_YEAR) = cmos_get_reg_val(RTC_YEAR);
+ *(rtc + RTC_CENTURY) = cmos_get_reg_val(RTC_CENTURY);
+ *(rtc + RTC_STATUSB) = cmos_get_reg_val(RTC_STATUSB);
+
+ return rtc_to_secs(vrtc);
+}
+
+static void vrtc_update_basetime(time_t physical_time, time_t offset)
+{
+ struct acrn_vm *vm;
+ uint32_t vm_id;
+
+ for (vm_id = 0U; vm_id < CONFIG_MAX_VM_NUM; vm_id++) {
+ vm = get_vm_from_vmid(vm_id);
+ if (is_rt_vm(vm) || is_prelaunched_vm(vm)) {
+ spinlock_obtain(&vrtc_rebase_lock);
+ vm->vrtc.base_tsc = cpu_ticks();
+ vm->vrtc.base_rtctime = physical_time;
+ vm->vrtc.offset_rtctime += offset;
+ spinlock_release(&vrtc_rebase_lock);
+ }
+ }
+}
+
+static void calibrate_timer_callback(__unused void *data)
+{
+ struct acrn_vrtc temp_vrtc;
+ time_t physical_time = vrtc_get_physical_rtc_time(&temp_vrtc);
+
+ vrtc_update_basetime(physical_time, 0);
+}
+
+static void calibrate_setup_timer(void)
+{
+ uint64_t period_in_cycle, fire_tsc;
+
+ period_in_cycle = TICKS_PER_MS * CALIBRATE_PERIOD;
+ fire_tsc = cpu_ticks() + period_in_cycle;
+ initialize_timer(&calibrate_timer,
+ calibrate_timer_callback, NULL,
+ fire_tsc, period_in_cycle);
+
+ /* Start an periodic timer */
+ if (add_timer(&calibrate_timer) != 0) {
+ pr_err("Failed to add calibrate timer");
+ }
+}
+
static void vrtc_set_basetime(struct acrn_vrtc *vrtc)
{
struct rtcdev *vrtcdev = &vrtc->rtcdev;
+ time_t current;

/*
* Read base time from physical rtc.
@@ -596,7 +663,10 @@ static void vrtc_set_basetime(struct acrn_vrtc *vrtc)
vrtcdev->reg_c = cmos_get_reg_val(RTC_INTR);
vrtcdev->reg_d = cmos_get_reg_val(RTC_STATUSD);

- vrtc->base_rtctime = rtc_to_secs(vrtc);
+ current = rtc_to_secs(vrtc);
+ spinlock_obtain(&vrtc_rebase_lock);
+ vrtc->base_rtctime = current;
+ spinlock_release(&vrtc_rebase_lock);
}

void vrtc_init(struct acrn_vm *vm)
@@ -610,6 +680,10 @@ void vrtc_init(struct acrn_vm *vm)
vm->vrtc.vm = vm;
register_pio_emulation_handler(vm, RTC_PIO_IDX, &range, vrtc_read, vrtc_write);

- vrtc_set_basetime(&vm->vrtc);
- vm->vrtc.base_tsc = cpu_ticks();
+ if (is_service_vm(vm)) {
+ calibrate_setup_timer();
+ } else {
+ vrtc_set_basetime(&vm->vrtc);
+ vm->vrtc.base_tsc = cpu_ticks();
+ }
}