summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--kexec/arch/arm/Makefile3
-rw-r--r--kexec/arch/arm/crashdump-arm.c320
-rw-r--r--kexec/arch/arm/crashdump-arm.h21
-rw-r--r--kexec/arch/arm/kexec-arm.c5
-rw-r--r--kexec/arch/arm/kexec-zImage-arm.c47
-rw-r--r--kexec/arch/arm/phys_to_virt.c20
6 files changed, 409 insertions, 7 deletions
diff --git a/kexec/arch/arm/Makefile b/kexec/arch/arm/Makefile
index 2ecdb66..7419621 100644
--- a/kexec/arch/arm/Makefile
+++ b/kexec/arch/arm/Makefile
@@ -5,8 +5,11 @@ arm_KEXEC_SRCS= kexec/arch/arm/kexec-elf-rel-arm.c
arm_KEXEC_SRCS+= kexec/arch/arm/kexec-zImage-arm.c
arm_KEXEC_SRCS+= kexec/arch/arm/kexec-uImage-arm.c
arm_KEXEC_SRCS+= kexec/arch/arm/kexec-arm.c
+arm_KEXEC_SRCS+= kexec/arch/arm/crashdump-arm.c
arm_KEXEC_SRCS+= kexec/kexec-uImage.c
+arm_PHYS_TO_VIRT = kexec/arch/arm/phys_to_virt.c
+
dist += kexec/arch/arm/Makefile $(arm_KEXEC_SRCS) \
kexec/arch/arm/kexec-arm.h \
kexec/arch/arm/include/arch/options.h
diff --git a/kexec/arch/arm/crashdump-arm.c b/kexec/arch/arm/crashdump-arm.c
new file mode 100644
index 0000000..e9b22bf
--- /dev/null
+++ b/kexec/arch/arm/crashdump-arm.c
@@ -0,0 +1,320 @@
+/*
+ * kexec: Linux boots Linux
+ *
+ * Copyright (C) Nokia Corporation, 2010.
+ * Author: Mika Westerberg
+ *
+ * Based on x86 implementation
+ * Copyright (C) IBM Corporation, 2005. All rights reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation (version 2 of the License).
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#include <elf.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "../../kexec.h"
+#include "../../kexec-elf.h"
+#include "../../crashdump.h"
+#include "crashdump-arm.h"
+
+static struct memory_range crash_memory_ranges[CRASH_MAX_MEMORY_RANGES];
+static int crash_memory_nr_ranges;
+
+/* memory range reserved for crashkernel */
+static struct memory_range crash_reserved_mem;
+
+static struct crash_elf_info elf_info = {
+ .class = ELFCLASS32,
+ .data = ELFDATA2LSB,
+ .machine = EM_ARM,
+ .page_offset = PAGE_OFFSET,
+};
+
+unsigned long phys_offset;
+
+/**
+ * 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 base,
+ unsigned long length)
+{
+ struct memory_range *range;
+
+ if (crash_memory_nr_ranges >= CRASH_MAX_MEMORY_RANGES)
+ return 1;
+
+ range = &crash_memory_ranges[crash_memory_nr_ranges];
+
+ if (strncmp(str, "System RAM\n", 11) == 0) {
+ range->start = base;
+ range->end = base + length - 1;
+ range->type = RANGE_RAM;
+ crash_memory_nr_ranges++;
+ } else if (strncmp(str, "Crash kernel\n", 13) == 0) {
+ crash_reserved_mem.start = base;
+ crash_reserved_mem.end = base + length - 1;
+ crash_reserved_mem.type = RANGE_RAM;
+ }
+
+ 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 < crash_memory_nr_ranges; i++) {
+ struct memory_range *r = &crash_memory_ranges[i];
+
+ /*
+ * We assume that crash area is fully contained in
+ * some larger memory area.
+ */
+ if (r->start <= range->start && r->end >= range->end) {
+ /*
+ * Let's split this area into 2 smaller ones and
+ * remove excluded range from between. First create
+ * new entry for the remaining area.
+ */
+ crash_memory_ranges[crash_memory_nr_ranges].start = range->end + 1;
+ crash_memory_ranges[crash_memory_nr_ranges].end = r->end;
+ crash_memory_nr_ranges++;
+ /*
+ * Next update this area to end before excluded range.
+ */
+ r->end = range->start - 1;
+ break;
+ }
+ }
+}
+
+static int range_cmp(const void *a1, const void *a2)
+{
+ const struct memory_range *r1 = a1;
+ const struct memory_range *r2 = a2;
+
+ if (r1->start > r2->start)
+ return 1;
+ if (r1->start < r2->start)
+ return -1;
+
+ return 0;
+}
+
+/**
+ * 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 (crash_memory_nr_ranges < 1) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /*
+ * Exclude memory reserved for crashkernel (this may result a split memory
+ * region).
+ */
+ crash_exclude_range();
+
+ /*
+ * Make sure that the memory regions are sorted.
+ */
+ qsort(crash_memory_ranges, crash_memory_nr_ranges,
+ sizeof(crash_memory_ranges[0]), range_cmp);
+
+ return 0;
+}
+
+/**
+ * cmdline_add_elfcorehdr() - adds elfcorehdr= to @cmdline
+ * @cmdline: buffer where parameter is placed
+ * @elfcorehdr: physical address of elfcorehdr
+ *
+ * Function appends 'elfcorehdr=start' at the end of the command line given in
+ * @cmdline. Note that @cmdline must be at least %COMMAND_LINE_SIZE bytes long
+ * (inclunding %NUL).
+ */
+static void cmdline_add_elfcorehdr(char *cmdline, unsigned long elfcorehdr)
+{
+ char buf[COMMAND_LINE_SIZE];
+ int buflen;
+
+ buflen = snprintf(buf, sizeof(buf), "%s elfcorehdr=%#lx",
+ cmdline, elfcorehdr);
+ if (buflen < 0)
+ die("Failed to construct elfcorehdr= command line parameter\n");
+ if (buflen >= sizeof(buf))
+ die("Command line overflow\n");
+
+ (void) strncpy(cmdline, buf, COMMAND_LINE_SIZE);
+ cmdline[COMMAND_LINE_SIZE - 1] = '\0';
+}
+
+/**
+ * cmdline_add_mem() - adds mem= parameter to kernel command line
+ * @cmdline: buffer where parameter is placed
+ * @size: size of the kernel reserved memory (in bytes)
+ *
+ * This function appends 'mem=size' at the end of the command line given in
+ * @cmdline. Note that @cmdline must be at least %COMMAND_LINE_SIZE bytes long
+ * (including %NUL).
+ */
+static void cmdline_add_mem(char *cmdline, unsigned long size)
+{
+ char buf[COMMAND_LINE_SIZE];
+ int buflen;
+
+ buflen = snprintf(buf, sizeof(buf), "%s mem=%ldK", cmdline, size >> 10);
+ if (buflen < 0)
+ die("Failed to construct mem= command line parameter\n");
+ if (buflen >= sizeof(buf))
+ die("Command line overflow\n");
+
+ (void) strncpy(cmdline, buf, COMMAND_LINE_SIZE);
+ cmdline[COMMAND_LINE_SIZE - 1] = '\0';
+}
+
+#ifdef DEBUG
+static unsigned long long range_size(const struct memory_range *r)
+{
+ return r->end - r->start + 1;
+}
+
+static void dump_memory_ranges(void)
+{
+ int i;
+
+ dbgprintf("crashkernel: [%#llx - %#llx] (%ldM)\n",
+ crash_reserved_mem.start, crash_reserved_mem.end,
+ (unsigned long)range_size(&crash_reserved_mem) >> 20);
+
+ for (i = 0; i < crash_memory_nr_ranges; i++) {
+ struct memory_range *r = &crash_memory_ranges[i];
+ dbgprintf("memory range: [%#llx - %#llx] (%ldM)\n",
+ r->start, r->end, (unsigned long)range_size(r) >> 20);
+ }
+}
+#else
+static inline void dump_memory_ranges(void) {}
+#endif
+
+/**
+ * load_crashdump_segments() - loads additional segments needed for kdump
+ * @info: kexec info structure
+ * @mod_cmdline: kernel command line
+ *
+ * This function loads additional segments which are needed for the dump capture
+ * kernel. It also updates kernel command line passed in @mod_cmdline to have
+ * right 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 *mod_cmdline)
+{
+ 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;
+
+ /*
+ * Now that we have memory regions sorted, we can use first memory
+ * region as PHYS_OFFSET.
+ */
+ phys_offset = crash_memory_ranges[0].start;
+ dbgprintf("phys_offset: %#lx\n", phys_offset);
+
+ err = crash_create_elf32_headers(info, &elf_info, crash_memory_ranges,
+ crash_memory_nr_ranges, &buf, &bufsz,
+ ELF_CORE_HEADER_ALIGN);
+ if (err)
+ return err;
+
+ /*
+ * We allocate ELF core header from the end of the memory area reserved
+ * for the crashkernel. We align the header to SECTION_SIZE (which is
+ * 1MB) so that available memory passed in kernel command line will be
+ * aligned to 1MB. This is because kernel create_mapping() wants memory
+ * regions to be aligned to SECTION_SIZE.
+ */
+ elfcorehdr = add_buffer_phys_virt(info, buf, bufsz, bufsz, 1 << 20,
+ crash_reserved_mem.start,
+ crash_reserved_mem.end, -1, 0);
+
+ dbgprintf("elfcorehdr: %#lx\n", elfcorehdr);
+ cmdline_add_elfcorehdr(mod_cmdline, elfcorehdr);
+
+ /*
+ * Add 'mem=size' parameter to dump capture kernel command line. This
+ * prevents the dump capture kernel from using any other memory regions
+ * which belong to the primary kernel.
+ */
+ cmdline_add_mem(mod_cmdline, elfcorehdr - crash_reserved_mem.start);
+
+ dump_memory_ranges();
+ dbgprintf("kernel command line: \"%s\"\n", mod_cmdline);
+
+ return 0;
+}
+
+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;
+}
diff --git a/kexec/arch/arm/crashdump-arm.h b/kexec/arch/arm/crashdump-arm.h
new file mode 100644
index 0000000..a2e7824
--- /dev/null
+++ b/kexec/arch/arm/crashdump-arm.h
@@ -0,0 +1,21 @@
+#ifndef CRASHDUMP_ARM_H
+#define CRASHDUMP_ARM_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define COMMAND_LINE_SIZE 1024
+#define PAGE_OFFSET 0xc0000000
+#define CRASH_MAX_MEMORY_RANGES 32
+
+struct kexec_info;
+
+extern unsigned long phys_offset;
+extern int load_crashdump_segments(struct kexec_info *, char *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* CRASHDUMP_ARM_H */
diff --git a/kexec/arch/arm/kexec-arm.c b/kexec/arch/arm/kexec-arm.c
index de3f713..8646833 100644
--- a/kexec/arch/arm/kexec-arm.c
+++ b/kexec/arch/arm/kexec-arm.c
@@ -104,8 +104,3 @@ int arch_compat_trampoline(struct kexec_info *UNUSED(info))
void arch_update_purgatory(struct kexec_info *UNUSED(info))
{
}
-
-int is_crashkernel_mem_reserved(void)
-{
- return 0; /* kdump is not supported on this platform (yet) */
-}
diff --git a/kexec/arch/arm/kexec-zImage-arm.c b/kexec/arch/arm/kexec-zImage-arm.c
index 1a446d9..8e27956 100644
--- a/kexec/arch/arm/kexec-zImage-arm.c
+++ b/kexec/arch/arm/kexec-zImage-arm.c
@@ -12,10 +12,12 @@
#include <stdint.h>
#include <unistd.h>
#include <getopt.h>
+#include <unistd.h>
#include <arch/options.h>
#include "../../kexec.h"
+#include "../../kexec-syscall.h"
+#include "crashdump-arm.h"
-#define COMMAND_LINE_SIZE 1024
#define BOOT_PARAMS_SIZE 1536
struct tag_header {
@@ -213,6 +215,7 @@ int zImage_arm_load(int argc, char **argv, const char *buf, off_t len,
unsigned int atag_offset = 0x1000; /* 4k offset from memory start */
unsigned int offset = 0x8000; /* 32k offset from memory start */
const char *command_line;
+ char *modified_cmdline = NULL;
off_t command_line_len;
const char *ramdisk;
char *ramdisk_buf;
@@ -266,7 +269,47 @@ int zImage_arm_load(int argc, char **argv, const char *buf, off_t len,
ramdisk_buf = slurp_file(ramdisk, &ramdisk_length);
}
- base = locate_hole(info,len+offset,0,0,ULONG_MAX,INT_MAX);
+ /*
+ * If we are loading a dump capture kernel, we need to update kernel
+ * command line and also add some additional segments.
+ */
+ if (info->kexec_flags & KEXEC_ON_CRASH) {
+ uint64_t start, end;
+
+ modified_cmdline = xmalloc(COMMAND_LINE_SIZE);
+ if (!modified_cmdline)
+ return -1;
+
+ if (command_line) {
+ (void) strncpy(modified_cmdline, command_line,
+ COMMAND_LINE_SIZE);
+ modified_cmdline[COMMAND_LINE_SIZE - 1] = '\0';
+ }
+
+ if (load_crashdump_segments(info, modified_cmdline) < 0) {
+ free(modified_cmdline);
+ return -1;
+ }
+
+ command_line = modified_cmdline;
+ command_line_len = strlen(command_line) + 1;
+
+ /*
+ * We put the dump capture kernel at the start of crashkernel
+ * reserved memory.
+ */
+ if (parse_iomem_single("Crash kernel\n", &start, &end)) {
+ /*
+ * No crash kernel memory reserved. We cannot do more
+ * but just bail out.
+ */
+ return -1;
+ }
+ base = start;
+ } else {
+ base = locate_hole(info,len+offset,0,0,ULONG_MAX,INT_MAX);
+ }
+
if (base == ULONG_MAX)
return -1;
diff --git a/kexec/arch/arm/phys_to_virt.c b/kexec/arch/arm/phys_to_virt.c
new file mode 100644
index 0000000..bcced52
--- /dev/null
+++ b/kexec/arch/arm/phys_to_virt.c
@@ -0,0 +1,20 @@
+#include "../../kexec.h"
+#include "../../crashdump.h"
+#include "crashdump-arm.h"
+
+/**
+ * phys_to_virt() - translate physical address to virtual address
+ * @paddr: physical address to translate
+ *
+ * For ARM we have following equation to translate from virtual address to
+ * physical:
+ * paddr = vaddr - PAGE_OFFSET + PHYS_OFFSET
+ *
+ * See also:
+ * http://lists.arm.linux.org.uk/lurker/message/20010723.185051.94ce743c.en.html
+ */
+unsigned long
+phys_to_virt(struct crash_elf_info *elf_info, unsigned long paddr)
+{
+ return paddr + elf_info->page_offset - phys_offset;
+}