18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Hibernation support for x86-64
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (c) 2007 Rafael J. Wysocki <rjw@sisk.pl>
68c2ecf20Sopenharmony_ci * Copyright (c) 2002 Pavel Machek <pavel@ucw.cz>
78c2ecf20Sopenharmony_ci * Copyright (c) 2001 Patrick Mochel <mochel@osdl.org>
88c2ecf20Sopenharmony_ci */
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_ci#include <linux/gfp.h>
118c2ecf20Sopenharmony_ci#include <linux/smp.h>
128c2ecf20Sopenharmony_ci#include <linux/suspend.h>
138c2ecf20Sopenharmony_ci#include <linux/scatterlist.h>
148c2ecf20Sopenharmony_ci#include <linux/kdebug.h>
158c2ecf20Sopenharmony_ci#include <linux/pgtable.h>
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_ci#include <crypto/hash.h>
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci#include <asm/e820/api.h>
208c2ecf20Sopenharmony_ci#include <asm/init.h>
218c2ecf20Sopenharmony_ci#include <asm/proto.h>
228c2ecf20Sopenharmony_ci#include <asm/page.h>
238c2ecf20Sopenharmony_ci#include <asm/mtrr.h>
248c2ecf20Sopenharmony_ci#include <asm/sections.h>
258c2ecf20Sopenharmony_ci#include <asm/suspend.h>
268c2ecf20Sopenharmony_ci#include <asm/tlbflush.h>
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_cistatic int set_up_temporary_text_mapping(pgd_t *pgd)
298c2ecf20Sopenharmony_ci{
308c2ecf20Sopenharmony_ci	pmd_t *pmd;
318c2ecf20Sopenharmony_ci	pud_t *pud;
328c2ecf20Sopenharmony_ci	p4d_t *p4d = NULL;
338c2ecf20Sopenharmony_ci	pgprot_t pgtable_prot = __pgprot(_KERNPG_TABLE);
348c2ecf20Sopenharmony_ci	pgprot_t pmd_text_prot = __pgprot(__PAGE_KERNEL_LARGE_EXEC);
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ci	/* Filter out unsupported __PAGE_KERNEL* bits: */
378c2ecf20Sopenharmony_ci	pgprot_val(pmd_text_prot) &= __default_kernel_pte_mask;
388c2ecf20Sopenharmony_ci	pgprot_val(pgtable_prot)  &= __default_kernel_pte_mask;
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci	/*
418c2ecf20Sopenharmony_ci	 * The new mapping only has to cover the page containing the image
428c2ecf20Sopenharmony_ci	 * kernel's entry point (jump_address_phys), because the switch over to
438c2ecf20Sopenharmony_ci	 * it is carried out by relocated code running from a page allocated
448c2ecf20Sopenharmony_ci	 * specifically for this purpose and covered by the identity mapping, so
458c2ecf20Sopenharmony_ci	 * the temporary kernel text mapping is only needed for the final jump.
468c2ecf20Sopenharmony_ci	 * Moreover, in that mapping the virtual address of the image kernel's
478c2ecf20Sopenharmony_ci	 * entry point must be the same as its virtual address in the image
488c2ecf20Sopenharmony_ci	 * kernel (restore_jump_address), so the image kernel's
498c2ecf20Sopenharmony_ci	 * restore_registers() code doesn't find itself in a different area of
508c2ecf20Sopenharmony_ci	 * the virtual address space after switching over to the original page
518c2ecf20Sopenharmony_ci	 * tables used by the image kernel.
528c2ecf20Sopenharmony_ci	 */
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_ci	if (pgtable_l5_enabled()) {
558c2ecf20Sopenharmony_ci		p4d = (p4d_t *)get_safe_page(GFP_ATOMIC);
568c2ecf20Sopenharmony_ci		if (!p4d)
578c2ecf20Sopenharmony_ci			return -ENOMEM;
588c2ecf20Sopenharmony_ci	}
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_ci	pud = (pud_t *)get_safe_page(GFP_ATOMIC);
618c2ecf20Sopenharmony_ci	if (!pud)
628c2ecf20Sopenharmony_ci		return -ENOMEM;
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_ci	pmd = (pmd_t *)get_safe_page(GFP_ATOMIC);
658c2ecf20Sopenharmony_ci	if (!pmd)
668c2ecf20Sopenharmony_ci		return -ENOMEM;
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_ci	set_pmd(pmd + pmd_index(restore_jump_address),
698c2ecf20Sopenharmony_ci		__pmd((jump_address_phys & PMD_MASK) | pgprot_val(pmd_text_prot)));
708c2ecf20Sopenharmony_ci	set_pud(pud + pud_index(restore_jump_address),
718c2ecf20Sopenharmony_ci		__pud(__pa(pmd) | pgprot_val(pgtable_prot)));
728c2ecf20Sopenharmony_ci	if (p4d) {
738c2ecf20Sopenharmony_ci		p4d_t new_p4d = __p4d(__pa(pud) | pgprot_val(pgtable_prot));
748c2ecf20Sopenharmony_ci		pgd_t new_pgd = __pgd(__pa(p4d) | pgprot_val(pgtable_prot));
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_ci		set_p4d(p4d + p4d_index(restore_jump_address), new_p4d);
778c2ecf20Sopenharmony_ci		set_pgd(pgd + pgd_index(restore_jump_address), new_pgd);
788c2ecf20Sopenharmony_ci	} else {
798c2ecf20Sopenharmony_ci		/* No p4d for 4-level paging: point the pgd to the pud page table */
808c2ecf20Sopenharmony_ci		pgd_t new_pgd = __pgd(__pa(pud) | pgprot_val(pgtable_prot));
818c2ecf20Sopenharmony_ci		set_pgd(pgd + pgd_index(restore_jump_address), new_pgd);
828c2ecf20Sopenharmony_ci	}
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_ci	return 0;
858c2ecf20Sopenharmony_ci}
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_cistatic void *alloc_pgt_page(void *context)
888c2ecf20Sopenharmony_ci{
898c2ecf20Sopenharmony_ci	return (void *)get_safe_page(GFP_ATOMIC);
908c2ecf20Sopenharmony_ci}
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_cistatic int set_up_temporary_mappings(void)
938c2ecf20Sopenharmony_ci{
948c2ecf20Sopenharmony_ci	struct x86_mapping_info info = {
958c2ecf20Sopenharmony_ci		.alloc_pgt_page	= alloc_pgt_page,
968c2ecf20Sopenharmony_ci		.page_flag	= __PAGE_KERNEL_LARGE_EXEC,
978c2ecf20Sopenharmony_ci		.offset		= __PAGE_OFFSET,
988c2ecf20Sopenharmony_ci	};
998c2ecf20Sopenharmony_ci	unsigned long mstart, mend;
1008c2ecf20Sopenharmony_ci	pgd_t *pgd;
1018c2ecf20Sopenharmony_ci	int result;
1028c2ecf20Sopenharmony_ci	int i;
1038c2ecf20Sopenharmony_ci
1048c2ecf20Sopenharmony_ci	pgd = (pgd_t *)get_safe_page(GFP_ATOMIC);
1058c2ecf20Sopenharmony_ci	if (!pgd)
1068c2ecf20Sopenharmony_ci		return -ENOMEM;
1078c2ecf20Sopenharmony_ci
1088c2ecf20Sopenharmony_ci	/* Prepare a temporary mapping for the kernel text */
1098c2ecf20Sopenharmony_ci	result = set_up_temporary_text_mapping(pgd);
1108c2ecf20Sopenharmony_ci	if (result)
1118c2ecf20Sopenharmony_ci		return result;
1128c2ecf20Sopenharmony_ci
1138c2ecf20Sopenharmony_ci	/* Set up the direct mapping from scratch */
1148c2ecf20Sopenharmony_ci	for (i = 0; i < nr_pfn_mapped; i++) {
1158c2ecf20Sopenharmony_ci		mstart = pfn_mapped[i].start << PAGE_SHIFT;
1168c2ecf20Sopenharmony_ci		mend   = pfn_mapped[i].end << PAGE_SHIFT;
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_ci		result = kernel_ident_mapping_init(&info, pgd, mstart, mend);
1198c2ecf20Sopenharmony_ci		if (result)
1208c2ecf20Sopenharmony_ci			return result;
1218c2ecf20Sopenharmony_ci	}
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_ci	temp_pgt = __pa(pgd);
1248c2ecf20Sopenharmony_ci	return 0;
1258c2ecf20Sopenharmony_ci}
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_ciasmlinkage int swsusp_arch_resume(void)
1288c2ecf20Sopenharmony_ci{
1298c2ecf20Sopenharmony_ci	int error;
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci	/* We have got enough memory and from now on we cannot recover */
1328c2ecf20Sopenharmony_ci	error = set_up_temporary_mappings();
1338c2ecf20Sopenharmony_ci	if (error)
1348c2ecf20Sopenharmony_ci		return error;
1358c2ecf20Sopenharmony_ci
1368c2ecf20Sopenharmony_ci	error = relocate_restore_code();
1378c2ecf20Sopenharmony_ci	if (error)
1388c2ecf20Sopenharmony_ci		return error;
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_ci	restore_image();
1418c2ecf20Sopenharmony_ci	return 0;
1428c2ecf20Sopenharmony_ci}
143