Message ID | 177affa377979b1f10d5bd39107b96fe506eb683.1470693832.git.geoff@infradead.org (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
On Mon, Aug 08, 2016 at 10:19:33PM +0000, Geoff Levand wrote: > Add kexec reboot support for ARM64 platforms. > > Signed-off-by: Geoff Levand <geoff@infradead.org> > --- > 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 | 126 ++++++ > kexec/arch/arm64/include/arch/options.h | 39 ++ > kexec/arch/arm64/kexec-arm64.c | 704 ++++++++++++++++++++++++++++++++ > kexec/arch/arm64/kexec-arm64.h | 70 ++++ > kexec/arch/arm64/kexec-elf-arm64.c | 130 ++++++ > kexec/arch/arm64/kexec-image-arm64.c | 44 ++ > 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, 1285 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 736da6e..ba89075 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..0761b0e > --- /dev/null > +++ b/kexec/arch/arm64/image-header.h > @@ -0,0 +1,126 @@ > +/* > + * 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); Why not define like: #define ARM64_FLAG_PAGE_SIZE_4K 1 #define ARM64_FLAG_PAGE_SIZE_16K 2 #define ARM64_FLAG_PAGE_SIZE_64K 3 if you have arm64_header_page_size()? > + > +/** > + * 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; Why not add assert() here like get_phys_offset()? > + > + 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; ditto > + > + return (le64toh(h->flags) & arm64_image_flag_be) >> 0; > +} > + > +/** > + * arm64_header_page_size > + */ > + > +static inline int arm64_header_page_size(const struct arm64_image_header *h) > +{ > + if (!h) > + return 0; ditto > + > + return (le64toh(h->flags) & arm64_image_flag_page_size) >> 1; > +} > + > +/** > + * arm64_header_placement > + */ > + > +static inline int arm64_header_placement(const struct arm64_image_header *h) > +{ > + if (!h) > + return 0; ditto > + > + return (le64toh(h->flags) & arm64_image_flag_placement) >> 3; > +} > + > +static inline uint64_t arm64_header_text_offset( > + const struct arm64_image_header *h) > +{ Why not check for !h? > + return le64toh(h->text_offset); > +} > + > +static inline uint64_t arm64_header_image_size( > + const struct arm64_image_header *h) > +{ ditto > + 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..940cddb > --- /dev/null > +++ b/kexec/arch/arm64/kexec-arm64.c > @@ -0,0 +1,704 @@ > +/* > + * 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.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, > + .page_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_page_offset(void) > +{ > + assert(arm64_mem.page_offset != arm64_mem_ngv); > + return arm64_mem.page_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; > + > + result = read_sys_dtb(dtb); > + > + if (!result) > + goto on_success; > + > + 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; > +} > + > +/** > + * arm64_load_other_segments - Prepare the dtb, initrd and purgatory segments. > + */ > + > +int arm64_load_other_segments(struct kexec_info *info, > + uint64_t kernel_entry) > +{ > + int result; > + uint64_t dtb_base; > + uint64_t image_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_2"; > + dtb.buf = slurp_file(arm64_opts.dtb, &dtb.size); > + } else { > + dtb.name = "dtb_1"; > + 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. */ > + > + image_base = arm64_mem.phys_offset + arm64_mem.text_offset; > + 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", &kernel_entry, > + sizeof(kernel_entry)); > + > + 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_page_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_dt - Try to get the memory ranges from the 1st stage dtb. > + */ > + > +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); > + > + 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) - 1; > + > + 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_phys_offset(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; EFAILED? > + > +on_exit: > + free(dtb.buf); > + return -EFAILED; return result? > +} > + > +/** > + * 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); > + > + if (result) > + result = get_memory_ranges_dt(array, &count); > + > + *range = result ? NULL : array; > + *ranges = result ? 0 : count; > + > + return -EFAILED; 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 I still believe those definitons should go into elf.h. > + > + 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..0fb03a8 > --- /dev/null > +++ b/kexec/arch/arm64/kexec-arm64.h > @@ -0,0 +1,70 @@ > +/* > + * 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 page_offset; > +}; > + > +#define arm64_mem_ngv UINT64_MAX In image_header.h, you define "const" variables, here you define a macro. > +struct arm64_mem arm64_mem; > + > +uint64_t get_phys_offset(void); > +uint64_t get_page_offset(void); > + > +static inline void reset_page_offset(void) > +{ > + arm64_mem.page_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); > +int arm64_load_other_segments(struct kexec_info *info, > + uint64_t 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 0000000..fcac9bb > --- /dev/null > +++ b/kexec/arch/arm64/kexec-elf-arm64.c > @@ -0,0 +1,130 @@ > +/* > + * ARM64 kexec elf support. > + */ > + > +#define _GNU_SOURCE > + > +#include <errno.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) > +{ > + 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]; > + const struct arm64_image_header *h; > + 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; > + > + h = (const struct arm64_image_header *)( > + kernel_buf + phdr->p_offset + header_offset); > + > + if (arm64_process_image_header(h)) > + continue; > + > + arm64_mem.page_offset = ehdr.e_entry - arm64_mem.text_offset; > + > + dbgprintf("%s: e_entry: %016llx -> %016lx\n", __func__, > + ehdr.e_entry, > + virt_to_phys(ehdr.e_entry)); > + dbgprintf("%s: p_vaddr: %016llx -> %016lx\n", __func__, > + phdr->p_vaddr, > + virt_to_phys(phdr->p_vaddr)); > + dbgprintf("%s: header_offset: %016lx\n", __func__, > + header_offset); > + 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: page_offset: %016lx\n", __func__, > + arm64_mem.page_offset); > + dbgprintf("%s: PE format: %s\n", __func__, > + (arm64_header_check_pe_sig(h) ? "yes" : "no")); > + > + result = elf_exec_load(&ehdr, info); > + > + if (result) { > + fprintf(stderr, "kexec: Elf load failed.\n"); dbgprintf? You use this for build_elf_exec_info() above. > + goto exit; > + } > + > + result = arm64_load_other_segments(info, > + virt_to_phys(ehdr.e_entry)); > + goto exit; > + } > + > + fprintf(stderr, "kexec: Bad arm64 image header.\n"); Is this an appropriate message? > + result = -EFAILED; > + goto exit; > + > +exit: > + reset_page_offset(); > + free_elf_info(&ehdr); > + 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..84386f7 > --- /dev/null > +++ b/kexec/arch/arm64/kexec-image-arm64.c > @@ -0,0 +1,44 @@ > +/* > + * ARM64 kexec binary image support. > + */ > + > +#define _GNU_SOURCE > + > +#include <errno.h> Not used. Thanks, -Takahiro AKASHI > + > +#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 -EFAILED; > +} > + > +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" > +" This file type is 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.5.0 > >
On Tue, 2016-08-09 at 12:05 +0900, AKASHI Takahiro wrote: > On Mon, Aug 08, 2016 at 10:19:33PM +0000, Geoff Levand wrote: > > +/** > > + * 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); > > Why not define like: > #define ARM64_FLAG_PAGE_SIZE_4K 1 > #define ARM64_FLAG_PAGE_SIZE_16K 2 > #define ARM64_FLAG_PAGE_SIZE_64K 3 > if you have arm64_header_page_size()? To give them a type, I added these in as enum arm64_header_page_size. > > > + > > +/** > > + * 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; > > Why not add assert() here like get_phys_offset()? The assert in get_phys_offset() is to check for incorrect usage. arm64_header_check_pe_sig is a generic routine, and having a null value may be OK. > > + > > +static inline uint64_t arm64_header_text_offset( > > +> > > > const struct arm64_image_header *h) > > +{ > > Why not check for !h? ok. > > --- /dev/null > > +++ b/kexec/arch/arm64/kexec-arm64.c > > +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); > > + > > +> > > > 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) - 1; > > + > > +> > > > > > > > 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_phys_offset(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; > > EFAILED? > > > + > > +on_exit: > > +> > > > free(dtb.buf); > > +> > > > return -EFAILED; > > return result? OK. > > > +} > > + > > +/** > > + * 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); > > + > > +> > > > if (result) > > +> > > > > > result = get_memory_ranges_dt(array, &count); > > + > > +> > > > *range = result ? NULL : array; > > +> > > > *ranges = result ? 0 : count; > > + > > +> > > > return -EFAILED; > > return result? OK. > > > +} > > + > > +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 > > I still believe those definitons should go into elf.h. They should be comming from the c library headers. When toolchains catch up these can be removed. > > --- /dev/null > > +++ b/kexec/arch/arm64/kexec-arm64.h > > @@ -0,0 +1,70 @@ > > +/* > > + * 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 > > + > > +#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 page_offset; > > +}; > > + > > +#define arm64_mem_ngv UINT64_MAX > > In image_header.h, you define "const" variables, > here you define a macro. Yes, because arm64_mem_ngv is used as a static initializer of arm64_mem. > > +struct arm64_mem arm64_mem; > > + > > +uint64_t get_phys_offset(void); > > +uint64_t get_page_offset(void); > > + > > +static inline void reset_page_offset(void) > > +{ > > +> > > > arm64_mem.page_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); > > +int arm64_load_other_segments(struct kexec_info *info, > > +> > > > uint64_t 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 0000000..fcac9bb > > --- /dev/null > > +++ b/kexec/arch/arm64/kexec-elf-arm64.c > > @@ -0,0 +1,130 @@ > > +/* > > + * ARM64 kexec elf support. > > + */ > > + > > +#define _GNU_SOURCE > > + > > +#include > > +#include > > +#include > > + > > +#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) > > +{ > > +> > > > 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]; > > +> > > > > > const struct arm64_image_header *h; > > +> > > > > > 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; > > + > > +> > > > > > h = (const struct arm64_image_header *)( > > +> > > > > > > > kernel_buf + phdr->p_offset + header_offset); > > + > > +> > > > > > if (arm64_process_image_header(h)) > > +> > > > > > > > continue; > > + > > +> > > > > > arm64_mem.page_offset = ehdr.e_entry - arm64_mem.text_offset; > > + > > +> > > > > > dbgprintf("%s: e_entry: %016llx -> %016lx\n", __func__, > > +> > > > > > > > ehdr.e_entry, > > +> > > > > > > > virt_to_phys(ehdr.e_entry)); > > +> > > > > > dbgprintf("%s: p_vaddr: %016llx -> %016lx\n", __func__, > > +> > > > > > > > phdr->p_vaddr, > > +> > > > > > > > virt_to_phys(phdr->p_vaddr)); > > +> > > > > > dbgprintf("%s: header_offset: %016lx\n", __func__, > > +> > > > > > > > header_offset); > > +> > > > > > 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: page_offset: %016lx\n", __func__, > > +> > > > > > > > arm64_mem.page_offset); > > +> > > > > > dbgprintf("%s: PE format: %s\n", __func__, > > +> > > > > > > > (arm64_header_check_pe_sig(h) ? "yes" : "no")); > > + > > +> > > > > > result = elf_exec_load(&ehdr, info); > > + > > +> > > > > > if (result) { > > +> > > > > > > > fprintf(stderr, "kexec: Elf load failed.\n"); > > dbgprintf? > You use this for build_elf_exec_info() above. > > > +> > > > > > > > goto exit; > > +> > > > > > } > > + > > +> > > > > > result = arm64_load_other_segments(info, > > +> > > > > > > > virt_to_phys(ehdr.e_entry)); > > +> > > > > > goto exit; > > +> > > > } > > + > > +> > > > fprintf(stderr, "kexec: Bad arm64 image header.\n"); > > Is this an appropriate message? I changed elf_arm64_load to output dbgprintf during its operation, then a final message to stderr on exit. > > > +> > > > result = -EFAILED; > > +> > > > goto exit; > > + > > +exit: > > +> > > > reset_page_offset(); > > +> > > > free_elf_info(&ehdr); > > +> > > > return result; > > +} > > + > > --- /dev/null > > +++ b/kexec/arch/arm64/kexec-image-arm64.c > > @@ -0,0 +1,44 @@ > > +/* > > + * ARM64 kexec binary image support. > > + */ > > + > > +#define _GNU_SOURCE > > + > > +#include > > Not used. OK. Thanks for the review. -Geoff
Hi Geoff, On 08/08/2016:10:19:33 PM, Geoff Levand wrote: > Add kexec reboot support for ARM64 platforms. > > Signed-off-by: Geoff Levand <geoff@infradead.org> > --- > + > + arm64_mem.page_offset = ehdr.e_entry - arm64_mem.text_offset; > + > + dbgprintf("%s: e_entry: %016llx -> %016lx\n", __func__, > + ehdr.e_entry, > + virt_to_phys(ehdr.e_entry)); > + dbgprintf("%s: p_vaddr: %016llx -> %016lx\n", __func__, > + phdr->p_vaddr, > + virt_to_phys(phdr->p_vaddr)); > + dbgprintf("%s: header_offset: %016lx\n", __func__, > + header_offset); > + 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: page_offset: %016lx\n", __func__, > + arm64_mem.page_offset); Although it works on mustang, I would like to understand few things: This is what is being printed by kexec-tools: elf_arm64_load: e_entry: fffffc0008080000 -> 0000004000080000 elf_arm64_load: p_vaddr: fffffc0008080000 -> 0000004000080000 elf_arm64_load: header_offset: 0000000000000000 elf_arm64_load: text_offset: 0000000000080000 elf_arm64_load: image_size: 00000000013a0000 elf_arm64_load: phys_offset: 0000004000000000 elf_arm64_load: page_offset: fffffc0008000000 kernel is having 42 VA Bits, so a debug print in kernel says that PAGE_OFFSET=fffffe0000000000. Notice the difference between the values calculated by kernel and kexec-tools. Isn't it confusing? I think kexec-tools page_offset is text_page_offset and not the one which kernel uses. So, probably we can rename it accordingly. Otherwise, we may sync kexec-tools's virt_to_phys() and vice-versa definitions with that of kernel definitions. In my case, kernel has kimage_voffset=fffffbc008000000. So kernel translate virtual address fffffc0008080000 as fffffc0008080000 - fffffbc008000000 = 0000004000080000 which is matching to the one calculated by kexec-tools (for e_entry). ~Pratyush
On Wed, 2016-08-10 at 09:27 +0530, Pratyush Anand wrote: > This is what is being printed by kexec-tools: > > elf_arm64_load: e_entry: fffffc0008080000 -> 0000004000080000 > elf_arm64_load: p_vaddr: fffffc0008080000 -> 0000004000080000 > elf_arm64_load: header_offset: 0000000000000000 > elf_arm64_load: text_offset: 0000000000080000 > elf_arm64_load: image_size: 00000000013a0000 > elf_arm64_load: phys_offset: 0000004000000000 > elf_arm64_load: page_offset: fffffc0008000000 > > kernel is having 42 VA Bits, so a debug print in kernel says that > PAGE_OFFSET=fffffe0000000000. Notice the difference between the values > calculated by kernel and kexec-tools. Isn't it confusing? I renamed page_offset to vp_offset. -Geoff
diff --git a/configure.ac b/configure.ac index 736da6e..ba89075 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..0761b0e --- /dev/null +++ b/kexec/arch/arm64/image-header.h @@ -0,0 +1,126 @@ +/* + * 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); + +/** + * 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 int 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 + */ + +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) +{ + return le64toh(h->text_offset); +} + +static inline uint64_t arm64_header_image_size( + const struct arm64_image_header *h) +{ + 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..940cddb --- /dev/null +++ b/kexec/arch/arm64/kexec-arm64.c @@ -0,0 +1,704 @@ +/* + * 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.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, + .page_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_page_offset(void) +{ + assert(arm64_mem.page_offset != arm64_mem_ngv); + return arm64_mem.page_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; + + result = read_sys_dtb(dtb); + + if (!result) + goto on_success; + + 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; +} + +/** + * arm64_load_other_segments - Prepare the dtb, initrd and purgatory segments. + */ + +int arm64_load_other_segments(struct kexec_info *info, + uint64_t kernel_entry) +{ + int result; + uint64_t dtb_base; + uint64_t image_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_2"; + dtb.buf = slurp_file(arm64_opts.dtb, &dtb.size); + } else { + dtb.name = "dtb_1"; + 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. */ + + image_base = arm64_mem.phys_offset + arm64_mem.text_offset; + 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", &kernel_entry, + sizeof(kernel_entry)); + + 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_page_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_dt - Try to get the memory ranges from the 1st stage dtb. + */ + +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); + + 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) - 1; + + 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_phys_offset(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 -EFAILED; +} + +/** + * 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); + + if (result) + result = get_memory_ranges_dt(array, &count); + + *range = result ? NULL : array; + *ranges = result ? 0 : count; + + return -EFAILED; +} + +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..0fb03a8 --- /dev/null +++ b/kexec/arch/arm64/kexec-arm64.h @@ -0,0 +1,70 @@ +/* + * 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 page_offset; +}; + +#define arm64_mem_ngv UINT64_MAX +struct arm64_mem arm64_mem; + +uint64_t get_phys_offset(void); +uint64_t get_page_offset(void); + +static inline void reset_page_offset(void) +{ + arm64_mem.page_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); +int arm64_load_other_segments(struct kexec_info *info, + uint64_t 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 0000000..fcac9bb --- /dev/null +++ b/kexec/arch/arm64/kexec-elf-arm64.c @@ -0,0 +1,130 @@ +/* + * ARM64 kexec elf support. + */ + +#define _GNU_SOURCE + +#include <errno.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) +{ + 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]; + const struct arm64_image_header *h; + 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; + + h = (const struct arm64_image_header *)( + kernel_buf + phdr->p_offset + header_offset); + + if (arm64_process_image_header(h)) + continue; + + arm64_mem.page_offset = ehdr.e_entry - arm64_mem.text_offset; + + dbgprintf("%s: e_entry: %016llx -> %016lx\n", __func__, + ehdr.e_entry, + virt_to_phys(ehdr.e_entry)); + dbgprintf("%s: p_vaddr: %016llx -> %016lx\n", __func__, + phdr->p_vaddr, + virt_to_phys(phdr->p_vaddr)); + dbgprintf("%s: header_offset: %016lx\n", __func__, + header_offset); + 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: page_offset: %016lx\n", __func__, + arm64_mem.page_offset); + dbgprintf("%s: PE format: %s\n", __func__, + (arm64_header_check_pe_sig(h) ? "yes" : "no")); + + result = elf_exec_load(&ehdr, info); + + if (result) { + fprintf(stderr, "kexec: Elf load failed.\n"); + goto exit; + } + + result = arm64_load_other_segments(info, + virt_to_phys(ehdr.e_entry)); + goto exit; + } + + fprintf(stderr, "kexec: Bad arm64 image header.\n"); + result = -EFAILED; + goto exit; + +exit: + reset_page_offset(); + free_elf_info(&ehdr); + 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..84386f7 --- /dev/null +++ b/kexec/arch/arm64/kexec-image-arm64.c @@ -0,0 +1,44 @@ +/* + * ARM64 kexec binary image support. + */ + +#define _GNU_SOURCE + +#include <errno.h> + +#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 -EFAILED; +} + +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" +" This file type is 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) +{ +}
Add kexec reboot support for ARM64 platforms. Signed-off-by: Geoff Levand <geoff@infradead.org> --- 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 | 126 ++++++ kexec/arch/arm64/include/arch/options.h | 39 ++ kexec/arch/arm64/kexec-arm64.c | 704 ++++++++++++++++++++++++++++++++ kexec/arch/arm64/kexec-arm64.h | 70 ++++ kexec/arch/arm64/kexec-elf-arm64.c | 130 ++++++ kexec/arch/arm64/kexec-image-arm64.c | 44 ++ 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, 1285 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