/*
 * 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"


/*
 * 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		= 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 (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;
}
