提交 369b4135 编写于 作者: T Tai Yunfang 提交者: Paolo Bonzini

mc146818rtc: precisely count the clock for periodic timer

There are two issues in current code:
1) If the period is changed by re-configuring RegA, the coalesced
   irq will be scaled to reflect the new period, however, it
   calculates the new interrupt number like this:
    s->irq_coalesced = (s->irq_coalesced * s->period) / period;

   There are some clocks will be lost if they are not enough to
   be squeezed to a single new period that will cause the VM clock
   slower

   In order to fix the issue, we calculate the interrupt window
   based on the precise clock rather than period, then the clocks
   lost during period is scaled can be compensated properly

2) If periodic_timer_update() is called due to RegA reconfiguration,
   i.e, the period is updated, current time is not the start point
   for the next periodic timer, instead, which should start from the
   last interrupt, otherwise, the clock in VM will become slow

   This patch takes the clocks from last interrupt to current clock
   into account and compensates the clocks for the next interrupt,
   especially if a complete interrupt was lost in this window, the
   time can be caught up by LOST_TICK_POLICY_SLEW
Signed-off-by: NTai Yunfang <yunfangtai@tencent.com>
Signed-off-by: NXiao Guangrong <xiaoguangrong@tencent.com>
Message-Id: <20170510083259.3900-3-xiaoguangrong@tencent.com>
Signed-off-by: NPaolo Bonzini <pbonzini@redhat.com>
上级 9a6e2dcf
...@@ -146,31 +146,100 @@ static void rtc_coalesced_timer(void *opaque) ...@@ -146,31 +146,100 @@ static void rtc_coalesced_timer(void *opaque)
} }
#endif #endif
/* handle periodic timer */ static uint32_t rtc_periodic_clock_ticks(RTCState *s)
static void periodic_timer_update(RTCState *s, int64_t current_time)
{ {
int period_code, period; int period_code;
int64_t cur_clock, next_irq_clock;
if (!(s->cmos_data[RTC_REG_B] & REG_B_PIE)) {
return 0;
}
period_code = s->cmos_data[RTC_REG_A] & 0x0f; period_code = s->cmos_data[RTC_REG_A] & 0x0f;
if (period_code != 0 if (!period_code) {
&& (s->cmos_data[RTC_REG_B] & REG_B_PIE)) { return 0;
if (period_code <= 2) }
period_code += 7;
/* period in 32 Khz cycles */ if (period_code <= 2) {
period = 1 << (period_code - 1); period_code += 7;
#ifdef TARGET_I386 }
if (period != s->period) {
s->irq_coalesced = (s->irq_coalesced * s->period) / period; /* period in 32 Khz cycles */
DPRINTF_C("cmos: coalesced irqs scaled to %d\n", s->irq_coalesced); return 1 << (period_code - 1);
} }
s->period = period;
#endif /*
* handle periodic timer. @old_period indicates the periodic timer update
* is just due to period adjustment.
*/
static void
periodic_timer_update(RTCState *s, int64_t current_time, uint32_t old_period)
{
uint32_t period;
int64_t cur_clock, next_irq_clock, lost_clock = 0;
period = rtc_periodic_clock_ticks(s);
if (period) {
/* compute 32 khz clock */ /* compute 32 khz clock */
cur_clock = cur_clock =
muldiv64(current_time, RTC_CLOCK_RATE, NANOSECONDS_PER_SECOND); muldiv64(current_time, RTC_CLOCK_RATE, NANOSECONDS_PER_SECOND);
next_irq_clock = (cur_clock & ~(period - 1)) + period; /*
* if the periodic timer's update is due to period re-configuration,
* we should count the clock since last interrupt.
*/
if (old_period) {
int64_t last_periodic_clock, next_periodic_clock;
next_periodic_clock = muldiv64(s->next_periodic_time,
RTC_CLOCK_RATE, NANOSECONDS_PER_SECOND);
last_periodic_clock = next_periodic_clock - old_period;
lost_clock = cur_clock - last_periodic_clock;
assert(lost_clock >= 0);
}
#ifdef TARGET_I386
/*
* s->irq_coalesced can change for two reasons:
*
* a) if one or more periodic timer interrupts have been lost,
* lost_clock will be more that a period.
*
* b) when the period may be reconfigured, we expect the OS to
* treat delayed tick as the new period. So, when switching
* from a shorter to a longer period, scale down the missing,
* because the OS will treat past delayed ticks as longer
* (leftovers are put back into lost_clock). When switching
* to a shorter period, scale up the missing ticks since the
* OS handler will treat past delayed ticks as shorter.
*/
if (s->lost_tick_policy == LOST_TICK_POLICY_SLEW) {
uint32_t old_irq_coalesced = s->irq_coalesced;
s->period = period;
lost_clock += old_irq_coalesced * old_period;
s->irq_coalesced = lost_clock / s->period;
lost_clock %= s->period;
if (old_irq_coalesced != s->irq_coalesced ||
old_period != s->period) {
DPRINTF_C("cmos: coalesced irqs scaled from %d to %d, "
"period scaled from %d to %d\n", old_irq_coalesced,
s->irq_coalesced, old_period, s->period);
rtc_coalesced_timer_update(s);
}
} else
#endif
{
/*
* no way to compensate the interrupt if LOST_TICK_POLICY_SLEW
* is not used, we should make the time progress anyway.
*/
lost_clock = MIN(lost_clock, period);
}
assert(lost_clock >= 0 && lost_clock <= period);
next_irq_clock = cur_clock + period - lost_clock;
s->next_periodic_time = muldiv64(next_irq_clock, NANOSECONDS_PER_SECOND, s->next_periodic_time = muldiv64(next_irq_clock, NANOSECONDS_PER_SECOND,
RTC_CLOCK_RATE) + 1; RTC_CLOCK_RATE) + 1;
timer_mod(s->periodic_timer, s->next_periodic_time); timer_mod(s->periodic_timer, s->next_periodic_time);
...@@ -186,7 +255,7 @@ static void rtc_periodic_timer(void *opaque) ...@@ -186,7 +255,7 @@ static void rtc_periodic_timer(void *opaque)
{ {
RTCState *s = opaque; RTCState *s = opaque;
periodic_timer_update(s, s->next_periodic_time); periodic_timer_update(s, s->next_periodic_time, 0);
s->cmos_data[RTC_REG_C] |= REG_C_PF; s->cmos_data[RTC_REG_C] |= REG_C_PF;
if (s->cmos_data[RTC_REG_B] & REG_B_PIE) { if (s->cmos_data[RTC_REG_B] & REG_B_PIE) {
s->cmos_data[RTC_REG_C] |= REG_C_IRQF; s->cmos_data[RTC_REG_C] |= REG_C_IRQF;
...@@ -391,6 +460,7 @@ static void cmos_ioport_write(void *opaque, hwaddr addr, ...@@ -391,6 +460,7 @@ static void cmos_ioport_write(void *opaque, hwaddr addr,
uint64_t data, unsigned size) uint64_t data, unsigned size)
{ {
RTCState *s = opaque; RTCState *s = opaque;
uint32_t old_period;
bool update_periodic_timer; bool update_periodic_timer;
if ((addr & 1) == 0) { if ((addr & 1) == 0) {
...@@ -425,6 +495,7 @@ static void cmos_ioport_write(void *opaque, hwaddr addr, ...@@ -425,6 +495,7 @@ static void cmos_ioport_write(void *opaque, hwaddr addr,
break; break;
case RTC_REG_A: case RTC_REG_A:
update_periodic_timer = (s->cmos_data[RTC_REG_A] ^ data) & 0x0f; update_periodic_timer = (s->cmos_data[RTC_REG_A] ^ data) & 0x0f;
old_period = rtc_periodic_clock_ticks(s);
if ((data & 0x60) == 0x60) { if ((data & 0x60) == 0x60) {
if (rtc_running(s)) { if (rtc_running(s)) {
...@@ -450,7 +521,8 @@ static void cmos_ioport_write(void *opaque, hwaddr addr, ...@@ -450,7 +521,8 @@ static void cmos_ioport_write(void *opaque, hwaddr addr,
(s->cmos_data[RTC_REG_A] & REG_A_UIP); (s->cmos_data[RTC_REG_A] & REG_A_UIP);
if (update_periodic_timer) { if (update_periodic_timer) {
periodic_timer_update(s, qemu_clock_get_ns(rtc_clock)); periodic_timer_update(s, qemu_clock_get_ns(rtc_clock),
old_period);
} }
check_update_timer(s); check_update_timer(s);
...@@ -458,6 +530,7 @@ static void cmos_ioport_write(void *opaque, hwaddr addr, ...@@ -458,6 +530,7 @@ static void cmos_ioport_write(void *opaque, hwaddr addr,
case RTC_REG_B: case RTC_REG_B:
update_periodic_timer = (s->cmos_data[RTC_REG_B] ^ data) update_periodic_timer = (s->cmos_data[RTC_REG_B] ^ data)
& REG_B_PIE; & REG_B_PIE;
old_period = rtc_periodic_clock_ticks(s);
if (data & REG_B_SET) { if (data & REG_B_SET) {
/* update cmos to when the rtc was stopping */ /* update cmos to when the rtc was stopping */
...@@ -487,7 +560,8 @@ static void cmos_ioport_write(void *opaque, hwaddr addr, ...@@ -487,7 +560,8 @@ static void cmos_ioport_write(void *opaque, hwaddr addr,
s->cmos_data[RTC_REG_B] = data; s->cmos_data[RTC_REG_B] = data;
if (update_periodic_timer) { if (update_periodic_timer) {
periodic_timer_update(s, qemu_clock_get_ns(rtc_clock)); periodic_timer_update(s, qemu_clock_get_ns(rtc_clock),
old_period);
} }
check_update_timer(s); check_update_timer(s);
...@@ -757,7 +831,7 @@ static int rtc_post_load(void *opaque, int version_id) ...@@ -757,7 +831,7 @@ static int rtc_post_load(void *opaque, int version_id)
uint64_t now = qemu_clock_get_ns(rtc_clock); uint64_t now = qemu_clock_get_ns(rtc_clock);
if (now < s->next_periodic_time || if (now < s->next_periodic_time ||
now > (s->next_periodic_time + get_max_clock_jump())) { now > (s->next_periodic_time + get_max_clock_jump())) {
periodic_timer_update(s, qemu_clock_get_ns(rtc_clock)); periodic_timer_update(s, qemu_clock_get_ns(rtc_clock), 0);
} }
} }
...@@ -822,7 +896,7 @@ static void rtc_notify_clock_reset(Notifier *notifier, void *data) ...@@ -822,7 +896,7 @@ static void rtc_notify_clock_reset(Notifier *notifier, void *data)
int64_t now = *(int64_t *)data; int64_t now = *(int64_t *)data;
rtc_set_date_from_host(ISA_DEVICE(s)); rtc_set_date_from_host(ISA_DEVICE(s));
periodic_timer_update(s, now); periodic_timer_update(s, now, 0);
check_update_timer(s); check_update_timer(s);
#ifdef TARGET_I386 #ifdef TARGET_I386
if (s->lost_tick_policy == LOST_TICK_POLICY_SLEW) { if (s->lost_tick_policy == LOST_TICK_POLICY_SLEW) {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册