// SPDX-License-Identifier: GPL-2.0 
 | 
/* 
 | 
 * Xen time implementation. 
 | 
 * 
 | 
 * This is implemented in terms of a clocksource driver which uses 
 | 
 * the hypervisor clock as a nanosecond timebase, and a clockevent 
 | 
 * driver which uses the hypervisor's timer mechanism. 
 | 
 * 
 | 
 * Jeremy Fitzhardinge <jeremy@xensource.com>, XenSource Inc, 2007 
 | 
 */ 
 | 
#include <linux/kernel.h> 
 | 
#include <linux/interrupt.h> 
 | 
#include <linux/clocksource.h> 
 | 
#include <linux/clockchips.h> 
 | 
#include <linux/gfp.h> 
 | 
#include <linux/slab.h> 
 | 
#include <linux/pvclock_gtod.h> 
 | 
#include <linux/timekeeper_internal.h> 
 | 
  
 | 
#include <asm/pvclock.h> 
 | 
#include <asm/xen/hypervisor.h> 
 | 
#include <asm/xen/hypercall.h> 
 | 
  
 | 
#include <xen/events.h> 
 | 
#include <xen/features.h> 
 | 
#include <xen/interface/xen.h> 
 | 
#include <xen/interface/vcpu.h> 
 | 
  
 | 
#include "xen-ops.h" 
 | 
  
 | 
/* Xen may fire a timer up to this many ns early */ 
 | 
#define TIMER_SLOP    100000 
 | 
  
 | 
static u64 xen_sched_clock_offset __read_mostly; 
 | 
  
 | 
/* Get the TSC speed from Xen */ 
 | 
static unsigned long xen_tsc_khz(void) 
 | 
{ 
 | 
    struct pvclock_vcpu_time_info *info = 
 | 
        &HYPERVISOR_shared_info->vcpu_info[0].time; 
 | 
  
 | 
    return pvclock_tsc_khz(info); 
 | 
} 
 | 
  
 | 
static u64 xen_clocksource_read(void) 
 | 
{ 
 | 
        struct pvclock_vcpu_time_info *src; 
 | 
    u64 ret; 
 | 
  
 | 
    preempt_disable_notrace(); 
 | 
    src = &__this_cpu_read(xen_vcpu)->time; 
 | 
    ret = pvclock_clocksource_read(src); 
 | 
    preempt_enable_notrace(); 
 | 
    return ret; 
 | 
} 
 | 
  
 | 
static u64 xen_clocksource_get_cycles(struct clocksource *cs) 
 | 
{ 
 | 
    return xen_clocksource_read(); 
 | 
} 
 | 
  
 | 
static u64 xen_sched_clock(void) 
 | 
{ 
 | 
    return xen_clocksource_read() - xen_sched_clock_offset; 
 | 
} 
 | 
  
 | 
static void xen_read_wallclock(struct timespec64 *ts) 
 | 
{ 
 | 
    struct shared_info *s = HYPERVISOR_shared_info; 
 | 
    struct pvclock_wall_clock *wall_clock = &(s->wc); 
 | 
        struct pvclock_vcpu_time_info *vcpu_time; 
 | 
  
 | 
    vcpu_time = &get_cpu_var(xen_vcpu)->time; 
 | 
    pvclock_read_wallclock(wall_clock, vcpu_time, ts); 
 | 
    put_cpu_var(xen_vcpu); 
 | 
} 
 | 
  
 | 
static void xen_get_wallclock(struct timespec64 *now) 
 | 
{ 
 | 
    xen_read_wallclock(now); 
 | 
} 
 | 
  
 | 
static int xen_set_wallclock(const struct timespec64 *now) 
 | 
{ 
 | 
    return -ENODEV; 
 | 
} 
 | 
  
 | 
