From patchwork Sun Jun 2 14:23:03 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Kirill A . Shutemov" X-Patchwork-Id: 13682895 Received: from mgamail.intel.com (mgamail.intel.com [192.198.163.16]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5D2D8433BB; Sun, 2 Jun 2024 14:23:13 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=192.198.163.16 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1717338194; cv=none; b=b9kbgMbeywAIn5dhVa3OCdT5XknzeeFnQuVJoP4S32UBSkSA/1uVbnUn4aULWXRkOmDTZOKAvYhi/xMtccP+LXlMQ6i5icNSt6mn5RY9VkD+jvZV3IhvwWzoHVAI2CWoE5Uhev/BlxbNuBI49ZX6CTbqLxNAvUKdGJ4hykC7AXg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1717338194; c=relaxed/simple; bh=u8EQwW4zBIz0lgHC7JYsnH7/Y26p2JW5dmoxCOMVnk8=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=WHlcvMaOn0hsgDSb8UkljzXC3IFqpX9gAd5fvQ09I1U5/c9YwpoWwCunASbfnRtUaWGpg1QU8Lpfmfec0ez30b7jBGgPGUDjfXG+gM/AkNtClmdYnfIFX9ZH/P1iSXeH8eRoGCkIK9Z1+nQGC26PYC12MccAD8N/IZ0lYC+SZ6Y= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.intel.com; spf=none smtp.helo=mgamail.intel.com; dkim=pass (2048-bit key) header.d=intel.com header.i=@intel.com header.b=SUr7JwG/; arc=none smtp.client-ip=192.198.163.16 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.intel.com Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.helo=mgamail.intel.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=intel.com header.i=@intel.com header.b="SUr7JwG/" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1717338193; x=1748874193; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=u8EQwW4zBIz0lgHC7JYsnH7/Y26p2JW5dmoxCOMVnk8=; b=SUr7JwG/l2NI9i+cHQoYFviOlT8TykzDXLPe9sQiNvhvlyoRDDeGlJ4+ FemSQgyaNWPrpapOoSmJ80cJ2dJmRBo7jgu+4q30q3Khv1SFa5Fc+AO/X MHFhrdrCL4S+gjJsS/RSlGlU2ogSEzInuiZp6KPizsNYJYnb5Ymh+lJj1 oNtNYSzekxLFnxVO6TKqGWSA+eTWbrYfmf8/dVZkRDC5tZzTf9NS8MFFQ x1IEDim67nqbaPAVCp21+2M9dMkKeQ+1bOGeFaIBfhcdN0IcKA1D9NWRO 9Ck/qMRznnfh/YehxDsRyl/HL85YjeA33aoRuoDh/VEE4+7bZI6CFb7Ni A==; X-CSE-ConnectionGUID: Zj0doteITaCndWezusCumg== X-CSE-MsgGUID: BY3rmzSMRHqZ2zctxxod5A== X-IronPort-AV: E=McAfee;i="6600,9927,11091"; a="11862673" X-IronPort-AV: E=Sophos;i="6.08,209,1712646000"; d="scan'208";a="11862673" Received: from orviesa003.jf.intel.com ([10.64.159.143]) by fmvoesa110.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 02 Jun 2024 07:23:12 -0700 X-CSE-ConnectionGUID: QswGMZZPQQ2dGBLjneGbfw== X-CSE-MsgGUID: yCpPsuk9TbWT+S3ywfpC7g== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="6.08,209,1712646000"; d="scan'208";a="41562676" Received: from black.fi.intel.com ([10.237.72.28]) by orviesa003.jf.intel.com with ESMTP; 02 Jun 2024 07:23:07 -0700 Received: by black.fi.intel.com (Postfix, from userid 1000) id E187F1CB; Sun, 02 Jun 2024 17:23:04 +0300 (EEST) From: "Kirill A. Shutemov" To: bp@alien8.de Cc: adrian.hunter@intel.com, ardb@kernel.org, ashish.kalra@amd.com, bhe@redhat.com, dave.hansen@linux.intel.com, elena.reshetova@intel.com, haiyangz@microsoft.com, hpa@zytor.com, jun.nakajima@intel.com, kai.huang@intel.com, kexec@lists.infradead.org, kirill.shutemov@linux.intel.com, kys@microsoft.com, linux-acpi@vger.kernel.org, linux-coco@lists.linux.dev, linux-hyperv@vger.kernel.org, linux-kernel@vger.kernel.org, ltao@redhat.com, mingo@redhat.com, peterz@infradead.org, rafael@kernel.org, rick.p.edgecombe@intel.com, sathyanarayanan.kuppuswamy@linux.intel.com, seanjc@google.com, tglx@linutronix.de, thomas.lendacky@amd.com, x86@kernel.org Subject: [PATCHv11.1 11/19] x86/tdx: Convert shared memory back to private on kexec Date: Sun, 2 Jun 2024 17:23:03 +0300 Message-ID: <20240602142303.3263551-1-kirill.shutemov@linux.intel.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20240531151442.GMZlnpYkDCRlg1_YS0@fat_crate.local> References: <20240531151442.GMZlnpYkDCRlg1_YS0@fat_crate.local> Precedence: bulk X-Mailing-List: linux-acpi@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 TDX guests allocate shared buffers to perform I/O. It is done by allocating pages normally from the buddy allocator and converting them to shared with set_memory_decrypted(). The second, kexec-ed kernel has no idea what memory is converted this way. It only sees E820_TYPE_RAM. Accessing shared memory via private mapping is fatal. It leads to unrecoverable TD exit. On kexec walk direct mapping and convert all shared memory back to private. It makes all RAM private again and second kernel may use it normally. The conversion occurs in two steps: stopping new conversions and unsharing all memory. In the case of normal kexec, the stopping of conversions takes place while scheduling is still functioning. This allows for waiting until any ongoing conversions are finished. The second step is carried out when all CPUs except one are inactive and interrupts are disabled. This prevents any conflicts with code that may access shared memory. Signed-off-by: Kirill A. Shutemov Reviewed-by: Rick Edgecombe Reviewed-by: Kai Huang Tested-by: Tao Liu --- arch/x86/coco/tdx/tdx.c | 90 +++++++++++++++++++++++++++++++ arch/x86/include/asm/pgtable.h | 5 ++ arch/x86/include/asm/set_memory.h | 3 ++ arch/x86/mm/pat/set_memory.c | 41 ++++++++++++-- 4 files changed, 136 insertions(+), 3 deletions(-) diff --git a/arch/x86/coco/tdx/tdx.c b/arch/x86/coco/tdx/tdx.c index 979891e97d83..afd71bc6eb02 100644 --- a/arch/x86/coco/tdx/tdx.c +++ b/arch/x86/coco/tdx/tdx.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -14,6 +15,7 @@ #include #include #include +#include /* MMIO direction */ #define EPT_READ 0 @@ -831,6 +833,91 @@ static int tdx_enc_status_change_finish(unsigned long vaddr, int numpages, return 0; } +/* Stop new private<->shared conversions */ +static void tdx_kexec_begin(bool crash) +{ + /* + * Crash kernel reaches here with interrupts disabled: can't wait for + * conversions to finish. + * + * If race happened, just report and proceed. + */ + if (!set_memory_enc_stop_conversion(!crash)) + pr_warn("Failed to stop shared<->private conversions\n"); +} + +/* Walk direct mapping and convert all shared memory back to private */ +static void tdx_kexec_finish(void) +{ + unsigned long addr, end; + long found = 0, shared; + + lockdep_assert_irqs_disabled(); + + addr = PAGE_OFFSET; + end = PAGE_OFFSET + get_max_mapped(); + + while (addr < end) { + unsigned long size; + unsigned int level; + pte_t *pte; + + pte = lookup_address(addr, &level); + size = page_level_size(level); + + if (pte && pte_decrypted(*pte)) { + int pages = size / PAGE_SIZE; + + /* + * Touching memory with shared bit set triggers implicit + * conversion to shared. + * + * Make sure nobody touches the shared range from + * now on. + */ + set_pte(pte, __pte(0)); + + /* + * The only thing one can do at this point on failure + * is panic. It is reasonable to proceed. + * + * Also, even if the failure is real and the page cannot + * be touched as private, the kdump kernel will boot + * fine as it uses pre-reserved memory. What happens + * next depends on what the dumping process does and + * there's a reasonable chance to produce useful dump + * on crash. + * + * Regardless, the print leaves a trace in the log to + * give a clue for debug. + * + * One possible reason for the failure is if kdump raced + * with memory conversion. In this case shared bit in + * page table got set (or not cleared) during + * shared<->private conversion, but the page is actually + * private. So this failure is not going to affect the + * kexec'ed kernel. + */ + if (!tdx_enc_status_changed(addr, pages, true)) { + pr_err("Failed to unshare range %#lx-%#lx\n", + addr, addr + size); + } + + found += pages; + } + + addr += size; + } + + __flush_tlb_all(); + + shared = atomic_long_read(&nr_shared); + if (shared != found) { + pr_err("shared page accounting is off\n"); + pr_err("nr_shared = %ld, nr_found = %ld\n", shared, found); + } +} + void __init tdx_early_init(void) { struct tdx_module_args args = { @@ -890,6 +977,9 @@ void __init tdx_early_init(void) x86_platform.guest.enc_cache_flush_required = tdx_cache_flush_required; x86_platform.guest.enc_tlb_flush_required = tdx_tlb_flush_required; + x86_platform.guest.enc_kexec_begin = tdx_kexec_begin; + x86_platform.guest.enc_kexec_finish = tdx_kexec_finish; + /* * TDX intercepts the RDMSR to read the X2APIC ID in the parallel * bringup low level code. That raises #VE which cannot be handled diff --git a/arch/x86/include/asm/pgtable.h b/arch/x86/include/asm/pgtable.h index 65b8e5bb902c..e39311a89bf4 100644 --- a/arch/x86/include/asm/pgtable.h +++ b/arch/x86/include/asm/pgtable.h @@ -140,6 +140,11 @@ static inline int pte_young(pte_t pte) return pte_flags(pte) & _PAGE_ACCESSED; } +static inline bool pte_decrypted(pte_t pte) +{ + return cc_mkdec(pte_val(pte)) == pte_val(pte); +} + #define pmd_dirty pmd_dirty static inline bool pmd_dirty(pmd_t pmd) { diff --git a/arch/x86/include/asm/set_memory.h b/arch/x86/include/asm/set_memory.h index 9aee31862b4a..d490db38db9e 100644 --- a/arch/x86/include/asm/set_memory.h +++ b/arch/x86/include/asm/set_memory.h @@ -49,8 +49,11 @@ int set_memory_wb(unsigned long addr, int numpages); int set_memory_np(unsigned long addr, int numpages); int set_memory_p(unsigned long addr, int numpages); int set_memory_4k(unsigned long addr, int numpages); + +bool set_memory_enc_stop_conversion(bool wait); int set_memory_encrypted(unsigned long addr, int numpages); int set_memory_decrypted(unsigned long addr, int numpages); + int set_memory_np_noalias(unsigned long addr, int numpages); int set_memory_nonglobal(unsigned long addr, int numpages); int set_memory_global(unsigned long addr, int numpages); diff --git a/arch/x86/mm/pat/set_memory.c b/arch/x86/mm/pat/set_memory.c index a7a7a6c6a3fb..2a548b65ef5f 100644 --- a/arch/x86/mm/pat/set_memory.c +++ b/arch/x86/mm/pat/set_memory.c @@ -2227,12 +2227,47 @@ static int __set_memory_enc_pgtable(unsigned long addr, int numpages, bool enc) return ret; } +/* + * The lock serializes conversions between private and shared memory. + * + * It is taken for read on conversion. A write lock guarantees that no + * concurrent conversions are in progress. + */ +static DECLARE_RWSEM(mem_enc_lock); + +/* + * Stop new private<->shared conversions. + * + * Taking the exclusive mem_enc_lock waits for in-flight conversions to complete. + * The lock is not released to prevent new conversions from being started. + * + * If sleep is not allowed, as in a crash scenario, try to take the lock. + * Failure indicates that there is a race with the conversion. + */ +bool set_memory_enc_stop_conversion(bool wait) +{ + if (!wait) + return down_write_trylock(&mem_enc_lock); + + down_write(&mem_enc_lock); + + return true; +} + static int __set_memory_enc_dec(unsigned long addr, int numpages, bool enc) { - if (cc_platform_has(CC_ATTR_MEM_ENCRYPT)) - return __set_memory_enc_pgtable(addr, numpages, enc); + int ret = 0; - return 0; + if (cc_platform_has(CC_ATTR_MEM_ENCRYPT)) { + if (!down_read_trylock(&mem_enc_lock)) + return -EBUSY; + + ret = __set_memory_enc_pgtable(addr, numpages, enc); + + up_read(&mem_enc_lock); + } + + return ret; } int set_memory_encrypted(unsigned long addr, int numpages)