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

x86/tdx: Add unaccepted memory support

Hookup TDX-specific code to accept memory.

Accepting the memory is done with ACCEPT_PAGE module call on every page
in the range. MAP_GPA hypercall is not required as the unaccepted memory
is considered private already.

Extract the part of tdx_enc_status_changed() that does memory acceptance
in a new helper. Move the helper tdx-shared.c. It is going to be used by
both main kernel and decompressor.

[ bp: Fix the INTEL_TDX_GUEST=y, KVM_GUEST=n build. ]

Signed-off-by: Kirill A. Shutemov <kirill.shutemov@linux.intel.com>
Signed-off-by: Borislav Petkov (AMD) <bp@alien8.de>
Link: https://lore.kernel.org/r/20230606142637.5171-10-kirill.shutemov@linux.intel.com

authored by

Kirill A. Shutemov and committed by
Borislav Petkov (AMD)
75d090fd c2b353ae

+162 -70
+2
arch/x86/Kconfig
··· 884 884 bool "Intel TDX (Trust Domain Extensions) - Guest Support" 885 885 depends on X86_64 && CPU_SUP_INTEL 886 886 depends on X86_X2APIC 887 + depends on EFI_STUB 887 888 select ARCH_HAS_CC_PLATFORM 888 889 select X86_MEM_ENCRYPT 889 890 select X86_MCE 891 + select UNACCEPTED_MEMORY 890 892 help 891 893 Support running as a guest under Intel TDX. Without this support, 892 894 the guest kernel can not boot or run under TDX.
+1 -1
arch/x86/boot/compressed/Makefile
··· 106 106 endif 107 107 108 108 vmlinux-objs-$(CONFIG_ACPI) += $(obj)/acpi.o 109 - vmlinux-objs-$(CONFIG_INTEL_TDX_GUEST) += $(obj)/tdx.o $(obj)/tdcall.o 109 + vmlinux-objs-$(CONFIG_INTEL_TDX_GUEST) += $(obj)/tdx.o $(obj)/tdcall.o $(obj)/tdx-shared.o 110 110 vmlinux-objs-$(CONFIG_UNACCEPTED_MEMORY) += $(obj)/mem.o 111 111 112 112 vmlinux-objs-$(CONFIG_EFI) += $(obj)/efi.o
+19
arch/x86/boot/compressed/error.c
··· 22 22 while (1) 23 23 asm("hlt"); 24 24 } 25 + 26 + /* EFI libstub provides vsnprintf() */ 27 + #ifdef CONFIG_EFI_STUB 28 + void panic(const char *fmt, ...) 29 + { 30 + static char buf[1024]; 31 + va_list args; 32 + int len; 33 + 34 + va_start(args, fmt); 35 + len = vsnprintf(buf, sizeof(buf), fmt, args); 36 + va_end(args); 37 + 38 + if (len && buf[len - 1] == '\n') 39 + buf[len - 1] = '\0'; 40 + 41 + error(buf); 42 + } 43 + #endif
+1
arch/x86/boot/compressed/error.h
··· 6 6 7 7 void warn(char *m); 8 8 void error(char *m) __noreturn; 9 + void panic(const char *fmt, ...) __noreturn __cold; 9 10 10 11 #endif /* BOOT_COMPRESSED_ERROR_H */
+34 -1
arch/x86/boot/compressed/mem.c
··· 2 2 3 3 #include "error.h" 4 4 #include "misc.h" 5 + #include "tdx.h" 6 + #include <asm/shared/tdx.h> 7 + 8 + /* 9 + * accept_memory() and process_unaccepted_memory() called from EFI stub which 10 + * runs before decompresser and its early_tdx_detect(). 11 + * 12 + * Enumerate TDX directly from the early users. 13 + */ 14 + static bool early_is_tdx_guest(void) 15 + { 16 + static bool once; 17 + static bool is_tdx; 18 + 19 + if (!IS_ENABLED(CONFIG_INTEL_TDX_GUEST)) 20 + return false; 21 + 22 + if (!once) { 23 + u32 eax, sig[3]; 24 + 25 + cpuid_count(TDX_CPUID_LEAF_ID, 0, &eax, 26 + &sig[0], &sig[2], &sig[1]); 27 + is_tdx = !memcmp(TDX_IDENT, sig, sizeof(sig)); 28 + once = true; 29 + } 30 + 31 + return is_tdx; 32 + } 5 33 6 34 void arch_accept_memory(phys_addr_t start, phys_addr_t end) 7 35 { 8 36 /* Platform-specific memory-acceptance call goes here */ 9 - error("Cannot accept memory"); 37 + if (early_is_tdx_guest()) { 38 + if (!tdx_accept_memory(start, end)) 39 + panic("TDX: Failed to accept memory\n"); 40 + } else { 41 + error("Cannot accept memory: unknown platform\n"); 42 + } 10 43 } 11 44 12 45 bool init_unaccepted_memory(void)
+2
arch/x86/boot/compressed/tdx-shared.c
··· 1 + #include "error.h" 2 + #include "../../coco/tdx/tdx-shared.c"
+1 -1
arch/x86/coco/tdx/Makefile
··· 1 1 # SPDX-License-Identifier: GPL-2.0 2 2 3 - obj-y += tdx.o tdcall.o 3 + obj-y += tdx.o tdx-shared.o tdcall.o
+71
arch/x86/coco/tdx/tdx-shared.c
··· 1 + #include <asm/tdx.h> 2 + #include <asm/pgtable.h> 3 + 4 + static unsigned long try_accept_one(phys_addr_t start, unsigned long len, 5 + enum pg_level pg_level) 6 + { 7 + unsigned long accept_size = page_level_size(pg_level); 8 + u64 tdcall_rcx; 9 + u8 page_size; 10 + 11 + if (!IS_ALIGNED(start, accept_size)) 12 + return 0; 13 + 14 + if (len < accept_size) 15 + return 0; 16 + 17 + /* 18 + * Pass the page physical address to the TDX module to accept the 19 + * pending, private page. 20 + * 21 + * Bits 2:0 of RCX encode page size: 0 - 4K, 1 - 2M, 2 - 1G. 22 + */ 23 + switch (pg_level) { 24 + case PG_LEVEL_4K: 25 + page_size = 0; 26 + break; 27 + case PG_LEVEL_2M: 28 + page_size = 1; 29 + break; 30 + case PG_LEVEL_1G: 31 + page_size = 2; 32 + break; 33 + default: 34 + return 0; 35 + } 36 + 37 + tdcall_rcx = start | page_size; 38 + if (__tdx_module_call(TDX_ACCEPT_PAGE, tdcall_rcx, 0, 0, 0, NULL)) 39 + return 0; 40 + 41 + return accept_size; 42 + } 43 + 44 + bool tdx_accept_memory(phys_addr_t start, phys_addr_t end) 45 + { 46 + /* 47 + * For shared->private conversion, accept the page using 48 + * TDX_ACCEPT_PAGE TDX module call. 49 + */ 50 + while (start < end) { 51 + unsigned long len = end - start; 52 + unsigned long accept_size; 53 + 54 + /* 55 + * Try larger accepts first. It gives chance to VMM to keep 56 + * 1G/2M Secure EPT entries where possible and speeds up 57 + * process by cutting number of hypercalls (if successful). 58 + */ 59 + 60 + accept_size = try_accept_one(start, len, PG_LEVEL_1G); 61 + if (!accept_size) 62 + accept_size = try_accept_one(start, len, PG_LEVEL_2M); 63 + if (!accept_size) 64 + accept_size = try_accept_one(start, len, PG_LEVEL_4K); 65 + if (!accept_size) 66 + return false; 67 + start += accept_size; 68 + } 69 + 70 + return true; 71 + }
+3 -67
arch/x86/coco/tdx/tdx.c
··· 713 713 return true; 714 714 } 715 715 716 - static unsigned long try_accept_one(phys_addr_t start, unsigned long len, 717 - enum pg_level pg_level) 718 - { 719 - unsigned long accept_size = page_level_size(pg_level); 720 - u64 tdcall_rcx; 721 - u8 page_size; 722 - 723 - if (!IS_ALIGNED(start, accept_size)) 724 - return 0; 725 - 726 - if (len < accept_size) 727 - return 0; 728 - 729 - /* 730 - * Pass the page physical address to the TDX module to accept the 731 - * pending, private page. 732 - * 733 - * Bits 2:0 of RCX encode page size: 0 - 4K, 1 - 2M, 2 - 1G. 734 - */ 735 - switch (pg_level) { 736 - case PG_LEVEL_4K: 737 - page_size = 0; 738 - break; 739 - case PG_LEVEL_2M: 740 - page_size = 1; 741 - break; 742 - case PG_LEVEL_1G: 743 - page_size = 2; 744 - break; 745 - default: 746 - return 0; 747 - } 748 - 749 - tdcall_rcx = start | page_size; 750 - if (__tdx_module_call(TDX_ACCEPT_PAGE, tdcall_rcx, 0, 0, 0, NULL)) 751 - return 0; 752 - 753 - return accept_size; 754 - } 755 - 756 716 /* 757 717 * Inform the VMM of the guest's intent for this physical page: shared with 758 718 * the VMM or private to the guest. The VMM is expected to change its mapping ··· 737 777 if (_tdx_hypercall(TDVMCALL_MAP_GPA, start, end - start, 0, 0)) 738 778 return false; 739 779 740 - /* private->shared conversion requires only MapGPA call */ 741 - if (!enc) 742 - return true; 743 - 744 - /* 745 - * For shared->private conversion, accept the page using 746 - * TDX_ACCEPT_PAGE TDX module call. 747 - */ 748 - while (start < end) { 749 - unsigned long len = end - start; 750 - unsigned long accept_size; 751 - 752 - /* 753 - * Try larger accepts first. It gives chance to VMM to keep 754 - * 1G/2M Secure EPT entries where possible and speeds up 755 - * process by cutting number of hypercalls (if successful). 756 - */ 757 - 758 - accept_size = try_accept_one(start, len, PG_LEVEL_1G); 759 - if (!accept_size) 760 - accept_size = try_accept_one(start, len, PG_LEVEL_2M); 761 - if (!accept_size) 762 - accept_size = try_accept_one(start, len, PG_LEVEL_4K); 763 - if (!accept_size) 764 - return false; 765 - start += accept_size; 766 - } 780 + /* shared->private conversion requires memory to be accepted before use */ 781 + if (enc) 782 + return tdx_accept_memory(start, end); 767 783 768 784 return true; 769 785 }
+2
arch/x86/include/asm/shared/tdx.h
··· 91 91 u64 __tdx_module_call(u64 fn, u64 rcx, u64 rdx, u64 r8, u64 r9, 92 92 struct tdx_module_output *out); 93 93 94 + bool tdx_accept_memory(phys_addr_t start, phys_addr_t end); 95 + 94 96 #endif /* !__ASSEMBLY__ */ 95 97 #endif /* _ASM_X86_SHARED_TDX_H */
+2
arch/x86/include/asm/tdx.h
··· 5 5 6 6 #include <linux/init.h> 7 7 #include <linux/bits.h> 8 + 9 + #include <asm/errno.h> 8 10 #include <asm/ptrace.h> 9 11 #include <asm/shared/tdx.h> 10 12
+24
arch/x86/include/asm/unaccepted_memory.h
··· 1 + #ifndef _ASM_X86_UNACCEPTED_MEMORY_H 2 + #define _ASM_X86_UNACCEPTED_MEMORY_H 3 + 4 + #include <linux/efi.h> 5 + #include <asm/tdx.h> 6 + 7 + static inline void arch_accept_memory(phys_addr_t start, phys_addr_t end) 8 + { 9 + /* Platform-specific memory-acceptance call goes here */ 10 + if (cpu_feature_enabled(X86_FEATURE_TDX_GUEST)) { 11 + if (!tdx_accept_memory(start, end)) 12 + panic("TDX: Failed to accept memory\n"); 13 + } else { 14 + panic("Cannot accept memory: unknown platform\n"); 15 + } 16 + } 17 + 18 + static inline struct efi_unaccepted_memory *efi_get_unaccepted_table(void) 19 + { 20 + if (efi.unaccepted == EFI_INVALID_TABLE_ADDR) 21 + return NULL; 22 + return __va(efi.unaccepted); 23 + } 24 + #endif