static int xen_pvclock_gtod_notify(struct notifier_block *nb, 
 | 
                   unsigned long was_set, void *priv) 
 | 
{ 
 | 
    /* Protected by the calling core code serialization */ 
 | 
    static struct timespec64 next_sync; 
 | 
  
 | 
    struct xen_platform_op op; 
 | 
    struct timespec64 now; 
 | 
    struct timekeeper *tk = priv; 
 | 
    static bool settime64_supported = true; 
 | 
    int ret; 
 | 
  
 | 
    now.tv_sec = tk->xtime_sec; 
 | 
    now.tv_nsec = (long)(tk->tkr_mono.xtime_nsec >> tk->tkr_mono.shift); 
 | 
  
 | 
    /* 
 | 
     * We only take the expensive HV call when the clock was set 
 | 
     * or when the 11 minutes RTC synchronization time elapsed. 
 | 
     */ 
 | 
    if (!was_set && timespec64_compare(&now, &next_sync) < 0) 
 | 
        return NOTIFY_OK; 
 | 
  
 | 
again: 
 | 
    if (settime64_supported) { 
 | 
        op.cmd = XENPF_settime64; 
 | 
        op.u.settime64.mbz = 0; 
 | 
        op.u.settime64.secs = now.tv_sec; 
 | 
        op.u.settime64.nsecs = now.tv_nsec; 
 | 
        op.u.settime64.system_time = xen_clocksource_read(); 
 | 
    } else { 
 | 
        op.cmd = XENPF_settime32; 
 | 
        op.u.settime32.secs = now.tv_sec; 
 | 
        op.u.settime32.nsecs = now.tv_nsec; 
 | 
        op.u.settime32.system_time = xen_clocksource_read(); 
 | 
    } 
 | 
  
 | 
    ret = HYPERVISOR_platform_op(&op); 
 | 
  
 | 
    if (ret == -ENOSYS && settime64_supported) { 
 | 
        settime64_supported = false; 
 | 
        goto again; 
 | 
    } 
 | 
    if (ret < 0) 
 | 
        return NOTIFY_BAD; 
 | 
  
 | 
    /* 
 | 
     * Move the next drift compensation time 11 minutes 
 | 
     * ahead. That's emulating the sync_cmos_clock() update for 
 | 
     * the hardware RTC. 
 | 
     */ 
 | 
    next_sync = now; 
 | 
    next_sync.tv_sec += 11 * 60; 
 | 
  
 | 
    return NOTIFY_OK; 
 | 
} 
 | 
  
 | 
static struct notifier_block xen_pvclock_gtod_notifier = { 
 | 
    .notifier_call = xen_pvclock_gtod_notify, 
 | 
}; 
 | 
  
 | 
static struct clocksource xen_clocksource __read_mostly = { 
 | 
    .name = "xen", 
 | 
    .rating = 400, 
 | 
    .read = xen_clocksource_get_cycles, 
 | 
    .mask = ~0, 
 | 
    .flags = CLOCK_SOURCE_IS_CONTINUOUS, 
 | 
}; 
 | 
  
 | 
/* 
 | 
   Xen clockevent implementation 
 | 
  
 | 
   Xen has two clockevent implementations: 
 | 
  
 | 
   The old timer_op one works with all released versions of Xen prior 
 | 
   to version 3.0.4.  This version of the hypervisor provides a 
 | 
   single-shot timer with nanosecond resolution.  However, sharing the 
 | 
   same event channel is a 100Hz tick which is delivered while the 
 | 
   vcpu is running.  We don't care about or use this tick, but it will 
 | 
   cause the core time code to think the timer fired too soon, and 
 | 
   will end up resetting it each time.  It could be filtered, but 
 | 
   doing so has complications when the ktime clocksource is not yet 
 | 
   the xen clocksource (ie, at boot time). 
 | 
  
 | 
   The new vcpu_op-based timer interface allows the tick timer period 
 | 
   to be changed or turned off.  The tick timer is not useful as a 
 | 
   periodic timer because events are only delivered to running vcpus. 
 | 
   The one-shot timer can report when a timeout is in the past, so 
 | 
   set_next_event is capable of returning -ETIME when appropriate. 
 | 
   This interface is used when available. 
 | 
*/ 
 | 
  
 | 
  
 | 
/* 
 | 
  Get a hypervisor absolute time.  In theory we could maintain an 
 | 
  offset between the kernel's time and the hypervisor's time, and 
 | 
  apply that to a kernel's absolute timeout.  Unfortunately the 
 | 
  hypervisor and kernel times can drift even if the kernel is using 
 | 
  the Xen clocksource, because ntp can warp the kernel's clocksource. 
 | 
*/ 
 | 
static s64 get_abs_timeout(unsigned long delta) 
 | 
{ 
 | 
    return xen_clocksource_read() + delta; 
 | 
} 
 | 
  
 | 
