arm64: Add arm64 kexec support

Add kexec reboot support for ARM64 platforms.

Signed-off-by: Geoff Levand <geoff@infradead.org>
This commit is contained in:
Geoff Levand
2016-08-08 14:52:54 -07:00
parent e58fa31dd8
commit 2b069ace9b
16 changed files with 1305 additions and 2 deletions

View File

@@ -34,6 +34,9 @@ case $target_cpu in
ARCH="ppc64"
SUBARCH="LE"
;;
aarch64* )
ARCH="arm64"
;;
arm* )
ARCH="arm"
;;

View File

@@ -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

40
kexec/arch/arm64/Makefile Normal file
View File

@@ -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

View File

@@ -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;
}

View File

@@ -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

View File

@@ -0,0 +1,146 @@
/*
* ARM64 binary image header.
*/
#if !defined(__ARM64_IMAGE_HEADER_H)
#define __ARM64_IMAGE_HEADER_H
#include <endian.h>
#include <stdint.h>
/**
* struct arm64_image_header - arm64 kernel image header.
*
* @pe_sig: Optional PE format 'MZ' signature.
* @branch_code: Reserved for instructions to branch to stext.
* @text_offset: The image load offset in LSB byte order.
* @image_size: An estimated size of the memory image size in LSB byte order.
* @flags: Bit flags in LSB byte order:
* Bit 0: Image byte order: 1=MSB.
* Bit 1-2: Kernel page size: 1=4K, 2=16K, 3=64K.
* Bit 3: Image placement: 0=low.
* @reserved_1: Reserved.
* @magic: Magic number, "ARM\x64".
* @pe_header: Optional offset to a PE format header.
**/
struct arm64_image_header {
uint8_t pe_sig[2];
uint16_t branch_code[3];
uint64_t text_offset;
uint64_t image_size;
uint64_t flags;
uint64_t reserved_1[3];
uint8_t magic[4];
uint32_t pe_header;
};
static const uint8_t arm64_image_magic[4] = {'A', 'R', 'M', 0x64U};
static const uint8_t arm64_image_pe_sig[2] = {'M', 'Z'};
static const uint64_t arm64_image_flag_be = (1UL << 0);
static const uint64_t arm64_image_flag_page_size = (3UL << 1);
static const uint64_t arm64_image_flag_placement = (1UL << 3);
/**
* enum arm64_header_page_size
*/
enum arm64_header_page_size {
arm64_header_page_size_invalid = 0,
arm64_header_page_size_4k,
arm64_header_page_size_16k,
arm64_header_page_size_64k
};
/**
* arm64_header_check_magic - Helper to check the arm64 image header.
*
* Returns non-zero if header is OK.
*/
static inline int arm64_header_check_magic(const struct arm64_image_header *h)
{
if (!h)
return 0;
return (h->magic[0] == arm64_image_magic[0]
&& h->magic[1] == arm64_image_magic[1]
&& h->magic[2] == arm64_image_magic[2]
&& h->magic[3] == arm64_image_magic[3]);
}
/**
* arm64_header_check_pe_sig - Helper to check the arm64 image header.
*
* Returns non-zero if 'MZ' signature is found.
*/
static inline int arm64_header_check_pe_sig(const struct arm64_image_header *h)
{
if (!h)
return 0;
return (h->pe_sig[0] == arm64_image_pe_sig[0]
&& h->pe_sig[1] == arm64_image_pe_sig[1]);
}
/**
* arm64_header_check_msb - Helper to check the arm64 image header.
*
* Returns non-zero if the image was built as big endian.
*/
static inline int arm64_header_check_msb(const struct arm64_image_header *h)
{
if (!h)
return 0;
return (le64toh(h->flags) & arm64_image_flag_be) >> 0;
}
/**
* arm64_header_page_size
*/
static inline enum arm64_header_page_size arm64_header_page_size(
const struct arm64_image_header *h)
{
if (!h)
return 0;
return (le64toh(h->flags) & arm64_image_flag_page_size) >> 1;
}
/**
* arm64_header_placement
*
* Returns non-zero if the image has no physical placement restrictions.
*/
static inline int arm64_header_placement(const struct arm64_image_header *h)
{
if (!h)
return 0;
return (le64toh(h->flags) & arm64_image_flag_placement) >> 3;
}
static inline uint64_t arm64_header_text_offset(
const struct arm64_image_header *h)
{
if (!h)
return 0;
return le64toh(h->text_offset);
}
static inline uint64_t arm64_header_image_size(
const struct arm64_image_header *h)
{
if (!h)
return 0;
return le64toh(h->image_size);
}
#endif

