summaryrefslogtreecommitdiffstats
path: root/kexec/arch/arm
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--kexec/arch/arm/Makefile34
-rw-r--r--kexec/arch/arm/crashdump-arm.c388
-rw-r--r--kexec/arch/arm/crashdump-arm.h27
-rw-r--r--kexec/arch/arm/include/arch/options.h52
-rw-r--r--kexec/arch/arm/iomem.h9
-rw-r--r--kexec/arch/arm/kexec-arm.c150
-rw-r--r--kexec/arch/arm/kexec-arm.h22
-rw-r--r--kexec/arch/arm/kexec-elf-rel-arm.c36
-rw-r--r--kexec/arch/arm/kexec-uImage-arm.c22
-rw-r--r--kexec/arch/arm/kexec-zImage-arm.c914
-rw-r--r--kexec/arch/arm/phys_to_virt.c22
-rw-r--r--kexec/arch/arm/phys_to_virt.h8
-rw-r--r--kexec/arch/arm64/Makefile50
-rw-r--r--kexec/arch/arm64/crashdump-arm64.c248
-rw-r--r--kexec/arch/arm64/crashdump-arm64.h29
-rw-r--r--kexec/arch/arm64/image-header.h147
-rw-r--r--kexec/arch/arm64/include/arch/options.h43
-rw-r--r--kexec/arch/arm64/iomem.h10
-rw-r--r--kexec/arch/arm64/kexec-arm64.c1365
-rw-r--r--kexec/arch/arm64/kexec-arm64.h84
-rw-r--r--kexec/arch/arm64/kexec-elf-arm64.c170
-rw-r--r--kexec/arch/arm64/kexec-image-arm64.c119
-rw-r--r--kexec/arch/arm64/kexec-uImage-arm64.c52
-rw-r--r--kexec/arch/arm64/kexec-vmlinuz-arm64.c110
24 files changed, 4111 insertions, 0 deletions
diff --git a/kexec/arch/arm/Makefile b/kexec/arch/arm/Makefile
new file mode 100644
index 0000000..4454f47
--- /dev/null
+++ b/kexec/arch/arm/Makefile
@@ -0,0 +1,34 @@
+#
+# kexec arm (linux booting linux)
+#
+include $(srcdir)/kexec/libfdt/Makefile.libfdt
+
+arm_FS2DT = kexec/fs2dt.c
+arm_FS2DT_INCLUDE = -include $(srcdir)/kexec/arch/arm/crashdump-arm.h \
+ -include $(srcdir)/kexec/arch/arm/kexec-arm.h
+
+arm_MEM_REGIONS = kexec/mem_regions.c
+
+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/fs2dt.c
+
+libfdt_SRCS += $(LIBFDT_SRCS:%=kexec/libfdt/%)
+
+arm_CPPFLAGS = -I$(srcdir)/kexec/libfdt
+
+# We want 64-bit file IO for kdump to work correctly on LPAE systems
+arm_CPPFLAGS += -D_FILE_OFFSET_BITS=64
+
+arm_KEXEC_SRCS += $(libfdt_SRCS)
+
+arm_UIMAGE = kexec/kexec-uImage.c
+arm_PHYS_TO_VIRT = kexec/arch/arm/phys_to_virt.c
+
+dist += kexec/arch/arm/Makefile $(arm_KEXEC_SRCS) $(arm_PHYS_TO_VIRT) \
+ kexec/arch/arm/iomem.h kexec/arch/arm/phys_to_virt.h \
+ kexec/arch/arm/crashdump-arm.h 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..1ec1826
--- /dev/null
+++ b/kexec/arch/arm/crashdump-arm.c
@@ -0,0 +1,388 @@
+/*
+ * 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 <limits.h>
+#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 "../../mem_regions.h"
+#include "crashdump-arm.h"
+#include "iomem.h"
+#include "phys_to_virt.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 = {
+ .max_size = CRASH_MAX_MEMORY_RANGES,
+ .ranges = crash_memory_ranges,
+};
+
+/* The boot-time physical memory range reserved for crashkernel region */
+struct memory_range crash_kernel_mem;
+
+/* reserved regions */
+#define CRASH_MAX_RESERVED_RANGES 2
+static struct memory_range crash_reserved_ranges[CRASH_MAX_RESERVED_RANGES];
+static struct memory_ranges crash_reserved_rgns = {
+ .max_size = CRASH_MAX_RESERVED_RANGES,
+ .ranges = crash_reserved_ranges,
+};
+
+struct memory_range elfcorehdr_mem;
+
+static struct crash_elf_info elf_info = {
+ .class = ELFCLASS32,
+ .data = ELFDATANATIVE,
+ .machine = EM_ARM,
+ .page_offset = DEFAULT_PAGE_OFFSET,
+};
+
+extern unsigned long long user_page_offset;
+
+static int get_kernel_page_offset(struct kexec_info *info,
+ struct crash_elf_info *elf_info)
+{
+ unsigned long long stext_sym_addr = get_kernel_sym("_stext");
+ if (stext_sym_addr == 0) {
+ if (user_page_offset != (-1ULL)) {
+ elf_info->page_offset = user_page_offset;
+ dbgprintf("Unable to get _stext symbol from /proc/kallsyms, "
+ "use user provided vaule: %llx\n",
+ elf_info->page_offset);
+ return 0;
+ }
+ elf_info->page_offset = (unsigned long long)DEFAULT_PAGE_OFFSET;
+ dbgprintf("Unable to get _stext symbol from /proc/kallsyms, "
+ "use default: %llx\n",
+ elf_info->page_offset);
+ return 0;
+ } else if ((user_page_offset != (-1ULL)) &&
+ (user_page_offset != stext_sym_addr)) {
+ fprintf(stderr, "PAGE_OFFSET is set to %llx "
+ "instead of user provided value %llx\n",
+ stext_sym_addr & (~KVBASE_MASK),
+ user_page_offset);
+ }
+ elf_info->page_offset = stext_sym_addr & (~KVBASE_MASK);
+ 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)
+{
+ int i;
+
+ if (usablemem_rgns.size < 1) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ dbgprint_mem_range("Reserved memory ranges",
+ crash_reserved_rgns.ranges,
+ crash_reserved_rgns.size);
+
+ /*
+ * Exclude all reserved memory from the usable memory regions.
+ * We want to avoid dumping the crashkernel region itself. Note
+ * that this may result memory regions in usablemem_rgns being
+ * split.
+ */
+ for (i = 0; i < crash_reserved_rgns.size; i++) {
+ if (mem_regions_exclude(&usablemem_rgns,
+ &crash_reserved_rgns.ranges[i])) {
+ fprintf(stderr,
+ "Error: Number of crash memory ranges excedeed the max limit\n");
+ errno = ENOMEM;
+ return -1;
+ }
+ }
+
+ /*
+ * Make sure that the memory regions are sorted.
+ */
+ mem_regions_sort(&usablemem_rgns);
+
+ dbgprint_mem_range("Coredump memory ranges",
+ usablemem_rgns.ranges, usablemem_rgns.size);
+
+ 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_kernel_mem.start, crash_kernel_mem.end,
+ (unsigned long)range_size(&crash_kernel_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;
+ int last_ranges;
+
+ /*
+ * 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;
+
+ if (get_kernel_page_offset(info, &elf_info))
+ return -1;
+
+ dbgprintf("phys offset = %#llx, page offset = %llx\n",
+ phys_offset, elf_info.page_offset);
+
+ /*
+ * Ensure that the crash kernel memory range is sane. The crash kernel
+ * must be located within memory which is visible during booting.
+ */
+ if (crash_kernel_mem.end > ARM_MAX_VIRTUAL) {
+ fprintf(stderr,
+ "Crash kernel memory [0x%llx-0x%llx] is inaccessible at boot - unable to load crash kernel\n",
+ crash_kernel_mem.start, crash_kernel_mem.end);
+ return -1;
+ }
+
+ last_ranges = usablemem_rgns.size - 1;
+ if (last_ranges < 0)
+ last_ranges = 0;
+
+ if (crash_memory_ranges[last_ranges].end > UINT32_MAX) {
+ dbgprintf("Using 64-bit ELF core format\n");
+
+ /* for support LPAE enabled kernel*/
+ elf_info.class = ELFCLASS64;
+
+ err = crash_create_elf64_headers(info, &elf_info,
+ usablemem_rgns.ranges,
+ usablemem_rgns.size, &buf, &bufsz,
+ ELF_CORE_HEADER_ALIGN);
+ } else {
+ dbgprintf("Using 32-bit ELF core format\n");
+ 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_kernel_mem.start,
+ crash_kernel_mem.end, -1, 0);
+
+ elfcorehdr_mem.start = elfcorehdr;
+ elfcorehdr_mem.end = elfcorehdr + bufsz - 1;
+
+ dbgprintf("elfcorehdr 0x%llx-0x%llx\n", elfcorehdr_mem.start,
+ elfcorehdr_mem.end);
+ 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_kernel_mem.start);
+
+ dump_memory_ranges();
+ dbgprintf("kernel command line: \"%s\"\n", mod_cmdline);
+
+ return 0;
+}
+
+/**
+ * iomem_range_callback() - callback called for each iomem region
+ * @data: not used
+ * @nr: not used
+ * @str: name of the memory region (not NULL terminated)
+ * @base: start address of the memory region
+ * @length: size of the memory region
+ *
+ * This function is called for each memory range in /proc/iomem, stores
+ * the location of the crash kernel range into @crash_kernel_mem, and
+ * stores the system RAM into @usablemem_rgns.
+ */
+static int iomem_range_callback(void *UNUSED(data), int UNUSED(nr),
+ char *str, unsigned long long base,
+ unsigned long long length)
+{
+ if (strncmp(str, CRASH_KERNEL_BOOT, strlen(CRASH_KERNEL_BOOT)) == 0) {
+ crash_kernel_mem.start = base;
+ crash_kernel_mem.end = base + length - 1;
+ crash_kernel_mem.type = RANGE_RAM;
+ return mem_regions_add(&crash_reserved_rgns,
+ base, length, RANGE_RAM);
+ }
+ else if (strncmp(str, CRASH_KERNEL, strlen(CRASH_KERNEL)) == 0) {
+ if (crash_kernel_mem.start == crash_kernel_mem.end) {
+ crash_kernel_mem.start = base;
+ crash_kernel_mem.end = base + length - 1;
+ crash_kernel_mem.type = RANGE_RAM;
+ }
+ return mem_regions_add(&crash_reserved_rgns,
+ base, length, RANGE_RAM);
+ }
+ else if (strncmp(str, SYSTEM_RAM, strlen(SYSTEM_RAM)) == 0) {
+ return mem_regions_add(&usablemem_rgns,
+ base, length, RANGE_RAM);
+ }
+ return 0;
+}
+
+/**
+ * is_crashkernel_mem_reserved() - check for the crashkernel reserved region
+ *
+ * Check for the crashkernel reserved region in /proc/iomem, and return
+ * true if it is present, or false otherwise. We use this to store the
+ * location of this region, and system RAM regions.
+ */
+int is_crashkernel_mem_reserved(void)
+{
+ kexec_iomem_for_each_line(NULL, iomem_range_callback, NULL);
+
+ return crash_kernel_mem.start != crash_kernel_mem.end;
+}
+
+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/arm/crashdump-arm.h b/kexec/arch/arm/crashdump-arm.h
new file mode 100644
index 0000000..bbdf8bf
--- /dev/null
+++ b/kexec/arch/arm/crashdump-arm.h
@@ -0,0 +1,27 @@
+#ifndef CRASHDUMP_ARM_H
+#define CRASHDUMP_ARM_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define COMMAND_LINE_SIZE 1024
+#define DEFAULT_PAGE_OFFSET (0xc0000000)
+#define KVBASE_MASK (0x1ffffff)
+#define CRASH_MAX_MEMORY_RANGES 32
+#define ARM_MAX_VIRTUAL UINT32_MAX
+
+
+extern struct memory_ranges usablemem_rgns;
+extern struct memory_range crash_kernel_mem;
+extern struct memory_range elfcorehdr_mem;
+
+struct kexec_info;
+
+extern int load_crashdump_segments(struct kexec_info *, char *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* CRASHDUMP_ARM_H */
diff --git a/kexec/arch/arm/include/arch/options.h b/kexec/arch/arm/include/arch/options.h
new file mode 100644
index 0000000..6fabfb7
--- /dev/null
+++ b/kexec/arch/arm/include/arch/options.h
@@ -0,0 +1,52 @@
+#ifndef KEXEC_ARCH_ARM_OPTIONS_H
+#define KEXEC_ARCH_ARM_OPTIONS_H
+
+#define OPT_DT_NO_OLD_ROOT (OPT_MAX+0)
+#define OPT_ARCH_MAX (OPT_MAX+1)
+
+#define OPT_DTB (OPT_ARCH_MAX+0)
+#define OPT_ATAGS (OPT_ARCH_MAX+1)
+#define OPT_IMAGE_SIZE (OPT_ARCH_MAX+2)
+#define OPT_PAGE_OFFSET (OPT_ARCH_MAX+3)
+#define OPT_APPEND (OPT_ARCH_MAX+4)
+#define OPT_RAMDISK (OPT_ARCH_MAX+5)
+
+/* Options relevant to the architecture (excluding loader-specific ones),
+ * in this case none:
+ */
+#define KEXEC_ARCH_OPTIONS \
+ KEXEC_OPTIONS \
+ { "dt-no-old-root", 0, 0, OPT_DT_NO_OLD_ROOT }, \
+
+#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 \
+ { "command-line", 1, 0, OPT_APPEND }, \
+ { "append", 1, 0, OPT_APPEND }, \
+ { "initrd", 1, 0, OPT_RAMDISK }, \
+ { "ramdisk", 1, 0, OPT_RAMDISK }, \
+ { "dtb", 1, 0, OPT_DTB }, \
+ { "atags", 0, 0, OPT_ATAGS }, \
+ { "image-size", 1, 0, OPT_IMAGE_SIZE }, \
+ { "page-offset", 1, 0, OPT_PAGE_OFFSET },
+
+#define KEXEC_ALL_OPT_STR KEXEC_ARCH_OPT_STR ""
+
+extern unsigned int kexec_arm_image_size;
+
+#endif /* KEXEC_ARCH_ARM_OPTIONS_H */
diff --git a/kexec/arch/arm/iomem.h b/kexec/arch/arm/iomem.h
new file mode 100644
index 0000000..85f958e
--- /dev/null
+++ b/kexec/arch/arm/iomem.h
@@ -0,0 +1,9 @@
+#ifndef IOMEM_H
+#define IOMEM_H
+
+#define SYSTEM_RAM "System RAM\n"
+#define SYSTEM_RAM_BOOT "System RAM (boot alias)\n"
+#define CRASH_KERNEL "Crash kernel\n"
+#define CRASH_KERNEL_BOOT "Crash kernel (boot alias)\n"
+
+#endif
diff --git a/kexec/arch/arm/kexec-arm.c b/kexec/arch/arm/kexec-arm.c
new file mode 100644
index 0000000..49f35b1
--- /dev/null
+++ b/kexec/arch/arm/kexec-arm.c
@@ -0,0 +1,150 @@
+/*
+ * kexec: Linux boots Linux
+ *
+ * modified from kexec-ppc.c
+ *
+ */
+
+#define _GNU_SOURCE
+#include <stddef.h>
+#include <stdio.h>
+#include <errno.h>
+#include <stdint.h>
+#include <string.h>
+#include <getopt.h>
+#include <unistd.h>
+#include "../../kexec.h"
+#include "../../kexec-syscall.h"
+#include "kexec-arm.h"
+#include <arch/options.h>
+#include "../../fs2dt.h"
+#include "iomem.h"
+
+#define MAX_MEMORY_RANGES 64
+#define MAX_LINE 160
+static struct memory_range memory_range[MAX_MEMORY_RANGES];
+
+/* Return a sorted list of available memory ranges. */
+int get_memory_ranges(struct memory_range **range, int *ranges,
+ unsigned long UNUSED(kexec_flags))
+{
+ const char *iomem = proc_iomem();
+ int memory_ranges = 0;
+ char line[MAX_LINE];
+ FILE *fp;
+ fp = fopen(iomem, "r");
+ if (!fp) {
+ fprintf(stderr, "Cannot open %s: %s\n",
+ iomem, strerror(errno));
+ return -1;
+ }
+
+ while(fgets(line, sizeof(line), fp) != 0) {
+ unsigned long long start, end;
+ char *str;
+ int type;
+ int consumed;
+ int count;
+ if (memory_ranges >= MAX_MEMORY_RANGES)
+ break;
+ count = sscanf(line, "%llx-%llx : %n",
+ &start, &end, &consumed);
+ if (count != 2)
+ continue;
+ str = line + consumed;
+
+ if (memcmp(str, SYSTEM_RAM_BOOT, strlen(SYSTEM_RAM_BOOT)) == 0 ||
+ memcmp(str, SYSTEM_RAM, strlen(SYSTEM_RAM)) == 0) {
+ type = RANGE_RAM;
+ }
+ else if (memcmp(str, "reserved\n", 9) == 0) {
+ type = RANGE_RESERVED;
+ }
+ else {
+ continue;
+ }
+
+ memory_range[memory_ranges].start = start;
+ memory_range[memory_ranges].end = end;
+ memory_range[memory_ranges].type = type;
+ memory_ranges++;
+ }
+ fclose(fp);
+ *range = memory_range;
+ *ranges = memory_ranges;
+
+ dbgprint_mem_range("MEMORY RANGES", *range, *ranges);
+
+ return 0;
+}
+
+/* Supported file types and callbacks */
+struct file_type file_type[] = {
+ /* uImage is probed before zImage because the latter also accepts
+ uncompressed images. */
+ {"uImage", uImage_arm_probe, uImage_arm_load, zImage_arm_usage},
+ {"zImage", zImage_arm_probe, zImage_arm_load, zImage_arm_usage},
+};
+int file_types = sizeof(file_type) / sizeof(file_type[0]);
+
+void arch_usage(void)
+{
+ printf(" --image-size=<size>\n"
+ " Specify the assumed total image size of\n"
+ " the kernel that is about to be loaded,\n"
+ " including the .bss section, as reported\n"
+ " by 'arm-linux-size vmlinux'. If not\n"
+ " specified, this value is implicitly set\n"
+ " to the compressed images size * 4.\n"
+ " --dt-no-old-root\n"
+ " do not reuse old kernel root= param.\n"
+ " while creating flatten device tree.\n");
+}
+
+int arch_process_options(int argc, char **argv)
+{
+ /* We look for all options so getopt_long doesn't start reordering
+ * argv[] before file_type[n].load() gets a look in.
+ */
+ static const struct option options[] = {
+ KEXEC_ALL_OPTIONS
+ { 0, 0, NULL, 0 },
+ };
+ static const char short_options[] = KEXEC_ALL_OPT_STR;
+ int opt;
+
+ opterr = 0; /* Don't complain about unrecognized options here */
+ while((opt = getopt_long(argc, argv, short_options, options, 0)) != -1) {
+ switch(opt) {
+ case OPT_DT_NO_OLD_ROOT:
+ dt_no_old_root = 1;
+ break;
+ default:
+ break;
+ }
+ }
+ /* Reset getopt for the next pass; called in other source modules */
+ opterr = 1;
+ optind = 1;
+ return 0;
+}
+
+const struct arch_map_entry arches[] = {
+ { "arm", KEXEC_ARCH_ARM },
+ { NULL, 0 },
+};
+
+int arch_compat_trampoline(struct kexec_info *UNUSED(info))
+{
+ return 0;
+}
+
+void arch_update_purgatory(struct kexec_info *UNUSED(info))
+{
+}
+
+/* return 1 if /sys/firmware/fdt exists, otherwise return 0 */
+int have_sysfs_fdt(void)
+{
+ return !access(SYSFS_FDT, F_OK);
+}
diff --git a/kexec/arch/arm/kexec-arm.h b/kexec/arch/arm/kexec-arm.h
new file mode 100644
index 0000000..a74cce2
--- /dev/null
+++ b/kexec/arch/arm/kexec-arm.h
@@ -0,0 +1,22 @@
+#ifndef KEXEC_ARM_H
+#define KEXEC_ARM_H
+
+#include <sys/types.h>
+
+#define SYSFS_FDT "/sys/firmware/fdt"
+#define BOOT_BLOCK_VERSION 17
+#define BOOT_BLOCK_LAST_COMP_VERSION 16
+
+extern off_t initrd_base, initrd_size;
+
+int zImage_arm_probe(const char *buf, off_t len);
+int zImage_arm_load(int argc, char **argv, const char *buf, off_t len,
+ struct kexec_info *info);
+void zImage_arm_usage(void);
+
+int uImage_arm_probe(const char *buf, off_t len);
+int uImage_arm_load(int argc, char **argv, const char *buf, off_t len,
+ struct kexec_info *info);
+extern int have_sysfs_fdt(void);
+
+#endif /* KEXEC_ARM_H */
diff --git a/kexec/arch/arm/kexec-elf-rel-arm.c b/kexec/arch/arm/kexec-elf-rel-arm.c
new file mode 100644
index 0000000..a939cf4
--- /dev/null
+++ b/kexec/arch/arm/kexec-elf-rel-arm.c
@@ -0,0 +1,36 @@
+#include <stdio.h>
+#include <elf.h>
+#include "../../kexec.h"
+#include "../../kexec-elf.h"
+
+int machine_verify_elf_rel(struct mem_ehdr *ehdr)
+{
+ if (ehdr->ei_data != ELFDATA2MSB) {
+ return 0;
+ }
+ if (ehdr->ei_class != ELFCLASS32) {
+ return 0;
+ }
+ if (ehdr->e_machine != EM_ARM)
+ {
+ return 0;
+ }
+ return 1;
+}
+
+void machine_apply_elf_rel(struct mem_ehdr *UNUSED(ehdr),
+ struct mem_sym *UNUSED(sym), unsigned long r_type, void *location,
+ unsigned long address, unsigned long value)
+{
+ switch(r_type) {
+ case R_ARM_ABS32:
+ *((uint32_t *)location) += value;
+ break;
+ case R_ARM_REL32:
+ *((uint32_t *)location) += value - address;
+ break;
+ default:
+ die("Unknown rel relocation: %lu\n", r_type);
+ break;
+ }
+}
diff --git a/kexec/arch/arm/kexec-uImage-arm.c b/kexec/arch/arm/kexec-uImage-arm.c
new file mode 100644
index 0000000..03c2f4d
--- /dev/null
+++ b/kexec/arch/arm/kexec-uImage-arm.c
@@ -0,0 +1,22 @@
+/*
+ * uImage support added by Marc Andre Tanner <mat@brain-dump.org>
+ */
+#include <stdint.h>
+#include <string.h>
+#include <sys/types.h>
+#include <image.h>
+#include <kexec-uImage.h>
+#include "../../kexec.h"
+#include "kexec-arm.h"
+
+int uImage_arm_probe(const char *buf, off_t len)
+{
+ return uImage_probe_kernel(buf, len, IH_ARCH_ARM);
+}
+
+int uImage_arm_load(int argc, char **argv, const char *buf, off_t len,
+ struct kexec_info *info)
+{
+ return zImage_arm_load(argc, argv, buf + sizeof(struct image_header),
+ len - sizeof(struct image_header), info);
+}
diff --git a/kexec/arch/arm/kexec-zImage-arm.c b/kexec/arch/arm/kexec-zImage-arm.c
new file mode 100644
index 0000000..8b474dd
--- /dev/null
+++ b/kexec/arch/arm/kexec-zImage-arm.c
@@ -0,0 +1,914 @@
+/*
+ * - 08/21/2007 ATAG support added by Uli Luckas <u.luckas@road.de>
+ *
+ */
+#define _GNU_SOURCE
+#define _XOPEN_SOURCE
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <unistd.h>
+#include <libfdt.h>
+#include <arch/options.h>
+#include "../../kexec.h"
+#include "../../kexec-syscall.h"
+#include "kexec-arm.h"
+#include "../../fs2dt.h"
+#include "crashdump-arm.h"
+#include "iomem.h"
+
+#define BOOT_PARAMS_SIZE 1536
+
+off_t initrd_base, initrd_size;
+unsigned int kexec_arm_image_size = 0;
+unsigned long long user_page_offset = (-1ULL);
+
+struct zimage_header {
+ uint32_t instr[9];
+ uint32_t magic;
+#define ZIMAGE_MAGIC cpu_to_le32(0x016f2818)
+ uint32_t start;
+ uint32_t end;
+ uint32_t endian;
+
+ /* Extension to the data passed to the boot agent. The offset
+ * points at a tagged table following a similar format to the
+ * ATAGs.
+ */
+ uint32_t magic2;
+#define ZIMAGE_MAGIC2 (0x45454545)
+ uint32_t extension_tag_offset;
+};
+
+struct android_image {
+ char magic[8];
+ uint32_t kernel_size;
+ uint32_t kernel_addr;
+ uint32_t ramdisk_size;
+ uint32_t ramdisk_addr;
+ uint32_t stage2_size;
+ uint32_t stage2_addr;
+ uint32_t tags_addr;
+ uint32_t page_size;
+ uint32_t reserved1;
+ uint32_t reserved2;
+ char name[16];
+ char command_line[512];
+ uint32_t chksum[8];
+};
+
+struct tag_header {
+ uint32_t size;
+ uint32_t tag;
+};
+
+/* The list must start with an ATAG_CORE node */
+#define ATAG_CORE 0x54410001
+
+struct tag_core {
+ uint32_t flags; /* bit 0 = read-only */
+ uint32_t pagesize;
+ uint32_t rootdev;
+};
+
+/* it is allowed to have multiple ATAG_MEM nodes */
+#define ATAG_MEM 0x54410002
+
+struct tag_mem32 {
+ uint32_t size;
+ uint32_t start; /* physical start address */
+};
+
+/* describes where the compressed ramdisk image lives (virtual address) */
+/*
+ * this one accidentally used virtual addresses - as such,
+ * it's deprecated.
+ */
+#define ATAG_INITRD 0x54410005
+
+/* describes where the compressed ramdisk image lives (physical address) */
+#define ATAG_INITRD2 0x54420005
+
+struct tag_initrd {
+ uint32_t start; /* physical start address */
+ uint32_t size; /* size of compressed ramdisk image in bytes */
+};
+
+/* command line: \0 terminated string */
+#define ATAG_CMDLINE 0x54410009
+
+struct tag_cmdline {
+ char cmdline[1]; /* this is the minimum size */
+};
+
+/* The list ends with an ATAG_NONE node. */
+#define ATAG_NONE 0x00000000
+
+struct tag {
+ struct tag_header hdr;
+ union {
+ struct tag_core core;
+ struct tag_mem32 mem;
+ struct tag_initrd initrd;
+ struct tag_cmdline cmdline;
+ } u;
+};
+
+#define tag_next(t) ((struct tag *)((uint32_t *)(t) + (t)->hdr.size))
+#define byte_size(t) ((t)->hdr.size << 2)
+#define tag_size(type) ((sizeof(struct tag_header) + sizeof(struct type) + 3) >> 2)
+
+struct zimage_tag {
+ struct tag_header hdr;
+ union {
+#define ZIMAGE_TAG_KRNL_SIZE cpu_to_le32(0x5a534c4b)
+ struct zimage_krnl_size {
+ uint32_t size_ptr;
+ uint32_t bss_size;
+ } krnl_size;
+ } u;
+};
+
+int zImage_arm_probe(const char *UNUSED(buf), off_t UNUSED(len))
+{
+ /*
+ * Only zImage loading is supported. Do not check if
+ * the buffer is valid kernel image
+ */
+ return 0;
+}
+
+void zImage_arm_usage(void)
+{
+ printf( " --command-line=STRING Set the kernel command line to STRING.\n"
+ " --append=STRING Set the kernel command line to STRING.\n"
+ " --initrd=FILE Use FILE as the kernel's initial ramdisk.\n"
+ " --ramdisk=FILE Use FILE as the kernel's initial ramdisk.\n"
+ " --dtb=FILE Use FILE as the fdt blob.\n"
+ " --atags Use ATAGs instead of device-tree.\n"
+ " --page-offset=PAGE_OFFSET\n"
+ " Set PAGE_OFFSET of crash dump vmcore\n"
+ );
+}
+
+static
+struct tag * atag_read_tags(void)
+{
+ static unsigned long buf[BOOT_PARAMS_SIZE];
+ const char fn[]= "/proc/atags";
+ FILE *fp;
+ fp = fopen(fn, "r");
+ if (!fp) {
+ fprintf(stderr, "Cannot open %s: %s\n",
+ fn, strerror(errno));
+ return NULL;
+ }
+
+ if (!fread(buf, sizeof(buf[1]), BOOT_PARAMS_SIZE, fp)) {
+ fclose(fp);
+ return NULL;
+ }
+
+ if (ferror(fp)) {
+ fprintf(stderr, "Cannot read %s: %s\n",
+ fn, strerror(errno));
+ fclose(fp);
+ return NULL;
+ }
+
+ fclose(fp);
+ return (struct tag *) buf;
+}
+
+static
+int create_mem32_tag(struct tag_mem32 *tag_mem32)
+{
+ const char fn[]= "/proc/device-tree/memory/reg";
+ uint32_t tmp[2];
+ FILE *fp;
+
+ fp = fopen(fn, "r");
+ if (!fp) {
+ fprintf(stderr, "Cannot open %s: %m\n", fn);
+ return -1;
+ }
+
+ if (fread(tmp, sizeof(tmp[0]), 2, fp) != 2) {
+ fprintf(stderr, "Short read %s\n", fn);
+ fclose(fp);
+ return -1;
+ }
+
+ if (ferror(fp)) {
+ fprintf(stderr, "Cannot read %s: %m\n", fn);
+ fclose(fp);
+ return -1;
+ }
+
+ /* atags_mem32 has base/size fields reversed! */
+ tag_mem32->size = be32_to_cpu(tmp[1]);
+ tag_mem32->start = be32_to_cpu(tmp[0]);
+
+ fclose(fp);
+ return 0;
+}
+
+static
+int atag_arm_load(struct kexec_info *info, unsigned long base,
+ const char *command_line, off_t command_line_len, const char *initrd)
+{
+ struct tag *saved_tags = atag_read_tags();
+ char *buf;
+ off_t len;
+ struct tag *params;
+
+ buf = xmalloc(getpagesize());
+ memset(buf, 0xff, getpagesize());
+ params = (struct tag *)buf;
+
+ if (saved_tags) {
+ // Copy tags
+ saved_tags = (struct tag *) saved_tags;
+ while(byte_size(saved_tags)) {
+ switch (saved_tags->hdr.tag) {
+ case ATAG_INITRD:
+ case ATAG_INITRD2:
+ case ATAG_CMDLINE:
+ case ATAG_NONE:
+ // skip these tags
+ break;
+ default:
+ // copy all other tags
+ memcpy(params, saved_tags, byte_size(saved_tags));
+ params = tag_next(params);
+ }
+ saved_tags = tag_next(saved_tags);
+ }
+ } else {
+ params->hdr.size = 2;
+ params->hdr.tag = ATAG_CORE;
+ params = tag_next(params);
+
+ if (!create_mem32_tag(&params->u.mem)) {
+ params->hdr.size = 4;
+ params->hdr.tag = ATAG_MEM;
+ params = tag_next(params);
+ }
+ }
+
+ if (initrd) {
+ params->hdr.size = tag_size(tag_initrd);
+ params->hdr.tag = ATAG_INITRD2;
+ params->u.initrd.start = initrd_base;
+ params->u.initrd.size = initrd_size;
+ params = tag_next(params);
+ }
+
+ if (command_line) {
+ params->hdr.size = (sizeof(struct tag_header) + command_line_len + 3) >> 2;
+ params->hdr.tag = ATAG_CMDLINE;
+ memcpy(params->u.cmdline.cmdline, command_line,
+ command_line_len);
+ params->u.cmdline.cmdline[command_line_len - 1] = '\0';
+ params = tag_next(params);
+ }
+
+ params->hdr.size = 0;
+ params->hdr.tag = ATAG_NONE;
+
+ len = ((char *)params - buf) + sizeof(struct tag_header);
+
+ add_segment(info, buf, len, base, len);
+
+ return 0;
+}
+
+static int setup_dtb_prop(char **bufp, off_t *sizep, int parentoffset,
+ const char *node_name, const char *prop_name,
+ const void *val, int len)
+{
+ char *dtb_buf;
+ off_t dtb_size;
+ int off;
+ int prop_len = 0;
+ const struct fdt_property *prop;
+
+ if ((bufp == NULL) || (sizep == NULL) || (*bufp == NULL))
+ die("Internal error\n");
+
+ dtb_buf = *bufp;
+ dtb_size = *sizep;
+
+ /* check if the subnode has already exist */
+ off = fdt_subnode_offset(dtb_buf, parentoffset, node_name);
+ if (off == -FDT_ERR_NOTFOUND) {
+ dtb_size += fdt_node_len(node_name);
+ fdt_set_totalsize(dtb_buf, dtb_size);
+ dtb_buf = xrealloc(dtb_buf, dtb_size);
+ off = fdt_add_subnode(dtb_buf, parentoffset, node_name);
+ }
+
+ if (off < 0) {
+ fprintf(stderr, "FDT: Error adding %s node.\n", node_name);
+ return -1;
+ }
+
+ prop = fdt_get_property(dtb_buf, off, prop_name, &prop_len);
+ if ((prop == NULL) && (prop_len != -FDT_ERR_NOTFOUND)) {
+ die("FDT: fdt_get_property");
+ } else if (prop == NULL) {
+ /* prop_len == -FDT_ERR_NOTFOUND */
+ /* prop doesn't exist */
+ dtb_size += fdt_prop_len(prop_name, len);
+ } else {
+ if (prop_len < len)
+ dtb_size += FDT_TAGALIGN(len - prop_len);
+ }
+
+ if (fdt_totalsize(dtb_buf) < dtb_size) {
+ fdt_set_totalsize(dtb_buf, dtb_size);
+ dtb_buf = xrealloc(dtb_buf, dtb_size);
+ }
+
+ if (fdt_setprop(dtb_buf, off, prop_name,
+ val, len) != 0) {
+ fprintf(stderr, "FDT: Error setting %s/%s property.\n",
+ node_name, prop_name);
+ return -1;
+ }
+ *bufp = dtb_buf;
+ *sizep = dtb_size;
+ return 0;
+}
+
+static const struct zimage_tag *find_extension_tag(const char *buf, off_t len,
+ uint32_t tag_id)
+{
+ const struct zimage_header *hdr = (const struct zimage_header *)buf;
+ const struct zimage_tag *tag;
+ uint32_t offset, size;
+ uint32_t max = len - sizeof(struct tag_header);
+
+ if (len < sizeof(*hdr) ||
+ hdr->magic != ZIMAGE_MAGIC ||
+ hdr->magic2 != ZIMAGE_MAGIC2)
+ return NULL;
+
+ for (offset = hdr->extension_tag_offset;
+ (tag = (void *)(buf + offset)) != NULL &&
+ offset < max &&
+ (size = le32_to_cpu(byte_size(tag))) != 0 &&
+ offset + size < len;
+ offset += size) {
+ dbgprintf(" offset 0x%08x tag 0x%08x size %u\n",
+ offset, le32_to_cpu(tag->hdr.tag), size);
+ if (tag->hdr.tag == tag_id)
+ return tag;
+ }
+
+ return NULL;
+}
+
+static int get_cells_size(void *fdt, uint32_t *address_cells,
+ uint32_t *size_cells)
+{
+ int nodeoffset;
+ const uint32_t *prop = NULL;
+ int prop_len;
+
+ /* default values */
+ *address_cells = 1;
+ *size_cells = 1;
+
+ /* under root node */
+ nodeoffset = fdt_path_offset(fdt, "/");
+ if (nodeoffset < 0)
+ return -1;
+
+ prop = fdt_getprop(fdt, nodeoffset, "#address-cells", &prop_len);
+ if (prop) {
+ if (prop_len != sizeof(*prop))
+ return -1;
+
+ *address_cells = fdt32_to_cpu(*prop);
+ }
+
+ prop = fdt_getprop(fdt, nodeoffset, "#size-cells", &prop_len);
+ if (prop) {
+ if (prop_len != sizeof(*prop))
+ return -1;
+
+ *size_cells = fdt32_to_cpu(*prop);
+ }
+
+ dbgprintf("%s: #address-cells:%d #size-cells:%d\n", __func__,
+ *address_cells, *size_cells);
+ return 0;
+}
+
+static bool cells_size_fitted(uint32_t address_cells, uint32_t size_cells,
+ struct memory_range *range)
+{
+ dbgprintf("%s: %llx-%llx\n", __func__, range->start, range->end);
+
+ /* if *_cells >= 2, cells can hold 64-bit values anyway */
+ if ((address_cells == 1) && (range->start >= (1ULL << 32)))
+ return false;
+
+ if ((size_cells == 1) &&
+ ((range->end - range->start + 1) >= (1ULL << 32)))
+ return false;
+
+ return true;
+}
+
+static void fill_property(void *buf, uint64_t val, uint32_t cells)
+{
+ uint32_t val32;
+ int i;
+
+ if (cells == 1) {
+ val32 = cpu_to_fdt32((uint32_t)val);
+ memcpy(buf, &val32, sizeof(uint32_t));
+ } else {
+ for (i = 0;
+ i < (cells * sizeof(uint32_t) - sizeof(uint64_t)); i++)
+ *(char *)buf++ = 0;
+
+ val = cpu_to_fdt64(val);
+ memcpy(buf, &val, sizeof(uint64_t));
+ }
+}
+
+static int setup_dtb_prop_range(char **bufp, off_t *sizep, int parentoffset,
+ const char *node_name, const char *prop_name,
+ struct memory_range *range,
+ uint32_t address_cells, uint32_t size_cells)
+{
+ void *buf, *prop;
+ size_t buf_size;
+ int result;
+
+ buf_size = (address_cells + size_cells) * sizeof(uint32_t);
+ prop = buf = xmalloc(buf_size);
+
+ fill_property(prop, range->start, address_cells);
+ prop += address_cells * sizeof(uint32_t);
+
+ fill_property(prop, range->end - range->start + 1, size_cells);
+ prop += size_cells * sizeof(uint32_t);
+
+ result = setup_dtb_prop(bufp, sizep, parentoffset, node_name,
+ prop_name, buf, buf_size);
+
+ free(buf);
+
+ return result;
+}
+
+int zImage_arm_load(int argc, char **argv, const char *buf, off_t len,
+ struct kexec_info *info)
+{
+ unsigned long page_size = getpagesize();
+ unsigned long base, kernel_base;
+ unsigned int atag_offset = 0x1000; /* 4k offset from memory start */
+ unsigned int extra_size = 0x8000; /* TEXT_OFFSET */
+ uint32_t address_cells, size_cells;
+ const struct zimage_tag *tag;
+ size_t kernel_buf_size;
+ size_t kernel_mem_size;
+ const char *command_line;
+ char *modified_cmdline = NULL;
+ off_t command_line_len;
+ const char *ramdisk;
+ const char *ramdisk_buf;
+ int opt;
+ int use_atags;
+ int result;
+ char *dtb_buf;
+ off_t dtb_length;
+ char *dtb_file;
+ off_t dtb_offset;
+ char *end;
+
+ /* See options.h -- add any more there, too. */
+ static const struct option options[] = {
+ KEXEC_ARCH_OPTIONS
+ { "command-line", 1, 0, OPT_APPEND },
+ { "append", 1, 0, OPT_APPEND },
+ { "initrd", 1, 0, OPT_RAMDISK },
+ { "ramdisk", 1, 0, OPT_RAMDISK },
+ { "dtb", 1, 0, OPT_DTB },
+ { "atags", 0, 0, OPT_ATAGS },
+ { "image-size", 1, 0, OPT_IMAGE_SIZE },
+ { "page-offset", 1, 0, OPT_PAGE_OFFSET },
+ { 0, 0, 0, 0 },
+ };
+ static const char short_options[] = KEXEC_ARCH_OPT_STR "";
+
+ /*
+ * Parse the command line arguments
+ */
+ command_line = 0;
+ command_line_len = 0;
+ ramdisk = 0;
+ ramdisk_buf = 0;
+ initrd_size = 0;
+ use_atags = 0;
+ dtb_file = NULL;
+ while((opt = getopt_long(argc, argv, short_options, options, 0)) != -1) {
+ switch(opt) {
+ default:
+ /* Ignore core options */
+ if (opt < OPT_ARCH_MAX) {
+ break;
+ }
+ case OPT_APPEND:
+ command_line = optarg;
+ break;
+ case OPT_RAMDISK:
+ ramdisk = optarg;
+ break;
+ case OPT_DTB:
+ dtb_file = optarg;
+ break;
+ case OPT_ATAGS:
+ use_atags = 1;
+ break;
+ case OPT_IMAGE_SIZE:
+ kexec_arm_image_size = strtoul(optarg, &end, 0);
+ break;
+ case OPT_PAGE_OFFSET:
+ user_page_offset = strtoull(optarg, &end, 0);
+ break;
+ }
+ }
+
+ if (use_atags && dtb_file) {
+ fprintf(stderr, "You can only use ATAGs if you don't specify a "
+ "dtb file.\n");
+ return -1;
+ }
+
+ if (!use_atags && !dtb_file) {
+ int f;
+
+ f = have_sysfs_fdt();
+ if (f)
+ dtb_file = SYSFS_FDT;
+ }
+
+ if (command_line) {
+ command_line_len = strlen(command_line) + 1;
+ if (command_line_len > COMMAND_LINE_SIZE)
+ command_line_len = COMMAND_LINE_SIZE;
+ }
+ if (ramdisk)
+ ramdisk_buf = slurp_file_mmap(ramdisk, &initrd_size);
+
+ if (dtb_file)
+ dtb_buf = slurp_file(dtb_file, &dtb_length);
+
+ if (len > sizeof(struct zimage_header)) {
+ const struct zimage_header *hdr;
+ off_t size;
+
+ hdr = (const struct zimage_header *)buf;
+
+ dbgprintf("zImage header: 0x%08x 0x%08x 0x%08x\n",
+ hdr->magic, hdr->start, hdr->end);
+
+ if (hdr->magic == ZIMAGE_MAGIC) {
+ size = le32_to_cpu(hdr->end) - le32_to_cpu(hdr->start);
+
+ dbgprintf("zImage size 0x%llx, file size 0x%llx\n",
+ (unsigned long long)size,
+ (unsigned long long)len);
+
+ if (size > len) {
+ fprintf(stderr,
+ "zImage is truncated - file 0x%llx vs header 0x%llx\n",
+ (unsigned long long)len,
+ (unsigned long long)size);
+ return -1;
+ }
+ if (size < len)
+ len = size;
+ }
+ }
+
+ /* Handle android images, 2048 is the minimum page size */
+ if (len > 2048 && !strncmp(buf, "ANDROID!", 8)) {
+ const struct android_image *aimg = (const void *)buf;
+ uint32_t page_size = le32_to_cpu(aimg->page_size);
+ uint32_t kernel_size = le32_to_cpu(aimg->kernel_size);
+ uint32_t ramdisk_size = le32_to_cpu(aimg->ramdisk_size);
+ uint32_t stage2_size = le32_to_cpu(aimg->stage2_size);
+ off_t aimg_size = page_size + _ALIGN(kernel_size, page_size) +
+ _ALIGN(ramdisk_size, page_size) + stage2_size;
+
+ if (len < aimg_size) {
+ fprintf(stderr, "Android image size is incorrect\n");
+ return -1;
+ }
+
+ /* Get the kernel */
+ buf = buf + page_size;
+ len = kernel_size;
+
+ /* And the ramdisk if none was given on the command line */
+ if (!ramdisk && ramdisk_size) {
+ initrd_size = ramdisk_size;
+ ramdisk_buf = buf + _ALIGN(kernel_size, page_size);
+ }
+
+ /* Likewise for the command line */
+ if (!command_line && aimg->command_line[0]) {
+ command_line = aimg->command_line;
+ if (command_line[sizeof(aimg->command_line) - 1])
+ command_line_len = sizeof(aimg->command_line);
+ else
+ command_line_len = strlen(command_line) + 1;
+ }
+ }
+
+ /*
+ * Save the length of the compressed kernel image w/o the appended DTB.
+ * This will be required later on when the kernel image contained
+ * in the zImage will be loaded into a kernel memory segment.
+ * And we want to load ONLY the compressed kernel image from the zImage
+ * and discard the appended DTB.
+ */
+ kernel_buf_size = len;
+
+ /*
+ * Always extend the zImage by four bytes to ensure that an appended
+ * DTB image always sees an initialised value after _edata.
+ */
+ kernel_mem_size = len + 4;
+
+ /*
+ * Check for a kernel size extension, and set or validate the
+ * image size. This is the total space needed to avoid the
+ * boot kernel BSS, so other data (such as initrd) does not get
+ * overwritten.
+ */
+ tag = find_extension_tag(buf, len, ZIMAGE_TAG_KRNL_SIZE);
+
+ /*
+ * The zImage length does not include its stack (4k) or its
+ * malloc space (64k). Include this.
+ */
+ len += 0x11000;
+
+ dbgprintf("zImage requires 0x%08llx bytes\n", (unsigned long long)len);
+
+ if (tag) {
+ uint32_t *p = (void *)buf + le32_to_cpu(tag->u.krnl_size.size_ptr);
+ uint32_t edata_size = le32_to_cpu(get_unaligned(p));
+ uint32_t bss_size = le32_to_cpu(tag->u.krnl_size.bss_size);
+ uint32_t kernel_size = edata_size + bss_size;
+
+ dbgprintf("Decompressed kernel sizes:\n");
+ dbgprintf(" text+data 0x%08lx bss 0x%08lx total 0x%08lx\n",
+ (unsigned long)edata_size,
+ (unsigned long)bss_size,
+ (unsigned long)kernel_size);
+
+ /*
+ * While decompressing, the zImage is placed past _edata
+ * of the decompressed kernel. Ensure we account for that.
+ */
+ if (kernel_size < edata_size + len)
+ kernel_size = edata_size + len;
+
+ dbgprintf("Resulting kernel space: 0x%08lx\n",
+ (unsigned long)kernel_size);
+
+ if (kexec_arm_image_size == 0)
+ kexec_arm_image_size = kernel_size;
+ else if (kexec_arm_image_size < kernel_size) {
+ fprintf(stderr,
+ "Kernel size is too small, increasing to 0x%lx\n",
+ (unsigned long)kernel_size);
+ kexec_arm_image_size = kernel_size;
+ }
+ }
+
+ /*
+ * If the user didn't specify the size of the image, and we don't
+ * have the extension tables, assume the maximum kernel compression
+ * ratio is 4. Note that we must include space for the compressed
+ * image here as well.
+ */
+ if (!kexec_arm_image_size)
+ kexec_arm_image_size = len * 5;
+
+ /*
+ * 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);
+ memset(modified_cmdline, '\0', COMMAND_LINE_SIZE);
+
+ 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_BOOT, &start, &end) &&
+ parse_iomem_single(CRASH_KERNEL, &start, &end)) {
+ /*
+ * No crash kernel memory reserved. We cannot do more
+ * but just bail out.
+ */
+ return ENOCRASHKERNEL;
+ }
+ base = start;
+ } else {
+ base = locate_hole(info, len + extra_size, 0, 0,
+ ULONG_MAX, INT_MAX);
+ }
+
+ if (base == ULONG_MAX)
+ return -1;
+
+ kernel_base = base + extra_size;
+
+ /*
+ * Calculate the minimum address of the initrd, which must be
+ * above the memory used by the zImage while it runs. This
+ * needs to be page-size aligned.
+ */
+ initrd_base = kernel_base + _ALIGN(kexec_arm_image_size, page_size);
+
+ dbgprintf("%-6s: address=0x%08lx size=0x%08lx\n", "Kernel",
+ (unsigned long)kernel_base,
+ (unsigned long)kexec_arm_image_size);
+
+ if (ramdisk_buf) {
+ /*
+ * Find a hole to place the initrd. The crash kernel use
+ * fixed address, so no check is ok.
+ */
+ if (!(info->kexec_flags & KEXEC_ON_CRASH)) {
+ initrd_base = locate_hole(info, initrd_size, page_size,
+ initrd_base,
+ ULONG_MAX, INT_MAX);
+ if (initrd_base == ULONG_MAX)
+ return -1;
+ }
+
+ dbgprintf("%-6s: address=0x%08lx size=0x%08lx\n", "Initrd",
+ (unsigned long)initrd_base,
+ (unsigned long)initrd_size);
+
+ add_segment(info, ramdisk_buf, initrd_size, initrd_base,
+ initrd_size);
+ }
+
+ if (use_atags) {
+ /*
+ * use ATAGs from /proc/atags
+ */
+ if (atag_arm_load(info, base + atag_offset,
+ command_line, command_line_len,
+ ramdisk_buf) == -1)
+ return -1;
+ } else {
+ /*
+ * Read a user-specified DTB file.
+ */
+ if (dtb_file) {
+ if (fdt_check_header(dtb_buf) != 0) {
+ fprintf(stderr, "Invalid FDT buffer.\n");
+ return -1;
+ }
+
+ if (command_line) {
+ /*
+ * Error should have been reported so
+ * directly return -1
+ */
+ if (setup_dtb_prop(&dtb_buf, &dtb_length, 0, "chosen",
+ "bootargs", command_line,
+ strlen(command_line) + 1))
+ return -1;
+ }
+ } else {
+ /*
+ * Extract the DTB from /proc/device-tree.
+ */
+ create_flatten_tree(&dtb_buf, &dtb_length, command_line);
+ }
+
+ /*
+ * Add the initrd parameters to the dtb
+ */
+ if (ramdisk_buf) {
+ unsigned long start, end;
+
+ start = cpu_to_be32((unsigned long)(initrd_base));
+ end = cpu_to_be32((unsigned long)(initrd_base + initrd_size));
+
+ if (setup_dtb_prop(&dtb_buf, &dtb_length, 0, "chosen",
+ "linux,initrd-start", &start,
+ sizeof(start)))
+ return -1;
+ if (setup_dtb_prop(&dtb_buf, &dtb_length, 0, "chosen",
+ "linux,initrd-end", &end,
+ sizeof(end)))
+ return -1;
+ }
+
+ if (info->kexec_flags & KEXEC_ON_CRASH) {
+ /* Determine #address-cells and #size-cells */
+ result = get_cells_size(dtb_buf, &address_cells,
+ &size_cells);
+ if (result) {
+ fprintf(stderr, "Cannot determine cells-size.\n");
+ return -1;
+ }
+
+ if (!cells_size_fitted(address_cells, size_cells,
+ &elfcorehdr_mem)) {
+ fprintf(stderr, "elfcorehdr doesn't fit cells-size.\n");
+ return -1;
+ }
+
+ if (!cells_size_fitted(address_cells, size_cells,
+ &crash_kernel_mem)) {
+ fprintf(stderr, "kexec: usable memory range doesn't fit cells-size.\n");
+ return -1;
+ }
+
+ /* Add linux,elfcorehdr */
+ if (setup_dtb_prop_range(&dtb_buf, &dtb_length, 0,
+ "chosen", "linux,elfcorehdr",
+ &elfcorehdr_mem,
+ address_cells, size_cells))
+ return -1;
+
+ /* Add linux,usable-memory-range */
+ if (setup_dtb_prop_range(&dtb_buf, &dtb_length, 0,
+ "chosen",
+ "linux,usable-memory-range",
+ &crash_kernel_mem,
+ address_cells, size_cells))
+ return -1;
+ }
+
+ /*
+ * The dtb must also be placed above the memory used by
+ * the zImage. We don't care about its position wrt the
+ * ramdisk, but we might as well place it after the initrd.
+ * We leave a buffer page between the initrd and the dtb.
+ */
+ dtb_offset = initrd_base + initrd_size + page_size;
+ dtb_offset = _ALIGN_DOWN(dtb_offset, page_size);
+
+ /*
+ * Find a hole to place the dtb above the initrd.
+ * Crash kernel use fixed address, no check is ok.
+ */
+ if (!(info->kexec_flags & KEXEC_ON_CRASH)) {
+ dtb_offset = locate_hole(info, dtb_length, page_size,
+ dtb_offset, ULONG_MAX, INT_MAX);
+ if (dtb_offset == ULONG_MAX)
+ return -1;
+ }
+
+ dbgprintf("%-6s: address=0x%08lx size=0x%08lx\n", "DT",
+ (unsigned long)dtb_offset, (unsigned long)dtb_length);
+
+ add_segment(info, dtb_buf, dtb_length, dtb_offset, dtb_length);
+ }
+
+ add_segment(info, buf, kernel_buf_size, kernel_base, kernel_mem_size);
+
+ info->entry = (void*)kernel_base;
+
+ return 0;
+}
diff --git a/kexec/arch/arm/phys_to_virt.c b/kexec/arch/arm/phys_to_virt.c
new file mode 100644
index 0000000..46a4f68
--- /dev/null
+++ b/kexec/arch/arm/phys_to_virt.c
@@ -0,0 +1,22 @@
+#include "../../kexec.h"
+#include "../../crashdump.h"
+#include "phys_to_virt.h"
+
+uint64_t phys_offset;
+
+/**
+ * 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 long paddr)
+{
+ return paddr + elf_info->page_offset - phys_offset;
+}
diff --git a/kexec/arch/arm/phys_to_virt.h b/kexec/arch/arm/phys_to_virt.h
new file mode 100644
index 0000000..b3147dd
--- /dev/null
+++ b/kexec/arch/arm/phys_to_virt.h
@@ -0,0 +1,8 @@
+#ifndef PHYS_TO_VIRT_H
+#define PHYS_TO_VIRT_H
+
+#include <stdint.h>
+
+extern uint64_t phys_offset;
+
+#endif
diff --git a/kexec/arch/arm64/Makefile b/kexec/arch/arm64/Makefile
new file mode 100644
index 0000000..59212f1
--- /dev/null
+++ b/kexec/arch/arm64/Makefile
@@ -0,0 +1,50 @@
+
+arm64_FS2DT += kexec/fs2dt.c
+arm64_FS2DT_INCLUDE += \
+ -include $(srcdir)/kexec/arch/arm64/crashdump-arm64.h \
+ -include $(srcdir)/kexec/arch/arm64/kexec-arm64.h
+
+arm64_DT_OPS += kexec/dt-ops.c
+
+arm64_MEM_REGIONS = kexec/mem_regions.c
+
+arm64_CPPFLAGS += -I $(srcdir)/kexec/
+
+arm64_KEXEC_SRCS += \
+ kexec/arch/arm64/crashdump-arm64.c \
+ kexec/arch/arm64/kexec-arm64.c \
+ kexec/arch/arm64/kexec-elf-arm64.c \
+ kexec/arch/arm64/kexec-uImage-arm64.c \
+ kexec/arch/arm64/kexec-image-arm64.c \
+ kexec/arch/arm64/kexec-vmlinuz-arm64.c
+
+arm64_UIMAGE = kexec/kexec-uImage.c
+
+arm64_ARCH_REUSE_INITRD =
+arm64_ADD_SEGMENT =
+arm64_VIRT_TO_PHYS =
+arm64_PHYS_TO_VIRT =
+
+dist += $(arm64_KEXEC_SRCS) \
+ kexec/arch/arm64/include/arch/options.h \
+ kexec/arch/arm64/crashdump-arm64.h \
+ kexec/arch/arm64/image-header.h \
+ kexec/arch/arm64/iomem.h \
+ kexec/arch/arm64/kexec-arm64.h \
+ kexec/arch/arm64/Makefile
+
+ifdef HAVE_LIBFDT
+
+LIBS += -lfdt
+
+else
+
+include $(srcdir)/kexec/libfdt/Makefile.libfdt
+
+libfdt_SRCS += $(LIBFDT_SRCS:%=kexec/libfdt/%)
+
+arm64_CPPFLAGS += -I$(srcdir)/kexec/libfdt
+
+arm64_KEXEC_SRCS += $(libfdt_SRCS)
+
+endif
diff --git a/kexec/arch/arm64/crashdump-arm64.c b/kexec/arch/arm64/crashdump-arm64.c
new file mode 100644
index 0000000..3098315
--- /dev/null
+++ b/kexec/arch/arm64/crashdump-arm64.c
@@ -0,0 +1,248 @@
+/*
+ * ARM64 crashdump.
+ * partly derived from arm implementation
+ *
+ * Copyright (c) 2014-2017 Linaro Limited
+ * Author: AKASHI Takahiro <takahiro.akashi@linaro.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <linux/elf.h>
+
+#include "kexec.h"
+#include "crashdump.h"
+#include "crashdump-arm64.h"
+#include "iomem.h"
+#include "kexec-arm64.h"
+#include "kexec-elf.h"
+#include "mem_regions.h"
+
+/* memory ranges of crashed kernel */
+static struct memory_ranges system_memory_rgns;
+
+/* memory range reserved for crashkernel */
+struct memory_range crash_reserved_mem[CRASH_MAX_RESERVED_RANGES];
+struct memory_ranges usablemem_rgns = {
+ .size = 0,
+ .max_size = CRASH_MAX_RESERVED_RANGES,
+ .ranges = crash_reserved_mem,
+};
+
+struct memory_range elfcorehdr_mem;
+
+static struct crash_elf_info elf_info = {
+ .class = ELFCLASS64,
+#if (__BYTE_ORDER == __LITTLE_ENDIAN)
+ .data = ELFDATA2LSB,
+#else
+ .data = ELFDATA2MSB,
+#endif
+ .machine = EM_AARCH64,
+};
+
+/*
+ * iomem_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, respectively, system_memory_rgns and usablemem_rgns.
+ */
+
+static int iomem_range_callback(void *UNUSED(data), int UNUSED(nr),
+ char *str, unsigned long long base,
+ unsigned long long length)
+{
+ if (strncmp(str, CRASH_KERNEL, strlen(CRASH_KERNEL)) == 0)
+ return mem_regions_alloc_and_add(&usablemem_rgns,
+ base, length, RANGE_RAM);
+ else if (strncmp(str, SYSTEM_RAM, strlen(SYSTEM_RAM)) == 0)
+ return mem_regions_alloc_and_add(&system_memory_rgns,
+ base, length, RANGE_RAM);
+ else if (strncmp(str, KERNEL_CODE, strlen(KERNEL_CODE)) == 0) {
+
+ unsigned long long kva_text = get_kernel_sym("_text");
+ unsigned long long kva_stext = get_kernel_sym("_stext");
+ unsigned long long kva_text_end = get_kernel_sym("__init_begin");
+
+ /*
+ * old: kernel_code.start = __pa_symbol(_text);
+ * new: kernel_code.start = __pa_symbol(_stext);
+ *
+ * For compatibility, deduce by comparing the gap "__init_begin - _stext"
+ * and the res size of "Kernel code" in /proc/iomem
+ */
+ if (kva_text_end - kva_stext == length)
+ elf_info.kern_paddr_start = base - (kva_stext - kva_text);
+ else
+ elf_info.kern_paddr_start = base;
+ }
+ else if (strncmp(str, KERNEL_DATA, strlen(KERNEL_DATA)) == 0)
+ elf_info.kern_size = base + length - elf_info.kern_paddr_start;
+
+ return 0;
+}
+
+int is_crashkernel_mem_reserved(void)
+{
+ if (!usablemem_rgns.size)
+ kexec_iomem_for_each_line(NULL, iomem_range_callback, NULL);
+
+ return usablemem_rgns.size;
+}
+
+/*
+ * crash_get_memory_ranges() - read system physical memory
+ *
+ * Function reads through system physical memory and stores found memory
+ * regions in system_memory_ranges.
+ * Regions are sorted in ascending order.
+ *
+ * Returns 0 in case of success and a negative value otherwise.
+ */
+static int crash_get_memory_ranges(void)
+{
+ int i;
+
+ /*
+ * First read all memory regions that can be considered as
+ * system memory including the crash area.
+ */
+ if (!usablemem_rgns.size)
+ kexec_iomem_for_each_line(NULL, iomem_range_callback, NULL);
+
+ /* allow one or two regions for crash dump kernel */
+ if (!usablemem_rgns.size)
+ return -EINVAL;
+
+ dbgprint_mem_range("Reserved memory range",
+ usablemem_rgns.ranges, usablemem_rgns.size);
+
+ for (i = 0; i < usablemem_rgns.size; i++) {
+ if (mem_regions_alloc_and_exclude(&system_memory_rgns,
+ &crash_reserved_mem[i])) {
+ fprintf(stderr, "Cannot allocate memory for ranges\n");
+ return -ENOMEM;
+ }
+ }
+
+ /*
+ * Make sure that the memory regions are sorted.
+ */
+ mem_regions_sort(&system_memory_rgns);
+
+ dbgprint_mem_range("Coredump memory ranges",
+ system_memory_rgns.ranges, system_memory_rgns.size);
+
+ /*
+ * For additional kernel code/data segment.
+ * kern_paddr_start/kern_size are determined in iomem_range_callback
+ */
+ elf_info.kern_vaddr_start = get_kernel_sym("_text");
+ if (!elf_info.kern_vaddr_start)
+ elf_info.kern_vaddr_start = UINT64_MAX;
+
+ return 0;
+}
+
+/*
+ * load_crashdump_segments() - load the elf core header
+ * @info: kexec info structure
+ *
+ * This function creates and loads an additional segment of elf core header
+ : which is used to construct /proc/vmcore on crash dump kernel.
+ *
+ * Return 0 in case of success and -1 in case of error.
+ */
+
+int load_crashdump_segments(struct kexec_info *info)
+{
+ 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 crash dump kernel during panic.
+ */
+
+ err = crash_get_memory_ranges();
+
+ if (err)
+ return EFAILED;
+
+ get_page_offset((unsigned long *)&elf_info.page_offset);
+ dbgprintf("%s: page_offset: %016llx\n", __func__,
+ elf_info.page_offset);
+
+ err = crash_create_elf64_headers(info, &elf_info,
+ system_memory_rgns.ranges, system_memory_rgns.size,
+ &buf, &bufsz, ELF_CORE_HEADER_ALIGN);
+
+ if (err)
+ return EFAILED;
+
+ elfcorehdr = add_buffer_phys_virt(info, buf, bufsz, bufsz, 0,
+ crash_reserved_mem[usablemem_rgns.size - 1].start,
+ crash_reserved_mem[usablemem_rgns.size - 1].end,
+ -1, 0);
+
+ elfcorehdr_mem.start = elfcorehdr;
+ elfcorehdr_mem.end = elfcorehdr + bufsz - 1;
+
+ dbgprintf("%s: elfcorehdr 0x%llx-0x%llx\n", __func__,
+ elfcorehdr_mem.start, elfcorehdr_mem.end);
+
+ return 0;
+}
+
+/*
+ * e_entry and p_paddr are actually in virtual address space.
+ * Those values will be translated to physcal addresses by using
+ * virt_to_phys() in add_segment().
+ * So let's fix up those values for later use so the memory base
+ * (arm64_mm.phys_offset) will be correctly replaced with
+ * crash_reserved_mem[usablemem_rgns.size - 1].start.
+ */
+void fixup_elf_addrs(struct mem_ehdr *ehdr)
+{
+ struct mem_phdr *phdr;
+ int i;
+
+ ehdr->e_entry += -arm64_mem.phys_offset +
+ crash_reserved_mem[usablemem_rgns.size - 1].start;
+
+ for (i = 0; i < ehdr->e_phnum; i++) {
+ phdr = &ehdr->e_phdr[i];
+ if (phdr->p_type != PT_LOAD)
+ continue;
+ phdr->p_paddr +=
+ (-arm64_mem.phys_offset +
+ crash_reserved_mem[usablemem_rgns.size - 1].start);
+ }
+}
+
+int get_crash_kernel_load_range(uint64_t *start, uint64_t *end)
+{
+ if (!usablemem_rgns.size)
+ kexec_iomem_for_each_line(NULL, iomem_range_callback, NULL);
+
+ if (!usablemem_rgns.size)
+ return -1;
+
+ *start = crash_reserved_mem[usablemem_rgns.size - 1].start;
+ *end = crash_reserved_mem[usablemem_rgns.size - 1].end;
+
+ return 0;
+}
diff --git a/kexec/arch/arm64/crashdump-arm64.h b/kexec/arch/arm64/crashdump-arm64.h
new file mode 100644
index 0000000..82fa69b
--- /dev/null
+++ b/kexec/arch/arm64/crashdump-arm64.h
@@ -0,0 +1,29 @@
+/*
+ * ARM64 crashdump.
+ *
+ * Copyright (c) 2014-2017 Linaro Limited
+ * Author: AKASHI Takahiro <takahiro.akashi@linaro.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef CRASHDUMP_ARM64_H
+#define CRASHDUMP_ARM64_H
+
+#include "kexec.h"
+
+#define CRASH_MAX_MEMORY_RANGES 32768
+
+/* crash dump kernel support at most two regions, low_region and high region. */
+#define CRASH_MAX_RESERVED_RANGES 2
+
+extern struct memory_ranges usablemem_rgns;
+extern struct memory_range crash_reserved_mem[];
+extern struct memory_range elfcorehdr_mem;
+
+extern int load_crashdump_segments(struct kexec_info *info);
+extern void fixup_elf_addrs(struct mem_ehdr *ehdr);
+
+#endif /* CRASHDUMP_ARM64_H */
diff --git a/kexec/arch/arm64/image-header.h b/kexec/arch/arm64/image-header.h
new file mode 100644
index 0000000..26bb02f
--- /dev/null
+++ b/kexec/arch/arm64/image-header.h
@@ -0,0 +1,147 @@
+/*
+ * ARM64 binary image header.
+ */
+
+#if !defined(__ARM64_IMAGE_HEADER_H)
+#define __ARM64_IMAGE_HEADER_H
+
+#include <endian.h>
+#include <stdint.h>
+
+/**
+ * struct arm64_image_header - arm64 kernel image header.
+ *
+ * @pe_sig: Optional PE format 'MZ' signature.
+ * @branch_code: Reserved for instructions to branch to stext.
+ * @text_offset: The image load offset in LSB byte order.
+ * @image_size: An estimated size of the memory image size in LSB byte order.
+ * @flags: Bit flags in LSB byte order:
+ * Bit 0: Image byte order: 1=MSB.
+ * Bit 1-2: Kernel page size: 1=4K, 2=16K, 3=64K.
+ * Bit 3: Image placement: 0=low.
+ * @reserved_1: Reserved.
+ * @magic: Magic number, "ARM\x64".
+ * @pe_header: Optional offset to a PE format header.
+ **/
+
+struct arm64_image_header {
+ uint8_t pe_sig[2];
+ uint16_t branch_code[3];
+ uint64_t text_offset;
+ uint64_t image_size;
+ uint64_t flags;
+ uint64_t reserved_1[3];
+ uint8_t magic[4];
+ uint32_t pe_header;
+};
+
+static const uint8_t arm64_image_magic[4] = {'A', 'R', 'M', 0x64U};
+static const uint8_t arm64_image_pe_sig[2] = {'M', 'Z'};
+static const uint8_t arm64_pe_machtype[6] = {'P','E', 0x0, 0x0, 0x64, 0xAA};
+static const uint64_t arm64_image_flag_be = (1UL << 0);
+static const uint64_t arm64_image_flag_page_size = (3UL << 1);
+static const uint64_t arm64_image_flag_placement = (1UL << 3);
+
+/**
+ * enum arm64_header_page_size
+ */
+
+enum arm64_header_page_size {
+ arm64_header_page_size_invalid = 0,
+ arm64_header_page_size_4k,
+ arm64_header_page_size_16k,
+ arm64_header_page_size_64k
+};
+
+/**
+ * arm64_header_check_magic - Helper to check the arm64 image header.
+ *
+ * Returns non-zero if header is OK.
+ */
+
+static inline int arm64_header_check_magic(const struct arm64_image_header *h)
+{
+ if (!h)
+ return 0;
+
+ return (h->magic[0] == arm64_image_magic[0]
+ && h->magic[1] == arm64_image_magic[1]
+ && h->magic[2] == arm64_image_magic[2]
+ && h->magic[3] == arm64_image_magic[3]);
+}
+
+/**
+ * arm64_header_check_pe_sig - Helper to check the arm64 image header.
+ *
+ * Returns non-zero if 'MZ' signature is found.
+ */
+
+static inline int arm64_header_check_pe_sig(const struct arm64_image_header *h)
+{
+ if (!h)
+ return 0;
+
+ return (h->pe_sig[0] == arm64_image_pe_sig[0]
+ && h->pe_sig[1] == arm64_image_pe_sig[1]);
+}
+
+/**
+ * arm64_header_check_msb - Helper to check the arm64 image header.
+ *
+ * Returns non-zero if the image was built as big endian.
+ */
+
+static inline int arm64_header_check_msb(const struct arm64_image_header *h)
+{
+ if (!h)
+ return 0;
+
+ return (le64toh(h->flags) & arm64_image_flag_be) >> 0;
+}
+
+/**
+ * arm64_header_page_size
+ */
+
+static inline enum arm64_header_page_size arm64_header_page_size(
+ const struct arm64_image_header *h)
+{
+ if (!h)
+ return 0;
+
+ return (le64toh(h->flags) & arm64_image_flag_page_size) >> 1;
+}
+
+/**
+ * arm64_header_placement
+ *
+ * Returns non-zero if the image has no physical placement restrictions.
+ */
+
+static inline int arm64_header_placement(const struct arm64_image_header *h)
+{
+ if (!h)
+ return 0;
+
+ return (le64toh(h->flags) & arm64_image_flag_placement) >> 3;
+}
+
+static inline uint64_t arm64_header_text_offset(
+ const struct arm64_image_header *h)
+{
+ if (!h)
+ return 0;
+
+ return le64toh(h->text_offset);
+}
+
+static inline uint64_t arm64_header_image_size(
+ const struct arm64_image_header *h)
+{
+ if (!h)
+ return 0;
+
+ return le64toh(h->image_size);
+}
+
+#endif
diff --git a/kexec/arch/arm64/include/arch/options.h b/kexec/arch/arm64/include/arch/options.h
new file mode 100644
index 0000000..8c695f3
--- /dev/null
+++ b/kexec/arch/arm64/include/arch/options.h
@@ -0,0 +1,43 @@
+#if !defined(KEXEC_ARCH_ARM64_OPTIONS_H)
+#define KEXEC_ARCH_ARM64_OPTIONS_H
+
+#define OPT_APPEND ((OPT_MAX)+0)
+#define OPT_DTB ((OPT_MAX)+1)
+#define OPT_INITRD ((OPT_MAX)+2)
+#define OPT_REUSE_CMDLINE ((OPT_MAX)+3)
+#define OPT_SERIAL ((OPT_MAX)+4)
+#define OPT_ARCH_MAX ((OPT_MAX)+5)
+
+#define KEXEC_ARCH_OPTIONS \
+ KEXEC_OPTIONS \
+ { "append", 1, NULL, OPT_APPEND }, \
+ { "command-line", 1, NULL, OPT_APPEND }, \
+ { "dtb", 1, NULL, OPT_DTB }, \
+ { "initrd", 1, NULL, OPT_INITRD }, \
+ { "serial", 1, NULL, OPT_SERIAL }, \
+ { "ramdisk", 1, NULL, OPT_INITRD }, \
+ { "reuse-cmdline", 0, NULL, OPT_REUSE_CMDLINE }, \
+
+#define KEXEC_ARCH_OPT_STR KEXEC_OPT_STR /* Only accept long arch options. */
+#define KEXEC_ALL_OPTIONS KEXEC_ARCH_OPTIONS
+#define KEXEC_ALL_OPT_STR KEXEC_ARCH_OPT_STR
+
+static const char arm64_opts_usage[] __attribute__ ((unused)) =
+" --append=STRING Set the kernel command line to STRING.\n"
+" --command-line=STRING Set the kernel command line to STRING.\n"
+" --dtb=FILE Use FILE as the device tree blob.\n"
+" --initrd=FILE Use FILE as the kernel initial ramdisk.\n"
+" --serial=STRING Name of console used for purgatory printing. (e.g. ttyAMA0)\n"
+" --ramdisk=FILE Use FILE as the kernel initial ramdisk.\n"
+" --reuse-cmdline Use kernel command line from running system.\n";
+
+struct arm64_opts {
+ const char *command_line;
+ const char *dtb;
+ const char *initrd;
+ const char *console;
+};
+
+extern struct arm64_opts arm64_opts;
+
+#endif
diff --git a/kexec/arch/arm64/iomem.h b/kexec/arch/arm64/iomem.h
new file mode 100644
index 0000000..d4864bb
--- /dev/null
+++ b/kexec/arch/arm64/iomem.h
@@ -0,0 +1,10 @@
+#ifndef IOMEM_H
+#define IOMEM_H
+
+#define SYSTEM_RAM "System RAM\n"
+#define KERNEL_CODE "Kernel code\n"
+#define KERNEL_DATA "Kernel data\n"
+#define CRASH_KERNEL "Crash kernel\n"
+#define IOMEM_RESERVED "reserved\n"
+
+#endif
diff --git a/kexec/arch/arm64/kexec-arm64.c b/kexec/arch/arm64/kexec-arm64.c
new file mode 100644
index 0000000..4a67b0d
--- /dev/null
+++ b/kexec/arch/arm64/kexec-arm64.c
@@ -0,0 +1,1365 @@
+/*
+ * ARM64 kexec.
+ */
+
+#define _GNU_SOURCE
+
+#include <assert.h>
+#include <errno.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <libfdt.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <linux/elf-em.h>
+#include <elf.h>
+#include <elf_info.h>
+
+#include <unistd.h>
+#include <syscall.h>
+#include <errno.h>
+#include <linux/random.h>
+
+#include "kexec.h"
+#include "kexec-arm64.h"
+#include "crashdump.h"
+#include "crashdump-arm64.h"
+#include "dt-ops.h"
+#include "fs2dt.h"
+#include "iomem.h"
+#include "kexec-syscall.h"
+#include "mem_regions.h"
+#include "arch/options.h"
+
+#define ROOT_NODE_ADDR_CELLS_DEFAULT 1
+#define ROOT_NODE_SIZE_CELLS_DEFAULT 1
+
+#define PROP_ADDR_CELLS "#address-cells"
+#define PROP_SIZE_CELLS "#size-cells"
+#define PROP_ELFCOREHDR "linux,elfcorehdr"
+#define PROP_USABLE_MEM_RANGE "linux,usable-memory-range"
+
+#define PAGE_OFFSET_36 ((0xffffffffffffffffUL) << 36)
+#define PAGE_OFFSET_39 ((0xffffffffffffffffUL) << 39)
+#define PAGE_OFFSET_42 ((0xffffffffffffffffUL) << 42)
+#define PAGE_OFFSET_47 ((0xffffffffffffffffUL) << 47)
+#define PAGE_OFFSET_48 ((0xffffffffffffffffUL) << 48)
+
+/* Global flag which indicates that we have tried reading
+ * PHYS_OFFSET from 'kcore' already.
+ */
+static bool try_read_phys_offset_from_kcore = false;
+
+/* Machine specific details. */
+static int va_bits = -1;
+static unsigned long page_offset;
+
+/* Global varables the core kexec routines expect. */
+
+unsigned char reuse_initrd;
+
+off_t initrd_base;
+off_t initrd_size;
+
+const struct arch_map_entry arches[] = {
+ { "aarch64", KEXEC_ARCH_ARM64 },
+ { "aarch64_be", KEXEC_ARCH_ARM64 },
+ { NULL, 0 },
+};
+
+struct file_type file_type[] = {
+ {"vmlinux", elf_arm64_probe, elf_arm64_load, elf_arm64_usage},
+ {"Image", image_arm64_probe, image_arm64_load, image_arm64_usage},
+ {"uImage", uImage_arm64_probe, uImage_arm64_load, uImage_arm64_usage},
+ {"vmlinuz", pez_arm64_probe, pez_arm64_load, pez_arm64_usage},
+};
+
+int file_types = sizeof(file_type) / sizeof(file_type[0]);
+
+/* arm64 global varables. */
+
+struct arm64_opts arm64_opts;
+struct arm64_mem arm64_mem = {
+ .phys_offset = arm64_mem_ngv,
+ .vp_offset = arm64_mem_ngv,
+};
+
+uint64_t get_phys_offset(void)
+{
+ assert(arm64_mem.phys_offset != arm64_mem_ngv);
+ return arm64_mem.phys_offset;
+}
+
+uint64_t get_vp_offset(void)
+{
+ assert(arm64_mem.vp_offset != arm64_mem_ngv);
+ return arm64_mem.vp_offset;
+}
+
+/**
+ * arm64_process_image_header - Process the arm64 image header.
+ *
+ * Make a guess that KERNEL_IMAGE_SIZE will be enough for older kernels.
+ */
+
+int arm64_process_image_header(const struct arm64_image_header *h)
+{
+#if !defined(KERNEL_IMAGE_SIZE)
+# define KERNEL_IMAGE_SIZE MiB(16)
+#endif
+
+ if (!arm64_header_check_magic(h))
+ return EFAILED;
+
+ if (h->image_size) {
+ arm64_mem.text_offset = arm64_header_text_offset(h);
+ arm64_mem.image_size = arm64_header_image_size(h);
+ } else {
+ /* For 3.16 and older kernels. */
+ arm64_mem.text_offset = 0x80000;
+ arm64_mem.image_size = KERNEL_IMAGE_SIZE;
+ fprintf(stderr,
+ "kexec: %s: Warning: Kernel image size set to %lu MiB.\n"
+ " Please verify compatability with lodaed kernel.\n",
+ __func__, KERNEL_IMAGE_SIZE / 1024UL / 1024UL);
+ }
+
+ return 0;
+}
+
+void arch_usage(void)
+{
+ printf(arm64_opts_usage);
+}
+
+int arch_process_options(int argc, char **argv)
+{
+ static const char short_options[] = KEXEC_OPT_STR "";
+ static const struct option options[] = {
+ KEXEC_ARCH_OPTIONS
+ { 0 }
+ };
+ int opt;
+ char *cmdline = NULL;
+ const char *append = NULL;
+ int do_kexec_file_syscall = 0;
+
+ for (opt = 0; opt != -1; ) {
+ opt = getopt_long(argc, argv, short_options, options, 0);
+
+ switch (opt) {
+ case OPT_APPEND:
+ append = optarg;
+ break;
+ case OPT_REUSE_CMDLINE:
+ cmdline = get_command_line();
+ break;
+ case OPT_DTB:
+ arm64_opts.dtb = optarg;
+ break;
+ case OPT_INITRD:
+ arm64_opts.initrd = optarg;
+ break;
+ case OPT_KEXEC_FILE_SYSCALL:
+ do_kexec_file_syscall = 1;
+ case OPT_SERIAL:
+ arm64_opts.console = optarg;
+ break;
+ default:
+ break; /* Ignore core and unknown options. */
+ }
+ }
+
+ arm64_opts.command_line = concat_cmdline(cmdline, append);
+
+ dbgprintf("%s:%d: command_line: %s\n", __func__, __LINE__,
+ arm64_opts.command_line);
+ dbgprintf("%s:%d: initrd: %s\n", __func__, __LINE__,
+ arm64_opts.initrd);
+ dbgprintf("%s:%d: dtb: %s\n", __func__, __LINE__,
+ (do_kexec_file_syscall && arm64_opts.dtb ? "(ignored)" :
+ arm64_opts.dtb));
+ dbgprintf("%s:%d: console: %s\n", __func__, __LINE__,
+ arm64_opts.console);
+
+ if (do_kexec_file_syscall)
+ arm64_opts.dtb = NULL;
+
+ return 0;
+}
+
+/**
+ * find_purgatory_sink - Find a sink for purgatory output.
+ */
+
+static uint64_t find_purgatory_sink(const char *console)
+{
+ int fd, ret;
+ char device[255], mem[255];
+ struct stat sb;
+ char buffer[10];
+ uint64_t iomem = 0x0;
+
+ if (!console)
+ return 0;
+
+ ret = snprintf(device, sizeof(device), "/sys/class/tty/%s", console);
+ if (ret < 0 || ret >= sizeof(device)) {
+ fprintf(stderr, "snprintf failed: %s\n", strerror(errno));
+ return 0;
+ }
+
+ if (stat(device, &sb) || !S_ISDIR(sb.st_mode)) {
+ fprintf(stderr, "kexec: %s: No valid console found for %s\n",
+ __func__, device);
+ return 0;
+ }
+
+ ret = snprintf(mem, sizeof(mem), "%s%s", device, "/iomem_base");
+ if (ret < 0 || ret >= sizeof(mem)) {
+ fprintf(stderr, "snprintf failed: %s\n", strerror(errno));
+ return 0;
+ }
+
+ printf("console memory read from %s\n", mem);
+
+ fd = open(mem, O_RDONLY);
+ if (fd < 0) {
+ fprintf(stderr, "kexec: %s: No able to open %s\n",
+ __func__, mem);
+ return 0;
+ }
+
+ memset(buffer, '\0', sizeof(buffer));
+ ret = read(fd, buffer, sizeof(buffer));
+ if (ret < 0) {
+ fprintf(stderr, "kexec: %s: not able to read fd\n", __func__);
+ close(fd);
+ return 0;
+ }
+
+ sscanf(buffer, "%lx", &iomem);
+ printf("console memory is at %#lx\n", iomem);
+
+ close(fd);
+ return iomem;
+}
+
+/**
+ * struct dtb - Info about a binary device tree.
+ *
+ * @buf: Device tree data.
+ * @size: Device tree data size.
+ * @name: Shorthand name of this dtb for messages.
+ * @path: Filesystem path.
+ */
+
+struct dtb {
+ char *buf;
+ off_t size;
+ const char *name;
+ const char *path;
+};
+
+/**
+ * dump_reservemap - Dump the dtb's reservemap.
+ */
+
+static void dump_reservemap(const struct dtb *dtb)
+{
+ int i;
+
+ for (i = 0; ; i++) {
+ uint64_t address;
+ uint64_t size;
+
+ fdt_get_mem_rsv(dtb->buf, i, &address, &size);
+
+ if (!size)
+ break;
+
+ dbgprintf("%s: %s {%" PRIx64 ", %" PRIx64 "}\n", __func__,
+ dtb->name, address, size);
+ }
+}
+
+/**
+ * set_bootargs - Set the dtb's bootargs.
+ */
+
+static int set_bootargs(struct dtb *dtb, const char *command_line)
+{
+ int result;
+
+ if (!command_line || !command_line[0])
+ return 0;
+
+ result = dtb_set_bootargs(&dtb->buf, &dtb->size, command_line);
+
+ if (result) {
+ fprintf(stderr,
+ "kexec: Set device tree bootargs failed.\n");
+ return EFAILED;
+ }
+
+ return 0;
+}
+
+/**
+ * read_proc_dtb - Read /proc/device-tree.
+ */
+
+static int read_proc_dtb(struct dtb *dtb)
+{
+ int result;
+ struct stat s;
+ static const char path[] = "/proc/device-tree";
+
+ result = stat(path, &s);
+
+ if (result) {
+ dbgprintf("%s: %s\n", __func__, strerror(errno));
+ return EFAILED;
+ }
+
+ dtb->path = path;
+ create_flatten_tree((char **)&dtb->buf, &dtb->size, NULL);
+
+ return 0;
+}
+
+/**
+ * read_sys_dtb - Read /sys/firmware/fdt.
+ */
+
+static int read_sys_dtb(struct dtb *dtb)
+{
+ int result;
+ struct stat s;
+ static const char path[] = "/sys/firmware/fdt";
+
+ result = stat(path, &s);
+
+ if (result) {
+ dbgprintf("%s: %s\n", __func__, strerror(errno));
+ return EFAILED;
+ }
+
+ dtb->path = path;
+ dtb->buf = slurp_file(path, &dtb->size);
+
+ return 0;
+}
+
+/**
+ * read_1st_dtb - Read the 1st stage kernel's dtb.
+ */
+
+static int read_1st_dtb(struct dtb *dtb)
+{
+ int result;
+
+ dtb->name = "dtb_sys";
+ result = read_sys_dtb(dtb);
+
+ if (!result)
+ goto on_success;
+
+ dtb->name = "dtb_proc";
+ result = read_proc_dtb(dtb);
+
+ if (!result)
+ goto on_success;
+
+ dbgprintf("%s: not found\n", __func__);
+ return EFAILED;
+
+on_success:
+ dbgprintf("%s: found %s\n", __func__, dtb->path);
+ return 0;
+}
+
+static int get_cells_size(void *fdt, uint32_t *address_cells,
+ uint32_t *size_cells)
+{
+ int nodeoffset;
+ const uint32_t *prop = NULL;
+ int prop_len;
+
+ /* default values */
+ *address_cells = ROOT_NODE_ADDR_CELLS_DEFAULT;
+ *size_cells = ROOT_NODE_SIZE_CELLS_DEFAULT;
+
+ /* under root node */
+ nodeoffset = fdt_path_offset(fdt, "/");
+ if (nodeoffset < 0)
+ goto on_error;
+
+ prop = fdt_getprop(fdt, nodeoffset, PROP_ADDR_CELLS, &prop_len);
+ if (prop) {
+ if (prop_len == sizeof(*prop))
+ *address_cells = fdt32_to_cpu(*prop);
+ else
+ goto on_error;
+ }
+
+ prop = fdt_getprop(fdt, nodeoffset, PROP_SIZE_CELLS, &prop_len);
+ if (prop) {
+ if (prop_len == sizeof(*prop))
+ *size_cells = fdt32_to_cpu(*prop);
+ else
+ goto on_error;
+ }
+
+ dbgprintf("%s: #address-cells:%d #size-cells:%d\n", __func__,
+ *address_cells, *size_cells);
+ return 0;
+
+on_error:
+ return EFAILED;
+}
+
+static bool cells_size_fitted(uint32_t address_cells, uint32_t size_cells,
+ struct memory_range *range)
+{
+ dbgprintf("%s: %llx-%llx\n", __func__, range->start, range->end);
+
+ /* if *_cells >= 2, cells can hold 64-bit values anyway */
+ if ((address_cells == 1) && (range->start >= (1ULL << 32)))
+ return false;
+
+ if ((size_cells == 1) &&
+ ((range->end - range->start + 1) >= (1ULL << 32)))
+ return false;
+
+ return true;
+}
+
+static void fill_property(void *buf, uint64_t val, uint32_t cells)
+{
+ uint32_t val32;
+ int i;
+
+ if (cells == 1) {
+ val32 = cpu_to_fdt32((uint32_t)val);
+ memcpy(buf, &val32, sizeof(uint32_t));
+ } else {
+ for (i = 0;
+ i < (cells * sizeof(uint32_t) - sizeof(uint64_t)); i++)
+ *(char *)buf++ = 0;
+
+ val = cpu_to_fdt64(val);
+ memcpy(buf, &val, sizeof(uint64_t));
+ }
+}
+
+static int fdt_setprop_ranges(void *fdt, int nodeoffset, const char *name,
+ struct memory_range *ranges, int nr_ranges, bool reverse,
+ uint32_t address_cells, uint32_t size_cells)
+{
+ void *buf, *prop;
+ size_t buf_size;
+ int i, result;
+ struct memory_range *range;
+
+ buf_size = (address_cells + size_cells) * sizeof(uint32_t) * nr_ranges;
+ prop = buf = xmalloc(buf_size);
+ if (!buf)
+ return -ENOMEM;
+
+ for (i = 0; i < nr_ranges; i++) {
+ if (reverse)
+ range = ranges + (nr_ranges - 1 - i);
+ else
+ range = ranges + i;
+
+ fill_property(prop, range->start, address_cells);
+ prop += address_cells * sizeof(uint32_t);
+
+ fill_property(prop, range->end - range->start + 1, size_cells);
+ prop += size_cells * sizeof(uint32_t);
+ }
+
+ result = fdt_setprop(fdt, nodeoffset, name, buf, buf_size);
+
+ free(buf);
+
+ return result;
+}
+
+/**
+ * setup_2nd_dtb - Setup the 2nd stage kernel's dtb.
+ */
+
+static int setup_2nd_dtb(struct dtb *dtb, char *command_line, int on_crash)
+{
+ uint32_t address_cells, size_cells;
+ uint64_t fdt_val64;
+ uint64_t *prop;
+ char *new_buf = NULL;
+ int len, range_len;
+ int nodeoffset;
+ int new_size;
+ int i, result, kaslr_seed;
+
+ result = fdt_check_header(dtb->buf);
+
+ if (result) {
+ fprintf(stderr, "kexec: Invalid 2nd device tree.\n");
+ return EFAILED;
+ }
+
+ result = set_bootargs(dtb, command_line);
+ if (result) {
+ fprintf(stderr, "kexec: cannot set bootargs.\n");
+ result = -EINVAL;
+ goto on_error;
+ }
+
+ /* determine #address-cells and #size-cells */
+ result = get_cells_size(dtb->buf, &address_cells, &size_cells);
+ if (result) {
+ fprintf(stderr, "kexec: cannot determine cells-size.\n");
+ result = -EINVAL;
+ goto on_error;
+ }
+
+ if (!cells_size_fitted(address_cells, size_cells,
+ &elfcorehdr_mem)) {
+ fprintf(stderr, "kexec: elfcorehdr doesn't fit cells-size.\n");
+ result = -EINVAL;
+ goto on_error;
+ }
+
+ for (i = 0; i < usablemem_rgns.size; i++) {
+ if (!cells_size_fitted(address_cells, size_cells,
+ &crash_reserved_mem[i])) {
+ fprintf(stderr, "kexec: usable memory range doesn't fit cells-size.\n");
+ result = -EINVAL;
+ goto on_error;
+ }
+ }
+
+ /* duplicate dt blob */
+ range_len = sizeof(uint32_t) * (address_cells + size_cells);
+ new_size = fdt_totalsize(dtb->buf)
+ + fdt_prop_len(PROP_ELFCOREHDR, range_len)
+ + fdt_prop_len(PROP_USABLE_MEM_RANGE, range_len * usablemem_rgns.size);
+
+ new_buf = xmalloc(new_size);
+ result = fdt_open_into(dtb->buf, new_buf, new_size);
+ if (result) {
+ dbgprintf("%s: fdt_open_into failed: %s\n", __func__,
+ fdt_strerror(result));
+ result = -ENOSPC;
+ goto on_error;
+ }
+
+ /* fixup 'kaslr-seed' with a random value, if supported */
+ nodeoffset = fdt_path_offset(new_buf, "/chosen");
+ prop = fdt_getprop_w(new_buf, nodeoffset,
+ "kaslr-seed", &len);
+ if (!prop || len != sizeof(uint64_t)) {
+ dbgprintf("%s: no kaslr-seed found\n",
+ __func__);
+ /* for kexec warm reboot case, we don't need to fixup
+ * other dtb properties
+ */
+ if (!on_crash) {
+ dump_reservemap(dtb);
+ if (new_buf)
+ free(new_buf);
+
+ return result;
+ }
+ } else {
+ kaslr_seed = fdt64_to_cpu(*prop);
+
+ /* kaslr_seed must be wiped clean by primary
+ * kernel during boot
+ */
+ if (kaslr_seed != 0) {
+ dbgprintf("%s: kaslr-seed is not wiped to 0.\n",
+ __func__);
+ result = -EINVAL;
+ goto on_error;
+ }
+
+ /*
+ * Invoke the getrandom system call with
+ * GRND_NONBLOCK, to make sure we
+ * have a valid random seed to pass to the
+ * secondary kernel.
+ */
+ result = syscall(SYS_getrandom, &fdt_val64,
+ sizeof(fdt_val64),
+ GRND_NONBLOCK);
+
+ if(result == -1) {
+ fprintf(stderr, "%s: Reading random bytes failed.\n",
+ __func__);
+
+ /* Currently on some arm64 platforms this
+ * 'getrandom' system call fails while booting
+ * the platform.
+ *
+ * In case, this happens at best we can set
+ * the 'kaslr_seed' as 0, indicating that the
+ * 2nd kernel will be booted with a 'nokaslr'
+ * like behaviour.
+ */
+ fdt_val64 = 0UL;
+ dbgprintf("%s: Disabling KASLR in secondary kernel.\n",
+ __func__);
+ }
+
+ nodeoffset = fdt_path_offset(new_buf, "/chosen");
+ result = fdt_setprop_inplace(new_buf,
+ nodeoffset, "kaslr-seed",
+ &fdt_val64, sizeof(fdt_val64));
+ if (result) {
+ dbgprintf("%s: fdt_setprop failed: %s\n",
+ __func__, fdt_strerror(result));
+ result = -EINVAL;
+ goto on_error;
+ }
+ }
+
+ if (on_crash) {
+ /* add linux,elfcorehdr */
+ nodeoffset = fdt_path_offset(new_buf, "/chosen");
+ result = fdt_setprop_ranges(new_buf, nodeoffset,
+ PROP_ELFCOREHDR, &elfcorehdr_mem, 1, false,
+ address_cells, size_cells);
+ if (result) {
+ dbgprintf("%s: fdt_setprop failed: %s\n", __func__,
+ fdt_strerror(result));
+ result = -EINVAL;
+ goto on_error;
+ }
+
+ /*
+ * add linux,usable-memory-range
+ *
+ * crash dump kernel support one or two regions, to make
+ * compatibility with existing user-space and older kdump, the
+ * low region is always the last one.
+ */
+ nodeoffset = fdt_path_offset(new_buf, "/chosen");
+ result = fdt_setprop_ranges(new_buf, nodeoffset,
+ PROP_USABLE_MEM_RANGE,
+ usablemem_rgns.ranges, usablemem_rgns.size, true,
+ address_cells, size_cells);
+ if (result) {
+ dbgprintf("%s: fdt_setprop failed: %s\n", __func__,
+ fdt_strerror(result));
+ result = -EINVAL;
+ goto on_error;
+ }
+ }
+
+ fdt_pack(new_buf);
+ dtb->buf = new_buf;
+ dtb->size = fdt_totalsize(new_buf);
+
+ dump_reservemap(dtb);
+
+ return result;
+
+on_error:
+ fprintf(stderr, "kexec: %s failed.\n", __func__);
+ if (new_buf)
+ free(new_buf);
+
+ return result;
+}
+
+unsigned long arm64_locate_kernel_segment(struct kexec_info *info)
+{
+ unsigned long hole;
+
+ if (info->kexec_flags & KEXEC_ON_CRASH) {
+ unsigned long hole_end;
+
+ hole = (crash_reserved_mem[usablemem_rgns.size - 1].start < mem_min ?
+ mem_min : crash_reserved_mem[usablemem_rgns.size - 1].start);
+ hole = _ALIGN_UP(hole, MiB(2));
+ hole_end = hole + arm64_mem.text_offset + arm64_mem.image_size;
+
+ if ((hole_end > mem_max) ||
+ (hole_end > crash_reserved_mem[usablemem_rgns.size - 1].end)) {
+ dbgprintf("%s: Crash kernel out of range\n", __func__);
+ hole = ULONG_MAX;
+ }
+ } else {
+ hole = locate_hole(info,
+ arm64_mem.text_offset + arm64_mem.image_size,
+ MiB(2), 0, ULONG_MAX, 1);
+
+ if (hole == ULONG_MAX)
+ dbgprintf("%s: locate_hole failed\n", __func__);
+ }
+
+ return hole;
+}
+
+/**
+ * arm64_load_other_segments - Prepare the dtb, initrd and purgatory segments.
+ */
+
+int arm64_load_other_segments(struct kexec_info *info,
+ unsigned long image_base)
+{
+ int result;
+ unsigned long dtb_base;
+ unsigned long hole_min;
+ unsigned long hole_max;
+ unsigned long initrd_end;
+ uint64_t purgatory_sink;
+ char *initrd_buf = NULL;
+ struct dtb dtb;
+ char command_line[COMMAND_LINE_SIZE] = "";
+
+ if (arm64_opts.command_line) {
+ if (strlen(arm64_opts.command_line) >
+ sizeof(command_line) - 1) {
+ fprintf(stderr,
+ "Kernel command line too long for kernel!\n");
+ return EFAILED;
+ }
+
+ strncpy(command_line, arm64_opts.command_line,
+ sizeof(command_line) - 1);
+ command_line[sizeof(command_line) - 1] = 0;
+ }
+
+ purgatory_sink = find_purgatory_sink(arm64_opts.console);
+
+ dbgprintf("%s:%d: purgatory sink: 0x%" PRIx64 "\n", __func__, __LINE__,
+ purgatory_sink);
+
+ if (arm64_opts.dtb) {
+ dtb.name = "dtb_user";
+ dtb.buf = slurp_file(arm64_opts.dtb, &dtb.size);
+ } else {
+ result = read_1st_dtb(&dtb);
+
+ if (result) {
+ fprintf(stderr,
+ "kexec: Error: No device tree available.\n");
+ return EFAILED;
+ }
+ }
+
+ result = setup_2nd_dtb(&dtb, command_line,
+ info->kexec_flags & KEXEC_ON_CRASH);
+
+ if (result)
+ return EFAILED;
+
+ /* Put the other segments after the image. */
+
+ hole_min = image_base + arm64_mem.image_size;
+ if (info->kexec_flags & KEXEC_ON_CRASH)
+ hole_max = crash_reserved_mem[usablemem_rgns.size - 1].end;
+ else
+ hole_max = ULONG_MAX;
+
+ if (arm64_opts.initrd) {
+ initrd_buf = slurp_file(arm64_opts.initrd, &initrd_size);
+
+ if (!initrd_buf)
+ fprintf(stderr, "kexec: Empty ramdisk file.\n");
+ else {
+ /* Put the initrd after the kernel. */
+
+ initrd_base = add_buffer_phys_virt(info, initrd_buf,
+ initrd_size, initrd_size, 0,
+ hole_min, hole_max, 1, 0);
+
+ initrd_end = initrd_base + initrd_size;
+
+ /* Check limits as specified in booting.txt.
+ * The kernel may have as little as 32 GB of address space to map
+ * system memory and both kernel and initrd must be 1GB aligend.
+ */
+
+ if (_ALIGN_UP(initrd_end, GiB(1)) - _ALIGN_DOWN(image_base, GiB(1)) > GiB(32)) {
+ fprintf(stderr, "kexec: Error: image + initrd too big.\n");
+ return EFAILED;
+ }
+
+ dbgprintf("initrd: base %lx, size %lxh (%ld)\n",
+ initrd_base, initrd_size, initrd_size);
+
+ result = dtb_set_initrd((char **)&dtb.buf,
+ &dtb.size, initrd_base,
+ initrd_base + initrd_size);
+
+ if (result)
+ return EFAILED;
+ }
+ }
+
+ if (!initrd_buf) {
+ /* Don't reuse the initrd addresses from 1st DTB */
+ dtb_clear_initrd((char **)&dtb.buf, &dtb.size);
+ }
+
+ /* Check size limit as specified in booting.txt. */
+
+ if (dtb.size > MiB(2)) {
+ fprintf(stderr, "kexec: Error: dtb too big.\n");
+ return EFAILED;
+ }
+
+ dtb_base = add_buffer_phys_virt(info, dtb.buf, dtb.size, dtb.size,
+ 0, hole_min, hole_max, 1, 0);
+
+ /* dtb_base is valid if we got here. */
+
+ dbgprintf("dtb: base %lx, size %lxh (%ld)\n", dtb_base, dtb.size,
+ dtb.size);
+
+ elf_rel_build_load(info, &info->rhdr, purgatory, purgatory_size,
+ hole_min, hole_max, 1, 0);
+
+ info->entry = (void *)elf_rel_get_addr(&info->rhdr, "purgatory_start");
+
+ elf_rel_set_symbol(&info->rhdr, "arm64_sink", &purgatory_sink,
+ sizeof(purgatory_sink));
+
+ elf_rel_set_symbol(&info->rhdr, "arm64_kernel_entry", &image_base,
+ sizeof(image_base));
+
+ elf_rel_set_symbol(&info->rhdr, "arm64_dtb_addr", &dtb_base,
+ sizeof(dtb_base));
+
+ return 0;
+}
+
+/**
+ * virt_to_phys - For processing elf file values.
+ */
+
+unsigned long virt_to_phys(unsigned long v)
+{
+ unsigned long p;
+
+ p = v - get_vp_offset() + get_phys_offset();
+
+ return p;
+}
+
+/**
+ * phys_to_virt - For crashdump setup.
+ */
+
+unsigned long phys_to_virt(struct crash_elf_info *elf_info,
+ unsigned long long p)
+{
+ unsigned long v;
+
+ v = p - get_phys_offset() + elf_info->page_offset;
+
+ return v;
+}
+
+/**
+ * add_segment - Use virt_to_phys when loading elf files.
+ */
+
+void add_segment(struct kexec_info *info, const void *buf, size_t bufsz,
+ unsigned long base, size_t memsz)
+{
+ add_segment_phys_virt(info, buf, bufsz, base, memsz, 1);
+}
+
+static inline void set_phys_offset(int64_t v, char *set_method)
+{
+ if (arm64_mem.phys_offset == arm64_mem_ngv
+ || v < arm64_mem.phys_offset) {
+ arm64_mem.phys_offset = v;
+ dbgprintf("%s: phys_offset : %016lx (method : %s)\n",
+ __func__, arm64_mem.phys_offset,
+ set_method);
+ }
+}
+
+/**
+ * get_va_bits - Helper for getting VA_BITS
+ */
+
+static int get_va_bits(void)
+{
+ unsigned long long stext_sym_addr;
+
+ /*
+ * if already got from kcore
+ */
+ if (va_bits != -1)
+ goto out;
+
+
+ /* For kernel older than v4.19 */
+ fprintf(stderr, "Warning, can't get the VA_BITS from kcore\n");
+ stext_sym_addr = get_kernel_sym("_stext");
+
+ if (stext_sym_addr == 0) {
+ fprintf(stderr, "Can't get the symbol of _stext.\n");
+ return -1;
+ }
+
+ /* Derive va_bits as per arch/arm64/Kconfig */
+ if ((stext_sym_addr & PAGE_OFFSET_36) == PAGE_OFFSET_36) {
+ va_bits = 36;
+ } else if ((stext_sym_addr & PAGE_OFFSET_39) == PAGE_OFFSET_39) {
+ va_bits = 39;
+ } else if ((stext_sym_addr & PAGE_OFFSET_42) == PAGE_OFFSET_42) {
+ va_bits = 42;
+ } else if ((stext_sym_addr & PAGE_OFFSET_47) == PAGE_OFFSET_47) {
+ va_bits = 47;
+ } else if ((stext_sym_addr & PAGE_OFFSET_48) == PAGE_OFFSET_48) {
+ va_bits = 48;
+ } else {
+ fprintf(stderr,
+ "Cannot find a proper _stext for calculating VA_BITS\n");
+ return -1;
+ }
+
+out:
+ dbgprintf("va_bits : %d\n", va_bits);
+
+ return 0;
+}
+
+/**
+ * get_page_offset - Helper for getting PAGE_OFFSET
+ */
+
+int get_page_offset(unsigned long *page_offset)
+{
+ unsigned long long text_sym_addr, kernel_va_mid;
+ int ret;
+
+ text_sym_addr = get_kernel_sym("_text");
+ if (text_sym_addr == 0) {
+ fprintf(stderr, "Can't get the symbol of _text to calculate page_offset.\n");
+ return -1;
+ }
+
+ ret = get_va_bits();
+ if (ret < 0)
+ return ret;
+
+ /* Since kernel 5.4, kernel image is put above
+ * UINT64_MAX << (va_bits - 1)
+ */
+ kernel_va_mid = UINT64_MAX << (va_bits - 1);
+ /* older kernel */
+ if (text_sym_addr < kernel_va_mid)
+ *page_offset = UINT64_MAX << (va_bits - 1);
+ else
+ *page_offset = UINT64_MAX << va_bits;
+
+ dbgprintf("page_offset : %lx\n", *page_offset);
+
+ return 0;
+}
+
+static void arm64_scan_vmcoreinfo(char *pos)
+{
+ const char *str;
+
+ str = "NUMBER(VA_BITS)=";
+ if (memcmp(str, pos, strlen(str)) == 0)
+ va_bits = strtoul(pos + strlen(str), NULL, 10);
+}
+
+/**
+ * get_phys_offset_from_vmcoreinfo_pt_note - Helper for getting PHYS_OFFSET (and va_bits)
+ * from VMCOREINFO note inside 'kcore'.
+ */
+
+static int get_phys_offset_from_vmcoreinfo_pt_note(long *phys_offset)
+{
+ int fd, ret = 0;
+
+ if ((fd = open("/proc/kcore", O_RDONLY)) < 0) {
+ fprintf(stderr, "Can't open (%s).\n", "/proc/kcore");
+ return EFAILED;
+ }
+
+ arch_scan_vmcoreinfo = arm64_scan_vmcoreinfo;
+ ret = read_phys_offset_elf_kcore(fd, phys_offset);
+
+ close(fd);
+ return ret;
+}
+
+/**
+ * get_phys_base_from_pt_load - Helper for getting PHYS_OFFSET
+ * from PT_LOADs inside 'kcore'.
+ */
+
+int get_phys_base_from_pt_load(long *phys_offset)
+{
+ int i, fd, ret;
+ unsigned long long phys_start;
+ unsigned long long virt_start;
+
+ ret = get_page_offset(&page_offset);
+ if (ret < 0)
+ return ret;
+
+ if ((fd = open("/proc/kcore", O_RDONLY)) < 0) {
+ fprintf(stderr, "Can't open (%s).\n", "/proc/kcore");
+ return EFAILED;
+ }
+
+ read_elf(fd);
+
+ for (i = 0; get_pt_load(i,
+ &phys_start, NULL, &virt_start, NULL);
+ i++) {
+ if (virt_start != NOT_KV_ADDR
+ && virt_start >= page_offset
+ && phys_start != NOT_PADDR)
+ *phys_offset = phys_start -
+ (virt_start & ~page_offset);
+ }
+
+ close(fd);
+ return 0;
+}
+
+static bool to_be_excluded(char *str, unsigned long long start, unsigned long long end)
+{
+ if (!strncmp(str, CRASH_KERNEL, strlen(CRASH_KERNEL))) {
+ uint64_t load_start, load_end;
+
+ if (!get_crash_kernel_load_range(&load_start, &load_end) &&
+ (load_start == start) && (load_end == end))
+ return false;
+
+ return true;
+ }
+
+ if (!strncmp(str, SYSTEM_RAM, strlen(SYSTEM_RAM)) ||
+ !strncmp(str, KERNEL_CODE, strlen(KERNEL_CODE)) ||
+ !strncmp(str, KERNEL_DATA, strlen(KERNEL_DATA)))
+ return false;
+ else
+ return true;
+}
+
+/**
+ * get_memory_ranges - Try to get the memory ranges from
+ * /proc/iomem.
+ */
+int get_memory_ranges(struct memory_range **range, int *ranges,
+ unsigned long kexec_flags)
+{
+ long phys_offset = -1;
+ FILE *fp;
+ const char *iomem = proc_iomem();
+ char line[MAX_LINE], *str;
+ unsigned long long start, end;
+ int n, consumed;
+ struct memory_ranges memranges;
+ struct memory_range *last, excl_range;
+ int ret;
+
+ if (!try_read_phys_offset_from_kcore) {
+ /* Since kernel version 4.19, 'kcore' contains
+ * a new PT_NOTE which carries the VMCOREINFO
+ * information.
+ * If the same is available, one should prefer the
+ * same to retrieve 'PHYS_OFFSET' value exported by
+ * the kernel as this is now the standard interface
+ * exposed by kernel for sharing machine specific
+ * details with the userland.
+ */
+ ret = get_phys_offset_from_vmcoreinfo_pt_note(&phys_offset);
+ if (!ret) {
+ if (phys_offset != -1)
+ set_phys_offset(phys_offset,
+ "vmcoreinfo pt_note");
+ } else {
+ /* If we are running on a older kernel,
+ * try to retrieve the 'PHYS_OFFSET' value
+ * exported by the kernel in the 'kcore'
+ * file by reading the PT_LOADs and determining
+ * the correct combination.
+ */
+ ret = get_phys_base_from_pt_load(&phys_offset);
+ if (!ret)
+ if (phys_offset != -1)
+ set_phys_offset(phys_offset,
+ "pt_load");
+ }
+
+ try_read_phys_offset_from_kcore = true;
+ }
+
+ fp = fopen(iomem, "r");
+ if (!fp)
+ die("Cannot open %s\n", iomem);
+
+ memranges.ranges = NULL;
+ memranges.size = memranges.max_size = 0;
+
+ while (fgets(line, sizeof(line), fp) != 0) {
+ n = sscanf(line, "%llx-%llx : %n", &start, &end, &consumed);
+ if (n != 2)
+ continue;
+ str = line + consumed;
+
+ if (!strncmp(str, SYSTEM_RAM, strlen(SYSTEM_RAM))) {
+ ret = mem_regions_alloc_and_add(&memranges,
+ start, end - start + 1, RANGE_RAM);
+ if (ret) {
+ fprintf(stderr,
+ "Cannot allocate memory for ranges\n");
+ fclose(fp);
+ return -ENOMEM;
+ }
+
+ dbgprintf("%s:+[%d] %016llx - %016llx\n", __func__,
+ memranges.size - 1,
+ memranges.ranges[memranges.size - 1].start,
+ memranges.ranges[memranges.size - 1].end);
+ } else if (to_be_excluded(str, start, end)) {
+ if (!memranges.size)
+ continue;
+
+ /*
+ * Note: mem_regions_exclude() doesn't guarantee
+ * that the ranges are sorted out, but as long as
+ * we cope with /proc/iomem, we only operate on
+ * the last entry and so it is safe.
+ */
+
+ /* The last System RAM range */
+ last = &memranges.ranges[memranges.size - 1];
+
+ if (last->end < start)
+ /* New resource outside of System RAM */
+ continue;
+ if (end < last->start)
+ /* Already excluded by parent resource */
+ continue;
+
+ excl_range.start = start;
+ excl_range.end = end;
+ ret = mem_regions_alloc_and_exclude(&memranges, &excl_range);
+ if (ret) {
+ fprintf(stderr,
+ "Cannot allocate memory for ranges (exclude)\n");
+ fclose(fp);
+ return -ENOMEM;
+ }
+ dbgprintf("%s:- %016llx - %016llx\n",
+ __func__, start, end);
+ }
+ }
+
+ fclose(fp);
+
+ *range = memranges.ranges;
+ *ranges = memranges.size;
+
+ /* As a fallback option, we can try determining the PHYS_OFFSET
+ * value from the '/proc/iomem' entries as well.
+ *
+ * But note that this can be flaky, as on certain arm64
+ * platforms, it has been noticed that due to a hole at the
+ * start of physical ram exposed to kernel
+ * (i.e. it doesn't start from address 0), the kernel still
+ * calculates the 'memstart_addr' kernel variable as 0.
+ *
+ * Whereas the SYSTEM_RAM or IOMEM_RESERVED range in
+ * '/proc/iomem' would carry a first entry whose start address
+ * is non-zero (as the physical ram exposed to the kernel
+ * starts from a non-zero address).
+ *
+ * In such cases, if we rely on '/proc/iomem' entries to
+ * calculate the phys_offset, then we will have mismatch
+ * between the user-space and kernel space 'PHYS_OFFSET'
+ * value.
+ */
+ if (memranges.size)
+ set_phys_offset(memranges.ranges[0].start, "iomem");
+
+ dbgprint_mem_range("System RAM ranges;",
+ memranges.ranges, memranges.size);
+
+ return 0;
+}
+
+int arch_compat_trampoline(struct kexec_info *info)
+{
+ return 0;
+}
+
+int machine_verify_elf_rel(struct mem_ehdr *ehdr)
+{
+ return (ehdr->e_machine == EM_AARCH64);
+}
+
+enum aarch64_rel_type {
+ R_AARCH64_NONE = 0,
+ R_AARCH64_ABS64 = 257,
+ R_AARCH64_PREL32 = 261,
+ R_AARCH64_MOVW_UABS_G0_NC = 264,
+ R_AARCH64_MOVW_UABS_G1_NC = 266,
+ R_AARCH64_MOVW_UABS_G2_NC = 268,
+ R_AARCH64_MOVW_UABS_G3 =269,
+ R_AARCH64_LD_PREL_LO19 = 273,
+ R_AARCH64_ADR_PREL_LO21 = 274,
+ R_AARCH64_ADR_PREL_PG_HI21 = 275,
+ R_AARCH64_ADD_ABS_LO12_NC = 277,
+ R_AARCH64_JUMP26 = 282,
+ R_AARCH64_CALL26 = 283,
+ R_AARCH64_LDST64_ABS_LO12_NC = 286,
+ R_AARCH64_LDST128_ABS_LO12_NC = 299
+};
+
+static uint32_t get_bits(uint32_t value, int start, int end)
+{
+ uint32_t mask = ((uint32_t)1 << (end + 1 - start)) - 1;
+ return (value >> start) & mask;
+}
+
+void machine_apply_elf_rel(struct mem_ehdr *ehdr, struct mem_sym *UNUSED(sym),
+ unsigned long r_type, void *ptr, unsigned long address,
+ unsigned long value)
+{
+ uint64_t *loc64;
+ uint32_t *loc32;
+ uint64_t *location = (uint64_t *)ptr;
+ uint64_t data = *location;
+ uint64_t imm;
+ const char *type = NULL;
+
+ switch((enum aarch64_rel_type)r_type) {
+ case R_AARCH64_ABS64:
+ type = "ABS64";
+ loc64 = ptr;
+ *loc64 = cpu_to_elf64(ehdr, value);
+ break;
+ case R_AARCH64_PREL32:
+ type = "PREL32";
+ loc32 = ptr;
+ *loc32 = cpu_to_elf32(ehdr, value - address);
+ break;
+
+ /* Set a MOV[KZ] immediate field to bits [15:0] of X. No overflow check */
+ case R_AARCH64_MOVW_UABS_G0_NC:
+ type = "MOVW_UABS_G0_NC";
+ loc32 = ptr;
+ imm = get_bits(value, 0, 15);
+ *loc32 = cpu_to_le32(le32_to_cpu(*loc32) + (imm << 5));
+ break;
+ /* Set a MOV[KZ] immediate field to bits [31:16] of X. No overflow check */
+ case R_AARCH64_MOVW_UABS_G1_NC:
+ type = "MOVW_UABS_G1_NC";
+ loc32 = ptr;
+ imm = get_bits(value, 16, 31);
+ *loc32 = cpu_to_le32(le32_to_cpu(*loc32) + (imm << 5));
+ break;
+ /* Set a MOV[KZ] immediate field to bits [47:32] of X. No overflow check */
+ case R_AARCH64_MOVW_UABS_G2_NC:
+ type = "MOVW_UABS_G2_NC";
+ loc32 = ptr;
+ imm = get_bits(value, 32, 47);
+ *loc32 = cpu_to_le32(le32_to_cpu(*loc32) + (imm << 5));
+ break;
+ /* Set a MOV[KZ] immediate field to bits [63:48] of X */
+ case R_AARCH64_MOVW_UABS_G3:
+ type = "MOVW_UABS_G3";
+ loc32 = ptr;
+ imm = get_bits(value, 48, 63);
+ *loc32 = cpu_to_le32(le32_to_cpu(*loc32) + (imm << 5));
+ break;
+
+ case R_AARCH64_LD_PREL_LO19:
+ type = "LD_PREL_LO19";
+ loc32 = ptr;
+ *loc32 = cpu_to_le32(le32_to_cpu(*loc32)
+ + (((value - address) << 3) & 0xffffe0));
+ break;
+ case R_AARCH64_ADR_PREL_LO21:
+ if (value & 3)
+ die("%s: ERROR Unaligned value: %lx\n", __func__,
+ value);
+ type = "ADR_PREL_LO21";
+ loc32 = ptr;
+ *loc32 = cpu_to_le32(le32_to_cpu(*loc32)
+ + (((value - address) << 3) & 0xffffe0));
+ break;
+ case R_AARCH64_ADR_PREL_PG_HI21:
+ type = "ADR_PREL_PG_HI21";
+ imm = ((value & ~0xfff) - (address & ~0xfff)) >> 12;
+ loc32 = ptr;
+ *loc32 = cpu_to_le32(le32_to_cpu(*loc32)
+ + ((imm & 3) << 29) + ((imm & 0x1ffffc) << (5 - 2)));
+ break;
+ case R_AARCH64_ADD_ABS_LO12_NC:
+ type = "ADD_ABS_LO12_NC";
+ loc32 = ptr;
+ *loc32 = cpu_to_le32(le32_to_cpu(*loc32)
+ + ((value & 0xfff) << 10));
+ break;
+ case R_AARCH64_JUMP26:
+ type = "JUMP26";
+ loc32 = ptr;
+ *loc32 = cpu_to_le32(le32_to_cpu(*loc32)
+ + (((value - address) >> 2) & 0x3ffffff));
+ break;
+ case R_AARCH64_CALL26:
+ type = "CALL26";
+ loc32 = ptr;
+ *loc32 = cpu_to_le32(le32_to_cpu(*loc32)
+ + (((value - address) >> 2) & 0x3ffffff));
+ break;
+ /* encode imm field with bits [11:3] of value */
+ case R_AARCH64_LDST64_ABS_LO12_NC:
+ if (value & 7)
+ die("%s: ERROR Unaligned value: %lx\n", __func__,
+ value);
+ type = "LDST64_ABS_LO12_NC";
+ loc32 = ptr;
+ *loc32 = cpu_to_le32(le32_to_cpu(*loc32)
+ + ((value & 0xff8) << (10 - 3)));
+ break;
+
+ /* encode imm field with bits [11:4] of value */
+ case R_AARCH64_LDST128_ABS_LO12_NC:
+ if (value & 15)
+ die("%s: ERROR Unaligned value: %lx\n", __func__,
+ value);
+ type = "LDST128_ABS_LO12_NC";
+ loc32 = ptr;
+ imm = value & 0xff0;
+ *loc32 = cpu_to_le32(le32_to_cpu(*loc32) + (imm << (10 - 4)));
+ break;
+ default:
+ die("%s: ERROR Unknown type: %lu\n", __func__, r_type);
+ break;
+ }
+
+ dbgprintf("%s: %s %016lx->%016lx\n", __func__, type, data, *location);
+}
+
+void arch_reuse_initrd(void)
+{
+ reuse_initrd = 1;
+}
+
+void arch_update_purgatory(struct kexec_info *UNUSED(info))
+{
+}
diff --git a/kexec/arch/arm64/kexec-arm64.h b/kexec/arch/arm64/kexec-arm64.h
new file mode 100644
index 0000000..95fb5c2
--- /dev/null
+++ b/kexec/arch/arm64/kexec-arm64.h
@@ -0,0 +1,84 @@
+/*
+ * ARM64 kexec.
+ */
+
+#if !defined(KEXEC_ARM64_H)
+#define KEXEC_ARM64_H
+
+#include <stdbool.h>
+#include <sys/types.h>
+
+#include "image-header.h"
+#include "kexec.h"
+
+#define KEXEC_SEGMENT_MAX 64
+
+#define BOOT_BLOCK_VERSION 17
+#define BOOT_BLOCK_LAST_COMP_VERSION 16
+#define COMMAND_LINE_SIZE 2048 /* from kernel */
+
+#define KiB(x) ((x) * 1024UL)
+#define MiB(x) (KiB(x) * 1024UL)
+#define GiB(x) (MiB(x) * 1024UL)
+
+#define ULONGLONG_MAX (~0ULL)
+
+/*
+ * Incorrect address
+ */
+#define NOT_KV_ADDR (0x0)
+#define NOT_PADDR (ULONGLONG_MAX)
+
+int elf_arm64_probe(const char *kernel_buf, off_t kernel_size);
+int elf_arm64_load(int argc, char **argv, const char *kernel_buf,
+ off_t kernel_size, struct kexec_info *info);
+void elf_arm64_usage(void);
+
+int image_arm64_probe(const char *kernel_buf, off_t kernel_size);
+int image_arm64_load(int argc, char **argv, const char *kernel_buf,
+ off_t kernel_size, struct kexec_info *info);
+void image_arm64_usage(void);
+
+int uImage_arm64_probe(const char *buf, off_t len);
+int uImage_arm64_load(int argc, char **argv, const char *buf, off_t len,
+ struct kexec_info *info);
+void uImage_arm64_usage(void);
+
+int pez_arm64_probe(const char *kernel_buf, off_t kernel_size);
+int pez_arm64_load(int argc, char **argv, const char *buf, off_t len,
+ struct kexec_info *info);
+void pez_arm64_usage(void);
+
+
+extern off_t initrd_base;
+extern off_t initrd_size;
+
+/**
+ * struct arm64_mem - Memory layout info.
+ */
+
+struct arm64_mem {
+ int64_t phys_offset;
+ uint64_t text_offset;
+ uint64_t image_size;
+ uint64_t vp_offset;
+};
+
+#define arm64_mem_ngv UINT64_MAX
+extern struct arm64_mem arm64_mem;
+
+uint64_t get_phys_offset(void);
+uint64_t get_vp_offset(void);
+int get_page_offset(unsigned long *offset);
+
+static inline void reset_vp_offset(void)
+{
+ arm64_mem.vp_offset = arm64_mem_ngv;
+}
+
+int arm64_process_image_header(const struct arm64_image_header *h);
+unsigned long arm64_locate_kernel_segment(struct kexec_info *info);
+int arm64_load_other_segments(struct kexec_info *info,
+ unsigned long image_base);
+
+#endif
diff --git a/kexec/arch/arm64/kexec-elf-arm64.c b/kexec/arch/arm64/kexec-elf-arm64.c
new file mode 100644
index 0000000..e14f8e9
--- /dev/null
+++ b/kexec/arch/arm64/kexec-elf-arm64.c
@@ -0,0 +1,170 @@
+/*
+ * ARM64 kexec elf support.
+ */
+
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <linux/elf.h>
+
+#include "arch/options.h"
+#include "crashdump-arm64.h"
+#include "kexec-arm64.h"
+#include "kexec-elf.h"
+#include "kexec-syscall.h"
+
+int elf_arm64_probe(const char *kernel_buf, off_t kernel_size)
+{
+ struct mem_ehdr ehdr;
+ int result;
+
+ result = build_elf_exec_info(kernel_buf, kernel_size, &ehdr, 0);
+
+ if (result < 0) {
+ dbgprintf("%s: Not an ELF executable.\n", __func__);
+ goto on_exit;
+ }
+
+ if (ehdr.e_machine != EM_AARCH64) {
+ dbgprintf("%s: Not an AARCH64 ELF executable.\n", __func__);
+ result = -1;
+ goto on_exit;
+ }
+
+ result = 0;
+on_exit:
+ free_elf_info(&ehdr);
+ return result;
+}
+
+int elf_arm64_load(int argc, char **argv, const char *kernel_buf,
+ off_t kernel_size, struct kexec_info *info)
+{
+ const struct arm64_image_header *header = NULL;
+ unsigned long kernel_segment;
+ struct mem_ehdr ehdr;
+ int result;
+ int i;
+
+ if (info->file_mode) {
+ fprintf(stderr,
+ "ELF executable is not supported in kexec_file\n");
+
+ return EFAILED;
+ }
+
+ result = build_elf_exec_info(kernel_buf, kernel_size, &ehdr, 0);
+
+ if (result < 0) {
+ dbgprintf("%s: build_elf_exec_info failed\n", __func__);
+ goto exit;
+ }
+
+ /* Find and process the arm64 image header. */
+
+ for (i = 0; i < ehdr.e_phnum; i++) {
+ struct mem_phdr *phdr = &ehdr.e_phdr[i];
+ unsigned long header_offset;
+
+ if (phdr->p_type != PT_LOAD)
+ continue;
+
+ /*
+ * When CONFIG_ARM64_RANDOMIZE_TEXT_OFFSET=y the image header
+ * could be offset in the elf segment. The linker script sets
+ * ehdr.e_entry to the start of text.
+ */
+
+ header_offset = ehdr.e_entry - phdr->p_vaddr;
+
+ header = (const struct arm64_image_header *)(
+ kernel_buf + phdr->p_offset + header_offset);
+
+ if (!arm64_process_image_header(header)) {
+ dbgprintf("%s: e_entry: %016llx\n", __func__,
+ ehdr.e_entry);
+ dbgprintf("%s: p_vaddr: %016llx\n", __func__,
+ phdr->p_vaddr);
+ dbgprintf("%s: header_offset: %016lx\n", __func__,
+ header_offset);
+
+ break;
+ }
+ }
+
+ if (i == ehdr.e_phnum) {
+ dbgprintf("%s: Valid arm64 header not found\n", __func__);
+ result = EFAILED;
+ goto exit;
+ }
+
+ kernel_segment = arm64_locate_kernel_segment(info);
+
+ if (kernel_segment == ULONG_MAX) {
+ dbgprintf("%s: Kernel segment is not allocated\n", __func__);
+ result = EFAILED;
+ goto exit;
+ }
+
+ arm64_mem.vp_offset = _ALIGN_DOWN(ehdr.e_entry, MiB(2));
+ if (!(info->kexec_flags & KEXEC_ON_CRASH))
+ arm64_mem.vp_offset -= kernel_segment - get_phys_offset();
+
+ dbgprintf("%s: kernel_segment: %016lx\n", __func__, kernel_segment);
+ dbgprintf("%s: text_offset: %016lx\n", __func__,
+ arm64_mem.text_offset);
+ dbgprintf("%s: image_size: %016lx\n", __func__,
+ arm64_mem.image_size);
+ dbgprintf("%s: phys_offset: %016lx\n", __func__,
+ arm64_mem.phys_offset);
+ dbgprintf("%s: vp_offset: %016lx\n", __func__,
+ arm64_mem.vp_offset);
+ dbgprintf("%s: PE format: %s\n", __func__,
+ (arm64_header_check_pe_sig(header) ? "yes" : "no"));
+
+ /* create and initialize elf core header segment */
+ if (info->kexec_flags & KEXEC_ON_CRASH) {
+ result = load_crashdump_segments(info);
+ if (result) {
+ dbgprintf("%s: Creating eflcorehdr failed.\n",
+ __func__);
+ goto exit;
+ }
+ }
+
+ /* load the kernel */
+ if (info->kexec_flags & KEXEC_ON_CRASH)
+ /*
+ * offset addresses in elf header in order to load
+ * vmlinux (elf_exec) into crash kernel's memory
+ */
+ fixup_elf_addrs(&ehdr);
+
+ result = elf_exec_load(&ehdr, info);
+
+ if (result) {
+ dbgprintf("%s: elf_exec_load failed\n", __func__);
+ goto exit;
+ }
+
+ /* load additional data */
+ result = arm64_load_other_segments(info, kernel_segment
+ + arm64_mem.text_offset);
+
+exit:
+ reset_vp_offset();
+ free_elf_info(&ehdr);
+ if (result)
+ fprintf(stderr, "kexec: Bad elf image file, load failed.\n");
+ return result;
+}
+
+void elf_arm64_usage(void)
+{
+ printf(
+" An ARM64 ELF image, big or little endian.\n"
+" Typically vmlinux or a stripped version of vmlinux.\n\n");
+}
diff --git a/kexec/arch/arm64/kexec-image-arm64.c b/kexec/arch/arm64/kexec-image-arm64.c
new file mode 100644
index 0000000..a196747
--- /dev/null
+++ b/kexec/arch/arm64/kexec-image-arm64.c
@@ -0,0 +1,119 @@
+/*
+ * ARM64 kexec binary image support.
+ */
+
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include "crashdump-arm64.h"
+#include "image-header.h"
+#include "kexec.h"
+#include "kexec-arm64.h"
+#include "kexec-syscall.h"
+#include "arch/options.h"
+
+int image_arm64_probe(const char *kernel_buf, off_t kernel_size)
+{
+ const struct arm64_image_header *h;
+
+ if (kernel_size < sizeof(struct arm64_image_header)) {
+ dbgprintf("%s: No arm64 image header.\n", __func__);
+ return -1;
+ }
+
+ h = (const struct arm64_image_header *)(kernel_buf);
+
+ if (!arm64_header_check_magic(h)) {
+ dbgprintf("%s: Bad arm64 image header.\n", __func__);
+ return -1;
+ }
+
+ return 0;
+}
+
+int image_arm64_load(int argc, char **argv, const char *kernel_buf,
+ off_t kernel_size, struct kexec_info *info)
+{
+ const struct arm64_image_header *header;
+ unsigned long kernel_segment;
+ int result;
+
+ if (info->file_mode) {
+ if (arm64_opts.initrd) {
+ info->initrd_fd = open(arm64_opts.initrd, O_RDONLY);
+ if (info->initrd_fd == -1) {
+ fprintf(stderr,
+ "Could not open initrd file %s:%s\n",
+ arm64_opts.initrd, strerror(errno));
+ result = EFAILED;
+ goto exit;
+ }
+ }
+
+ if (arm64_opts.command_line) {
+ info->command_line = (char *)arm64_opts.command_line;
+ info->command_line_len =
+ strlen(arm64_opts.command_line) + 1;
+ }
+
+ return 0;
+ }
+
+ header = (const struct arm64_image_header *)(kernel_buf);
+
+ if (arm64_process_image_header(header))
+ return EFAILED;
+
+ kernel_segment = arm64_locate_kernel_segment(info);
+
+ if (kernel_segment == ULONG_MAX) {
+ dbgprintf("%s: Kernel segment is not allocated\n", __func__);
+ result = EFAILED;
+ goto exit;
+ }
+
+ dbgprintf("%s: kernel_segment: %016lx\n", __func__, kernel_segment);
+ dbgprintf("%s: text_offset: %016lx\n", __func__,
+ arm64_mem.text_offset);
+ dbgprintf("%s: image_size: %016lx\n", __func__,
+ arm64_mem.image_size);
+ dbgprintf("%s: phys_offset: %016lx\n", __func__,
+ arm64_mem.phys_offset);
+ dbgprintf("%s: vp_offset: %016lx\n", __func__,
+ arm64_mem.vp_offset);
+ dbgprintf("%s: PE format: %s\n", __func__,
+ (arm64_header_check_pe_sig(header) ? "yes" : "no"));
+
+ /* create and initialize elf core header segment */
+ if (info->kexec_flags & KEXEC_ON_CRASH) {
+ result = load_crashdump_segments(info);
+ if (result) {
+ dbgprintf("%s: Creating eflcorehdr failed.\n",
+ __func__);
+ goto exit;
+ }
+ }
+
+ /* load the kernel */
+ add_segment_phys_virt(info, kernel_buf, kernel_size,
+ kernel_segment + arm64_mem.text_offset,
+ arm64_mem.image_size, 0);
+
+ /* load additional data */
+ result = arm64_load_other_segments(info, kernel_segment
+ + arm64_mem.text_offset);
+
+exit:
+ if (result)
+ fprintf(stderr, "kexec: load failed.\n");
+ return result;
+}
+
+void image_arm64_usage(void)
+{
+ printf(
+" An ARM64 binary image, compressed or not, big or little endian.\n"
+" Typically an Image file.\n\n");
+}
diff --git a/kexec/arch/arm64/kexec-uImage-arm64.c b/kexec/arch/arm64/kexec-uImage-arm64.c
new file mode 100644
index 0000000..c466913
--- /dev/null
+++ b/kexec/arch/arm64/kexec-uImage-arm64.c
@@ -0,0 +1,52 @@
+/*
+ * uImage support added by David Woodhouse <dwmw2@infradead.org>
+ */
+#include <stdint.h>
+#include <string.h>
+#include <sys/types.h>
+#include <image.h>
+#include <kexec-uImage.h>
+#include "../../kexec.h"
+#include "kexec-arm64.h"
+
+int uImage_arm64_probe(const char *buf, off_t len)
+{
+ int ret;
+
+ ret = uImage_probe_kernel(buf, len, IH_ARCH_ARM64);
+
+ /* 0 - valid uImage.
+ * -1 - uImage is corrupted.
+ * 1 - image is not a uImage.
+ */
+ if (!ret)
+ return 0;
+ else
+ return -1;
+}
+
+int uImage_arm64_load(int argc, char **argv, const char *buf, off_t len,
+ struct kexec_info *info)
+{
+ struct Image_info img;
+ int ret;
+
+ if (info->file_mode) {
+ fprintf(stderr,
+ "uImage is not supported in kexec_file\n");
+
+ return EFAILED;
+ }
+
+ ret = uImage_load(buf, len, &img);
+ if (ret)
+ return ret;
+
+ return image_arm64_load(argc, argv, img.buf, img.len, info);
+}
+
+void uImage_arm64_usage(void)
+{
+ printf(
+" An ARM64 U-boot uImage file, compressed or not, big or little endian.\n\n");
+}
diff --git a/kexec/arch/arm64/kexec-vmlinuz-arm64.c b/kexec/arch/arm64/kexec-vmlinuz-arm64.c
new file mode 100644
index 0000000..c0ee47c
--- /dev/null
+++ b/kexec/arch/arm64/kexec-vmlinuz-arm64.c
@@ -0,0 +1,110 @@
+/*
+ * ARM64 PE compressed Image (vmlinuz, ZBOOT) support.
+ *
+ * Several distros use 'make zinstall' rule inside
+ * 'arch/arm64/boot/Makefile' to install the arm64
+ * ZBOOT compressed file inside the boot destination
+ * directory (for e.g. /boot).
+ *
+ * Currently we cannot use kexec_file_load() to load vmlinuz
+ * PE images that self decompress.
+ *
+ * To support ZBOOT, we should:
+ * a). Copy the compressed contents of vmlinuz to a temporary file.
+ * b). Decompress (gunzip-decompress) the contents inside the
+ * temporary file.
+ * c). Validate the resulting image and write it back to the
+ * temporary file.
+ * d). Pass the 'fd' of the temporary file to the kernel space.
+ *
+ * Note this, module doesn't provide a _load() function instead
+ * relying on image_arm64_load() to load the resulting decompressed
+ * image.
+ *
+ * So basically the kernel space still gets a decompressed
+ * kernel image to load via kexec-tools.
+ */
+
+#define _GNU_SOURCE
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include "kexec-arm64.h"
+#include <kexec-pe-zboot.h>
+#include "arch/options.h"
+
+static int kernel_fd = -1;
+
+/* Returns:
+ * -1 : in case of error/invalid format (not a valid PE+compressed ZBOOT format.
+ */
+int pez_arm64_probe(const char *kernel_buf, off_t kernel_size)
+{
+ int ret = -1;
+ const struct arm64_image_header *h;
+ char *buf;
+ off_t buf_sz;
+
+ buf = (char *)kernel_buf;
+ buf_sz = kernel_size;
+ if (!buf)
+ return -1;
+ h = (const struct arm64_image_header *)buf;
+
+ dbgprintf("%s: PROBE.\n", __func__);
+ if (buf_sz < sizeof(struct arm64_image_header)) {
+ dbgprintf("%s: Not large enough to be a PE image.\n", __func__);
+ return -1;
+ }
+ if (!arm64_header_check_pe_sig(h)) {
+ dbgprintf("%s: Not an PE image.\n", __func__);
+ return -1;
+ }
+
+ if (buf_sz < sizeof(struct arm64_image_header) + h->pe_header) {
+ dbgprintf("%s: PE image offset larger than image.\n", __func__);
+ return -1;
+ }
+
+ if (memcmp(&buf[h->pe_header],
+ arm64_pe_machtype, sizeof(arm64_pe_machtype))) {
+ dbgprintf("%s: PE header doesn't match machine type.\n", __func__);
+ return -1;
+ }
+
+ ret = pez_prepare(buf, buf_sz, &kernel_fd);
+
+ if (!ret) {
+ /* validate the arm64 specific header */
+ struct arm64_image_header hdr_check;
+ if (read(kernel_fd, &hdr_check, sizeof(hdr_check)) != sizeof(hdr_check))
+ goto bad_header;
+
+ lseek(kernel_fd, 0, SEEK_SET);
+
+ if (!arm64_header_check_magic(&hdr_check)) {
+ dbgprintf("%s: Bad arm64 image header.\n", __func__);
+ goto bad_header;
+ }
+ }
+
+ return ret;
+bad_header:
+ close(kernel_fd);
+ free(buf);
+ return -1;
+}
+
+int pez_arm64_load(int argc, char **argv, const char *buf, off_t len,
+ struct kexec_info *info)
+{
+ info->kernel_fd = kernel_fd;
+ return image_arm64_load(argc, argv, buf, len, info);
+}
+
+void pez_arm64_usage(void)
+{
+ printf(
+" An ARM64 vmlinuz, PE image of a compressed, little endian.\n"
+" kernel, built with ZBOOT enabled.\n\n");
+}