commit 46779b37c81545e057ee49ed6684e7b5327b3aa5
Author: Nick Kossifidis <mick@ics.forth.gr>
Date:   Fri Apr 24 19:37:53 2020 +0300

    RISC-V: Add support for kexec/kdump on kexec-tools
    
    This patch adds support for kexec on RISC-V on kexec-tools.
    For now this only includes support for the kexec call, no
    kexec_file. The only supported image type is an ELF image
    such as vmlinux. When the new kernel starts a0 will contain
    the current hart id and a1 the pointer to the dtb (PA).
    
    I tested this on riscv64 QEMU on both smp and non-smp
    setups and works as expected. Note that in order for this
    to compile you also need to update config.sub and
    config.guess.
    
    Signed-off-by: Nick Kossifidis <mick@ics.forth.gr>

diff --git a/configure.ac b/configure.ac
index f025823..ea7bec6 100644
--- a/configure.ac
+++ b/configure.ac
@@ -58,6 +58,9 @@ case $target_cpu in
 	hppa*)
 		ARCH="hppa"
 		;;
+	riscv32|riscv64 )
+		ARCH="riscv"
+		;;
 	* )
 		AC_MSG_ERROR([unsupported architecture $target_cpu])
 		;;
diff --git a/include/elf.h b/include/elf.h
index b7677a2..123f167 100644
--- a/include/elf.h
+++ b/include/elf.h
@@ -259,7 +259,8 @@ typedef struct
 #define EM_ARC_A5	93		/* ARC Cores Tangent-A5 */
 #define EM_XTENSA	94		/* Tensilica Xtensa Architecture */
 #define EM_AARCH64	183		/* ARM AARCH64 */
