Blob Blame History Raw
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 <takahiro.akashi@linaro.org>
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 <takahiro.akashi@linaro.org>
+ *
+ * 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 <assert.h>
+#include <elf.h>
 #include <errno.h>
-#include <linux/elf.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
 
 #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 <takahiro.akashi@linaro.org>
+ *
+ * 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 <errno.h>
 #include <getopt.h>
 #include <libfdt.h>
+#include <stdlib.h>
 
-#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