static int xen_timerop_shutdown(struct clock_event_device *evt) 
 | 
{ 
 | 
    /* cancel timeout */ 
 | 
    HYPERVISOR_set_timer_op(0); 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static int xen_timerop_set_next_event(unsigned long delta, 
 | 
                      struct clock_event_device *evt) 
 | 
{ 
 | 
    WARN_ON(!clockevent_state_oneshot(evt)); 
 | 
  
 | 
    if (HYPERVISOR_set_timer_op(get_abs_timeout(delta)) < 0) 
 | 
        BUG(); 
 | 
  
 | 
    /* We may have missed the deadline, but there's no real way of 
 | 
       knowing for sure.  If the event was in the past, then we'll 
 | 
       get an immediate interrupt. */ 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static const struct clock_event_device xen_timerop_clockevent = { 
 | 
    .name            = "xen", 
 | 
    .features        = CLOCK_EVT_FEAT_ONESHOT, 
 | 
  
 | 
    .max_delta_ns        = 0xffffffff, 
 | 
    .max_delta_ticks    = 0xffffffff, 
 | 
    .min_delta_ns        = TIMER_SLOP, 
 | 
    .min_delta_ticks    = TIMER_SLOP, 
 | 
  
 | 
    .mult            = 1, 
 | 
    .shift            = 0, 
 | 
    .rating            = 500, 
 | 
  
 | 
    .set_state_shutdown    = xen_timerop_shutdown, 
 | 
    .set_next_event        = xen_timerop_set_next_event, 
 | 
}; 
 | 
  
 | 
static int xen_vcpuop_shutdown(struct clock_event_device *evt) 
 | 
{ 
 | 
    int cpu = smp_processor_id(); 
 | 
  
 | 
    if (HYPERVISOR_vcpu_op(VCPUOP_stop_singleshot_timer, xen_vcpu_nr(cpu), 
 | 
                   NULL) || 
 | 
        HYPERVISOR_vcpu_op(VCPUOP_stop_periodic_timer, xen_vcpu_nr(cpu), 
 | 
                   NULL)) 
 | 
        BUG(); 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static int xen_vcpuop_set_oneshot(struct clock_event_device *evt) 
 | 
{ 
 | 
    int cpu = smp_processor_id(); 
 | 
  
 | 
    if (HYPERVISOR_vcpu_op(VCPUOP_stop_periodic_timer, xen_vcpu_nr(cpu), 
 | 
                   NULL)) 
 | 
        BUG(); 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static int xen_vcpuop_set_next_event(unsigned long delta, 
 | 
                     struct clock_event_device *evt) 
 | 
{ 
 | 
    int cpu = smp_processor_id(); 
 | 
    struct vcpu_set_singleshot_timer single; 
 | 
    int ret; 
 | 
  
 | 
    WARN_ON(!clockevent_state_oneshot(evt)); 
 | 
  
 | 
    single.timeout_abs_ns = get_abs_timeout(delta); 
 | 
    /* Get an event anyway, even if the timeout is already expired */ 
 | 
    single.flags = 0; 
 | 
  
 | 
    ret = HYPERVISOR_vcpu_op(VCPUOP_set_singleshot_timer, xen_vcpu_nr(cpu), 
 | 
                 &single); 
 | 
    BUG_ON(ret != 0); 
 | 
  
 | 
    return ret; 
 | 
} 
 | 
  
 | 
static const struct clock_event_device xen_vcpuop_clockevent = { 
 | 
    .name = "xen", 
 | 
    .features = CLOCK_EVT_FEAT_ONESHOT, 
 | 
  
 | 
    .max_delta_ns = 0xffffffff, 
 | 
    .max_delta_ticks = 0xffffffff, 
 | 
    .min_delta_ns = TIMER_SLOP, 
 | 
    .min_delta_ticks = TIMER_SLOP, 
 | 
  
 | 
    .mult = 1, 
 | 
    .shift = 0, 
 | 
    .rating = 500, 
 | 
  
 | 
    .set_state_shutdown = xen_vcpuop_shutdown, 
 | 
    .set_state_oneshot = xen_vcpuop_set_oneshot, 
 | 
    .set_next_event = xen_vcpuop_set_next_event, 
 | 
}; 
 | 
  
 | 
static const struct clock_event_device *xen_clockevent = 
 | 
    &xen_timerop_clockevent; 
 | 
  
 | 
struct xen_clock_event_device { 
 | 
    struct clock_event_device evt; 
 | 
    char name[16]; 
 | 
}; 
 | 
static DEFINE_PER_CPU(struct xen_clock_event_device, xen_clock_events) = { .evt.irq = -1 }; 
 | 
  
 | 
static irqreturn_t xen_timer_interrupt(int irq, void *dev_id) 
 | 
{ 
 | 
    struct clock_event_device *evt = this_cpu_ptr(&xen_clock_events.evt); 
 | 
    irqreturn_t ret; 
 | 
  
 | 
    ret = IRQ_NONE; 
 | 
    if (evt->event_handler) { 
 | 
        evt->event_handler(evt); 
 | 
        ret = IRQ_HANDLED; 
 | 
    } 
 | 
  
 | 
    return ret; 
 | 
} 
 | 
  
 | 
void xen_teardown_timer(int cpu) 
 | 
{ 
 | 
    struct clock_event_device *evt; 
 | 
    evt = &per_cpu(xen_clock_events, cpu).evt; 
 | 
  
 | 
    if (evt->irq >= 0) { 
 | 
        unbind_from_irqhandler(evt->irq, NULL); 
 | 
        evt->irq = -1; 
 | 
    } 
 | 
} 
 | 
  
 | 
void xen_setup_timer(int cpu) 
 | 
{ 
 | 
    struct xen_clock_event_device *xevt = &per_cpu(xen_clock_events, cpu); 
 | 
    struct clock_event_device *evt = &xevt->evt; 
 | 
    int irq; 
 | 
  
 | 
    WARN(evt->irq >= 0, "IRQ%d for CPU%d is already allocated\n", evt->irq, cpu); 
 | 
    if (evt->irq >= 0) 
 | 
        xen_teardown_timer(cpu); 
 | 
  
 | 
    printk(KERN_INFO "installing Xen timer for CPU %d\n", cpu); 
 | 
  
 | 
    snprintf(xevt->name, sizeof(xevt->name), "timer%d", cpu); 
 | 
  
 | 
    irq = bind_virq_to_irqhandler(VIRQ_TIMER, cpu, xen_timer_interrupt, 
 | 
                      IRQF_PERCPU|IRQF_NOBALANCING|IRQF_TIMER| 
 | 
                      IRQF_FORCE_RESUME|IRQF_EARLY_RESUME, 
 | 
                      xevt->name, NULL); 
 | 
    (void)xen_set_irq_priority(irq, XEN_IRQ_PRIORITY_MAX); 
 | 
  
 | 
    memcpy(evt, xen_clockevent, sizeof(*evt)); 
 | 
  
 | 
    evt->cpumask = cpumask_of(cpu); 
 | 
    evt->irq = irq; 
 | 
} 
 | 
  
 | 
  
 | 
void xen_setup_cpu_clockevents(void) 
 | 
{ 
 | 
    clockevents_register_device(this_cpu_ptr(&xen_clock_events.evt)); 
 | 
} 
 | 
  
 | 
void xen_timer_resume(void) 
 | 
{ 
 | 
    int cpu; 
 | 
  
 | 
    if (xen_clockevent != &xen_vcpuop_clockevent) 
 | 
        return; 
 | 
  
 | 
    for_each_online_cpu(cpu) { 
 | 
        if (HYPERVISOR_vcpu_op(VCPUOP_stop_periodic_timer, 
 | 
                       xen_vcpu_nr(cpu), NULL)) 
 | 
            BUG(); 
 | 
    } 
 | 
} 
 | 
  
 | 
static const struct pv_time_ops xen_time_ops __initconst = { 
 | 
    .sched_clock = xen_sched_clock, 
 | 
    .steal_clock = xen_steal_clock, 
 | 
}; 
 | 
  
 | 
static struct pvclock_vsyscall_time_info *xen_clock __read_mostly; 
 | 
static u64 xen_clock_value_saved; 
 | 
  
 | 
void xen_save_time_memory_area(void) 
 | 
{ 
 | 
    struct vcpu_register_time_memory_area t; 
 | 
    int ret; 
 | 
  
 | 
    xen_clock_value_saved = xen_clocksource_read() - xen_sched_clock_offset; 
 | 
  
 | 
    if (!xen_clock) 
 | 
        return; 
 | 
  
 | 
    t.addr.v = NULL; 
 | 
  
 | 
    ret = HYPERVISOR_vcpu_op(VCPUOP_register_vcpu_time_memory_area, 0, &t); 
 | 
    if (ret != 0) 
 | 
        pr_notice("Cannot save secondary vcpu_time_info (err %d)", 
 | 
              ret); 
 | 
    else 
 | 
        clear_page(xen_clock); 
 | 
} 
 | 
  
 | 
void xen_restore_time_memory_area(void) 
 | 
{ 
 | 
    struct vcpu_register_time_memory_area t; 
 | 
    int ret; 
 | 
  
 | 
    if (!xen_clock) 
 | 
        goto out; 
 | 
  
 | 
    t.addr.v = &xen_clock->pvti; 
 | 
  
 | 
    ret = HYPERVISOR_vcpu_op(VCPUOP_register_vcpu_time_memory_area, 0, &t); 
 | 
  
 | 
    /* 
 | 
     * We don't disable VCLOCK_PVCLOCK entirely if it fails to register the 
 | 
     * secondary time info with Xen or if we migrated to a host without the 
 | 
     * necessary flags. On both of these cases what happens is either 
 | 
     * process seeing a zeroed out pvti or seeing no PVCLOCK_TSC_STABLE_BIT 
 | 
     * bit set. Userspace checks the latter and if 0, it discards the data 
 | 
     * in pvti and fallbacks to a system call for a reliable timestamp. 
 | 
     */ 
 | 
    if (ret != 0) 
 | 
        pr_notice("Cannot restore secondary vcpu_time_info (err %d)", 
 | 
              ret); 
 | 
  
 | 
out: 
 | 
    /* Need pvclock_resume() before using xen_clocksource_read(). */ 
 | 
    pvclock_resume(); 
 | 
    xen_sched_clock_offset = xen_clocksource_read() - xen_clock_value_saved; 
 | 
} 
 | 
  
 | 
static void xen_setup_vsyscall_time_info(void) 
 | 
{ 
 | 
    struct vcpu_register_time_memory_area t; 
 | 
    struct pvclock_vsyscall_time_info *ti; 
 | 
    int ret; 
 | 
  
 | 
    ti = (struct pvclock_vsyscall_time_info *)get_zeroed_page(GFP_KERNEL); 
 | 
    if (!ti) 
 | 
        return; 
 | 
  
 | 
    t.addr.v = &ti->pvti; 
 | 
  
 | 
    ret = HYPERVISOR_vcpu_op(VCPUOP_register_vcpu_time_memory_area, 0, &t); 
 | 
    if (ret) { 
 | 
        pr_notice("xen: VCLOCK_PVCLOCK not supported (err %d)\n", ret); 
 | 
        free_page((unsigned long)ti); 
 | 
        return; 
 | 
    } 
 | 
  
 | 
    /* 
 | 
     * If primary time info had this bit set, secondary should too since 
 | 
     * it's the same data on both just different memory regions. But we 
 | 
     * still check it in case hypervisor is buggy. 
 | 
     */ 
 | 
    if (!(ti->pvti.flags & PVCLOCK_TSC_STABLE_BIT)) { 
 | 
        t.addr.v = NULL; 
 | 
        ret = HYPERVISOR_vcpu_op(VCPUOP_register_vcpu_time_memory_area, 
 | 
                     0, &t); 
 | 
        if (!ret) 
 | 
            free_page((unsigned long)ti); 
 | 
  
 | 
        pr_notice("xen: VCLOCK_PVCLOCK not supported (tsc unstable)\n"); 
 | 
        return; 
 | 
    } 
 | 
  
 | 
    xen_clock = ti; 
 | 
    pvclock_set_pvti_cpu0_va(xen_clock); 
 | 
  
 | 
    xen_clocksource.archdata.vclock_mode = VCLOCK_PVCLOCK; 
 | 
} 
 | 
  
 | 
static void __init xen_time_init(void) 
 | 
{ 
 | 
    struct pvclock_vcpu_time_info *pvti; 
 | 
    int cpu = smp_processor_id(); 
 | 
    struct timespec64 tp; 
 | 
  
 | 
    /* As Dom0 is never moved, no penalty on using TSC there */ 
 | 
    if (xen_initial_domain()) 
 | 
        xen_clocksource.rating = 275; 
 | 
  
 | 
    clocksource_register_hz(&xen_clocksource, NSEC_PER_SEC); 
 | 
  
 | 
    if (HYPERVISOR_vcpu_op(VCPUOP_stop_periodic_timer, xen_vcpu_nr(cpu), 
 | 
                   NULL) == 0) { 
 | 
        /* Successfully turned off 100Hz tick, so we have the 
 | 
           vcpuop-based timer interface */ 
 | 
        printk(KERN_DEBUG "Xen: using vcpuop timer interface\n"); 
 | 
        xen_clockevent = &xen_vcpuop_clockevent; 
 | 
    } 
 | 
  
 | 
    /* Set initial system time with full resolution */ 
 | 
    xen_read_wallclock(&tp); 
 | 
    do_settimeofday64(&tp); 
 | 
  
 | 
    setup_force_cpu_cap(X86_FEATURE_TSC); 
 | 
  
 | 
    /* 
 | 
     * We check ahead on the primary time info if this 
 | 
     * bit is supported hence speeding up Xen clocksource. 
 | 
     */ 
 | 
    pvti = &__this_cpu_read(xen_vcpu)->time; 
 | 
    if (pvti->flags & PVCLOCK_TSC_STABLE_BIT) { 
 | 
        pvclock_set_flags(PVCLOCK_TSC_STABLE_BIT); 
 | 
        xen_setup_vsyscall_time_info(); 
 | 
    } 
 | 
  
 | 
    xen_setup_runstate_info(cpu); 
 | 
    xen_setup_timer(cpu); 
 | 
    xen_setup_cpu_clockevents(); 
 | 
  
 | 
    xen_time_setup_guest(); 
 | 
  
 | 
    if (xen_initial_domain()) 
 | 
        pvclock_gtod_register_notifier(&xen_pvclock_gtod_notifier); 
 | 
} 
 | 
  
 | 
void __init xen_init_time_ops(void) 
 | 
{ 
 | 
    xen_sched_clock_offset = xen_clocksource_read(); 
 | 
    pv_time_ops = xen_time_ops; 
 | 
  
 | 
    x86_init.timers.timer_init = xen_time_init; 
 | 
    x86_init.timers.setup_percpu_clockev = x86_init_noop; 
 | 
    x86_cpuinit.setup_percpu_clockev = x86_init_noop; 
 | 
  
 | 
    x86_platform.calibrate_tsc = xen_tsc_khz; 
 | 
    x86_platform.get_wallclock = xen_get_wallclock; 
 | 
    /* Dom0 uses the native method to set the hardware RTC. */ 
 | 
    if (!xen_initial_domain()) 
 | 
        x86_platform.set_wallclock = xen_set_wallclock; 
 | 
} 
 | 
  
 | 
#ifdef CONFIG_XEN_PVHVM 
 | 
static void xen_hvm_setup_cpu_clockevents(void) 
 | 
{ 
 | 
    int cpu = smp_processor_id(); 
 | 
    xen_setup_runstate_info(cpu); 
 | 
    /* 
 | 
     * xen_setup_timer(cpu) - snprintf is bad in atomic context. Hence 
 | 
     * doing it xen_hvm_cpu_notify (which gets called by smp_init during 
 | 
     * early bootup and also during CPU hotplug events). 
 | 
     */ 
 | 
    xen_setup_cpu_clockevents(); 
 | 
} 
 | 
  
 | 
void __init xen_hvm_init_time_ops(void) 
 | 
{ 
 | 
    /* 
 | 
     * vector callback is needed otherwise we cannot receive interrupts 
 | 
     * on cpu > 0 and at this point we don't know how many cpus are 
 | 
     * available. 
 | 
     */ 
 | 
    if (!xen_have_vector_callback) 
 | 
        return; 
 | 
  
 | 
    if (!xen_feature(XENFEAT_hvm_safe_pvclock)) { 
 | 
        pr_info("Xen doesn't support pvclock on HVM, disable pv timer"); 
 | 
        return; 
 | 
    } 
 | 
  
 | 
    xen_sched_clock_offset = xen_clocksource_read(); 
 | 
    pv_time_ops = xen_time_ops; 
 | 
    x86_init.timers.setup_percpu_clockev = xen_time_init; 
 | 
    x86_cpuinit.setup_percpu_clockev = xen_hvm_setup_cpu_clockevents; 
 | 
  
 | 
    x86_platform.calibrate_tsc = xen_tsc_khz; 
 | 
    x86_platform.get_wallclock = xen_get_wallclock; 
 | 
    x86_platform.set_wallclock = xen_set_wallclock; 
 | 
} 
 | 
#endif 
 |