1 /******************************************************************************
2 * arch/x86/guest/hyperv/hyperv.c
3 *
4 * Support for detecting and running under Hyper-V.
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 * You should have received a copy of the GNU General Public License
17 * along with this program; If not, see <http://www.gnu.org/licenses/>.
18 *
19 * Copyright (c) 2019 Microsoft.
20 */
21 #include <xen/init.h>
22 #include <xen/version.h>
23
24 #include <asm/fixmap.h>
25 #include <asm/guest.h>
26 #include <asm/guest/hyperv-tlfs.h>
27 #include <asm/processor.h>
28
29 #include "private.h"
30
31 struct ms_hyperv_info __read_mostly ms_hyperv;
32 DEFINE_PER_CPU_READ_MOSTLY(void *, hv_input_page);
33 DEFINE_PER_CPU_READ_MOSTLY(void *, hv_vp_assist);
34 DEFINE_PER_CPU_READ_MOSTLY(unsigned int, hv_vp_index);
35
36 unsigned int __read_mostly hv_max_vp_index;
37 static bool __read_mostly hcall_page_ready;
38
generate_guest_id(void)39 static uint64_t generate_guest_id(void)
40 {
41 union hv_guest_os_id id = {};
42
43 id.vendor = HV_XEN_VENDOR_ID;
44 id.major = xen_major_version();
45 id.minor = xen_minor_version();
46
47 return id.raw;
48 }
49
50 static const struct hypervisor_ops ops;
51
hyperv_probe(void)52 const struct hypervisor_ops *__init hyperv_probe(void)
53 {
54 uint32_t eax, ebx, ecx, edx;
55 uint64_t required_msrs = HV_X64_MSR_HYPERCALL_AVAILABLE |
56 HV_X64_MSR_VP_INDEX_AVAILABLE;
57
58 cpuid(0x40000000, &eax, &ebx, &ecx, &edx);
59 if ( !((ebx == 0x7263694d) && /* "Micr" */
60 (ecx == 0x666f736f) && /* "osof" */
61 (edx == 0x76482074)) ) /* "t Hv" */
62 return NULL;
63
64 cpuid(0x40000001, &eax, &ebx, &ecx, &edx);
65 if ( eax != 0x31237648 ) /* Hv#1 */
66 return NULL;
67
68 /* Extract more information from Hyper-V */
69 cpuid(HYPERV_CPUID_FEATURES, &eax, &ebx, &ecx, &edx);
70 ms_hyperv.features = eax;
71 ms_hyperv.misc_features = edx;
72
73 ms_hyperv.hints = cpuid_eax(HYPERV_CPUID_ENLIGHTMENT_INFO);
74
75 if ( ms_hyperv.hints & HV_X64_ENLIGHTENED_VMCS_RECOMMENDED )
76 ms_hyperv.nested_features = cpuid_eax(HYPERV_CPUID_NESTED_FEATURES);
77
78 cpuid(HYPERV_CPUID_IMPLEMENT_LIMITS, &eax, &ebx, &ecx, &edx);
79 ms_hyperv.max_vp_index = eax;
80 ms_hyperv.max_lp_index = ebx;
81
82 if ( (ms_hyperv.features & required_msrs) != required_msrs )
83 {
84 /*
85 * Oops, required MSRs are not available. Treat this as
86 * "Hyper-V is not available".
87 */
88 memset(&ms_hyperv, 0, sizeof(ms_hyperv));
89 return NULL;
90 }
91
92 return &ops;
93 }
94
setup_hypercall_page(void)95 static void __init setup_hypercall_page(void)
96 {
97 union hv_x64_msr_hypercall_contents hypercall_msr;
98 union hv_guest_os_id guest_id;
99 unsigned long mfn;
100
101 BUILD_BUG_ON(HV_HYP_PAGE_SHIFT != PAGE_SHIFT);
102
103 rdmsrl(HV_X64_MSR_GUEST_OS_ID, guest_id.raw);
104 if ( !guest_id.raw )
105 {
106 guest_id.raw = generate_guest_id();
107 wrmsrl(HV_X64_MSR_GUEST_OS_ID, guest_id.raw);
108 }
109
110 rdmsrl(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64);
111 if ( !hypercall_msr.enable )
112 {
113 mfn = HV_HCALL_MFN;
114 hypercall_msr.enable = 1;
115 hypercall_msr.guest_physical_address = mfn;
116 wrmsrl(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64);
117 }
118 else
119 mfn = hypercall_msr.guest_physical_address;
120
121 rdmsrl(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64);
122 BUG_ON(!hypercall_msr.enable);
123
124 set_fixmap_x(FIX_X_HYPERV_HCALL, mfn << PAGE_SHIFT);
125
126 hcall_page_ready = true;
127 }
128
setup_hypercall_pcpu_arg(void)129 static int setup_hypercall_pcpu_arg(void)
130 {
131 uint64_t vp_index_msr;
132
133 if ( this_cpu(hv_input_page) )
134 return 0;
135
136 this_cpu(hv_input_page) = alloc_xenheap_page();
137 if ( !this_cpu(hv_input_page) )
138 {
139 printk("CPU%u: Failed to allocate hypercall input page\n",
140 smp_processor_id());
141 return -ENOMEM;
142 }
143
144 rdmsrl(HV_X64_MSR_VP_INDEX, vp_index_msr);
145 this_cpu(hv_vp_index) = vp_index_msr;
146
147 if ( vp_index_msr > hv_max_vp_index )
148 hv_max_vp_index = vp_index_msr;
149
150 return 0;
151 }
152
setup_vp_assist(void)153 static int setup_vp_assist(void)
154 {
155 union hv_vp_assist_page_msr msr;
156
157 if ( !this_cpu(hv_vp_assist) )
158 {
159 this_cpu(hv_vp_assist) = alloc_xenheap_page();
160 if ( !this_cpu(hv_vp_assist) )
161 {
162 printk("CPU%u: Failed to allocate vp_assist page\n",
163 smp_processor_id());
164 return -ENOMEM;
165 }
166
167 clear_page(this_cpu(hv_vp_assist));
168 }
169
170 rdmsrl(HV_X64_MSR_VP_ASSIST_PAGE, msr.raw);
171 msr.pfn = virt_to_mfn(this_cpu(hv_vp_assist));
172 msr.enabled = 1;
173 wrmsrl(HV_X64_MSR_VP_ASSIST_PAGE, msr.raw);
174
175 return 0;
176 }
177
setup(void)178 static void __init setup(void)
179 {
180 ASM_CONSTANT(HV_HCALL_PAGE, __fix_x_to_virt(FIX_X_HYPERV_HCALL));
181
182 setup_hypercall_page();
183
184 if ( setup_hypercall_pcpu_arg() )
185 panic("Hyper-V hypercall percpu arg setup failed\n");
186
187 if ( setup_vp_assist() )
188 panic("VP assist page setup failed\n");
189 }
190
ap_setup(void)191 static int ap_setup(void)
192 {
193 int rc;
194
195 rc = setup_hypercall_pcpu_arg();
196 if ( rc )
197 return rc;
198
199 return setup_vp_assist();
200 }
201
e820_fixup(struct e820map * e820)202 static void __init e820_fixup(struct e820map *e820)
203 {
204 uint64_t s = HV_HCALL_MFN << PAGE_SHIFT;
205
206 if ( !e820_add_range(e820, s, s + PAGE_SIZE, E820_RESERVED) )
207 panic("Unable to reserve Hyper-V hypercall range\n");
208 }
209
flush_tlb(const cpumask_t * mask,const void * va,unsigned int flags)210 static int flush_tlb(const cpumask_t *mask, const void *va,
211 unsigned int flags)
212 {
213 if ( !(ms_hyperv.hints & HV_X64_REMOTE_TLB_FLUSH_RECOMMENDED) )
214 return -EOPNOTSUPP;
215
216 if ( !hcall_page_ready || !this_cpu(hv_input_page) )
217 return -ENXIO;
218
219 return hyperv_flush_tlb(mask, va, flags);
220 }
221
222 static const struct hypervisor_ops __initconstrel ops = {
223 .name = "Hyper-V",
224 .setup = setup,
225 .ap_setup = ap_setup,
226 .e820_fixup = e820_fixup,
227 .flush_tlb = flush_tlb,
228 };
229
230 /*
231 * Local variables:
232 * mode: C
233 * c-file-style: "BSD"
234 * c-basic-offset: 4
235 * tab-width: 4
236 * indent-tabs-mode: nil
237 * End:
238 */
239