1408 lines
33 KiB
Diff
1408 lines
33 KiB
Diff
|
From 522df5f7217fda01ece3f6ac3e9987b0320c2bb0 Mon Sep 17 00:00:00 2001
|
||
|
From: Geoff Levand <geoff@infradead.org>
|
||
|
Date: Wed, 21 Sep 2016 18:14:25 +0000
|
||
|
Subject: [PATCH 2/3] arm64: Add arm64 kexec support
|
||
|
|
||
|
Add kexec reboot support for ARM64 platforms.
|
||
|
|
||
|
Signed-off-by: Geoff Levand <geoff@infradead.org>
|
||
|
Tested-By: Pratyush Anand <panand@redhat.com>
|
||
|
Tested-By: Matthias Brugger <mbrugger@suse.com>
|
||
|
Signed-off-by: Simon Horman <horms@verge.net.au>
|
||
|
---
|
||
|
configure.ac | 3 +
|
||
|
kexec/Makefile | 1 +
|
||
|
kexec/arch/arm64/Makefile | 40 +++
|
||
|
kexec/arch/arm64/crashdump-arm64.c | 21 ++
|
||
|
kexec/arch/arm64/crashdump-arm64.h | 12 +
|
||
|
kexec/arch/arm64/image-header.h | 146 ++++++++
|
||
|
kexec/arch/arm64/include/arch/options.h | 39 ++
|
||
|
kexec/arch/arm64/kexec-arm64.c | 615 ++++++++++++++++++++++++++++++++
|
||
|
kexec/arch/arm64/kexec-arm64.h | 71 ++++
|
||
|
kexec/arch/arm64/kexec-elf-arm64.c | 146 ++++++++
|
||
|
kexec/arch/arm64/kexec-image-arm64.c | 41 +++
|
||
|
kexec/kexec-syscall.h | 8 +-
|
||
|
purgatory/Makefile | 1 +
|
||
|
purgatory/arch/arm64/Makefile | 18 +
|
||
|
purgatory/arch/arm64/entry.S | 51 +++
|
||
|
purgatory/arch/arm64/purgatory-arm64.c | 19 +
|
||
|
16 files changed, 1230 insertions(+), 2 deletions(-)
|
||
|
create mode 100644 kexec/arch/arm64/Makefile
|
||
|
create mode 100644 kexec/arch/arm64/crashdump-arm64.c
|
||
|
create mode 100644 kexec/arch/arm64/crashdump-arm64.h
|
||
|
create mode 100644 kexec/arch/arm64/image-header.h
|
||
|
create mode 100644 kexec/arch/arm64/include/arch/options.h
|
||
|
create mode 100644 kexec/arch/arm64/kexec-arm64.c
|
||
|
create mode 100644 kexec/arch/arm64/kexec-arm64.h
|
||
|
create mode 100644 kexec/arch/arm64/kexec-elf-arm64.c
|
||
|
create mode 100644 kexec/arch/arm64/kexec-image-arm64.c
|
||
|
create mode 100644 purgatory/arch/arm64/Makefile
|
||
|
create mode 100644 purgatory/arch/arm64/entry.S
|
||
|
create mode 100644 purgatory/arch/arm64/purgatory-arm64.c
|
||
|
|
||
|
diff --git a/configure.ac b/configure.ac
|
||
|
index 2bc5767..252b048 100644
|
||
|
--- a/configure.ac
|
||
|
+++ b/configure.ac
|
||
|
@@ -34,6 +34,9 @@ case $target_cpu in
|
||
|
ARCH="ppc64"
|
||
|
SUBARCH="LE"
|
||
|
;;
|
||
|
+ aarch64* )
|
||
|
+ ARCH="arm64"
|
||
|
+ ;;
|
||
|
arm* )
|
||
|
ARCH="arm"
|
||
|
;;
|
||
|
diff --git a/kexec/Makefile b/kexec/Makefile
|
||
|
index cc3f08b..39f365f 100644
|
||
|
--- a/kexec/Makefile
|
||
|
+++ b/kexec/Makefile
|
||
|
@@ -79,6 +79,7 @@ KEXEC_SRCS += $($(ARCH)_DT_OPS)
|
||
|
|
||
|
include $(srcdir)/kexec/arch/alpha/Makefile
|
||
|
include $(srcdir)/kexec/arch/arm/Makefile
|
||
|
+include $(srcdir)/kexec/arch/arm64/Makefile
|
||
|
include $(srcdir)/kexec/arch/i386/Makefile
|
||
|
include $(srcdir)/kexec/arch/ia64/Makefile
|
||
|
include $(srcdir)/kexec/arch/m68k/Makefile
|
||
|
diff --git a/kexec/arch/arm64/Makefile b/kexec/arch/arm64/Makefile
|
||
|
new file mode 100644
|
||
|
index 0000000..37414dc
|
||
|
--- /dev/null
|
||
|
+++ b/kexec/arch/arm64/Makefile
|
||
|
@@ -0,0 +1,40 @@
|
||
|
+
|
||
|
+arm64_FS2DT += kexec/fs2dt.c
|
||
|
+arm64_FS2DT_INCLUDE += -include $(srcdir)/kexec/arch/arm64/kexec-arm64.h \
|
||
|
+ -include $(srcdir)/kexec/arch/arm64/crashdump-arm64.h
|
||
|
+
|
||
|
+arm64_DT_OPS += kexec/dt-ops.c
|
||
|
+
|
||
|
+arm64_CPPFLAGS += -I $(srcdir)/kexec/
|
||
|
+
|
||
|
+arm64_KEXEC_SRCS += \
|
||
|
+ kexec/arch/arm64/kexec-arm64.c \
|
||
|
+ kexec/arch/arm64/kexec-image-arm64.c \
|
||
|
+ kexec/arch/arm64/kexec-elf-arm64.c \
|
||
|
+ kexec/arch/arm64/crashdump-arm64.c
|
||
|
+
|
||
|
+arm64_ARCH_REUSE_INITRD =
|
||
|
+arm64_ADD_SEGMENT =
|
||
|
+arm64_VIRT_TO_PHYS =
|
||
|
+arm64_PHYS_TO_VIRT =
|
||
|
+
|
||
|
+dist += $(arm64_KEXEC_SRCS) \
|
||
|
+ kexec/arch/arm64/Makefile \
|
||
|
+ kexec/arch/arm64/kexec-arm64.h \
|
||
|
+ kexec/arch/arm64/crashdump-arm64.h
|
||
|
+
|
||
|
+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..d2272c8
|
||
|
--- /dev/null
|
||
|
+++ b/kexec/arch/arm64/crashdump-arm64.c
|
||
|
@@ -0,0 +1,21 @@
|
||
|
+/*
|
||
|
+ * ARM64 crashdump.
|
||
|
+ */
|
||
|
+
|
||
|
+#define _GNU_SOURCE
|
||
|
+
|
||
|
+#include <errno.h>
|
||
|
+#include <linux/elf.h>
|
||
|
+
|
||
|
+#include "kexec.h"
|
||
|
+#include "crashdump.h"
|
||
|
+#include "crashdump-arm64.h"
|
||
|
+#include "kexec-arm64.h"
|
||
|
+#include "kexec-elf.h"
|
||
|
+
|
||
|
+struct memory_ranges usablemem_rgns = {};
|
||
|
+
|
||
|
+int is_crashkernel_mem_reserved(void)
|
||
|
+{
|
||
|
+ return 0;
|
||
|
+}
|
||
|
diff --git a/kexec/arch/arm64/crashdump-arm64.h b/kexec/arch/arm64/crashdump-arm64.h
|
||
|
new file mode 100644
|
||
|
index 0000000..f33c7a2
|
||
|
--- /dev/null
|
||
|
+++ b/kexec/arch/arm64/crashdump-arm64.h
|
||
|
@@ -0,0 +1,12 @@
|
||
|
+/*
|
||
|
+ * ARM64 crashdump.
|
||
|
+ */
|
||
|
+
|
||
|
+#if !defined(CRASHDUMP_ARM64_H)
|
||
|
+#define CRASHDUMP_ARM64_H
|
||
|
+
|
||
|
+#include "kexec.h"
|
||
|
+
|
||
|
+extern struct memory_ranges usablemem_rgns;
|
||
|
+
|
||
|
+#endif
|
||
|
diff --git a/kexec/arch/arm64/image-header.h b/kexec/arch/arm64/image-header.h
|
||
|
new file mode 100644
|
||
|
index 0000000..158d411
|
||
|
--- /dev/null
|
||
|
+++ b/kexec/arch/arm64/image-header.h
|
||
|
@@ -0,0 +1,146 @@
|
||
|
+/*
|
||
|
+ * 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 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..a17d933
|
||
|
--- /dev/null
|
||
|
+++ b/kexec/arch/arm64/include/arch/options.h
|
||
|
@@ -0,0 +1,39 @@
|
||
|
+#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_ARCH_MAX ((OPT_MAX)+4)
|
||
|
+
|
||
|
+#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 }, \
|
||
|
+ { "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"
|
||
|
+" --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;
|
||
|
+};
|
||
|
+
|
||
|
+extern struct arm64_opts arm64_opts;
|
||
|
+
|
||
|
+#endif
|
||
|
diff --git a/kexec/arch/arm64/kexec-arm64.c b/kexec/arch/arm64/kexec-arm64.c
|
||
|
new file mode 100644
|
||
|
index 0000000..2e8839a
|
||
|
--- /dev/null
|
||
|
+++ b/kexec/arch/arm64/kexec-arm64.c
|
||
|
@@ -0,0 +1,615 @@
|
||
|
+/*
|
||
|
+ * ARM64 kexec.
|
||
|
+ */
|
||
|
+
|
||
|
+#define _GNU_SOURCE
|
||
|
+
|
||
|
+#include <assert.h>
|
||
|
+#include <errno.h>
|
||
|
+#include <getopt.h>
|
||
|
+#include <inttypes.h>
|
||
|
+#include <libfdt.h>
|
||
|
+#include <limits.h>
|
||
|
+#include <stdlib.h>
|
||
|
+#include <sys/stat.h>
|
||
|
+#include <linux/elf-em.h>
|
||
|
+#include <elf.h>
|
||
|
+
|
||
|
+#include "kexec.h"
|
||
|
+#include "kexec-arm64.h"
|
||
|
+#include "crashdump.h"
|
||
|
+#include "crashdump-arm64.h"
|
||
|
+#include "dt-ops.h"
|
||
|
+#include "fs2dt.h"
|
||
|
+#include "kexec-syscall.h"
|
||
|
+#include "arch/options.h"
|
||
|
+
|
||
|
+/* 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},
|
||
|
+};
|
||
|
+
|
||
|
+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;
|
||
|
+
|
||
|
+ 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_PANIC:
|
||
|
+ die("load-panic (-p) not supported");
|
||
|
+ 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__, arm64_opts.dtb);
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+/**
|
||
|
+ * 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;
|
||
|
+}
|
||
|
+
|
||
|
+/**
|
||
|
+ * setup_2nd_dtb - Setup the 2nd stage kernel's dtb.
|
||
|
+ */
|
||
|
+
|
||
|
+static int setup_2nd_dtb(struct dtb *dtb, char *command_line)
|
||
|
+{
|
||
|
+ int result;
|
||
|
+
|
||
|
+ result = fdt_check_header(dtb->buf);
|
||
|
+
|
||
|
+ if (result) {
|
||
|
+ fprintf(stderr, "kexec: Invalid 2nd device tree.\n");
|
||
|
+ return -EFAILED;
|
||
|
+ }
|
||
|
+
|
||
|
+ result = set_bootargs(dtb, command_line);
|
||
|
+
|
||
|
+ dump_reservemap(dtb);
|
||
|
+
|
||
|
+ return result;
|
||
|
+}
|
||
|
+
|
||
|
+unsigned long arm64_locate_kernel_segment(struct kexec_info *info)
|
||
|
+{
|
||
|
+ unsigned long hole;
|
||
|
+
|
||
|
+ 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;
|
||
|
+ char *initrd_buf = NULL;
|
||
|
+ struct dtb dtb;
|
||
|
+ char command_line[COMMAND_LINE_SIZE] = "";
|
||
|
+
|
||
|
+ if (arm64_opts.command_line) {
|
||
|
+ strncpy(command_line, arm64_opts.command_line,
|
||
|
+ sizeof(command_line));
|
||
|
+ command_line[sizeof(command_line) - 1] = 0;
|
||
|
+ }
|
||
|
+
|
||
|
+ 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);
|
||
|
+
|
||
|
+ if (result)
|
||
|
+ return -EFAILED;
|
||
|
+
|
||
|
+ /* Put the other segments after the image. */
|
||
|
+
|
||
|
+ hole_min = image_base + arm64_mem.image_size;
|
||
|
+ 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. As specified in
|
||
|
+ * booting.txt, align to 1 GiB.
|
||
|
+ */
|
||
|
+
|
||
|
+ initrd_base = add_buffer_phys_virt(info, initrd_buf,
|
||
|
+ initrd_size, initrd_size, GiB(1),
|
||
|
+ hole_min, hole_max, 1, 0);
|
||
|
+
|
||
|
+ /* initrd_base is valid if we got here. */
|
||
|
+
|
||
|
+ dbgprintf("initrd: base %lx, size %lxh (%ld)\n",
|
||
|
+ initrd_base, initrd_size, initrd_size);
|
||
|
+
|
||
|
+ /* Check size limit as specified in booting.txt. */
|
||
|
+
|
||
|
+ if (initrd_base - image_base + initrd_size > GiB(32)) {
|
||
|
+ fprintf(stderr, "kexec: Error: image + initrd too big.\n");
|
||
|
+ return -EFAILED;
|
||
|
+ }
|
||
|
+
|
||
|
+ result = dtb_set_initrd((char **)&dtb.buf,
|
||
|
+ &dtb.size, initrd_base,
|
||
|
+ initrd_base + initrd_size);
|
||
|
+
|
||
|
+ if (result)
|
||
|
+ return -EFAILED;
|
||
|
+ }
|
||
|
+ }
|
||
|
+
|
||
|
+ /* 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_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);
|
||
|
+}
|
||
|
+
|
||
|
+/**
|
||
|
+ * get_memory_ranges_iomem_cb - Helper for get_memory_ranges_iomem.
|
||
|
+ */
|
||
|
+
|
||
|
+static int get_memory_ranges_iomem_cb(void *data, int nr, char *str,
|
||
|
+ unsigned long long base, unsigned long long length)
|
||
|
+{
|
||
|
+ struct memory_range *r;
|
||
|
+
|
||
|
+ if (nr >= KEXEC_SEGMENT_MAX)
|
||
|
+ return -1;
|
||
|
+
|
||
|
+ r = (struct memory_range *)data + nr;
|
||
|
+ r->type = RANGE_RAM;
|
||
|
+ r->start = base;
|
||
|
+ r->end = base + length - 1;
|
||
|
+
|
||
|
+ set_phys_offset(r->start);
|
||
|
+
|
||
|
+ dbgprintf("%s: %016llx - %016llx : %s", __func__, r->start,
|
||
|
+ r->end, str);
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+/**
|
||
|
+ * get_memory_ranges_iomem - Try to get the memory ranges from /proc/iomem.
|
||
|
+ */
|
||
|
+
|
||
|
+static int get_memory_ranges_iomem(struct memory_range *array,
|
||
|
+ unsigned int *count)
|
||
|
+{
|
||
|
+ *count = kexec_iomem_for_each_line("System RAM\n",
|
||
|
+ get_memory_ranges_iomem_cb, array);
|
||
|
+
|
||
|
+ if (!*count) {
|
||
|
+ dbgprintf("%s: failed: No RAM found.\n", __func__);
|
||
|
+ return -EFAILED;
|
||
|
+ }
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+/**
|
||
|
+ * get_memory_ranges - Try to get the memory ranges some how.
|
||
|
+ */
|
||
|
+
|
||
|
+int get_memory_ranges(struct memory_range **range, int *ranges,
|
||
|
+ unsigned long kexec_flags)
|
||
|
+{
|
||
|
+ static struct memory_range array[KEXEC_SEGMENT_MAX];
|
||
|
+ unsigned int count;
|
||
|
+ int result;
|
||
|
+
|
||
|
+ result = get_memory_ranges_iomem(array, &count);
|
||
|
+
|
||
|
+ *range = result ? NULL : array;
|
||
|
+ *ranges = result ? 0 : count;
|
||
|
+
|
||
|
+ return result;
|
||
|
+}
|
||
|
+
|
||
|
+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);
|
||
|
+}
|
||
|
+
|
||
|
+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)
|
||
|
+{
|
||
|
+#if !defined(R_AARCH64_ABS64)
|
||
|
+# define R_AARCH64_ABS64 257
|
||
|
+#endif
|
||
|
+
|
||
|
+#if !defined(R_AARCH64_LD_PREL_LO19)
|
||
|
+# define R_AARCH64_LD_PREL_LO19 273
|
||
|
+#endif
|
||
|
+
|
||
|
+#if !defined(R_AARCH64_ADR_PREL_LO21)
|
||
|
+# define R_AARCH64_ADR_PREL_LO21 274
|
||
|
+#endif
|
||
|
+
|
||
|
+#if !defined(R_AARCH64_JUMP26)
|
||
|
+# define R_AARCH64_JUMP26 282
|
||
|
+#endif
|
||
|
+
|
||
|
+#if !defined(R_AARCH64_CALL26)
|
||
|
+# define R_AARCH64_CALL26 283
|
||
|
+#endif
|
||
|
+
|
||
|
+ uint64_t *loc64;
|
||
|
+ uint32_t *loc32;
|
||
|
+ uint64_t *location = (uint64_t *)ptr;
|
||
|
+ uint64_t data = *location;
|
||
|
+ const char *type = NULL;
|
||
|
+
|
||
|
+ switch(r_type) {
|
||
|
+ case R_AARCH64_ABS64:
|
||
|
+ type = "ABS64";
|
||
|
+ loc64 = ptr;
|
||
|
+ *loc64 = cpu_to_elf64(ehdr, elf64_to_cpu(ehdr, *loc64) + value);
|
||
|
+ 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_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;
|
||
|
+ 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..bac62f8
|
||
|
--- /dev/null
|
||
|
+++ b/kexec/arch/arm64/kexec-arm64.h
|
||
|
@@ -0,0 +1,71 @@
|
||
|
+/*
|
||
|
+ * 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 16
|
||
|
+
|
||
|
+#define BOOT_BLOCK_VERSION 17
|
||
|
+#define BOOT_BLOCK_LAST_COMP_VERSION 16
|
||
|
+#define COMMAND_LINE_SIZE 512
|
||
|
+
|
||
|
+#define KiB(x) ((x) * 1024UL)
|
||
|
+#define MiB(x) (KiB(x) * 1024UL)
|
||
|
+#define GiB(x) (MiB(x) * 1024UL)
|
||
|
+
|
||
|
+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);
|
||
|
+
|
||
|
+off_t initrd_base;
|
||
|
+off_t initrd_size;
|
||
|
+
|
||
|
+/**
|
||
|
+ * struct arm64_mem - Memory layout info.
|
||
|
+ */
|
||
|
+
|
||
|
+struct arm64_mem {
|
||
|
+ uint64_t phys_offset;
|
||
|
+ uint64_t text_offset;
|
||
|
+ uint64_t image_size;
|
||
|
+ uint64_t vp_offset;
|
||
|
+};
|
||
|
+
|
||
|
+#define arm64_mem_ngv UINT64_MAX
|
||
|
+struct arm64_mem arm64_mem;
|
||
|
+
|
||
|
+uint64_t get_phys_offset(void);
|
||
|
+uint64_t get_vp_offset(void);
|
||
|
+
|
||
|
+static inline void reset_vp_offset(void)
|
||
|
+{
|
||
|
+ arm64_mem.vp_offset = arm64_mem_ngv;
|
||
|
+}
|
||
|
+
|
||
|
+static inline void set_phys_offset(uint64_t v)
|
||
|
+{
|
||
|
+ if (arm64_mem.phys_offset == arm64_mem_ngv
|
||
|
+ || v < arm64_mem.phys_offset)
|
||
|
+ arm64_mem.phys_offset = v;
|
||
|
+}
|
||
|
+
|
||
|
+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..daf8bf0
|
||
|
--- /dev/null
|
||
|
+++ b/kexec/arch/arm64/kexec-elf-arm64.c
|
||
|
@@ -0,0 +1,146 @@
|
||
|
+/*
|
||
|
+ * ARM64 kexec elf support.
|
||
|
+ */
|
||
|
+
|
||
|
+#define _GNU_SOURCE
|
||
|
+
|
||
|
+#include <errno.h>
|
||
|
+#include <limits.h>
|
||
|
+#include <stdlib.h>
|
||
|
+#include <linux/elf.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->kexec_flags & KEXEC_ON_CRASH) {
|
||
|
+ fprintf(stderr, "kexec: kdump not yet supported on arm64\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));
|
||
|
+ 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"));
|
||
|
+
|
||
|
+ /* load the kernel */
|
||
|
+ result = elf_exec_load(&ehdr, info);
|
||
|
+
|
||
|
+ if (result) {
|
||
|
+ dbgprintf("%s: elf_exec_load failed\n", __func__);
|
||
|
+ goto exit;
|
||
|
+ }
|
||
|
+
|
||
|
+ 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..42d2ea7
|
||
|
--- /dev/null
|
||
|
+++ b/kexec/arch/arm64/kexec-image-arm64.c
|
||
|
@@ -0,0 +1,41 @@
|
||
|
+/*
|
||
|
+ * ARM64 kexec binary image support.
|
||
|
+ */
|
||
|
+
|
||
|
+#define _GNU_SOURCE
|
||
|
+#include "kexec-arm64.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;
|
||
|
+ }
|
||
|
+
|
||
|
+ fprintf(stderr, "kexec: ARM64 binary image files are currently NOT SUPPORTED.\n");
|
||
|
+ return -1;
|
||
|
+}
|
||
|
+
|
||
|
+int image_arm64_load(int argc, char **argv, const char *kernel_buf,
|
||
|
+ off_t kernel_size, struct kexec_info *info)
|
||
|
+{
|
||
|
+ return -1;
|
||
|
+}
|
||
|
+
|
||
|
+void image_arm64_usage(void)
|
||
|
+{
|
||
|
+ printf(
|
||
|
+" An ARM64 binary image, compressed or not, big or little endian.\n"
|
||
|
+" Typically an Image, Image.gz or Image.lzma file.\n\n");
|
||
|
+ printf(
|
||
|
+" ARM64 binary image files are currently NOT SUPPORTED.\n\n");
|
||
|
+}
|
||
|
diff --git a/kexec/kexec-syscall.h b/kexec/kexec-syscall.h
|
||
|
index ce2e20b..c0d0bea 100644
|
||
|
--- a/kexec/kexec-syscall.h
|
||
|
+++ b/kexec/kexec-syscall.h
|
||
|
@@ -39,8 +39,8 @@
|
||
|
#ifdef __s390__
|
||
|
#define __NR_kexec_load 277
|
||
|
#endif
|
||
|
-#ifdef __arm__
|
||
|
-#define __NR_kexec_load __NR_SYSCALL_BASE + 347
|
||
|
+#if defined(__arm__) || defined(__arm64__)
|
||
|
+#define __NR_kexec_load __NR_SYSCALL_BASE + 347
|
||
|
#endif
|
||
|
#if defined(__mips__)
|
||
|
#define __NR_kexec_load 4311
|
||
|
@@ -108,6 +108,7 @@ static inline long kexec_file_load(int kernel_fd, int initrd_fd,
|
||
|
#define KEXEC_ARCH_PPC64 (21 << 16)
|
||
|
#define KEXEC_ARCH_IA_64 (50 << 16)
|
||
|
#define KEXEC_ARCH_ARM (40 << 16)
|
||
|
+#define KEXEC_ARCH_ARM64 (183 << 16)
|
||
|
#define KEXEC_ARCH_S390 (22 << 16)
|
||
|
#define KEXEC_ARCH_SH (42 << 16)
|
||
|
#define KEXEC_ARCH_MIPS_LE (10 << 16)
|
||
|
@@ -153,5 +154,8 @@ static inline long kexec_file_load(int kernel_fd, int initrd_fd,
|
||
|
#ifdef __m68k__
|
||
|
#define KEXEC_ARCH_NATIVE KEXEC_ARCH_68K
|
||
|
#endif
|
||
|
+#if defined(__arm64__)
|
||
|
+#define KEXEC_ARCH_NATIVE KEXEC_ARCH_ARM64
|
||
|
+#endif
|
||
|
|
||
|
#endif /* KEXEC_SYSCALL_H */
|
||
|
diff --git a/purgatory/Makefile b/purgatory/Makefile
|
||
|
index 2b5c061..ca0443c 100644
|
||
|
--- a/purgatory/Makefile
|
||
|
+++ b/purgatory/Makefile
|
||
|
@@ -19,6 +19,7 @@ dist += purgatory/Makefile $(PURGATORY_SRCS) \
|
||
|
|
||
|
include $(srcdir)/purgatory/arch/alpha/Makefile
|
||
|
include $(srcdir)/purgatory/arch/arm/Makefile
|
||
|
+include $(srcdir)/purgatory/arch/arm64/Makefile
|
||
|
include $(srcdir)/purgatory/arch/i386/Makefile
|
||
|
include $(srcdir)/purgatory/arch/ia64/Makefile
|
||
|
include $(srcdir)/purgatory/arch/mips/Makefile
|
||
|
diff --git a/purgatory/arch/arm64/Makefile b/purgatory/arch/arm64/Makefile
|
||
|
new file mode 100644
|
||
|
index 0000000..636abea
|
||
|
--- /dev/null
|
||
|
+++ b/purgatory/arch/arm64/Makefile
|
||
|
@@ -0,0 +1,18 @@
|
||
|
+
|
||
|
+arm64_PURGATORY_EXTRA_CFLAGS = \
|
||
|
+ -mcmodel=large \
|
||
|
+ -fno-stack-protector \
|
||
|
+ -fno-asynchronous-unwind-tables \
|
||
|
+ -Wundef \
|
||
|
+ -Werror-implicit-function-declaration \
|
||
|
+ -Wdeclaration-after-statement \
|
||
|
+ -Werror=implicit-int \
|
||
|
+ -Werror=strict-prototypes
|
||
|
+
|
||
|
+arm64_PURGATORY_SRCS += \
|
||
|
+ purgatory/arch/arm64/entry.S \
|
||
|
+ purgatory/arch/arm64/purgatory-arm64.c
|
||
|
+
|
||
|
+dist += \
|
||
|
+ $(arm64_PURGATORY_SRCS) \
|
||
|
+ purgatory/arch/arm64/Makefile
|
||
|
diff --git a/purgatory/arch/arm64/entry.S b/purgatory/arch/arm64/entry.S
|
||
|
new file mode 100644
|
||
|
index 0000000..adf16f4
|
||
|
--- /dev/null
|
||
|
+++ b/purgatory/arch/arm64/entry.S
|
||
|
@@ -0,0 +1,51 @@
|
||
|
+/*
|
||
|
+ * ARM64 purgatory.
|
||
|
+ */
|
||
|
+
|
||
|
+.macro size, sym:req
|
||
|
+ .size \sym, . - \sym
|
||
|
+.endm
|
||
|
+
|
||
|
+.text
|
||
|
+
|
||
|
+.globl purgatory_start
|
||
|
+purgatory_start:
|
||
|
+
|
||
|
+ adr x19, .Lstack
|
||
|
+ mov sp, x19
|
||
|
+
|
||
|
+ bl purgatory
|
||
|
+
|
||
|
+ /* Start new image. */
|
||
|
+ ldr x17, arm64_kernel_entry
|
||
|
+ ldr x0, arm64_dtb_addr
|
||
|
+ mov x1, xzr
|
||
|
+ mov x2, xzr
|
||
|
+ mov x3, xzr
|
||
|
+ br x17
|
||
|
+
|
||
|
+size purgatory_start
|
||
|
+
|
||
|
+.ltorg
|
||
|
+
|
||
|
+.align 4
|
||
|
+ .rept 256
|
||
|
+ .quad 0
|
||
|
+ .endr
|
||
|
+.Lstack:
|
||
|
+
|
||
|
+.data
|
||
|
+
|
||
|
+.align 3
|
||
|
+
|
||
|
+.globl arm64_kernel_entry
|
||
|
+arm64_kernel_entry:
|
||
|
+ .quad 0
|
||
|
+size arm64_kernel_entry
|
||
|
+
|
||
|
+.globl arm64_dtb_addr
|
||
|
+arm64_dtb_addr:
|
||
|
+ .quad 0
|
||
|
+size arm64_dtb_addr
|
||
|
+
|
||
|
+.end
|
||
|
\ No newline at end of file
|
||
|
diff --git a/purgatory/arch/arm64/purgatory-arm64.c b/purgatory/arch/arm64/purgatory-arm64.c
|
||
|
new file mode 100644
|
||
|
index 0000000..fe50fcf
|
||
|
--- /dev/null
|
||
|
+++ b/purgatory/arch/arm64/purgatory-arm64.c
|
||
|
@@ -0,0 +1,19 @@
|
||
|
+/*
|
||
|
+ * ARM64 purgatory.
|
||
|
+ */
|
||
|
+
|
||
|
+#include <stdint.h>
|
||
|
+#include <purgatory.h>
|
||
|
+
|
||
|
+void putchar(int ch)
|
||
|
+{
|
||
|
+ /* Nothing for now */
|
||
|
+}
|
||
|
+
|
||
|
+void post_verification_setup_arch(void)
|
||
|
+{
|
||
|
+}
|
||
|
+
|
||
|
+void setup_arch(void)
|
||
|
+{
|
||
|
+}
|
||
|
--
|
||
|
2.9.3
|
||
|
|