Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+19
View File
@@ -0,0 +1,19 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for the linux kernel.
#
obj-y += core.o core_$(BITS).o ranges.o
obj-$(CONFIG_PPC32) += relocate_32.o
obj-$(CONFIG_KEXEC_FILE) += file_load.o file_load_$(BITS).o elf_$(BITS).o
obj-$(CONFIG_VMCORE_INFO) += vmcore_info.o
obj-$(CONFIG_CRASH_DUMP) += crash.o
# Disable GCOV, KCOV & sanitizers in odd or sensitive code
GCOV_PROFILE_core_$(BITS).o := n
KCOV_INSTRUMENT_core_$(BITS).o := n
UBSAN_SANITIZE_core_$(BITS).o := n
KASAN_SANITIZE_core.o := n
KASAN_SANITIZE_core_$(BITS).o := n
+227
View File
@@ -0,0 +1,227 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Code to handle transition of Linux booting another kernel.
*
* Copyright (C) 2002-2003 Eric Biederman <ebiederm@xmission.com>
* GameCube/ppc32 port Copyright (C) 2004 Albert Herranz
* Copyright (C) 2005 IBM Corporation.
*/
#include <linux/kexec.h>
#include <linux/reboot.h>
#include <linux/threads.h>
#include <linux/memblock.h>
#include <linux/of.h>
#include <linux/irq.h>
#include <linux/ftrace.h>
#include <asm/kdump.h>
#include <asm/machdep.h>
#include <asm/pgalloc.h>
#include <asm/sections.h>
#include <asm/setup.h>
#include <asm/firmware.h>
#define cpu_to_be_ulong __PASTE(cpu_to_be, BITS_PER_LONG)
#define __be_word __PASTE(__be, BITS_PER_LONG)
#ifdef CONFIG_CRASH_DUMP
void machine_crash_shutdown(struct pt_regs *regs)
{
default_machine_crash_shutdown(regs);
}
#endif
void machine_kexec_cleanup(struct kimage *image)
{
}
/*
* Do not allocate memory (or fail in any way) in machine_kexec().
* We are past the point of no return, committed to rebooting now.
*/
void machine_kexec(struct kimage *image)
{
int save_ftrace_enabled;
save_ftrace_enabled = __ftrace_enabled_save();
this_cpu_disable_ftrace();
if (ppc_md.machine_kexec)
ppc_md.machine_kexec(image);
else
default_machine_kexec(image);
this_cpu_enable_ftrace();
__ftrace_enabled_restore(save_ftrace_enabled);
/* Fall back to normal restart if we're still alive. */
machine_restart(NULL);
for(;;);
}
#ifdef CONFIG_CRASH_RESERVE
static unsigned long long crashk_cma_size;
static unsigned long long __init get_crash_base(unsigned long long crash_base)
{
#ifndef CONFIG_NONSTATIC_KERNEL
if (crash_base != KDUMP_KERNELBASE)
printk("Crash kernel location must be 0x%x\n",
KDUMP_KERNELBASE);
return KDUMP_KERNELBASE;
#else
unsigned long long crash_base_align;
if (!crash_base) {
#ifdef CONFIG_PPC64
/*
* On the LPAR platform place the crash kernel to mid of
* RMA size (max. of 512MB) to ensure the crash kernel
* gets enough space to place itself and some stack to be
* in the first segment. At the same time normal kernel
* also get enough space to allocate memory for essential
* system resource in the first segment. Keep the crash
* kernel starts at 128MB offset on other platforms.
*/
if (firmware_has_feature(FW_FEATURE_LPAR))
crash_base = min_t(u64, ppc64_rma_size / 2, SZ_512M);
else
crash_base = min_t(u64, ppc64_rma_size / 2, SZ_128M);
#else
crash_base = KDUMP_KERNELBASE;
#endif
}
crash_base_align = PAGE_ALIGN(crash_base);
if (crash_base != crash_base_align)
pr_warn("Crash kernel base must be aligned to 0x%lx\n", PAGE_SIZE);
return crash_base_align;
#endif
}
void __init arch_reserve_crashkernel(void)
{
unsigned long long crash_size, crash_base, crash_end;
unsigned long long kernel_start, kernel_size;
unsigned long long total_mem_sz;
int ret;
total_mem_sz = memory_limit ? memory_limit : memblock_phys_mem_size();
/* use common parsing */
ret = parse_crashkernel(boot_command_line, total_mem_sz, &crash_size,
&crash_base, NULL, &crashk_cma_size, NULL);
if (ret)
return;
crash_base = get_crash_base(crash_base);
crash_end = crash_base + crash_size - 1;
kernel_start = __pa(_stext);
kernel_size = _end - _stext;
/* The crash region must not overlap the current kernel */
if ((kernel_start + kernel_size > crash_base) && (kernel_start <= crash_end)) {
pr_warn("Crash kernel can not overlap current kernel\n");
return;
}
reserve_crashkernel_generic(crash_size, crash_base, 0, false);
}
void __init kdump_cma_reserve(void)
{
if (crashk_cma_size)
reserve_crashkernel_cma(crashk_cma_size);
}
int __init overlaps_crashkernel(unsigned long start, unsigned long size)
{
return (start + size) > crashk_res.start && start <= crashk_res.end;
}
/* Values we need to export to the second kernel via the device tree. */
static __be_word crashk_base;
static __be_word crashk_size;
static __be_word mem_limit;
static struct property crashk_base_prop = {
.name = "linux,crashkernel-base",
.length = sizeof(__be_word),
.value = &crashk_base
};
static struct property crashk_size_prop = {
.name = "linux,crashkernel-size",
.length = sizeof(__be_word),
.value = &crashk_size,
};
static struct property memory_limit_prop = {
.name = "linux,memory-limit",
.length = sizeof(__be_word),
.value = &mem_limit,
};
static void __init export_crashk_values(struct device_node *node)
{
/* There might be existing crash kernel properties, but we can't
* be sure what's in them, so remove them. */
of_remove_property(node, of_find_property(node,
"linux,crashkernel-base", NULL));
of_remove_property(node, of_find_property(node,
"linux,crashkernel-size", NULL));
if (crashk_res.start != 0) {
crashk_base = cpu_to_be_ulong(crashk_res.start),
of_add_property(node, &crashk_base_prop);
crashk_size = cpu_to_be_ulong(resource_size(&crashk_res));
of_add_property(node, &crashk_size_prop);
}
/*
* memory_limit is required by the kexec-tools to limit the
* crash regions to the actual memory used.
*/
mem_limit = cpu_to_be_ulong(memory_limit);
of_update_property(node, &memory_limit_prop);
}
#endif /* CONFIG_CRASH_RESERVE */
static __be_word kernel_end;
static struct property kernel_end_prop = {
.name = "linux,kernel-end",
.length = sizeof(__be_word),
.value = &kernel_end,
};
static int __init kexec_setup(void)
{
struct device_node *node;
node = of_find_node_by_path("/chosen");
if (!node)
return -ENOENT;
/* remove any stale properties so ours can be found */
of_remove_property(node, of_find_property(node, kernel_end_prop.name,
NULL));
/* information needed by userspace when using default_machine_kexec */
kernel_end = cpu_to_be_ulong(__pa(_end));
of_add_property(node, &kernel_end_prop);
#ifdef CONFIG_CRASH_RESERVE
export_crashk_values(node);
#endif
of_node_put(node);
return 0;
}
late_initcall(kexec_setup);
+70
View File
@@ -0,0 +1,70 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* PPC32 code to handle Linux booting another kernel.
*
* Copyright (C) 2002-2003 Eric Biederman <ebiederm@xmission.com>
* GameCube/ppc32 port Copyright (C) 2004 Albert Herranz
* Copyright (C) 2005 IBM Corporation.
*/
#include <linux/irq.h>
#include <linux/kexec.h>
#include <linux/mm.h>
#include <linux/string.h>
#include <asm/cacheflush.h>
#include <asm/hw_irq.h>
#include <asm/io.h>
typedef void (*relocate_new_kernel_t)(
unsigned long indirection_page,
unsigned long reboot_code_buffer,
unsigned long start_address) __noreturn;
/*
* This is a generic machine_kexec function suitable at least for
* non-OpenFirmware embedded platforms.
* It merely copies the image relocation code to the control page and
* jumps to it.
* A platform specific function may just call this one.
*/
void default_machine_kexec(struct kimage *image)
{
extern const unsigned int relocate_new_kernel_size;
unsigned long page_list;
unsigned long reboot_code_buffer, reboot_code_buffer_phys;
relocate_new_kernel_t rnk;
/* Interrupts aren't acceptable while we reboot */
local_irq_disable();
/* mask each interrupt so we are in a more sane state for the
* kexec kernel */
machine_kexec_mask_interrupts();
page_list = image->head;
/* we need both effective and real address here */
reboot_code_buffer =
(unsigned long)page_address(image->control_code_page);
reboot_code_buffer_phys = virt_to_phys((void *)reboot_code_buffer);
/* copy our kernel relocation code to the control code page */
memcpy((void *)reboot_code_buffer, relocate_new_kernel,
relocate_new_kernel_size);
flush_icache_range(reboot_code_buffer,
reboot_code_buffer + KEXEC_CONTROL_PAGE_SIZE);
printk(KERN_INFO "Bye!\n");
if (!IS_ENABLED(CONFIG_PPC_85xx) && !IS_ENABLED(CONFIG_44x))
relocate_new_kernel(page_list, reboot_code_buffer_phys, image->start);
/* now call it */
rnk = (relocate_new_kernel_t) reboot_code_buffer;
(*rnk)(page_list, reboot_code_buffer_phys, image->start);
}
int machine_kexec_prepare(struct kimage *image)
{
return 0;
}
+561
View File
@@ -0,0 +1,561 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* PPC64 code to handle Linux booting another kernel.
*
* Copyright (C) 2004-2005, IBM Corp.
*
* Created by: Milton D Miller II
*/
#include <linux/kexec.h>
#include <linux/smp.h>
#include <linux/thread_info.h>
#include <linux/init_task.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/cpu.h>
#include <linux/hardirq.h>
#include <linux/of.h>
#include <linux/libfdt.h>
#include <asm/page.h>
#include <asm/current.h>
#include <asm/machdep.h>
#include <asm/cacheflush.h>
#include <asm/firmware.h>
#include <asm/paca.h>
#include <asm/mmu.h>
#include <asm/sections.h> /* _end */
#include <asm/setup.h>
#include <asm/smp.h>
#include <asm/hw_breakpoint.h>
#include <asm/svm.h>
#include <asm/ultravisor.h>
#include <asm/crashdump-ppc64.h>
int machine_kexec_prepare(struct kimage *image)
{
int i;
unsigned long begin, end; /* limits of segment */
unsigned long low, high; /* limits of blocked memory range */
struct device_node *node;
const unsigned long *basep;
const unsigned int *sizep;
/*
* Since we use the kernel fault handlers and paging code to
* handle the virtual mode, we must make sure no destination
* overlaps kernel static data or bss.
*/
for (i = 0; i < image->nr_segments; i++)
if (image->segment[i].mem < __pa(_end))
return -ETXTBSY;
/* We also should not overwrite the tce tables */
for_each_node_by_type(node, "pci") {
basep = of_get_property(node, "linux,tce-base", NULL);
sizep = of_get_property(node, "linux,tce-size", NULL);
if (basep == NULL || sizep == NULL)
continue;
low = *basep;
high = low + (*sizep);
for (i = 0; i < image->nr_segments; i++) {
begin = image->segment[i].mem;
end = begin + image->segment[i].memsz;
if ((begin < high) && (end > low)) {
of_node_put(node);
return -ETXTBSY;
}
}
}
return 0;
}
/* Called during kexec sequence with MMU off */
static notrace void copy_segments(unsigned long ind)
{
unsigned long entry;
unsigned long *ptr;
void *dest;
void *addr;
/*
* We rely on kexec_load to create a lists that properly
* initializes these pointers before they are used.
* We will still crash if the list is wrong, but at least
* the compiler will be quiet.
*/
ptr = NULL;
dest = NULL;
for (entry = ind; !(entry & IND_DONE); entry = *ptr++) {
addr = __va(entry & PAGE_MASK);
switch (entry & IND_FLAGS) {
case IND_DESTINATION:
dest = addr;
break;
case IND_INDIRECTION:
ptr = addr;
break;
case IND_SOURCE:
copy_page(dest, addr);
dest += PAGE_SIZE;
}
}
}
/* Called during kexec sequence with MMU off */
notrace void kexec_copy_flush(struct kimage *image)
{
long i, nr_segments = image->nr_segments;
struct kexec_segment ranges[KEXEC_SEGMENT_MAX];
/* save the ranges on the stack to efficiently flush the icache */
memcpy(ranges, image->segment, sizeof(ranges));
/*
* After this call we may not use anything allocated in dynamic
* memory, including *image.
*
* Only globals and the stack are allowed.
*/
copy_segments(image->head);
/*
* we need to clear the icache for all dest pages sometime,
* including ones that were in place on the original copy
*/
for (i = 0; i < nr_segments; i++)
flush_icache_range((unsigned long)__va(ranges[i].mem),
(unsigned long)__va(ranges[i].mem + ranges[i].memsz));
}
#ifdef CONFIG_SMP
static int kexec_all_irq_disabled = 0;
static void kexec_smp_down(void *arg)
{
local_irq_disable();
hard_irq_disable();
mb(); /* make sure our irqs are disabled before we say they are */
get_paca()->kexec_state = KEXEC_STATE_IRQS_OFF;
while(kexec_all_irq_disabled == 0)
cpu_relax();
mb(); /* make sure all irqs are disabled before this */
hw_breakpoint_disable();
/*
* Now every CPU has IRQs off, we can clear out any pending
* IPIs and be sure that no more will come in after this.
*/
if (ppc_md.kexec_cpu_down)
ppc_md.kexec_cpu_down(0, 1);
reset_sprs();
kexec_smp_wait();
/* NOTREACHED */
}
static void kexec_prepare_cpus_wait(int wait_state)
{
int my_cpu, i, notified=-1;
hw_breakpoint_disable();
my_cpu = raw_smp_processor_id();
/* Make sure each CPU has at least made it to the state we need.
*
* FIXME: There is a (slim) chance of a problem if not all of the CPUs
* are correctly onlined. If somehow we start a CPU on boot with RTAS
* start-cpu, but somehow that CPU doesn't write callin_cpu_map[] in
* time, the boot CPU will timeout. If it does eventually execute
* stuff, the secondary will start up (paca_ptrs[]->cpu_start was
* written) and get into a peculiar state.
* If the platform supports smp_ops->take_timebase(), the secondary CPU
* will probably be spinning in there. If not (i.e. pseries), the
* secondary will continue on and try to online itself/idle/etc. If it
* survives that, we need to find these
* possible-but-not-online-but-should-be CPUs and chaperone them into
* kexec_smp_wait().
*/
for_each_online_cpu(i) {
if (i == my_cpu)
continue;
while (paca_ptrs[i]->kexec_state < wait_state) {
barrier();
if (i != notified) {
printk(KERN_INFO "kexec: waiting for cpu %d "
"(physical %d) to enter %i state\n",
i, paca_ptrs[i]->hw_cpu_id, wait_state);
notified = i;
}
}
}
mb();
}
/*
* The add_cpu() call in wake_offline_cpus() can fail as cpu_bootable()
* returns false for CPUs that fail the cpu_smt_thread_allowed() check
* or non primary threads if SMT is disabled. Re-enable SMT and set the
* number of SMT threads to threads per core.
*/
static void kexec_smt_reenable(void)
{
#if defined(CONFIG_SMP) && defined(CONFIG_HOTPLUG_SMT)
lock_device_hotplug();
cpu_smt_num_threads = threads_per_core;
cpu_smt_control = CPU_SMT_ENABLED;
unlock_device_hotplug();
#endif
}
/*
* We need to make sure each present CPU is online. The next kernel will scan
* the device tree and assume primary threads are online and query secondary
* threads via RTAS to online them if required. If we don't online primary
* threads, they will be stuck. However, we also online secondary threads as we
* may be using 'cede offline'. In this case RTAS doesn't see the secondary
* threads as offline -- and again, these CPUs will be stuck.
*
* So, we online all CPUs that should be running, including secondary threads.
*/
static void wake_offline_cpus(void)
{
int cpu = 0;
kexec_smt_reenable();
for_each_present_cpu(cpu) {
if (!cpu_online(cpu)) {
printk(KERN_INFO "kexec: Waking offline cpu %d.\n",
cpu);
WARN_ON(add_cpu(cpu));
}
}
}
static void kexec_prepare_cpus(void)
{
wake_offline_cpus();
smp_call_function(kexec_smp_down, NULL, /* wait */0);
local_irq_disable();
hard_irq_disable();
mb(); /* make sure IRQs are disabled before we say they are */
get_paca()->kexec_state = KEXEC_STATE_IRQS_OFF;
kexec_prepare_cpus_wait(KEXEC_STATE_IRQS_OFF);
/* we are sure every CPU has IRQs off at this point */
kexec_all_irq_disabled = 1;
/*
* Before removing MMU mappings make sure all CPUs have entered real
* mode:
*/
kexec_prepare_cpus_wait(KEXEC_STATE_REAL_MODE);
/* after we tell the others to go down */
if (ppc_md.kexec_cpu_down)
ppc_md.kexec_cpu_down(0, 0);
}
#else /* ! SMP */
static void kexec_prepare_cpus(void)
{
/*
* move the secondarys to us so that we can copy
* the new kernel 0-0x100 safely
*
* do this if kexec in setup.c ?
*
* We need to release the cpus if we are ever going from an
* UP to an SMP kernel.
*/
smp_release_cpus();
if (ppc_md.kexec_cpu_down)
ppc_md.kexec_cpu_down(0, 0);
local_irq_disable();
hard_irq_disable();
}
#endif /* SMP */
/*
* kexec thread structure and stack.
*
* We need to make sure that this is 16384-byte aligned due to the
* way process stacks are handled. It also must be statically allocated
* or allocated as part of the kimage, because everything else may be
* overwritten when we copy the kexec image. We piggyback on the
* "init_task" linker section here to statically allocate a stack.
*
* We could use a smaller stack if we don't care about anything using
* current, but that audit has not been performed.
*/
static union thread_union kexec_stack = { };
/*
* For similar reasons to the stack above, the kexecing CPU needs to be on a
* static PACA; we switch to kexec_paca.
*/
static struct paca_struct kexec_paca;
/* Our assembly helper, in misc_64.S */
extern void kexec_sequence(void *newstack, unsigned long start,
void *image, void *control,
void (*clear_all)(void),
bool copy_with_mmu_off) __noreturn;
/* too late to fail here */
void default_machine_kexec(struct kimage *image)
{
bool copy_with_mmu_off;
/* prepare control code if any */
/*
* If the kexec boot is the normal one, need to shutdown other cpus
* into our wait loop and quiesce interrupts.
* Otherwise, in the case of crashed mode (crashing_cpu >= 0),
* stopping other CPUs and collecting their pt_regs is done before
* using debugger IPI.
*/
if (!kdump_in_progress())
kexec_prepare_cpus();
#ifdef CONFIG_PPC_PSERIES
/*
* This must be done after other CPUs have shut down, otherwise they
* could execute the 'scv' instruction, which is not supported with
* reloc disabled (see configure_exceptions()).
*/
if (firmware_has_feature(FW_FEATURE_SET_MODE))
pseries_disable_reloc_on_exc();
#endif
printk("kexec: Starting switchover sequence.\n");
/* switch to a staticly allocated stack. Based on irq stack code.
* We setup preempt_count to avoid using VMX in memcpy.
* XXX: the task struct will likely be invalid once we do the copy!
*/
current_thread_info()->flags = 0;
current_thread_info()->preempt_count = HARDIRQ_OFFSET;
/* We need a static PACA, too; copy this CPU's PACA over and switch to
* it. Also poison per_cpu_offset and NULL lppaca to catch anyone using
* non-static data.
*/
memcpy(&kexec_paca, get_paca(), sizeof(struct paca_struct));
kexec_paca.data_offset = 0xedeaddeadeeeeeeeUL;
#ifdef CONFIG_PPC_PSERIES
kexec_paca.lppaca_ptr = NULL;
#endif
if (is_secure_guest() && !(image->preserve_context ||
image->type == KEXEC_TYPE_CRASH)) {
uv_unshare_all_pages();
printk("kexec: Unshared all shared pages.\n");
}
paca_ptrs[kexec_paca.paca_index] = &kexec_paca;
setup_paca(&kexec_paca);
/*
* The lppaca should be unregistered at this point so the HV won't
* touch it. In the case of a crash, none of the lppacas are
* unregistered so there is not much we can do about it here.
*/
/*
* On Book3S, the copy must happen with the MMU off if we are either
* using Radix page tables or we are not in an LPAR since we can
* overwrite the page tables while copying.
*
* In an LPAR, we keep the MMU on otherwise we can't access beyond
* the RMA. On BookE there is no real MMU off mode, so we have to
* keep it enabled as well (but then we have bolted TLB entries).
*/
#ifdef CONFIG_PPC_BOOK3E_64
copy_with_mmu_off = false;
#else
copy_with_mmu_off = radix_enabled() ||
!(firmware_has_feature(FW_FEATURE_LPAR) ||
firmware_has_feature(FW_FEATURE_PS3_LV1));
#endif
/* Some things are best done in assembly. Finding globals with
* a toc is easier in C, so pass in what we can.
*/
kexec_sequence(&kexec_stack, image->start, image,
page_address(image->control_code_page),
mmu_cleanup_all, copy_with_mmu_off);
/* NOTREACHED */
}
#ifdef CONFIG_PPC_64S_HASH_MMU
/* Values we need to export to the second kernel via the device tree. */
static __be64 htab_base;
static __be64 htab_size;
static struct property htab_base_prop = {
.name = "linux,htab-base",
.length = sizeof(unsigned long),
.value = &htab_base,
};
static struct property htab_size_prop = {
.name = "linux,htab-size",
.length = sizeof(unsigned long),
.value = &htab_size,
};
static int __init export_htab_values(void)
{
struct device_node *node;
/* On machines with no htab htab_address is NULL */
if (!htab_address)
return -ENODEV;
node = of_find_node_by_path("/chosen");
if (!node)
return -ENODEV;
/* remove any stale properties so ours can be found */
of_remove_property(node, of_find_property(node, htab_base_prop.name, NULL));
of_remove_property(node, of_find_property(node, htab_size_prop.name, NULL));
htab_base = cpu_to_be64(__pa(htab_address));
of_add_property(node, &htab_base_prop);
htab_size = cpu_to_be64(htab_size_bytes);
of_add_property(node, &htab_size_prop);
of_node_put(node);
return 0;
}
late_initcall(export_htab_values);
#endif /* CONFIG_PPC_64S_HASH_MMU */
#if defined(CONFIG_KEXEC_FILE) || defined(CONFIG_CRASH_DUMP)
/**
* add_node_props - Reads node properties from device node structure and add
* them to fdt.
* @fdt: Flattened device tree of the kernel
* @node_offset: offset of the node to add a property at
* @dn: device node pointer
*
* Returns 0 on success, negative errno on error.
*/
static int add_node_props(void *fdt, int node_offset, const struct device_node *dn)
{
int ret = 0;
struct property *pp;
if (!dn)
return -EINVAL;
for_each_property_of_node(dn, pp) {
ret = fdt_setprop(fdt, node_offset, pp->name, pp->value, pp->length);
if (ret < 0) {
pr_err("Unable to add %s property: %s\n", pp->name, fdt_strerror(ret));
return ret;
}
}
return ret;
}
/**
* update_cpus_node - Update cpus node of flattened device tree using of_root
* device node.
* @fdt: Flattened device tree of the kernel.
*
* Returns 0 on success, negative errno on error.
*
* Note: expecting no subnodes under /cpus/<node> with device_type == "cpu".
* If this changes, update this function to include them.
*/
int update_cpus_node(void *fdt)
{
int prev_node_offset;
const char *device_type;
const struct fdt_property *prop;
struct device_node *cpus_node, *dn;
int cpus_offset, cpus_subnode_offset, ret = 0;
cpus_offset = fdt_path_offset(fdt, "/cpus");
if (cpus_offset < 0 && cpus_offset != -FDT_ERR_NOTFOUND) {
pr_err("Malformed device tree: error reading /cpus node: %s\n",
fdt_strerror(cpus_offset));
return cpus_offset;
}
prev_node_offset = cpus_offset;
/* Delete sub-nodes of /cpus node with device_type == "cpu" */
for (cpus_subnode_offset = fdt_first_subnode(fdt, cpus_offset); cpus_subnode_offset >= 0;) {
/* Ignore nodes that do not have a device_type property or device_type != "cpu" */
prop = fdt_get_property(fdt, cpus_subnode_offset, "device_type", NULL);
if (!prop || strcmp(prop->data, "cpu")) {
prev_node_offset = cpus_subnode_offset;
goto next_node;
}
ret = fdt_del_node(fdt, cpus_subnode_offset);
if (ret < 0) {
pr_err("Failed to delete a cpus sub-node: %s\n", fdt_strerror(ret));
return ret;
}
next_node:
if (prev_node_offset == cpus_offset)
cpus_subnode_offset = fdt_first_subnode(fdt, cpus_offset);
else
cpus_subnode_offset = fdt_next_subnode(fdt, prev_node_offset);
}
cpus_node = of_find_node_by_path("/cpus");
/* Fail here to avoid kexec/kdump kernel boot hung */
if (!cpus_node) {
pr_err("No /cpus node found\n");
return -EINVAL;
}
/* Add all /cpus sub-nodes of device_type == "cpu" to FDT */
for_each_child_of_node(cpus_node, dn) {
/* Ignore device nodes that do not have a device_type property
* or device_type != "cpu".
*/
device_type = of_get_property(dn, "device_type", NULL);
if (!device_type || strcmp(device_type, "cpu"))
continue;
cpus_subnode_offset = fdt_add_subnode(fdt, cpus_offset, dn->full_name);
if (cpus_subnode_offset < 0) {
pr_err("Unable to add %s subnode: %s\n", dn->full_name,
fdt_strerror(cpus_subnode_offset));
ret = cpus_subnode_offset;
goto out;
}
ret = add_node_props(fdt, cpus_subnode_offset, dn);
if (ret < 0)
goto out;
}
out:
of_node_put(cpus_node);
of_node_put(dn);
return ret;
}
#endif /* CONFIG_KEXEC_FILE || CONFIG_CRASH_DUMP */
+656
View File
@@ -0,0 +1,656 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Architecture specific (PPC64) functions for kexec based crash dumps.
*
* Copyright (C) 2005, IBM Corp.
*
* Created by: Haren Myneni
*/
#include <linux/kernel.h>
#include <linux/smp.h>
#include <linux/reboot.h>
#include <linux/kexec.h>
#include <linux/export.h>
#include <linux/crash_dump.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <linux/types.h>
#include <linux/libfdt.h>
#include <linux/memory.h>
#include <asm/processor.h>
#include <asm/machdep.h>
#include <asm/kexec.h>
#include <asm/smp.h>
#include <asm/setjmp.h>
#include <asm/debug.h>
#include <asm/interrupt.h>
#include <asm/kexec_ranges.h>
#include <asm/crashdump-ppc64.h>
/*
* The primary CPU waits a while for all secondary CPUs to enter. This is to
* avoid sending an IPI if the secondary CPUs are entering
* crash_kexec_secondary on their own (eg via a system reset).
*
* The secondary timeout has to be longer than the primary. Both timeouts are
* in milliseconds.
*/
#define PRIMARY_TIMEOUT 500
#define SECONDARY_TIMEOUT 1000
#define IPI_TIMEOUT 10000
#define REAL_MODE_TIMEOUT 10000
static int time_to_dump;
/*
* In case of system reset, secondary CPUs enter crash_kexec_secondary with out
* having to send an IPI explicitly. So, indicate if the crash is via
* system reset to avoid sending another IPI.
*/
static int is_via_system_reset;
/*
* crash_wake_offline should be set to 1 by platforms that intend to wake
* up offline cpus prior to jumping to a kdump kernel. Currently powernv
* sets it to 1, since we want to avoid things from happening when an
* offline CPU wakes up due to something like an HMI (malfunction error),
* which propagates to all threads.
*/
int crash_wake_offline;
#define CRASH_HANDLER_MAX 3
/* List of shutdown handles */
static crash_shutdown_t crash_shutdown_handles[CRASH_HANDLER_MAX];
static DEFINE_SPINLOCK(crash_handlers_lock);
static unsigned long crash_shutdown_buf[JMP_BUF_LEN];
static int crash_shutdown_cpu = -1;
static int handle_fault(struct pt_regs *regs)
{
if (crash_shutdown_cpu == smp_processor_id())
longjmp(crash_shutdown_buf, 1);
return 0;
}
#ifdef CONFIG_SMP
static atomic_t cpus_in_crash;
void crash_ipi_callback(struct pt_regs *regs)
{
static cpumask_t cpus_state_saved = CPU_MASK_NONE;
int cpu = smp_processor_id();
hard_irq_disable();
if (!cpumask_test_cpu(cpu, &cpus_state_saved)) {
crash_save_cpu(regs, cpu);
cpumask_set_cpu(cpu, &cpus_state_saved);
}
atomic_inc(&cpus_in_crash);
smp_mb__after_atomic();
/*
* Starting the kdump boot.
* This barrier is needed to make sure that all CPUs are stopped.
*/
while (!time_to_dump)
cpu_relax();
if (ppc_md.kexec_cpu_down)
ppc_md.kexec_cpu_down(1, 1);
#ifdef CONFIG_PPC64
kexec_smp_wait();
#else
for (;;); /* FIXME */
#endif
/* NOTREACHED */
}
static void crash_kexec_prepare_cpus(void)
{
unsigned int msecs;
volatile unsigned int ncpus = num_online_cpus() - 1;/* Excluding the panic cpu */
volatile int tries = 0;
int (*old_handler)(struct pt_regs *regs);
printk(KERN_EMERG "Sending IPI to other CPUs\n");
if (crash_wake_offline)
ncpus = num_present_cpus() - 1;
/*
* If we came in via system reset, secondaries enter via crash_kexec_secondary().
* So, wait a while for the secondary CPUs to enter for that case.
* Else, send IPI to all other CPUs.
*/
if (is_via_system_reset)
mdelay(PRIMARY_TIMEOUT);
else
crash_send_ipi(crash_ipi_callback);
smp_wmb();
again:
/*
* FIXME: Until we will have the way to stop other CPUs reliably,
* the crash CPU will send an IPI and wait for other CPUs to
* respond.
*/
msecs = IPI_TIMEOUT;
while ((atomic_read(&cpus_in_crash) < ncpus) && (--msecs > 0))
mdelay(1);
/* Would it be better to replace the trap vector here? */
if (atomic_read(&cpus_in_crash) >= ncpus) {
printk(KERN_EMERG "IPI complete\n");
return;
}
printk(KERN_EMERG "ERROR: %d cpu(s) not responding\n",
ncpus - atomic_read(&cpus_in_crash));
/*
* If we have a panic timeout set then we can't wait indefinitely
* for someone to activate system reset. We also give up on the
* second time through if system reset fail to work.
*/
if ((panic_timeout > 0) || (tries > 0))
return;
/*
* A system reset will cause all CPUs to take an 0x100 exception.
* The primary CPU returns here via setjmp, and the secondary
* CPUs reexecute the crash_kexec_secondary path.
*/
old_handler = __debugger;
__debugger = handle_fault;
crash_shutdown_cpu = smp_processor_id();
if (setjmp(crash_shutdown_buf) == 0) {
printk(KERN_EMERG "Activate system reset (dumprestart) "
"to stop other cpu(s)\n");
/*
* A system reset will force all CPUs to execute the
* crash code again. We need to reset cpus_in_crash so we
* wait for everyone to do this.
*/
atomic_set(&cpus_in_crash, 0);
smp_mb();
while (atomic_read(&cpus_in_crash) < ncpus)
cpu_relax();
}
crash_shutdown_cpu = -1;
__debugger = old_handler;
tries++;
goto again;
}
/*
* This function will be called by secondary cpus.
*/
void crash_kexec_secondary(struct pt_regs *regs)
{
unsigned long flags;
int msecs = SECONDARY_TIMEOUT;
local_irq_save(flags);
/* Wait for the primary crash CPU to signal its progress */
while (crashing_cpu < 0) {
if (--msecs < 0) {
/* No response, kdump image may not have been loaded */
local_irq_restore(flags);
return;
}
mdelay(1);
}
crash_ipi_callback(regs);
}
#else /* ! CONFIG_SMP */
static void crash_kexec_prepare_cpus(void)
{
/*
* move the secondaries to us so that we can copy
* the new kernel 0-0x100 safely
*
* do this if kexec in setup.c ?
*/
#ifdef CONFIG_PPC64
smp_release_cpus();
#else
/* FIXME */
#endif
}
void crash_kexec_secondary(struct pt_regs *regs)
{
}
#endif /* CONFIG_SMP */
/* wait for all the CPUs to hit real mode but timeout if they don't come in */
#if defined(CONFIG_SMP) && defined(CONFIG_PPC64)
noinstr static void __maybe_unused crash_kexec_wait_realmode(int cpu)
{
unsigned int msecs;
int i;
msecs = REAL_MODE_TIMEOUT;
for (i=0; i < nr_cpu_ids && msecs > 0; i++) {
if (i == cpu)
continue;
while (paca_ptrs[i]->kexec_state < KEXEC_STATE_REAL_MODE) {
barrier();
if (!cpu_possible(i) || !cpu_online(i) || (msecs <= 0))
break;
msecs--;
mdelay(1);
}
}
mb();
}
#else
static inline void crash_kexec_wait_realmode(int cpu) {}
#endif /* CONFIG_SMP && CONFIG_PPC64 */
void crash_kexec_prepare(void)
{
/* Avoid hardlocking with irresponsive CPU holding logbuf_lock */
printk_deferred_enter();
/*
* This function is only called after the system
* has panicked or is otherwise in a critical state.
* The minimum amount of code to allow a kexec'd kernel
* to run successfully needs to happen here.
*
* In practice this means stopping other cpus in
* an SMP system.
* The kernel is broken so disable interrupts.
*/
hard_irq_disable();
/*
* Make a note of crashing cpu. Will be used in machine_kexec
* such that another IPI will not be sent.
*/
crashing_cpu = smp_processor_id();
crash_kexec_prepare_cpus();
}
/*
* Register a function to be called on shutdown. Only use this if you
* can't reset your device in the second kernel.
*/
int crash_shutdown_register(crash_shutdown_t handler)
{
unsigned int i, rc;
spin_lock(&crash_handlers_lock);
for (i = 0 ; i < CRASH_HANDLER_MAX; i++)
if (!crash_shutdown_handles[i]) {
/* Insert handle at first empty entry */
crash_shutdown_handles[i] = handler;
rc = 0;
break;
}
if (i == CRASH_HANDLER_MAX) {
printk(KERN_ERR "Crash shutdown handles full, "
"not registered.\n");
rc = 1;
}
spin_unlock(&crash_handlers_lock);
return rc;
}
EXPORT_SYMBOL(crash_shutdown_register);
int crash_shutdown_unregister(crash_shutdown_t handler)
{
unsigned int i, rc;
spin_lock(&crash_handlers_lock);
for (i = 0 ; i < CRASH_HANDLER_MAX; i++)
if (crash_shutdown_handles[i] == handler)
break;
if (i == CRASH_HANDLER_MAX) {
printk(KERN_ERR "Crash shutdown handle not found\n");
rc = 1;
} else {
/* Shift handles down */
for (; i < (CRASH_HANDLER_MAX - 1); i++)
crash_shutdown_handles[i] =
crash_shutdown_handles[i+1];
/*
* Reset last entry to NULL now that it has been shifted down,
* this will allow new handles to be added here.
*/
crash_shutdown_handles[i] = NULL;
rc = 0;
}
spin_unlock(&crash_handlers_lock);
return rc;
}
EXPORT_SYMBOL(crash_shutdown_unregister);
void default_machine_crash_shutdown(struct pt_regs *regs)
{
volatile unsigned int i;
int (*old_handler)(struct pt_regs *regs);
if (TRAP(regs) == INTERRUPT_SYSTEM_RESET)
is_via_system_reset = 1;
if (IS_ENABLED(CONFIG_SMP))
crash_smp_send_stop();
else
crash_kexec_prepare();
crash_save_cpu(regs, crashing_cpu);
time_to_dump = 1;
crash_kexec_wait_realmode(crashing_cpu);
machine_kexec_mask_interrupts();
/*
* Call registered shutdown routines safely. Swap out
* __debugger_fault_handler, and replace on exit.
*/
old_handler = __debugger_fault_handler;
__debugger_fault_handler = handle_fault;
crash_shutdown_cpu = smp_processor_id();
for (i = 0; i < CRASH_HANDLER_MAX && crash_shutdown_handles[i]; i++) {
if (setjmp(crash_shutdown_buf) == 0) {
/*
* Insert syncs and delay to ensure
* instructions in the dangerous region don't
* leak away from this protected region.
*/
asm volatile("sync; isync");
/* dangerous region */
crash_shutdown_handles[i]();
asm volatile("sync; isync");
}
}
crash_shutdown_cpu = -1;
__debugger_fault_handler = old_handler;
if (ppc_md.kexec_cpu_down)
ppc_md.kexec_cpu_down(1, 0);
}
#ifdef CONFIG_CRASH_DUMP
/**
* sync_backup_region_phdr - synchronize backup region offset between
* kexec image and ELF core header.
* @image: Kexec image.
* @ehdr: ELF core header.
* @phdr_to_kimage: If true, read the offset from the ELF program header
* and update the kimage backup region. If false, update
* the ELF program header offset from the kimage backup
* region.
*
* Note: During kexec_load, this is called with phdr_to_kimage = true. For
* kexec_file_load and ELF core header recreation during memory hotplug
* events, it is called with phdr_to_kimage = false.
*
* Returns nothing.
*/
void sync_backup_region_phdr(struct kimage *image, Elf64_Ehdr *ehdr, bool phdr_to_kimage)
{
Elf64_Phdr *phdr;
unsigned int i;
phdr = (Elf64_Phdr *)(ehdr + 1);
for (i = 0; i < ehdr->e_phnum; i++, phdr++) {
if (phdr->p_paddr == BACKUP_SRC_START) {
if (phdr_to_kimage)
image->arch.backup_start = phdr->p_offset;
else
phdr->p_offset = image->arch.backup_start;
kexec_dprintk("Backup region offset updated to 0x%lx\n",
image->arch.backup_start);
return;
}
}
}
#endif /* CONFIG_CRASH_DUMP */
#ifdef CONFIG_CRASH_HOTPLUG
int machine_kexec_post_load(struct kimage *image)
{
int i;
unsigned long mem;
unsigned char *ptr;
if (image->type != KEXEC_TYPE_CRASH)
return 0;
if (image->file_mode)
return 0;
for (i = 0; i < image->nr_segments; i++) {
mem = image->segment[i].mem;
ptr = (char *)__va(mem);
if (ptr && memcmp(ptr, ELFMAG, SELFMAG) == 0)
sync_backup_region_phdr(image, (Elf64_Ehdr *) ptr, true);
}
return 0;
}
#undef pr_fmt
#define pr_fmt(fmt) "crash hp: " fmt
/*
* Advertise preferred elfcorehdr size to userspace via
* /sys/kernel/crash_elfcorehdr_size sysfs interface.
*/
unsigned int arch_crash_get_elfcorehdr_size(void)
{
unsigned long phdr_cnt;
/* A program header for possible CPUs + vmcoreinfo */
phdr_cnt = num_possible_cpus() + 1;
if (IS_ENABLED(CONFIG_MEMORY_HOTPLUG))
phdr_cnt += CONFIG_CRASH_MAX_MEMORY_RANGES;
return sizeof(struct elfhdr) + (phdr_cnt * sizeof(Elf64_Phdr));
}
/**
* update_crash_elfcorehdr() - Recreate the elfcorehdr and replace it with old
* elfcorehdr in the kexec segment array.
* @image: the active struct kimage
* @mn: struct memory_notify data handler
*/
static void update_crash_elfcorehdr(struct kimage *image, struct memory_notify *mn)
{
int ret;
struct crash_mem *cmem = NULL;
struct kexec_segment *ksegment;
void *ptr, *mem, *elfbuf = NULL;
unsigned long elfsz, memsz, base_addr, size;
ksegment = &image->segment[image->elfcorehdr_index];
mem = (void *) ksegment->mem;
memsz = ksegment->memsz;
ret = get_crash_memory_ranges(&cmem);
if (ret) {
pr_err("Failed to get crash mem range\n");
return;
}
/*
* The hot unplugged memory is part of crash memory ranges,
* remove it here.
*/
if (image->hp_action == KEXEC_CRASH_HP_REMOVE_MEMORY) {
base_addr = PFN_PHYS(mn->start_pfn);
size = mn->nr_pages * PAGE_SIZE;
ret = remove_mem_range(&cmem, base_addr, size);
if (ret) {
pr_err("Failed to remove hot-unplugged memory from crash memory ranges\n");
goto out;
}
}
ret = crash_prepare_elf64_headers(cmem, false, &elfbuf, &elfsz);
if (ret) {
pr_err("Failed to prepare elf header\n");
goto out;
}
/*
* It is unlikely that kernel hit this because elfcorehdr kexec
* segment (memsz) is built with addition space to accommodate growing
* number of crash memory ranges while loading the kdump kernel. It is
* Just to avoid any unforeseen case.
*/
if (elfsz > memsz) {
pr_err("Updated crash elfcorehdr elfsz %lu > memsz %lu", elfsz, memsz);
goto out;
}
sync_backup_region_phdr(image, (Elf64_Ehdr *) elfbuf, false);
ptr = __va(mem);
if (ptr) {
/* Temporarily invalidate the crash image while it is replaced */
xchg(&kexec_crash_image, NULL);
/* Replace the old elfcorehdr with newly prepared elfcorehdr */
memcpy((void *)ptr, elfbuf, elfsz);
/* The crash image is now valid once again */
xchg(&kexec_crash_image, image);
}
out:
kvfree(cmem);
kvfree(elfbuf);
}
/**
* get_fdt_index - Loop through the kexec segment array and find
* the index of the FDT segment.
* @image: a pointer to kexec_crash_image
*
* Returns the index of FDT segment in the kexec segment array
* if found; otherwise -1.
*/
static int get_fdt_index(struct kimage *image)
{
void *ptr;
unsigned long mem;
int i, fdt_index = -1;
/* Find the FDT segment index in kexec segment array. */
for (i = 0; i < image->nr_segments; i++) {
mem = image->segment[i].mem;
ptr = __va(mem);
if (ptr && fdt_magic(ptr) == FDT_MAGIC) {
fdt_index = i;
break;
}
}
return fdt_index;
}
/**
* update_crash_fdt - updates the cpus node of the crash FDT.
*
* @image: a pointer to kexec_crash_image
*/
static void update_crash_fdt(struct kimage *image)
{
void *fdt;
int fdt_index;
fdt_index = get_fdt_index(image);
if (fdt_index < 0) {
pr_err("Unable to locate FDT segment.\n");
return;
}
fdt = __va((void *)image->segment[fdt_index].mem);
/* Temporarily invalidate the crash image while it is replaced */
xchg(&kexec_crash_image, NULL);
/* update FDT to reflect changes in CPU resources */
if (update_cpus_node(fdt))
pr_err("Failed to update crash FDT");
/* The crash image is now valid once again */
xchg(&kexec_crash_image, image);
}
int arch_crash_hotplug_support(struct kimage *image, unsigned long kexec_flags)
{
#ifdef CONFIG_KEXEC_FILE
if (image->file_mode)
return 1;
#endif
return kexec_flags & KEXEC_CRASH_HOTPLUG_SUPPORT;
}
/**
* arch_crash_handle_hotplug_event - Handle crash CPU/Memory hotplug events to update the
* necessary kexec segments based on the hotplug event.
* @image: a pointer to kexec_crash_image
* @arg: struct memory_notify handler for memory hotplug case and NULL for CPU hotplug case.
*
* Update the kdump image based on the type of hotplug event, represented by image->hp_action.
* CPU add: Update the FDT segment to include the newly added CPU.
* CPU remove: No action is needed, with the assumption that it's okay to have offline CPUs
* part of the FDT.
* Memory add/remove: No action is taken as this is not yet supported.
*/
void arch_crash_handle_hotplug_event(struct kimage *image, void *arg)
{
struct memory_notify *mn;
switch (image->hp_action) {
case KEXEC_CRASH_HP_REMOVE_CPU:
return;
case KEXEC_CRASH_HP_ADD_CPU:
update_crash_fdt(image);
break;
case KEXEC_CRASH_HP_REMOVE_MEMORY:
case KEXEC_CRASH_HP_ADD_MEMORY:
mn = (struct memory_notify *)arg;
update_crash_elfcorehdr(image, mn);
return;
default:
pr_warn_once("Unknown hotplug action\n");
}
}
#endif /* CONFIG_CRASH_HOTPLUG */
+168
View File
@@ -0,0 +1,168 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Load ELF vmlinux file for the kexec_file_load syscall.
*
* Copyright (C) 2004 Adam Litke (agl@us.ibm.com)
* Copyright (C) 2004 IBM Corp.
* Copyright (C) 2005 R Sharada (sharada@in.ibm.com)
* Copyright (C) 2006 Mohan Kumar M (mohan@in.ibm.com)
* Copyright (C) 2016 IBM Corporation
*
* Based on kexec-tools' kexec-elf-exec.c and kexec-elf-ppc64.c.
* Heavily modified for the kernel by
* Thiago Jung Bauermann <bauerman@linux.vnet.ibm.com>.
*/
#define pr_fmt(fmt) "kexec_elf: " fmt
#include <linux/elf.h>
#include <linux/kexec.h>
#include <linux/libfdt.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_fdt.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <asm/kexec_ranges.h>
static void *elf64_load(struct kimage *image, char *kernel_buf,
unsigned long kernel_len, char *initrd,
unsigned long initrd_len, char *cmdline,
unsigned long cmdline_len)
{
int ret;
unsigned long kernel_load_addr;
unsigned long initrd_load_addr = 0, fdt_load_addr;
void *fdt;
const void *slave_code;
struct elfhdr ehdr;
char *modified_cmdline = NULL;
struct crash_mem *rmem = NULL;
struct kexec_elf_info elf_info;
struct kexec_buf kbuf = { .image = image, .buf_min = 0,
.buf_max = ppc64_rma_size };
struct kexec_buf pbuf = { .image = image, .buf_min = 0,
.buf_max = ppc64_rma_size, .top_down = true,
.mem = KEXEC_BUF_MEM_UNKNOWN };
ret = kexec_build_elf_info(kernel_buf, kernel_len, &ehdr, &elf_info);
if (ret)
return ERR_PTR(ret);
if (IS_ENABLED(CONFIG_CRASH_DUMP) && image->type == KEXEC_TYPE_CRASH) {
/* min & max buffer values for kdump case */
kbuf.buf_min = pbuf.buf_min = crashk_res.start;
kbuf.buf_max = pbuf.buf_max =
((crashk_res.end < ppc64_rma_size) ?
crashk_res.end : (ppc64_rma_size - 1));
}
ret = kexec_elf_load(image, &ehdr, &elf_info, &kbuf, &kernel_load_addr);
if (ret)
goto out;
kexec_dprintk("Loaded the kernel at 0x%lx\n", kernel_load_addr);
ret = kexec_load_purgatory(image, &pbuf);
if (ret) {
pr_err("Loading purgatory failed.\n");
goto out;
}
kexec_dprintk("Loaded purgatory at 0x%lx\n", pbuf.mem);
/* Load additional segments needed for panic kernel */
if (IS_ENABLED(CONFIG_CRASH_DUMP) && image->type == KEXEC_TYPE_CRASH) {
ret = load_crashdump_segments_ppc64(image, &kbuf);
if (ret) {
pr_err("Failed to load kdump kernel segments\n");
goto out;
}
ret = crash_load_dm_crypt_keys(image);
if (ret)
goto out;
/* Setup cmdline for kdump kernel case */
modified_cmdline = setup_kdump_cmdline(image, cmdline,
cmdline_len);
if (!modified_cmdline) {
pr_err("Setting up cmdline for kdump kernel failed\n");
ret = -EINVAL;
goto out;
}
cmdline = modified_cmdline;
}
if (initrd != NULL) {
kbuf.buffer = initrd;
kbuf.bufsz = kbuf.memsz = initrd_len;
kbuf.buf_align = PAGE_SIZE;
kbuf.top_down = false;
kbuf.mem = KEXEC_BUF_MEM_UNKNOWN;
ret = kexec_add_buffer(&kbuf);
if (ret)
goto out;
initrd_load_addr = kbuf.mem;
kexec_dprintk("Loaded initrd at 0x%lx\n", initrd_load_addr);
}
ret = get_reserved_memory_ranges(&rmem);
if (ret)
goto out;
fdt = of_kexec_alloc_and_setup_fdt(image, initrd_load_addr,
initrd_len, cmdline,
kexec_extra_fdt_size_ppc64(image, rmem));
if (!fdt) {
pr_err("Error setting up the new device tree.\n");
ret = -EINVAL;
goto out;
}
ret = setup_new_fdt_ppc64(image, fdt, rmem);
if (ret)
goto out_free_fdt;
if (!IS_ENABLED(CONFIG_CRASH_HOTPLUG) || image->type != KEXEC_TYPE_CRASH)
fdt_pack(fdt);
kbuf.buffer = fdt;
kbuf.bufsz = kbuf.memsz = fdt_totalsize(fdt);
kbuf.buf_align = PAGE_SIZE;
kbuf.top_down = true;
kbuf.mem = KEXEC_BUF_MEM_UNKNOWN;
ret = kexec_add_buffer(&kbuf);
if (ret)
goto out_free_fdt;
/* FDT will be freed in arch_kimage_file_post_load_cleanup */
image->arch.fdt = fdt;
fdt_load_addr = kbuf.mem;
kexec_dprintk("Loaded device tree at 0x%lx\n", fdt_load_addr);
slave_code = elf_info.buffer + elf_info.proghdrs[0].p_offset;
ret = setup_purgatory_ppc64(image, slave_code, fdt, kernel_load_addr,
fdt_load_addr);
if (ret)
pr_err("Error setting up the purgatory.\n");
goto out;
out_free_fdt:
kvfree(fdt);
out:
kfree(rmem);
kfree(modified_cmdline);
kexec_free_elf_info(&elf_info);
return ret ? ERR_PTR(ret) : NULL;
}
const struct kexec_file_ops kexec_elf64_ops = {
.probe = kexec_elf_probe,
.load = elf64_load,
};
+109
View File
@@ -0,0 +1,109 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* powerpc code to implement the kexec_file_load syscall
*
* Copyright (C) 2004 Adam Litke (agl@us.ibm.com)
* Copyright (C) 2004 IBM Corp.
* Copyright (C) 2004,2005 Milton D Miller II, IBM Corporation
* Copyright (C) 2005 R Sharada (sharada@in.ibm.com)
* Copyright (C) 2006 Mohan Kumar M (mohan@in.ibm.com)
* Copyright (C) 2016 IBM Corporation
*
* Based on kexec-tools' kexec-elf-ppc64.c, fs2dt.c.
* Heavily modified for the kernel by
* Thiago Jung Bauermann <bauerman@linux.vnet.ibm.com>.
*/
#include <linux/slab.h>
#include <linux/kexec.h>
#include <linux/of_fdt.h>
#include <linux/libfdt.h>
#include <asm/setup.h>
#define SLAVE_CODE_SIZE 256 /* First 0x100 bytes */
/**
* setup_kdump_cmdline - Prepend "elfcorehdr=<addr> " to command line
* of kdump kernel for exporting the core.
* @image: Kexec image
* @cmdline: Command line parameters to update.
* @cmdline_len: Length of the cmdline parameters.
*
* kdump segment must be setup before calling this function.
*
* Returns new cmdline buffer for kdump kernel on success, NULL otherwise.
*/
char *setup_kdump_cmdline(struct kimage *image, char *cmdline,
unsigned long cmdline_len)
{
int elfcorehdr_strlen;
char *cmdline_ptr;
cmdline_ptr = kzalloc(COMMAND_LINE_SIZE, GFP_KERNEL);
if (!cmdline_ptr)
return NULL;
elfcorehdr_strlen = sprintf(cmdline_ptr, "elfcorehdr=0x%lx ",
image->elf_load_addr);
if (elfcorehdr_strlen + cmdline_len > COMMAND_LINE_SIZE) {
pr_err("Appending elfcorehdr=<addr> exceeds cmdline size\n");
kfree(cmdline_ptr);
return NULL;
}
memcpy(cmdline_ptr + elfcorehdr_strlen, cmdline, cmdline_len);
// Ensure it's nul terminated
cmdline_ptr[COMMAND_LINE_SIZE - 1] = '\0';
return cmdline_ptr;
}
/**
* setup_purgatory - initialize the purgatory's global variables
* @image: kexec image.
* @slave_code: Slave code for the purgatory.
* @fdt: Flattened device tree for the next kernel.
* @kernel_load_addr: Address where the kernel is loaded.
* @fdt_load_addr: Address where the flattened device tree is loaded.
*
* Return: 0 on success, or negative errno on error.
*/
int setup_purgatory(struct kimage *image, const void *slave_code,
const void *fdt, unsigned long kernel_load_addr,
unsigned long fdt_load_addr)
{
unsigned int *slave_code_buf, master_entry;
int ret;
slave_code_buf = kmalloc(SLAVE_CODE_SIZE, GFP_KERNEL);
if (!slave_code_buf)
return -ENOMEM;
/* Get the slave code from the new kernel and put it in purgatory. */
ret = kexec_purgatory_get_set_symbol(image, "purgatory_start",
slave_code_buf, SLAVE_CODE_SIZE,
true);
if (ret) {
kfree(slave_code_buf);
return ret;
}
master_entry = slave_code_buf[0];
memcpy(slave_code_buf, slave_code, SLAVE_CODE_SIZE);
slave_code_buf[0] = master_entry;
ret = kexec_purgatory_get_set_symbol(image, "purgatory_start",
slave_code_buf, SLAVE_CODE_SIZE,
false);
kfree(slave_code_buf);
ret = kexec_purgatory_get_set_symbol(image, "kernel", &kernel_load_addr,
sizeof(kernel_load_addr), false);
if (ret)
return ret;
ret = kexec_purgatory_get_set_symbol(image, "dt_offset", &fdt_load_addr,
sizeof(fdt_load_addr), false);
if (ret)
return ret;
return 0;
}
@@ -0,0 +1,856 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* ppc64 code to implement the kexec_file_load syscall
*
* Copyright (C) 2004 Adam Litke (agl@us.ibm.com)
* Copyright (C) 2004 IBM Corp.
* Copyright (C) 2004,2005 Milton D Miller II, IBM Corporation
* Copyright (C) 2005 R Sharada (sharada@in.ibm.com)
* Copyright (C) 2006 Mohan Kumar M (mohan@in.ibm.com)
* Copyright (C) 2020 IBM Corporation
*
* Based on kexec-tools' kexec-ppc64.c, kexec-elf-rel-ppc64.c, fs2dt.c.
* Heavily modified for the kernel by
* Hari Bathini, IBM Corporation.
*/
#include <linux/kexec.h>
#include <linux/of_fdt.h>
#include <linux/libfdt.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/memblock.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <asm/setup.h>
#include <asm/drmem.h>
#include <asm/firmware.h>
#include <asm/kexec_ranges.h>
#include <asm/crashdump-ppc64.h>
#include <asm/mmzone.h>
#include <asm/iommu.h>
#include <asm/prom.h>
#include <asm/plpks.h>
#include <asm/cputhreads.h>
struct umem_info {
__be64 *buf; /* data buffer for usable-memory property */
u32 size; /* size allocated for the data buffer */
u32 max_entries; /* maximum no. of entries */
u32 idx; /* index of current entry */
/* usable memory ranges to look up */
unsigned int nr_ranges;
const struct range *ranges;
};
const struct kexec_file_ops * const kexec_file_loaders[] = {
&kexec_elf64_ops,
NULL
};
int arch_check_excluded_range(struct kimage *image, unsigned long start,
unsigned long end)
{
struct crash_mem *emem;
int i;
emem = image->arch.exclude_ranges;
for (i = 0; i < emem->nr_ranges; i++)
if (start < emem->ranges[i].end && end > emem->ranges[i].start)
return 1;
return 0;
}
#ifdef CONFIG_CRASH_DUMP
/**
* check_realloc_usable_mem - Reallocate buffer if it can't accommodate entries
* @um_info: Usable memory buffer and ranges info.
* @cnt: No. of entries to accommodate.
*
* Frees up the old buffer if memory reallocation fails.
*
* Returns buffer on success, NULL on error.
*/
static __be64 *check_realloc_usable_mem(struct umem_info *um_info, int cnt)
{
u32 new_size;
__be64 *tbuf;
if ((um_info->idx + cnt) <= um_info->max_entries)
return um_info->buf;
new_size = um_info->size + MEM_RANGE_CHUNK_SZ;
tbuf = krealloc(um_info->buf, new_size, GFP_KERNEL);
if (tbuf) {
um_info->buf = tbuf;
um_info->size = new_size;
um_info->max_entries = (um_info->size / sizeof(u64));
}
return tbuf;
}
/**
* add_usable_mem - Add the usable memory ranges within the given memory range
* to the buffer
* @um_info: Usable memory buffer and ranges info.
* @base: Base address of memory range to look for.
* @end: End address of memory range to look for.
*
* Returns 0 on success, negative errno on error.
*/
static int add_usable_mem(struct umem_info *um_info, u64 base, u64 end)
{
u64 loc_base, loc_end;
bool add;
int i;
for (i = 0; i < um_info->nr_ranges; i++) {
add = false;
loc_base = um_info->ranges[i].start;
loc_end = um_info->ranges[i].end;
if (loc_base >= base && loc_end <= end)
add = true;
else if (base < loc_end && end > loc_base) {
if (loc_base < base)
loc_base = base;
if (loc_end > end)
loc_end = end;
add = true;
}
if (add) {
if (!check_realloc_usable_mem(um_info, 2))
return -ENOMEM;
um_info->buf[um_info->idx++] = cpu_to_be64(loc_base);
um_info->buf[um_info->idx++] =
cpu_to_be64(loc_end - loc_base + 1);
}
}
return 0;
}
/**
* kdump_setup_usable_lmb - This is a callback function that gets called by
* walk_drmem_lmbs for every LMB to set its
* usable memory ranges.
* @lmb: LMB info.
* @usm: linux,drconf-usable-memory property value.
* @data: Pointer to usable memory buffer and ranges info.
*
* Returns 0 on success, negative errno on error.
*/
static int kdump_setup_usable_lmb(struct drmem_lmb *lmb, const __be32 **usm,
void *data)
{
struct umem_info *um_info;
int tmp_idx, ret;
u64 base, end;
/*
* kdump load isn't supported on kernels already booted with
* linux,drconf-usable-memory property.
*/
if (*usm) {
pr_err("linux,drconf-usable-memory property already exists!");
return -EINVAL;
}
um_info = data;
tmp_idx = um_info->idx;
if (!check_realloc_usable_mem(um_info, 1))
return -ENOMEM;
um_info->idx++;
base = lmb->base_addr;
end = base + drmem_lmb_size() - 1;
ret = add_usable_mem(um_info, base, end);
if (!ret) {
/*
* Update the no. of ranges added. Two entries (base & size)
* for every range added.
*/
um_info->buf[tmp_idx] =
cpu_to_be64((um_info->idx - tmp_idx - 1) / 2);
}
return ret;
}
#define NODE_PATH_LEN 256
/**
* add_usable_mem_property - Add usable memory property for the given
* memory node.
* @fdt: Flattened device tree for the kdump kernel.
* @dn: Memory node.
* @um_info: Usable memory buffer and ranges info.
*
* Returns 0 on success, negative errno on error.
*/
static int add_usable_mem_property(void *fdt, struct device_node *dn,
struct umem_info *um_info)
{
int node;
char path[NODE_PATH_LEN];
int i, ret;
u64 base, size;
of_node_get(dn);
if (snprintf(path, NODE_PATH_LEN, "%pOF", dn) > (NODE_PATH_LEN - 1)) {
pr_err("Buffer (%d) too small for memory node: %pOF\n",
NODE_PATH_LEN, dn);
return -EOVERFLOW;
}
kexec_dprintk("Memory node path: %s\n", path);
/* Now that we know the path, find its offset in kdump kernel's fdt */
node = fdt_path_offset(fdt, path);
if (node < 0) {
pr_err("Malformed device tree: error reading %s\n", path);
ret = -EINVAL;
goto out;
}
um_info->idx = 0;
if (!check_realloc_usable_mem(um_info, 2)) {
ret = -ENOMEM;
goto out;
}
/*
* "reg" property represents sequence of (addr,size) tuples
* each representing a memory range.
*/
for (i = 0; ; i++) {
ret = of_property_read_reg(dn, i, &base, &size);
if (ret)
break;
ret = add_usable_mem(um_info, base, base + size - 1);
if (ret)
goto out;
}
// No reg or empty reg? Skip this node.
if (i == 0)
goto out;
/*
* No kdump kernel usable memory found in this memory node.
* Write (0,0) tuple in linux,usable-memory property for
* this region to be ignored.
*/
if (um_info->idx == 0) {
um_info->buf[0] = 0;
um_info->buf[1] = 0;
um_info->idx = 2;
}
ret = fdt_setprop(fdt, node, "linux,usable-memory", um_info->buf,
(um_info->idx * sizeof(u64)));
out:
of_node_put(dn);
return ret;
}
/**
* update_usable_mem_fdt - Updates kdump kernel's fdt with linux,usable-memory
* and linux,drconf-usable-memory DT properties as
* appropriate to restrict its memory usage.
* @fdt: Flattened device tree for the kdump kernel.
* @usable_mem: Usable memory ranges for kdump kernel.
*
* Returns 0 on success, negative errno on error.
*/
static int update_usable_mem_fdt(void *fdt, struct crash_mem *usable_mem)
{
struct umem_info um_info;
struct device_node *dn;
int node, ret = 0;
if (!usable_mem) {
pr_err("Usable memory ranges for kdump kernel not found\n");
return -ENOENT;
}
node = fdt_path_offset(fdt, "/ibm,dynamic-reconfiguration-memory");
if (node == -FDT_ERR_NOTFOUND)
kexec_dprintk("No dynamic reconfiguration memory found\n");
else if (node < 0) {
pr_err("Malformed device tree: error reading /ibm,dynamic-reconfiguration-memory.\n");
return -EINVAL;
}
um_info.buf = NULL;
um_info.size = 0;
um_info.max_entries = 0;
um_info.idx = 0;
/* Memory ranges to look up */
um_info.ranges = &(usable_mem->ranges[0]);
um_info.nr_ranges = usable_mem->nr_ranges;
dn = of_find_node_by_path("/ibm,dynamic-reconfiguration-memory");
if (dn) {
ret = walk_drmem_lmbs(dn, &um_info, kdump_setup_usable_lmb);
of_node_put(dn);
if (ret) {
pr_err("Could not setup linux,drconf-usable-memory property for kdump\n");
goto out;
}
ret = fdt_setprop(fdt, node, "linux,drconf-usable-memory",
um_info.buf, (um_info.idx * sizeof(u64)));
if (ret) {
pr_err("Failed to update fdt with linux,drconf-usable-memory property: %s",
fdt_strerror(ret));
goto out;
}
}
/*
* Walk through each memory node and set linux,usable-memory property
* for the corresponding node in kdump kernel's fdt.
*/
for_each_node_by_type(dn, "memory") {
ret = add_usable_mem_property(fdt, dn, &um_info);
if (ret) {
pr_err("Failed to set linux,usable-memory property for %s node",
dn->full_name);
of_node_put(dn);
goto out;
}
}
out:
kfree(um_info.buf);
return ret;
}
/**
* load_backup_segment - Locate a memory hole to place the backup region.
* @image: Kexec image.
* @kbuf: Buffer contents and memory parameters.
*
* Returns 0 on success, negative errno on error.
*/
static int load_backup_segment(struct kimage *image, struct kexec_buf *kbuf)
{
void *buf;
int ret;
/*
* Setup a source buffer for backup segment.
*
* A source buffer has no meaning for backup region as data will
* be copied from backup source, after crash, in the purgatory.
* But as load segment code doesn't recognize such segments,
* setup a dummy source buffer to keep it happy for now.
*/
buf = vzalloc(BACKUP_SRC_SIZE);
if (!buf)
return -ENOMEM;
kbuf->buffer = buf;
kbuf->mem = KEXEC_BUF_MEM_UNKNOWN;
kbuf->bufsz = kbuf->memsz = BACKUP_SRC_SIZE;
kbuf->top_down = false;
ret = kexec_add_buffer(kbuf);
if (ret) {
vfree(buf);
return ret;
}
image->arch.backup_buf = buf;
image->arch.backup_start = kbuf->mem;
return 0;
}
static unsigned int kdump_extra_elfcorehdr_size(struct crash_mem *cmem)
{
#if defined(CONFIG_CRASH_HOTPLUG) && defined(CONFIG_MEMORY_HOTPLUG)
unsigned int extra_sz = 0;
if (CONFIG_CRASH_MAX_MEMORY_RANGES > (unsigned int)PN_XNUM)
pr_warn("Number of Phdrs %u exceeds max\n", CONFIG_CRASH_MAX_MEMORY_RANGES);
else if (cmem->nr_ranges >= CONFIG_CRASH_MAX_MEMORY_RANGES)
pr_warn("Configured crash mem ranges may not be enough\n");
else
extra_sz = (CONFIG_CRASH_MAX_MEMORY_RANGES - cmem->nr_ranges) * sizeof(Elf64_Phdr);
return extra_sz;
#endif
return 0;
}
/**
* load_elfcorehdr_segment - Setup crash memory ranges and initialize elfcorehdr
* segment needed to load kdump kernel.
* @image: Kexec image.
* @kbuf: Buffer contents and memory parameters.
*
* Returns 0 on success, negative errno on error.
*/
static int load_elfcorehdr_segment(struct kimage *image, struct kexec_buf *kbuf)
{
struct crash_mem *cmem = NULL;
unsigned long headers_sz;
void *headers = NULL;
int ret;
ret = get_crash_memory_ranges(&cmem);
if (ret)
goto out;
/* Setup elfcorehdr segment */
ret = crash_prepare_elf64_headers(cmem, false, &headers, &headers_sz);
if (ret) {
pr_err("Failed to prepare elf headers for the core\n");
goto out;
}
/* Fix the offset for backup region in the ELF header */
sync_backup_region_phdr(image, headers, false);
kbuf->buffer = headers;
kbuf->mem = KEXEC_BUF_MEM_UNKNOWN;
kbuf->bufsz = headers_sz;
/*
* Account for extra space required to accommodate additional memory
* ranges in elfcorehdr due to memory hotplug events.
*/
kbuf->memsz = headers_sz + kdump_extra_elfcorehdr_size(cmem);
kbuf->top_down = false;
ret = kexec_add_buffer(kbuf);
if (ret) {
vfree(headers);
goto out;
}
image->elf_load_addr = kbuf->mem;
/*
* If CONFIG_CRASH_HOTPLUG is enabled, the elfcorehdr kexec segment
* memsz can be larger than bufsz. Always initialize elf_headers_sz
* with memsz. This ensures the correct size is reserved for elfcorehdr
* memory in the FDT prepared for kdump.
*/
image->elf_headers_sz = kbuf->memsz;
image->elf_headers = headers;
out:
kfree(cmem);
return ret;
}
/**
* load_crashdump_segments_ppc64 - Initialize the additional segements needed
* to load kdump kernel.
* @image: Kexec image.
* @kbuf: Buffer contents and memory parameters.
*
* Returns 0 on success, negative errno on error.
*/
int load_crashdump_segments_ppc64(struct kimage *image,
struct kexec_buf *kbuf)
{
int ret;
/* Load backup segment - first 64K bytes of the crashing kernel */
ret = load_backup_segment(image, kbuf);
if (ret) {
pr_err("Failed to load backup segment\n");
return ret;
}
kexec_dprintk("Loaded the backup region at 0x%lx\n", kbuf->mem);
/* Load elfcorehdr segment - to export crashing kernel's vmcore */
ret = load_elfcorehdr_segment(image, kbuf);
if (ret) {
pr_err("Failed to load elfcorehdr segment\n");
return ret;
}
kexec_dprintk("Loaded elf core header at 0x%lx, bufsz=0x%lx memsz=0x%lx\n",
image->elf_load_addr, kbuf->bufsz, kbuf->memsz);
return 0;
}
#endif
/**
* setup_purgatory_ppc64 - initialize PPC64 specific purgatory's global
* variables and call setup_purgatory() to initialize
* common global variable.
* @image: kexec image.
* @slave_code: Slave code for the purgatory.
* @fdt: Flattened device tree for the next kernel.
* @kernel_load_addr: Address where the kernel is loaded.
* @fdt_load_addr: Address where the flattened device tree is loaded.
*
* Returns 0 on success, negative errno on error.
*/
int setup_purgatory_ppc64(struct kimage *image, const void *slave_code,
const void *fdt, unsigned long kernel_load_addr,
unsigned long fdt_load_addr)
{
struct device_node *dn = NULL;
int ret;
ret = setup_purgatory(image, slave_code, fdt, kernel_load_addr,
fdt_load_addr);
if (ret)
goto out;
if (image->type == KEXEC_TYPE_CRASH) {
u32 my_run_at_load = 1;
/*
* Tell relocatable kernel to run at load address
* via the word meant for that at 0x5c.
*/
ret = kexec_purgatory_get_set_symbol(image, "run_at_load",
&my_run_at_load,
sizeof(my_run_at_load),
false);
if (ret)
goto out;
}
/* Tell purgatory where to look for backup region */
ret = kexec_purgatory_get_set_symbol(image, "backup_start",
&image->arch.backup_start,
sizeof(image->arch.backup_start),
false);
if (ret)
goto out;
/* Setup OPAL base & entry values */
dn = of_find_node_by_path("/ibm,opal");
if (dn) {
u64 val;
ret = of_property_read_u64(dn, "opal-base-address", &val);
if (ret)
goto out;
ret = kexec_purgatory_get_set_symbol(image, "opal_base", &val,
sizeof(val), false);
if (ret)
goto out;
ret = of_property_read_u64(dn, "opal-entry-address", &val);
if (ret)
goto out;
ret = kexec_purgatory_get_set_symbol(image, "opal_entry", &val,
sizeof(val), false);
}
out:
if (ret)
pr_err("Failed to setup purgatory symbols");
of_node_put(dn);
return ret;
}
/**
* cpu_node_size - Compute the size of a CPU node in the FDT.
* This should be done only once and the value is stored in
* a static variable.
* Returns the max size of a CPU node in the FDT.
*/
static unsigned int cpu_node_size(void)
{
static unsigned int size;
struct device_node *dn;
struct property *pp;
/*
* Don't compute it twice, we are assuming that the per CPU node size
* doesn't change during the system's life.
*/
if (size)
return size;
dn = of_find_node_by_type(NULL, "cpu");
if (WARN_ON_ONCE(!dn)) {
// Unlikely to happen
return 0;
}
/*
* We compute the sub node size for a CPU node, assuming it
* will be the same for all.
*/
size += strlen(dn->name) + 5;
for_each_property_of_node(dn, pp) {
size += strlen(pp->name);
size += pp->length;
}
of_node_put(dn);
return size;
}
static unsigned int kdump_extra_fdt_size_ppc64(struct kimage *image, unsigned int cpu_nodes)
{
unsigned int extra_size = 0;
u64 usm_entries;
#ifdef CONFIG_CRASH_HOTPLUG
unsigned int possible_cpu_nodes;
#endif
if (!IS_ENABLED(CONFIG_CRASH_DUMP) || image->type != KEXEC_TYPE_CRASH)
return 0;
/*
* For kdump kernel, account for linux,usable-memory and
* linux,drconf-usable-memory properties. Get an approximate on the
* number of usable memory entries and use for FDT size estimation.
*/
if (drmem_lmb_size()) {
usm_entries = ((memory_hotplug_max() / drmem_lmb_size()) +
(2 * (resource_size(&crashk_res) / drmem_lmb_size())));
extra_size += (unsigned int)(usm_entries * sizeof(u64));
}
#ifdef CONFIG_CRASH_HOTPLUG
/*
* Make sure enough space is reserved to accommodate possible CPU nodes
* in the crash FDT. This allows packing possible CPU nodes which are
* not yet present in the system without regenerating the entire FDT.
*/
if (image->type == KEXEC_TYPE_CRASH) {
possible_cpu_nodes = num_possible_cpus() / threads_per_core;
if (possible_cpu_nodes > cpu_nodes)
extra_size += (possible_cpu_nodes - cpu_nodes) * cpu_node_size();
}
#endif
return extra_size;
}
/**
* kexec_extra_fdt_size_ppc64 - Return the estimated additional size needed to
* setup FDT for kexec/kdump kernel.
* @image: kexec image being loaded.
*
* Returns the estimated extra size needed for kexec/kdump kernel FDT.
*/
unsigned int kexec_extra_fdt_size_ppc64(struct kimage *image, struct crash_mem *rmem)
{
struct device_node *dn;
unsigned int cpu_nodes = 0, extra_size = 0;
// Budget some space for the password blob. There's already extra space
// for the key name
if (plpks_is_available())
extra_size += (unsigned int)plpks_get_passwordlen();
/* Get the number of CPU nodes in the current device tree */
for_each_node_by_type(dn, "cpu") {
cpu_nodes++;
}
/* Consider extra space for CPU nodes added since the boot time */
if (cpu_nodes > boot_cpu_node_count)
extra_size += (cpu_nodes - boot_cpu_node_count) * cpu_node_size();
/* Consider extra space for reserved memory ranges if any */
if (rmem->nr_ranges > 0)
extra_size += sizeof(struct fdt_reserve_entry) * rmem->nr_ranges;
return extra_size + kdump_extra_fdt_size_ppc64(image, cpu_nodes);
}
static int copy_property(void *fdt, int node_offset, const struct device_node *dn,
const char *propname)
{
const void *prop, *fdtprop;
int len = 0, fdtlen = 0;
prop = of_get_property(dn, propname, &len);
fdtprop = fdt_getprop(fdt, node_offset, propname, &fdtlen);
if (fdtprop && !prop)
return fdt_delprop(fdt, node_offset, propname);
else if (prop)
return fdt_setprop(fdt, node_offset, propname, prop, len);
else
return -FDT_ERR_NOTFOUND;
}
static int update_pci_dma_nodes(void *fdt, const char *dmapropname)
{
struct device_node *dn;
int pci_offset, root_offset, ret = 0;
if (!firmware_has_feature(FW_FEATURE_LPAR))
return 0;
root_offset = fdt_path_offset(fdt, "/");
for_each_node_with_property(dn, dmapropname) {
pci_offset = fdt_subnode_offset(fdt, root_offset, of_node_full_name(dn));
if (pci_offset < 0)
continue;
ret = copy_property(fdt, pci_offset, dn, "ibm,dma-window");
if (ret < 0) {
of_node_put(dn);
break;
}
ret = copy_property(fdt, pci_offset, dn, dmapropname);
if (ret < 0) {
of_node_put(dn);
break;
}
}
return ret;
}
/**
* setup_new_fdt_ppc64 - Update the flattend device-tree of the kernel
* being loaded.
* @image: kexec image being loaded.
* @fdt: Flattened device tree for the next kernel.
* @rmem: Reserved memory ranges.
*
* Returns 0 on success, negative errno on error.
*/
int setup_new_fdt_ppc64(const struct kimage *image, void *fdt, struct crash_mem *rmem)
{
struct crash_mem *umem = NULL;
int i, nr_ranges, ret;
#ifdef CONFIG_CRASH_DUMP
/*
* Restrict memory usage for kdump kernel by setting up
* usable memory ranges and memory reserve map.
*/
if (image->type == KEXEC_TYPE_CRASH) {
ret = get_usable_memory_ranges(&umem);
if (ret)
goto out;
ret = update_usable_mem_fdt(fdt, umem);
if (ret) {
pr_err("Error setting up usable-memory property for kdump kernel\n");
goto out;
}
/*
* Ensure we don't touch crashed kernel's memory except the
* first 64K of RAM, which will be backed up.
*/
ret = fdt_add_mem_rsv(fdt, BACKUP_SRC_END + 1,
crashk_res.start - BACKUP_SRC_SIZE);
if (ret) {
pr_err("Error reserving crash memory: %s\n",
fdt_strerror(ret));
goto out;
}
/* Ensure backup region is not used by kdump/capture kernel */
ret = fdt_add_mem_rsv(fdt, image->arch.backup_start,
BACKUP_SRC_SIZE);
if (ret) {
pr_err("Error reserving memory for backup: %s\n",
fdt_strerror(ret));
goto out;
}
}
#endif
/* Update cpus nodes information to account hotplug CPUs. */
ret = update_cpus_node(fdt);
if (ret < 0)
goto out;
ret = update_pci_dma_nodes(fdt, DIRECT64_PROPNAME);
if (ret < 0)
goto out;
ret = update_pci_dma_nodes(fdt, DMA64_PROPNAME);
if (ret < 0)
goto out;
/* Update memory reserve map */
nr_ranges = rmem ? rmem->nr_ranges : 0;
for (i = 0; i < nr_ranges; i++) {
u64 base, size;
base = rmem->ranges[i].start;
size = rmem->ranges[i].end - base + 1;
ret = fdt_add_mem_rsv(fdt, base, size);
if (ret) {
pr_err("Error updating memory reserve map: %s\n",
fdt_strerror(ret));
goto out;
}
}
// If we have PLPKS active, we need to provide the password to the new kernel
if (plpks_is_available())
ret = plpks_populate_fdt(fdt);
out:
kfree(umem);
return ret;
}
/**
* arch_kexec_kernel_image_probe - Does additional handling needed to setup
* kexec segments.
* @image: kexec image being loaded.
* @buf: Buffer pointing to elf data.
* @buf_len: Length of the buffer.
*
* Returns 0 on success, negative errno on error.
*/
int arch_kexec_kernel_image_probe(struct kimage *image, void *buf,
unsigned long buf_len)
{
int ret;
/* Get exclude memory ranges needed for setting up kexec segments */
ret = get_exclude_memory_ranges(&(image->arch.exclude_ranges));
if (ret) {
pr_err("Failed to setup exclude memory ranges for buffer lookup\n");
return ret;
}
return kexec_image_probe_default(image, buf, buf_len);
}
/**
* arch_kimage_file_post_load_cleanup - Frees up all the allocations done
* while loading the image.
* @image: kexec image being loaded.
*
* Returns 0 on success, negative errno on error.
*/
int arch_kimage_file_post_load_cleanup(struct kimage *image)
{
kfree(image->arch.exclude_ranges);
image->arch.exclude_ranges = NULL;
vfree(image->arch.backup_buf);
image->arch.backup_buf = NULL;
vfree(image->elf_headers);
image->elf_headers = NULL;
image->elf_headers_sz = 0;
kvfree(image->arch.fdt);
image->arch.fdt = NULL;
return kexec_image_post_load_cleanup_default(image);
}
+729
View File
@@ -0,0 +1,729 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* powerpc code to implement the kexec_file_load syscall
*
* Copyright (C) 2004 Adam Litke (agl@us.ibm.com)
* Copyright (C) 2004 IBM Corp.
* Copyright (C) 2004,2005 Milton D Miller II, IBM Corporation
* Copyright (C) 2005 R Sharada (sharada@in.ibm.com)
* Copyright (C) 2006 Mohan Kumar M (mohan@in.ibm.com)
* Copyright (C) 2020 IBM Corporation
*
* Based on kexec-tools' kexec-ppc64.c, fs2dt.c.
* Heavily modified for the kernel by
* Hari Bathini, IBM Corporation.
*/
#define pr_fmt(fmt) "kexec ranges: " fmt
#include <linux/sort.h>
#include <linux/kexec.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/memblock.h>
#include <linux/crash_core.h>
#include <asm/sections.h>
#include <asm/kexec_ranges.h>
#include <asm/crashdump-ppc64.h>
#if defined(CONFIG_KEXEC_FILE) || defined(CONFIG_CRASH_DUMP)
/**
* get_max_nr_ranges - Get the max no. of ranges crash_mem structure
* could hold, given the size allocated for it.
* @size: Allocation size of crash_mem structure.
*
* Returns the maximum no. of ranges.
*/
static inline unsigned int get_max_nr_ranges(size_t size)
{
return ((size - sizeof(struct crash_mem)) /
sizeof(struct range));
}
/**
* get_mem_rngs_size - Get the allocated size of mem_rngs based on
* max_nr_ranges and chunk size.
* @mem_rngs: Memory ranges.
*
* Returns the maximum size of @mem_rngs.
*/
static inline size_t get_mem_rngs_size(struct crash_mem *mem_rngs)
{
size_t size;
if (!mem_rngs)
return 0;
size = (sizeof(struct crash_mem) +
(mem_rngs->max_nr_ranges * sizeof(struct range)));
/*
* Memory is allocated in size multiple of MEM_RANGE_CHUNK_SZ.
* So, align to get the actual length.
*/
return ALIGN(size, MEM_RANGE_CHUNK_SZ);
}
/**
* __add_mem_range - add a memory range to memory ranges list.
* @mem_ranges: Range list to add the memory range to.
* @base: Base address of the range to add.
* @size: Size of the memory range to add.
*
* (Re)allocates memory, if needed.
*
* Returns 0 on success, negative errno on error.
*/
static int __add_mem_range(struct crash_mem **mem_ranges, u64 base, u64 size)
{
struct crash_mem *mem_rngs = *mem_ranges;
if (!mem_rngs || (mem_rngs->nr_ranges == mem_rngs->max_nr_ranges)) {
mem_rngs = realloc_mem_ranges(mem_ranges);
if (!mem_rngs)
return -ENOMEM;
}
mem_rngs->ranges[mem_rngs->nr_ranges].start = base;
mem_rngs->ranges[mem_rngs->nr_ranges].end = base + size - 1;
pr_debug("Added memory range [%#016llx - %#016llx] at index %d\n",
base, base + size - 1, mem_rngs->nr_ranges);
mem_rngs->nr_ranges++;
return 0;
}
/**
* __merge_memory_ranges - Merges the given memory ranges list.
* @mem_rngs: Range list to merge.
*
* Assumes a sorted range list.
*
* Returns nothing.
*/
static void __merge_memory_ranges(struct crash_mem *mem_rngs)
{
struct range *ranges;
int i, idx;
if (!mem_rngs)
return;
idx = 0;
ranges = &(mem_rngs->ranges[0]);
for (i = 1; i < mem_rngs->nr_ranges; i++) {
if (ranges[i].start <= (ranges[i-1].end + 1))
ranges[idx].end = ranges[i].end;
else {
idx++;
if (i == idx)
continue;
ranges[idx] = ranges[i];
}
}
mem_rngs->nr_ranges = idx + 1;
}
/* cmp_func_t callback to sort ranges with sort() */
static int rngcmp(const void *_x, const void *_y)
{
const struct range *x = _x, *y = _y;
if (x->start > y->start)
return 1;
if (x->start < y->start)
return -1;
return 0;
}
/**
* sort_memory_ranges - Sorts the given memory ranges list.
* @mem_rngs: Range list to sort.
* @merge: If true, merge the list after sorting.
*
* Returns nothing.
*/
void sort_memory_ranges(struct crash_mem *mem_rngs, bool merge)
{
int i;
if (!mem_rngs)
return;
/* Sort the ranges in-place */
sort(&(mem_rngs->ranges[0]), mem_rngs->nr_ranges,
sizeof(mem_rngs->ranges[0]), rngcmp, NULL);
if (merge)
__merge_memory_ranges(mem_rngs);
/* For debugging purpose */
pr_debug("Memory ranges:\n");
for (i = 0; i < mem_rngs->nr_ranges; i++) {
pr_debug("\t[%03d][%#016llx - %#016llx]\n", i,
mem_rngs->ranges[i].start,
mem_rngs->ranges[i].end);
}
}
/**
* realloc_mem_ranges - reallocate mem_ranges with size incremented
* by MEM_RANGE_CHUNK_SZ. Frees up the old memory,
* if memory allocation fails.
* @mem_ranges: Memory ranges to reallocate.
*
* Returns pointer to reallocated memory on success, NULL otherwise.
*/
struct crash_mem *realloc_mem_ranges(struct crash_mem **mem_ranges)
{
struct crash_mem *mem_rngs = *mem_ranges;
unsigned int nr_ranges;
size_t size;
size = get_mem_rngs_size(mem_rngs);
nr_ranges = mem_rngs ? mem_rngs->nr_ranges : 0;
size += MEM_RANGE_CHUNK_SZ;
mem_rngs = krealloc(*mem_ranges, size, GFP_KERNEL);
if (!mem_rngs) {
kfree(*mem_ranges);
*mem_ranges = NULL;
return NULL;
}
mem_rngs->nr_ranges = nr_ranges;
mem_rngs->max_nr_ranges = get_max_nr_ranges(size);
*mem_ranges = mem_rngs;
return mem_rngs;
}
/**
* add_mem_range - Updates existing memory range, if there is an overlap.
* Else, adds a new memory range.
* @mem_ranges: Range list to add the memory range to.
* @base: Base address of the range to add.
* @size: Size of the memory range to add.
*
* (Re)allocates memory, if needed.
*
* Returns 0 on success, negative errno on error.
*/
int add_mem_range(struct crash_mem **mem_ranges, u64 base, u64 size)
{
struct crash_mem *mem_rngs = *mem_ranges;
u64 mstart, mend, end;
unsigned int i;
if (!size)
return 0;
end = base + size - 1;
if (!mem_rngs || !(mem_rngs->nr_ranges))
return __add_mem_range(mem_ranges, base, size);
for (i = 0; i < mem_rngs->nr_ranges; i++) {
mstart = mem_rngs->ranges[i].start;
mend = mem_rngs->ranges[i].end;
if (base < mend && end > mstart) {
if (base < mstart)
mem_rngs->ranges[i].start = base;
if (end > mend)
mem_rngs->ranges[i].end = end;
return 0;
}
}
return __add_mem_range(mem_ranges, base, size);
}
#endif /* CONFIG_KEXEC_FILE || CONFIG_CRASH_DUMP */
#ifdef CONFIG_KEXEC_FILE
/**
* add_tce_mem_ranges - Adds tce-table range to the given memory ranges list.
* @mem_ranges: Range list to add the memory range(s) to.
*
* Returns 0 on success, negative errno on error.
*/
static int add_tce_mem_ranges(struct crash_mem **mem_ranges)
{
struct device_node *dn = NULL;
int ret = 0;
for_each_node_by_type(dn, "pci") {
u64 base;
u32 size;
ret = of_property_read_u64(dn, "linux,tce-base", &base);
ret |= of_property_read_u32(dn, "linux,tce-size", &size);
if (ret) {
/*
* It is ok to have pci nodes without tce. So, ignore
* property does not exist error.
*/
if (ret == -EINVAL) {
ret = 0;
continue;
}
break;
}
ret = add_mem_range(mem_ranges, base, size);
if (ret)
break;
}
of_node_put(dn);
return ret;
}
/**
* add_initrd_mem_range - Adds initrd range to the given memory ranges list,
* if the initrd was retained.
* @mem_ranges: Range list to add the memory range to.
*
* Returns 0 on success, negative errno on error.
*/
static int add_initrd_mem_range(struct crash_mem **mem_ranges)
{
u64 base, end;
int ret;
/* This range means something, only if initrd was retained */
if (!strstr(saved_command_line, "retain_initrd"))
return 0;
ret = of_property_read_u64(of_chosen, "linux,initrd-start", &base);
ret |= of_property_read_u64(of_chosen, "linux,initrd-end", &end);
if (!ret)
ret = add_mem_range(mem_ranges, base, end - base + 1);
return ret;
}
/**
* add_htab_mem_range - Adds htab range to the given memory ranges list,
* if it exists
* @mem_ranges: Range list to add the memory range to.
*
* Returns 0 on success, negative errno on error.
*/
static int add_htab_mem_range(struct crash_mem **mem_ranges)
{
#ifdef CONFIG_PPC_64S_HASH_MMU
if (!htab_address)
return 0;
return add_mem_range(mem_ranges, __pa(htab_address), htab_size_bytes);
#else
return 0;
#endif
}
/**
* add_kernel_mem_range - Adds kernel text region to the given
* memory ranges list.
* @mem_ranges: Range list to add the memory range to.
*
* Returns 0 on success, negative errno on error.
*/
static int add_kernel_mem_range(struct crash_mem **mem_ranges)
{
return add_mem_range(mem_ranges, 0, __pa(_end));
}
#endif /* CONFIG_KEXEC_FILE */
#if defined(CONFIG_KEXEC_FILE) || defined(CONFIG_CRASH_DUMP)
/**
* add_rtas_mem_range - Adds RTAS region to the given memory ranges list.
* @mem_ranges: Range list to add the memory range to.
*
* Returns 0 on success, negative errno on error.
*/
static int add_rtas_mem_range(struct crash_mem **mem_ranges)
{
struct device_node *dn;
u32 base, size;
int ret = 0;
dn = of_find_node_by_path("/rtas");
if (!dn)
return 0;
ret = of_property_read_u32(dn, "linux,rtas-base", &base);
ret |= of_property_read_u32(dn, "rtas-size", &size);
if (!ret)
ret = add_mem_range(mem_ranges, base, size);
of_node_put(dn);
return ret;
}
/**
* add_opal_mem_range - Adds OPAL region to the given memory ranges list.
* @mem_ranges: Range list to add the memory range to.
*
* Returns 0 on success, negative errno on error.
*/
static int add_opal_mem_range(struct crash_mem **mem_ranges)
{
struct device_node *dn;
u64 base, size;
int ret;
dn = of_find_node_by_path("/ibm,opal");
if (!dn)
return 0;
ret = of_property_read_u64(dn, "opal-base-address", &base);
ret |= of_property_read_u64(dn, "opal-runtime-size", &size);
if (!ret)
ret = add_mem_range(mem_ranges, base, size);
of_node_put(dn);
return ret;
}
#endif /* CONFIG_KEXEC_FILE || CONFIG_CRASH_DUMP */
#ifdef CONFIG_KEXEC_FILE
/**
* add_reserved_mem_ranges - Adds "/reserved-ranges" regions exported by f/w
* to the given memory ranges list.
* @mem_ranges: Range list to add the memory ranges to.
*
* Returns 0 on success, negative errno on error.
*/
static int add_reserved_mem_ranges(struct crash_mem **mem_ranges)
{
int n_mem_addr_cells, n_mem_size_cells, i, len, cells, ret = 0;
struct device_node *root = of_find_node_by_path("/");
const __be32 *prop;
prop = of_get_property(root, "reserved-ranges", &len);
n_mem_addr_cells = of_n_addr_cells(root);
n_mem_size_cells = of_n_size_cells(root);
of_node_put(root);
if (!prop)
return 0;
cells = n_mem_addr_cells + n_mem_size_cells;
/* Each reserved range is an (address,size) pair */
for (i = 0; i < (len / (sizeof(u32) * cells)); i++) {
u64 base, size;
base = of_read_number(prop + (i * cells), n_mem_addr_cells);
size = of_read_number(prop + (i * cells) + n_mem_addr_cells,
n_mem_size_cells);
ret = add_mem_range(mem_ranges, base, size);
if (ret)
break;
}
return ret;
}
/**
* get_reserved_memory_ranges - Get reserve memory ranges. This list includes
* memory regions that should be added to the
* memory reserve map to ensure the region is
* protected from any mischief.
* @mem_ranges: Range list to add the memory ranges to.
*
* Returns 0 on success, negative errno on error.
*/
int get_reserved_memory_ranges(struct crash_mem **mem_ranges)
{
int ret;
ret = add_rtas_mem_range(mem_ranges);
if (ret)
goto out;
ret = add_tce_mem_ranges(mem_ranges);
if (ret)
goto out;
ret = add_reserved_mem_ranges(mem_ranges);
out:
if (ret)
pr_err("Failed to setup reserved memory ranges\n");
return ret;
}
/**
* get_exclude_memory_ranges - Get exclude memory ranges. This list includes
* regions like opal/rtas, tce-table, initrd,
* kernel, htab which should be avoided while
* setting up kexec load segments.
* @mem_ranges: Range list to add the memory ranges to.
*
* Returns 0 on success, negative errno on error.
*/
int get_exclude_memory_ranges(struct crash_mem **mem_ranges)
{
int ret;
ret = add_tce_mem_ranges(mem_ranges);
if (ret)
goto out;
ret = add_initrd_mem_range(mem_ranges);
if (ret)
goto out;
ret = add_htab_mem_range(mem_ranges);
if (ret)
goto out;
ret = add_kernel_mem_range(mem_ranges);
if (ret)
goto out;
ret = add_rtas_mem_range(mem_ranges);
if (ret)
goto out;
ret = add_opal_mem_range(mem_ranges);
if (ret)
goto out;
ret = add_reserved_mem_ranges(mem_ranges);
if (ret)
goto out;
/* exclude memory ranges should be sorted for easy lookup */
sort_memory_ranges(*mem_ranges, true);
out:
if (ret)
pr_err("Failed to setup exclude memory ranges\n");
return ret;
}
#ifdef CONFIG_CRASH_DUMP
/**
* get_usable_memory_ranges - Get usable memory ranges. This list includes
* regions like crashkernel, opal/rtas & tce-table,
* that kdump kernel could use.
* @mem_ranges: Range list to add the memory ranges to.
*
* Returns 0 on success, negative errno on error.
*/
int get_usable_memory_ranges(struct crash_mem **mem_ranges)
{
int ret, i;
/*
* Early boot failure observed on guests when low memory (first memory
* block?) is not added to usable memory. So, add [0, crashk_res.end]
* instead of [crashk_res.start, crashk_res.end] to workaround it.
* Also, crashed kernel's memory must be added to reserve map to
* avoid kdump kernel from using it.
*/
ret = add_mem_range(mem_ranges, 0, crashk_res.end + 1);
if (ret)
goto out;
for (i = 0; i < crashk_cma_cnt; i++) {
ret = add_mem_range(mem_ranges, crashk_cma_ranges[i].start,
crashk_cma_ranges[i].end - crashk_cma_ranges[i].start + 1);
if (ret)
goto out;
}
ret = add_rtas_mem_range(mem_ranges);
if (ret)
goto out;
ret = add_opal_mem_range(mem_ranges);
if (ret)
goto out;
ret = add_tce_mem_ranges(mem_ranges);
out:
if (ret)
pr_err("Failed to setup usable memory ranges\n");
return ret;
}
#endif /* CONFIG_CRASH_DUMP */
#endif /* CONFIG_KEXEC_FILE */
#ifdef CONFIG_CRASH_DUMP
static int crash_exclude_mem_range_guarded(struct crash_mem **mem_ranges,
unsigned long long mstart,
unsigned long long mend)
{
struct crash_mem *tmem = *mem_ranges;
/* Reallocate memory ranges if there is no space to split ranges */
if (tmem && (tmem->nr_ranges == tmem->max_nr_ranges)) {
tmem = realloc_mem_ranges(mem_ranges);
if (!tmem)
return -ENOMEM;
}
return crash_exclude_mem_range(tmem, mstart, mend);
}
/**
* get_crash_memory_ranges - Get crash memory ranges. This list includes
* first/crashing kernel's memory regions that
* would be exported via an elfcore.
* @mem_ranges: Range list to add the memory ranges to.
*
* Returns 0 on success, negative errno on error.
*/
int get_crash_memory_ranges(struct crash_mem **mem_ranges)
{
phys_addr_t base, end;
u64 i;
int ret;
for_each_mem_range(i, &base, &end) {
u64 size = end - base;
/* Skip backup memory region, which needs a separate entry */
if (base == BACKUP_SRC_START) {
if (size > BACKUP_SRC_SIZE) {
base = BACKUP_SRC_END + 1;
size -= BACKUP_SRC_SIZE;
} else
continue;
}
ret = add_mem_range(mem_ranges, base, size);
if (ret)
goto out;
/* Try merging adjacent ranges before reallocation attempt */
if ((*mem_ranges)->nr_ranges == (*mem_ranges)->max_nr_ranges)
sort_memory_ranges(*mem_ranges, true);
}
/* Exclude crashkernel region */
ret = crash_exclude_mem_range_guarded(mem_ranges, crashk_res.start, crashk_res.end);
if (ret)
goto out;
for (i = 0; i < crashk_cma_cnt; ++i) {
ret = crash_exclude_mem_range_guarded(mem_ranges, crashk_cma_ranges[i].start,
crashk_cma_ranges[i].end);
if (ret)
goto out;
}
/*
* FIXME: For now, stay in parity with kexec-tools but if RTAS/OPAL
* regions are exported to save their context at the time of
* crash, they should actually be backed up just like the
* first 64K bytes of memory.
*/
ret = add_rtas_mem_range(mem_ranges);
if (ret)
goto out;
ret = add_opal_mem_range(mem_ranges);
if (ret)
goto out;
/* create a separate program header for the backup region */
ret = add_mem_range(mem_ranges, BACKUP_SRC_START, BACKUP_SRC_SIZE);
if (ret)
goto out;
sort_memory_ranges(*mem_ranges, false);
out:
if (ret)
pr_err("Failed to setup crash memory ranges\n");
return ret;
}
/**
* remove_mem_range - Removes the given memory range from the range list.
* @mem_ranges: Range list to remove the memory range to.
* @base: Base address of the range to remove.
* @size: Size of the memory range to remove.
*
* (Re)allocates memory, if needed.
*
* Returns 0 on success, negative errno on error.
*/
int remove_mem_range(struct crash_mem **mem_ranges, u64 base, u64 size)
{
u64 end;
int ret = 0;
unsigned int i;
u64 mstart, mend;
struct crash_mem *mem_rngs = *mem_ranges;
if (!size)
return 0;
/*
* Memory range are stored as start and end address, use
* the same format to do remove operation.
*/
end = base + size - 1;
for (i = 0; i < mem_rngs->nr_ranges; i++) {
mstart = mem_rngs->ranges[i].start;
mend = mem_rngs->ranges[i].end;
/*
* Memory range to remove is not part of this range entry
* in the memory range list
*/
if (!(base >= mstart && end <= mend))
continue;
/*
* Memory range to remove is equivalent to this entry in the
* memory range list. Remove the range entry from the list.
*/
if (base == mstart && end == mend) {
for (; i < mem_rngs->nr_ranges - 1; i++) {
mem_rngs->ranges[i].start = mem_rngs->ranges[i+1].start;
mem_rngs->ranges[i].end = mem_rngs->ranges[i+1].end;
}
mem_rngs->nr_ranges--;
goto out;
}
/*
* Start address of the memory range to remove and the
* current memory range entry in the list is same. Just
* move the start address of the current memory range
* entry in the list to end + 1.
*/
else if (base == mstart) {
mem_rngs->ranges[i].start = end + 1;
goto out;
}
/*
* End address of the memory range to remove and the
* current memory range entry in the list is same.
* Just move the end address of the current memory
* range entry in the list to base - 1.
*/
else if (end == mend) {
mem_rngs->ranges[i].end = base - 1;
goto out;
}
/*
* Memory range to remove is not at the edge of current
* memory range entry. Split the current memory entry into
* two half.
*/
else {
size = mem_rngs->ranges[i].end - end + 1;
mem_rngs->ranges[i].end = base - 1;
ret = add_mem_range(mem_ranges, end + 1, size);
}
}
out:
return ret;
}
#endif /* CONFIG_CRASH_DUMP */
@@ -0,0 +1,499 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* This file contains kexec low-level functions.
*
* Copyright (C) 2002-2003 Eric Biederman <ebiederm@xmission.com>
* GameCube/ppc32 port Copyright (C) 2004 Albert Herranz
* PPC44x port. Copyright (C) 2011, IBM Corporation
* Author: Suzuki Poulose <suzuki@in.ibm.com>
*/
#include <linux/objtool.h>
#include <asm/reg.h>
#include <asm/page.h>
#include <asm/mmu.h>
#include <asm/ppc_asm.h>
#include <asm/kexec.h>
.text
/*
* Must be relocatable PIC code callable as a C function.
*/
.globl relocate_new_kernel
relocate_new_kernel:
/* r3 = page_list */
/* r4 = reboot_code_buffer */
/* r5 = start_address */
#ifdef CONFIG_PPC_85xx
mr r29, r3
mr r30, r4
mr r31, r5
#define ENTRY_MAPPING_KEXEC_SETUP
#include <kernel/85xx_entry_mapping.S>
#undef ENTRY_MAPPING_KEXEC_SETUP
mr r3, r29
mr r4, r30
mr r5, r31
li r0, 0
#elif defined(CONFIG_44x)
/* Save our parameters */
mr r29, r3
mr r30, r4
mr r31, r5
#ifdef CONFIG_PPC_47x
/* Check for 47x cores */
mfspr r3,SPRN_PVR
srwi r3,r3,16
cmplwi cr0,r3,PVR_476FPE@h
beq setup_map_47x
cmplwi cr0,r3,PVR_476@h
beq setup_map_47x
cmplwi cr0,r3,PVR_476_ISS@h
beq setup_map_47x
#endif /* CONFIG_PPC_47x */
/*
* Code for setting up 1:1 mapping for PPC440x for KEXEC
*
* We cannot switch off the MMU on PPC44x.
* So we:
* 1) Invalidate all the mappings except the one we are running from.
* 2) Create a tmp mapping for our code in the other address space(TS) and
* jump to it. Invalidate the entry we started in.
* 3) Create a 1:1 mapping for 0-2GiB in chunks of 256M in original TS.
* 4) Jump to the 1:1 mapping in original TS.
* 5) Invalidate the tmp mapping.
*
* - Based on the kexec support code for FSL BookE
*
*/
/*
* Load the PID with kernel PID (0).
* Also load our MSR_IS and TID to MMUCR for TLB search.
*/
li r3, 0
mtspr SPRN_PID, r3
mfmsr r4
andi. r4,r4,MSR_IS@l
beq wmmucr
oris r3,r3,PPC44x_MMUCR_STS@h
wmmucr:
mtspr SPRN_MMUCR,r3
sync
/*
* Invalidate all the TLB entries except the current entry
* where we are running from
*/
bcl 20,31,$+4 /* Find our address */
0: mflr r5 /* Make it accessible */
tlbsx r23,0,r5 /* Find entry we are in */
li r4,0 /* Start at TLB entry 0 */
li r3,0 /* Set PAGEID inval value */
1: cmpw r23,r4 /* Is this our entry? */
beq skip /* If so, skip the inval */
tlbwe r3,r4,PPC44x_TLB_PAGEID /* If not, inval the entry */
skip:
addi r4,r4,1 /* Increment */
cmpwi r4,64 /* Are we done? */
bne 1b /* If not, repeat */
isync
/* Create a temp mapping and jump to it */
andi. r6, r23, 1 /* Find the index to use */
addi r24, r6, 1 /* r24 will contain 1 or 2 */
mfmsr r9 /* get the MSR */
rlwinm r5, r9, 27, 31, 31 /* Extract the MSR[IS] */
xori r7, r5, 1 /* Use the other address space */
/* Read the current mapping entries */
tlbre r3, r23, PPC44x_TLB_PAGEID
tlbre r4, r23, PPC44x_TLB_XLAT
tlbre r5, r23, PPC44x_TLB_ATTRIB
/* Save our current XLAT entry */
mr r25, r4
/* Extract the TLB PageSize */
li r10, 1 /* r10 will hold PageSize */
rlwinm r11, r3, 0, 24, 27 /* bits 24-27 */
/* XXX: As of now we use 256M, 4K pages */
cmpwi r11, PPC44x_TLB_256M
bne tlb_4k
rotlwi r10, r10, 28 /* r10 = 256M */
b write_out
tlb_4k:
cmpwi r11, PPC44x_TLB_4K
bne default
rotlwi r10, r10, 12 /* r10 = 4K */
b write_out
default:
rotlwi r10, r10, 10 /* r10 = 1K */
write_out:
/*
* Write out the tmp 1:1 mapping for this code in other address space
* Fixup EPN = RPN , TS=other address space
*/
insrwi r3, r7, 1, 23 /* Bit 23 is TS for PAGEID field */
/* Write out the tmp mapping entries */
tlbwe r3, r24, PPC44x_TLB_PAGEID
tlbwe r4, r24, PPC44x_TLB_XLAT
tlbwe r5, r24, PPC44x_TLB_ATTRIB
subi r11, r10, 1 /* PageOffset Mask = PageSize - 1 */
not r10, r11 /* Mask for PageNum */
/* Switch to other address space in MSR */
insrwi r9, r7, 1, 26 /* Set MSR[IS] = r7 */
bcl 20,31,$+4
1: mflr r8
addi r8, r8, (2f-1b) /* Find the target offset */
/* Jump to the tmp mapping */
mtspr SPRN_SRR0, r8
mtspr SPRN_SRR1, r9
rfi
2:
/* Invalidate the entry we were executing from */
li r3, 0
tlbwe r3, r23, PPC44x_TLB_PAGEID
/* attribute fields. rwx for SUPERVISOR mode */
li r5, 0
ori r5, r5, (PPC44x_TLB_SW | PPC44x_TLB_SR | PPC44x_TLB_SX | PPC44x_TLB_G)
/* Create 1:1 mapping in 256M pages */
xori r7, r7, 1 /* Revert back to Original TS */
li r8, 0 /* PageNumber */
li r6, 3 /* TLB Index, start at 3 */
next_tlb:
rotlwi r3, r8, 28 /* Create EPN (bits 0-3) */
mr r4, r3 /* RPN = EPN */
ori r3, r3, (PPC44x_TLB_VALID | PPC44x_TLB_256M) /* SIZE = 256M, Valid */
insrwi r3, r7, 1, 23 /* Set TS from r7 */
tlbwe r3, r6, PPC44x_TLB_PAGEID /* PageID field : EPN, V, SIZE */
tlbwe r4, r6, PPC44x_TLB_XLAT /* Address translation : RPN */
tlbwe r5, r6, PPC44x_TLB_ATTRIB /* Attributes */
addi r8, r8, 1 /* Increment PN */
addi r6, r6, 1 /* Increment TLB Index */
cmpwi r8, 8 /* Are we done ? */
bne next_tlb
isync
/* Jump to the new mapping 1:1 */
li r9,0
insrwi r9, r7, 1, 26 /* Set MSR[IS] = r7 */
bcl 20,31,$+4
1: mflr r8
and r8, r8, r11 /* Get our offset within page */
addi r8, r8, (2f-1b)
and r5, r25, r10 /* Get our target PageNum */
or r8, r8, r5 /* Target jump address */
mtspr SPRN_SRR0, r8
mtspr SPRN_SRR1, r9
rfi
2:
/* Invalidate the tmp entry we used */
li r3, 0
tlbwe r3, r24, PPC44x_TLB_PAGEID
sync
b ppc44x_map_done
#ifdef CONFIG_PPC_47x
/* 1:1 mapping for 47x */
setup_map_47x:
/*
* Load the kernel pid (0) to PID and also to MMUCR[TID].
* Also set the MSR IS->MMUCR STS
*/
li r3, 0
mtspr SPRN_PID, r3 /* Set PID */
mfmsr r4 /* Get MSR */
andi. r4, r4, MSR_IS@l /* TS=1? */
beq 1f /* If not, leave STS=0 */
oris r3, r3, PPC47x_MMUCR_STS@h /* Set STS=1 */
1: mtspr SPRN_MMUCR, r3 /* Put MMUCR */
sync
/* Find the entry we are running from */
bcl 20,31,$+4
2: mflr r23
tlbsx r23, 0, r23
tlbre r24, r23, 0 /* TLB Word 0 */
tlbre r25, r23, 1 /* TLB Word 1 */
tlbre r26, r23, 2 /* TLB Word 2 */
/*
* Invalidates all the tlb entries by writing to 256 RPNs(r4)
* of 4k page size in all 4 ways (0-3 in r3).
* This would invalidate the entire UTLB including the one we are
* running from. However the shadow TLB entries would help us
* to continue the execution, until we flush them (rfi/isync).
*/
addis r3, 0, 0x8000 /* specify the way */
addi r4, 0, 0 /* TLB Word0 = (EPN=0, VALID = 0) */
addi r5, 0, 0
b clear_utlb_entry
/* Align the loop to speed things up. from head_44x.S */
.align 6
clear_utlb_entry:
tlbwe r4, r3, 0
tlbwe r5, r3, 1
tlbwe r5, r3, 2
addis r3, r3, 0x2000 /* Increment the way */
cmpwi r3, 0
bne clear_utlb_entry
addis r3, 0, 0x8000
addis r4, r4, 0x100 /* Increment the EPN */
cmpwi r4, 0
bne clear_utlb_entry
/* Create the entries in the other address space */
mfmsr r5
rlwinm r7, r5, 27, 31, 31 /* Get the TS (Bit 26) from MSR */
xori r7, r7, 1 /* r7 = !TS */
insrwi r24, r7, 1, 21 /* Change the TS in the saved TLB word 0 */
/*
* write out the TLB entries for the tmp mapping
* Use way '0' so that we could easily invalidate it later.
*/
lis r3, 0x8000 /* Way '0' */
tlbwe r24, r3, 0
tlbwe r25, r3, 1
tlbwe r26, r3, 2
/* Update the msr to the new TS */
insrwi r5, r7, 1, 26
bcl 20,31,$+4
1: mflr r6
addi r6, r6, (2f-1b)
mtspr SPRN_SRR0, r6
mtspr SPRN_SRR1, r5
rfi
/*
* Now we are in the tmp address space.
* Create a 1:1 mapping for 0-2GiB in the original TS.
*/
2:
li r3, 0
li r4, 0 /* TLB Word 0 */
li r5, 0 /* TLB Word 1 */
li r6, 0
ori r6, r6, PPC47x_TLB2_S_RWX /* TLB word 2 */
li r8, 0 /* PageIndex */
xori r7, r7, 1 /* revert back to original TS */
write_utlb:
rotlwi r5, r8, 28 /* RPN = PageIndex * 256M */
/* ERPN = 0 as we don't use memory above 2G */
mr r4, r5 /* EPN = RPN */
ori r4, r4, (PPC47x_TLB0_VALID | PPC47x_TLB0_256M)
insrwi r4, r7, 1, 21 /* Insert the TS to Word 0 */
tlbwe r4, r3, 0 /* Write out the entries */
tlbwe r5, r3, 1
tlbwe r6, r3, 2
addi r8, r8, 1
cmpwi r8, 8 /* Have we completed ? */
bne write_utlb
/* make sure we complete the TLB write up */
isync
/*
* Prepare to jump to the 1:1 mapping.
* 1) Extract page size of the tmp mapping
* DSIZ = TLB_Word0[22:27]
* 2) Calculate the physical address of the address
* to jump to.
*/
rlwinm r10, r24, 0, 22, 27
cmpwi r10, PPC47x_TLB0_4K
li r10, 0x1000 /* r10 = 4k */
beq 0f
/* Defaults to 256M */
lis r10, 0x1000
0: bcl 20,31,$+4
1: mflr r4
addi r4, r4, (2f-1b) /* virtual address of 2f */
subi r11, r10, 1 /* offsetmask = Pagesize - 1 */
not r10, r11 /* Pagemask = ~(offsetmask) */
and r5, r25, r10 /* Physical page */
and r6, r4, r11 /* offset within the current page */
or r5, r5, r6 /* Physical address for 2f */
/* Switch the TS in MSR to the original one */
mfmsr r8
insrwi r8, r7, 1, 26
mtspr SPRN_SRR1, r8
mtspr SPRN_SRR0, r5
rfi
2:
/* Invalidate the tmp mapping */
lis r3, 0x8000 /* Way '0' */
clrrwi r24, r24, 12 /* Clear the valid bit */
tlbwe r24, r3, 0
tlbwe r25, r3, 1
tlbwe r26, r3, 2
/* Make sure we complete the TLB write and flush the shadow TLB */
isync
#endif
ppc44x_map_done:
/* Restore the parameters */
mr r3, r29
mr r4, r30
mr r5, r31
li r0, 0
#else
li r0, 0
/*
* Set Machine Status Register to a known status,
* switch the MMU off and jump to 1: in a single step.
*/
mr r8, r0
ori r8, r8, MSR_RI|MSR_ME
mtspr SPRN_SRR1, r8
addi r8, r4, 1f - relocate_new_kernel
mtspr SPRN_SRR0, r8
sync
rfi
1:
#endif
/* from this point address translation is turned off */
/* and interrupts are disabled */
/* set a new stack at the bottom of our page... */
/* (not really needed now) */
addi r1, r4, KEXEC_CONTROL_PAGE_SIZE - 8 /* for LR Save+Back Chain */
stw r0, 0(r1)
/* Do the copies */
li r6, 0 /* checksum */
mr r0, r3
b 1f
0: /* top, read another word for the indirection page */
lwzu r0, 4(r3)
1:
/* is it a destination page? (r8) */
rlwinm. r7, r0, 0, 31, 31 /* IND_DESTINATION (1<<0) */
beq 2f
rlwinm r8, r0, 0, 0, 19 /* clear kexec flags, page align */
b 0b
2: /* is it an indirection page? (r3) */
rlwinm. r7, r0, 0, 30, 30 /* IND_INDIRECTION (1<<1) */
beq 2f
rlwinm r3, r0, 0, 0, 19 /* clear kexec flags, page align */
subi r3, r3, 4
b 0b
2: /* are we done? */
rlwinm. r7, r0, 0, 29, 29 /* IND_DONE (1<<2) */
beq 2f
b 3f
2: /* is it a source page? (r9) */
rlwinm. r7, r0, 0, 28, 28 /* IND_SOURCE (1<<3) */
beq 0b
rlwinm r9, r0, 0, 0, 19 /* clear kexec flags, page align */
li r7, PAGE_SIZE / 4
mtctr r7
subi r9, r9, 4
subi r8, r8, 4
9:
lwzu r0, 4(r9) /* do the copy */
xor r6, r6, r0
stwu r0, 4(r8)
dcbst 0, r8
sync
icbi 0, r8
bdnz 9b
addi r9, r9, 4
addi r8, r8, 4
b 0b
3:
/* To be certain of avoiding problems with self-modifying code
* execute a serializing instruction here.
*/
isync
sync
mfspr r3, SPRN_PIR /* current core we are running on */
mr r4, r5 /* load physical address of chunk called */
/* jump to the entry point, usually the setup routine */
mtlr r5
blrl
1: b 1b
relocate_new_kernel_end:
.globl relocate_new_kernel_size
relocate_new_kernel_size:
.long relocate_new_kernel_end - relocate_new_kernel
@@ -0,0 +1,32 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/vmcore_info.h>
#include <asm/pgalloc.h>
void arch_crash_save_vmcoreinfo(void)
{
#ifdef CONFIG_NUMA
VMCOREINFO_SYMBOL(node_data);
VMCOREINFO_LENGTH(node_data, MAX_NUMNODES);
#endif
#ifndef CONFIG_NUMA
VMCOREINFO_SYMBOL(contig_page_data);
#endif
#if defined(CONFIG_PPC64) && defined(CONFIG_SPARSEMEM_VMEMMAP)
VMCOREINFO_SYMBOL(vmemmap_list);
VMCOREINFO_SYMBOL(mmu_vmemmap_psize);
VMCOREINFO_SYMBOL(mmu_psize_defs);
VMCOREINFO_STRUCT_SIZE(vmemmap_backing);
VMCOREINFO_OFFSET(vmemmap_backing, list);
VMCOREINFO_OFFSET(vmemmap_backing, phys);
VMCOREINFO_OFFSET(vmemmap_backing, virt_addr);
VMCOREINFO_STRUCT_SIZE(mmu_psize_def);
VMCOREINFO_OFFSET(mmu_psize_def, shift);
#endif
VMCOREINFO_SYMBOL(cur_cpu_spec);
VMCOREINFO_OFFSET(cpu_spec, cpu_features);
VMCOREINFO_OFFSET(cpu_spec, mmu_features);
vmcoreinfo_append_str("NUMBER(RADIX_MMU)=%d\n", early_radix_enabled());
vmcoreinfo_append_str("KERNELOFFSET=%lx\n", kaslr_offset());
}