/* * 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 #include #include #include #include #include "../../kexec.h" #include "../../kexec-elf.h" #include "../../crashdump.h" #include "crashdump-arm.h" #if __BYTE_ORDER == __LITTLE_ENDIAN #define ELFDATANATIVE ELFDATA2LSB #elif __BYTE_ORDER == __BIG_ENDIAN #define ELFDATANATIVE ELFDATA2MSB #else #error "Unknown machine endian" #endif /* * Used to save various memory ranges/regions needed for the captured * kernel to boot. (lime memmap= option in other archs) */ static struct memory_range crash_memory_ranges[CRASH_MAX_MEMORY_RANGES]; struct memory_ranges usablemem_rgns = { .size = 0, .ranges = crash_memory_ranges, }; /* memory range reserved for crashkernel */ static struct memory_range crash_reserved_mem; static struct crash_elf_info elf_info = { .class = ELFCLASS32, .data = ELFDATANATIVE, .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 (usablemem_rgns.size >= CRASH_MAX_MEMORY_RANGES) return 1; range = usablemem_rgns.ranges + usablemem_rgns.size; if (strncmp(str, "System RAM\n", 11) == 0) { range->start = base; range->end = base + length - 1; range->type = RANGE_RAM; usablemem_rgns.size++; } 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 < usablemem_rgns.size; i++) { struct memory_range *r = usablemem_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; /* * Let's split this area into 2 smaller ones and * remove excluded range from between. First create * new entry for the remaining area. */ new = usablemem_rgns.ranges + usablemem_rgns.size; new->start = range->end + 1; new->end = r->end; usablemem_rgns.size++; /* * 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 (usablemem_rgns.size < 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(usablemem_rgns.ranges, usablemem_rgns.size, sizeof(*usablemem_rgns.ranges), 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'; } 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; if (!kexec_debug) return; 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 < usablemem_rgns.size; i++) { struct memory_range *r = usablemem_rgns.ranges + i; dbgprintf("memory range: [%#llx - %#llx] (%ldM)\n", r->start, r->end, (unsigned long)range_size(r) >> 20); } } /** * 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 = usablemem_rgns.ranges->start; dbgprintf("phys_offset: %#lx\n", phys_offset); err = crash_create_elf32_headers(info, &elf_info, usablemem_rgns.ranges, usablemem_rgns.size, &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; }