diff options
-rw-r--r-- | drivers/hv/hv_util.c | 179 |
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 | ||
211 | static void hv_set_host_time(struct work_struct *work) | 213 | static 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 | */ |
253 | static struct adj_time_work wrk; | 241 | static struct adj_time_work wrk; |
254 | static 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 | */ | ||
248 | static struct { | ||
249 | u64 host_time; | ||
250 | u64 ref_time; | ||
251 | struct system_time_snapshot snap; | ||
252 | spinlock_t lock; | ||
253 | } host_ts; | ||
254 | |||
255 | static 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 | ||
508 | static int hv_ptp_enable(struct ptp_clock_info *info, | ||
509 | struct ptp_clock_request *request, int on) | ||
510 | { | ||
511 | return -EOPNOTSUPP; | ||
512 | } | ||
513 | |||
514 | static int hv_ptp_settime(struct ptp_clock_info *p, const struct timespec64 *ts) | ||
515 | { | ||
516 | return -EOPNOTSUPP; | ||
517 | } | ||
518 | |||
519 | static int hv_ptp_adjfreq(struct ptp_clock_info *ptp, s32 delta) | ||
520 | { | ||
521 | return -EOPNOTSUPP; | ||
522 | } | ||
523 | static int hv_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta) | ||
524 | { | ||
525 | return -EOPNOTSUPP; | ||
526 | } | ||
527 | |||
528 | static 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 | |||
542 | static 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 | |||
553 | static 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 | |||
575 | static 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 | |||
586 | static struct ptp_clock *hv_ptp_clock; | ||
587 | |||
482 | static int hv_timesync_init(struct hv_util_service *srv) | 588 | static 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 | ||
488 | static void hv_timesync_deinit(void) | 611 | static 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 | ||