aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVitaly Kuznetsov <vkuznets@redhat.com>2017-02-04 11:57:14 -0500
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2017-02-10 09:40:19 -0500
commit3716a49a81ba19dda7202633a68b28564ba95eb5 (patch)
treeabeef1d98cc89a1f0b9c7b842ee02ac35c5c6813
parentdee863b571b0a76e9c549ee99e8782bb4bc6502b (diff)
hv_utils: implement Hyper-V PTP source
With TimeSync version 4 protocol support we started updating system time continuously through the whole lifetime of Hyper-V guests. Every 5 seconds there is a time sample from the host which triggers do_settimeofday[64](). While the time from the host is very accurate such adjustments may cause issues: - Time is jumping forward and backward, some applications may misbehave. - In case an NTP server runs in parallel and uses something else for time sync (network, PTP,...) system time will never converge. - Systemd starts annoying you by printing "Time has been changed" every 5 seconds to the system log. Instead of doing in-kernel time adjustments offload the work to an NTP client by exposing TimeSync messages as a PTP device. Users may now decide what they want to use as a source. I tested the solution with chrony, the config was: refclock PHC /dev/ptp0 poll 3 dpoll -2 offset 0 The result I'm seeing is accurate enough, the time delta between the guest and the host is almost always within [-10us, +10us], the in-kernel solution was giving us comparable results. I also tried implementing PPS device instead of PTP by using not currently used Hyper-V synthetic timers (we use only one of four for clockevent) but with PPS source only chrony wasn't able to give me the required accuracy, the delta often more that 100us. Signed-off-by: Vitaly Kuznetsov <vkuznets@redhat.com> Signed-off-by: K. Y. Srinivasan <kys@microsoft.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r--drivers/hv/hv_util.c179
1 files changed, 152 insertions, 27 deletions
diff --git a/drivers/hv/hv_util.c b/drivers/hv/hv_util.c
index d42ede78a9dd..3076ee3ccc6c 100644
--- a/drivers/hv/hv_util.c
+++ b/drivers/hv/hv_util.c
@@ -27,6 +27,8 @@
27#include <linux/sysctl.h> 27#include <linux/sysctl.h>
28#include <linux/reboot.h> 28#include <linux/reboot.h>
29#include <linux/hyperv.h> 29#include <linux/hyperv.h>
30#include <linux/clockchips.h>
31#include <linux/ptp_clock_kernel.h>
30#include <asm/mshyperv.h> 32#include <asm/mshyperv.h>
31 33
32#include "hyperv_vmbus.h" 34#include "hyperv_vmbus.h"
@@ -210,29 +212,15 @@ struct adj_time_work {
210 212
211static void hv_set_host_time(struct work_struct *work) 213static void hv_set_host_time(struct work_struct *work)
212{ 214{
213 struct adj_time_work *wrk; 215 struct adj_time_work *wrk;
214 s64 host_tns;
215 u64 newtime;
216 struct timespec64 host_ts; 216 struct timespec64 host_ts;
217 u64 reftime, newtime;
217 218
218 wrk = container_of(work, struct adj_time_work, work); 219 wrk = container_of(work, struct adj_time_work, work);
219 220
220 newtime = wrk->host_time; 221 reftime = hyperv_cs->read(hyperv_cs);
221 if (ts_srv_version > TS_VERSION_3) { 222 newtime = wrk->host_time + (reftime - wrk->ref_time);
222 /* 223 host_ts = ns_to_timespec64((newtime - WLTIMEDELTA) * 100);
223 * Some latency has been introduced since Hyper-V generated
224 * its time sample. Take that latency into account before
225 * using TSC reference time sample from Hyper-V.
226 *
227 * This sample is given by TimeSync v4 and above hosts.
228 */
229 u64 current_tick;
230
231 hv_get_current_tick(current_tick);
232 newtime += (current_tick - wrk->ref_time);
233 }
234 host_tns = (newtime - WLTIMEDELTA) * 100;
235 host_ts = ns_to_timespec64(host_tns);
236 224
237 do_settimeofday64(&host_ts); 225 do_settimeofday64(&host_ts);
238} 226}
@@ -251,22 +239,60 @@ static void hv_set_host_time(struct work_struct *work)
251 * to discipline the clock. 239 * to discipline the clock.
252 */ 240 */
253static struct adj_time_work wrk; 241static struct adj_time_work wrk;
254static inline void adj_guesttime(u64 hosttime, u64 reftime, u8 flags) 242
243/*
244 * The last time sample, received from the host. PTP device responds to
245 * requests by using this data and the current partition-wide time reference
246 * count.
247 */
248static struct {
249 u64 host_time;
250 u64 ref_time;
251 struct system_time_snapshot snap;
252 spinlock_t lock;
253} host_ts;
254
255static inline void adj_guesttime(u64 hosttime, u64 reftime, u8 adj_flags)
255{ 256{
257 unsigned long flags;
258 u64 cur_reftime;
256 259
257 /* 260 /*
258 * This check is safe since we are executing in the 261 * This check is safe since we are executing in the
259 * interrupt context and time synch messages arre always 262 * interrupt context and time synch messages arre always
260 * delivered on the same CPU. 263 * delivered on the same CPU.
261 */ 264 */
262 if (work_pending(&wrk.work)) 265 if (adj_flags & ICTIMESYNCFLAG_SYNC) {
263 return; 266 /* Queue a job to do do_settimeofday64() */
264 267 if (work_pending(&wrk.work))
265 wrk.host_time = hosttime; 268 return;
266 wrk.ref_time = reftime; 269
267 wrk.flags = flags; 270 wrk.host_time = hosttime;
268 if ((flags & (ICTIMESYNCFLAG_SYNC | ICTIMESYNCFLAG_SAMPLE)) != 0) { 271 wrk.ref_time = reftime;
272 wrk.flags = adj_flags;
269 schedule_work(&wrk.work); 273 schedule_work(&wrk.work);
274 } else {
275 /*
276 * Save the adjusted time sample from the host and the snapshot
277 * of the current system time for PTP device.
278 */
279 spin_lock_irqsave(&host_ts.lock, flags);
280
281 cur_reftime = hyperv_cs->read(hyperv_cs);
282 host_ts.host_time = hosttime;
283 host_ts.ref_time = cur_reftime;
284 ktime_get_snapshot(&host_ts.snap);
285
286 /*
287 * TimeSync v4 messages contain reference time (guest's Hyper-V
288 * clocksource read when the time sample was generated), we can
289 * improve the precision by adding the delta between now and the
290 * time of generation.
291 */
292 if (ts_srv_version > TS_VERSION_3)
293 host_ts.host_time += (cur_reftime - reftime);
294
295 spin_unlock_irqrestore(&host_ts.lock, flags);
270 } 296 }
271} 297}
272 298
@@ -479,14 +505,113 @@ static struct hv_driver util_drv = {
479 .remove = util_remove, 505 .remove = util_remove,
480}; 506};
481 507
508static int hv_ptp_enable(struct ptp_clock_info *info,
509 struct ptp_clock_request *request, int on)
510{
511 return -EOPNOTSUPP;
512}
513
514static int hv_ptp_settime(struct ptp_clock_info *p, const struct timespec64 *ts)
515{
516 return -EOPNOTSUPP;
517}
518
519static int hv_ptp_adjfreq(struct ptp_clock_info *ptp, s32 delta)
520{
521 return -EOPNOTSUPP;
522}
523static int hv_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
524{
525 return -EOPNOTSUPP;
526}
527
528static int hv_ptp_gettime(struct ptp_clock_info *info, struct timespec64 *ts)
529{
530 unsigned long flags;
531 u64 newtime, reftime;
532
533 spin_lock_irqsave(&host_ts.lock, flags);
534 reftime = hyperv_cs->read(hyperv_cs);
535 newtime = host_ts.host_time + (reftime - host_ts.ref_time);
536 *ts = ns_to_timespec64((newtime - WLTIMEDELTA) * 100);
537 spin_unlock_irqrestore(&host_ts.lock, flags);
538
539 return 0;
540}
541
542static int hv_ptp_get_syncdevicetime(ktime_t *device,
543 struct system_counterval_t *system,
544 void *ctx)
545{
546 system->cs = hyperv_cs;
547 system->cycles = host_ts.ref_time;
548 *device = ns_to_ktime((host_ts.host_time - WLTIMEDELTA) * 100);
549
550 return 0;
551}
552
553static int hv_ptp_getcrosststamp(struct ptp_clock_info *ptp,
554 struct system_device_crosststamp *xtstamp)
555{
556 unsigned long flags;
557 int ret;
558
559 spin_lock_irqsave(&host_ts.lock, flags);
560
561 /*
562 * host_ts contains the last time sample from the host and the snapshot
563 * of system time. We don't need to calculate the time delta between
564 * the reception and now as get_device_system_crosststamp() does the
565 * required interpolation.
566 */
567 ret = get_device_system_crosststamp(hv_ptp_get_syncdevicetime,
568 NULL, &host_ts.snap, xtstamp);
569
570 spin_unlock_irqrestore(&host_ts.lock, flags);
571
572 return ret;
573}
574
575static struct ptp_clock_info ptp_hyperv_info = {
576 .name = "hyperv",
577 .enable = hv_ptp_enable,
578 .adjtime = hv_ptp_adjtime,
579 .adjfreq = hv_ptp_adjfreq,
580 .gettime64 = hv_ptp_gettime,
581 .getcrosststamp = hv_ptp_getcrosststamp,
582 .settime64 = hv_ptp_settime,
583 .owner = THIS_MODULE,
584};
585
586static struct ptp_clock *hv_ptp_clock;
587
482static int hv_timesync_init(struct hv_util_service *srv) 588static int hv_timesync_init(struct hv_util_service *srv)
483{ 589{
590 /* TimeSync requires Hyper-V clocksource. */
591 if (!hyperv_cs)
592 return -ENODEV;
593
484 INIT_WORK(&wrk.work, hv_set_host_time); 594 INIT_WORK(&wrk.work, hv_set_host_time);
595
596 /*
597 * ptp_clock_register() returns NULL when CONFIG_PTP_1588_CLOCK is
598 * disabled but the driver is still useful without the PTP device
599 * as it still handles the ICTIMESYNCFLAG_SYNC case.
600 */
601 hv_ptp_clock = ptp_clock_register(&ptp_hyperv_info, NULL);
602 if (IS_ERR_OR_NULL(hv_ptp_clock)) {
603 pr_err("cannot register PTP clock: %ld\n",
604 PTR_ERR(hv_ptp_clock));
605 hv_ptp_clock = NULL;
606 }
607
485 return 0; 608 return 0;
486} 609}
487 610
488static void hv_timesync_deinit(void) 611static void hv_timesync_deinit(void)
489{ 612{
613 if (hv_ptp_clock)
614 ptp_clock_unregister(hv_ptp_clock);
490 cancel_work_sync(&wrk.work); 615 cancel_work_sync(&wrk.work);
491} 616}
492 617