diff options
-rw-r--r-- | drivers/ptp/Kconfig | 12 | ||||
-rw-r--r-- | drivers/ptp/Makefile | 1 | ||||
-rw-r--r-- | drivers/ptp/ptp_kvm.c | 200 |
3 files changed, 213 insertions, 0 deletions
diff --git a/drivers/ptp/Kconfig b/drivers/ptp/Kconfig index bdce33291161..384f661a6496 100644 --- a/drivers/ptp/Kconfig +++ b/drivers/ptp/Kconfig | |||
@@ -90,4 +90,16 @@ config PTP_1588_CLOCK_PCH | |||
90 | To compile this driver as a module, choose M here: the module | 90 | To compile this driver as a module, choose M here: the module |
91 | will be called ptp_pch. | 91 | will be called ptp_pch. |
92 | 92 | ||
93 | config PTP_1588_CLOCK_KVM | ||
94 | tristate "KVM virtual PTP clock" | ||
95 | depends on PTP_1588_CLOCK | ||
96 | depends on KVM_GUEST && X86 | ||
97 | default y | ||
98 | help | ||
99 | This driver adds support for using kvm infrastructure as a PTP | ||
100 | clock. This clock is only useful if you are using KVM guests. | ||
101 | |||
102 | To compile this driver as a module, choose M here: the module | ||
103 | will be called ptp_kvm. | ||
104 | |||
93 | endmenu | 105 | endmenu |
diff --git a/drivers/ptp/Makefile b/drivers/ptp/Makefile index 8b58597298de..530736161a8b 100644 --- a/drivers/ptp/Makefile +++ b/drivers/ptp/Makefile | |||
@@ -6,3 +6,4 @@ ptp-y := ptp_clock.o ptp_chardev.o ptp_sysfs.o | |||
6 | obj-$(CONFIG_PTP_1588_CLOCK) += ptp.o | 6 | obj-$(CONFIG_PTP_1588_CLOCK) += ptp.o |
7 | obj-$(CONFIG_PTP_1588_CLOCK_IXP46X) += ptp_ixp46x.o | 7 | obj-$(CONFIG_PTP_1588_CLOCK_IXP46X) += ptp_ixp46x.o |
8 | obj-$(CONFIG_PTP_1588_CLOCK_PCH) += ptp_pch.o | 8 | obj-$(CONFIG_PTP_1588_CLOCK_PCH) += ptp_pch.o |
9 | obj-$(CONFIG_PTP_1588_CLOCK_KVM) += ptp_kvm.o | ||
diff --git a/drivers/ptp/ptp_kvm.c b/drivers/ptp/ptp_kvm.c new file mode 100644 index 000000000000..0a54e8326a90 --- /dev/null +++ b/drivers/ptp/ptp_kvm.c | |||
@@ -0,0 +1,200 @@ | |||
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 | |||
30 | struct kvm_ptp_clock { | ||
31 | struct ptp_clock *ptp_clock; | ||
32 | struct ptp_clock_info caps; | ||
33 | }; | ||
34 | |||
35 | DEFINE_SPINLOCK(kvm_ptp_lock); | ||
36 | |||
37 | static struct pvclock_vsyscall_time_info *hv_clock; | ||
38 | |||
39 | static struct kvm_clock_pairing clock_pair; | ||
40 | static phys_addr_t clock_pair_gpa; | ||
41 | |||
42 | static 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 | |||
95 | static 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 | |||
106 | static int ptp_kvm_adjfreq(struct ptp_clock_info *ptp, s32 ppb) | ||
107 | { | ||
108 | return -EOPNOTSUPP; | ||
109 | } | ||
110 | |||
111 | static int ptp_kvm_adjtime(struct ptp_clock_info *ptp, s64 delta) | ||
112 | { | ||
113 | return -EOPNOTSUPP; | ||
114 | } | ||
115 | |||
116 | static int ptp_kvm_settime(struct ptp_clock_info *ptp, | ||
117 | const struct timespec64 *ts) | ||
118 | { | ||
119 | return -EOPNOTSUPP; | ||
120 | } | ||
121 | |||
122 | static 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 | |||
147 | static int ptp_kvm_enable(struct ptp_clock_info *ptp, | ||
148 | struct ptp_clock_request *rq, int on) | ||
149 | { | ||
150 | return -EOPNOTSUPP; | ||
151 | } | ||
152 | |||
153 | static 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 | |||
170 | static struct kvm_ptp_clock kvm_ptp_clock; | ||
171 | |||
172 | static void __exit ptp_kvm_exit(void) | ||
173 | { | ||
174 | ptp_clock_unregister(kvm_ptp_clock.ptp_clock); | ||
175 | } | ||
176 | |||
177 | static int __init ptp_kvm_init(void) | ||
178 | { | ||
179 | clock_pair_gpa = slow_virt_to_phys(&clock_pair); | ||
180 | hv_clock = pvclock_pvti_cpu0_va(); | ||
181 | |||
182 | if (!hv_clock) | ||
183 | return -ENODEV; | ||
184 | |||
185 | kvm_ptp_clock.caps = ptp_kvm_caps; | ||
186 | |||
187 | kvm_ptp_clock.ptp_clock = ptp_clock_register(&kvm_ptp_clock.caps, NULL); | ||
188 | |||
189 | if (IS_ERR(kvm_ptp_clock.ptp_clock)) | ||
190 | return PTR_ERR(kvm_ptp_clock.ptp_clock); | ||
191 | |||
192 | return 0; | ||
193 | } | ||
194 | |||
195 | module_init(ptp_kvm_init); | ||
196 | module_exit(ptp_kvm_exit); | ||
197 | |||
198 | MODULE_AUTHOR("Marcelo Tosatti <mtosatti@redhat.com>"); | ||
199 | MODULE_DESCRIPTION("PTP clock using KVMCLOCK"); | ||
200 | MODULE_LICENSE("GPL"); | ||