162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Hibernation support for x86-64 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (c) 2007 Rafael J. Wysocki <rjw@sisk.pl> 662306a36Sopenharmony_ci * Copyright (c) 2002 Pavel Machek <pavel@ucw.cz> 762306a36Sopenharmony_ci * Copyright (c) 2001 Patrick Mochel <mochel@osdl.org> 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <linux/gfp.h> 1162306a36Sopenharmony_ci#include <linux/smp.h> 1262306a36Sopenharmony_ci#include <linux/suspend.h> 1362306a36Sopenharmony_ci#include <linux/scatterlist.h> 1462306a36Sopenharmony_ci#include <linux/kdebug.h> 1562306a36Sopenharmony_ci#include <linux/pgtable.h> 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci#include <crypto/hash.h> 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci#include <asm/e820/api.h> 2062306a36Sopenharmony_ci#include <asm/init.h> 2162306a36Sopenharmony_ci#include <asm/proto.h> 2262306a36Sopenharmony_ci#include <asm/page.h> 2362306a36Sopenharmony_ci#include <asm/mtrr.h> 2462306a36Sopenharmony_ci#include <asm/sections.h> 2562306a36Sopenharmony_ci#include <asm/suspend.h> 2662306a36Sopenharmony_ci#include <asm/tlbflush.h> 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_cistatic int set_up_temporary_text_mapping(pgd_t *pgd) 2962306a36Sopenharmony_ci{ 3062306a36Sopenharmony_ci pmd_t *pmd; 3162306a36Sopenharmony_ci pud_t *pud; 3262306a36Sopenharmony_ci p4d_t *p4d = NULL; 3362306a36Sopenharmony_ci pgprot_t pgtable_prot = __pgprot(_KERNPG_TABLE); 3462306a36Sopenharmony_ci pgprot_t pmd_text_prot = __pgprot(__PAGE_KERNEL_LARGE_EXEC); 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci /* Filter out unsupported __PAGE_KERNEL* bits: */ 3762306a36Sopenharmony_ci pgprot_val(pmd_text_prot) &= __default_kernel_pte_mask; 3862306a36Sopenharmony_ci pgprot_val(pgtable_prot) &= __default_kernel_pte_mask; 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_ci /* 4162306a36Sopenharmony_ci * The new mapping only has to cover the page containing the image 4262306a36Sopenharmony_ci * kernel's entry point (jump_address_phys), because the switch over to 4362306a36Sopenharmony_ci * it is carried out by relocated code running from a page allocated 4462306a36Sopenharmony_ci * specifically for this purpose and covered by the identity mapping, so 4562306a36Sopenharmony_ci * the temporary kernel text mapping is only needed for the final jump. 4662306a36Sopenharmony_ci * Moreover, in that mapping the virtual address of the image kernel's 4762306a36Sopenharmony_ci * entry point must be the same as its virtual address in the image 4862306a36Sopenharmony_ci * kernel (restore_jump_address), so the image kernel's 4962306a36Sopenharmony_ci * restore_registers() code doesn't find itself in a different area of 5062306a36Sopenharmony_ci * the virtual address space after switching over to the original page 5162306a36Sopenharmony_ci * tables used by the image kernel. 5262306a36Sopenharmony_ci */ 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci if (pgtable_l5_enabled()) { 5562306a36Sopenharmony_ci p4d = (p4d_t *)get_safe_page(GFP_ATOMIC); 5662306a36Sopenharmony_ci if (!p4d) 5762306a36Sopenharmony_ci return -ENOMEM; 5862306a36Sopenharmony_ci } 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_ci pud = (pud_t *)get_safe_page(GFP_ATOMIC); 6162306a36Sopenharmony_ci if (!pud) 6262306a36Sopenharmony_ci return -ENOMEM; 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci pmd = (pmd_t *)get_safe_page(GFP_ATOMIC); 6562306a36Sopenharmony_ci if (!pmd) 6662306a36Sopenharmony_ci return -ENOMEM; 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci set_pmd(pmd + pmd_index(restore_jump_address), 6962306a36Sopenharmony_ci __pmd((jump_address_phys & PMD_MASK) | pgprot_val(pmd_text_prot))); 7062306a36Sopenharmony_ci set_pud(pud + pud_index(restore_jump_address), 7162306a36Sopenharmony_ci __pud(__pa(pmd) | pgprot_val(pgtable_prot))); 7262306a36Sopenharmony_ci if (p4d) { 7362306a36Sopenharmony_ci p4d_t new_p4d = __p4d(__pa(pud) | pgprot_val(pgtable_prot)); 7462306a36Sopenharmony_ci pgd_t new_pgd = __pgd(__pa(p4d) | pgprot_val(pgtable_prot)); 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci set_p4d(p4d + p4d_index(restore_jump_address), new_p4d); 7762306a36Sopenharmony_ci set_pgd(pgd + pgd_index(restore_jump_address), new_pgd); 7862306a36Sopenharmony_ci } else { 7962306a36Sopenharmony_ci /* No p4d for 4-level paging: point the pgd to the pud page table */ 8062306a36Sopenharmony_ci pgd_t new_pgd = __pgd(__pa(pud) | pgprot_val(pgtable_prot)); 8162306a36Sopenharmony_ci set_pgd(pgd + pgd_index(restore_jump_address), new_pgd); 8262306a36Sopenharmony_ci } 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci return 0; 8562306a36Sopenharmony_ci} 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_cistatic void *alloc_pgt_page(void *context) 8862306a36Sopenharmony_ci{ 8962306a36Sopenharmony_ci return (void *)get_safe_page(GFP_ATOMIC); 9062306a36Sopenharmony_ci} 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_cistatic int set_up_temporary_mappings(void) 9362306a36Sopenharmony_ci{ 9462306a36Sopenharmony_ci struct x86_mapping_info info = { 9562306a36Sopenharmony_ci .alloc_pgt_page = alloc_pgt_page, 9662306a36Sopenharmony_ci .page_flag = __PAGE_KERNEL_LARGE_EXEC, 9762306a36Sopenharmony_ci .offset = __PAGE_OFFSET, 9862306a36Sopenharmony_ci }; 9962306a36Sopenharmony_ci unsigned long mstart, mend; 10062306a36Sopenharmony_ci pgd_t *pgd; 10162306a36Sopenharmony_ci int result; 10262306a36Sopenharmony_ci int i; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci pgd = (pgd_t *)get_safe_page(GFP_ATOMIC); 10562306a36Sopenharmony_ci if (!pgd) 10662306a36Sopenharmony_ci return -ENOMEM; 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci /* Prepare a temporary mapping for the kernel text */ 10962306a36Sopenharmony_ci result = set_up_temporary_text_mapping(pgd); 11062306a36Sopenharmony_ci if (result) 11162306a36Sopenharmony_ci return result; 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci /* Set up the direct mapping from scratch */ 11462306a36Sopenharmony_ci for (i = 0; i < nr_pfn_mapped; i++) { 11562306a36Sopenharmony_ci mstart = pfn_mapped[i].start << PAGE_SHIFT; 11662306a36Sopenharmony_ci mend = pfn_mapped[i].end << PAGE_SHIFT; 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci result = kernel_ident_mapping_init(&info, pgd, mstart, mend); 11962306a36Sopenharmony_ci if (result) 12062306a36Sopenharmony_ci return result; 12162306a36Sopenharmony_ci } 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci temp_pgt = __pa(pgd); 12462306a36Sopenharmony_ci return 0; 12562306a36Sopenharmony_ci} 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ciasmlinkage int swsusp_arch_resume(void) 12862306a36Sopenharmony_ci{ 12962306a36Sopenharmony_ci int error; 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci /* We have got enough memory and from now on we cannot recover */ 13262306a36Sopenharmony_ci error = set_up_temporary_mappings(); 13362306a36Sopenharmony_ci if (error) 13462306a36Sopenharmony_ci return error; 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci error = relocate_restore_code(); 13762306a36Sopenharmony_ci if (error) 13862306a36Sopenharmony_ci return error; 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci restore_image(); 14162306a36Sopenharmony_ci return 0; 14262306a36Sopenharmony_ci} 143