From 9cd9f0ab98ce6ddbdbb9a045ed2887e0dd857afe Mon Sep 17 00:00:00 2001 Message-Id: <9cd9f0ab98ce6ddbdbb9a045ed2887e0dd857afe.1431592766.git.panand@redhat.com> In-Reply-To: <1fb6841aa15407dbf371589d7abca7bc2d35815c.1431592766.git.panand@redhat.com> References: <1fb6841aa15407dbf371589d7abca7bc2d35815c.1431592766.git.panand@redhat.com> From: AKASHI Takahiro Date: Tue, 17 Feb 2015 16:06:55 +0900 Subject: [PATCH 08/17] arm64: kdump: create elf core header --- kexec/arch/arm64/crashdump-arm64.c | 295 ++++++++++++++++++++++++++++++++++++- kexec/arch/arm64/crashdump-arm64.h | 17 ++- kexec/arch/arm64/kexec-arm64.c | 4 +- kexec/arch/arm64/kexec-elf-arm64.c | 31 +++- 4 files changed, 332 insertions(+), 15 deletions(-) diff --git a/kexec/arch/arm64/crashdump-arm64.c b/kexec/arch/arm64/crashdump-arm64.c index d2272c8124d0..ec83cf0c0f38 100644 --- a/kexec/arch/arm64/crashdump-arm64.c +++ b/kexec/arch/arm64/crashdump-arm64.c @@ -1,11 +1,28 @@ /* - * ARM64 crashdump. + * crashdump for arm64 + * + * Copyright (C) Nokia Corporation, 2010. + * Author: Mika Westerberg + * + * Based on x86 implementation + * Copyright (C) IBM Corporation, 2005. All rights reserved + * + * Copyright (c) 2014 Linaro Limited + * Author: AKASHI Takahiro + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. */ -#define _GNU_SOURCE +#define _GNU_SOURCE /* for asprintf */ +#include +#include #include -#include +#include +#include +#include #include "kexec.h" #include "crashdump.h" @@ -13,9 +30,279 @@ #include "kexec-arm64.h" #include "kexec-elf.h" -struct memory_ranges usablemem_rgns = {}; + +/* + * Used to save various memory ranges/regions needed for the captured + * kernel to boot. (like memmap= option in other archs) + */ +static struct memory_range crash_memory_ranges[CRASH_MAX_MEMORY_RANGES]; +struct memory_ranges crashmem_rgns = { + .size = 0, + .ranges = crash_memory_ranges, +}; + +/* memory range reserved for crashkernel */ +struct memory_range crash_reserved_mem; +struct memory_ranges usablemem_rgns = { + .size = 0, + .ranges = &crash_reserved_mem, +}; + +static struct crash_elf_info elf_info = { + .class = ELFCLASS64, +#if (__BYTE_ORDER == __LITTLE_ENDIAN) + .data = ELFDATA2LSB, +#else + .data = ELFDATA2MSB, +#endif + .machine = EM_AARCH64, +}; int is_crashkernel_mem_reserved(void) { + uint64_t start, end; + + if (parse_iomem_single("Crash kernel\n", &start, &end) == 0) + return start != end; + + return 0; +} + +/* + * crash_range_callback() - callback called for each iomem region + * @data: not used + * @nr: not used + * @str: name of the memory region + * @base: start address of the memory region + * @length: size of the memory region + * + * This function is called once for each memory region found in /proc/iomem. + * It locates system RAM and crashkernel reserved memory and places these to + * variables: @crash_memory_ranges and @crash_reserved_mem. Number of memory + * regions is placed in @crash_memory_nr_ranges. + */ + +static int crash_range_callback(void *UNUSED(data), int UNUSED(nr), + char *str, unsigned long long base, + unsigned long long length) +{ + struct memory_range *range; + + assert(arm64_mem.memstart); + + if (crashmem_rgns.size >= CRASH_MAX_MEMORY_RANGES) + return 1; + + range = crashmem_rgns.ranges + crashmem_rgns.size; + + if (strncmp(str, "System RAM\n", 11) == 0) { + range->start = base; + range->end = base + length - 1; + range->type = RANGE_RAM; + crashmem_rgns.size++; + } else if (strncmp(str, "Crash kernel\n", 13) == 0) { + if (base < arm64_mem.memstart) + base += arm64_mem.memstart; + crash_reserved_mem.start = base; + crash_reserved_mem.end = base + length - 1; + crash_reserved_mem.type = RANGE_RAM; + usablemem_rgns.size++; + } + + return 0; +} + +/* + * crash_exclude_range() - excludes memory region reserved for crashkernel + * + * Function locates where crashkernel reserved memory is and removes that + * region from the available memory regions. + */ +static void crash_exclude_range(void) +{ + const struct memory_range *range = &crash_reserved_mem; + int i; + + for (i = 0; i < crashmem_rgns.size; i++) { + struct memory_range *r = crashmem_rgns.ranges + i; + + /* + * We assume that crash area is fully contained in + * some larger memory area. + */ + if (r->start <= range->start && r->end >= range->end) { + struct memory_range *new; + + if (r->start == range->start) { + if (r->end == range->end) { + memcpy(r, r + 1, + sizeof(r) + * (crashmem_rgns.size - i - 1)); + crashmem_rgns.size--; + } else { + r->start = range->end + 1; + } + break; + } + if (r->end == range->end) { + r->end = range->start - 1; + break; + } + + /* + * Let's split this area into 2 smaller ones and + * remove excluded range from between. First create + * new entry for the remaining area. + */ + new = crashmem_rgns.ranges + crashmem_rgns.size; + new->start = range->end + 1; + new->end = r->end; + crashmem_rgns.size++; + /* + * Next update this area to end before excluded range. + */ + r->end = range->start - 1; + break; + } + } +} + +/* + * crash_get_memory_ranges() - read system physical memory + * + * Function reads through system physical memory and stores found memory + * regions in @crash_memory_ranges. Number of memory regions found is placed + * in @crash_memory_nr_ranges. Regions are sorted in ascending order. + * + * Returns %0 in case of success and %-1 otherwise (errno is set). + */ +static int crash_get_memory_ranges(void) +{ + /* + * First read all memory regions that can be considered as + * system memory including the crash area. + */ + kexec_iomem_for_each_line(NULL, crash_range_callback, NULL); + + if (usablemem_rgns.size != 1) { + errno = EINVAL; + return -1; + } + + /* + * Exclude memory reserved for crashkernel (this may result int + * split memory regions). + */ + /* + * FIXME: + * Do we have to check crashkernel is within main memory? + */ + crash_exclude_range(); + + return 0; +} + +/* + * range_size - Return range size in MiB. + */ + +static unsigned long range_size(const struct memory_range *r) +{ + return (r->end - r->start + 1) >> 20; +} + +static void dump_crash_ranges(void) +{ + int i; + + if (!kexec_debug) + return; + + dbgprintf("%s: kernel: %016llx - %016llx (%ld MiB)\n", __func__, + crash_reserved_mem.start, crash_reserved_mem.end, + range_size(&crash_reserved_mem)); + + for (i = 0; i < crashmem_rgns.size; i++) { + struct memory_range *r = crashmem_rgns.ranges + i; + dbgprintf("%s: RAM: %016llx - %016llx (%ld MiB)\n", __func__, + r->start, r->end, range_size(r)); + } +} + +/* + * load_crashdump_segments() - create elf core header for /proc/vmcore + * @info: kexec info structure + * @option: To be appended to kernel command line + * + * This function loads additional segments which are needed for the dump + * capture kernel. It also updates kernel command line passed in + * @command_line_extra to have the correct parameters for the dump capture + * kernel. + * + * Return %0 in case of success and %-1 in case of error. + */ + +int load_crashdump_segments(struct kexec_info *info, char **option) +{ + unsigned long elfcorehdr; + unsigned long bufsz; + void *buf; + int err; + + /* + * First fetch all the memory (RAM) ranges that we are going to + * pass to the crashdump kernel during panic. + */ + + err = crash_get_memory_ranges(); + + if (err) + return err; + + dump_crash_ranges(); + + elf_info.page_offset = arm64_mem.page_offset; + + err = crash_create_elf64_headers(info, &elf_info, crashmem_rgns.ranges, + crashmem_rgns.size, &buf, &bufsz, ELF_CORE_HEADER_ALIGN); + + if (err) + return err; + + /* + * FIXME: check if we need 128KB alignment here as stated in arm. + */ + elfcorehdr = add_buffer_phys_virt(info, buf, bufsz, bufsz, 0, + crash_reserved_mem.start, crash_reserved_mem.end, + -1, 0); + + err = asprintf(option, " elfcorehdr=%#lx@%#lx", bufsz, elfcorehdr); + + if (err == -1) + return err; + + dbgprintf("%s:%s\n", __func__, *option); + return 0; } + +void modify_ehdr_for_crashmem(struct mem_ehdr *ehdr) +{ + struct mem_phdr *phdr; + int i; + + ehdr->e_entry += crash_reserved_mem.start; + + for (i = 0; i < ehdr->e_phnum; i++) { + phdr = &ehdr->e_phdr[i]; + if (phdr->p_type != PT_LOAD) + continue; + phdr->p_paddr += + (-arm64_mem.memstart + crash_reserved_mem.start); + } +} + +void set_crash_entry(struct mem_ehdr *ehdr, struct kexec_info *info) +{ + info->entry = (void *)crash_reserved_mem.start + arm64_mem.text_offset; +} diff --git a/kexec/arch/arm64/crashdump-arm64.h b/kexec/arch/arm64/crashdump-arm64.h index f33c7a25b454..3f8b38350841 100644 --- a/kexec/arch/arm64/crashdump-arm64.h +++ b/kexec/arch/arm64/crashdump-arm64.h @@ -1,12 +1,25 @@ /* - * ARM64 crashdump. + * crashdump for arm64 + * + * Copyright (c) 2014 Linaro Limited + * Author: AKASHI Takahiro + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. */ -#if !defined(CRASHDUMP_ARM64_H) +#ifndef CRASHDUMP_ARM64_H #define CRASHDUMP_ARM64_H #include "kexec.h" +#define CRASH_MAX_MEMORY_RANGES 32 + extern struct memory_ranges usablemem_rgns; +int load_crashdump_segments(struct kexec_info *info, char **option); +void modify_ehdr_for_crashmem(struct mem_ehdr *ehdr); +void set_crash_entry(struct mem_ehdr *ehdr, struct kexec_info *info); + #endif diff --git a/kexec/arch/arm64/kexec-arm64.c b/kexec/arch/arm64/kexec-arm64.c index eb68b6b3d9e3..e83e83bdd6db 100644 --- a/kexec/arch/arm64/kexec-arm64.c +++ b/kexec/arch/arm64/kexec-arm64.c @@ -758,7 +758,7 @@ unsigned long phys_to_virt(struct crash_elf_info *UNUSED(elf_info), v = p - arm64_mem.memstart + arm64_mem.page_offset; dbgprintf("%s: %016lx -> %016lx\n", __func__, p, v); - return p; + return v; } void add_segment(struct kexec_info *info, const void *buf, size_t bufsz, @@ -924,7 +924,7 @@ static int get_memory_ranges_iomem(struct memory_range *array, r.type = RANGE_RAM; - dbgprintf("%s:%d: RAM: %016llx - %016llx : %s", __func__, + dbgprintf("%s:%d: RAM: %016llx - %016llx : %s", __func__, __LINE__, r.start, r.end, str); array[(*count)++] = r; diff --git a/kexec/arch/arm64/kexec-elf-arm64.c b/kexec/arch/arm64/kexec-elf-arm64.c index 8b336054a6ab..903602985eb4 100644 --- a/kexec/arch/arm64/kexec-elf-arm64.c +++ b/kexec/arch/arm64/kexec-elf-arm64.c @@ -9,9 +9,10 @@ #include #include #include +#include -#include "dt-ops.h" #include "crashdump-arm64.h" +#include "dt-ops.h" #include "kexec-arm64.h" #include "fs2dt.h" #include "kexec-syscall.h" @@ -45,16 +46,13 @@ on_exit: int elf_arm64_load(int argc, char **argv, const char *kernel_buf, off_t kernel_size, struct kexec_info *info) { + char *header_option = NULL; int result; struct mem_ehdr ehdr; bool found_header; int i; - if (info->kexec_flags & KEXEC_ON_CRASH) { - fprintf(stderr, "kexec: kdump not yet supported on arm64\n"); - return -EINVAL; - } - + /* Parse the Elf file */ result = build_elf_exec_info(kernel_buf, kernel_size, &ehdr, 0); if (result < 0) { @@ -94,6 +92,18 @@ int elf_arm64_load(int argc, char **argv, const char *kernel_buf, goto exit; } + if (info->kexec_flags & KEXEC_ON_CRASH) { + result = load_crashdump_segments(info, &header_option); + + if (result) { + fprintf(stderr, "kexec: creating eflcorehdr failed.\n"); + goto exit; + } + } + + if (info->kexec_flags & KEXEC_ON_CRASH) + modify_ehdr_for_crashmem(&ehdr); + result = elf_exec_load(&ehdr, info); if (result) { @@ -101,6 +111,11 @@ int elf_arm64_load(int argc, char **argv, const char *kernel_buf, goto exit; } + if (info->kexec_flags & KEXEC_ON_CRASH) + set_crash_entry(&ehdr, info); + else + info->entry = (void *)virt_to_phys(ehdr.e_entry); + dbgprintf("%s: text_offset: %016lx\n", __func__, arm64_mem.text_offset); dbgprintf("%s: image_size: %016lx\n", __func__, arm64_mem.image_size); dbgprintf("%s: page_offset: %016lx\n", __func__, arm64_mem.page_offset); @@ -108,9 +123,11 @@ int elf_arm64_load(int argc, char **argv, const char *kernel_buf, dbgprintf("%s: e_entry: %016llx -> %016lx\n", __func__, ehdr.e_entry, virt_to_phys(ehdr.e_entry)); - result = arm64_load_other_segments(info, virt_to_phys(ehdr.e_entry)); + result = arm64_load_other_segments(info, (unsigned long)info->entry); exit: free_elf_info(&ehdr); + if (header_option) + free(header_option); return result; } -- 2.1.0