-#define EM_NUM		184
+#define EM_RISCV	243		/* RISC-V */
+#define EM_NUM		244
 
 /* If it is necessary to assign new unofficial EM_* values, please
    pick large random numbers (0x8523, 0xa7f2, etc.) to minimize the
diff --git a/kexec/Makefile b/kexec/Makefile
index 8e3e9ea..3d7a16d 100644
--- a/kexec/Makefile
+++ b/kexec/Makefile
@@ -89,6 +89,7 @@ include $(srcdir)/kexec/arch/mips/Makefile
 include $(srcdir)/kexec/arch/cris/Makefile
 include $(srcdir)/kexec/arch/ppc/Makefile
 include $(srcdir)/kexec/arch/ppc64/Makefile
+include $(srcdir)/kexec/arch/riscv/Makefile
 include $(srcdir)/kexec/arch/s390/Makefile
 include $(srcdir)/kexec/arch/sh/Makefile
 include $(srcdir)/kexec/arch/x86_64/Makefile
diff --git a/kexec/arch/riscv/Makefile b/kexec/arch/riscv/Makefile
new file mode 100644
index 0000000..f26cc90
--- /dev/null
+++ b/kexec/arch/riscv/Makefile
@@ -0,0 +1,35 @@
+#
+# kexec riscv
+#
+riscv_KEXEC_SRCS =  kexec/arch/riscv/kexec-riscv.c
+riscv_KEXEC_SRCS += kexec/arch/riscv/kexec-elf-riscv.c
+riscv_KEXEC_SRCS += kexec/arch/riscv/crashdump-riscv.c
+
+riscv_MEM_REGIONS = kexec/mem_regions.c
+
+riscv_DT_OPS += kexec/dt-ops.c
+
+riscv_ARCH_REUSE_INITRD =
+
+riscv_CPPFLAGS += -I $(srcdir)/kexec/
+
+dist += kexec/arch/riscv/Makefile $(riscv_KEXEC_SRCS)			\
+	kexec/arch/riscv/kexec-riscv.h					\
+	kexec/arch/riscv/include/arch/options.h
+
+ifdef HAVE_LIBFDT
+
+LIBS += -lfdt
+
+else
+
+include $(srcdir)/kexec/libfdt/Makefile.libfdt
+
+libfdt_SRCS += $(LIBFDT_SRCS:%=kexec/libfdt/%)
+
+riscv_CPPFLAGS += -I$(srcdir)/kexec/libfdt
+
+riscv_KEXEC_SRCS += $(libfdt_SRCS)
+
+endif
+
diff --git a/kexec/arch/riscv/crashdump-riscv.c b/kexec/arch/riscv/crashdump-riscv.c
new file mode 100644
index 0000000..4f3ecd6
--- /dev/null
+++ b/kexec/arch/riscv/crashdump-riscv.c
@@ -0,0 +1,142 @@
+#include <errno.h>
+#include <linux/elf.h>
+#include <unistd.h>
+
+#include "kexec.h"
+#include "crashdump.h"
+#include "kexec-elf.h"
+#include "mem_regions.h"
+
+static struct crash_elf_info elf_info = {
+#if __riscv_xlen == 64
+	.class		= ELFCLASS64,
+#else
+	.class		= ELFCLASS32,
+#endif
+	.data		= ELFDATA2LSB,
+	.machine	= EM_RISCV,
+};
+
+static struct memory_ranges crash_mem_ranges = {0};
+struct memory_range elfcorehdr_mem = {0};
+
+static unsigned long long get_page_offset(struct kexec_info *info)
+{
+	unsigned long long vaddr_off = 0;
+	unsigned long long page_size = sysconf(_SC_PAGESIZE);
+	unsigned long long init_start = get_kernel_sym("_sinittext");
+
+	/*
+	 * Begining of init section is aligned to page size and since
+	 * it doesn't start from 0 (there is code before that) it'll
+	 * start from page_size.
+	 */
+	vaddr_off = init_start - page_size;
+
+	return vaddr_off;
+}
+
+int load_elfcorehdr(struct kexec_info *info)
+{
+	struct memory_range crashkern_range = {0};
+	struct memory_range *ranges = NULL;
+	unsigned long start = 0;
+	unsigned long end = 0;
+	unsigned long buf_size = 0;
+	unsigned long elfcorehdr_addr = 0;
+	void* buf = NULL;
+	int i = 0;
+	int ret = 0;
+
+	ret = parse_iomem_single("Kernel code\n", &start, NULL);
+	if (ret) {
+		fprintf(stderr, "Cannot determine kernel physical base addr\n");
+		return -EINVAL;
+	}
+	elf_info.kern_paddr_start = start;
+
+	ret = parse_iomem_single("Kernel bss\n", NULL, &end);
+	if (ret) {
+		fprintf(stderr, "Cannot determine kernel physical bss addr\n");
+		return -EINVAL;
+	}
+	elf_info.kern_paddr_start = start;
+	elf_info.kern_size = end - start;
+
+	elf_info.kern_vaddr_start = get_kernel_sym("_text");
+	if (!elf_info.kern_vaddr_start) {
+		elf_info.kern_vaddr_start = UINT64_MAX;
+	}
+
+	elf_info.page_offset = get_page_offset(info);
+	dbgprintf("page_offset:   %016llx\n", elf_info.page_offset);
+
+	ret = parse_iomem_single("Crash kernel\n", &start, &end);
+	if (ret) {
+		fprintf(stderr, "Cannot determine kernel physical bss addr\n");
+		return -EINVAL;
+	}
+	crashkern_range.start = start;
+	crashkern_range.end = end;
+	crashkern_range.type = RANGE_RESERVED;
+
+	ranges = info->memory_range;
+	for (i = 0; i < info->memory_ranges; i++) {
+		ret = mem_regions_alloc_and_add(&crash_mem_ranges,
+						ranges[i].start,
+						ranges[i].end - ranges[i].start,
+						ranges[i].type);
+		if (ret ) {
+			fprintf(stderr, "Could not create crash_mem_ranges\n");
+			return ret;
+		}
+	}
+
+	ret = mem_regions_alloc_and_exclude(&crash_mem_ranges,
+					    &crashkern_range);
+	if (ret) {
+		fprintf(stderr, "Could not exclude crashkern_range\n");
+		return ret;
+	}
+
+#if __riscv_xlen == 64
+	crash_create_elf64_headers(info, &elf_info, crash_mem_ranges.ranges,
+				   crash_mem_ranges.size, &buf, &buf_size,
+				   ELF_CORE_HEADER_ALIGN);
+
+#else
+	crash_create_elf32_headers(info, &elf_info, crash_mem_ranges.ranges,
+				   crash_mem_ranges.size, &buf, &buf_size,
+				   ELF_CORE_HEADER_ALIGN);
+#endif
+
+
+	elfcorehdr_addr = add_buffer_phys_virt(info, buf, buf_size,
+					       buf_size, 0,
+					       crashkern_range.start,
+					       crashkern_range.end,
+					       -1, 0);
+
+	elfcorehdr_mem.start = elfcorehdr_addr;
+	elfcorehdr_mem.end = elfcorehdr_addr + buf_size - 1;
+
+	dbgprintf("%s: elfcorehdr 0x%llx-0x%llx\n", __func__,
+		  elfcorehdr_mem.start, elfcorehdr_mem.end);
+
+	return 0;
+}
+
+int is_crashkernel_mem_reserved(void)
+{
+	uint64_t start = 0;
+	uint64_t end = 0;
+
+	return parse_iomem_single("Crash kernel\n", &start, &end) == 0 ?
+	       (start != end) : 0;
+}
+
+int get_crash_kernel_load_range(uint64_t *start, uint64_t *end)
+{
+	return parse_iomem_single("Crash kernel\n", start, end);
+}
+
diff --git a/kexec/arch/riscv/include/arch/options.h b/kexec/arch/riscv/include/arch/options.h
new file mode 100644
index 0000000..7c24184
--- /dev/null
+++ b/kexec/arch/riscv/include/arch/options.h
@@ -0,0 +1,43 @@
+#ifndef KEXEC_ARCH_RISCV_OPTIONS_H
+#define KEXEC_ARCH_RISCV_OPTIONS_H
+
+#define OPT_APPEND		((OPT_MAX)+0)
+#define OPT_DTB			((OPT_MAX)+1)
+#define OPT_INITRD		((OPT_MAX)+2)
+#define OPT_CMDLINE		((OPT_MAX)+3)
+#define OPT_REUSE_CMDLINE	((OPT_MAX)+4)
+#define OPT_ARCH_MAX		((OPT_MAX)+5)
+
+/* Options relevant to the architecture (excluding loader-specific ones),
+ * in this case none:
+ */
+#define KEXEC_ARCH_OPTIONS \
+	KEXEC_OPTIONS \
+	{ "append",		1, 0, OPT_APPEND}, \
+	{ "dtb",		1, 0, OPT_DTB }, \
+	{ "initrd",		1, 0, OPT_INITRD }, \
+	{ "command-line",	1, 0, OPT_CMDLINE}, \
+	{ "reuse-cmdline",	0, NULL, OPT_REUSE_CMDLINE }, \
+
+
+#define KEXEC_ARCH_OPT_STR KEXEC_OPT_STR ""
+
+/* The following two #defines list ALL of the options added by all of the
+ * architecture's loaders.
+ * o	main() uses this complete list to scan for its options, ignoring
+ *	arch-specific/loader-specific ones.
+ * o	Then, arch_process_options() uses this complete list to scan for its
+ *	options, ignoring general/loader-specific ones.
+ * o	Then, the file_type[n].load re-scans for options, using
+ *	KEXEC_ARCH_OPTIONS plus its loader-specific options subset.
+ *	Any unrecognised options cause an error here.
+ *
+ * This is done so that main()'s/arch_process_options()'s getopt_long() calls
+ * don't choose a kernel filename from random arguments to options they don't
+ * recognise -- as they now recognise (if not act upon) all possible options.
+ */
+#define KEXEC_ALL_OPTIONS KEXEC_ARCH_OPTIONS
+
+#define KEXEC_ALL_OPT_STR KEXEC_ARCH_OPT_STR
+
+#endif /* KEXEC_ARCH_RISCV_OPTIONS_H */
diff --git a/kexec/arch/riscv/kexec-elf-riscv.c b/kexec/arch/riscv/kexec-elf-riscv.c
new file mode 100644
index 0000000..4fe0940
--- /dev/null
+++ b/kexec/arch/riscv/kexec-elf-riscv.c
@@ -0,0 +1,265 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2019 FORTH-ICS/CARV
+ *              Nick Kossifidis <mick@ics.forth.gr>
+ */
+
+#include "kexec.h"
+#include "dt-ops.h"		/* For dtb_set/clear_initrd() */
+#include <elf.h>		/* For ELF header handling */
+#include <errno.h>		/* For EFBIG/EINVAL */
+#include <unistd.h>		/* For getpagesize() */
+#include "kexec-syscall.h"	/* For KEXEC_ON_CRASH */
+#include "kexec-riscv.h"
+
+
+/*********\
+* HELPERS *
+\*********/
+
+/*
+ * Go through the available physical memory regions and
+ * find one that can hold an image of the specified size.
+ * Note: This is called after get_memory_ranges so
+ * info->memory_range[] should be populated. Also note that
+ * memory ranges are sorted, so we'll return the first region
+ * that's big enough for holding the image.
+ */
+static int elf_riscv_find_pbase(struct kexec_info *info, off_t *addr,
+				off_t size)
+{
+	int i = 0;
+	off_t start = 0;
+	off_t end = 0;
+	int ret = 0;
+
+	/*
+	 * If this image is for a crash kernel, use the region
+	 * the primary kernel has already reserved for us.
+	 */
+	if (info->kexec_flags & KEXEC_ON_CRASH) {
+		ret = get_crash_kernel_load_range((uint64_t *) &start,
+						  (uint64_t *) &end);
+		if (!ret) {
+			/*
+			 * Kernel should be aligned to the nearest
+			 * hugepage (2MB for RV64, 4MB for RV32).
+			 */
+#if __riscv_xlen == 64
+			start = _ALIGN_UP(start, 0x200000);
+#else
+			start = _ALIGN_UP(start, 0x400000);
+#endif
+			if (end > start && ((end - start) >= size)) {
+				*addr = start;
+				return 0;
+			}
+
+			return -EFBIG;
+		} else
+			return ENOCRASHKERNEL;
+	}
+
+	for (i = 0; i < info->memory_ranges; i++) {
+		if (info->memory_range[i].type != RANGE_RAM)
+			continue;
+
+		start = info->memory_range[i].start;
+		end = info->memory_range[i].end;
+
+		/*
+		 * XXX: Region's base address may be already aligned but
+		 * firmware may be loaded there and we'll overwrite it (or
+		 * get a fault due to PMP). Until BBL / OpenSBI update the
+		 * device tree to mark their memory as reserved the assumption
+		 * is that the kernel won't use any memory below its load
+		 * address, that the firmware will be < 2MB in size and that
+		 * the kernel will not use the first hugepage (where the
+		 * firmware is), hence the + 1 below.
+		 */
+#if __riscv_xlen == 64
+		start = _ALIGN_UP(start + 1, 0x200000);
+#else
+		start = _ALIGN_UP(start + 1, 0x400000);
+#endif
+
+		if (end > start && ((end - start) >= size)) {
+			*addr = start;
+			return 0;
+		}
+	}
+
+	return -EFBIG;
+}
+
+/**************\
+* ENTRY POINTS *
+\**************/
+
+int elf_riscv_probe(const char *buf, off_t len)
+{
+	struct mem_ehdr ehdr = {0};
+	int ret = 0;
+
+	ret = build_elf_exec_info(buf, len, &ehdr, 0);
+	if (ret < 0)
+		goto cleanup;
+
+	if (ehdr.e_machine != EM_RISCV) {
+		fprintf(stderr, "Not for this architecture.\n");
+		ret = -EINVAL;
+		goto cleanup;
+	}
+
+	ret = 0;
+
+ cleanup:
+	free_elf_info(&ehdr);
+	return ret;
+}
+
+void elf_riscv_usage(void)
+{
+}
+
+int elf_riscv_load(int argc, char **argv, const char *buf, off_t len,
+	struct kexec_info *info)
+{
+	struct mem_ehdr ehdr = {0};
+	struct mem_phdr *phdr = NULL;
+	off_t new_base_addr = 0;
+	off_t kernel_size = 0;
+	off_t page_size = getpagesize();
+	off_t max_addr = 0;
+	off_t old_base_addr = 0;
+	off_t old_start_addr = 0;
+	int i = 0;
+	int ret = 0;
+
+	if (info->file_mode) {
+		fprintf(stderr, "kexec_file not supported on this "
+				"architecture\n");
+		return -EINVAL;
+	}
+
+	/* Parse the ELF file */
+	ret = build_elf_exec_info(buf, len, &ehdr, 0);
+	if (ret < 0) {
+		fprintf(stderr, "ELF exec parse failed\n");
+		return -EINVAL;
+	}
+
+	max_addr = elf_max_addr(&ehdr);
+	old_base_addr = max_addr;
+	old_start_addr = max_addr;
+
+	/*
+	 * Get the memory footprint, base physical
+	 * and start address of the ELF image
+	 */
+	for (i = 0; i < ehdr.e_phnum; i++) {
+		phdr = &ehdr.e_phdr[i];
+		if (phdr->p_type != PT_LOAD)
+			continue;
+
+		/*
+		 * Note: According to ELF spec the loadable regions
+		 * are sorted on p_vaddr, not p_paddr.
+		 */
+		if (old_base_addr > phdr->p_paddr)
+			old_base_addr = phdr->p_paddr;
+
+		if (phdr->p_vaddr == ehdr.e_entry ||
+		    phdr->p_paddr == ehdr.e_entry)
+			old_start_addr = phdr->p_paddr;
+
+		kernel_size += _ALIGN_UP(phdr->p_memsz, page_size);
+	}
+
+	if (old_base_addr == max_addr || kernel_size == 0) {
+		fprintf(stderr, "No loadable segments present on the "
+				"provided ELF image\n");
+		return -EINVAL;
+	}
+
+	if (old_start_addr == max_addr) {
+		fprintf(stderr, "Could not find the entry point address of "
+				"provided ELF image\n");
+		return -EINVAL;
+	}
+
+	dbgprintf("Got ELF with total memsz %luKB\n"
+		  "Base paddr: 0x%lX, start_addr: 0x%lX\n",
+		  kernel_size / 1024, old_base_addr, old_start_addr);
+
+	/* Get a continuous physical region that can hold the kernel */
+	ret = elf_riscv_find_pbase(info, &new_base_addr, kernel_size);
+	if (ret < 0) {
+		fprintf(stderr, "Could not find a memory region for the "
+				"provided ELF image\n");
+		return ret;
+	}
+
+	dbgprintf("New base paddr for the ELF: 0x%lX\n", new_base_addr);
+
+	/* Re-set the base physical address of the ELF */
+	for (i = 0; i < ehdr.e_phnum; i++) {
+		phdr = &ehdr.e_phdr[i];
+		if (phdr->p_type != PT_LOAD)
+			continue;
+
+		phdr->p_paddr -= old_base_addr;
+		phdr->p_paddr += new_base_addr;
+	}
+
+	/* Re-set the entry point address */
+	ehdr.e_entry = (old_start_addr - old_base_addr) + new_base_addr;
+	info->entry = (void *) ehdr.e_entry;
+	dbgprintf("New entry point for the ELF: 0x%llX\n", ehdr.e_entry);
+
+
+	/* Load the ELF executable */
+	ret = elf_exec_load(&ehdr, info);
+	if (ret < 0) {
+		fprintf(stderr, "ELF exec load failed\n");
+		return ret;
+	}
+
+	ret = load_extra_segments(info, new_base_addr,
+				  kernel_size, max_addr);
+	return ret;
+}
+
+
+/*******\
+* STUBS *
+\*******/
+
+int machine_verify_elf_rel(struct mem_ehdr *ehdr)
+{
+	if (ehdr->ei_data != ELFDATA2LSB)
+		return 0;
+#if __riscv_xlen == 64
+	if (ehdr->ei_class != ELFCLASS64)
+#else
+	if (ehdr->ei_class != ELFCLASS32)
+#endif
+		return 0;
+	if (ehdr->e_machine != EM_RISCV)
+		return 0;
+	return 1;
+}
+
+void machine_apply_elf_rel(struct mem_ehdr *UNUSED(ehdr),
+			   struct mem_sym *UNUSED(sym),
+			   unsigned long r_type,
+			   void *UNUSED(location),
+			   unsigned long UNUSED(address),
+			   unsigned long UNUSED(value))
+{
+	switch (r_type) {
+	default:
+		die("Unknown rela relocation: %lu\n", r_type);
+		break;
+	}
+}
diff --git a/kexec/arch/riscv/kexec-riscv.c b/kexec/arch/riscv/kexec-riscv.c
new file mode 100644
index 0000000..5e34635
--- /dev/null
+++ b/kexec/arch/riscv/kexec-riscv.c
@@ -0,0 +1,733 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2019 FORTH-ICS/CARV
+ *              Nick Kossifidis <mick@ics.forth.gr>
+ */
+
+#include "kexec-syscall.h"	/* For KEXEC_ARCH_RISCV */
+#include "kexec.h"		/* For OPT_MAX and concat_cmdline() */
+#include "mem_regions.h"	/* For mem_regions_sort() */
+#include "dt-ops.h"		/* For dtb_set_bootargs() */
+#include <arch/options.h>	/* For KEXEC_ARCH_OPTIONS */
+#include <getopt.h>		/* For struct option */
+#include <sys/stat.h>		/* For stat() and struct stat */
+#include <stdlib.h>		/* For free() */
+#include <errno.h>		/* For EINVAL */
+#include <libfdt.h>		/* For DeviceTree handling */
+#include "kexec-riscv.h"
+
+const struct arch_map_entry arches[] = {
+	{ "riscv32", KEXEC_ARCH_RISCV },
+	{ "riscv64", KEXEC_ARCH_RISCV },
+	{ NULL, 0 },
+};
+
+
+struct file_type file_type[] = {
+	{"elf-riscv", elf_riscv_probe, elf_riscv_load, elf_riscv_usage},
+};
+int file_types = sizeof(file_type) / sizeof(file_type[0]);
+
+static const char riscv_opts_usage[] =
+"	--append=STRING		Append STRING to the kernel command line.\n"
+"	--dtb=FILE		Use FILE as the device tree blob.\n"
+"	--initrd=FILE		Use FILE as the kernel initial ramdisk.\n"
+"	--cmdline=STRING	Use STRING as the kernel's command line.\n"
+"	--reuse-cmdline		Use kernel command line from running system.\n";
+
+static struct riscv_opts arch_options = {0};
+static struct fdt_image provided_fdt = {0};
+static struct fdt_image active_fdt = {0};
+
+
+/**************************\
+* DEVICE TREE MANIPULATION *
+\**************************/
+
+void fdt_read_property(uint64_t *val, const void *buf, uint32_t cells)
+{
+	const uint32_t *prop32 = NULL;
+	const uint64_t *prop64 = NULL;
+
+	if (cells == 1) {
+		prop32 = (const uint32_t *) buf;
+		*val = (uint64_t) be32_to_cpu(*prop32);
+	} else {
+		/* Skip any leading cells */
+		prop64 = (const uint64_t *) (uint32_t *)buf + cells - 2;
+		*val = (uint64_t) be64_to_cpu(*prop64);
+	}
+}
+
+void fdt_write_property(void *buf, uint64_t val, uint32_t cells)
+{
+	uint32_t prop32 = 0;
+	uint64_t prop64 = 0;
+
+	if (cells == 1) {
+		prop32 = cpu_to_fdt32((uint32_t) val);
+		memcpy(buf, &prop32, sizeof(uint32_t));
+	} else {
+		prop64 = cpu_to_fdt64(val);
+		/* Skip any leading cells */
+		memcpy((uint64_t *)(uint32_t *)buf + cells - 2,
+		       &prop64, sizeof(uint64_t));
+	}
+}
+
+int fdt_add_range(struct fdt_image *fdt, uint64_t start,
+		  uint64_t end, const char *name)
+{
+	uint32_t addr_cells = 0;
+	uint32_t size_cells = 0;
+	int prop_size = 0;
+	uint32_t new_size = 0;
+	void *new_buf = NULL;
+	void *prop = NULL;
+	const uint32_t *prop32 = NULL;
+	int chosen_offt = 0;
+	int ret = 0;
+
+	chosen_offt = fdt_path_offset(fdt->buf, "/chosen");
+
+	prop32 = fdt_getprop(fdt->buf, 0, "#address-cells", NULL);
+	if (!prop32) {
+		fprintf(stderr, "No #address-cells property on root node\n");
+		return -EINVAL;
+	}
+	addr_cells = be32_to_cpu(*prop32);
+
+	prop32 = fdt_getprop(fdt->buf, 0, "#size-cells", NULL);
+	if (!prop32) {
+		fprintf(stderr, "No #size-cells property on root node\n");
+		return -EINVAL;
+	}
+	size_cells = be32_to_cpu(*prop32);
+
+	/* Can the range fit with the given address/size cells ? */
+	if ((addr_cells == 1) && (start >= (1ULL << 32)))
+		return -EINVAL;
+
+	if ((size_cells == 1) && ((end - start + 1) >= (1ULL << 32)))
+		return -EINVAL;
+
+	/* Expand fdt buffer to include the new property */
+	prop_size = sizeof(uint32_t) * (addr_cells + size_cells);
+	new_size = fdt_totalsize(fdt->buf) +
+		   fdt_prop_len(name, prop_size);
+
+	new_buf = xrealloc(fdt->buf, new_size);
+	if (!new_buf) {
+		fprintf(stderr, "Couldn't expand fdt image\n");
+		return -ENOMEM;
+	}
+	fdt->buf = new_buf;
+
+	prop = xmalloc(prop_size);
+	fdt_write_property(prop, start, addr_cells);
+
+	fdt_write_property((void *)((uint32_t *)prop + addr_cells),
+			   end - start + 1, size_cells);
+
+	ret = fdt_setprop(fdt->buf, chosen_offt, name, prop, prop_size);
+	return ret;
+}
+
+static int fdt_remove_from_chosen(struct fdt_image *fdt, const char* name)
+{
+	int chosen_offt = 0;
+
+	chosen_offt = fdt_path_offset(fdt->buf, "/chosen");
+	return fdt_delprop(fdt->buf, chosen_offt, name);
+}
+
+/************************\
+* MEMORY RANGES HANDLING *
+\************************/
+
+static struct memory_ranges sysmem_ranges = {0};
+
+static int add_memory_range(struct memory_ranges *mem_ranges, uint64_t start,
+			    uint64_t end, unsigned type)
+{
+	struct memory_range this_region = {0};
+	struct memory_range *ranges = mem_ranges->ranges;
+	int i = 0;
+	int ret = 0;
+
+	if (start == end) {
+		dbgprintf("Ignoring empty region\n");
+		return -EINVAL;
+	}
+
+	/* Check if we are adding an existing region */
+	for (i = 0; i < mem_ranges->size; i++) {
+		if (start == ranges[i].start && end == ranges[i].end) {
+			dbgprintf("Duplicate: 0x%lx - 0x%lx\n", start, end);
+			if (type == ranges[i].type)
+				return 0;
+			else if (type == RANGE_RESERVED) {
+				ranges[i].type = RANGE_RESERVED;
+				return 0;
+			}
+
+			fprintf(stderr, "Conflicting types for region:"
+					" 0x%lx - 0x%lx\n",
+					start, end);
+			return -EINVAL;
+		}
+	}
+
+	/*
+	 * Reserved regions should be part of an existing /memory
+	 * region and shouldn't overlap according to spec, so
+	 * since we add /memory regions first, we can exclude
+	 * reserved regions here from the existing ranges[].
+	 */
+	if (type == RANGE_RESERVED) {
+		this_region.start = start;
+		this_region.end = end;
+		this_region.type = type;
+		ret = mem_regions_exclude(mem_ranges, &this_region);
+		if (ret)
+			return ret;
+	}
+
+	ret = mem_regions_alloc_and_add(mem_ranges, start,
+					end - start, type);
+
+	return ret;
+}
+
+static int add_memory_region(struct memory_ranges *mem_ranges,
+			     struct fdt_image *fdt, int node_offset,
+			     int type)
+{
+	const uint32_t *prop32 = NULL;
+	uint32_t addr_cells = 0;
+	uint32_t size_cells = 0;
+	uint64_t addr = 0;
+	uint64_t size = 0;
+	const char *reg = NULL;
+	int prop_size = 0;
+	int offset = 0;
+	int entry_size = 0;
+	int num_entries = 0;
+	int ret = 0;
+
+	/*
+	 * Get address-cells and size-cells properties (according to
+	 * binding spec these are the same as in the root node)
+	 */
+	prop32 = fdt_getprop(fdt->buf, 0, "#address-cells", NULL);
+	if (!prop32) {
+		fprintf(stderr, "No #address-cells property on root node\n");
+		return -EINVAL;
+	}
+	addr_cells = be32_to_cpu(*prop32);
+
+	prop32 = fdt_getprop(fdt->buf, 0, "#size-cells", NULL);
+	if (!prop32) {
+		fprintf(stderr, "No #size-cells property on root node\n");
+		return -EINVAL;
+	}
+	size_cells = be32_to_cpu(*prop32);
+
+	/*
+	 * Parse the reg array, acording to device tree spec it includes
+	 * an arbitary number of <address><size> pairs
+	 */
+	entry_size = (addr_cells + size_cells) * sizeof(uint32_t);
+	reg = fdt_getprop(fdt->buf, node_offset, "reg", &prop_size);
+	if (!reg) {
+		fprintf(stderr, "Warning: Malformed memory region with no "
+				"reg property (%s) !\n",
+				fdt_get_name(fdt->buf, node_offset, NULL));
+		return -EINVAL;
+	}
+
+	num_entries = prop_size / entry_size;
+	dbgprintf("Got region with %i entries: %s\n", num_entries,
+		  fdt_get_name(fdt->buf, node_offset, NULL));
+
+	for (num_entries--; num_entries >= 0; num_entries--) {
+		offset = num_entries * entry_size;
+
+		fdt_read_property(&addr, reg + offset,
+				  addr_cells);
+		offset += addr_cells * sizeof(uint32_t);
+		fdt_read_property(&size, reg + offset,
+				  size_cells);
+
+		ret = add_memory_range(mem_ranges, addr,
+				       addr + size, type);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int parse_memory_reservations_table(struct memory_ranges *mem_ranges,
+					   struct fdt_image *fdt)
+{
+	int total_memrsrv = 0;
+	uint64_t addr = 0;
+	uint64_t size = 0;
+	int ret = 0;
+	int i = 0;
+
+	total_memrsrv = fdt_num_mem_rsv(fdt->buf);
+	for (i = 0; i < total_memrsrv; i++) {
+		ret = fdt_get_mem_rsv(fdt->buf, i, &addr, &size);
+		if (ret)
+			continue;
+		ret = add_memory_range(mem_ranges, addr, addr + size - 1,
+				       RANGE_RESERVED);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int parse_reserved_memory_regions(struct memory_ranges *mem_ranges,
+					 struct fdt_image *fdt)
+{
+	int node_offset = 0;
+	int node_depth = 0;
+	int parent_depth = 0;
+	int ranges_size = 0;
+	int ret = 0;
+
+	/* This calls fdt_next_node internaly */
+	node_offset = fdt_subnode_offset(fdt->buf, 0, "reserved-memory");
+	if (node_offset == -FDT_ERR_NOTFOUND)
+		return 0;
+	else if (node_offset < 0) {
+		fprintf(stderr, "Error while looking for reserved-memory: %s\n",
+			fdt_strerror(node_offset));
+		return node_offset;
+	}
+
+	parent_depth = fdt_node_depth(fdt->buf, node_offset);
+	if (parent_depth < 0) {
+		fprintf(stderr, "Error while looking for reserved-memory: %s\n",
+			fdt_strerror(parent_depth));
+		return parent_depth;
+	}
+
+	/* Look for the ranges property */
+	fdt_getprop(fdt->buf, node_offset, "ranges", &ranges_size);
+	if (ranges_size < 0) {
+		fprintf(stderr, "Malformed reserved-memory node !\n");
+		return -EINVAL;
+	}
+
+	/* Got the parent node, check for sub-nodes */
+
+	/* fdt_next_node() increases or decreases depth */
+	node_depth = parent_depth;
+	node_offset = fdt_next_node(fdt->buf, node_offset, &node_depth);
+	if (ret < 0) {
+		fprintf(stderr, "Unable to get next node: %s\n",
+			fdt_strerror(ret));
+		return -EINVAL;
+	}
+
+	while (node_depth != parent_depth) {
+		ret = add_memory_region(mem_ranges, fdt, node_offset,
+					RANGE_RESERVED);
+		if (ret)
+			return ret;
+
+		node_offset = fdt_next_node(fdt->buf, node_offset, &node_depth);
+		if (ret < 0) {
+			fprintf(stderr, "Unable to get next node: %s\n",
+				fdt_strerror(ret));
+			return -EINVAL;
+		}
+	}
+
+	return 0;
+}
+
+static int parse_memory_regions(struct memory_ranges *mem_ranges,
+				struct fdt_image *fdt)
+{
+	int node_offset = 0;
+	int num_regions = 0;
+	int ret = 0;
+
+	for (; ; num_regions++) {
+		node_offset = fdt_subnode_offset(fdt->buf, node_offset,
+						 "memory");
+		if (node_offset < 0)
+			break;
+
+		ret = add_memory_region(mem_ranges, fdt, node_offset,
+					RANGE_RAM);
+		if (ret)
+			return ret;
+	}
+
+	if (!num_regions) {
+		fprintf(stderr, "Malformed dtb, no /memory nodes present !\n");
+		return -EINVAL;
+	}
+
+	dbgprintf("Got %i /memory nodes\n", num_regions);
+
+	return 0;
+}
+
+
+/****************\
+* COMMON HELPERS *
+\****************/
+
+int load_extra_segments(struct kexec_info *info, uint64_t kernel_base,
+			uint64_t kernel_size, uint64_t max_addr)
+{
+	struct fdt_image *fdt = arch_options.fdt;
+	char *initrd_buf = NULL;
+	off_t initrd_size = 0;
+	uint64_t initrd_base = 0;
+	uint64_t start = 0;
+	uint64_t end = 0;
+	uint64_t min_usable = kernel_base + kernel_size;
+	uint64_t max_usable = max_addr;
+	int ret = 0;
+
+	/* Prepare the device tree */
+	if (info->kexec_flags & KEXEC_ON_CRASH) {
+		ret = load_elfcorehdr(info);
+		if (ret) {
+			fprintf(stderr, "Couldn't create elfcorehdr\n");
+			return ret;
+		}
+
+		ret = fdt_add_range(fdt, elfcorehdr_mem.start, elfcorehdr_mem.end,
+				    "linux,elfcorehdr");
+		if (ret) {
+			fprintf(stderr, "Couldn't add elfcorehdr to fdt\n");
+			return ret;
+		}
+
+		ret = get_crash_kernel_load_range(&start, &end);
+		if (ret) {
+			fprintf(stderr, "Couldn't get crashkenel region\n");
+			return ret;
+		}
+
+		ret = fdt_add_range(fdt, start, end,
+				    "linux,usable-memory-range");
+		if (ret) {
+			fprintf(stderr, "Couldn't add usablemem to fdt\n");
+			return ret;
+		}
+
+		max_usable = end;
+	} else {
+		/*
+		 * Make sure we remove elfcorehdr and usable-memory-range
+		 * when switching from crash kernel to a normal one.
+		 */
+		fdt_remove_from_chosen(fdt, "linux,elfcorehdr");
+		fdt_remove_from_chosen(fdt, "linux,usable-memory-range");
+	}
+
+	/* Do we need to include an initrd image ? */
+	if (!arch_options.initrd_path && !arch_options.initrd_end)
+		dtb_clear_initrd(&fdt->buf, &fdt->size);
+	else if (arch_options.initrd_path) {
+		if (arch_options.initrd_end)
+			fprintf(stderr, "Warning: An initrd image was provided"
+					", will ignore reuseinitrd\n");
+
+		initrd_buf = slurp_file(arch_options.initrd_path,
+					&initrd_size);
+		if (!initrd_buf) {
+			fprintf(stderr, "Couldn't read provided initrd\n");
+			return -EINVAL;
+		}
+
+		/* Put initrd above kernel */
+		initrd_base = add_buffer_phys_virt(info, initrd_buf,
+						   initrd_size,
+						   initrd_size, 0,
+						   min_usable,
+						   max_usable, -1, 0);
+
+		/* Update dtb */
+		dtb_set_initrd(&fdt->buf, &fdt->size, initrd_base,
+			       initrd_base + initrd_size);
+
+		dbgprintf("Base addr for initrd image: 0x%lX\n", initrd_base);
+		min_usable = initrd_base;
+	}
+
+	/* Add device tree */
+	add_buffer_phys_virt(info, fdt->buf, fdt->size, fdt->size, 0,
+			     min_usable, max_usable, -1, 0);
+
+	return 0;
+}
+
+
+/**************\
+* ENTRY POINTS *
+\**************/
+
+void arch_usage(void)
+{
+	printf(riscv_opts_usage);
+}
+
+int arch_process_options(int argc, char **argv)
+{
+	static const struct option options[] = {
+		KEXEC_ARCH_OPTIONS
+		{ 0 },
+	};
+	static const char short_options[] = KEXEC_ARCH_OPT_STR;
+	struct stat st = {0};
+	char *append = NULL;
+	char *cmdline = NULL;
+	void *tmp = NULL;
+	off_t tmp_size = 0;
+	int opt = 0;
+	int ret = 0;
+
+	while ((opt = getopt_long(argc, argv, short_options,
+				  options, 0)) != -1) {
+		switch (opt) {
+		case OPT_APPEND:
+			append = optarg;
+			break;
+		case OPT_CMDLINE:
+			if (cmdline)
+				fprintf(stderr,
+					"Warning: Kernel's cmdline "
+					"set twice !\n");
+			cmdline = optarg;
+			break;
+		case OPT_REUSE_CMDLINE:
+			if (cmdline)
+				fprintf(stderr,
+					"Warning: Kernel's cmdline "
+					"set twice !\n");
+			cmdline = get_command_line();
+			break;
+		case OPT_DTB:
+			ret = stat(optarg, &st);
+			if (ret) {
+				fprintf(stderr,
+					"Could not find the provided dtb !\n");
+				return -EINVAL;
+			}
+			arch_options.fdt_path = optarg;
+			break;
+		case OPT_INITRD:
+			ret = stat(optarg, &st);
+			if (ret) {
+				fprintf(stderr,
+					"Could not find the provided "
+					"initrd image !\n");
+				return -EINVAL;
+			}
+			arch_options.initrd_path = optarg;
+			break;
+		default:
+			break;
+		}
+	}
+
+	/* Handle Kernel's command line */
+	if (append && !cmdline)
+		fprintf(stderr, "Warning: No cmdline provided, "
+				"using append string as cmdline\n");
+	if (!append && !cmdline)
+		fprintf(stderr, "Warning: No cmdline or append string "
+				"provided\n");
+
+	if (append || cmdline)
+		/*
+		 * Note that this also handles the case where "cmdline"
+		 * or "append" is NULL.
+		 */
+		arch_options.cmdline = concat_cmdline(cmdline, append);
+
+	/* Handle FDT image */
+	if (!arch_options.fdt_path) {
+		ret = stat("/sys/firmware/fdt", &st);
+		if (ret) {
+			fprintf(stderr, "No dtb provided and "
+					"/sys/firmware/fdt is not present\n");
+			return -EINVAL;
+		}
+		fprintf(stderr, "Warning: No dtb provided, "
+				"using /sys/firmware/fdt\n");
+		arch_options.fdt_path = "/sys/firmware/fdt";
+	}
+
+	tmp = slurp_file(arch_options.fdt_path, &tmp_size);
+	if (!tmp) {
+		fprintf(stderr, "Couldn't read provided fdt\n");
+		return -EINVAL;
+	}
+
+	ret = fdt_check_header(tmp);
+	if (ret) {
+		fprintf(stderr, "Got an ivalid fdt image !\n");
+		free(tmp);
+		return -EINVAL;
+	}
+	provided_fdt.buf = tmp;
+	provided_fdt.size = tmp_size;
+
+	arch_options.fdt = &provided_fdt;
+
+	if (arch_options.cmdline) {
+		ret = dtb_set_bootargs(&provided_fdt.buf, &provided_fdt.size,
+				       arch_options.cmdline);
+		if (ret < 0) {
+			fprintf(stderr, "Could not set bootargs on "
+					"the fdt image\n");
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * This one is called after arch_process_options so we already
+ * have an fdt image in place.
+ */
+void arch_reuse_initrd(void)
+{
+	const uint32_t *prop32 = NULL;
+	uint32_t addr_cells = 0;
+	const void *prop = 0;
+	int prop_size = 0;
+	uint64_t initrd_start = 0;
+	uint64_t initrd_end = 0;
+	int chosen_offset = 0;
+	struct fdt_image *fdt = &provided_fdt;
+
+	chosen_offset = fdt_subnode_offset(fdt->buf, 0, "chosen");
+	if (chosen_offset < 0) {
+		fprintf(stderr, "No /chosen node found on fdt image "
+				"unable to reuse initrd\n");
+		return;
+	}
+
+	prop32 = fdt_getprop(fdt->buf, 0, "#address-cells", NULL);
+	if (!prop32) {
+		fprintf(stderr, "No #address-cells property on root node\n");
+		return;
+	}
+	addr_cells = be32_to_cpu(*prop32);
+
+	prop = fdt_getprop(fdt->buf, chosen_offset,
+			   "linux,initrd-start", &prop_size);
+	if (!prop) {
+		fprintf(stderr, "Could not get linux,initrd-start\n");
+		return;
+	}
+	fdt_read_property(&initrd_start, prop, addr_cells);
+
+	prop = fdt_getprop(fdt->buf, chosen_offset,
+			   "linux,initrd-end", &prop_size);
+	if (!prop) {
+		fprintf(stderr, "Could not get linux,initrd-end\n");
+		return;
+	}
+	fdt_read_property(&initrd_end, prop, addr_cells);
+
+	arch_options.initrd_start = initrd_start;
+	arch_options.initrd_end = initrd_end;
+	dbgprintf("initrd_start: 0x%lX, initrd_end: 0x%lX\n",
+		  initrd_start, initrd_end);
+
+}
+
+int get_memory_ranges(struct memory_range **range, int *ranges,
+		      unsigned long kexec_flags)
+{
+	const char *active_fdt_path = "/sys/firmware/fdt";
+	struct stat st = {0};
+	void *tmp = NULL;
+	off_t tmp_size = 0;
+	int i = 0;
+	int ret = 0;
+
+	ret = stat(active_fdt_path, &st);
+	if (ret) {
+		fprintf(stderr, "/sys/firmware/fdt is not present\n");
+		return -EINVAL;
+	}
+
+	tmp = slurp_file(active_fdt_path, &tmp_size);
+	ret = fdt_check_header(tmp);
+	if (ret) {
+		fprintf(stderr, "/sys/firmware/fdt is invalid !\n");
+		free(tmp);
+		return -EINVAL;
+	}
+	active_fdt.buf = tmp;
+	active_fdt.size = tmp_size;
+
+	ret = parse_memory_regions(&sysmem_ranges, &active_fdt);
+	if (ret)
+		return ret;
+
+	ret = parse_memory_reservations_table(&sysmem_ranges, &active_fdt);
+	if (ret)
+		return ret;
+
+	ret = parse_reserved_memory_regions(&sysmem_ranges, &active_fdt);
+	if (ret)
+		return ret;
+
+	if (arch_options.initrd_start && arch_options.initrd_end) {
+		dbgprintf("Marking current intird image as reserved\n");
+		ret = add_memory_range(&sysmem_ranges,
+				       arch_options.initrd_start,
+				       arch_options.initrd_end,
+				       RANGE_RESERVED);
+		if (ret)
+			return ret;
+	}
+
+	mem_regions_sort(&sysmem_ranges);
+
+	*range = sysmem_ranges.ranges;
+	*ranges = sysmem_ranges.size;
+
+	dbgprintf("Memory regions:\n");
+	for (i = 0; i < sysmem_ranges.size; i++) {
+		dbgprintf("\t0x%llx - 0x%llx : %s (%i)\n",
+			  sysmem_ranges.ranges[i].start,
+			  sysmem_ranges.ranges[i].end,
+			  sysmem_ranges.ranges[i].type == RANGE_RESERVED ?
+			  "RANGE_RESERVED" : "RANGE_RAM",
+			  sysmem_ranges.ranges[i].type);
+	}
+
+	return 0;
+}
+
+/*******\
+* STUBS *
+\*******/
+
+int arch_compat_trampoline(struct kexec_info *UNUSED(info))
+{
+	return 0;
+}
+
+void arch_update_purgatory(struct kexec_info *UNUSED(info))
+{
+}
diff --git a/kexec/arch/riscv/kexec-riscv.h b/kexec/arch/riscv/kexec-riscv.h
new file mode 100644
index 0000000..c4323a6
--- /dev/null
+++ b/kexec/arch/riscv/kexec-riscv.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2019 FORTH-ICS/CARV
+ *              Nick Kossifidis <mick@ics.forth.gr>
+ */
+
+struct fdt_image {
+	char	*buf;
+	off_t	size;
+};
+
+struct riscv_opts {
+	char *cmdline;
+	char *fdt_path;
+	char *initrd_path;
+	uint64_t initrd_start;
+	uint64_t initrd_end;
+	struct fdt_image *fdt;
+};
+
+/* crashdump-riscv.c */
+extern struct memory_range elfcorehdr_mem;
+int load_elfcorehdr(struct kexec_info *info);
+
+/* kexec-riscv.c */
+int load_extra_segments(struct kexec_info *info, uint64_t kernel_base,
+			uint64_t kernel_size, uint64_t max_addr);
+
+int elf_riscv_probe(const char *buf, off_t len);
+void elf_riscv_usage(void);
+int elf_riscv_load(int argc, char **argv, const char *buf, off_t len,
+		   struct kexec_info *info);
diff --git a/kexec/kexec-syscall.h b/kexec/kexec-syscall.h
index bea29d4..2e99e2b 100644
--- a/kexec/kexec-syscall.h
+++ b/kexec/kexec-syscall.h
@@ -134,6 +134,7 @@ static inline long kexec_file_load(int kernel_fd, int initrd_fd,
 #define KEXEC_ARCH_MIPS_LE (10 << 16)
 #define KEXEC_ARCH_MIPS    ( 8 << 16)
 #define KEXEC_ARCH_CRIS    (76 << 16)
+#define KEXEC_ARCH_RISCV   (243 << 16)
 
 #define KEXEC_MAX_SEGMENTS 16
 
@@ -177,5 +178,8 @@ static inline long kexec_file_load(int kernel_fd, int initrd_fd,
 #if defined(__arm64__)
 #define KEXEC_ARCH_NATIVE	KEXEC_ARCH_ARM64
 #endif
+#if defined(__riscv__) || defined(__riscv)
+#define KEXEC_ARCH_NATIVE	KEXEC_ARCH_RISCV
+#endif
 
 #endif /* KEXEC_SYSCALL_H */
diff --git a/kexec/kexec.c b/kexec/kexec.c
index 7ef3d2a..ac4d234 100644
--- a/kexec/kexec.c
+++ b/kexec/kexec.c
@@ -61,7 +61,7 @@ unsigned long long mem_max = ULONG_MAX;
 static unsigned long kexec_flags = 0;
 /* Flags for kexec file (fd) based syscall */
 static unsigned long kexec_file_flags = 0;
-int kexec_debug = 0;
+int kexec_debug = 1;
 
 void dbgprint_mem_range(const char *prefix, struct memory_range *mr, int nr_mr)
 {
diff --git a/purgatory/Makefile b/purgatory/Makefile
index 2dd6c47..0a9d1c1 100644
--- a/purgatory/Makefile
+++ b/purgatory/Makefile
@@ -25,6 +25,7 @@ include $(srcdir)/purgatory/arch/ia64/Makefile
 include $(srcdir)/purgatory/arch/mips/Makefile
 include $(srcdir)/purgatory/arch/ppc/Makefile
 include $(srcdir)/purgatory/arch/ppc64/Makefile
+include $(srcdir)/purgatory/arch/riscv/Makefile
 include $(srcdir)/purgatory/arch/s390/Makefile
 include $(srcdir)/purgatory/arch/sh/Makefile
 include $(srcdir)/purgatory/arch/x86_64/Makefile
diff --git a/purgatory/arch/riscv/Makefile b/purgatory/arch/riscv/Makefile
new file mode 100644
index 0000000..8bded71
--- /dev/null
+++ b/purgatory/arch/riscv/Makefile
@@ -0,0 +1,7 @@
+#
+# Purgatory riscv
+#
+
+riscv_PURGATORY_SRCS =
+
+dist += purgatory/arch/sh/Makefile $(riscv_PURGATORY_SRCS)
diff --git a/kexec/arch/riscv/kexec-riscv.c b/kexec/arch/riscv/kexec-riscv.c
index 5e34635..f5b7e3f 100644
--- a/kexec/arch/riscv/kexec-riscv.c
+++ b/kexec/arch/riscv/kexec-riscv.c
@@ -44,7 +44,7 @@ static struct fdt_image active_fdt = {0};
 * DEVICE TREE MANIPULATION *
 \**************************/
 
-void fdt_read_property(uint64_t *val, const void *buf, uint32_t cells)
+static void fdt_read_property(uint64_t *val, const void *buf, uint32_t cells)
 {
 	const uint32_t *prop32 = NULL;
 	const uint64_t *prop64 = NULL;
@@ -59,7 +59,7 @@ void fdt_read_property(uint64_t *val, const void *buf, uint32_t cells)
 	}
 }
 
-void fdt_write_property(void *buf, uint64_t val, uint32_t cells)
+static void fdt_write_property(void *buf, uint64_t val, uint32_t cells)
 {
 	uint32_t prop32 = 0;
 	uint64_t prop64 = 0;
@@ -75,21 +75,17 @@ void fdt_write_property(void *buf, uint64_t val, uint32_t cells)
 	}
 }
 
-int fdt_add_range(struct fdt_image *fdt, uint64_t start,
-		  uint64_t end, const char *name)
+static int fdt_add_range_to(struct fdt_image *fdt, uint64_t start,
+		  uint64_t end, const char *name, const char* root, off_t root_off)
 {
 	uint32_t addr_cells = 0;
 	uint32_t size_cells = 0;
-	int prop_size = 0;
-	uint32_t new_size = 0;
-	void *new_buf = NULL;
-	void *prop = NULL;
 	const uint32_t *prop32 = NULL;
-	int chosen_offt = 0;
+	void *prop = NULL;
+	int new_fdt_size = 0;
+	int prop_size = 0;
 	int ret = 0;
 
-	chosen_offt = fdt_path_offset(fdt->buf, "/chosen");
-
 	prop32 = fdt_getprop(fdt->buf, 0, "#address-cells", NULL);
 	if (!prop32) {
 		fprintf(stderr, "No #address-cells property on root node\n");
@@ -111,34 +107,253 @@ int fdt_add_range(struct fdt_image *fdt, uint64_t start,
 	if ((size_cells == 1) && ((end - start + 1) >= (1ULL << 32)))
 		return -EINVAL;
 
-	/* Expand fdt buffer to include the new property */
 	prop_size = sizeof(uint32_t) * (addr_cells + size_cells);
-	new_size = fdt_totalsize(fdt->buf) +
-		   fdt_prop_len(name, prop_size);
+	prop = xmalloc(prop_size);
+
+	fdt_write_property(prop, start, addr_cells);
+	fdt_write_property((void *)((uint32_t *)prop + addr_cells),
+			   end - start + 1, size_cells);
 
-	new_buf = xrealloc(fdt->buf, new_size);
-	if (!new_buf) {
-		fprintf(stderr, "Couldn't expand fdt image\n");
+	/* Add by node path name */
+	if (root != NULL)
+		return dtb_set_property(&fdt->buf, &fdt->size, root, name, prop, prop_size);
+
+	/* Add by node offset */
+	if (root_off <= 0) {
+		free(prop);
+		return -EINVAL;
+	}
+
+	new_fdt_size = FDT_TAGALIGN(fdt->size + fdt_prop_len(name, prop_size));
+	fdt->buf = xrealloc(fdt->buf, new_fdt_size);
+	if (!fdt->buf) {
+		fprintf(stderr, "Could not expand fdt\n");
+		free(prop);
 		return -ENOMEM;
 	}
-	fdt->buf = new_buf;
+	fdt_set_totalsize(fdt->buf, new_fdt_size);
+	fdt->size = fdt_totalsize(fdt->buf);
 
-	prop = xmalloc(prop_size);
-	fdt_write_property(prop, start, addr_cells);
+	ret = fdt_setprop(fdt->buf, root_off, name, prop, prop_size);
+	if (ret < 0)
+		fprintf(stderr, "Could not add %s property: %s\n", name,
+			fdt_strerror(ret));
 
-	fdt_write_property((void *)((uint32_t *)prop + addr_cells),
-			   end - start + 1, size_cells);
+	return ret;
+}
+
+static int fdt_remove_prop_from(struct fdt_image *fdt, const char* name, const char* root)
+{
+	int root_offt = 0;
+
+	root_offt = fdt_path_offset(fdt->buf, root);
+	return fdt_delprop(fdt->buf, root_offt, name);
+}
+
+static int fdt_remove_node_by_compatible(struct fdt_image *fdt, const char* compatible)
+{
+	int ret = 0;
+	int node_offset = fdt_node_offset_by_compatible(fdt->buf, 0, compatible);
+	if (node_offset == -FDT_ERR_NOTFOUND) {
+		return 0;
+	} else if (node_offset < 0) {
+		fprintf(stderr, "Error while looking for %s: %s\n",
+			compatible, fdt_strerror(node_offset));
+		return node_offset;
+	}
+
+	ret = fdt_del_node(fdt->buf, node_offset);
+	if (ret < 0) {
+		fprintf(stderr, "Could not delete node %s: %s\n",
+			compatible, fdt_strerror(ret));
+	}
+	fdt->size = fdt_totalsize(fdt->buf);
 
-	ret = fdt_setprop(fdt->buf, chosen_offt, name, prop, prop_size);
 	return ret;
 }
 
-static int fdt_remove_from_chosen(struct fdt_image *fdt, const char* name)
+static int fdt_get_reserved_memory_node(struct fdt_image *fdt, int create)
 {
-	int chosen_offt = 0;
+	uint32_t root_addr_cells = 0;
+	uint32_t root_size_cells = 0;
+	uint32_t addr_cells = 0;
+	uint32_t size_cells = 0;
+	const uint32_t *prop32 = NULL;
+	int prop_size = 0;
+	int node_offset = 0;
+	int parent_depth = 0;
+	int ret = 0;
+
+	prop32 = fdt_getprop(fdt->buf, 0, "#address-cells", NULL);
+	if (!prop32) {
+		fprintf(stderr, "No #address-cells property on root node\n");
+		return -EINVAL;
+	}
+	root_addr_cells = be32_to_cpu(*prop32);
 
-	chosen_offt = fdt_path_offset(fdt->buf, "/chosen");
-	return fdt_delprop(fdt->buf, chosen_offt, name);
+	prop32 = fdt_getprop(fdt->buf, 0, "#size-cells", NULL);
+	if (!prop32) {
+		fprintf(stderr, "No #size-cells property on root node\n");
+		return -EINVAL;
+	}
+	root_size_cells = be32_to_cpu(*prop32);
+
+	/* This calls fdt_next_node internaly */
+	node_offset = fdt_subnode_offset(fdt->buf, 0, "reserved-memory");
+	if (node_offset == -FDT_ERR_NOTFOUND) {
+		if (create)
+			goto create;
+		return node_offset;
+	} else if (node_offset < 0) {
+		fprintf(stderr, "Error while looking for reserved-memory: %s\n",
+			fdt_strerror(node_offset));
+		return node_offset;
+	}
+
+	parent_depth = fdt_node_depth(fdt->buf, node_offset);
+	if (parent_depth < 0) {
+		fprintf(stderr, "Error while looking for reserved-memory: %s\n",
+			fdt_strerror(parent_depth));
+		return parent_depth;
+	}
+
+	/* Look for the ranges property */
+	fdt_getprop(fdt->buf, node_offset, "ranges", &prop_size);
+	if (prop_size < 0) {
+		fprintf(stderr, "Malformed reserved-memory node !\n");
+		return -EINVAL;
+	}
+
+	/* Verify address-cells / size-cells */
+	prop32 = fdt_getprop(fdt->buf, 0, "#address-cells", NULL);
+	if (!prop32) {
+		fprintf(stderr, "No #address-cells property on reserved-memory node\n");
+		return -EINVAL;
+	}
+	addr_cells = be32_to_cpu(*prop32);
+
+	prop32 = fdt_getprop(fdt->buf, 0, "#size-cells", NULL);
+	if (!prop32) {
+		fprintf(stderr, "No #size-cells property on reserved-memory node\n");
+		return -EINVAL;
+	}
+	size_cells = be32_to_cpu(*prop32);
+
+	if (addr_cells != root_addr_cells) {
+		fprintf(stderr, "Invalid #address-cells property on reserved-memory node\n");
+		return -EINVAL;
+	}
+
+	if (size_cells != root_size_cells) {
+		fprintf(stderr, "Invalid #size-cells property on reserved-memory node\n");
+		return -EINVAL;
+
+	}
+
+	return node_offset;
+
+ create:
+	ret = dtb_set_property(&fdt->buf, &fdt->size, "reserved-memory",
+			       "ranges", NULL, 0);
+	if (ret < 0)
+		goto error;
+
+	addr_cells = cpu_to_be32(root_addr_cells);
+	ret = dtb_set_property(&fdt->buf, &fdt->size, "reserved-memory",
+			       "#address-cells", &addr_cells, sizeof(uint32_t));
+	if (ret < 0)
+		goto error;
+
+	size_cells = cpu_to_be32(root_size_cells);
+	ret = dtb_set_property(&fdt->buf, &fdt->size, "reserved-memory",
+			       "#size-cells", &size_cells, sizeof(uint32_t));
+	if (ret < 0)
+		goto error;
+
+        node_offset = fdt_subnode_offset(fdt->buf, 0, "reserved-memory");
+	if (node_offset <= 0) {
+		ret =  node_offset;
+		goto error;
+	}
+
+	return node_offset;
+ error:
+	fprintf(stderr, "Could not create reserved-memory node\n");
+	return ret;
+}
+
+static int fdt_add_reserved_memory_subnode(struct fdt_image *fdt, const char* name,
+					const char* compatible, uint64_t start,
+					uint64_t end)
+{
+	uint32_t root_addr_cells = 0;
+	const uint32_t *prop32 = NULL;
+	void *prop = NULL;
+	int prop_size = 0;
+	int new_fdt_size = 0;
+	char *full_name = NULL;
+	off_t subnode_off = 0;
+	int name_len = 0;
+	int root_off = 0;
+	int ret = 0;
+
+	root_off = fdt_get_reserved_memory_node(fdt, 1);
+	if (root_off < 0)
+		return root_off;
+
+	/* Create the subnode's name <name>@<address> */
+	prop32 = fdt_getprop(fdt->buf, 0, "#address-cells", NULL);
+	if (!prop32) {
+		fprintf(stderr, "No #address-cells property on root node\n");
+		return -EINVAL;
+	}
+	root_addr_cells = be32_to_cpu(*prop32);
+
+	name_len = strlen(name) + (root_addr_cells * 8) + 1;
+	full_name = xmalloc(name_len);
+	if (!full_name) {
+		fprintf(stderr, "Could not allocate buffer for subnode name\n");
+		return -ENOMEM;
+	}
+	snprintf(full_name, name_len, "%s@%lx", name, start);
+
+	/* Re-size the fdt and put the new subnode in */
+	new_fdt_size = FDT_TAGALIGN(fdt->size + fdt_node_len(full_name));
+	fdt->buf = xrealloc(fdt->buf, new_fdt_size);
+	if (!fdt->buf) {
+		fprintf(stderr, "Could not expand fdt\n");
+		return -ENOMEM;
+	}
+	fdt_set_totalsize(fdt->buf, new_fdt_size);
+	fdt->size = fdt_totalsize(fdt->buf);
+
+	ret = fdt_add_subnode(fdt->buf, root_off, (const char*) full_name);
+	if (ret < 0) {
+		fprintf(stderr, "Could not add subnode: %s\n", fdt_strerror(ret));
+		return ret;
+	}
+	subnode_off = ret;
+
+	/* Add the compatible property */
+	prop_size = strlen(compatible) + 1;
+	new_fdt_size = FDT_TAGALIGN(fdt->size + fdt_prop_len("compatible", prop_size));
+	fdt->buf = xrealloc(fdt->buf, new_fdt_size);
+	if (!fdt->buf) {
+		fprintf(stderr, "Could not expand fdt\n");
+		return -ENOMEM;
+	}
+	fdt_set_totalsize(fdt->buf, new_fdt_size);
+	fdt->size = fdt_totalsize(fdt->buf);
+
+	ret = fdt_setprop(fdt->buf, subnode_off, "compatible", compatible, prop_size);
+	if (ret < 0) {
+		fprintf(stderr, "Could not add compatible property: %s\n",
+			fdt_strerror(ret));
+		return ret;
+	}
+
+	ret = fdt_add_range_to(fdt, start, end, "reg", NULL, subnode_off);
+	return ret;
 }
 
 /************************\
@@ -297,18 +512,13 @@ static int parse_reserved_memory_regions(struct memory_ranges *mem_ranges,
 	int node_offset = 0;
 	int node_depth = 0;
 	int parent_depth = 0;
-	int ranges_size = 0;
 	int ret = 0;
 
-	/* This calls fdt_next_node internaly */
-	node_offset = fdt_subnode_offset(fdt->buf, 0, "reserved-memory");
+	node_offset = fdt_get_reserved_memory_node(fdt, 0);
 	if (node_offset == -FDT_ERR_NOTFOUND)
 		return 0;
-	else if (node_offset < 0) {
-		fprintf(stderr, "Error while looking for reserved-memory: %s\n",
-			fdt_strerror(node_offset));
+	else if (node_offset < 0)
 		return node_offset;
-	}
 
 	parent_depth = fdt_node_depth(fdt->buf, node_offset);
 	if (parent_depth < 0) {
@@ -317,13 +527,6 @@ static int parse_reserved_memory_regions(struct memory_ranges *mem_ranges,
 		return parent_depth;
 	}
 
-	/* Look for the ranges property */
-	fdt_getprop(fdt->buf, node_offset, "ranges", &ranges_size);
-	if (ranges_size < 0) {
-		fprintf(stderr, "Malformed reserved-memory node !\n");
-		return -EINVAL;
-	}
-
 	/* Got the parent node, check for sub-nodes */
 
 	/* fdt_next_node() increases or decreases depth */
@@ -407,8 +610,8 @@ int load_extra_segments(struct kexec_info *info, uint64_t kernel_base,
 			return ret;
 		}
 
-		ret = fdt_add_range(fdt, elfcorehdr_mem.start, elfcorehdr_mem.end,
-				    "linux,elfcorehdr");
+		ret = fdt_add_reserved_memory_subnode(fdt, "elfcorehdr", "linux,elfcorehdr",
+						      elfcorehdr_mem.start, elfcorehdr_mem.end);
 		if (ret) {
 			fprintf(stderr, "Couldn't add elfcorehdr to fdt\n");
 			return ret;
@@ -420,8 +623,8 @@ int load_extra_segments(struct kexec_info *info, uint64_t kernel_base,
 			return ret;
 		}
 
-		ret = fdt_add_range(fdt, start, end,
-				    "linux,usable-memory-range");
+		ret = fdt_add_range_to(fdt, start, end,
+					"linux,usable-memory", "/memory", 0);
 		if (ret) {
 			fprintf(stderr, "Couldn't add usablemem to fdt\n");
 			return ret;
@@ -430,11 +633,12 @@ int load_extra_segments(struct kexec_info *info, uint64_t kernel_base,
 		max_usable = end;
 	} else {
 		/*
-		 * Make sure we remove elfcorehdr and usable-memory-range
+		 * Make sure we remove elfcorehdr and usable-memory
 		 * when switching from crash kernel to a normal one.
 		 */
-		fdt_remove_from_chosen(fdt, "linux,elfcorehdr");
-		fdt_remove_from_chosen(fdt, "linux,usable-memory-range");
+		fdt_remove_prop_from(fdt, "linux,elfcorehdr", "/chosen");
+		fdt_remove_node_by_compatible(fdt, "linux,elfcorehdr");
+		fdt_remove_prop_from(fdt, "linux,usable-memory", "/memory");
 	}
 
 	/* Do we need to include an initrd image ? */
@@ -586,8 +790,6 @@ int arch_process_options(int argc, char **argv)
 	provided_fdt.buf = tmp;
 	provided_fdt.size = tmp_size;
 
-	arch_options.fdt = &provided_fdt;
-
 	if (arch_options.cmdline) {
 		ret = dtb_set_bootargs(&provided_fdt.buf, &provided_fdt.size,
 				       arch_options.cmdline);
@@ -598,6 +800,8 @@ int arch_process_options(int argc, char **argv)
 		}
 	}
 
+	arch_options.fdt = &provided_fdt;
+
 	return 0;
 }
 
