Commit | Line | Data |
---|---|---|
fd1fea68 MK |
1 | /* SPDX-License-Identifier: GPL-2.0 */ |
2 | ||
3 | /* | |
4 | * Definitions for the clocksource provided by the Hyper-V | |
5 | * hypervisor to guest VMs, as described in the Hyper-V Top | |
6 | * Level Functional Spec (TLFS). | |
7 | * | |
8 | * Copyright (C) 2019, Microsoft, Inc. | |
9 | * | |
10 | * Author: Michael Kelley <mikelley@microsoft.com> | |
11 | */ | |
12 | ||
13 | #ifndef __CLKSOURCE_HYPERV_TIMER_H | |
14 | #define __CLKSOURCE_HYPERV_TIMER_H | |
15 | ||
dd2cb348 MK |
16 | #include <linux/clocksource.h> |
17 | #include <linux/math64.h> | |
18 | #include <asm/mshyperv.h> | |
19 | ||
fd1fea68 MK |
20 | #define HV_MAX_MAX_DELTA_TICKS 0xffffffff |
21 | #define HV_MIN_DELTA_TICKS 1 | |
22 | ||
23 | /* Routines called by the VMbus driver */ | |
24 | extern int hv_stimer_alloc(int sint); | |
25 | extern void hv_stimer_free(void); | |
26 | extern void hv_stimer_init(unsigned int cpu); | |
27 | extern void hv_stimer_cleanup(unsigned int cpu); | |
28 | extern void hv_stimer_global_cleanup(void); | |
29 | extern void hv_stimer0_isr(void); | |
30 | ||
3e2d9453 | 31 | #ifdef CONFIG_HYPERV_TIMER |
dd2cb348 MK |
32 | extern struct clocksource *hyperv_cs; |
33 | extern void hv_init_clocksource(void); | |
dd2cb348 | 34 | |
dd2cb348 MK |
35 | extern struct ms_hyperv_tsc_page *hv_get_tsc_page(void); |
36 | ||
37 | static inline notrace u64 | |
38 | hv_read_tsc_page_tsc(const struct ms_hyperv_tsc_page *tsc_pg, u64 *cur_tsc) | |
39 | { | |
40 | u64 scale, offset; | |
41 | u32 sequence; | |
42 | ||
43 | /* | |
44 | * The protocol for reading Hyper-V TSC page is specified in Hypervisor | |
45 | * Top-Level Functional Specification ver. 3.0 and above. To get the | |
46 | * reference time we must do the following: | |
47 | * - READ ReferenceTscSequence | |
48 | * A special '0' value indicates the time source is unreliable and we | |
49 | * need to use something else. The currently published specification | |
50 | * versions (up to 4.0b) contain a mistake and wrongly claim '-1' | |
51 | * instead of '0' as the special value, see commit c35b82ef0294. | |
52 | * - ReferenceTime = | |
53 | * ((RDTSC() * ReferenceTscScale) >> 64) + ReferenceTscOffset | |
54 | * - READ ReferenceTscSequence again. In case its value has changed | |
55 | * since our first reading we need to discard ReferenceTime and repeat | |
56 | * the whole sequence as the hypervisor was updating the page in | |
57 | * between. | |
58 | */ | |
59 | do { | |
60 | sequence = READ_ONCE(tsc_pg->tsc_sequence); | |
61 | if (!sequence) | |
62 | return U64_MAX; | |
63 | /* | |
64 | * Make sure we read sequence before we read other values from | |
65 | * TSC page. | |
66 | */ | |
67 | smp_rmb(); | |
68 | ||
69 | scale = READ_ONCE(tsc_pg->tsc_scale); | |
70 | offset = READ_ONCE(tsc_pg->tsc_offset); | |
71 | *cur_tsc = hv_get_raw_timer(); | |
72 | ||
73 | /* | |
74 | * Make sure we read sequence after we read all other values | |
75 | * from TSC page. | |
76 | */ | |
77 | smp_rmb(); | |
78 | ||
79 | } while (READ_ONCE(tsc_pg->tsc_sequence) != sequence); | |
80 | ||
81 | return mul_u64_u64_shr(*cur_tsc, scale, 64) + offset; | |
82 | } | |
83 | ||
84 | static inline notrace u64 | |
85 | hv_read_tsc_page(const struct ms_hyperv_tsc_page *tsc_pg) | |
86 | { | |
87 | u64 cur_tsc; | |
88 | ||
89 | return hv_read_tsc_page_tsc(tsc_pg, &cur_tsc); | |
90 | } | |
91 | ||
3e2d9453 | 92 | #else /* CONFIG_HYPERV_TIMER */ |
dd2cb348 MK |
93 | static inline struct ms_hyperv_tsc_page *hv_get_tsc_page(void) |
94 | { | |
95 | return NULL; | |
96 | } | |
97 | ||
98 | static inline u64 hv_read_tsc_page_tsc(const struct ms_hyperv_tsc_page *tsc_pg, | |
99 | u64 *cur_tsc) | |
100 | { | |
101 | return U64_MAX; | |
102 | } | |
3e2d9453 | 103 | #endif /* CONFIG_HYPERV_TIMER */ |
dd2cb348 | 104 | |
fd1fea68 | 105 | #endif |