162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Hibernation support for x86
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#include <linux/gfp.h>
1062306a36Sopenharmony_ci#include <linux/smp.h>
1162306a36Sopenharmony_ci#include <linux/suspend.h>
1262306a36Sopenharmony_ci#include <linux/scatterlist.h>
1362306a36Sopenharmony_ci#include <linux/kdebug.h>
1462306a36Sopenharmony_ci#include <linux/cpu.h>
1562306a36Sopenharmony_ci#include <linux/pgtable.h>
1662306a36Sopenharmony_ci#include <linux/types.h>
1762306a36Sopenharmony_ci#include <linux/crc32.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_ci/*
2962306a36Sopenharmony_ci * Address to jump to in the last phase of restore in order to get to the image
3062306a36Sopenharmony_ci * kernel's text (this value is passed in the image header).
3162306a36Sopenharmony_ci */
3262306a36Sopenharmony_ciunsigned long restore_jump_address __visible;
3362306a36Sopenharmony_ciunsigned long jump_address_phys;
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci/*
3662306a36Sopenharmony_ci * Value of the cr3 register from before the hibernation (this value is passed
3762306a36Sopenharmony_ci * in the image header).
3862306a36Sopenharmony_ci */
3962306a36Sopenharmony_ciunsigned long restore_cr3 __visible;
4062306a36Sopenharmony_ciunsigned long temp_pgt __visible;
4162306a36Sopenharmony_ciunsigned long relocated_restore_code __visible;
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci/**
4462306a36Sopenharmony_ci *	pfn_is_nosave - check if given pfn is in the 'nosave' section
4562306a36Sopenharmony_ci */
4662306a36Sopenharmony_ciint pfn_is_nosave(unsigned long pfn)
4762306a36Sopenharmony_ci{
4862306a36Sopenharmony_ci	unsigned long nosave_begin_pfn;
4962306a36Sopenharmony_ci	unsigned long nosave_end_pfn;
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci	nosave_begin_pfn = __pa_symbol(&__nosave_begin) >> PAGE_SHIFT;
5262306a36Sopenharmony_ci	nosave_end_pfn = PAGE_ALIGN(__pa_symbol(&__nosave_end)) >> PAGE_SHIFT;
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci	return pfn >= nosave_begin_pfn && pfn < nosave_end_pfn;
5562306a36Sopenharmony_ci}
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_cistruct restore_data_record {
5862306a36Sopenharmony_ci	unsigned long jump_address;
5962306a36Sopenharmony_ci	unsigned long jump_address_phys;
6062306a36Sopenharmony_ci	unsigned long cr3;
6162306a36Sopenharmony_ci	unsigned long magic;
6262306a36Sopenharmony_ci	unsigned long e820_checksum;
6362306a36Sopenharmony_ci};
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci/**
6662306a36Sopenharmony_ci * compute_e820_crc32 - calculate crc32 of a given e820 table
6762306a36Sopenharmony_ci *
6862306a36Sopenharmony_ci * @table: the e820 table to be calculated
6962306a36Sopenharmony_ci *
7062306a36Sopenharmony_ci * Return: the resulting checksum
7162306a36Sopenharmony_ci */
7262306a36Sopenharmony_cistatic inline u32 compute_e820_crc32(struct e820_table *table)
7362306a36Sopenharmony_ci{
7462306a36Sopenharmony_ci	int size = offsetof(struct e820_table, entries) +
7562306a36Sopenharmony_ci		sizeof(struct e820_entry) * table->nr_entries;
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci	return ~crc32_le(~0, (unsigned char const *)table, size);
7862306a36Sopenharmony_ci}
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci#ifdef CONFIG_X86_64
8162306a36Sopenharmony_ci#define RESTORE_MAGIC	0x23456789ABCDEF02UL
8262306a36Sopenharmony_ci#else
8362306a36Sopenharmony_ci#define RESTORE_MAGIC	0x12345679UL
8462306a36Sopenharmony_ci#endif
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci/**
8762306a36Sopenharmony_ci *	arch_hibernation_header_save - populate the architecture specific part
8862306a36Sopenharmony_ci *		of a hibernation image header
8962306a36Sopenharmony_ci *	@addr: address to save the data at
9062306a36Sopenharmony_ci */
9162306a36Sopenharmony_ciint arch_hibernation_header_save(void *addr, unsigned int max_size)
9262306a36Sopenharmony_ci{
9362306a36Sopenharmony_ci	struct restore_data_record *rdr = addr;
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	if (max_size < sizeof(struct restore_data_record))
9662306a36Sopenharmony_ci		return -EOVERFLOW;
9762306a36Sopenharmony_ci	rdr->magic = RESTORE_MAGIC;
9862306a36Sopenharmony_ci	rdr->jump_address = (unsigned long)restore_registers;
9962306a36Sopenharmony_ci	rdr->jump_address_phys = __pa_symbol(restore_registers);
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	/*
10262306a36Sopenharmony_ci	 * The restore code fixes up CR3 and CR4 in the following sequence:
10362306a36Sopenharmony_ci	 *
10462306a36Sopenharmony_ci	 * [in hibernation asm]
10562306a36Sopenharmony_ci	 * 1. CR3 <= temporary page tables
10662306a36Sopenharmony_ci	 * 2. CR4 <= mmu_cr4_features (from the kernel that restores us)
10762306a36Sopenharmony_ci	 * 3. CR3 <= rdr->cr3
10862306a36Sopenharmony_ci	 * 4. CR4 <= mmu_cr4_features (from us, i.e. the image kernel)
10962306a36Sopenharmony_ci	 * [in restore_processor_state()]
11062306a36Sopenharmony_ci	 * 5. CR4 <= saved CR4
11162306a36Sopenharmony_ci	 * 6. CR3 <= saved CR3
11262306a36Sopenharmony_ci	 *
11362306a36Sopenharmony_ci	 * Our mmu_cr4_features has CR4.PCIDE=0, and toggling
11462306a36Sopenharmony_ci	 * CR4.PCIDE while CR3's PCID bits are nonzero is illegal, so
11562306a36Sopenharmony_ci	 * rdr->cr3 needs to point to valid page tables but must not
11662306a36Sopenharmony_ci	 * have any of the PCID bits set.
11762306a36Sopenharmony_ci	 */
11862306a36Sopenharmony_ci	rdr->cr3 = restore_cr3 & ~CR3_PCID_MASK;
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	rdr->e820_checksum = compute_e820_crc32(e820_table_firmware);
12162306a36Sopenharmony_ci	return 0;
12262306a36Sopenharmony_ci}
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci/**
12562306a36Sopenharmony_ci *	arch_hibernation_header_restore - read the architecture specific data
12662306a36Sopenharmony_ci *		from the hibernation image header
12762306a36Sopenharmony_ci *	@addr: address to read the data from
12862306a36Sopenharmony_ci */
12962306a36Sopenharmony_ciint arch_hibernation_header_restore(void *addr)
13062306a36Sopenharmony_ci{
13162306a36Sopenharmony_ci	struct restore_data_record *rdr = addr;
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	if (rdr->magic != RESTORE_MAGIC) {
13462306a36Sopenharmony_ci		pr_crit("Unrecognized hibernate image header format!\n");
13562306a36Sopenharmony_ci		return -EINVAL;
13662306a36Sopenharmony_ci	}
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci	restore_jump_address = rdr->jump_address;
13962306a36Sopenharmony_ci	jump_address_phys = rdr->jump_address_phys;
14062306a36Sopenharmony_ci	restore_cr3 = rdr->cr3;
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	if (rdr->e820_checksum != compute_e820_crc32(e820_table_firmware)) {
14362306a36Sopenharmony_ci		pr_crit("Hibernate inconsistent memory map detected!\n");
14462306a36Sopenharmony_ci		return -ENODEV;
14562306a36Sopenharmony_ci	}
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	return 0;
14862306a36Sopenharmony_ci}
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ciint relocate_restore_code(void)
15162306a36Sopenharmony_ci{
15262306a36Sopenharmony_ci	pgd_t *pgd;
15362306a36Sopenharmony_ci	p4d_t *p4d;
15462306a36Sopenharmony_ci	pud_t *pud;
15562306a36Sopenharmony_ci	pmd_t *pmd;
15662306a36Sopenharmony_ci	pte_t *pte;
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci	relocated_restore_code = get_safe_page(GFP_ATOMIC);
15962306a36Sopenharmony_ci	if (!relocated_restore_code)
16062306a36Sopenharmony_ci		return -ENOMEM;
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci	__memcpy((void *)relocated_restore_code, core_restore_code, PAGE_SIZE);
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	/* Make the page containing the relocated code executable */
16562306a36Sopenharmony_ci	pgd = (pgd_t *)__va(read_cr3_pa()) +
16662306a36Sopenharmony_ci		pgd_index(relocated_restore_code);
16762306a36Sopenharmony_ci	p4d = p4d_offset(pgd, relocated_restore_code);
16862306a36Sopenharmony_ci	if (p4d_large(*p4d)) {
16962306a36Sopenharmony_ci		set_p4d(p4d, __p4d(p4d_val(*p4d) & ~_PAGE_NX));
17062306a36Sopenharmony_ci		goto out;
17162306a36Sopenharmony_ci	}
17262306a36Sopenharmony_ci	pud = pud_offset(p4d, relocated_restore_code);
17362306a36Sopenharmony_ci	if (pud_large(*pud)) {
17462306a36Sopenharmony_ci		set_pud(pud, __pud(pud_val(*pud) & ~_PAGE_NX));
17562306a36Sopenharmony_ci		goto out;
17662306a36Sopenharmony_ci	}
17762306a36Sopenharmony_ci	pmd = pmd_offset(pud, relocated_restore_code);
17862306a36Sopenharmony_ci	if (pmd_large(*pmd)) {
17962306a36Sopenharmony_ci		set_pmd(pmd, __pmd(pmd_val(*pmd) & ~_PAGE_NX));
18062306a36Sopenharmony_ci		goto out;
18162306a36Sopenharmony_ci	}
18262306a36Sopenharmony_ci	pte = pte_offset_kernel(pmd, relocated_restore_code);
18362306a36Sopenharmony_ci	set_pte(pte, __pte(pte_val(*pte) & ~_PAGE_NX));
18462306a36Sopenharmony_ciout:
18562306a36Sopenharmony_ci	__flush_tlb_all();
18662306a36Sopenharmony_ci	return 0;
18762306a36Sopenharmony_ci}
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ciint arch_resume_nosmt(void)
19062306a36Sopenharmony_ci{
19162306a36Sopenharmony_ci	int ret = 0;
19262306a36Sopenharmony_ci	/*
19362306a36Sopenharmony_ci	 * We reached this while coming out of hibernation. This means
19462306a36Sopenharmony_ci	 * that SMT siblings are sleeping in hlt, as mwait is not safe
19562306a36Sopenharmony_ci	 * against control transition during resume (see comment in
19662306a36Sopenharmony_ci	 * hibernate_resume_nonboot_cpu_disable()).
19762306a36Sopenharmony_ci	 *
19862306a36Sopenharmony_ci	 * If the resumed kernel has SMT disabled, we have to take all the
19962306a36Sopenharmony_ci	 * SMT siblings out of hlt, and offline them again so that they
20062306a36Sopenharmony_ci	 * end up in mwait proper.
20162306a36Sopenharmony_ci	 *
20262306a36Sopenharmony_ci	 * Called with hotplug disabled.
20362306a36Sopenharmony_ci	 */
20462306a36Sopenharmony_ci	cpu_hotplug_enable();
20562306a36Sopenharmony_ci	if (cpu_smt_control == CPU_SMT_DISABLED ||
20662306a36Sopenharmony_ci			cpu_smt_control == CPU_SMT_FORCE_DISABLED) {
20762306a36Sopenharmony_ci		enum cpuhp_smt_control old = cpu_smt_control;
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci		ret = cpuhp_smt_enable();
21062306a36Sopenharmony_ci		if (ret)
21162306a36Sopenharmony_ci			goto out;
21262306a36Sopenharmony_ci		ret = cpuhp_smt_disable(old);
21362306a36Sopenharmony_ci		if (ret)
21462306a36Sopenharmony_ci			goto out;
21562306a36Sopenharmony_ci	}
21662306a36Sopenharmony_ciout:
21762306a36Sopenharmony_ci	cpu_hotplug_disable();
21862306a36Sopenharmony_ci	return ret;
21962306a36Sopenharmony_ci}
220