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