diff mbox

[05/10] RTC: Update the RTC clock only when reading it

Message ID 1343839312-24030-6-git-send-email-pbonzini@redhat.com
State New
Headers show

Commit Message

Paolo Bonzini Aug. 1, 2012, 4:41 p.m. UTC
From: Yang Zhang <yang.z.zhang@intel.com>

Calculate guest RTC based on the time of the last update, instead of
using timers.  The formula is

    (base_rtc + guest_time_now - guest_time_last_update + offset)

Base_rtc is the RTC value when the RTC was last updated.
Guest_time_now is the guest time when the access happens.
Guest_time_last_update was the guest time when the RTC was last updated.
Offset is used when divider reset happens or the set bit is toggled.

The timer is kept in order to signal interrupts, but it only needs to
run when either UF or AF is cleared.  When the bits are both set, the
timer does not run.

UIP is now synthesized when reading register A.  If the timer is not set,
or if there is more than one second before it (as is the case at the
end of this series), the leading edge of UIP is computed and the rising
edge occurs 220us later.  If the update timer occurs within one second,
however, the rising edge of the AF and UF bits should coincide withe
the falling edge of UIP.  We do not know exactly when this will happen
because there could be delays in the servicing of the timer.  Hence, in
this case reading register A only computes for the rising edge of UIP,
and latches the bit until the timer is fired and clears it.

Signed-off-by: Yang Zhang <yang.z.zhang@intel.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 hw/mc146818rtc.c |  321 +++++++++++++++++++++++++++++++-----------------------
 1 file changed, 186 insertions(+), 135 deletions(-)

Comments

Anthony Liguori Aug. 1, 2012, 7:48 p.m. UTC | #1
Paolo Bonzini <pbonzini@redhat.com> writes:

