blob: e7840e01797961afac1e401ee048bf22adc9da3b [file] [log] [blame]
/*
* kexec: Linux boots Linux
*
* 2005 (C) IBM Corporation.
* 2008 (C) MontaVista Software, Inc.
*
* 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 <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
#include <elf.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include "../../kexec.h"
#include "../../kexec-elf.h"
#include "../../kexec-syscall.h"
#include "../../crashdump.h"
#include "kexec-mips.h"
#include "crashdump-mips.h"
#include "unused.h"
/* Stores a sorted list of RAM memory ranges for which to create elf headers.
* A separate program header is created for backup region */
static struct memory_range crash_memory_range[CRASH_MAX_MEMORY_RANGES];
/* Memory region reserved for storing panic kernel and other data. */
static struct memory_range crash_reserved_mem;
/* Read kernel physical load addr from the file returned by proc_iomem()
* (Kernel Code) and store in kexec_info */
static int get_kernel_paddr(struct crash_elf_info *elf_info)
{
uint64_t start;
if (xen_present()) /* Kernel not entity mapped under Xen */
return 0;
if (parse_iomem_single("Kernel code\n", &start, NULL) == 0) {
elf_info->kern_paddr_start = start;
dbgprintf("kernel load physical addr start = 0x%lx\n", start);
return 0;
}
fprintf(stderr, "Cannot determine kernel physical load addr\n");
return -1;
}
static int get_kernel_vaddr_and_size(struct crash_elf_info *elf_info,
unsigned long start_offset)
{
uint64_t end;
if (!elf_info->kern_paddr_start)
return -1;
elf_info->kern_vaddr_start = elf_info->kern_paddr_start |
start_offset;
if (parse_iomem_single("Kernel data\n", NULL, &end) == 0) {
elf_info->kern_size = end - elf_info->kern_paddr_start;
dbgprintf("kernel_vaddr= 0x%llx paddr %llx\n",
elf_info->kern_vaddr_start,
elf_info->kern_paddr_start);
dbgprintf("kernel size = 0x%lx\n", elf_info->kern_size);
return 0;
}
fprintf(stderr, "Cannot determine kernel virtual load addr and size\n");
return -1;
}
/* Removes crash reserve region from list of memory chunks for whom elf program
* headers have to be created. Assuming crash reserve region to be a single
* continuous area fully contained inside one of the memory chunks */
static int exclude_crash_reserve_region(int *nr_ranges)
{
int i, j, tidx = -1;
unsigned long long cstart, cend;
struct memory_range temp_region = {
.start = 0,
.end = 0
};
/* Crash reserved region. */
cstart = crash_reserved_mem.start;
cend = crash_reserved_mem.end;
for (i = 0; i < (*nr_ranges); i++) {
unsigned long long mstart, mend;
mstart = crash_memory_range[i].start;
mend = crash_memory_range[i].end;
if (cstart < mend && cend > mstart) {
if (cstart != mstart && cend != mend) {
/* Split memory region */
crash_memory_range[i].end = cstart - 1;
temp_region.start = cend + 1;
temp_region.end = mend;
temp_region.type = RANGE_RAM;
tidx = i+1;
} else if (cstart != mstart)
crash_memory_range[i].end = cstart - 1;
else
crash_memory_range[i].start = cend + 1;
}
}
/* Insert split memory region, if any. */
if (tidx >= 0) {
if (*nr_ranges == CRASH_MAX_MEMORY_RANGES) {
/* No space to insert another element. */
fprintf(stderr, "Error: Number of crash memory ranges"
" excedeed the max limit\n");
return -1;
}
for (j = (*nr_ranges - 1); j >= tidx; j--)
crash_memory_range[j+1] = crash_memory_range[j];
crash_memory_range[tidx].start = temp_region.start;
crash_memory_range[tidx].end = temp_region.end;
crash_memory_range[tidx].type = temp_region.type;
(*nr_ranges)++;
}
return 0;
}
/* Reads the appropriate file and retrieves the SYSTEM RAM regions for whom to
* create Elf headers. Keeping it separate from get_memory_ranges() as
* requirements are different in the case of normal kexec and crashdumps.
*
* Normal kexec needs to look at all of available physical memory irrespective
* of the fact how much of it is being used by currently running kernel.
* Crashdumps need to have access to memory regions actually being used by
* running kernel. Expecting a different file/data structure than /proc/iomem
* to look into down the line. May be something like /proc/kernelmem or may
* be zone data structures exported from kernel.
*/
static int get_crash_memory_ranges(struct memory_range **range, int *ranges)
{
const char iomem[] = "/proc/iomem";
int i, memory_ranges = 0;
char line[MAX_LINE];
FILE *fp;
unsigned long long start, end;
fp = fopen(iomem, "r");
if (!fp) {
fprintf(stderr, "Cannot open %s: %s\n",
iomem, strerror(errno));
return -1;
}
/* Separate segment for backup region */
crash_memory_range[0].start = BACKUP_SRC_START;
crash_memory_range[0].end = BACKUP_SRC_END;
crash_memory_range[0].type = RANGE_RAM;
memory_ranges++;
while (fgets(line, sizeof(line), fp) != 0) {
char *str;
int type, consumed, count;
if (memory_ranges >= CRASH_MAX_MEMORY_RANGES)
break;
count = sscanf(line, "%Lx-%Lx : %n",
&start, &end, &consumed);
if (count != 2)
continue;
str = line + consumed;
/* Only Dumping memory of type System RAM. */
if (memcmp(str, "System RAM\n", 11) == 0) {
type = RANGE_RAM;
} else if (memcmp(str, "Crash kernel\n", 13) == 0) {
/* Reserved memory region. New kernel can
* use this region to boot into. */
crash_reserved_mem.start = start;
crash_reserved_mem.end = end;
crash_reserved_mem.type = RANGE_RAM;
continue;
} else
continue;
if (start == BACKUP_SRC_START && end >= (BACKUP_SRC_END + 1))
start = BACKUP_SRC_END + 1;
crash_memory_range[memory_ranges].start = start;
crash_memory_range[memory_ranges].end = end;
crash_memory_range[memory_ranges].type = type;
memory_ranges++;
/* Segregate linearly mapped region. */
if ((MAXMEM - 1) >= start && (MAXMEM - 1) <= end) {
crash_memory_range[memory_ranges - 1].end = MAXMEM - 1;
/* Add segregated region. */
crash_memory_range[memory_ranges].start = MAXMEM;
crash_memory_range[memory_ranges].end = end;
crash_memory_range[memory_ranges].type = type;
memory_ranges++;
}
}
fclose(fp);
if (exclude_crash_reserve_region(&memory_ranges) < 0)
return -1;
*range = crash_memory_range;
*ranges = memory_ranges;
return 0;
}
/* Converts unsigned long to ascii string. */
static void ultoa(unsigned long i, char *str)
{
int j = 0, k;
char tmp;
do {
str[j++] = i % 10 + '0';
} while ((i /= 10) > 0);
str[j] = '\0';
/* Reverse the string. */
for (j = 0, k = strlen(str) - 1; j < k; j++, k--) {
tmp = str[k];
str[k] = str[j];
str[j] = tmp;
}
}
/* Adds the appropriate mem= options to command line, indicating the
* memory region the new kernel can use to boot into. */
static int cmdline_add_mem(char *cmdline, unsigned long addr,
unsigned long size)
{
int cmdlen, len;
char str[50], *ptr;
addr = addr/1024;
size = size/1024;
ptr = str;
strcpy(str, " mem=");
ptr += strlen(str);
ultoa(size, ptr);
strcat(str, "K@");
ptr = str + strlen(str);
ultoa(addr, ptr);
strcat(str, "K");
len = strlen(str);
cmdlen = strlen(cmdline) + len;
if (cmdlen > (COMMAND_LINE_SIZE - 1))
die("Command line overflow\n");
strcat(cmdline, str);
return 0;
}
/* Adds the elfcorehdr= command line parameter to command line. */
static int cmdline_add_elfcorehdr(char *cmdline, unsigned long addr)
{
int cmdlen, len, align = 1024;
char str[30], *ptr;
/* Passing in elfcorehdr=xxxK format. Saves space required in cmdline.
* Ensure 1K alignment*/
if (addr%align)
return -1;
addr = addr/align;
ptr = str;
strcpy(str, " elfcorehdr=");
ptr += strlen(str);
ultoa(addr, ptr);
strcat(str, "K");
len = strlen(str);
cmdlen = strlen(cmdline) + len;
if (cmdlen > (COMMAND_LINE_SIZE - 1))
die("Command line overflow\n");
strcat(cmdline, str);
return 0;
}
#ifdef __mips64
static struct crash_elf_info elf_info64 = {
class: ELFCLASS64,
data : ELFDATA2MSB,
machine : EM_MIPS,
page_offset : PAGE_OFFSET,
lowmem_limit : MAXMEM,
};
#endif
static struct crash_elf_info elf_info32 = {
class: ELFCLASS32,
data : ELFDATA2MSB,
machine : EM_MIPS,
page_offset : PAGE_OFFSET,
lowmem_limit : MAXMEM,
};
/* Loads additional segments in case of a panic kernel is being loaded.
* One segment for backup region, another segment for storing elf headers
* for crash memory image.
*/
int load_crashdump_segments(struct kexec_info *info, char* mod_cmdline,
unsigned long UNUSED(max_addr),
unsigned long UNUSED(min_base))
{
void *tmp;
unsigned long sz, elfcorehdr;
int nr_ranges, align = 1024;
struct memory_range *mem_range;
crash_create_elf_headers_func crash_create = crash_create_elf32_headers;
struct crash_elf_info *elf_info = &elf_info32;
unsigned long start_offset = 0x80000000UL;
#ifdef __mips64
if (arch_options.core_header_type == CORE_TYPE_ELF64) {
elf_info = &elf_info64;
crash_create = crash_create_elf64_headers;
start_offset = 0xffffffff80000000UL;
}
#endif
if (get_kernel_paddr(elf_info))
return -1;
if (get_kernel_vaddr_and_size(elf_info, start_offset))
return -1;
if (get_crash_memory_ranges(&mem_range, &nr_ranges) < 0)
return -1;
info->backup_src_start = BACKUP_SRC_START;
info->backup_src_size = BACKUP_SRC_SIZE;
/* Create a backup region segment to store backup data*/
sz = _ALIGN(BACKUP_SRC_SIZE, align);
tmp = xmalloc(sz);
memset(tmp, 0, sz);
info->backup_start = add_buffer(info, tmp, sz, sz, align,
crash_reserved_mem.start,
crash_reserved_mem.end, -1);
if (crash_create(info, elf_info, crash_memory_range, nr_ranges,
&tmp, &sz, ELF_CORE_HEADER_ALIGN) < 0)
return -1;
elfcorehdr = add_buffer(info, tmp, sz, sz, align,
crash_reserved_mem.start,
crash_reserved_mem.end, -1);
/*
* backup segment is after elfcorehdr, so use elfcorehdr as top of
* kernel's available memory
*/
cmdline_add_mem(mod_cmdline, crash_reserved_mem.start,
elfcorehdr - crash_reserved_mem.start);
cmdline_add_elfcorehdr(mod_cmdline, elfcorehdr);
dbgprintf("CRASH MEMORY RANGES:\n");
dbgprintf("%016Lx-%016Lx\n", crash_reserved_mem.start,
crash_reserved_mem.end);
return 0;
}
int is_crashkernel_mem_reserved(void)
{
uint64_t start, end;
return parse_iomem_single("Crash kernel\n", &start, &end) == 0 ?
(start != end) : 0;
}