aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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