Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

x86/hyperv: Add Write/Read MSR registers via ghcb page

Hyperv provides GHCB protocol to write Synthetic Interrupt
Controller MSR registers in Isolation VM with AMD SEV SNP
and these registers are emulated by hypervisor directly.
Hyperv requires to write SINTx MSR registers twice. First
writes MSR via GHCB page to communicate with hypervisor
and then writes wrmsr instruction to talk with paravisor
which runs in VMPL0. Guest OS ID MSR also needs to be set
via GHCB page.

Reviewed-by: Michael Kelley <mikelley@microsoft.com>
Signed-off-by: Tianyu Lan <Tianyu.Lan@microsoft.com>
Link: https://lore.kernel.org/r/20211025122116.264793-7-ltykernel@gmail.com
Signed-off-by: Wei Liu <wei.liu@kernel.org>

authored by

Tianyu Lan and committed by
Wei Liu
faff4406 d4dccf35

+232 -59
+7 -29
arch/x86/hyperv/hv_init.c
··· 37 37 void *hv_hypercall_pg; 38 38 EXPORT_SYMBOL_GPL(hv_hypercall_pg); 39 39 40 - void __percpu **hv_ghcb_pg; 40 + union hv_ghcb __percpu **hv_ghcb_pg; 41 41 42 42 /* Storage to save the hypercall page temporarily for hibernation */ 43 43 static void *hv_hypercall_pg_saved; ··· 406 406 } 407 407 408 408 if (hv_isolation_type_snp()) { 409 - hv_ghcb_pg = alloc_percpu(void *); 409 + hv_ghcb_pg = alloc_percpu(union hv_ghcb *); 410 410 if (!hv_ghcb_pg) 411 411 goto free_vp_assist_page; 412 412 } ··· 423 423 */ 424 424 guest_id = generate_guest_id(0, LINUX_VERSION_CODE, 0); 425 425 wrmsrl(HV_X64_MSR_GUEST_OS_ID, guest_id); 426 + 427 + /* Hyper-V requires to write guest os id via ghcb in SNP IVM. */ 428 + hv_ghcb_msr_write(HV_X64_MSR_GUEST_OS_ID, guest_id); 426 429 427 430 hv_hypercall_pg = __vmalloc_node_range(PAGE_SIZE, 1, VMALLOC_START, 428 431 VMALLOC_END, GFP_KERNEL, PAGE_KERNEL_ROX, ··· 504 501 505 502 clean_guest_os_id: 506 503 wrmsrl(HV_X64_MSR_GUEST_OS_ID, 0); 504 + hv_ghcb_msr_write(HV_X64_MSR_GUEST_OS_ID, 0); 507 505 cpuhp_remove_state(cpuhp); 508 506 free_ghcb_page: 509 507 free_percpu(hv_ghcb_pg); ··· 526 522 527 523 /* Reset our OS id */ 528 524 wrmsrl(HV_X64_MSR_GUEST_OS_ID, 0); 525 + hv_ghcb_msr_write(HV_X64_MSR_GUEST_OS_ID, 0); 529 526 530 527 /* 531 528 * Reset hypercall page reference before reset the page, ··· 597 592 return hypercall_msr.enable; 598 593 } 599 594 EXPORT_SYMBOL_GPL(hv_is_hyperv_initialized); 600 - 601 - enum hv_isolation_type hv_get_isolation_type(void) 602 - { 603 - if (!(ms_hyperv.priv_high & HV_ISOLATION)) 604 - return HV_ISOLATION_TYPE_NONE; 605 - return FIELD_GET(HV_ISOLATION_TYPE, ms_hyperv.isolation_config_b); 606 - } 607 - EXPORT_SYMBOL_GPL(hv_get_isolation_type); 608 - 609 - bool hv_is_isolation_supported(void) 610 - { 611 - if (!cpu_feature_enabled(X86_FEATURE_HYPERVISOR)) 612 - return false; 613 - 614 - if (!hypervisor_is_type(X86_HYPER_MS_HYPERV)) 615 - return false; 616 - 617 - return hv_get_isolation_type() != HV_ISOLATION_TYPE_NONE; 618 - } 619 - 620 - DEFINE_STATIC_KEY_FALSE(isolation_type_snp); 621 - 622 - bool hv_isolation_type_snp(void) 623 - { 624 - return static_branch_unlikely(&isolation_type_snp); 625 - } 626 - EXPORT_SYMBOL_GPL(hv_isolation_type_snp);
+111
arch/x86/hyperv/ivm.c
··· 6 6 * Tianyu Lan <Tianyu.Lan@microsoft.com> 7 7 */ 8 8 9 + #include <linux/types.h> 10 + #include <linux/bitfield.h> 9 11 #include <linux/hyperv.h> 10 12 #include <linux/types.h> 11 13 #include <linux/bitfield.h> 12 14 #include <linux/slab.h> 15 + #include <asm/svm.h> 16 + #include <asm/sev.h> 13 17 #include <asm/io.h> 14 18 #include <asm/mshyperv.h> 19 + #include <asm/hypervisor.h> 20 + 21 + #ifdef CONFIG_AMD_MEM_ENCRYPT 22 + union hv_ghcb { 23 + struct ghcb ghcb; 24 + } __packed __aligned(HV_HYP_PAGE_SIZE); 25 + 26 + void hv_ghcb_msr_write(u64 msr, u64 value) 27 + { 28 + union hv_ghcb *hv_ghcb; 29 + void **ghcb_base; 30 + unsigned long flags; 31 + struct es_em_ctxt ctxt; 32 + 33 + if (!hv_ghcb_pg) 34 + return; 35 + 36 + WARN_ON(in_nmi()); 37 + 38 + local_irq_save(flags); 39 + ghcb_base = (void **)this_cpu_ptr(hv_ghcb_pg); 40 + hv_ghcb = (union hv_ghcb *)*ghcb_base; 41 + if (!hv_ghcb) { 42 + local_irq_restore(flags); 43 + return; 44 + } 45 + 46 + ghcb_set_rcx(&hv_ghcb->ghcb, msr); 47 + ghcb_set_rax(&hv_ghcb->ghcb, lower_32_bits(value)); 48 + ghcb_set_rdx(&hv_ghcb->ghcb, upper_32_bits(value)); 49 + 50 + if (sev_es_ghcb_hv_call(&hv_ghcb->ghcb, false, &ctxt, 51 + SVM_EXIT_MSR, 1, 0)) 52 + pr_warn("Fail to write msr via ghcb %llx.\n", msr); 53 + 54 + local_irq_restore(flags); 55 + } 56 + EXPORT_SYMBOL_GPL(hv_ghcb_msr_write); 57 + 58 + void hv_ghcb_msr_read(u64 msr, u64 *value) 59 + { 60 + union hv_ghcb *hv_ghcb; 61 + void **ghcb_base; 62 + unsigned long flags; 63 + struct es_em_ctxt ctxt; 64 + 65 + /* Check size of union hv_ghcb here. */ 66 + BUILD_BUG_ON(sizeof(union hv_ghcb) != HV_HYP_PAGE_SIZE); 67 + 68 + if (!hv_ghcb_pg) 69 + return; 70 + 71 + WARN_ON(in_nmi()); 72 + 73 + local_irq_save(flags); 74 + ghcb_base = (void **)this_cpu_ptr(hv_ghcb_pg); 75 + hv_ghcb = (union hv_ghcb *)*ghcb_base; 76 + if (!hv_ghcb) { 77 + local_irq_restore(flags); 78 + return; 79 + } 80 + 81 + ghcb_set_rcx(&hv_ghcb->ghcb, msr); 82 + if (sev_es_ghcb_hv_call(&hv_ghcb->ghcb, false, &ctxt, 83 + SVM_EXIT_MSR, 0, 0)) 84 + pr_warn("Fail to read msr via ghcb %llx.\n", msr); 85 + else 86 + *value = (u64)lower_32_bits(hv_ghcb->ghcb.save.rax) 87 + | ((u64)lower_32_bits(hv_ghcb->ghcb.save.rdx) << 32); 88 + local_irq_restore(flags); 89 + } 90 + EXPORT_SYMBOL_GPL(hv_ghcb_msr_read); 91 + #endif 92 + 93 + enum hv_isolation_type hv_get_isolation_type(void) 94 + { 95 + if (!(ms_hyperv.priv_high & HV_ISOLATION)) 96 + return HV_ISOLATION_TYPE_NONE; 97 + return FIELD_GET(HV_ISOLATION_TYPE, ms_hyperv.isolation_config_b); 98 + } 99 + EXPORT_SYMBOL_GPL(hv_get_isolation_type); 100 + 101 + /* 102 + * hv_is_isolation_supported - Check system runs in the Hyper-V 103 + * isolation VM. 104 + */ 105 + bool hv_is_isolation_supported(void) 106 + { 107 + if (!cpu_feature_enabled(X86_FEATURE_HYPERVISOR)) 108 + return false; 109 + 110 + if (!hypervisor_is_type(X86_HYPER_MS_HYPERV)) 111 + return false; 112 + 113 + return hv_get_isolation_type() != HV_ISOLATION_TYPE_NONE; 114 + } 115 + 116 + DEFINE_STATIC_KEY_FALSE(isolation_type_snp); 117 + 118 + /* 119 + * hv_isolation_type_snp - Check system runs in the AMD SEV-SNP based 120 + * isolation VM. 121 + */ 122 + bool hv_isolation_type_snp(void) 123 + { 124 + return static_branch_unlikely(&isolation_type_snp); 125 + } 15 126 16 127 /* 17 128 * hv_mark_gpa_visibility - Set pages visible to host via hvcall.
+49 -14
arch/x86/include/asm/mshyperv.h
··· 11 11 #include <asm/paravirt.h> 12 12 #include <asm/mshyperv.h> 13 13 14 + union hv_ghcb; 15 + 14 16 DECLARE_STATIC_KEY_FALSE(isolation_type_snp); 15 17 16 18 typedef int (*hyperv_fill_flush_list_func)( 17 19 struct hv_guest_mapping_flush_list *flush, 18 20 void *data); 19 - 20 - static inline void hv_set_register(unsigned int reg, u64 value) 21 - { 22 - wrmsrl(reg, value); 23 - } 24 - 25 - static inline u64 hv_get_register(unsigned int reg) 26 - { 27 - u64 value; 28 - 29 - rdmsrl(reg, value); 30 - return value; 31 - } 32 21 33 22 #define hv_get_raw_timer() rdtsc_ordered() 34 23 ··· 30 41 31 42 extern u64 hv_current_partition_id; 32 43 33 - extern void __percpu **hv_ghcb_pg; 44 + extern union hv_ghcb __percpu **hv_ghcb_pg; 34 45 35 46 int hv_call_deposit_pages(int node, u64 partition_id, u32 num_pages); 36 47 int hv_call_add_logical_proc(int node, u32 lp_index, u32 acpi_id); ··· 182 193 struct hv_interrupt_entry *entry); 183 194 int hv_unmap_ioapic_interrupt(int ioapic_id, struct hv_interrupt_entry *entry); 184 195 int hv_set_mem_host_visibility(unsigned long addr, int numpages, bool visible); 196 + 197 + #ifdef CONFIG_AMD_MEM_ENCRYPT 198 + void hv_ghcb_msr_write(u64 msr, u64 value); 199 + void hv_ghcb_msr_read(u64 msr, u64 *value); 200 + #else 201 + static inline void hv_ghcb_msr_write(u64 msr, u64 value) {} 202 + static inline void hv_ghcb_msr_read(u64 msr, u64 *value) {} 203 + #endif 204 + 205 + extern bool hv_isolation_type_snp(void); 206 + 207 + static inline bool hv_is_synic_reg(unsigned int reg) 208 + { 209 + if ((reg >= HV_REGISTER_SCONTROL) && 210 + (reg <= HV_REGISTER_SINT15)) 211 + return true; 212 + return false; 213 + } 214 + 215 + static inline u64 hv_get_register(unsigned int reg) 216 + { 217 + u64 value; 218 + 219 + if (hv_is_synic_reg(reg) && hv_isolation_type_snp()) 220 + hv_ghcb_msr_read(reg, &value); 221 + else 222 + rdmsrl(reg, value); 223 + return value; 224 + } 225 + 226 + static inline void hv_set_register(unsigned int reg, u64 value) 227 + { 228 + if (hv_is_synic_reg(reg) && hv_isolation_type_snp()) { 229 + hv_ghcb_msr_write(reg, value); 230 + 231 + /* Write proxy bit via wrmsl instruction */ 232 + if (reg >= HV_REGISTER_SINT0 && 233 + reg <= HV_REGISTER_SINT15) 234 + wrmsrl(reg, value | 1 << 20); 235 + } else { 236 + wrmsrl(reg, value); 237 + } 238 + } 239 + 185 240 #else /* CONFIG_HYPERV */ 186 241 static inline void hyperv_init(void) {} 187 242 static inline void hyperv_setup_mmu_ops(void) {} ··· 242 209 { 243 210 return -1; 244 211 } 212 + static inline void hv_set_register(unsigned int reg, u64 value) { } 213 + static inline u64 hv_get_register(unsigned int reg) { return 0; } 245 214 static inline int hv_set_mem_host_visibility(unsigned long addr, int numpages, 246 215 bool visible) 247 216 {
+58 -16
drivers/hv/hv.c
··· 8 8 */ 9 9 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 10 10 11 + #include <linux/io.h> 11 12 #include <linux/kernel.h> 12 13 #include <linux/mm.h> 13 14 #include <linux/slab.h> ··· 137 136 tasklet_init(&hv_cpu->msg_dpc, 138 137 vmbus_on_msg_dpc, (unsigned long) hv_cpu); 139 138 140 - hv_cpu->synic_message_page = 141 - (void *)get_zeroed_page(GFP_ATOMIC); 142 - if (hv_cpu->synic_message_page == NULL) { 143 - pr_err("Unable to allocate SYNIC message page\n"); 144 - goto err; 145 - } 139 + /* 140 + * Synic message and event pages are allocated by paravisor. 141 + * Skip these pages allocation here. 142 + */ 143 + if (!hv_isolation_type_snp()) { 144 + hv_cpu->synic_message_page = 145 + (void *)get_zeroed_page(GFP_ATOMIC); 146 + if (hv_cpu->synic_message_page == NULL) { 147 + pr_err("Unable to allocate SYNIC message page\n"); 148 + goto err; 149 + } 146 150 147 - hv_cpu->synic_event_page = (void *)get_zeroed_page(GFP_ATOMIC); 148 - if (hv_cpu->synic_event_page == NULL) { 149 - pr_err("Unable to allocate SYNIC event page\n"); 150 - goto err; 151 + hv_cpu->synic_event_page = 152 + (void *)get_zeroed_page(GFP_ATOMIC); 153 + if (hv_cpu->synic_event_page == NULL) { 154 + pr_err("Unable to allocate SYNIC event page\n"); 155 + goto err; 156 + } 151 157 } 152 158 153 159 hv_cpu->post_msg_page = (void *)get_zeroed_page(GFP_ATOMIC); ··· 209 201 /* Setup the Synic's message page */ 210 202 simp.as_uint64 = hv_get_register(HV_REGISTER_SIMP); 211 203 simp.simp_enabled = 1; 212 - simp.base_simp_gpa = virt_to_phys(hv_cpu->synic_message_page) 213 - >> HV_HYP_PAGE_SHIFT; 204 + 205 + if (hv_isolation_type_snp()) { 206 + hv_cpu->synic_message_page 207 + = memremap(simp.base_simp_gpa << HV_HYP_PAGE_SHIFT, 208 + HV_HYP_PAGE_SIZE, MEMREMAP_WB); 209 + if (!hv_cpu->synic_message_page) 210 + pr_err("Fail to map syinc message page.\n"); 211 + } else { 212 + simp.base_simp_gpa = virt_to_phys(hv_cpu->synic_message_page) 213 + >> HV_HYP_PAGE_SHIFT; 214 + } 214 215 215 216 hv_set_register(HV_REGISTER_SIMP, simp.as_uint64); 216 217 217 218 /* Setup the Synic's event page */ 218 219 siefp.as_uint64 = hv_get_register(HV_REGISTER_SIEFP); 219 220 siefp.siefp_enabled = 1; 220 - siefp.base_siefp_gpa = virt_to_phys(hv_cpu->synic_event_page) 221 - >> HV_HYP_PAGE_SHIFT; 221 + 222 + if (hv_isolation_type_snp()) { 223 + hv_cpu->synic_event_page = 224 + memremap(siefp.base_siefp_gpa << HV_HYP_PAGE_SHIFT, 225 + HV_HYP_PAGE_SIZE, MEMREMAP_WB); 226 + 227 + if (!hv_cpu->synic_event_page) 228 + pr_err("Fail to map syinc event page.\n"); 229 + } else { 230 + siefp.base_siefp_gpa = virt_to_phys(hv_cpu->synic_event_page) 231 + >> HV_HYP_PAGE_SHIFT; 232 + } 222 233 223 234 hv_set_register(HV_REGISTER_SIEFP, siefp.as_uint64); 224 235 ··· 284 257 */ 285 258 void hv_synic_disable_regs(unsigned int cpu) 286 259 { 260 + struct hv_per_cpu_context *hv_cpu 261 + = per_cpu_ptr(hv_context.cpu_context, cpu); 287 262 union hv_synic_sint shared_sint; 288 263 union hv_synic_simp simp; 289 264 union hv_synic_siefp siefp; ··· 302 273 shared_sint.as_uint64); 303 274 304 275 simp.as_uint64 = hv_get_register(HV_REGISTER_SIMP); 276 + /* 277 + * In Isolation VM, sim and sief pages are allocated by 278 + * paravisor. These pages also will be used by kdump 279 + * kernel. So just reset enable bit here and keep page 280 + * addresses. 281 + */ 305 282 simp.simp_enabled = 0; 306 - simp.base_simp_gpa = 0; 283 + if (hv_isolation_type_snp()) 284 + memunmap(hv_cpu->synic_message_page); 285 + else 286 + simp.base_simp_gpa = 0; 307 287 308 288 hv_set_register(HV_REGISTER_SIMP, simp.as_uint64); 309 289 310 290 siefp.as_uint64 = hv_get_register(HV_REGISTER_SIEFP); 311 291 siefp.siefp_enabled = 0; 312 - siefp.base_siefp_gpa = 0; 292 + 293 + if (hv_isolation_type_snp()) 294 + memunmap(hv_cpu->synic_event_page); 295 + else 296 + siefp.base_siefp_gpa = 0; 313 297 314 298 hv_set_register(HV_REGISTER_SIEFP, siefp.as_uint64); 315 299
+6
drivers/hv/hv_common.c
··· 249 249 } 250 250 EXPORT_SYMBOL_GPL(hv_is_isolation_supported); 251 251 252 + bool __weak hv_isolation_type_snp(void) 253 + { 254 + return false; 255 + } 256 + EXPORT_SYMBOL_GPL(hv_isolation_type_snp); 257 + 252 258 void __weak hv_setup_vmbus_handler(void (*handler)(void)) 253 259 { 254 260 }
+1
include/asm-generic/mshyperv.h
··· 54 54 55 55 extern u64 hv_do_hypercall(u64 control, void *inputaddr, void *outputaddr); 56 56 extern u64 hv_do_fast_hypercall8(u16 control, u64 input8); 57 + extern bool hv_isolation_type_snp(void); 57 58 58 59 /* Helper functions that provide a consistent pattern for checking Hyper-V hypercall status. */ 59 60 static inline int hv_result(u64 status)