View File

@@ -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

View File

@@ -0,0 +1,705 @@
/*
* ARM64 kexec.
*/
#define _GNU_SOURCE
#include <assert.h>
#include <errno.h>
#include <getopt.h>
#include <inttypes.h>
#include <libfdt.h>
#include <limits.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <linux/elf-em.h>
#include <elf.h>
#include "kexec.h"
#include "kexec-arm64.h"
#include "crashdump.h"
#include "crashdump-arm64.h"
#include "dt-ops.h"
#include "fs2dt.h"
#include "kexec-syscall.h"
#include "arch/options.h"
/* Global varables the core kexec routines expect. */
unsigned char reuse_initrd;
off_t initrd_base;
off_t initrd_size;
const struct arch_map_entry arches[] = {
{ "aarch64", KEXEC_ARCH_ARM64 },
{ "aarch64_be", KEXEC_ARCH_ARM64 },
{ NULL, 0 },
};
struct file_type file_type[] = {
{"vmlinux", elf_arm64_probe, elf_arm64_load, elf_arm64_usage},
{"Image", image_arm64_probe, image_arm64_load, image_arm64_usage},
};
int file_types = sizeof(file_type) / sizeof(file_type[0]);
/* arm64 global varables. */
struct arm64_opts arm64_opts;
struct arm64_mem arm64_mem = {
.phys_offset = arm64_mem_ngv,
.vp_offset = arm64_mem_ngv,
};
uint64_t get_phys_offset(void)
{
assert(arm64_mem.phys_offset != arm64_mem_ngv);
return arm64_mem.phys_offset;
}
uint64_t get_vp_offset(void)
{
assert(arm64_mem.vp_offset != arm64_mem_ngv);
return arm64_mem.vp_offset;
}
/**
* arm64_process_image_header - Process the arm64 image header.
*
* Make a guess that KERNEL_IMAGE_SIZE will be enough for older kernels.
*/
int arm64_process_image_header(const struct arm64_image_header *h)
{
#if !defined(KERNEL_IMAGE_SIZE)
# define KERNEL_IMAGE_SIZE MiB(16)
#endif
if (!arm64_header_check_magic(h))
return -EFAILED;
if (h->image_size) {
arm64_mem.text_offset = arm64_header_text_offset(h);
arm64_mem.image_size = arm64_header_image_size(h);
} else {
/* For 3.16 and older kernels. */
arm64_mem.text_offset = 0x80000;
arm64_mem.image_size = KERNEL_IMAGE_SIZE;
fprintf(stderr,
"kexec: %s: Warning: Kernel image size set to %lu MiB.\n"
" Please verify compatability with lodaed kernel.\n",
__func__, KERNEL_IMAGE_SIZE / 1024UL / 1024UL);
}
return 0;
}
void arch_usage(void)
{
printf(arm64_opts_usage);
}
int arch_process_options(int argc, char **argv)
{
static const char short_options[] = KEXEC_OPT_STR "";
static const struct option options[] = {
KEXEC_ARCH_OPTIONS
{ 0 }
};
int opt;
char *cmdline = NULL;
const char *append = NULL;
for (opt = 0; opt != -1; ) {
opt = getopt_long(argc, argv, short_options, options, 0);
switch (opt) {
case OPT_APPEND:
append = optarg;
break;
case OPT_REUSE_CMDLINE:
cmdline = get_command_line();
break;
case OPT_DTB:
arm64_opts.dtb = optarg;
break;
case OPT_INITRD:
arm64_opts.initrd = optarg;
break;
case OPT_PANIC:
die("load-panic (-p) not supported");
break;
default:
break; /* Ignore core and unknown options. */
}
}
arm64_opts.command_line = concat_cmdline(cmdline, append);
dbgprintf("%s:%d: command_line: %s\n", __func__, __LINE__,
arm64_opts.command_line);
dbgprintf("%s:%d: initrd: %s\n", __func__, __LINE__,
arm64_opts.initrd);
dbgprintf("%s:%d: dtb: %s\n", __func__, __LINE__, arm64_opts.dtb);
return 0;
}
/**
* struct dtb - Info about a binary device tree.
*
* @buf: Device tree data.
* @size: Device tree data size.
* @name: Shorthand name of this dtb for messages.
* @path: Filesystem path.
*/
struct dtb {
char *buf;
off_t size;
const char *name;
const char *path;
};
/**
* dump_reservemap - Dump the dtb's reservemap.
*/
static void dump_reservemap(const struct dtb *dtb)
{
int i;
for (i = 0; ; i++) {
uint64_t address;
uint64_t size;
fdt_get_mem_rsv(dtb->buf, i, &address, &size);
if (!size)
break;
dbgprintf("%s: %s {%" PRIx64 ", %" PRIx64 "}\n", __func__,
dtb->name, address, size);
}
}
/**
* set_bootargs - Set the dtb's bootargs.
*/
static int set_bootargs(struct dtb *dtb, const char *command_line)
{
int result;
if (!command_line || !command_line[0])
return 0;
result = dtb_set_bootargs(&dtb->buf, &dtb->size, command_line);
if (result) {
fprintf(stderr,
"kexec: Set device tree bootargs failed.\n");
return -EFAILED;
}
return 0;
}
/**
* read_proc_dtb - Read /proc/device-tree.
*/
static int read_proc_dtb(struct dtb *dtb)
{
int result;
struct stat s;
static const char path[] = "/proc/device-tree";
result = stat(path, &s);
if (result) {
dbgprintf("%s: %s\n", __func__, strerror(errno));
return -EFAILED;
}
dtb->path = path;
create_flatten_tree((char **)&dtb->buf, &dtb->size, NULL);
return 0;
}
/**
* read_sys_dtb - Read /sys/firmware/fdt.
*/
static int read_sys_dtb(struct dtb *dtb)
{
int result;
struct stat s;
static const char path[] = "/sys/firmware/fdt";
result = stat(path, &s);
if (result) {
dbgprintf("%s: %s\n", __func__, strerror(errno));
return -EFAILED;
}
dtb->path = path;
dtb->buf = slurp_file(path, &dtb->size);
return 0;
}
/**
* read_1st_dtb - Read the 1st stage kernel's dtb.
*/
static int read_1st_dtb(struct dtb *dtb)
{
int result;
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_vp_offset() + get_phys_offset();
return p;
}
/**
* phys_to_virt - For crashdump setup.
*/
unsigned long phys_to_virt(struct crash_elf_info *elf_info,
unsigned long long p)
{
unsigned long v;
v = p - get_phys_offset() + elf_info->page_offset;
return v;
}
/**
* add_segment - Use virt_to_phys when loading elf files.
*/
void add_segment(struct kexec_info *info, const void *buf, size_t bufsz,
unsigned long base, size_t memsz)
{
add_segment_phys_virt(info, buf, bufsz, base, memsz, 1);
}
/**
* get_memory_ranges_iomem_cb - Helper for get_memory_ranges_iomem.
*/
static int get_memory_ranges_iomem_cb(void *data, int nr, char *str,
unsigned long long base, unsigned long long length)
{
struct memory_range *r;
if (nr >= KEXEC_SEGMENT_MAX)
return -1;
r = (struct memory_range *)data + nr;
r->type = RANGE_RAM;
r->start = base;
r->end = base + length - 1;
set_phys_offset(r->start);
dbgprintf("%s: %016llx - %016llx : %s", __func__, r->start,
r->end, str);
return 0;
}
/**
* get_memory_ranges_iomem - Try to get the memory ranges from /proc/iomem.
*/
static int get_memory_ranges_iomem(struct memory_range *array,
unsigned int *count)
{
*count = kexec_iomem_for_each_line("System RAM\n",
get_memory_ranges_iomem_cb, array);
if (!*count) {
dbgprintf("%s: failed: No RAM found.\n", __func__);
return -EFAILED;
}
return 0;
}
/**
* get_memory_ranges_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 = -EFAILED;
on_exit:
free(dtb.buf);
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 result;
}
int arch_compat_trampoline(struct kexec_info *info)
{
return 0;
}
int machine_verify_elf_rel(struct mem_ehdr *ehdr)
{
return (ehdr->e_machine == EM_AARCH64);
}
void machine_apply_elf_rel(struct mem_ehdr *ehdr, struct mem_sym *UNUSED(sym),
unsigned long r_type, void *ptr, unsigned long address,
unsigned long value)
{
#if !defined(R_AARCH64_ABS64)
# define R_AARCH64_ABS64 257
#endif
#if !defined(R_AARCH64_LD_PREL_LO19)
# define R_AARCH64_LD_PREL_LO19 273
#endif
#if !defined(R_AARCH64_ADR_PREL_LO21)
# define R_AARCH64_ADR_PREL_LO21 274
#endif
#if !defined(R_AARCH64_JUMP26)
# define R_AARCH64_JUMP26 282
#endif
#if !defined(R_AARCH64_CALL26)
# define R_AARCH64_CALL26 283
#endif
uint64_t *loc64;
uint32_t *loc32;
uint64_t *location = (uint64_t *)ptr;
uint64_t data = *location;
const char *type = NULL;
switch(r_type) {
case R_AARCH64_ABS64:
type = "ABS64";
loc64 = ptr;
*loc64 = cpu_to_elf64(ehdr, elf64_to_cpu(ehdr, *loc64) + value);
break;
case R_AARCH64_LD_PREL_LO19:
type = "LD_PREL_LO19";
loc32 = ptr;
*loc32 = cpu_to_le32(le32_to_cpu(*loc32)
+ (((value - address) << 3) & 0xffffe0));
break;
case R_AARCH64_ADR_PREL_LO21:
if (value & 3)
die("%s: ERROR Unaligned value: %lx\n", __func__,
value);
type = "ADR_PREL_LO21";
loc32 = ptr;
*loc32 = cpu_to_le32(le32_to_cpu(*loc32)
+ (((value - address) << 3) & 0xffffe0));
break;
case R_AARCH64_JUMP26:
type = "JUMP26";
loc32 = ptr;
*loc32 = cpu_to_le32(le32_to_cpu(*loc32)
+ (((value - address) >> 2) & 0x3ffffff));
break;
case R_AARCH64_CALL26:
type = "CALL26";
loc32 = ptr;
*loc32 = cpu_to_le32(le32_to_cpu(*loc32)
+ (((value - address) >> 2) & 0x3ffffff));
break;
default:
die("%s: ERROR Unknown type: %lu\n", __func__, r_type);
break;
}
dbgprintf("%s: %s %016lx->%016lx\n", __func__, type, data, *location);
}
void arch_reuse_initrd(void)
{
reuse_initrd = 1;
}
void arch_update_purgatory(struct kexec_info *UNUSED(info))
{
}

View File

@@ -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 vp_offset;
};
#define arm64_mem_ngv UINT64_MAX
struct arm64_mem arm64_mem;
uint64_t get_phys_offset(void);
uint64_t get_vp_offset(void);
static inline void reset_vp_offset(void)
{
arm64_mem.vp_offset = arm64_mem_ngv;
}
static inline void set_phys_offset(uint64_t v)
{
if (arm64_mem.phys_offset == arm64_mem_ngv
|| v < arm64_mem.phys_offset)
arm64_mem.phys_offset = v;
}
int arm64_process_image_header(const struct arm64_image_header *h);
int arm64_load_other_segments(struct kexec_info *info,
uint64_t kernel_entry);
#endif

View File

@@ -0,0 +1,132 @@
/*
* 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.vp_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: vp_offset: %016lx\n", __func__,
arm64_mem.vp_offset);
dbgprintf("%s: PE format: %s\n", __func__,
(arm64_header_check_pe_sig(h) ? "yes" : "no"));
result = elf_exec_load(&ehdr, info);
if (result) {
dbgprintf("%s: elf_exec_load failed\n", __func__);
goto exit;
}
result = arm64_load_other_segments(info,
virt_to_phys(ehdr.e_entry));
goto exit;
}
dbgprintf("%s: Bad arm64 image header\n", __func__);
result = -EFAILED;
goto exit;
exit:
reset_vp_offset();
free_elf_info(&ehdr);
if (result)
fprintf(stderr, "kexec: Bad elf image file, load failed.\n");
return result;
}
void elf_arm64_usage(void)
{
printf(
" An ARM64 ELF image, big or little endian.\n"
" Typically vmlinux or a stripped version of vmlinux.\n\n");
}

View File

@@ -0,0 +1,41 @@
/*
* ARM64 kexec binary image support.
*/
#define _GNU_SOURCE
#include "kexec-arm64.h"
int image_arm64_probe(const char *kernel_buf, off_t kernel_size)
{
const struct arm64_image_header *h;
if (kernel_size < sizeof(struct arm64_image_header)) {
dbgprintf("%s: No arm64 image header.\n", __func__);
return -1;
}
h = (const struct arm64_image_header *)(kernel_buf);
if (!arm64_header_check_magic(h)) {
dbgprintf("%s: Bad arm64 image header.\n", __func__);
return -1;
}
fprintf(stderr, "kexec: ARM64 binary image files are currently NOT SUPPORTED.\n");
return -1;
}
int image_arm64_load(int argc, char **argv, const char *kernel_buf,
off_t kernel_size, struct kexec_info *info)
{
return -1;
}
void image_arm64_usage(void)
{
printf(
" An ARM64 binary image, compressed or not, big or little endian.\n"
" Typically an Image, Image.gz or Image.lzma file.\n\n");
printf(
" ARM64 binary image files are currently NOT SUPPORTED.\n\n");
}

View File

@@ -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 */

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)
{
}