From 21014aa0868f11223e3b33afa1b6320b79187dbc Mon Sep 17 00:00:00 2001 Message-Id: <21014aa0868f11223e3b33afa1b6320b79187dbc.1431592766.git.panand@redhat.com> In-Reply-To: <1fb6841aa15407dbf371589d7abca7bc2d35815c.1431592766.git.panand@redhat.com> References: <1fb6841aa15407dbf371589d7abca7bc2d35815c.1431592766.git.panand@redhat.com> From: Geoff Levand Date: Fri, 17 Apr 2015 10:54:55 -0700 Subject: [PATCH 02/17] arm64: Add arm64 kexec support Add kexec reboot support for ARM64 platforms. Uses purgatory. Signed-off-by: Geoff Levand --- 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 | 95 +++ kexec/arch/arm64/include/arch/options.h | 44 ++ kexec/arch/arm64/kexec-arm64.c | 1041 +++++++++++++++++++++++++++++++ kexec/arch/arm64/kexec-arm64.h | 51 ++ kexec/arch/arm64/kexec-elf-arm64.c | 123 ++++ kexec/arch/arm64/kexec-image-arm64.c | 50 ++ kexec/kexec-syscall.h | 9 +- purgatory/Makefile | 1 + purgatory/arch/arm64/Makefile | 18 + purgatory/arch/arm64/entry.S | 54 ++ purgatory/arch/arm64/purgatory-arm64.c | 35 ++ 16 files changed, 1596 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 6946780d498f..701ed69c5033 100644 --- a/configure.ac +++ b/configure.ac @@ -35,6 +35,9 @@ case $target_cpu in ARCH="ppc64" SUBARCH="LE" ;; + aarch64* ) + ARCH="arm64" + ;; arm* ) ARCH="arm" ;; diff --git a/kexec/Makefile b/kexec/Makefile index 6937a4dbb38b..fac668072463 100644 --- a/kexec/Makefile +++ b/kexec/Makefile @@ -75,6 +75,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 000000000000..37414dc5c900 --- /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 000000000000..d2272c8124d0 --- /dev/null +++ b/kexec/arch/arm64/crashdump-arm64.c @@ -0,0 +1,21 @@ +/* + * ARM64 crashdump. + */ + +#define _GNU_SOURCE + +#include +#include + +#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 000000000000..f33c7a25b454 --- /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 000000000000..acb839ab889c --- /dev/null +++ b/kexec/arch/arm64/image-header.h @@ -0,0 +1,95 @@ +/* + * ARM64 binary image support. + * Copyright (C) 2014 Linaro. + */ + +#if !defined(__ARM64_IMAGE_HEADER_H) +#define __ARM64_IMAGE_HEADER_H + +#if !defined(__KERNEL__) +#include +#endif + +#if !defined(__ASSEMBLY__) + +/** + * 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: + * Bit 7.0: Image byte order, 1=MSB. + * @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; + uint8_t flags[8]; + 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_7_be = 0x01U; + +/** + * 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; + + if (!h->text_offset) + 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 !!(h->flags[7] & arm64_image_flag_7_be); +} + +#endif /* !defined(__ASSEMBLY__) */ + +#endif diff --git a/kexec/arch/arm64/include/arch/options.h b/kexec/arch/arm64/include/arch/options.h new file mode 100644 index 000000000000..51afc5f1f6f2 --- /dev/null +++ b/kexec/arch/arm64/include/arch/options.h @@ -0,0 +1,44 @@ +#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_LITE ((OPT_MAX)+3) +#define OPT_PORT ((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 }, \ + { "lite", 0, NULL, OPT_LITE }, \ + { "port", 1, NULL, OPT_PORT }, \ + { "ramdisk", 1, NULL, OPT_INITRD }, \ + +#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" +" --lite Fast reboot, no memory integrity checks - currently NOT SUPPORTED.\n"); +" --port=ADDRESS Purgatory output to port ADDRESS.\n" +" --ramdisk=FILE Use FILE as the kernel initial ramdisk.\n"; + +struct arm64_opts { + const char *command_line; + const char *dtb; + const char *initrd; + uint64_t port; + int lite; +}; + +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 000000000000..0860810b6e86 --- /dev/null +++ b/kexec/arch/arm64/kexec-arm64.c @@ -0,0 +1,1041 @@ +/* + * ARM64 kexec. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "dt-ops.h" +#include "kexec.h" +#include "crashdump.h" +#include "crashdump-arm64.h" +#include "kexec-arm64.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 }, +}; + +/* arm64 global varables. */ + +struct arm64_opts arm64_opts; +struct arm64_mem arm64_mem = { + .memstart = UINT64_MAX, +}; + +static void set_memstart(uint64_t v) +{ + if (arm64_mem.memstart == UINT64_MAX || v < arm64_mem.memstart) + arm64_mem.memstart = v; +} + +static int check_memstart(void) +{ + return arm64_mem.memstart != UINT64_MAX; +} + +void arch_usage(void) +{ + dbgprintf("Build time: %s : %s\n", __DATE__, __TIME__); + 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; + + for (opt = 0; opt != -1; ) { + opt = getopt_long(argc, argv, short_options, options, 0); + + switch (opt) { + case OPT_APPEND: + arm64_opts.command_line = optarg; + break; + case OPT_DTB: + arm64_opts.dtb = optarg; + break; + case OPT_INITRD: + arm64_opts.initrd = optarg; + break; + case OPT_LITE: + arm64_opts.lite = 1; + fprintf(stderr, "kexec: --lite option currently NOT SUPPORTED.\n"); + break; + case OPT_PORT: + arm64_opts.port = strtoull(optarg, NULL, 0); + break; + default: + break; /* Ignore core and unknown options. */ + } + } + + kexec_debug = 1; // FIXME: for debugging only. + + 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); + dbgprintf("%s:%d: lite: %d\n", __func__, __LINE__, arm64_opts.lite); + dbgprintf("%s:%d: port: 0x%" PRIx64 "\n", __func__, __LINE__, + arm64_opts.port); + + return 0; +} + +struct dtb { + char *buf; + off_t size; + const char *name; + const char *path; +}; + +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); + } +} + +enum cpu_enable_method { + cpu_enable_method_unknown, + cpu_enable_method_psci, + cpu_enable_method_spin_table, +}; + +/** + * struct cpu_properties - Various properties from a device tree cpu node. + * + * These properties will be valid over a dtb re-size. + */ + +struct cpu_properties { + uint64_t hwid; + uint64_t cpu_release_addr; + char node_path[128]; + char enable_method[128]; + enum cpu_enable_method type; +}; + +/** + * read_cpu_properties - Helper to read the device tree cpu properties. + */ + +static int read_cpu_properties(struct cpu_properties *cp, + const struct dtb *dtb, int node_offset, unsigned int address_cells) +{ + int result; + const void *data; + + result = fdt_get_path(dtb->buf, node_offset, cp->node_path, + sizeof(cp->node_path)); + + if (result < 0) { + fprintf(stderr, "kexec: %s:%d: %s: fdt_get_path failed: %s\n", + __func__, __LINE__, dtb->name, fdt_strerror(result)); + return result; + } + + data = fdt_getprop(dtb->buf, node_offset, "device_type", &result); + + if (!data) { + dbgprintf("%s: %s (%s) read device_type failed: %s\n", + __func__, dtb->name, cp->node_path, + fdt_strerror(result)); + return result == -FDT_ERR_NOTFOUND ? 0 : result; + } + + if (strcmp(data, "cpu")) { + dbgprintf("%s: %s (%s): '%s'\n", __func__, dtb->name, + cp->node_path, (const char *)data); + return 0; + } + + data = fdt_getprop(dtb->buf, node_offset, "reg", &result); + + if (!data) { + fprintf(stderr, "kexec: %s:%d: read hwid failed: %s\n", + __func__, __LINE__, fdt_strerror(result)); + return result; + } + + cp->hwid = (address_cells == 1) ? fdt32_to_cpu(*(uint32_t *)data) : + fdt64_to_cpu(*(uint64_t *)data); + + data = fdt_getprop(dtb->buf, node_offset, "enable-method", &result); + + if (!data) { + fprintf(stderr, + "kexec: %s:%d: read enable_method failed: %s\n", + __func__, __LINE__, fdt_strerror(result)); + return result; + } + + strncpy(cp->enable_method, data, sizeof(cp->enable_method)); + cp->enable_method[sizeof(cp->enable_method) - 1] = 0; + + if (!strcmp(cp->enable_method, "psci")) { + cp->type = cpu_enable_method_psci; + return 1; + } + + if (strcmp(cp->enable_method, "spin-table")) { + cp->type = cpu_enable_method_unknown; + return -1; + } + + cp->type = cpu_enable_method_spin_table; + + data = fdt_getprop(dtb->buf, node_offset, "cpu-release-addr", &result); + + if (!data) { + fprintf(stderr, "kexec: %s:%d: " + "read cpu-release-addr failed: %s\n", + __func__, __LINE__, fdt_strerror(result)); + return result; + } + + cp->cpu_release_addr = fdt64_to_cpu(*(uint64_t *)data); + + return 1; +} + +static int check_cpu_properties(const struct cpu_properties *cp_1, + const struct cpu_properties *cp_2) +{ + assert(cp_1->hwid == cp_2->hwid); + + if (cp_1->type != cp_2->type) { + fprintf(stderr, + "%s:%d: hwid-%" PRIx64 ": " + "Error: Different enable methods: %s -> %s\n", + __func__, __LINE__, cp_1->hwid, cp_1->enable_method, + cp_2->enable_method); + return -EINVAL; + } + + if (cp_1->type != cpu_enable_method_psci + && cp_1->type != cpu_enable_method_spin_table) { + fprintf(stderr, + "%s:%d: hwid-%" PRIx64 ": " + "Warning: Unknown enable method: %s.\n", + __func__, __LINE__, cp_1->hwid, + cp_1->enable_method); + } + + if (cp_1->type == cpu_enable_method_spin_table) { + if (cp_1->cpu_release_addr != cp_2->cpu_release_addr) { + fprintf(stderr, "%s:%d: hwid-%" PRIx64 ": " + "Error: Different cpu-release-addr: " + "%" PRIx64 " -> %" PRIx64 ".\n", + __func__, __LINE__, + cp_1->hwid, + cp_2->cpu_release_addr, + cp_1->cpu_release_addr); + return -EINVAL; + } + } + + dbgprintf("%s: hwid-%" PRIx64 ": OK\n", __func__, cp_1->hwid); + + return 0; +} + +struct cpu_info { + unsigned int cpu_count; + struct cpu_properties *cp; +}; + +static int read_cpu_info(struct cpu_info *info, const struct dtb *dtb) +{ + int i; + int offset; + int result; + int depth; + const void *data; + unsigned int address_cells; + + offset = fdt_subnode_offset(dtb->buf, 0, "cpus"); + + if (offset < 0) { + fprintf(stderr, "kexec: %s:%d: read cpus node failed: %s\n", + __func__, __LINE__, fdt_strerror(offset)); + return offset; + } + + data = fdt_getprop(dtb->buf, offset, "#address-cells", &result); + + if (!data) { + fprintf(stderr, + "kexec: %s:%d: read cpus address-cells failed: %s\n", + __func__, __LINE__, fdt_strerror(result)); + return result; + } + + address_cells = fdt32_to_cpu(*(uint32_t *)data); + + if (address_cells < 1 || address_cells > 2) { + fprintf(stderr, + "kexec: %s:%d: bad cpus address-cells value: %u\n", + __func__, __LINE__, address_cells); + return -EINVAL; + } + + for (i = 0, depth = 0; ; i++) { + info->cp = realloc(info->cp, (i + 1) * sizeof(*info->cp)); + + if (!info->cp) { + fprintf(stderr, "kexec: %s:%d: malloc failed: %s\n", + __func__, __LINE__, fdt_strerror(offset)); + result = -ENOMEM; + goto on_error; + } + +next_node: + memset(&info->cp[i], 0, sizeof(*info->cp)); + + offset = fdt_next_node(dtb->buf, offset, &depth); + + if (offset < 0) { + fprintf(stderr, "kexec: %s:%d: " + "read cpu node failed: %s\n", __func__, + __LINE__, fdt_strerror(offset)); + result = offset; + goto on_error; + } + + if (depth != 1) + break; + + result = read_cpu_properties(&info->cp[i], dtb, offset, + address_cells); + + if (result == 0) + goto next_node; + + if (result < 0) + goto on_error; + + if (info->cp[i].type == cpu_enable_method_psci) + dbgprintf("%s: %s cpu-%d (%s): hwid-%" PRIx64 ", '%s'\n", + __func__, dtb->name, i, info->cp[i].node_path, + info->cp[i].hwid, + info->cp[i].enable_method); + else + dbgprintf("%s: %s cpu-%d (%s): hwid-%" PRIx64 ", '%s', " + "cpu-release-addr %" PRIx64 "\n", + __func__, dtb->name, i, info->cp[i].node_path, + info->cp[i].hwid, + info->cp[i].enable_method, + info->cp[i].cpu_release_addr); + } + + info->cpu_count = i; + return 0; + +on_error: + free(info->cp); + info->cp = NULL; + return result; +} + +static int check_cpu_nodes(const struct dtb *dtb_1, const struct dtb *dtb_2) +{ + int result; + unsigned int cpu_1; + struct cpu_info info_1; + struct cpu_info info_2; + unsigned int to_process; + + memset(&info_1, 0, sizeof(info_1)); + memset(&info_2, 0, sizeof(info_2)); + + result = read_cpu_info(&info_1, dtb_1); + + if (result) + goto on_exit; + + result = read_cpu_info(&info_2, dtb_2); + + if (result) + goto on_exit; + + to_process = info_1.cpu_count < info_2.cpu_count + ? info_1.cpu_count : info_2.cpu_count; + + for (cpu_1 = 0; cpu_1 < info_1.cpu_count; cpu_1++) { + struct cpu_properties *cp_1 = &info_1.cp[cpu_1]; + unsigned int cpu_2; + + for (cpu_2 = 0; cpu_2 < info_2.cpu_count; cpu_2++) { + struct cpu_properties *cp_2 = &info_2.cp[cpu_2]; + + if (cp_1->hwid != cp_2->hwid) + continue; + + to_process--; + + result = check_cpu_properties(cp_1, cp_2); + + if (result) + goto on_exit; + } + } + + if (to_process) { + fprintf(stderr, "kexec: %s:%d: Warning: " + "Failed to process %u CPUs.\n", + __func__, __LINE__, to_process); + result = -EINVAL; + goto on_exit; + } + +on_exit: + free(info_1.cp); + free(info_2.cp); + return result; +} + +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((char **)&dtb->buf, &dtb->size, command_line); + + if (result) + fprintf(stderr, + "kexec: Set device tree bootargs failed.\n"); + + return result; +} + +static int read_proc_dtb(struct dtb *dtb, const char *command_line) +{ + 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 -1; + } + + dtb->path = path; + create_flatten_tree((char **)&dtb->buf, &dtb->size, + (command_line && command_line[0]) ? command_line : NULL); + + return 0; +} + +static int read_sys_dtb(struct dtb *dtb, const char *command_line) +{ + 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 -1; + } + + dtb->path = path; + dtb->buf = slurp_file("/sys/firmware/fdt", &dtb->size); + + return set_bootargs(dtb, command_line); +} + +static int read_1st_dtb(struct dtb *dtb, const char *command_line) +{ + int result; + + result = read_sys_dtb(dtb, command_line); + + if (!result) + goto on_success; + + result = read_proc_dtb(dtb, command_line); + + if (!result) + goto on_success; + + return -1; + +on_success: + dbgprintf("%s: found %s\n", __func__, dtb->path); + return 0; +} + +static int setup_2nd_dtb(char *command_line, const struct dtb *dtb_1, + struct dtb *dtb_2) +{ + int result; + + result = fdt_check_header(dtb_2->buf); + + if (result) { + fprintf(stderr, "kexec: Invalid 2nd device tree.\n"); + return -EINVAL; + } + + result = set_bootargs(dtb_2, command_line); + + dump_reservemap(dtb_2); + + return result; +} + +static uint64_t read_sink(const char *command_line) +{ + uint64_t v; + const char *p; + + if (arm64_opts.port) + return arm64_opts.port; + +#if defined(ARM64_DEBUG_PORT) + return (uint64_t)(ARM64_DEBUG_PORT); +#endif + if (!command_line) + return 0; + + p = strstr(command_line, "earlyprintk="); + + if (!p) + return 0; + + while (*p != ',') + p++; + + p++; + + while (isspace(*p)) + p++; + + if (*p == 0) + return 0; + + errno = 0; + + v = strtoull(p, NULL, 0); + + if (errno) + return 0; + + return v; +} + +/** + * arm64_load_other_segments - Prepare the dtb, initrd and purgatory segments. + */ + +int arm64_load_other_segments(struct kexec_info *info, + unsigned long kernel_entry) +{ + int result; + struct mem_ehdr ehdr; + unsigned long dtb_max; + unsigned long dtb_base; + char *initrd_buf = NULL; + uint64_t purgatory_sink; + unsigned long purgatory_base; + struct dtb dtb_1 = {.name = "dtb_1"}; + struct dtb dtb_2 = {.name = "dtb_2"}; + 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; + } + + purgatory_sink = read_sink(command_line); + + dbgprintf("%s:%d: purgatory sink: 0x%" PRIx64 "\n", __func__, __LINE__, + purgatory_sink); + + if (arm64_opts.dtb) { + dtb_2.buf = slurp_file(arm64_opts.dtb, &dtb_2.size); + assert(dtb_2.buf); + } + + result = read_1st_dtb(&dtb_1, command_line); + + if (result && !arm64_opts.dtb) { + fprintf(stderr, "kexec: Error: No device tree available.\n"); + return result; + } + + if (result && arm64_opts.dtb) + dtb_1 = dtb_2; + else if (!result && !arm64_opts.dtb) + dtb_2 = dtb_1; + + result = setup_2nd_dtb(command_line, &dtb_1, &dtb_2); + + if (result) + return result; + + result = check_cpu_nodes(&dtb_1, &dtb_2); + + if (result) + return result; + + /* + * Put the DTB after the kernel with an alignment of 128 KiB, giving + * a max supported DTB size of 128 KiB (worst case). Also add 2 KiB + * to the DTB size for any DTB growth. + */ + + dtb_max = dtb_2.size + 2 * 1024; + + dtb_base = locate_hole(info, dtb_max, 128UL * 1024, + arm64_mem.memstart + arm64_mem.text_offset + + arm64_mem.image_size, + _ALIGN_UP(arm64_mem.memstart + arm64_mem.text_offset, + 512UL * 1024 * 1024), + 1); + + dbgprintf("dtb: base %lx, size %lxh (%ld)\n", dtb_base, dtb_2.size, + dtb_2.size); + + if (dtb_base == ULONG_MAX) + return -ENOMEM; + + purgatory_base = dtb_base + dtb_2.size; + initrd_base = 0; + initrd_size = 0; + + 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 DTB with an alignment of + * page size. */ + + initrd_base = locate_hole(info, initrd_size, 0, + dtb_base + dtb_max, -1, 1); + + dbgprintf("initrd: base %lx, size %lxh (%ld)\n", + initrd_base, initrd_size, initrd_size); + + if (initrd_base == ULONG_MAX) + return -ENOMEM; + + result = dtb_set_initrd((char **)&dtb_2.buf, + &dtb_2.size, initrd_base, + initrd_base + initrd_size); + + if (result) + return result; + + purgatory_base = initrd_base + initrd_size; + } + } + + if (dtb_2.size > dtb_max) { + fprintf(stderr, "%s: Error: Too many DTB mods.\n", __func__); + return -EINVAL; + } + + add_segment_phys_virt(info, dtb_2.buf, dtb_2.size, dtb_base, + dtb_2.size, 0); + + if (arm64_opts.initrd) + add_segment_phys_virt(info, initrd_buf, initrd_size, + initrd_base, initrd_size, 0); + + if (arm64_opts.lite) { + fprintf(stderr, "kexec: --lite option currently NOT SUPPORTED.\n"); + return -ENOSYS; + } else { + result = build_elf_rel_info(purgatory, purgatory_size, &ehdr, + 0); + + if (result < 0) { + fprintf(stderr, "%s: Error: " + "build_elf_rel_info failed.\n", __func__); + return -EBADF; + } + + elf_rel_build_load(info, &info->rhdr, purgatory, purgatory_size, + purgatory_base, ULONG_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", + &kernel_entry, sizeof(kernel_entry)); + + elf_rel_set_symbol(&info->rhdr, "arm64_dtb_addr", &dtb_base, + sizeof(dtb_base)); + } + + return 0; +} + +unsigned long virt_to_phys(unsigned long v) +{ + unsigned long p; + + assert(arm64_mem.page_offset); + assert(check_memstart()); + + p = v - arm64_mem.page_offset + arm64_mem.memstart; + + dbgprintf("%s: %016lx -> %016lx\n", __func__, v, p); + return p; +} + +unsigned long phys_to_virt(struct crash_elf_info *UNUSED(elf_info), + unsigned long p) +{ + unsigned long v; + + assert(arm64_mem.page_offset); + assert(check_memstart()); + + v = p - arm64_mem.memstart + arm64_mem.page_offset; + + dbgprintf("%s: %016lx -> %016lx\n", __func__, p, v); + return p; +} + +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); +} + +int arm64_process_image_header(const struct arm64_image_header *h) +{ +#if !defined(KERNEL_IMAGE_SIZE) +# define KERNEL_IMAGE_SIZE (768 * 1024) +#endif + + if (!arm64_header_check_magic(h)) + return -EINVAL; + + if (h->image_size) { + arm64_mem.text_offset = le64_to_cpu(h->text_offset); + arm64_mem.image_size = le64_to_cpu(h->image_size); + } else { + /* For 3.16 and older kernels. */ + arm64_mem.text_offset = 0x80000; + arm64_mem.image_size = KERNEL_IMAGE_SIZE; + } + + return 0; +} + +static int get_memory_ranges_dt(struct memory_range *array, unsigned int *count) +{ + struct region {uint64_t base; uint64_t size;}; + struct dtb dtb = {.name = "range_dtb"}; + int offset; + int result; + + *count = 0; + + result = read_1st_dtb(&dtb, NULL); + + if (result) { + goto on_error; + } + + result = fdt_check_header(dtb.buf); + + if (result) { + dbgprintf("%s:%d: %s: fdt_check_header failed:%s\n", __func__, + __LINE__, dtb.path, fdt_strerror(result)); + goto on_error; + } + + for (offset = 0; ; ) { + const struct region *region; + const struct region *end; + int len; + + offset = fdt_subnode_offset(dtb.buf, offset, "memory"); + + if (offset == -FDT_ERR_NOTFOUND) + break; + + if (offset <= 0) { + dbgprintf("%s:%d: fdt_subnode_offset failed: %d %s\n", + __func__, __LINE__, offset, + fdt_strerror(offset)); + goto on_error; + } + + dbgprintf("%s:%d: node_%d %s\n", __func__, __LINE__, offset, + fdt_get_name(dtb.buf, offset, NULL)); + + region = fdt_getprop(dtb.buf, offset, "reg", &len); + + if (region <= 0) { + dbgprintf("%s:%d: fdt_getprop failed: %d %s\n", + __func__, __LINE__, offset, + fdt_strerror(offset)); + goto on_error; + } + + for (end = region + len / sizeof(*region); + region < end && *count < KEXEC_SEGMENT_MAX; + region++) { + struct memory_range r; + + r.type = RANGE_RAM; + r.start = fdt64_to_cpu(region->base); + r.end = r.start + fdt64_to_cpu(region->size); + + if (!region->size) { + dbgprintf("%s:%d: SKIP: %016llx - %016llx\n", + __func__, __LINE__, r.start, r.end); + continue; + } + + dbgprintf("%s:%d: RAM: %016llx - %016llx\n", __func__, + __LINE__, r.start, r.end); + + array[(*count)++] = r; + + set_memstart(r.start); + } + } + + if (!*count) { + dbgprintf("%s:%d: %s: No RAM found.\n", __func__, __LINE__, + dtb.path); + goto on_error; + } + + dbgprintf("%s:%d: %s: Success\n", __func__, __LINE__, dtb.path); + result = 0; + goto on_exit; + +on_error: + fprintf(stderr, "%s:%d: %s: Unusable device-tree file\n", __func__, + __LINE__, dtb.path); + result = -1; + +on_exit: + free(dtb.buf); + return result; +} + +static int get_memory_ranges_iomem(struct memory_range *array, + unsigned int *count) +{ + const char *iomem; + char line[MAX_LINE]; + FILE *fp; + + *count = 0; + + iomem = proc_iomem(); + 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) { + struct memory_range r; + char *str; + int consumed; + + if (*count >= KEXEC_SEGMENT_MAX) + break; + + if (sscanf(line, "%Lx-%Lx : %n", &r.start, &r.end, &consumed) + != 2) + continue; + + str = line + consumed; + r.end++; + + if (memcmp(str, "System RAM\n", 11)) { + dbgprintf("%s:%d: SKIP: %016Lx - %016Lx : %s", __func__, + __LINE__, r.start, r.end, str); + continue; + } + + r.type = RANGE_RAM; + + dbgprintf("%s:%d: RAM: %016llx - %016llx : %s", __func__, + __LINE__, r.start, r.end, str); + + array[(*count)++] = r; + + set_memstart(r.start); + } + + fclose(fp); + + if (!*count) { + dbgprintf("%s:%d: failed: No RAM found.\n", __func__, __LINE__); + return -1; + } + + dbgprintf("%s:%d: Success\n", __func__, __LINE__); + return 0; +} + +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_dt(array, &count); + + if (result) + result = get_memory_ranges_iomem(array, &count); + + *range = result ? NULL : array; + *ranges = result ? 0 : count; + + return result; +} + +struct file_type file_type[] = { + {"elf-arm64", elf_arm64_probe, elf_arm64_load, elf_arm64_usage}, + {"image-arm64", image_arm64_probe, image_arm64_load, image_arm64_usage}, +}; + +int file_types = sizeof(file_type) / sizeof(file_type[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); +} + +void machine_apply_elf_rel(struct mem_ehdr *ehdr, 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 *location = (uint64_t *)ptr; + uint64_t data = *location; + const char *type = NULL; + + switch(r_type) { + case R_AARCH64_ABS64: + type = "ABS64"; + *location += value; + break; + case R_AARCH64_LD_PREL_LO19: + type = "LD_PREL_LO19"; + *location += ((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"; + *location += ((value - address) << 3) & 0xffffe0; + break; + case R_AARCH64_JUMP26: + type = "JUMP26"; + *location += ((value - address) >> 2) & 0x3ffffff; + break; + case R_AARCH64_CALL26: + type = "CALL26"; + *location += ((value - address) >> 2) & 0x3ffffff; + break; + default: + die("%s: ERROR Unknown type: %lu\n", __func__, r_type); + break; + } + + dbgprintf("%s: %s %lx->%lx\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 000000000000..057acf313b49 --- /dev/null +++ b/kexec/arch/arm64/kexec-arm64.h @@ -0,0 +1,51 @@ +/* + * ARM64 kexec. + */ + +#if !defined(KEXEC_ARM64_H) +#define KEXEC_ARM64_H + +#include +#include + +#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 + +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); + +struct memory_ranges usablemem_rgns; +off_t initrd_base; +off_t initrd_size; + +/** + * struct arm64_mem - Memory layout info. + */ + +struct arm64_mem { + uint64_t text_offset; + uint64_t image_size; + uint64_t page_offset; + uint64_t memstart; +}; + +extern struct arm64_mem arm64_mem; + +int arm64_process_image_header(const struct arm64_image_header *h); +int arm64_load_other_segments(struct kexec_info *info, + unsigned long kernel_entry); + +#endif diff --git a/kexec/arch/arm64/kexec-elf-arm64.c b/kexec/arch/arm64/kexec-elf-arm64.c new file mode 100644 index 000000000000..13dc5e2724d9 --- /dev/null +++ b/kexec/arch/arm64/kexec-elf-arm64.c @@ -0,0 +1,123 @@ +/* + * ARM64 kexec elf support. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include + +#include + +#include "dt-ops.h" +#include "crashdump-arm64.h" +#include "kexec-arm64.h" +#include "fs2dt.h" +#include "kexec-syscall.h" +#include "arch/options.h" + +int elf_arm64_probe(const char *kernel_buf, off_t kernel_size) +{ + int result; + struct mem_ehdr ehdr; + + 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 = -EINVAL; + 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) +{ + int result; + struct mem_ehdr ehdr; + bool found_header; + int i; + + if (info->kexec_flags & KEXEC_ON_CRASH) { + fprintf(stderr, "kexec: kdump not yet supported on arm64\n"); + return -EINVAL; + } + + 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, found_header = false; i < ehdr.e_phnum; i++) { + struct mem_phdr *phdr = &ehdr.e_phdr[i]; + const struct arm64_image_header *h; + + if (phdr->p_type != PT_LOAD) + continue; + + h = (const struct arm64_image_header *)(kernel_buf + + phdr->p_offset); + + if (arm64_process_image_header(h)) + continue; + + found_header = true; + + arm64_mem.page_offset = phdr->p_vaddr - arm64_mem.text_offset; + + dbgprintf("%s: PE format: %s\n", __func__, + (arm64_header_check_pe_sig(h) ? "yes" : "no")); + dbgprintf("p_vaddr: %016llx\n", phdr->p_vaddr); + + break; + } + + if (!found_header) { + fprintf(stderr, "kexec: Bad arm64 image header.\n"); + result = -EINVAL; + goto exit; + } + + result = elf_exec_load(&ehdr, info); + + if (result) { + fprintf(stderr, "kexec: Elf load failed.\n"); + goto exit; + } + + dbgprintf("%s: text_offset: %016lx\n", __func__, arm64_mem.text_offset); + dbgprintf("%s: image_size: %016lx\n", __func__, arm64_mem.image_size); + dbgprintf("%s: page_offset: %016lx\n", __func__, arm64_mem.page_offset); + dbgprintf("%s: memstart: %016lx\n", __func__, arm64_mem.memstart); + dbgprintf("%s: e_entry: %016llx -> %016lx\n", __func__, + ehdr.e_entry, virt_to_phys(ehdr.e_entry)); + + result = arm64_load_other_segments(info, virt_to_phys(ehdr.e_entry)); +exit: + free_elf_info(&ehdr); + return result; +} + +void elf_arm64_usage(void) +{ + printf( +" An arm64 ELF file, 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 000000000000..b025dc6c0185 --- /dev/null +++ b/kexec/arch/arm64/kexec-image-arm64.c @@ -0,0 +1,50 @@ +/* + * ARM64 kexec binary image support. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include + +#include "dt-ops.h" +#include "image-header.h" +#include "kexec-arm64.h" +#include "fs2dt.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)) + return -EINVAL; + + h = (const struct arm64_image_header *)(kernel_buf); + + if (!arm64_header_check_magic(h)) + return -1; + + dbgprintf("%s: PE format: %s\n", __func__, + (arm64_header_check_pe_sig(h) ? "yes" : "no")); + + 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 -ENOSYS; +} + +void image_arm64_usage(void) +{ + printf( +" An arm64 binary Image file, big or little endian.\n" +" This file type is currently NOT SUPPORTED.\n\n"); +} diff --git a/kexec/kexec-syscall.h b/kexec/kexec-syscall.h index ce2e20b8b04b..267b75b02272 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,8 @@ 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_AARCH64 (183 << 16) */ #define KEXEC_ARCH_S390 (22 << 16) #define KEXEC_ARCH_SH (42 << 16) #define KEXEC_ARCH_MIPS_LE (10 << 16) @@ -153,5 +155,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 19457029e79f..0c85da6e29b8 100644 --- a/purgatory/Makefile +++ b/purgatory/Makefile @@ -18,6 +18,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 000000000000..636abeab17b2 --- /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 000000000000..140e91d87ab1 --- /dev/null +++ b/purgatory/arch/arm64/entry.S @@ -0,0 +1,54 @@ +/* + * ARM64 purgatory. + */ + +.macro debug_brk + mov x0, #0x18; /* angel_SWIreason_ReportException */ + mov x1, #0x20000; + add x1, x1, #0x20; /* ADP_Stopped_BreakPoint */ + hlt #0xf000 /* A64 semihosting */ +.endm + +.macro size, sym:req + .size \sym, . - \sym +.endm + +.text + +.globl purgatory_start +purgatory_start: + + adr x19, .Lstack + mov sp, x19 + + bl purgatory + +1: debug_brk + b 1b + +size purgatory_start + +.align 4 + .rept 256 + .quad 0 + .endr +.Lstack: + +.data + +.align 3 + +.globl arm64_sink +arm64_sink: + .quad 0 +size arm64_sink + +.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 diff --git a/purgatory/arch/arm64/purgatory-arm64.c b/purgatory/arch/arm64/purgatory-arm64.c new file mode 100644 index 000000000000..25960c30bd05 --- /dev/null +++ b/purgatory/arch/arm64/purgatory-arm64.c @@ -0,0 +1,35 @@ +/* + * ARM64 purgatory. + */ + +#include +#include + +/* Symbols set by kexec. */ + +extern uint32_t *arm64_sink; +extern void (*arm64_kernel_entry)(uint64_t); +extern uint64_t arm64_dtb_addr; + +void putchar(int ch) +{ + if (!arm64_sink) + return; + + *arm64_sink = ch; + + if (ch == '\n') + *arm64_sink = '\r'; +} + +void setup_arch(void) +{ + printf("purgatory: kernel_entry: %lx\n", + (unsigned long)arm64_kernel_entry); + printf("purgatory: dtb: %lx\n", arm64_dtb_addr); +} + +void post_verification_setup_arch(void) +{ + arm64_kernel_entry(arm64_dtb_addr); +} -- 2.1.0