linux/drivers/ptp/ptp_kvm.c
<<
>>
Prefs
   1/*
   2 * Virtual PTP 1588 clock for use with KVM guests
   3 *
   4 * Copyright (C) 2017 Red Hat Inc.
   5 *
   6 *  This program is free software; you can redistribute it and/or modify
   7 *  it under the terms of the GNU General Public License as published by
   8 *  the Free Software Foundation; either version 2 of the License, or
   9 *  (at your option) any later version.
  10 *
  11 *  This program is distributed in the hope that it will be useful,
  12 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  13 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14 *  GNU General Public License for more details.
  15 *
  16 */
  17#include <linux/device.h>
  18#include <linux/err.h>
  19#include <linux/init.h>
  20#include <linux/kernel.h>
  21#include <linux/module.h>
  22#include <uapi/linux/kvm_para.h>
  23#include <asm/kvm_para.h>
  24#include <asm/pvclock.h>
  25#include <asm/kvmclock.h>
  26#include <uapi/asm/kvm_para.h>
  27
  28#include <linux/ptp_clock_kernel.h>
  29
  30struct kvm_ptp_clock {
  31        struct ptp_clock *ptp_clock;
  32        struct ptp_clock_info caps;
  33};
  34
  35DEFINE_SPINLOCK(kvm_ptp_lock);
  36
  37static struct pvclock_vsyscall_time_info *hv_clock;
  38
  39static struct kvm_clock_pairing clock_pair;
  40static phys_addr_t clock_pair_gpa;
  41
  42static int ptp_kvm_get_time_fn(ktime_t *device_time,
  43                               struct system_counterval_t *system_counter,
  44                               void *ctx)
  45{
  46        unsigned long ret;
  47        struct timespec64 tspec;
  48        unsigned version;
  49        int cpu;
  50        struct pvclock_vcpu_time_info *src;
  51
  52        spin_lock(&kvm_ptp_lock);
  53
  54        preempt_disable_notrace();
  55        cpu = smp_processor_id();
  56        src = &hv_clock[cpu].pvti;
  57
  58        do {
  59                /*
  60                 * We are using a TSC value read in the hosts
  61                 * kvm_hc_clock_pairing handling.
  62                 * So any changes to tsc_to_system_mul
  63                 * and tsc_shift or any other pvclock
  64                 * data invalidate that measurement.
  65                 */
  66                version = pvclock_read_begin(src);
  67
  68                ret = kvm_hypercall2(KVM_HC_CLOCK_PAIRING,
  69                                     clock_pair_gpa,
  70                                     KVM_CLOCK_PAIRING_WALLCLOCK);
  71                if (ret != 0) {
  72                        pr_err_ratelimited("clock pairing hypercall ret %lu\n", ret);
  73                        spin_unlock(&kvm_ptp_lock);
  74                        preempt_enable_notrace();
  75                        return -EOPNOTSUPP;
  76                }
  77
  78                tspec.tv_sec = clock_pair.sec;
  79                tspec.tv_nsec = clock_pair.nsec;
  80                ret = __pvclock_read_cycles(src, clock_pair.tsc);
  81        } while (pvclock_read_retry(src, version));
  82
  83        preempt_enable_notrace();
  84
  85        system_counter->cycles = ret;
  86        system_counter->cs = &kvm_clock;
  87
  88        *device_time = timespec64_to_ktime(tspec);
  89
  90        spin_unlock(&kvm_ptp_lock);
  91
  92        return 0;
  93}
  94
  95static int ptp_kvm_getcrosststamp(struct ptp_clock_info *ptp,
  96                                  struct system_device_crosststamp *xtstamp)
  97{
  98        return get_device_system_crosststamp(ptp_kvm_get_time_fn, NULL,
  99                                             NULL, xtstamp);
 100}
 101
 102/*
 103 * PTP clock operations
 104 */
 105
 106static int ptp_kvm_adjfreq(struct ptp_clock_info *ptp, s32 ppb)
 107{
 108        return -EOPNOTSUPP;
 109}
 110
 111static int ptp_kvm_adjtime(struct ptp_clock_info *ptp, s64 delta)
 112{
 113        return -EOPNOTSUPP;
 114}
 115
 116static int ptp_kvm_settime(struct ptp_clock_info *ptp,
 117                           const struct timespec64 *ts)
 118{
 119        return -EOPNOTSUPP;
 120}
 121
 122static int ptp_kvm_gettime(struct ptp_clock_info *ptp, struct timespec64 *ts)
 123{
 124        unsigned long ret;
 125        struct timespec64 tspec;
 126
 127        spin_lock(&kvm_ptp_lock);
 128
 129        ret = kvm_hypercall2(KVM_HC_CLOCK_PAIRING,
 130                             clock_pair_gpa,
 131                             KVM_CLOCK_PAIRING_WALLCLOCK);
 132        if (ret != 0) {
 133                pr_err_ratelimited("clock offset hypercall ret %lu\n", ret);
 134                spin_unlock(&kvm_ptp_lock);
 135                return -EOPNOTSUPP;
 136        }
 137
 138        tspec.tv_sec = clock_pair.sec;
 139        tspec.tv_nsec = clock_pair.nsec;
 140        spin_unlock(&kvm_ptp_lock);
 141
 142        memcpy(ts, &tspec, sizeof(struct timespec64));
 143
 144        return 0;
 145}
 146
 147static int ptp_kvm_enable(struct ptp_clock_info *ptp,
 148                          struct ptp_clock_request *rq, int on)
 149{
 150        return -EOPNOTSUPP;
 151}
 152
 153static const struct ptp_clock_info ptp_kvm_caps = {
 154        .owner          = THIS_MODULE,
 155        .name           = "KVM virtual PTP",
 156        .max_adj        = 0,
 157        .n_ext_ts       = 0,
 158        .n_pins         = 0,
 159        .pps            = 0,
 160        .adjfreq        = ptp_kvm_adjfreq,
 161        .adjtime        = ptp_kvm_adjtime,
 162        .gettime64      = ptp_kvm_gettime,
 163        .settime64      = ptp_kvm_settime,
 164        .enable         = ptp_kvm_enable,
 165        .getcrosststamp = ptp_kvm_getcrosststamp,
 166};
 167
 168/* module operations */
 169
 170static struct kvm_ptp_clock kvm_ptp_clock;
 171
 172static void __exit ptp_kvm_exit(void)
 173{
 174        ptp_clock_unregister(kvm_ptp_clock.ptp_clock);
 175}
 176
 177static int __init ptp_kvm_init(void)
 178{
 179        long ret;
 180
 181        clock_pair_gpa = slow_virt_to_phys(&clock_pair);
 182        hv_clock = pvclock_pvti_cpu0_va();
 183
 184        if (!hv_clock)
 185                return -ENODEV;
 186
 187        ret = kvm_hypercall2(KVM_HC_CLOCK_PAIRING, clock_pair_gpa,
 188                        KVM_CLOCK_PAIRING_WALLCLOCK);
 189        if (ret == -KVM_ENOSYS || ret == -KVM_EOPNOTSUPP)
 190                return -ENODEV;
 191
 192        kvm_ptp_clock.caps = ptp_kvm_caps;
 193
 194        kvm_ptp_clock.ptp_clock = ptp_clock_register(&kvm_ptp_clock.caps, NULL);
 195
 196        return PTR_ERR_OR_ZERO(kvm_ptp_clock.ptp_clock);
 197}
 198
 199module_init(ptp_kvm_init);
 200module_exit(ptp_kvm_exit);
 201
 202MODULE_AUTHOR("Marcelo Tosatti <mtosatti@redhat.com>");
 203MODULE_DESCRIPTION("PTP clock using KVMCLOCK");
 204MODULE_LICENSE("GPL");
 205