> From: Yang Zhang <yang.z.zhang@intel.com>
>
> Calculate guest RTC based on the time of the last update, instead of
> using timers.  The formula is
>
>     (base_rtc + guest_time_now - guest_time_last_update + offset)
>
> Base_rtc is the RTC value when the RTC was last updated.
> Guest_time_now is the guest time when the access happens.
> Guest_time_last_update was the guest time when the RTC was last updated.
> Offset is used when divider reset happens or the set bit is toggled.
>
> The timer is kept in order to signal interrupts, but it only needs to
> run when either UF or AF is cleared.  When the bits are both set, the
> timer does not run.
>
> UIP is now synthesized when reading register A.  If the timer is not set,
> or if there is more than one second before it (as is the case at the
> end of this series), the leading edge of UIP is computed and the rising
> edge occurs 220us later.  If the update timer occurs within one second,
> however, the rising edge of the AF and UF bits should coincide withe
> the falling edge of UIP.  We do not know exactly when this will happen
> because there could be delays in the servicing of the timer.  Hence, in
> this case reading register A only computes for the rising edge of UIP,
> and latches the bit until the timer is fired and clears it.
>
> Signed-off-by: Yang Zhang <yang.z.zhang@intel.com>
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> ---
>  hw/mc146818rtc.c |  321 +++++++++++++++++++++++++++++++-----------------------
>  1 file changed, 186 insertions(+), 135 deletions(-)
>
> diff --git a/hw/mc146818rtc.c b/hw/mc146818rtc.c
> index 293f174..2c34a82 100644
> --- a/hw/mc146818rtc.c
> +++ b/hw/mc146818rtc.c
> @@ -45,6 +45,8 @@
>  # define DPRINTF_C(format, ...)      do { } while (0)
>  #endif
>  
> +#define NSEC_PER_SEC    1000000000LL
> +
>  #define RTC_REINJECT_ON_ACK_COUNT 20
>  
>  typedef struct RTCState {
> @@ -54,27 +56,40 @@ typedef struct RTCState {
>      uint8_t cmos_index;
>      struct tm current_tm;
>      int32_t base_year;
> +    uint64_t base_rtc;
> +    uint64_t last_update;
> +    int64_t offset;
>      qemu_irq irq;
>      qemu_irq sqw_irq;
>      int it_shift;
>      /* periodic timer */
>      QEMUTimer *periodic_timer;
>      int64_t next_periodic_time;
> -    /* second update */
> -    int64_t next_second_time;
> +    /* update-ended timer */
> +    QEMUTimer *update_timer;
>      uint16_t irq_reinject_on_ack_count;
>      uint32_t irq_coalesced;
>      uint32_t period;
>      QEMUTimer *coalesced_timer;
> -    QEMUTimer *second_timer;
> -    QEMUTimer *second_timer2;
>      Notifier clock_reset_notifier;
>      LostTickPolicy lost_tick_policy;
>      Notifier suspend_notifier;
>  } RTCState;
>  
>  static void rtc_set_time(RTCState *s);
> -static void rtc_copy_date(RTCState *s);
> +static void rtc_update_time(RTCState *s);
> +static void rtc_set_cmos(RTCState *s);
> +static inline int rtc_from_bcd(RTCState *s, int a);
> +
> +static uint64_t get_guest_rtc_ns(RTCState *s)
> +{
> +    uint64_t guest_rtc;
> +    uint64_t guest_clock = qemu_get_clock_ns(rtc_clock);
> +
> +    guest_rtc = s->base_rtc * NSEC_PER_SEC
> +                 + guest_clock - s->last_update + s->offset;
> +    return guest_rtc;
> +}
>  
>  #ifdef TARGET_I386
>  static void rtc_coalesced_timer_update(RTCState *s)
> @@ -110,6 +125,7 @@ static void rtc_coalesced_timer(void *opaque)
>  }
>  #endif
>  
> +/* handle periodic timer */
>  static void periodic_timer_update(RTCState *s, int64_t current_time)
>  {
>      int period_code, period;
> @@ -175,6 +191,100 @@ static void rtc_periodic_timer(void *opaque)
>      }
>  }
>  
> +/* handle update-ended timer */
> +static void check_update_timer(RTCState *s)
> +{
> +    uint64_t next_update_time;
> +    uint64_t guest_nsec;
> +
> +    /* From the data sheet: setting the SET bit does not prevent
> +     * interrupts from occurring!  However, it will prevent an
> +     * alarm interrupt from occurring, because the time of day is
> +     * not updated.
> +     */
> +    if ((s->cmos_data[RTC_REG_C] & REG_C_UF) &&
> +        (s->cmos_data[RTC_REG_B] & REG_B_SET)) {
> +        qemu_del_timer(s->update_timer);
> +        return;
> +    }
> +    if ((s->cmos_data[RTC_REG_C] & REG_C_UF) &&
> +        (s->cmos_data[RTC_REG_C] & REG_C_AF)) {
> +        qemu_del_timer(s->update_timer);
> +        return;
> +    }
> +
> +    guest_nsec = get_guest_rtc_ns(s) % NSEC_PER_SEC;
> +    /* reprogram to next second */
> +    next_update_time = qemu_get_clock_ns(rtc_clock)
> +        + NSEC_PER_SEC - guest_nsec;
> +    if (next_update_time != qemu_timer_expire_time_ns(s->update_timer)) {
> +        qemu_mod_timer(s->update_timer, next_update_time);
> +    }
> +}
> +
> +static inline uint8_t convert_hour(RTCState *s, uint8_t hour)
> +{
> +    if (!(s->cmos_data[RTC_REG_B] & REG_B_24H)) {
> +        hour %= 12;
> +        if (s->cmos_data[RTC_HOURS] & 0x80) {
> +            hour += 12;
> +        }
> +    }
> +    return hour;
> +}
> +
> +static uint32_t check_alarm(RTCState *s)
> +{
> +    uint8_t alarm_hour, alarm_min, alarm_sec;
> +    uint8_t cur_hour, cur_min, cur_sec;
> +
> +    alarm_sec = rtc_from_bcd(s, s->cmos_data[RTC_SECONDS_ALARM]);
> +    alarm_min = rtc_from_bcd(s, s->cmos_data[RTC_MINUTES_ALARM]);
> +    alarm_hour = rtc_from_bcd(s, s->cmos_data[RTC_HOURS_ALARM]);
> +    alarm_hour = convert_hour(s, alarm_hour);
> +
> +    cur_sec = rtc_from_bcd(s, s->cmos_data[RTC_SECONDS]);
> +    cur_min = rtc_from_bcd(s, s->cmos_data[RTC_MINUTES]);
> +    cur_hour = rtc_from_bcd(s, s->cmos_data[RTC_HOURS]);
> +    cur_hour = convert_hour(s, cur_hour);
> +
> +    if (((s->cmos_data[RTC_SECONDS_ALARM] & 0xc0) == 0xc0
> +                || alarm_sec == cur_sec) &&
> +            ((s->cmos_data[RTC_MINUTES_ALARM] & 0xc0) == 0xc0
> +             || alarm_min == cur_min) &&
> +            ((s->cmos_data[RTC_HOURS_ALARM] & 0xc0) == 0xc0
> +             || alarm_hour == cur_hour)) {
> +        return 1;
> +    }
> +    return 0;
> +
> +}
> +
> +static void rtc_update_timer(void *opaque)
> +{
> +    RTCState *s = opaque;
> +    int32_t irqs = REG_C_UF;
> +    int32_t new_irqs;
> +
> +    /* UIP might have been latched, update time and clear it.  */
> +    rtc_update_time(s);
> +    s->cmos_data[RTC_REG_A] &= ~REG_A_UIP;
> +
> +    if (check_alarm(s)) {
> +        irqs |= REG_C_AF;
> +        if (s->cmos_data[RTC_REG_B] & REG_B_AIE) {
> +            qemu_system_wakeup_request(QEMU_WAKEUP_REASON_RTC);
> +        }
> +    }
> +    new_irqs = irqs & ~s->cmos_data[RTC_REG_C];
> +    s->cmos_data[RTC_REG_C] |= irqs;
> +    if ((new_irqs & s->cmos_data[RTC_REG_B]) != 0) {
> +        s->cmos_data[RTC_REG_C] |= REG_C_IRQF;
> +        qemu_irq_raise(s->irq);
> +    }
> +    check_update_timer(s);
> +}
> +
>  static void cmos_ioport_write(void *opaque, uint32_t addr, uint32_t data)
>  {
>      RTCState *s = opaque;
> @@ -189,6 +299,7 @@ static void cmos_ioport_write(void *opaque, uint32_t addr, uint32_t data)
>          case RTC_MINUTES_ALARM:
>          case RTC_HOURS_ALARM:
>              s->cmos_data[s->cmos_index] = data;
> +            check_update_timer(s);
>              break;
>          case RTC_SECONDS:
>          case RTC_MINUTES:
> @@ -201,6 +312,7 @@ static void cmos_ioport_write(void *opaque, uint32_t addr, uint32_t data)
>              /* if in set mode, do not update the time */
>              if (!(s->cmos_data[RTC_REG_B] & REG_B_SET)) {
>                  rtc_set_time(s);
> +                check_update_timer(s);
>              }
>              break;
>          case RTC_REG_A:
> @@ -208,15 +320,21 @@ static void cmos_ioport_write(void *opaque, uint32_t addr, uint32_t data)
>              s->cmos_data[RTC_REG_A] = (data & ~REG_A_UIP) |
>                  (s->cmos_data[RTC_REG_A] & REG_A_UIP);
>              periodic_timer_update(s, qemu_get_clock_ns(rtc_clock));
> +            check_update_timer(s);
>              break;
>          case RTC_REG_B:
>              if (data & REG_B_SET) {
> +                /* update cmos to when the rtc was stopping */
> +                if (!(s->cmos_data[RTC_REG_B] & REG_B_SET)) {
> +                    rtc_update_time(s);
> +                }
>                  /* set mode: reset UIP mode */
>                  s->cmos_data[RTC_REG_A] &= ~REG_A_UIP;
>                  data &= ~REG_B_UIE;
>              } else {
>                  /* if disabling set mode, update the time */
>                  if (s->cmos_data[RTC_REG_B] & REG_B_SET) {
> +                    s->offset = get_guest_rtc_ns(s) % NSEC_PER_SEC;
>                      rtc_set_time(s);
>                  }
>              }
> @@ -231,6 +349,7 @@ static void cmos_ioport_write(void *opaque, uint32_t addr, uint32_t data)
>              }
>              s->cmos_data[RTC_REG_B] = data;
>              periodic_timer_update(s, qemu_get_clock_ns(rtc_clock));
> +            check_update_timer(s);
>              break;
>          case RTC_REG_C:
>          case RTC_REG_D:
> @@ -279,10 +398,13 @@ static void rtc_set_time(RTCState *s)
>      tm->tm_mon = rtc_from_bcd(s, s->cmos_data[RTC_MONTH]) - 1;
>      tm->tm_year = rtc_from_bcd(s, s->cmos_data[RTC_YEAR]) + s->base_year - 1900;
>  
> +    s->base_rtc = mktimegm(tm);
> +    s->last_update = qemu_get_clock_ns(rtc_clock);
> +
>      rtc_change_mon_event(tm);
>  }
>  
> -static void rtc_copy_date(RTCState *s)
> +static void rtc_set_cmos(RTCState *s)
>  {
>      const struct tm *tm = &s->current_tm;
>      int year;
> @@ -308,122 +430,41 @@ static void rtc_copy_date(RTCState *s)
>      s->cmos_data[RTC_YEAR] = rtc_to_bcd(s, year);
>  }
>  
> -/* month is between 0 and 11. */
> -static int get_days_in_month(int month, int year)
> +static void rtc_update_time(RTCState *s)
>  {
> -    static const int days_tab[12] = {
> -        31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
> -    };
> -    int d;
> -    if ((unsigned )month >= 12)
> -        return 31;
> -    d = days_tab[month];
> -    if (month == 1) {
> -        if ((year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0))
> -            d++;
> -    }
> -    return d;
> +    struct tm *ret;
> +    time_t guest_sec;
> +    int64_t guest_nsec;
> +
> +    guest_nsec = get_guest_rtc_ns(s);
> +    guest_sec = guest_nsec / NSEC_PER_SEC;
> +    ret = gmtime(&guest_sec);

gmtime_r() ?

> +    s->current_tm = *ret;
> +    rtc_set_cmos(s);
>  }
>  
> -/* update 'tm' to the next second */
> -static void rtc_next_second(struct tm *tm)
> +static int update_in_progress(RTCState *s)
>  {
> -    int days_in_month;
> -
> -    tm->tm_sec++;
> -    if ((unsigned)tm->tm_sec >= 60) {
> -        tm->tm_sec = 0;
> -        tm->tm_min++;
> -        if ((unsigned)tm->tm_min >= 60) {
> -            tm->tm_min = 0;
> -            tm->tm_hour++;
> -            if ((unsigned)tm->tm_hour >= 24) {
> -                tm->tm_hour = 0;
> -                /* next day */
> -                tm->tm_wday++;
> -                if ((unsigned)tm->tm_wday >= 7)
> -                    tm->tm_wday = 0;
> -                days_in_month = get_days_in_month(tm->tm_mon,
> -                                                  tm->tm_year + 1900);
> -                tm->tm_mday++;
> -                if (tm->tm_mday < 1) {
> -                    tm->tm_mday = 1;
> -                } else if (tm->tm_mday > days_in_month) {
> -                    tm->tm_mday = 1;
> -                    tm->tm_mon++;
> -                    if (tm->tm_mon >= 12) {
> -                        tm->tm_mon = 0;
> -                        tm->tm_year++;
> -                    }
> -                }
> -            }
> -        }
> -    }
> -}
> -
> -
> -static void rtc_update_second(void *opaque)
> -{
> -    RTCState *s = opaque;
> -    int64_t delay;
> -
> -    /* if the oscillator is not in normal operation, we do not update */
> -    if ((s->cmos_data[RTC_REG_A] & 0x70) != 0x20) {
> -        s->next_second_time += get_ticks_per_sec();
> -        qemu_mod_timer(s->second_timer, s->next_second_time);
> -    } else {
> -        rtc_next_second(&s->current_tm);
> -
> -        if (!(s->cmos_data[RTC_REG_B] & REG_B_SET)) {
> -            /* update in progress bit */
> -            s->cmos_data[RTC_REG_A] |= REG_A_UIP;
> -        }
> -        /* should be 244 us = 8 / 32768 seconds, but currently the
> -           timers do not have the necessary resolution. */
> -        delay = (get_ticks_per_sec() * 1) / 100;
> -        if (delay < 1)
> -            delay = 1;
> -        qemu_mod_timer(s->second_timer2,
> -                       s->next_second_time + delay);
> -    }
> -}
> -
> -static void rtc_update_second2(void *opaque)
> -{
> -    RTCState *s = opaque;
> +    int64_t guest_nsec;
>  
> -    if (!(s->cmos_data[RTC_REG_B] & REG_B_SET)) {
> -        rtc_copy_date(s);
> +    if (s->cmos_data[RTC_REG_B] & REG_B_SET) {
> +        return 0;
>      }
> -
> -    /* check alarm */
> -    if (((s->cmos_data[RTC_SECONDS_ALARM] & 0xc0) == 0xc0 ||
> -         rtc_from_bcd(s, s->cmos_data[RTC_SECONDS_ALARM]) == s->current_tm.tm_sec) &&
> -        ((s->cmos_data[RTC_MINUTES_ALARM] & 0xc0) == 0xc0 ||
> -         rtc_from_bcd(s, s->cmos_data[RTC_MINUTES_ALARM]) == s->current_tm.tm_min) &&
> -        ((s->cmos_data[RTC_HOURS_ALARM] & 0xc0) == 0xc0 ||
> -         rtc_from_bcd(s, s->cmos_data[RTC_HOURS_ALARM]) == s->current_tm.tm_hour)) {
> -
> -        s->cmos_data[RTC_REG_C] |= REG_C_AF;
> -        if (s->cmos_data[RTC_REG_B] & REG_B_AIE) {
> -            qemu_system_wakeup_request(QEMU_WAKEUP_REASON_RTC);
> -            qemu_irq_raise(s->irq);
> -            s->cmos_data[RTC_REG_C] |= REG_C_IRQF;
> +    if (qemu_timer_pending(s->update_timer)) {
> +        int64_t next_update_time = qemu_timer_expire_time_ns(s->update_timer);
> +        /* Latch UIP until the timer expires.  */
> +        if (qemu_get_clock_ns(rtc_clock) >= (next_update_time - 244000)) {
> +            s->cmos_data[RTC_REG_A] |= REG_A_UIP;
> +            return 1;
>          }
>      }
>  
> -    /* update ended interrupt */
> -    s->cmos_data[RTC_REG_C] |= REG_C_UF;
> -    if (s->cmos_data[RTC_REG_B] & REG_B_UIE) {
> -        s->cmos_data[RTC_REG_C] |= REG_C_IRQF;
> -        qemu_irq_raise(s->irq);
> +    guest_nsec = get_guest_rtc_ns(s);
> +    /* UIP bit will be set at last 244us of every second. */
> +    if ((guest_nsec % NSEC_PER_SEC) >= (NSEC_PER_SEC - 244000)) {
> +        return 1;
>      }

244us isn't quite right, 8 / 32khz is closer to 245us but it makes more
sense to me to:

/* UIP gets held for 8 cycles */
#define RTC_CLOCK_RATE   (32768)
#define UIP_HOLD_LENGTH  ((8 * NSEC_PER_SEC) / RTC_CLOCK_RATE)

Regards,

Anthony Liguori
Juan Quintela Aug. 2, 2012, 9:09 a.m. UTC | #2
Paolo Bonzini <pbonzini@redhat.com> wrote:
> From: Yang Zhang <yang.z.zhang@intel.com>
>
> Calculate guest RTC based on the time of the last update, instead of
> using timers.  The formula is
>
>     (base_rtc + guest_time_now - guest_time_last_update + offset)
>
> Base_rtc is the RTC value when the RTC was last updated.
> Guest_time_now is the guest time when the access happens.
> Guest_time_last_update was the guest time when the RTC was last updated.
> Offset is used when divider reset happens or the set bit is toggled.
>
> The timer is kept in order to signal interrupts, but it only needs to
> run when either UF or AF is cleared.  When the bits are both set, the
> timer does not run.
>
> UIP is now synthesized when reading register A.  If the timer is not set,
> or if there is more than one second before it (as is the case at the
> end of this series), the leading edge of UIP is computed and the rising
> edge occurs 220us later.  If the update timer occurs within one second,
> however, the rising edge of the AF and UF bits should coincide withe
> the falling edge of UIP.  We do not know exactly when this will happen
> because there could be delays in the servicing of the timer.  Hence, in
> this case reading register A only computes for the rising edge of UIP,
> and latches the bit until the timer is fired and clears it.
>
> Signed-off-by: Yang Zhang <yang.z.zhang@intel.com>
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>

>  
>  static const VMStateDescription vmstate_rtc = {
>      .name = "mc146818rtc",
> -    .version_id = 2,
> -    .minimum_version_id = 1,
> -    .minimum_version_id_old = 1,
> +    .version_id = 3,
> +    .minimum_version_id = 3,
> +    .minimum_version_id_old = 3,
>      .post_load = rtc_post_load,
>      .fields      = (VMStateField []) {
>          VMSTATE_BUFFER(cmos_data, RTCState),
> @@ -542,11 +595,12 @@ static const VMStateDescription vmstate_rtc = {
>          VMSTATE_INT32(current_tm.tm_year, RTCState),
>          VMSTATE_TIMER(periodic_timer, RTCState),
>          VMSTATE_INT64(next_periodic_time, RTCState),
> -        VMSTATE_INT64(next_second_time, RTCState),
> -        VMSTATE_TIMER(second_timer, RTCState),
> -        VMSTATE_TIMER(second_timer2, RTCState),
>          VMSTATE_UINT32_V(irq_coalesced, RTCState, 2),
>          VMSTATE_UINT32_V(period, RTCState, 2),
> +        VMSTATE_UINT64_V(base_rtc, RTCState, 3),
> +        VMSTATE_UINT64_V(last_update, RTCState, 3),
> +        VMSTATE_INT64_V(offset, RTCState, 3),
> +        VMSTATE_TIMER_V(update_timer, RTCState, 3),
>          VMSTATE_END_OF_LIST()
>      }
>  };

Why did you remove all the migration from previous versions?
You can't migrate now from version{1,2}, and we used to be able to do
it?

Why did you remove it?

Later, Juan.
Paolo Bonzini Aug. 2, 2012, 9:14 a.m. UTC | #3
Il 02/08/2012 11:09, Juan Quintela ha scritto:
> Why did you remove all the migration from previous versions?
> You can't migrate now from version{1,2}, and we used to be able to do
> it?
> 
> Why did you remove it?

Because it won't work; we removed three fields.  You need to add
rtc_load_old which is done later in the series.  But I guess I can use
VMSTATE_UNUSED instead.

Paolo
Juan Quintela Aug. 2, 2012, 9:58 a.m. UTC | #4
Paolo Bonzini <pbonzini@redhat.com> wrote:
> Il 02/08/2012 11:09, Juan Quintela ha scritto:
>> Why did you remove all the migration from previous versions?
>> You can't migrate now from version{1,2}, and we used to be able to do
>> it?
>> 
>> Why did you remove it?
>
> Because it won't work; we removed three fields.  You need to add
> rtc_load_old which is done later in the series.  But I guess I can use
> VMSTATE_UNUSED instead.

Something like (completely untested):

static bool version_less_3(void *opaque, int version_id)
{
    return version_id < 3;
}

static const VMStateDescription vmstate_rtc = {
    .name = "mc146818rtc",
    .version_id = 3,
    .minimum_version_id = 1,
    .minimum_version_id_old = 1,
    .post_load = rtc_post_load,
    .fields      = (VMStateField []) {
         VMSTATE_BUFFER(cmos_data, RTCState),
 @@ -542,11 +595,12 @@ static const VMStateDescription vmstate_rtc = {
         VMSTATE_INT32(current_tm.tm_year, RTCState),
         VMSTATE_TIMER(periodic_timer, RTCState),
         VMSTATE_INT64(next_periodic_time, RTCState),
         VMSTATE_UNUSED_TEST(8*3, v_less_3); //* whatever space */
-        VMSTATE_INT64(next_second_time, RTCState),
-        VMSTATE_TIMER(second_timer, RTCState),
-        VMSTATE_TIMER(second_timer2, RTCState),
         VMSTATE_UINT32_V(irq_coalesced, RTCState, 2),
         VMSTATE_UINT32_V(period, RTCState, 2),
+        VMSTATE_UINT64_V(base_rtc, RTCState, 3),
+        VMSTATE_UINT64_V(last_update, RTCState, 3),
+        VMSTATE_INT64_V(offset, RTCState, 3),
+        VMSTATE_TIMER_V(update_timer, RTCState, 3),
         VMSTATE_END_OF_LIST()
     }
 };


This will make "migration protocol" work,  I have zero clue if
"obviating" the value of next_second_time, and the two second_timers*
can work, that depends on how rtc works.  Perhaps some extra magic on
post_load() is needed, though.

Could you tell me if you need anything else?

Later, Juan.
diff mbox

Patch

diff --git a/hw/mc146818rtc.c b/hw/mc146818rtc.c
index 293f174..2c34a82 100644
--- a/hw/mc146818rtc.c
+++ b/hw/mc146818rtc.c
@@ -45,6 +45,8 @@ 
 # define DPRINTF_C(format, ...)      do { } while (0)
 #endif
 
+#define NSEC_PER_SEC    1000000000LL
+
 #define RTC_REINJECT_ON_ACK_COUNT 20
 
 typedef struct RTCState {
@@ -54,27 +56,40 @@  typedef struct RTCState {
     uint8_t cmos_index;
     struct tm current_tm;
     int32_t base_year;
+    uint64_t base_rtc;
+    uint64_t last_update;
+    int64_t offset;
     qemu_irq irq;
     qemu_irq sqw_irq;
     int it_shift;
     /* periodic timer */
     QEMUTimer *periodic_timer;
     int64_t next_periodic_time;
-    /* second update */
-    int64_t next_second_time;
+    /* update-ended timer */
+    QEMUTimer *update_timer;
     uint16_t irq_reinject_on_ack_count;
     uint32_t irq_coalesced;
     uint32_t period;
     QEMUTimer *coalesced_timer;
-    QEMUTimer *second_timer;
-    QEMUTimer *second_timer2;
     Notifier clock_reset_notifier;
     LostTickPolicy lost_tick_policy;
     Notifier suspend_notifier;
 } RTCState;
 
 static void rtc_set_time(RTCState *s);
-static void rtc_copy_date(RTCState *s);
+static void rtc_update_time(RTCState *s);
+static void rtc_set_cmos(RTCState *s);
+static inline int rtc_from_bcd(RTCState *s, int a);
+
+static uint64_t get_guest_rtc_ns(RTCState *s)
+{
+    uint64_t guest_rtc;
+    uint64_t guest_clock = qemu_get_clock_ns(rtc_clock);
+
+    guest_rtc = s->base_rtc * NSEC_PER_SEC
+                 + guest_clock - s->last_update + s->offset;
+    return guest_rtc;
+}
 
 #ifdef TARGET_I386
 static void rtc_coalesced_timer_update(RTCState *s)
@@ -110,6 +125,7 @@  static void rtc_coalesced_timer(void *opaque)
 }
 #endif
 
+/* handle periodic timer */
 static void periodic_timer_update(RTCState *s, int64_t current_time)
 {
     int period_code, period;
@@ -175,6 +191,100 @@  static void rtc_periodic_timer(void *opaque)
     }
 }
 
+/* handle update-ended timer */
+static void check_update_timer(RTCState *s)
+{
+    uint64_t next_update_time;
+    uint64_t guest_nsec;
+
+    /* From the data sheet: setting the SET bit does not prevent
+     * interrupts from occurring!  However, it will prevent an
+     * alarm interrupt from occurring, because the time of day is
+     * not updated.
+     */
+    if ((s->cmos_data[RTC_REG_C] & REG_C_UF) &&
+        (s->cmos_data[RTC_REG_B] & REG_B_SET)) {
+        qemu_del_timer(s->update_timer);
+        return;
+    }
+    if ((s->cmos_data[RTC_REG_C] & REG_C_UF) &&
+        (s->cmos_data[RTC_REG_C] & REG_C_AF)) {
+        qemu_del_timer(s->update_timer);
+        return;
+    }
+
+    guest_nsec = get_guest_rtc_ns(s) % NSEC_PER_SEC;
+    /* reprogram to next second */
+    next_update_time = qemu_get_clock_ns(rtc_clock)
+        + NSEC_PER_SEC - guest_nsec;
+    if (next_update_time != qemu_timer_expire_time_ns(s->update_timer)) {
+        qemu_mod_timer(s->update_timer, next_update_time);
+    }
+}
+
+static inline uint8_t convert_hour(RTCState *s, uint8_t hour)
+{
+    if (!(s->cmos_data[RTC_REG_B] & REG_B_24H)) {
+        hour %= 12;
+        if (s->cmos_data[RTC_HOURS] & 0x80) {
+            hour += 12;
+        }
+    }
+    return hour;
+}
+
+static uint32_t check_alarm(RTCState *s)
+{
+    uint8_t alarm_hour, alarm_min, alarm_sec;
+    uint8_t cur_hour, cur_min, cur_sec;
+
+    alarm_sec = rtc_from_bcd(s, s->cmos_data[RTC_SECONDS_ALARM]);
+    alarm_min = rtc_from_bcd(s, s->cmos_data[RTC_MINUTES_ALARM]);
+    alarm_hour = rtc_from_bcd(s, s->cmos_data[RTC_HOURS_ALARM]);
+    alarm_hour = convert_hour(s, alarm_hour);
+
+    cur_sec = rtc_from_bcd(s, s->cmos_data[RTC_SECONDS]);
+    cur_min = rtc_from_bcd(s, s->cmos_data[RTC_MINUTES]);
+    cur_hour = rtc_from_bcd(s, s->cmos_data[RTC_HOURS]);
+    cur_hour = convert_hour(s, cur_hour);
+
+    if (((s->cmos_data[RTC_SECONDS_ALARM] & 0xc0) == 0xc0
+                || alarm_sec == cur_sec) &&
+            ((s->cmos_data[RTC_MINUTES_ALARM] & 0xc0) == 0xc0
+             || alarm_min == cur_min) &&
+            ((s->cmos_data[RTC_HOURS_ALARM] & 0xc0) == 0xc0
+             || alarm_hour == cur_hour)) {
+        return 1;
+    }
+    return 0;
+
+}
+
+static void rtc_update_timer(void *opaque)
+{
+    RTCState *s = opaque;
+    int32_t irqs = REG_C_UF;
+    int32_t new_irqs;
+
+    /* UIP might have been latched, update time and clear it.  */
+    rtc_update_time(s);
+    s->cmos_data[RTC_REG_A] &= ~REG_A_UIP;
+
+    if (check_alarm(s)) {
+        irqs |= REG_C_AF;
+        if (s->cmos_data[RTC_REG_B] & REG_B_AIE) {
+            qemu_system_wakeup_request(QEMU_WAKEUP_REASON_RTC);
+        }
+    }
+    new_irqs = irqs & ~s->cmos_data[RTC_REG_C];
+    s->cmos_data[RTC_REG_C] |= irqs;
+    if ((new_irqs & s->cmos_data[RTC_REG_B]) != 0) {
+        s->cmos_data[RTC_REG_C] |= REG_C_IRQF;
+        qemu_irq_raise(s->irq);
+    }
+    check_update_timer(s);
+}
+
 static void cmos_ioport_write(void *opaque, uint32_t addr, uint32_t data)
 {
     RTCState *s = opaque;
@@ -189,6 +299,7 @@  static void cmos_ioport_write(void *opaque, uint32_t addr, uint32_t data)
         case RTC_MINUTES_ALARM:
         case RTC_HOURS_ALARM:
             s->cmos_data[s->cmos_index] = data;
+            check_update_timer(s);
             break;
         case RTC_SECONDS:
         case RTC_MINUTES:
@@ -201,6 +312,7 @@  static void cmos_ioport_write(void *opaque, uint32_t addr, uint32_t data)
             /* if in set mode, do not update the time */
             if (!(s->cmos_data[RTC_REG_B] & REG_B_SET)) {
                 rtc_set_time(s);
+                check_update_timer(s);
             }
             break;
         case RTC_REG_A:
@@ -208,15 +320,21 @@  static void cmos_ioport_write(void *opaque, uint32_t addr, uint32_t data)
             s->cmos_data[RTC_REG_A] = (data & ~REG_A_UIP) |
                 (s->cmos_data[RTC_REG_A] & REG_A_UIP);
             periodic_timer_update(s, qemu_get_clock_ns(rtc_clock));
+            check_update_timer(s);
             break;
         case RTC_REG_B:
             if (data & REG_B_SET) {
+                /* update cmos to when the rtc was stopping */
+                if (!(s->cmos_data[RTC_REG_B] & REG_B_SET)) {
+                    rtc_update_time(s);
+                }
                 /* set mode: reset UIP mode */
                 s->cmos_data[RTC_REG_A] &= ~REG_A_UIP;
                 data &= ~REG_B_UIE;
             } else {
                 /* if disabling set mode, update the time */
                 if (s->cmos_data[RTC_REG_B] & REG_B_SET) {
+                    s->offset = get_guest_rtc_ns(s) % NSEC_PER_SEC;
                     rtc_set_time(s);
                 }
             }
@@ -231,6 +349,7 @@  static void cmos_ioport_write(void *opaque, uint32_t addr, uint32_t data)
             }
             s->cmos_data[RTC_REG_B] = data;
             periodic_timer_update(s, qemu_get_clock_ns(rtc_clock));
+            check_update_timer(s);
             break;
         case RTC_REG_C:
         case RTC_REG_D:
@@ -279,10 +398,13 @@  static void rtc_set_time(RTCState *s)
     tm->tm_mon = rtc_from_bcd(s, s->cmos_data[RTC_MONTH]) - 1;
     tm->tm_year = rtc_from_bcd(s, s->cmos_data[RTC_YEAR]) + s->base_year - 1900;
 
+    s->base_rtc = mktimegm(tm);
+    s->last_update = qemu_get_clock_ns(rtc_clock);
+
     rtc_change_mon_event(tm);
 }
 
-static void rtc_copy_date(RTCState *s)
+static void rtc_set_cmos(RTCState *s)
 {
     const struct tm *tm = &s->current_tm;
     int year;
@@ -308,122 +430,41 @@  static void rtc_copy_date(RTCState *s)
     s->cmos_data[RTC_YEAR] = rtc_to_bcd(s, year);
 }
 
-/* month is between 0 and 11. */
-static int get_days_in_month(int month, int year)
+static void rtc_update_time(RTCState *s)
 {
-    static const int days_tab[12] = {
-        31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
-    };
-    int d;
-    if ((unsigned )month >= 12)
-        return 31;
-    d = days_tab[month];
-    if (month == 1) {
-        if ((year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0))
-            d++;
-    }
-    return d;
+    struct tm *ret;
+    time_t guest_sec;
+    int64_t guest_nsec;
+
+    guest_nsec = get_guest_rtc_ns(s);
+    guest_sec = guest_nsec / NSEC_PER_SEC;
+    ret = gmtime(&guest_sec);
+    s->current_tm = *ret;
+    rtc_set_cmos(s);
 }
 
-/* update 'tm' to the next second */
-static void rtc_next_second(struct tm *tm)
+static int update_in_progress(RTCState *s)
 {
-    int days_in_month;
-
-    tm->tm_sec++;
-    if ((unsigned)tm->tm_sec >= 60) {
-        tm->tm_sec = 0;
-        tm->tm_min++;
-        if ((unsigned)tm->tm_min >= 60) {
-            tm->tm_min = 0;
-            tm->tm_hour++;
-            if ((unsigned)tm->tm_hour >= 24) {
-                tm->tm_hour = 0;
-                /* next day */
-                tm->tm_wday++;
-                if ((unsigned)tm->tm_wday >= 7)
-                    tm->tm_wday = 0;
-                days_in_month = get_days_in_month(tm->tm_mon,
-                                                  tm->tm_year + 1900);
-                tm->tm_mday++;
-                if (tm->tm_mday < 1) {
-                    tm->tm_mday = 1;
-                } else if (tm->tm_mday > days_in_month) {
-                    tm->tm_mday = 1;
-                    tm->tm_mon++;
-                    if (tm->tm_mon >= 12) {
-                        tm->tm_mon = 0;
-                        tm->tm_year++;
-                    }
-                }
-            }
-        }
-    }
-}
-
-
-static void rtc_update_second(void *opaque)
-{
-    RTCState *s = opaque;
-    int64_t delay;
-
-    /* if the oscillator is not in normal operation, we do not update */
-    if ((s->cmos_data[RTC_REG_A] & 0x70) != 0x20) {
-        s->next_second_time += get_ticks_per_sec();
-        qemu_mod_timer(s->second_timer, s->next_second_time);
-    } else {
-        rtc_next_second(&s->current_tm);
-
-        if (!(s->cmos_data[RTC_REG_B] & REG_B_SET)) {
-            /* update in progress bit */
-            s->cmos_data[RTC_REG_A] |= REG_A_UIP;
-        }
-        /* should be 244 us = 8 / 32768 seconds, but currently the
-           timers do not have the necessary resolution. */
-        delay = (get_ticks_per_sec() * 1) / 100;
-        if (delay < 1)
-            delay = 1;
-        qemu_mod_timer(s->second_timer2,
-                       s->next_second_time + delay);
-    }
-}
-
-static void rtc_update_second2(void *opaque)
-{
-    RTCState *s = opaque;
+    int64_t guest_nsec;
 
-    if (!(s->cmos_data[RTC_REG_B] & REG_B_SET)) {
-        rtc_copy_date(s);
+    if (s->cmos_data[RTC_REG_B] & REG_B_SET) {
+        return 0;
     }
-
-    /* check alarm */
-    if (((s->cmos_data[RTC_SECONDS_ALARM] & 0xc0) == 0xc0 ||
-         rtc_from_bcd(s, s->cmos_data[RTC_SECONDS_ALARM]) == s->current_tm.tm_sec) &&
-        ((s->cmos_data[RTC_MINUTES_ALARM] & 0xc0) == 0xc0 ||
-         rtc_from_bcd(s, s->cmos_data[RTC_MINUTES_ALARM]) == s->current_tm.tm_min) &&
-        ((s->cmos_data[RTC_HOURS_ALARM] & 0xc0) == 0xc0 ||
-         rtc_from_bcd(s, s->cmos_data[RTC_HOURS_ALARM]) == s->current_tm.tm_hour)) {
-
-        s->cmos_data[RTC_REG_C] |= REG_C_AF;
-        if (s->cmos_data[RTC_REG_B] & REG_B_AIE) {
-            qemu_system_wakeup_request(QEMU_WAKEUP_REASON_RTC);
-            qemu_irq_raise(s->irq);
-            s->cmos_data[RTC_REG_C] |= REG_C_IRQF;
+    if (qemu_timer_pending(s->update_timer)) {
+        int64_t next_update_time = qemu_timer_expire_time_ns(s->update_timer);
+        /* Latch UIP until the timer expires.  */
+        if (qemu_get_clock_ns(rtc_clock) >= (next_update_time - 244000)) {
+            s->cmos_data[RTC_REG_A] |= REG_A_UIP;
+            return 1;
         }
     }
 
-    /* update ended interrupt */
-    s->cmos_data[RTC_REG_C] |= REG_C_UF;
-    if (s->cmos_data[RTC_REG_B] & REG_B_UIE) {
-        s->cmos_data[RTC_REG_C] |= REG_C_IRQF;
-        qemu_irq_raise(s->irq);
+    guest_nsec = get_guest_rtc_ns(s);
+    /* UIP bit will be set at last 244us of every second. */
+    if ((guest_nsec % NSEC_PER_SEC) >= (NSEC_PER_SEC - 244000)) {
+        return 1;
     }
-
-    /* clear update in progress bit */
-    s->cmos_data[RTC_REG_A] &= ~REG_A_UIP;
-
-    s->next_second_time += get_ticks_per_sec();
-    qemu_mod_timer(s->second_timer, s->next_second_time);
+    return 0;
 }
 
 static uint32_t cmos_ioport_read(void *opaque, uint32_t addr)
@@ -441,15 +482,28 @@  static uint32_t cmos_ioport_read(void *opaque, uint32_t addr)
         case RTC_DAY_OF_MONTH:
         case RTC_MONTH:
         case RTC_YEAR:
+            /* if not in set mode, calibrate cmos before
+             * reading*/
+            if (!(s->cmos_data[RTC_REG_B] & REG_B_SET)) {
+                rtc_update_time(s);
+            }
             ret = s->cmos_data[s->cmos_index];
             break;
         case RTC_REG_A:
+            if (update_in_progress(s)) {
+                s->cmos_data[s->cmos_index] |= REG_A_UIP;
+            } else {
+                s->cmos_data[s->cmos_index] &= ~REG_A_UIP;
+            }
             ret = s->cmos_data[s->cmos_index];
             break;
         case RTC_REG_C:
             ret = s->cmos_data[s->cmos_index];
             qemu_irq_lower(s->irq);
             s->cmos_data[RTC_REG_C] = 0x00;
+            if (ret & (REG_C_UF | REG_C_AF)) {
+                check_update_timer(s);
+            }
 #ifdef TARGET_I386
             if(s->irq_coalesced &&
                     (s->cmos_data[RTC_REG_B] & REG_B_PIE) &&
@@ -484,13 +538,6 @@  void rtc_set_memory(ISADevice *dev, int addr, int val)
         s->cmos_data[addr] = val;
 }
 
-void rtc_set_date(ISADevice *dev, const struct tm *tm)
-{
-    RTCState *s = DO_UPCAST(RTCState, dev, dev);
-    s->current_tm = *tm;
-    rtc_copy_date(s);
-}
-
 /* PC cmos mappings */
 #define REG_IBM_CENTURY_BYTE        0x32
 #define REG_IBM_PS2_CENTURY_BYTE    0x37
@@ -501,9 +548,15 @@  static void rtc_set_date_from_host(ISADevice *dev)
     struct tm tm;
     int val;
 
-    /* set the CMOS date */
     qemu_get_timedate(&tm, 0);
-    rtc_set_date(dev, &tm);
+
+    s->base_rtc = mktimegm(&tm);
+    s->last_update = qemu_get_clock_ns(rtc_clock);
+    s->offset = 0;
+
+    /* set the CMOS date */
+    s->current_tm = tm;
+    rtc_set_cmos(s);
 
     val = rtc_to_bcd(s, (tm.tm_year / 100) + 19);
     rtc_set_memory(dev, REG_IBM_CENTURY_BYTE, val);
@@ -526,9 +579,9 @@  static int rtc_post_load(void *opaque, int version_id)
 
 static const VMStateDescription vmstate_rtc = {
     .name = "mc146818rtc",
-    .version_id = 2,
-    .minimum_version_id = 1,
-    .minimum_version_id_old = 1,
+    .version_id = 3,
+    .minimum_version_id = 3,
+    .minimum_version_id_old = 3,
     .post_load = rtc_post_load,
     .fields      = (VMStateField []) {
         VMSTATE_BUFFER(cmos_data, RTCState),
@@ -542,11 +595,12 @@  static const VMStateDescription vmstate_rtc = {
         VMSTATE_INT32(current_tm.tm_year, RTCState),
         VMSTATE_TIMER(periodic_timer, RTCState),
         VMSTATE_INT64(next_periodic_time, RTCState),
-        VMSTATE_INT64(next_second_time, RTCState),
-        VMSTATE_TIMER(second_timer, RTCState),
-        VMSTATE_TIMER(second_timer2, RTCState),
         VMSTATE_UINT32_V(irq_coalesced, RTCState, 2),
         VMSTATE_UINT32_V(period, RTCState, 2),
+        VMSTATE_UINT64_V(base_rtc, RTCState, 3),
+        VMSTATE_UINT64_V(last_update, RTCState, 3),
+        VMSTATE_INT64_V(offset, RTCState, 3),
+        VMSTATE_TIMER_V(update_timer, RTCState, 3),
         VMSTATE_END_OF_LIST()
     }
 };
@@ -557,9 +611,8 @@  static void rtc_notify_clock_reset(Notifier *notifier, void *data)
     int64_t now = *(int64_t *)data;
 
     rtc_set_date_from_host(&s->dev);
-    s->next_second_time = now + (get_ticks_per_sec() * 99) / 100;
-    qemu_mod_timer(s->second_timer2, s->next_second_time);
     periodic_timer_update(s, now);
+    check_update_timer(s);
 #ifdef TARGET_I386
     if (s->lost_tick_policy == LOST_TICK_SLEW) {
         rtc_coalesced_timer_update(s);
@@ -581,6 +634,7 @@  static void rtc_reset(void *opaque)
 
     s->cmos_data[RTC_REG_B] &= ~(REG_B_PIE | REG_B_AIE | REG_B_SQWE);
     s->cmos_data[RTC_REG_C] &= ~(REG_C_UF | REG_C_IRQF | REG_C_PF | REG_C_AF);
+    check_update_timer(s);
 
     qemu_irq_lower(s->irq);
 
@@ -606,6 +660,7 @@  static void rtc_get_date(Object *obj, Visitor *v, void *opaque,
     ISADevice *isa = ISA_DEVICE(obj);
     RTCState *s = DO_UPCAST(RTCState, dev, isa);
 
+    rtc_update_time(s);
     visit_start_struct(v, NULL, "struct tm", name, 0, errp);
     visit_type_int32(v, &s->current_tm.tm_year, "tm_year", errp);
     visit_type_int32(v, &s->current_tm.tm_mon, "tm_mon", errp);
@@ -642,8 +697,8 @@  static int rtc_initfn(ISADevice *dev)
 #endif
 
     s->periodic_timer = qemu_new_timer_ns(rtc_clock, rtc_periodic_timer, s);
-    s->second_timer = qemu_new_timer_ns(rtc_clock, rtc_update_second, s);
-    s->second_timer2 = qemu_new_timer_ns(rtc_clock, rtc_update_second2, s);
+    s->update_timer = qemu_new_timer_ns(rtc_clock, rtc_update_timer, s);
+    check_update_timer(s);
 
     s->clock_reset_notifier.notify = rtc_notify_clock_reset;
     qemu_register_clock_reset_notifier(rtc_clock, &s->clock_reset_notifier);
@@ -651,10 +706,6 @@  static int rtc_initfn(ISADevice *dev)
     s->suspend_notifier.notify = rtc_notify_suspend;
     qemu_register_suspend_notifier(&s->suspend_notifier);
 
-    s->next_second_time =
-        qemu_get_clock_ns(rtc_clock) + (get_ticks_per_sec() * 99) / 100;
-    qemu_mod_timer(s->second_timer2, s->next_second_time);
-
     memory_region_init_io(&s->io, &cmos_ops, s, "rtc", 2);
     isa_register_ioport(dev, &s->io, base);