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