Provide notification and reading of RTC change by HVM guest If the HVM guest changes the current date/time in the RTC, signal this to the control plan via the DOM_EXC virq. Allow the control plane to obtain the new time via a a new call to read the time offset for a specific domain. Close initial condition window. If the offset is set before the domain starts, it may not be used until after the first second update. This leaves a small hole in which the wrong values could be read. Signed-off-by: Ben Thomas (ben@virtualiron.com) diff -r 9646036c745d tools/libxc/xc_domain.c --- a/tools/libxc/xc_domain.c Mon Mar 19 15:00:30 2007 -0400 +++ b/tools/libxc/xc_domain.c Tue Mar 20 10:55:51 2007 -0400 @@ -180,6 +180,7 @@ int xc_domain_getinfo(int xc_handle, info->blocked = !!(domctl.u.getdomaininfo.flags&XEN_DOMINF_blocked); info->running = !!(domctl.u.getdomaininfo.flags&XEN_DOMINF_running); info->hvm = !!(domctl.u.getdomaininfo.flags&XEN_DOMINF_hvm_guest); + info->time_chg = !!(domctl.u.getdomaininfo.flags&XEN_DOMINF_time_chg); info->shutdown_reason = (domctl.u.getdomaininfo.flags>>XEN_DOMINF_shutdownshift) & @@ -425,6 +426,26 @@ int xc_domain_set_time_offset(int xc_han domctl.domain = (domid_t)domid; domctl.u.settimeoffset.time_offset_seconds = time_offset_seconds; return do_domctl(xc_handle, &domctl); +} + +int xc_domain_get_time_offset(int xc_handle, + uint32_t domid, + int32_t *time_offset_seconds) +{ + int rc; + DECLARE_DOMCTL; + + if (time_offset_seconds == NULL) + return -EINVAL; + + domctl.cmd = XEN_DOMCTL_gettimeoffset; + domctl.domain = (domid_t)domid; + + rc = do_domctl(xc_handle, &domctl); + + *time_offset_seconds = domctl.u.gettimeoffset.time_offset_seconds; + + return rc; } int xc_domain_memory_increase_reservation(int xc_handle, diff -r 9646036c745d tools/libxc/xenctrl.h --- a/tools/libxc/xenctrl.h Mon Mar 19 15:00:30 2007 -0400 +++ b/tools/libxc/xenctrl.h Mon Mar 19 15:01:11 2007 -0400 @@ -150,7 +150,7 @@ typedef struct xc_dominfo { uint32_t ssidref; unsigned int dying:1, crashed:1, shutdown:1, paused:1, blocked:1, running:1, - hvm:1; + hvm:1, time_chg:1; unsigned int shutdown_reason; /* only meaningful if shutdown==1 */ unsigned long nr_pages; unsigned long shared_info_frame; @@ -487,6 +487,10 @@ int xc_domain_set_time_offset(int xc_han int xc_domain_set_time_offset(int xc_handle, uint32_t domid, int32_t time_offset_seconds); + +int xc_domain_get_time_offset(int xc_handle, + uint32_t domid, + int32_t *time_offset_seconds); int xc_domain_memory_increase_reservation(int xc_handle, uint32_t domid, diff -r 9646036c745d tools/xenstore/xenstored_domain.c --- a/tools/xenstore/xenstored_domain.c Mon Mar 19 15:00:30 2007 -0400 +++ b/tools/xenstore/xenstored_domain.c Mon Mar 19 15:10:42 2007 -0400 @@ -195,6 +195,8 @@ static void domain_cleanup(void) domain->shutdown = 1; notify = 1; } + if (dominfo.time_chg) + notify = 1; if (!dominfo.dying) continue; } diff -r 9646036c745d xen/arch/x86/hvm/rtc.c --- a/xen/arch/x86/hvm/rtc.c Mon Mar 19 15:00:30 2007 -0400 +++ b/xen/arch/x86/hvm/rtc.c Tue Mar 20 13:16:20 2007 -0400 @@ -27,8 +27,24 @@ #include #include #include +#include /* #define DEBUG_RTC */ + +/* Nonzero if YEAR is a leap year */ +#define __isleap(year) \ + ((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0)) + +/* How many days are in each month. */ +static const unsigned short int __mon_lengths[2][12] = { + /* Normal years. */ + {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, + /* Leap years. */ + {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} +}; + +static void rtc_copy_date(RTCState *s); +static unsigned long RTC_mktime(struct tm *tbuf); void rtc_periodic_cb(struct vcpu *v, void *opaque) { @@ -73,6 +89,13 @@ static int rtc_ioport_write(void *opaque static int rtc_ioport_write(void *opaque, uint32_t addr, uint32_t data) { RTCState *s = opaque; + + /* Before the first write, make sure we're completely initialized */ + if ( unlikely(!s->accessed) ) + { + rtc_copy_date(s); + s->accessed = 1; + } if ( (addr & 1) == 0 ) { @@ -157,7 +180,10 @@ static void rtc_set_time(RTCState *s) static void rtc_set_time(RTCState *s) { struct tm *tm = &s->current_tm; - + unsigned long new_time; + unsigned long current_time; + int32_t new_offset; + tm->tm_sec = from_bcd(s, s->hw.cmos_data[RTC_SECONDS]); tm->tm_min = from_bcd(s, s->hw.cmos_data[RTC_MINUTES]); tm->tm_hour = from_bcd(s, s->hw.cmos_data[RTC_HOURS] & 0x7f); @@ -168,6 +194,18 @@ static void rtc_set_time(RTCState *s) tm->tm_mday = from_bcd(s, s->hw.cmos_data[RTC_DAY_OF_MONTH]); tm->tm_mon = from_bcd(s, s->hw.cmos_data[RTC_MONTH]) - 1; tm->tm_year = from_bcd(s, s->hw.cmos_data[RTC_YEAR]) + 100; + + current_time = get_localtime(s->pt.vcpu->domain) - + s->pt.vcpu->domain->time_offset_seconds; + new_time = RTC_mktime(tm); + new_offset = new_time - current_time; + if (new_offset != s->time_offset_seconds) { + s->time_offset_seconds = new_offset; + s->pt.vcpu->domain->time_offset_seconds = new_offset; + set_bit(_DOMF_time_chg, &s->pt.vcpu->domain->domain_flags); + send_guest_global_virq(dom0, VIRQ_DOM_EXC); + } + } static void rtc_copy_date(RTCState *s) @@ -328,6 +366,13 @@ static uint32_t rtc_ioport_read(void *op if ( (addr & 1) == 0 ) return 0xff; + /* Before the first read, make sure we're completely initialized */ + if ( unlikely(!s->accessed) ) + { + rtc_copy_date(s); + s->accessed = 1; + } + switch ( s->hw.cmos_index ) { case RTC_SECONDS: @@ -434,7 +479,6 @@ void rtc_init(struct vcpu *v, int base) void rtc_init(struct vcpu *v, int base) { RTCState *s = &v->domain->arch.hvm_domain.pl_time.vrtc; - s->pt.vcpu = v; s->hw.cmos_data[RTC_REG_A] = RTC_REF_CLCK_32KHZ | 6; /* ~1kHz */ s->hw.cmos_data[RTC_REG_B] = RTC_24H; @@ -462,3 +506,36 @@ void rtc_deinit(struct domain *d) kill_timer(&s->second_timer); kill_timer(&s->second_timer2); } + +/* This routine returns value like mktime, but is not anywhere + near as rigorous. It assumes reasonable input values from the + RTC structures. Some structures borrowed from gmtime in time.c */ + +static unsigned long RTC_mktime(struct tm *tbuf) +{ + int i; + unsigned long result; + + if (tbuf == NULL) + return -1; + + result = 0; + for (i=70; i < tbuf->tm_year; i++) + result += (__isleap(i+1900) ? 366 :365); + + for (i = 0; i < tbuf->tm_mon; i++) + result += __mon_lengths[__isleap(tbuf->tm_year+1900)][i]; + + result += tbuf->tm_mday - 1; + + result *= 24; + result += tbuf->tm_hour; + + result *= 60; + result += tbuf->tm_min; + + result *= 60; + result += tbuf->tm_sec; + + return result; +} diff -r 9646036c745d xen/common/domctl.c --- a/xen/common/domctl.c Mon Mar 19 15:00:30 2007 -0400 +++ b/xen/common/domctl.c Tue Mar 20 10:56:19 2007 -0400 @@ -117,6 +117,7 @@ void getdomaininfo(struct domain *d, str ((d->domain_flags & DOMF_dying) ? XEN_DOMINF_dying : 0) | ((d->domain_flags & DOMF_shutdown) ? XEN_DOMINF_shutdown : 0) | ((d->domain_flags & DOMF_ctrl_pause) ? XEN_DOMINF_paused : 0) | + ((d->domain_flags & DOMF_time_chg) ? XEN_DOMINF_time_chg : 0) | d->shutdown_code << XEN_DOMINF_shutdownshift; if ( is_hvm_domain(d) ) @@ -711,6 +712,25 @@ long do_domctl(XEN_GUEST_HANDLE(xen_domc } break; + case XEN_DOMCTL_gettimeoffset: + { + struct domain *d; + + ret = -ESRCH; + d = rcu_lock_domain_by_id(op->domain); + if ( d != NULL ) + { + op->u.gettimeoffset.time_offset_seconds = d->time_offset_seconds; + ret = 0; + if ( copy_to_guest(u_domctl, op, 1) ) + ret = -EFAULT; + + clear_bit(_DOMF_time_chg, &d->domain_flags); + rcu_unlock_domain(d); + } + } + break; + default: ret = arch_do_domctl(op, u_domctl); break; diff -r 9646036c745d xen/include/asm-x86/hvm/vpt.h --- a/xen/include/asm-x86/hvm/vpt.h Mon Mar 19 15:00:30 2007 -0400 +++ b/xen/include/asm-x86/hvm/vpt.h Tue Mar 20 11:18:22 2007 -0400 @@ -93,6 +93,7 @@ typedef struct RTCState { struct timer second_timer2; struct periodic_time pt; int32_t time_offset_seconds; + unsigned int accessed:1; } RTCState; #define FREQUENCE_PMTIMER 3579545 /* Timer should run at 3.579545 MHz */ diff -r 9646036c745d xen/include/public/domctl.h --- a/xen/include/public/domctl.h Mon Mar 19 15:00:30 2007 -0400 +++ b/xen/include/public/domctl.h Mon Mar 19 15:01:11 2007 -0400 @@ -85,6 +85,10 @@ struct xen_domctl_getdomaininfo { /* Domain is currently running. */ #define _XEN_DOMINF_running 5 #define XEN_DOMINF_running (1U<<_XEN_DOMINF_running) +/* Domain has a time offset change */ +#define _XEN_DOMINF_time_chg 6 +#define XEN_DOMINF_time_chg (1U<<_XEN_DOMINF_time_chg) + /* CPU to which this domain is bound. */ #define XEN_DOMINF_cpumask 255 #define XEN_DOMINF_cpushift 8 @@ -388,6 +392,13 @@ struct xen_domctl_settimeoffset { }; typedef struct xen_domctl_settimeoffset xen_domctl_settimeoffset_t; DEFINE_XEN_GUEST_HANDLE(xen_domctl_settimeoffset_t); + +#define XEN_DOMCTL_gettimeoffset 37 +struct xen_domctl_gettimeoffset { + int32_t time_offset_seconds; /* applied to domain wallclock time */ +}; +typedef struct xen_domctl_gettimeoffset xen_domctl_gettimeoffset_t; +DEFINE_XEN_GUEST_HANDLE(xen_domctl_gettimeoffset_t); #define XEN_DOMCTL_gethvmcontext 33 @@ -453,6 +464,7 @@ struct xen_domctl { struct xen_domctl_hypercall_init hypercall_init; struct xen_domctl_arch_setup arch_setup; struct xen_domctl_settimeoffset settimeoffset; + struct xen_domctl_gettimeoffset gettimeoffset; struct xen_domctl_real_mode_area real_mode_area; struct xen_domctl_hvmcontext hvmcontext; struct xen_domctl_address_size address_size; diff -r 9646036c745d xen/include/xen/sched.h --- a/xen/include/xen/sched.h Mon Mar 19 15:00:30 2007 -0400 +++ b/xen/include/xen/sched.h Mon Mar 19 15:01:11 2007 -0400 @@ -474,6 +474,9 @@ extern struct domain *domain_list; /* Domain is a compatibility one? */ #define _DOMF_compat 6 #define DOMF_compat (1UL<<_DOMF_compat) + /* Domain time offset has changed */ +#define _DOMF_time_chg 7 +#define DOMF_time_chg (1UL<<_DOMF_time_chg) static inline int vcpu_runnable(struct vcpu *v) {