[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: [Qemu-devel] [PATCH v2 2/5] mc146818rtc: precisely count the clock f
From: |
Paolo Bonzini |
Subject: |
Re: [Qemu-devel] [PATCH v2 2/5] mc146818rtc: precisely count the clock for periodic timer |
Date: |
Thu, 4 May 2017 14:31:21 +0200 |
User-agent: |
Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Thunderbird/45.8.0 |
On 04/05/2017 13:59, address@hidden wrote:
> From: Tai Yunfang <address@hidden>
>
> 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
>
> [ Xiao: redesign the algorithm based on Yunfang's original work. ]
>
> Signed-off-by: Xiao Guangrong <address@hidden>
> Signed-off-by: Tai Yunfang <address@hidden>
> ---
> hw/timer/mc146818rtc.c | 116
> ++++++++++++++++++++++++++++++++++++++++---------
> 1 file changed, 96 insertions(+), 20 deletions(-)
>
> diff --git a/hw/timer/mc146818rtc.c b/hw/timer/mc146818rtc.c
> index 5cccb2a..14bde1a 100644
> --- a/hw/timer/mc146818rtc.c
> +++ b/hw/timer/mc146818rtc.c
> @@ -146,31 +146,104 @@ static void rtc_coalesced_timer(void *opaque)
> }
> #endif
>
> -/* handle periodic timer */
> -static void periodic_timer_update(RTCState *s, int64_t current_time)
> +static int period_code_to_clock(int period_code)
> +{
> + /* periodic timer is disabled. */
> + if (!period_code) {
> + return 0;
> + }
> +
> + if (period_code <= 2) {
> + period_code += 7;
> + }
> +
> + /* period in 32 Khz cycles */
> + return 1 << (period_code - 1);
> +}
> +
> +/*
> + * 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, int old_period)
We need old_period to distinguish reconfiguration from interrupts ("if
(old_period)" below), but it can be a bool. Instead of passing the old
period value, we can use s->period which is
period_code_to_clock(old_period).
That is, just do
old_period = s->period;
s->period = rtc_periodic_clock_ticks(s);
/*
* if the periodic timer's update is due to period re-configuration,
* the clock should count since last interrupt.
*/
if (s->period != 0) {
...
if (!from_interrupt) {
}
...
}
where rtc_periodic_clock_ticks takes into account both regA and regB,
returning the result in 32 kHZ ticks.
> {
> int period_code, period;
> - int64_t cur_clock, next_irq_clock;
> + int64_t cur_clock, next_irq_clock, lost_clock = 0;
>
> period_code = s->cmos_data[RTC_REG_A] & 0x0f;
> if (period_code != 0
> && (s->cmos_data[RTC_REG_B] & REG_B_PIE)) {
> - if (period_code <= 2)
> - period_code += 7;
> - /* period in 32 Khz cycles */
> - period = 1 << (period_code - 1);
> -#ifdef TARGET_I386
> - if (period != s->period) {
> - s->irq_coalesced = (s->irq_coalesced * s->period) / period;
> - DPRINTF_C("cmos: coalesced irqs scaled to %d\n",
> s->irq_coalesced);
> - }
> - s->period = period;
> -#endif
> + period = period_code_to_clock(period_code);
Since we have old_period, we can assign s->period here.
> /* compute 32 khz clock */
> cur_clock =
> 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;
> +
> + last_periodic_clock = muldiv64(s->next_periodic_time,
> + RTC_CLOCK_RATE, NANOSECONDS_PER_SECOND);
> + /*
> + * if the next interrupt has not happened yet, we recall the last
> + * interrupt based on the original period.
> + */
> + if (last_periodic_clock > cur_clock) {
> + last_periodic_clock -= period_code_to_clock(old_period);
This becomes "last_periodic_clock -= old_period".
But, I'm not sure how it can happen that last_periodic_clock <=
cur_clock. More precisely, if it happened, you have lost a tick and you
should add it to s->irq_coalesced it below. The simplest way to do it,
is to *always* do the subtraction.
> +
> + /* the last interrupt must have happened. */
> + assert(cur_clock >= last_periodic_clock);
> + }
> +
> + /* calculate the clock since last interrupt. */
> + lost_clock = cur_clock - last_periodic_clock;
> + }
I think the "assert(lost_clock >= 0)" should be here. Then you can also
remove the "the last interrupt must have happened" assertion, since the
two test exactly the same formula:
lost_clock >= 0
cur_clock - last_periodic_clock >= 0
cur_clock >= last_periodic_clock.
Putting everything together, this becomes:
if (!from_interrupt) {
int64_t next_periodic_clock, last_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);
}
if (s->lost_tick_policy == LOST_TICK_POLICY_SLEW) {
...
} else {
lost_clock = MIN(lost_clock, s->period);
}
assert(lost_clock >= 0 && lost_clock < s->period);
> + /*
> +#ifdef TARGET_I386
> + /*
> + * recalculate the coalesced irqs for two reasons:
> + * a) the lost_clock is more that a period, i,e. the timer
> + * interrupt has been lost, we should catch up the time.
> + *
> + * b) the period may be reconfigured, under this case, when
> + * switching from a shorter to a longer period, scale down
> + * the missing ticks since we expect the OS handler to
> + * treat the delayed ticks as longer. Any leftovers are
> + * put back into lost_clock.
> + * When switching to a shorter period, scale up the missing
> + * ticks since we expect the OS handler to treat the delayed
> + * ticks as shorter.
> + */
> + if (s->lost_tick_policy == LOST_TICK_POLICY_SLEW) {
> + uint32_t cur_irq_coalesced = s->irq_coalesced;
> + uint32_t cur_period = s->period;
These are really "old" values rather than current.
> +
> + lost_clock += cur_irq_coalesced * cur_period;
> + s->irq_coalesced = lost_clock / period;
> + lost_clock %= period;
> + s->period = period;
> + if ((cur_irq_coalesced != s->irq_coalesced) ||
> + (cur_period |= s->period)) {
Typo here, so this becomes:
uint32_t old_irq_coalesced = s->irq_coalesced;
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 ...
rtc_coalesced_timer_update(s);
}
> + DPRINTF_C("cmos: coalesced irqs scaled from %d to %d, "
> + "period scaled from %d to %d\n", cur_irq_coalesced,
> + s->irq_coalesced, cur_period, s->period);
> + rtc_coalesced_timer_update(s);
> + }
> + }
> +#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);
See above---let's put it under "else", since it is unnecessary in the
SLEW case.
This is already much more understandable, and all of it makes a lot of
sense. The next version should be perfect. :)
Paolo
> + assert(lost_clock >= 0);
> +
> + next_irq_clock = cur_clock + period - lost_clock;
> s->next_periodic_time = muldiv64(next_irq_clock,
> NANOSECONDS_PER_SECOND,
> RTC_CLOCK_RATE) + 1;
> timer_mod(s->periodic_timer, s->next_periodic_time);
> @@ -186,7 +259,7 @@ static void rtc_periodic_timer(void *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;
> if (s->cmos_data[RTC_REG_B] & REG_B_PIE) {
> s->cmos_data[RTC_REG_C] |= REG_C_IRQF;
> @@ -391,6 +464,7 @@ static void cmos_ioport_write(void *opaque, hwaddr addr,
> uint64_t data, unsigned size)
> {
> RTCState *s = opaque;
> + int cur_period;
> bool update_periodic_timer;
>
> if ((addr & 1) == 0) {
> @@ -424,6 +498,7 @@ static void cmos_ioport_write(void *opaque, hwaddr addr,
> }
> break;
> case RTC_REG_A:
> + cur_period = s->cmos_data[RTC_REG_A] & 0xf;
> update_periodic_timer = (s->cmos_data[RTC_REG_A] ^ data) & 0x0f;
>
> if ((data & 0x60) == 0x60) {
> @@ -450,7 +525,8 @@ static void cmos_ioport_write(void *opaque, hwaddr addr,
> (s->cmos_data[RTC_REG_A] & REG_A_UIP);
>
> if (update_periodic_timer) {
> - periodic_timer_update(s, qemu_clock_get_ns(rtc_clock));
> + periodic_timer_update(s, qemu_clock_get_ns(rtc_clock),
> + cur_period);
> }
>
> check_update_timer(s);
> @@ -487,7 +563,7 @@ static void cmos_ioport_write(void *opaque, hwaddr addr,
> s->cmos_data[RTC_REG_B] = data;
>
> if (update_periodic_timer) {
> - periodic_timer_update(s, qemu_clock_get_ns(rtc_clock));
> + periodic_timer_update(s, qemu_clock_get_ns(rtc_clock), 0);
> }
>
> check_update_timer(s);
> @@ -757,7 +833,7 @@ static int rtc_post_load(void *opaque, int version_id)
> uint64_t now = qemu_clock_get_ns(rtc_clock);
> if (now < s->next_periodic_time ||
> 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 +898,7 @@ static void rtc_notify_clock_reset(Notifier *notifier,
> void *data)
> int64_t now = *(int64_t *)data;
>
> rtc_set_date_from_host(ISA_DEVICE(s));
> - periodic_timer_update(s, now);
> + periodic_timer_update(s, now, 0);
> check_update_timer(s);
> #ifdef TARGET_I386
> if (s->lost_tick_policy == LOST_TICK_POLICY_SLEW) {
>
- [Qemu-devel] [PATCH v2 0/5] mc146818rtc: fix Windows VM clock faster, guangrong . xiao, 2017/05/04
- [Qemu-devel] [PATCH v2 1/5] mc146818rtc: update periodic timer only if it is needed, guangrong . xiao, 2017/05/04
- [Qemu-devel] [PATCH v2 3/5] mc146818rtc: ensure LOST_TICK_POLICY_SLEW is only enabled on TARGET_I386, guangrong . xiao, 2017/05/04
- [Qemu-devel] [PATCH v2 4/5] mc146818rtc: drop unnecessary '#ifdef TARGET_I386', guangrong . xiao, 2017/05/04
- [Qemu-devel] [PATCH v2 2/5] mc146818rtc: precisely count the clock for periodic timer, guangrong . xiao, 2017/05/04
- Re: [Qemu-devel] [PATCH v2 2/5] mc146818rtc: precisely count the clock for periodic timer,
Paolo Bonzini <=
- [Qemu-devel] [PATCH v2 5/5] mc146818rtc: embrace all x86 specific code, guangrong . xiao, 2017/05/04
- Re: [Qemu-devel] [PATCH v2 0/5] mc146818rtc: fix Windows VM clock faster, no-reply, 2017/05/04