// SPDX-License-Identifier: GPL-2.0-only /*: * Hibernate support specific for ARM64 * * Derived from work on ARM hibernation support by: * * Ubuntu project, hibernation support for mach-dove * Copyright (C) 2010 Nokia Corporation (Hiroshi Doyu) * Copyright (C) 2010 Texas Instruments, Inc. (Teerth Reddy et al.) * Copyright (C) 2006 Rafael J. Wysocki <rjw@sisk.pl>
*/ #define pr_fmt(x) "hibernate: " x #include <linux/cpu.h> #include <linux/kvm_host.h> #include <linux/pm.h> #include <linux/sched.h> #include <linux/suspend.h> #include <linux/utsname.h>
/* * Hibernate core relies on this value being 0 on resume, and marks it * __nosavedata assuming it will keep the resume kernel's '0' value. This * doesn't happen with either KASLR. * * defined as "__visible int in_suspend __nosavedata" in * kernel/power/hibernate.c
*/ externint in_suspend;
/* Do we need to reset el2? */ #define el2_reset_needed() (is_hyp_nvhe())
/* hyp-stub vectors, used to restore el2 during resume from hibernate. */ externchar __hyp_stub_vectors[];
/* * The logical cpu number we should resume on, initialised to a non-cpu * number.
*/ staticint sleep_cpu = -EINVAL;
/* * Values that may not change over hibernate/resume. We put the build number * and date in here so that we guarantee not to resume with a different * kernel.
*/ struct arch_hibernate_hdr_invariants { char uts_version[__NEW_UTS_LEN + 1];
};
/* These values need to be know across a hibernate/restore. */ staticstruct arch_hibernate_hdr { struct arch_hibernate_hdr_invariants invariants;
/* These are needed to find the relocated kernel if built with kaslr */
phys_addr_t ttbr1_el1; void (*reenter_kernel)(void);
/* * We need to know where the __hyp_stub_vectors are after restore to * re-configure el2.
*/
phys_addr_t __hyp_stub_vectors;
/* We can't use __hyp_get_vectors() because kvm may still be loaded */ if (el2_reset_needed())
hdr->__hyp_stub_vectors = __pa_symbol(__hyp_stub_vectors); else
hdr->__hyp_stub_vectors = 0;
/* Save the mpidr of the cpu we called cpu_suspend() on... */ if (sleep_cpu < 0) {
pr_err("Failing to hibernate on an unknown CPU.\n"); return -ENODEV;
}
hdr->sleep_cpu_mpidr = cpu_logical_map(sleep_cpu);
pr_info("Hibernating on CPU %d [mpidr:0x%llx]\n", sleep_cpu,
hdr->sleep_cpu_mpidr);
int arch_hibernation_header_restore(void *addr)
{ int ret; struct arch_hibernate_hdr_invariants invariants; struct arch_hibernate_hdr *hdr = addr;
arch_hdr_invariants(&invariants); if (memcmp(&hdr->invariants, &invariants, sizeof(invariants))) {
pr_crit("Hibernate image not generated by this kernel!\n"); return -EINVAL;
}
sleep_cpu = get_logical_index(hdr->sleep_cpu_mpidr);
pr_info("Hibernated on CPU %d [mpidr:0x%llx]\n", sleep_cpu,
hdr->sleep_cpu_mpidr); if (sleep_cpu < 0) {
pr_crit("Hibernated on a CPU not known to this kernel!\n");
sleep_cpu = -EINVAL; return -EINVAL;
}
ret = bringup_hibernate_cpu(sleep_cpu); if (ret) {
sleep_cpu = -EINVAL; return ret;
}
/* * Copies length bytes, starting at src_start into an new page, * perform cache maintenance, then maps it at the specified address low * address as executable. * * This is used by hibernate to copy the code it needs to execute when * overwriting the kernel text. This function generates a new set of page * tables, which it loads into ttbr0. * * Length is provided as we probably only want 4K of data, even on a 64K * page system.
*/ staticint create_safe_exec_page(void *src_start, size_t length,
phys_addr_t *phys_dst_addr)
{ struct trans_pgd_info trans_info = {
.trans_alloc_page = hibernate_page_alloc,
.trans_alloc_arg = (__force void *)GFP_ATOMIC,
};
/* Clean kvm setup code to PoC? */ if (el2_reset_needed()) {
dcache_clean_inval_poc(
(unsignedlong)__hyp_idmap_text_start,
(unsignedlong)__hyp_idmap_text_end);
dcache_clean_inval_poc((unsignedlong)__hyp_text_start,
(unsignedlong)__hyp_text_end);
}
swsusp_mte_restore_tags();
/* make the crash dump kernel image protected again */
crash_post_resume();
/* * Tell the hibernation core that we've just restored * the memory
*/
in_suspend = 0;
sleep_cpu = -EINVAL;
__cpu_suspend_exit();
/* * Just in case the boot kernel did turn the SSBD * mitigation off behind our back, let's set the state * to what we expect it to be.
*/
spectre_v4_enable_mitigation(NULL);
}
local_daif_restore(flags);
return ret;
}
/* * Setup then Resume from the hibernate image using swsusp_arch_suspend_exit(). * * Memory allocated by get_safe_page() will be dealt with by the hibernate code, * we don't need to free it here.
*/ int swsusp_arch_resume(void)
{ int rc; void *zero_page;
size_t exit_size;
pgd_t *tmp_pg_dir;
phys_addr_t el2_vectors; void __noreturn (*hibernate_exit)(phys_addr_t, phys_addr_t, void *, void *, phys_addr_t, phys_addr_t); struct trans_pgd_info trans_info = {
.trans_alloc_page = hibernate_page_alloc,
.trans_alloc_arg = (__force void *)GFP_ATOMIC,
};
/* * Restoring the memory image will overwrite the ttbr1 page tables. * Create a second copy of just the linear map, and use this when * restoring.
*/
rc = trans_pgd_create_copy(&trans_info, &tmp_pg_dir, PAGE_OFFSET,
PAGE_END); if (rc) return rc;
/* * We need a zero page that is zero before & after resume in order * to break before make on the ttbr1 page tables.
*/
zero_page = (void *)get_safe_page(GFP_ATOMIC); if (!zero_page) {
pr_err("Failed to allocate zero page.\n"); return -ENOMEM;
}
if (el2_reset_needed()) {
rc = trans_pgd_copy_el2_vectors(&trans_info, &el2_vectors); if (rc) {
pr_err("Failed to setup el2 vectors\n"); return rc;
}
}
exit_size = __hibernate_exit_text_end - __hibernate_exit_text_start; /* * Copy swsusp_arch_suspend_exit() to a safe page. This will generate * a new set of ttbr0 page tables and load them.
*/
rc = create_safe_exec_page(__hibernate_exit_text_start, exit_size,
(phys_addr_t *)&hibernate_exit); if (rc) {
pr_err("Failed to create safe executable page for hibernate_exit code.\n"); return rc;
}
/* * KASLR will cause the el2 vectors to be in a different location in * the resumed kernel. Load hibernate's temporary copy into el2. * * We can skip this step if we booted at EL1, or are running with VHE.
*/ if (el2_reset_needed())
__hyp_set_vectors(el2_vectors);
int hibernate_resume_nonboot_cpu_disable(void)
{ if (sleep_cpu < 0) {
pr_err("Failing to resume from hibernate on an unknown CPU.\n"); return -ENODEV;
}
return freeze_secondary_cpus(sleep_cpu);
}
Messung V0.5
¤ Dauer der Verarbeitung: 0.14 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.