Message ID | 20200428151725.31091-24-joro@8bytes.org (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | x86: SEV-ES Guest Support | expand |
On Tue, Apr 28, 2020 at 05:16:33PM +0200, Joerg Roedel wrote: > @@ -63,3 +175,45 @@ void __init do_vc_no_ghcb(struct pt_regs *regs, unsigned long exit_code) > while (true) > asm volatile("hlt\n"); > } > + > +static enum es_result vc_insn_string_read(struct es_em_ctxt *ctxt, > + void *src, char *buf, > + unsigned int data_size, > + unsigned int count, > + bool backwards) > +{ > + int i, b = backwards ? -1 : 1; > + enum es_result ret = ES_OK; > + > + for (i = 0; i < count; i++) { > + void *s = src + (i * data_size * b); > + char *d = buf + (i * data_size); From a previous review: Where are we checking whether that count is not exceeding @buf or similar discrepancies? Ditto below. > + > + ret = vc_read_mem(ctxt, s, d, data_size); > + if (ret != ES_OK) > + break; > + } > + > + return ret; > +} > + > +static enum es_result vc_insn_string_write(struct es_em_ctxt *ctxt, > + void *dst, char *buf, > + unsigned int data_size, > + unsigned int count, > + bool backwards) > +{ > + int i, s = backwards ? -1 : 1; > + enum es_result ret = ES_OK; > + > + for (i = 0; i < count; i++) { > + void *d = dst + (i * data_size * s); > + char *b = buf + (i * data_size); > + > + ret = vc_write_mem(ctxt, d, b, data_size); > + if (ret != ES_OK) > + break; > + } > + > + return ret; > +} > -- > 2.17.1 >
On Tue, Apr 28, 2020 at 05:16:33PM +0200, Joerg Roedel wrote: > From: Joerg Roedel <jroedel@suse.de> > > Install an exception handler for #VC exception that uses a GHCB. Also > add the infrastructure for handling different exit-codes by decoding > the instruction that caused the exception and error handling. > > Signed-off-by: Joerg Roedel <jroedel@suse.de> > --- > arch/x86/Kconfig | 1 + > arch/x86/boot/compressed/Makefile | 3 + > arch/x86/boot/compressed/idt_64.c | 4 + > arch/x86/boot/compressed/idt_handlers_64.S | 3 +- > arch/x86/boot/compressed/misc.c | 7 + > arch/x86/boot/compressed/misc.h | 7 + > arch/x86/boot/compressed/sev-es.c | 110 +++++++++++++++ > arch/x86/include/asm/sev-es.h | 39 ++++++ > arch/x86/include/uapi/asm/svm.h | 1 + > arch/x86/kernel/sev-es-shared.c | 154 +++++++++++++++++++++ > 10 files changed, 328 insertions(+), 1 deletion(-) > > diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig > index 1197b5596d5a..2ba5f74f186d 100644 > --- a/arch/x86/Kconfig > +++ b/arch/x86/Kconfig > @@ -1523,6 +1523,7 @@ config AMD_MEM_ENCRYPT > select DYNAMIC_PHYSICAL_MASK > select ARCH_USE_MEMREMAP_PROT > select ARCH_HAS_FORCE_DMA_UNENCRYPTED > + select INSTRUCTION_DECODER > ---help--- > Say yes to enable support for the encryption of system memory. > This requires an AMD processor that supports Secure Memory > diff --git a/arch/x86/boot/compressed/Makefile b/arch/x86/boot/compressed/Makefile > index a7847a1ef63a..8372b85c9c0e 100644 > --- a/arch/x86/boot/compressed/Makefile > +++ b/arch/x86/boot/compressed/Makefile > @@ -41,6 +41,9 @@ KBUILD_CFLAGS += -Wno-pointer-sign > KBUILD_CFLAGS += $(call cc-option,-fmacro-prefix-map=$(srctree)/=) > KBUILD_CFLAGS += -fno-asynchronous-unwind-tables > > +# sev-es.c inludes generated $(objtree)/arch/x86/lib/inat-tables.c "includes" > +CFLAGS_sev-es.o += -I$(objtree)/arch/x86/lib/ Does it? I see #include "../../lib/inat.c" #include "../../lib/insn.c" only and with the above CFLAGS-line removed, it builds still. Leftover from earlier? > + > KBUILD_AFLAGS := $(KBUILD_CFLAGS) -D__ASSEMBLY__ > GCOV_PROFILE := n > UBSAN_SANITIZE :=n > diff --git a/arch/x86/boot/compressed/idt_64.c b/arch/x86/boot/compressed/idt_64.c > index f8295d68b3e1..44d20c4f47c9 100644 > --- a/arch/x86/boot/compressed/idt_64.c > +++ b/arch/x86/boot/compressed/idt_64.c > @@ -45,5 +45,9 @@ void load_stage2_idt(void) > > set_idt_entry(X86_TRAP_PF, boot_page_fault); > > +#ifdef CONFIG_AMD_MEM_ENCRYPT > + set_idt_entry(X86_TRAP_VC, boot_stage2_vc); > +#endif if IS_ENABLED()... ... > +static enum es_result vc_decode_insn(struct es_em_ctxt *ctxt) > +{ > + char buffer[MAX_INSN_SIZE]; > + enum es_result ret; > + > + memcpy(buffer, (unsigned char *)ctxt->regs->ip, MAX_INSN_SIZE); > + > + insn_init(&ctxt->insn, buffer, MAX_INSN_SIZE, 1); > + insn_get_length(&ctxt->insn); > + > + ret = ctxt->insn.immediate.got ? ES_OK : ES_DECODE_FAILED; Why are we checking whether the immediate? insn_get_length() sets insn->length unconditionally while insn_get_immediate() can error out and not set ->got... ? > + > + return ret; > +} ... > +static bool sev_es_setup_ghcb(void) > +{ > + if (!sev_es_negotiate_protocol()) > + sev_es_terminate(GHCB_SEV_ES_REASON_PROTOCOL_UNSUPPORTED); > + > + if (set_page_decrypted((unsigned long)&boot_ghcb_page)) > + return false; > + > + /* Page is now mapped decrypted, clear it */ > + memset(&boot_ghcb_page, 0, sizeof(boot_ghcb_page)); > + > + boot_ghcb = &boot_ghcb_page; > + > + /* Initialize lookup tables for the instruction decoder */ > + inat_init_tables(); Yeah, that call doesn't logically belong in this function AFAICT as this function should setup the GHCB only. You can move it to the caller. > + > + return true; > +} > + > +void sev_es_shutdown_ghcb(void) > +{ > + if (!boot_ghcb) > + return; > + > + /* > + * GHCB Page must be flushed from the cache and mapped encrypted again. > + * Otherwise the running kernel will see strange cache effects when > + * trying to use that page. > + */ > + if (set_page_encrypted((unsigned long)&boot_ghcb_page)) > + error("Can't map GHCB page encrypted"); Is that error() call enough? Shouldn't we BUG_ON() here or mark that page Reserved or so, so that nothing uses it during the system lifetime and thus avoid the strange cache effects? ... > +static enum es_result sev_es_ghcb_hv_call(struct ghcb *ghcb, > + struct es_em_ctxt *ctxt, > + u64 exit_code, u64 exit_info_1, > + u64 exit_info_2) > +{ > + enum es_result ret; > + > + /* Fill in protocol and format specifiers */ > + ghcb->protocol_version = GHCB_PROTOCOL_MAX; > + ghcb->ghcb_usage = GHCB_DEFAULT_USAGE; > + > + ghcb_set_sw_exit_code(ghcb, exit_code); > + ghcb_set_sw_exit_info_1(ghcb, exit_info_1); > + ghcb_set_sw_exit_info_2(ghcb, exit_info_2); > + > + sev_es_wr_ghcb_msr(__pa(ghcb)); > + VMGEXIT(); > + > + if ((ghcb->save.sw_exit_info_1 & 0xffffffff) == 1) { ^^^^^^^^^^^ (1UL << 32) - 1 I guess.
On Tue, May 12, 2020 at 08:11:57PM +0200, Borislav Petkov wrote: > > +# sev-es.c inludes generated $(objtree)/arch/x86/lib/inat-tables.c > > "includes" > > > +CFLAGS_sev-es.o += -I$(objtree)/arch/x86/lib/ > > Does it? > > I see > > #include "../../lib/inat.c" > #include "../../lib/insn.c" > > only and with the above CFLAGS-line removed, it builds still. > > Leftover from earlier? No, this is a recent addition, otherwise this breaks out-of-tree builds (make O=/some/path ...) because inat-tables.c (included from inat.c) is generated during build and ends up in the $(objtree). > > + insn_init(&ctxt->insn, buffer, MAX_INSN_SIZE, 1); > > + insn_get_length(&ctxt->insn); > > + > > + ret = ctxt->insn.immediate.got ? ES_OK : ES_DECODE_FAILED; > > Why are we checking whether the immediate? insn_get_length() sets > insn->length unconditionally while insn_get_immediate() can error out > and not set ->got... ? Because the immediate is the last part of the instruction which is decoded (even if there is no immediate). The .got field is set when either the immediate was decoded successfully or, in case the instruction has no immediate, when the rest of the instruction was decoded successfully. So testing immediate.got is the indicator whether decoding was successful. > > > + > > + return ret; > > +} > > ... > > > +static bool sev_es_setup_ghcb(void) > > +{ > > + if (!sev_es_negotiate_protocol()) > > + sev_es_terminate(GHCB_SEV_ES_REASON_PROTOCOL_UNSUPPORTED); > > + > > + if (set_page_decrypted((unsigned long)&boot_ghcb_page)) > > + return false; > > + > > + /* Page is now mapped decrypted, clear it */ > > + memset(&boot_ghcb_page, 0, sizeof(boot_ghcb_page)); > > + > > + boot_ghcb = &boot_ghcb_page; > > + > > + /* Initialize lookup tables for the instruction decoder */ > > + inat_init_tables(); > > Yeah, that call doesn't logically belong in this function AFAICT as this > function should setup the GHCB only. You can move it to the caller. Probably better rename the function, it also does the sev-es protocol version negotiation and all other related setup tasks. Maybe sev_es_setup() is a better name? > > + if (set_page_encrypted((unsigned long)&boot_ghcb_page)) > > + error("Can't map GHCB page encrypted"); > > Is that error() call enough? > > Shouldn't we BUG_ON() here or mark that page Reserved or so, so that > nothing uses it during the system lifetime and thus avoid the strange > cache effects? If the above call fails its the end of the systems lifetime, because we can't continue to boot an SEV-ES guest when we have no GHCB. BUG_ON() and friends are also not available in the pre-decompression boot stage. > > ... > > > +static enum es_result sev_es_ghcb_hv_call(struct ghcb *ghcb, > > + struct es_em_ctxt *ctxt, > > + u64 exit_code, u64 exit_info_1, > > + u64 exit_info_2) > > +{ > > + enum es_result ret; > > + > > + /* Fill in protocol and format specifiers */ > > + ghcb->protocol_version = GHCB_PROTOCOL_MAX; > > + ghcb->ghcb_usage = GHCB_DEFAULT_USAGE; > > + > > + ghcb_set_sw_exit_code(ghcb, exit_code); > > + ghcb_set_sw_exit_info_1(ghcb, exit_info_1); > > + ghcb_set_sw_exit_info_2(ghcb, exit_info_2); > > + > > + sev_es_wr_ghcb_msr(__pa(ghcb)); > > + VMGEXIT(); > > + > > + if ((ghcb->save.sw_exit_info_1 & 0xffffffff) == 1) { > ^^^^^^^^^^^ > > (1UL << 32) - 1 > > I guess. Or lower_32_bits(), probably. I'll change it. Thanks, Joerg
On Tue, May 12, 2020 at 11:08:12PM +0200, Joerg Roedel wrote: > No, this is a recent addition, otherwise this breaks out-of-tree builds > (make O=/some/path ...) because inat-tables.c (included from inat.c) is > generated during build and ends up in the $(objtree). Please add a blurb about this above it otherwise no one would have a clue why it is there. > Because the immediate is the last part of the instruction which is > decoded (even if there is no immediate). The .got field is set when > either the immediate was decoded successfully or, in case the > instruction has no immediate, when the rest of the instruction was > decoded successfully. So testing immediate.got is the indicator whether > decoding was successful. Hm, whether the immediate was parsed correctly or it wasn't there and using that as the sign whether the instruction was decoded successfully sounds kinda arbitrary. @Masami: shouldn't that insn_get_length() thing return a value to denote whether the decode was successful or not instead of testing arbitrary fields? Pasting for you the code sequence again: ... insn_get_length(&ctxt->insn); ret = ctxt->insn.immediate.got ? ES_OK : ES_DECODE_FAILED; ... I wonder if one would be able to do instead: if (insn_get_length(&ctxt->insn)) return ES_OK; return ES_DECODE_FAILED; to have it straight-forward... > Probably better rename the function, it also does the sev-es protocol > version negotiation and all other related setup tasks. Maybe > sev_es_setup() is a better name? Sure. > If the above call fails its the end of the systems lifetime, because we > can't continue to boot an SEV-ES guest when we have no GHCB. > > BUG_ON() and friends are also not available in the pre-decompression > boot stage. Oh ok, error() does hlt the system. Thx.
On Mon, May 11, 2020 at 10:07:09PM +0200, Borislav Petkov wrote: > On Tue, Apr 28, 2020 at 05:16:33PM +0200, Joerg Roedel wrote: > > @@ -63,3 +175,45 @@ void __init do_vc_no_ghcb(struct pt_regs *regs, unsigned long exit_code) > > while (true) > > asm volatile("hlt\n"); > > } > > + > > +static enum es_result vc_insn_string_read(struct es_em_ctxt *ctxt, > > + void *src, char *buf, > > + unsigned int data_size, > > + unsigned int count, > > + bool backwards) > > +{ > > + int i, b = backwards ? -1 : 1; > > + enum es_result ret = ES_OK; > > + > > + for (i = 0; i < count; i++) { > > + void *s = src + (i * data_size * b); > > + char *d = buf + (i * data_size); > > >From a previous review: > > Where are we checking whether that count is not exceeding @buf or > similar discrepancies? These two functions are only called from vc_handle_ioio() and buf always points to ghcb->shared_buffer. In general, the caller has to make sure that sizeof(*buf) is at least data_size*count, and handle_ioio() calculates count based on the size of *buf. Joerg
diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index 1197b5596d5a..2ba5f74f186d 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -1523,6 +1523,7 @@ config AMD_MEM_ENCRYPT select DYNAMIC_PHYSICAL_MASK select ARCH_USE_MEMREMAP_PROT select ARCH_HAS_FORCE_DMA_UNENCRYPTED + select INSTRUCTION_DECODER ---help--- Say yes to enable support for the encryption of system memory. This requires an AMD processor that supports Secure Memory diff --git a/arch/x86/boot/compressed/Makefile b/arch/x86/boot/compressed/Makefile index a7847a1ef63a..8372b85c9c0e 100644 --- a/arch/x86/boot/compressed/Makefile +++ b/arch/x86/boot/compressed/Makefile @@ -41,6 +41,9 @@ KBUILD_CFLAGS += -Wno-pointer-sign KBUILD_CFLAGS += $(call cc-option,-fmacro-prefix-map=$(srctree)/=) KBUILD_CFLAGS += -fno-asynchronous-unwind-tables +# sev-es.c inludes generated $(objtree)/arch/x86/lib/inat-tables.c +CFLAGS_sev-es.o += -I$(objtree)/arch/x86/lib/ + KBUILD_AFLAGS := $(KBUILD_CFLAGS) -D__ASSEMBLY__ GCOV_PROFILE := n UBSAN_SANITIZE :=n diff --git a/arch/x86/boot/compressed/idt_64.c b/arch/x86/boot/compressed/idt_64.c index f8295d68b3e1..44d20c4f47c9 100644 --- a/arch/x86/boot/compressed/idt_64.c +++ b/arch/x86/boot/compressed/idt_64.c @@ -45,5 +45,9 @@ void load_stage2_idt(void) set_idt_entry(X86_TRAP_PF, boot_page_fault); +#ifdef CONFIG_AMD_MEM_ENCRYPT + set_idt_entry(X86_TRAP_VC, boot_stage2_vc); +#endif + load_boot_idt(&boot_idt_desc); } diff --git a/arch/x86/boot/compressed/idt_handlers_64.S b/arch/x86/boot/compressed/idt_handlers_64.S index 8473bf88e64e..bd058aa21e4f 100644 --- a/arch/x86/boot/compressed/idt_handlers_64.S +++ b/arch/x86/boot/compressed/idt_handlers_64.S @@ -71,5 +71,6 @@ SYM_FUNC_END(\name) EXCEPTION_HANDLER boot_page_fault do_boot_page_fault error_code=1 #ifdef CONFIG_AMD_MEM_ENCRYPT -EXCEPTION_HANDLER boot_stage1_vc do_vc_no_ghcb error_code=1 +EXCEPTION_HANDLER boot_stage1_vc do_vc_no_ghcb error_code=1 +EXCEPTION_HANDLER boot_stage2_vc do_boot_stage2_vc error_code=1 #endif diff --git a/arch/x86/boot/compressed/misc.c b/arch/x86/boot/compressed/misc.c index 9652d5c2afda..dba49e75095a 100644 --- a/arch/x86/boot/compressed/misc.c +++ b/arch/x86/boot/compressed/misc.c @@ -441,6 +441,13 @@ asmlinkage __visible void *extract_kernel(void *rmode, memptr heap, parse_elf(output); handle_relocations(output, output_len, virt_addr); debug_putstr("done.\nBooting the kernel.\n"); + + /* + * Flush GHCB from cache and map it encrypted again when running as + * SEV-ES guest. + */ + sev_es_shutdown_ghcb(); + return output; } diff --git a/arch/x86/boot/compressed/misc.h b/arch/x86/boot/compressed/misc.h index 5e569e8a7d75..4d37a28370ed 100644 --- a/arch/x86/boot/compressed/misc.h +++ b/arch/x86/boot/compressed/misc.h @@ -115,6 +115,12 @@ static inline void console_init(void) void set_sev_encryption_mask(void); +#ifdef CONFIG_AMD_MEM_ENCRYPT +void sev_es_shutdown_ghcb(void); +#else +static inline void sev_es_shutdown_ghcb(void) { } +#endif + /* acpi.c */ #ifdef CONFIG_ACPI acpi_physical_address get_rsdp_addr(void); @@ -144,5 +150,6 @@ extern struct desc_ptr boot_idt_desc; /* IDT Entry Points */ void boot_page_fault(void); void boot_stage1_vc(void); +void boot_stage2_vc(void); #endif /* BOOT_COMPRESSED_MISC_H */ diff --git a/arch/x86/boot/compressed/sev-es.c b/arch/x86/boot/compressed/sev-es.c index bb91cbb5920e..940d72571fc9 100644 --- a/arch/x86/boot/compressed/sev-es.c +++ b/arch/x86/boot/compressed/sev-es.c @@ -13,10 +13,16 @@ #include "misc.h" #include <asm/sev-es.h> +#include <asm/trap_defs.h> #include <asm/msr-index.h> #include <asm/ptrace.h> #include <asm/svm.h> +#include "error.h" + +struct ghcb boot_ghcb_page __aligned(PAGE_SIZE); +struct ghcb *boot_ghcb; + static inline u64 sev_es_rd_ghcb_msr(void) { unsigned long low, high; @@ -38,8 +44,112 @@ static inline void sev_es_wr_ghcb_msr(u64 val) "a"(low), "d" (high) : "memory"); } +static enum es_result vc_decode_insn(struct es_em_ctxt *ctxt) +{ + char buffer[MAX_INSN_SIZE]; + enum es_result ret; + + memcpy(buffer, (unsigned char *)ctxt->regs->ip, MAX_INSN_SIZE); + + insn_init(&ctxt->insn, buffer, MAX_INSN_SIZE, 1); + insn_get_length(&ctxt->insn); + + ret = ctxt->insn.immediate.got ? ES_OK : ES_DECODE_FAILED; + + return ret; +} + +static enum es_result vc_write_mem(struct es_em_ctxt *ctxt, + void *dst, char *buf, size_t size) +{ + memcpy(dst, buf, size); + + return ES_OK; +} + +static enum es_result vc_read_mem(struct es_em_ctxt *ctxt, + void *src, char *buf, size_t size) +{ + memcpy(buf, src, size); + + return ES_OK; +} + #undef __init +#undef __pa #define __init +#define __pa(x) ((unsigned long)(x)) + +#define __BOOT_COMPRESSED + +/* Basic instruction decoding support needed */ +#include "../../lib/inat.c" +#include "../../lib/insn.c" /* Include code for early handlers */ #include "../../kernel/sev-es-shared.c" + +static bool sev_es_setup_ghcb(void) +{ + if (!sev_es_negotiate_protocol()) + sev_es_terminate(GHCB_SEV_ES_REASON_PROTOCOL_UNSUPPORTED); + + if (set_page_decrypted((unsigned long)&boot_ghcb_page)) + return false; + + /* Page is now mapped decrypted, clear it */ + memset(&boot_ghcb_page, 0, sizeof(boot_ghcb_page)); + + boot_ghcb = &boot_ghcb_page; + + /* Initialize lookup tables for the instruction decoder */ + inat_init_tables(); + + return true; +} + +void sev_es_shutdown_ghcb(void) +{ + if (!boot_ghcb) + return; + + /* + * GHCB Page must be flushed from the cache and mapped encrypted again. + * Otherwise the running kernel will see strange cache effects when + * trying to use that page. + */ + if (set_page_encrypted((unsigned long)&boot_ghcb_page)) + error("Can't map GHCB page encrypted"); +} + +void do_boot_stage2_vc(struct pt_regs *regs, unsigned long exit_code) +{ + struct es_em_ctxt ctxt; + enum es_result result; + + if (!boot_ghcb && !sev_es_setup_ghcb()) + sev_es_terminate(GHCB_SEV_ES_REASON_GENERAL_REQUEST); + + vc_ghcb_invalidate(boot_ghcb); + result = vc_init_em_ctxt(&ctxt, regs, exit_code); + if (result != ES_OK) + goto finish; + + switch (exit_code) { + default: + result = ES_UNSUPPORTED; + break; + } + +finish: + if (result == ES_OK) { + vc_finish_insn(&ctxt); + } else if (result != ES_RETRY) { + /* + * For now, just halt the machine. That makes debugging easier, + * later we just call sev_es_terminate() here. + */ + while (true) + asm volatile("hlt\n"); + } +} diff --git a/arch/x86/include/asm/sev-es.h b/arch/x86/include/asm/sev-es.h index 5d49a8a429d3..7c0807b84546 100644 --- a/arch/x86/include/asm/sev-es.h +++ b/arch/x86/include/asm/sev-es.h @@ -9,7 +9,14 @@ #define __ASM_ENCRYPTED_STATE_H #include <linux/types.h> +#include <asm/insn.h> +#define GHCB_SEV_INFO 0x001UL +#define GHCB_SEV_INFO_REQ 0x002UL +#define GHCB_INFO(v) ((v) & 0xfffUL) +#define GHCB_PROTO_MAX(v) (((v) >> 48) & 0xffffUL) +#define GHCB_PROTO_MIN(v) (((v) >> 32) & 0xffffUL) +#define GHCB_PROTO_OUR 0x0001UL #define GHCB_SEV_CPUID_REQ 0x004UL #define GHCB_CPUID_REQ_EAX 0 #define GHCB_CPUID_REQ_EBX 1 @@ -19,12 +26,44 @@ (((unsigned long)reg & 3) << 30) | \ (((unsigned long)fn) << 32)) +#define GHCB_PROTOCOL_MAX 0x0001UL +#define GHCB_DEFAULT_USAGE 0x0000UL + #define GHCB_SEV_CPUID_RESP 0x005UL #define GHCB_SEV_TERMINATE 0x100UL +#define GHCB_SEV_TERMINATE_REASON(reason_set, reason_val) \ + (((((u64)reason_set) & 0x7) << 12) | \ + ((((u64)reason_val) & 0xff) << 16)) +#define GHCB_SEV_ES_REASON_GENERAL_REQUEST 0 +#define GHCB_SEV_ES_REASON_PROTOCOL_UNSUPPORTED 1 #define GHCB_SEV_GHCB_RESP_CODE(v) ((v) & 0xfff) #define VMGEXIT() { asm volatile("rep; vmmcall\n\r"); } +enum es_result { + ES_OK, /* All good */ + ES_UNSUPPORTED, /* Requested operation not supported */ + ES_VMM_ERROR, /* Unexpected state from the VMM */ + ES_DECODE_FAILED, /* Instruction decoding failed */ + ES_EXCEPTION, /* Instruction caused exception */ + ES_RETRY, /* Retry instruction emulation */ +}; + +struct es_fault_info { + unsigned long vector; + unsigned long error_code; + unsigned long cr2; +}; + +struct pt_regs; + +/* ES instruction emulation context */ +struct es_em_ctxt { + struct pt_regs *regs; + struct insn insn; + struct es_fault_info fi; +}; + void __init do_vc_no_ghcb(struct pt_regs *regs, unsigned long exit_code); static inline u64 lower_bits(u64 val, unsigned int bits) diff --git a/arch/x86/include/uapi/asm/svm.h b/arch/x86/include/uapi/asm/svm.h index 2e8a30f06c74..c68d1618c9b0 100644 --- a/arch/x86/include/uapi/asm/svm.h +++ b/arch/x86/include/uapi/asm/svm.h @@ -29,6 +29,7 @@ #define SVM_EXIT_WRITE_DR6 0x036 #define SVM_EXIT_WRITE_DR7 0x037 #define SVM_EXIT_EXCP_BASE 0x040 +#define SVM_EXIT_LAST_EXCP 0x05f #define SVM_EXIT_INTR 0x060 #define SVM_EXIT_NMI 0x061 #define SVM_EXIT_SMI 0x062 diff --git a/arch/x86/kernel/sev-es-shared.c b/arch/x86/kernel/sev-es-shared.c index 5927152487ad..22eb3ed89186 100644 --- a/arch/x86/kernel/sev-es-shared.c +++ b/arch/x86/kernel/sev-es-shared.c @@ -9,6 +9,118 @@ * and is included directly into both code-bases. */ +static void sev_es_terminate(unsigned int reason) +{ + u64 val = GHCB_SEV_TERMINATE; + + /* + * Tell the hypervisor what went wrong - only reason-set 0 is + * currently supported. + */ + val |= GHCB_SEV_TERMINATE_REASON(0, reason); + + /* Request Guest Termination from Hypvervisor */ + sev_es_wr_ghcb_msr(val); + VMGEXIT(); + + while (true) + asm volatile("hlt\n" : : : "memory"); +} + +static bool sev_es_negotiate_protocol(void) +{ + u64 val; + + /* Do the GHCB protocol version negotiation */ + sev_es_wr_ghcb_msr(GHCB_SEV_INFO_REQ); + VMGEXIT(); + val = sev_es_rd_ghcb_msr(); + + if (GHCB_INFO(val) != GHCB_SEV_INFO) + return false; + + if (GHCB_PROTO_MAX(val) < GHCB_PROTO_OUR || + GHCB_PROTO_MIN(val) > GHCB_PROTO_OUR) + return false; + + return true; +} + +static void vc_ghcb_invalidate(struct ghcb *ghcb) +{ + memset(ghcb->save.valid_bitmap, 0, sizeof(ghcb->save.valid_bitmap)); +} + +static bool vc_decoding_needed(unsigned long exit_code) +{ + /* Exceptions don't require to decode the instruction */ + return !(exit_code >= SVM_EXIT_EXCP_BASE && + exit_code <= SVM_EXIT_LAST_EXCP); +} + +static enum es_result vc_init_em_ctxt(struct es_em_ctxt *ctxt, + struct pt_regs *regs, + unsigned long exit_code) +{ + enum es_result ret = ES_OK; + + memset(ctxt, 0, sizeof(*ctxt)); + ctxt->regs = regs; + + if (vc_decoding_needed(exit_code)) + ret = vc_decode_insn(ctxt); + + return ret; +} + +static void vc_finish_insn(struct es_em_ctxt *ctxt) +{ + ctxt->regs->ip += ctxt->insn.length; +} + +static enum es_result sev_es_ghcb_hv_call(struct ghcb *ghcb, + struct es_em_ctxt *ctxt, + u64 exit_code, u64 exit_info_1, + u64 exit_info_2) +{ + enum es_result ret; + + /* Fill in protocol and format specifiers */ + ghcb->protocol_version = GHCB_PROTOCOL_MAX; + ghcb->ghcb_usage = GHCB_DEFAULT_USAGE; + + ghcb_set_sw_exit_code(ghcb, exit_code); + ghcb_set_sw_exit_info_1(ghcb, exit_info_1); + ghcb_set_sw_exit_info_2(ghcb, exit_info_2); + + sev_es_wr_ghcb_msr(__pa(ghcb)); + VMGEXIT(); + + if ((ghcb->save.sw_exit_info_1 & 0xffffffff) == 1) { + u64 info = ghcb->save.sw_exit_info_2; + unsigned long v; + + info = ghcb->save.sw_exit_info_2; + v = info & SVM_EVTINJ_VEC_MASK; + + /* Check if exception information from hypervisor is sane. */ + if ((info & SVM_EVTINJ_VALID) && + ((v == X86_TRAP_GP) || (v == X86_TRAP_UD)) && + ((info & SVM_EVTINJ_TYPE_MASK) == SVM_EVTINJ_TYPE_EXEPT)) { + ctxt->fi.vector = v; + if (info & SVM_EVTINJ_VALID_ERR) + ctxt->fi.error_code = info >> 32; + ret = ES_EXCEPTION; + } else { + ret = ES_VMM_ERROR; + } + } else { + ret = ES_OK; + } + + return ret; +} + /* * Boot VC Handler - This is the first VC handler during boot, there is no GHCB * page yet, so it only supports the MSR based communication with the @@ -63,3 +175,45 @@ void __init do_vc_no_ghcb(struct pt_regs *regs, unsigned long exit_code) while (true) asm volatile("hlt\n"); } + +static enum es_result vc_insn_string_read(struct es_em_ctxt *ctxt, + void *src, char *buf, + unsigned int data_size, + unsigned int count, + bool backwards) +{ + int i, b = backwards ? -1 : 1; + enum es_result ret = ES_OK; + + for (i = 0; i < count; i++) { + void *s = src + (i * data_size * b); + char *d = buf + (i * data_size); + + ret = vc_read_mem(ctxt, s, d, data_size); + if (ret != ES_OK) + break; + } + + return ret; +} + +static enum es_result vc_insn_string_write(struct es_em_ctxt *ctxt, + void *dst, char *buf, + unsigned int data_size, + unsigned int count, + bool backwards) +{ + int i, s = backwards ? -1 : 1; + enum es_result ret = ES_OK; + + for (i = 0; i < count; i++) { + void *d = dst + (i * data_size * s); + char *b = buf + (i * data_size); + + ret = vc_write_mem(ctxt, d, b, data_size); + if (ret != ES_OK) + break; + } + + return ret; +}