From: Oleksij Rempel <linux@rempel-privat.de>
To: barebox@lists.infradead.org
Subject: [RFC PATCH 3/7] bootm: add kexec ELF support
Date: Sun, 29 Apr 2018 15:09:03 +0200 [thread overview]
Message-ID: <20180429130907.20506-4-linux@rempel-privat.de> (raw)
In-Reply-To: <20180429130907.20506-1-linux@rempel-privat.de>
From: Antony Pavlov <antonynpavlov@gmail.com>
Also introduce reboot() for starting already loaded
via kexec ELF segments.
Signed-off-by: Antony Pavlov <antonynpavlov@gmail.com>
---
commands/Kconfig | 7 +
common/Kconfig | 3 +
include/bootm.h | 3 +
include/linux/reboot.h | 14 +
lib/Makefile | 1 +
lib/kexec/Makefile | 4 +
lib/kexec/kexec-bootm-elf.c | 37 +++
lib/kexec/kexec-elf-exec.c | 79 ++++++
lib/kexec/kexec-elf.c | 678 ++++++++++++++++++++++++++++++++++++++++++++
lib/kexec/kexec-elf.h | 66 +++++
lib/kexec/kexec.c | 281 ++++++++++++++++++
lib/kexec/kexec.h | 92 ++++++
12 files changed, 1265 insertions(+)
create mode 100644 include/linux/reboot.h
create mode 100644 lib/kexec/Makefile
create mode 100644 lib/kexec/kexec-bootm-elf.c
create mode 100644 lib/kexec/kexec-elf-exec.c
create mode 100644 lib/kexec/kexec-elf.c
create mode 100644 lib/kexec/kexec-elf.h
create mode 100644 lib/kexec/kexec.c
create mode 100644 lib/kexec/kexec.h
diff --git a/commands/Kconfig b/commands/Kconfig
index 951a86963..a49928ad1 100644
--- a/commands/Kconfig
+++ b/commands/Kconfig
@@ -476,6 +476,13 @@ config CMD_SAVES
Save S-Record file to serial line with offset OFFS and length LEN.
+config KEXEC
+ bool
+ prompt "bootm ELF image support"
+ depends on CMD_BOOTM && HAS_KEXEC
+ help
+ Support using ELF Images.
+
config CMD_UIMAGE
select UIMAGE
tristate
diff --git a/common/Kconfig b/common/Kconfig
index b7000c4d7..d40c79dbc 100644
--- a/common/Kconfig
+++ b/common/Kconfig
@@ -1218,3 +1218,6 @@ config HAS_DEBUG_LL
config DDR_SPD
bool
select CRC16
+
+config HAS_KEXEC
+ bool
diff --git a/include/bootm.h b/include/bootm.h
index 62951d605..3a642fe70 100644
--- a/include/bootm.h
+++ b/include/bootm.h
@@ -72,6 +72,9 @@ struct image_data {
char *oftree_file;
char *oftree_part;
+ unsigned long oftree_address;
+
+ unsigned long cmdline_address;
const void *fit_kernel;
unsigned long fit_kernel_size;
diff --git a/include/linux/reboot.h b/include/linux/reboot.h
new file mode 100644
index 000000000..1679070b4
--- /dev/null
+++ b/include/linux/reboot.h
@@ -0,0 +1,14 @@
+#ifndef _LINUX_REBOOT_H
+#define _LINUX_REBOOT_H
+
+/*
+ * Commands accepted by the _reboot() system call.
+ *
+ * KEXEC Restart system using a previously loaded Linux kernel
+ */
+
+#define LINUX_REBOOT_CMD_KEXEC 0x45584543
+
+extern int reboot(int cmd, void *opaque);
+
+#endif /* _LINUX_REBOOT_H */
diff --git a/lib/Makefile b/lib/Makefile
index a7498288a..eebaf3488 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -65,3 +65,4 @@ obj-y += int_sqrt.o
obj-y += parseopt.o
obj-y += clz_ctz.o
obj-$(CONFIG_CRC_CCITT) += crc-ccitt.o
+obj-$(CONFIG_KEXEC) += kexec/
diff --git a/lib/kexec/Makefile b/lib/kexec/Makefile
new file mode 100644
index 000000000..2f3dc1dd3
--- /dev/null
+++ b/lib/kexec/Makefile
@@ -0,0 +1,4 @@
+obj-y += kexec.o
+obj-y += kexec-elf.o
+obj-y += kexec-elf-exec.o
+obj-y += kexec-bootm-elf.o
diff --git a/lib/kexec/kexec-bootm-elf.c b/lib/kexec/kexec-bootm-elf.c
new file mode 100644
index 000000000..78c9f85cf
--- /dev/null
+++ b/lib/kexec/kexec-bootm-elf.c
@@ -0,0 +1,37 @@
+#include <bootm.h>
+#include <init.h>
+#include <binfmt.h>
+#include <errno.h>
+#include <linux/reboot.h>
+#include <environment.h>
+
+#include "kexec.h"
+
+static int do_bootm_elf(struct image_data *data)
+{
+ kexec_load_bootm_data(data);
+
+ reboot(LINUX_REBOOT_CMD_KEXEC, data);
+
+ return -ERESTARTSYS;
+}
+
+static struct image_handler elf_handler = {
+ .name = "ELF",
+ .bootm = do_bootm_elf,
+ .filetype = filetype_elf,
+};
+
+static struct binfmt_hook binfmt_elf_hook = {
+ .type = filetype_elf,
+ .exec = "bootm",
+};
+
+static int elf_register_image_handler(void)
+{
+ register_image_handler(&elf_handler);
+ binfmt_register(&binfmt_elf_hook);
+
+ return 0;
+}
+late_initcall(elf_register_image_handler);
diff --git a/lib/kexec/kexec-elf-exec.c b/lib/kexec/kexec-elf-exec.c
new file mode 100644
index 000000000..094970e3b
--- /dev/null
+++ b/lib/kexec/kexec-elf-exec.c
@@ -0,0 +1,79 @@
+#include <common.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <memory.h>
+#include <elf.h>
+#include "kexec.h"
+#include "kexec-elf.h"
+
+int build_elf_exec_info(const char *buf, off_t len, struct mem_ehdr *ehdr,
+ uint32_t flags)
+{
+ struct mem_phdr *phdr, *end_phdr;
+ int result;
+
+ result = build_elf_info(buf, len, ehdr, flags);
+ if (result < 0)
+ return result;
+
+ if (ehdr->e_type != ET_EXEC) {
+ printf("Not ELF type ET_EXEC\n");
+ return -1;
+ }
+
+ if (!ehdr->e_phdr) {
+ printf("No ELF program header\n");
+ return -1;
+ }
+
+ end_phdr = &ehdr->e_phdr[ehdr->e_phnum];
+ for (phdr = ehdr->e_phdr; phdr != end_phdr; phdr++) {
+ /* Kexec does not support loading interpreters.
+ * In addition this check keeps us from attempting
+ * to kexec ordinay executables.
+ */
+ if (phdr->p_type == PT_INTERP) {
+ printf("Requires an ELF interpreter\n");
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int elf_exec_load(struct mem_ehdr *ehdr, struct kexec_info *info)
+{
+ int result;
+ size_t i;
+
+ if (!ehdr->e_phdr) {
+ printf("No program header?\n");
+ result = -1;
+ goto out;
+ }
+
+ /* Read in the PT_LOAD segments */
+ for (i = 0; i < ehdr->e_phnum; i++) {
+ struct mem_phdr *phdr;
+ size_t size;
+
+ phdr = &ehdr->e_phdr[i];
+
+ if (phdr->p_type != PT_LOAD)
+ continue;
+
+ size = phdr->p_filesz;
+
+ if (size > phdr->p_memsz)
+ size = phdr->p_memsz;
+
+ add_segment(info,
+ phdr->p_data, size,
+ phdr->p_paddr, phdr->p_memsz);
+ }
+
+ result = 0;
+ out:
+ return result;
+}
diff --git a/lib/kexec/kexec-elf.c b/lib/kexec/kexec-elf.c
new file mode 100644
index 000000000..44eb23288
--- /dev/null
+++ b/lib/kexec/kexec-elf.c
@@ -0,0 +1,678 @@
+#include <common.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <malloc.h>
+#include <memory.h>
+#include <asm/io.h>
+#include "elf.h"
+#include "kexec.h"
+#include "kexec-elf.h"
+
+uint16_t elf16_to_cpu(const struct mem_ehdr *ehdr, uint16_t value)
+{
+ if (ehdr->ei_data == ELFDATA2LSB)
+ value = le16_to_cpu(value);
+ else if (ehdr->ei_data == ELFDATA2MSB)
+ value = be16_to_cpu(value);
+
+ return value;
+}
+
+uint32_t elf32_to_cpu(const struct mem_ehdr *ehdr, uint32_t value)
+{
+ if (ehdr->ei_data == ELFDATA2LSB)
+ value = le32_to_cpu(value);
+ else if (ehdr->ei_data == ELFDATA2MSB)
+ value = be32_to_cpu(value);
+
+ return value;
+}
+
+uint64_t elf64_to_cpu(const struct mem_ehdr *ehdr, uint64_t value)
+{
+ if (ehdr->ei_data == ELFDATA2LSB)
+ value = le64_to_cpu(value);
+ else if (ehdr->ei_data == ELFDATA2MSB)
+ value = be64_to_cpu(value);
+
+ return value;
+}
+
+static int build_mem_elf32_ehdr(const char *buf, off_t len,
+ struct mem_ehdr *ehdr)
+{
+ Elf32_Ehdr lehdr;
+
+ if ((size_t)len < sizeof(lehdr)) {
+ printf("Buffer is too small to hold ELF header\n");
+ return -1;
+ }
+
+ memcpy(&lehdr, buf, sizeof(lehdr));
+ if (elf16_to_cpu(ehdr, lehdr.e_ehsize) != sizeof(Elf32_Ehdr)) {
+ printf("Bad ELF header size\n");
+ return -1;
+ }
+
+ if (elf32_to_cpu(ehdr, lehdr.e_entry) > UINT32_MAX) {
+ printf("ELF e_entry is too large\n");
+ return -1;
+ }
+
+ if (elf32_to_cpu(ehdr, lehdr.e_phoff) > UINT32_MAX) {
+ printf("ELF e_phoff is too large\n");
+ return -1;
+ }
+
+ if (elf32_to_cpu(ehdr, lehdr.e_shoff) > UINT32_MAX) {
+ printf("ELF e_shoff is too large\n");
+ return -1;
+ }
+
+ ehdr->e_type = elf16_to_cpu(ehdr, lehdr.e_type);
+ ehdr->e_machine = elf16_to_cpu(ehdr, lehdr.e_machine);
+ ehdr->e_version = elf32_to_cpu(ehdr, lehdr.e_version);
+ ehdr->e_entry = elf32_to_cpu(ehdr, lehdr.e_entry);
+ ehdr->e_phoff = elf32_to_cpu(ehdr, lehdr.e_phoff);
+ ehdr->e_shoff = elf32_to_cpu(ehdr, lehdr.e_shoff);
+ ehdr->e_flags = elf32_to_cpu(ehdr, lehdr.e_flags);
+ ehdr->e_phnum = elf16_to_cpu(ehdr, lehdr.e_phnum);
+ ehdr->e_shnum = elf16_to_cpu(ehdr, lehdr.e_shnum);
+ ehdr->e_shstrndx = elf16_to_cpu(ehdr, lehdr.e_shstrndx);
+
+ if ((ehdr->e_phnum > 0) &&
+ (elf16_to_cpu(ehdr, lehdr.e_phentsize) != sizeof(Elf32_Phdr))) {
+ printf("ELF bad program header size\n");
+ return -1;
+ }
+
+ if ((ehdr->e_shnum > 0) &&
+ (elf16_to_cpu(ehdr, lehdr.e_shentsize) != sizeof(Elf32_Shdr))) {
+ printf("ELF bad section header size\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int build_mem_elf64_ehdr(const char *buf, off_t len,
+ struct mem_ehdr *ehdr)
+{
+ Elf64_Ehdr lehdr;
+
+ if ((size_t)len < sizeof(lehdr)) {
+ printf("Buffer is too small to hold ELF header\n");
+ return -1;
+ }
+
+ memcpy(&lehdr, buf, sizeof(lehdr));
+ if (elf16_to_cpu(ehdr, lehdr.e_ehsize) != sizeof(Elf64_Ehdr)) {
+ printf("Bad ELF header size\n");
+ return -1;
+ }
+
+ ehdr->e_type = elf16_to_cpu(ehdr, lehdr.e_type);
+ ehdr->e_machine = elf16_to_cpu(ehdr, lehdr.e_machine);
+ ehdr->e_version = elf32_to_cpu(ehdr, lehdr.e_version);
+ ehdr->e_entry = elf64_to_cpu(ehdr, lehdr.e_entry);
+ ehdr->e_phoff = elf64_to_cpu(ehdr, lehdr.e_phoff);
+ ehdr->e_shoff = elf64_to_cpu(ehdr, lehdr.e_shoff);
+ ehdr->e_flags = elf32_to_cpu(ehdr, lehdr.e_flags);
+ ehdr->e_phnum = elf16_to_cpu(ehdr, lehdr.e_phnum);
+ ehdr->e_shnum = elf16_to_cpu(ehdr, lehdr.e_shnum);
+ ehdr->e_shstrndx = elf16_to_cpu(ehdr, lehdr.e_shstrndx);
+
+ if ((ehdr->e_phnum > 0) &&
+ (elf16_to_cpu(ehdr, lehdr.e_phentsize) != sizeof(Elf64_Phdr))) {
+ printf("ELF bad program header size\n");
+ return -1;
+ }
+
+ if ((ehdr->e_shnum > 0) &&
+ (elf16_to_cpu(ehdr, lehdr.e_shentsize) != sizeof(Elf64_Shdr))) {
+ printf("ELF bad section header size\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int build_mem_ehdr(const char *buf, off_t len, struct mem_ehdr *ehdr)
+{
+ unsigned char e_ident[EI_NIDENT];
+ int result;
+
+ memset(ehdr, 0, sizeof(*ehdr));
+
+ if ((size_t)len < sizeof(e_ident)) {
+ printf("Buffer is too small to hold ELF e_ident\n");
+
+ return -1;
+ }
+
+ memcpy(e_ident, buf, sizeof(e_ident));
+
+ ehdr->ei_class = e_ident[EI_CLASS];
+ ehdr->ei_data = e_ident[EI_DATA];
+ if ((ehdr->ei_class != ELFCLASS32) &&
+ (ehdr->ei_class != ELFCLASS64)) {
+ printf("Not a supported ELF class\n");
+ return -1;
+ }
+
+ if ((ehdr->ei_data != ELFDATA2LSB) &&
+ (ehdr->ei_data != ELFDATA2MSB)) {
+ printf("Not a supported ELF data format\n");
+ return -1;
+ }
+
+ result = -1;
+ if (ehdr->ei_class == ELFCLASS32)
+ result = build_mem_elf32_ehdr(buf, len, ehdr);
+
+ if (ehdr->ei_class == ELFCLASS64)
+ result = build_mem_elf64_ehdr(buf, len, ehdr);
+
+ if (result < 0)
+ return result;
+
+ if ((e_ident[EI_VERSION] != EV_CURRENT) ||
+ (ehdr->e_version != EV_CURRENT)) {
+ printf("Unknown ELF version\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int build_mem_elf32_phdr(const char *buf, struct mem_ehdr *ehdr, int idx)
+{
+ struct mem_phdr *phdr;
+ const char *pbuf;
+ Elf32_Phdr lphdr;
+
+ pbuf = buf + ehdr->e_phoff + (idx * sizeof(lphdr));
+ phdr = &ehdr->e_phdr[idx];
+ memcpy(&lphdr, pbuf, sizeof(lphdr));
+
+ if ((elf32_to_cpu(ehdr, lphdr.p_filesz) > UINT32_MAX) ||
+ (elf32_to_cpu(ehdr, lphdr.p_memsz) > UINT32_MAX) ||
+ (elf32_to_cpu(ehdr, lphdr.p_offset) > UINT32_MAX) ||
+ (elf32_to_cpu(ehdr, lphdr.p_paddr) > UINT32_MAX) ||
+ (elf32_to_cpu(ehdr, lphdr.p_vaddr) > UINT32_MAX) ||
+ (elf32_to_cpu(ehdr, lphdr.p_align) > UINT32_MAX)) {
+ printf("Program segment size out of range\n");
+ return -1;
+ }
+
+ phdr->p_type = elf32_to_cpu(ehdr, lphdr.p_type);
+ phdr->p_paddr = elf32_to_cpu(ehdr, lphdr.p_paddr);
+ phdr->p_vaddr = elf32_to_cpu(ehdr, lphdr.p_vaddr);
+ phdr->p_filesz = elf32_to_cpu(ehdr, lphdr.p_filesz);
+ phdr->p_memsz = elf32_to_cpu(ehdr, lphdr.p_memsz);
+ phdr->p_offset = elf32_to_cpu(ehdr, lphdr.p_offset);
+ phdr->p_flags = elf32_to_cpu(ehdr, lphdr.p_flags);
+ phdr->p_align = elf32_to_cpu(ehdr, lphdr.p_align);
+
+ return 0;
+}
+
+static int build_mem_elf64_phdr(const char *buf, struct mem_ehdr *ehdr, int idx)
+{
+ struct mem_phdr *phdr;
+ const char *pbuf;
+ Elf64_Phdr lphdr;
+
+ pbuf = buf + ehdr->e_phoff + (idx * sizeof(lphdr));
+ phdr = &ehdr->e_phdr[idx];
+ memcpy(&lphdr, pbuf, sizeof(lphdr));
+ phdr->p_type = elf32_to_cpu(ehdr, lphdr.p_type);
+ phdr->p_paddr = elf64_to_cpu(ehdr, lphdr.p_paddr);
+ phdr->p_vaddr = elf64_to_cpu(ehdr, lphdr.p_vaddr);
+ phdr->p_filesz = elf64_to_cpu(ehdr, lphdr.p_filesz);
+ phdr->p_memsz = elf64_to_cpu(ehdr, lphdr.p_memsz);
+ phdr->p_offset = elf64_to_cpu(ehdr, lphdr.p_offset);
+ phdr->p_flags = elf32_to_cpu(ehdr, lphdr.p_flags);
+ phdr->p_align = elf64_to_cpu(ehdr, lphdr.p_align);
+
+ return 0;
+}
+
+static int build_mem_phdrs(const char *buf, off_t len, struct mem_ehdr *ehdr,
+ uint32_t flags)
+{
+ size_t phdr_size, mem_phdr_size, i;
+
+ /* e_phnum is at most 65535 so calculating
+ * the size of the program header cannot overflow.
+ */
+ /* Is the program header in the file buffer? */
+ phdr_size = 0;
+ if (ehdr->ei_class == ELFCLASS32) {
+ phdr_size = sizeof(Elf32_Phdr);
+ } else if (ehdr->ei_class == ELFCLASS64) {
+ phdr_size = sizeof(Elf64_Phdr);
+ } else {
+ printf("Invalid ei_class?\n");
+ return -1;
+ }
+ phdr_size *= ehdr->e_phnum;
+
+ /* Allocate the e_phdr array */
+ mem_phdr_size = sizeof(ehdr->e_phdr[0]) * ehdr->e_phnum;
+ ehdr->e_phdr = xmalloc(mem_phdr_size);
+
+ for (i = 0; i < ehdr->e_phnum; i++) {
+ struct mem_phdr *phdr;
+ int result;
+
+ result = -1;
+ if (ehdr->ei_class == ELFCLASS32)
+ result = build_mem_elf32_phdr(buf, ehdr, i);
+
+ if (ehdr->ei_class == ELFCLASS64)
+ result = build_mem_elf64_phdr(buf, ehdr, i);
+
+ if (result < 0)
+ return result;
+
+ /* Check the program headers to be certain
+ * they are safe to use.
+ */
+ phdr = &ehdr->e_phdr[i];
+ if ((phdr->p_paddr + phdr->p_memsz) < phdr->p_paddr) {
+ /* The memory address wraps */
+ printf("ELF address wrap around\n");
+ return -1;
+ }
+
+ /* Remember where the segment lives in the buffer */
+ phdr->p_data = buf + phdr->p_offset;
+ }
+
+ return 0;
+}
+
+static int build_mem_elf32_shdr(const char *buf, struct mem_ehdr *ehdr, int idx)
+{
+ struct mem_shdr *shdr;
+ const char *sbuf;
+ int size_ok;
+ Elf32_Shdr lshdr;
+
+ sbuf = buf + ehdr->e_shoff + (idx * sizeof(lshdr));
+ shdr = &ehdr->e_shdr[idx];
+ memcpy(&lshdr, sbuf, sizeof(lshdr));
+
+ if ((elf32_to_cpu(ehdr, lshdr.sh_flags) > UINT32_MAX) ||
+ (elf32_to_cpu(ehdr, lshdr.sh_addr) > UINT32_MAX) ||
+ (elf32_to_cpu(ehdr, lshdr.sh_offset) > UINT32_MAX) ||
+ (elf32_to_cpu(ehdr, lshdr.sh_size) > UINT32_MAX) ||
+ (elf32_to_cpu(ehdr, lshdr.sh_addralign) > UINT32_MAX) ||
+ (elf32_to_cpu(ehdr, lshdr.sh_entsize) > UINT32_MAX)) {
+ printf("Program section size out of range\n");
+ return -1;
+ }
+
+ shdr->sh_name = elf32_to_cpu(ehdr, lshdr.sh_name);
+ shdr->sh_type = elf32_to_cpu(ehdr, lshdr.sh_type);
+ shdr->sh_flags = elf32_to_cpu(ehdr, lshdr.sh_flags);
+ shdr->sh_addr = elf32_to_cpu(ehdr, lshdr.sh_addr);
+ shdr->sh_offset = elf32_to_cpu(ehdr, lshdr.sh_offset);
+ shdr->sh_size = elf32_to_cpu(ehdr, lshdr.sh_size);
+ shdr->sh_link = elf32_to_cpu(ehdr, lshdr.sh_link);
+ shdr->sh_info = elf32_to_cpu(ehdr, lshdr.sh_info);
+ shdr->sh_addralign = elf32_to_cpu(ehdr, lshdr.sh_addralign);
+ shdr->sh_entsize = elf32_to_cpu(ehdr, lshdr.sh_entsize);
+
+ /* Now verify sh_entsize */
+ size_ok = 0;
+ switch (shdr->sh_type) {
+ case SHT_SYMTAB:
+ size_ok = shdr->sh_entsize == sizeof(Elf32_Sym);
+ break;
+ case SHT_RELA:
+ size_ok = shdr->sh_entsize == sizeof(Elf32_Rela);
+ break;
+ case SHT_DYNAMIC:
+ size_ok = shdr->sh_entsize == sizeof(Elf32_Dyn);
+ break;
+ case SHT_REL:
+ size_ok = shdr->sh_entsize == sizeof(Elf32_Rel);
+ break;
+ case SHT_NOTE:
+ case SHT_NULL:
+ case SHT_PROGBITS:
+ case SHT_HASH:
+ case SHT_NOBITS:
+ default:
+ /* This is a section whose entsize requirements
+ * I don't care about. If I don't know about
+ * the section I can't care about it's entsize
+ * requirements.
+ */
+ size_ok = 1;
+ break;
+ }
+
+ if (!size_ok) {
+ printf("Bad section header(%x) entsize: %lld\n",
+ shdr->sh_type, shdr->sh_entsize);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int build_mem_elf64_shdr(const char *buf, struct mem_ehdr *ehdr, int idx)
+{
+ struct mem_shdr *shdr;
+ const char *sbuf;
+ int size_ok;
+ Elf64_Shdr lshdr;
+
+ sbuf = buf + ehdr->e_shoff + (idx * sizeof(lshdr));
+ shdr = &ehdr->e_shdr[idx];
+ memcpy(&lshdr, sbuf, sizeof(lshdr));
+ shdr->sh_name = elf32_to_cpu(ehdr, lshdr.sh_name);
+ shdr->sh_type = elf32_to_cpu(ehdr, lshdr.sh_type);
+ shdr->sh_flags = elf64_to_cpu(ehdr, lshdr.sh_flags);
+ shdr->sh_addr = elf64_to_cpu(ehdr, lshdr.sh_addr);
+ shdr->sh_offset = elf64_to_cpu(ehdr, lshdr.sh_offset);
+ shdr->sh_size = elf64_to_cpu(ehdr, lshdr.sh_size);
+ shdr->sh_link = elf32_to_cpu(ehdr, lshdr.sh_link);
+ shdr->sh_info = elf32_to_cpu(ehdr, lshdr.sh_info);
+ shdr->sh_addralign = elf64_to_cpu(ehdr, lshdr.sh_addralign);
+ shdr->sh_entsize = elf64_to_cpu(ehdr, lshdr.sh_entsize);
+
+ /* Now verify sh_entsize */
+ size_ok = 0;
+ switch (shdr->sh_type) {
+ case SHT_SYMTAB:
+ size_ok = shdr->sh_entsize == sizeof(Elf64_Sym);
+ break;
+ case SHT_RELA:
+ size_ok = shdr->sh_entsize == sizeof(Elf64_Rela);
+ break;
+ case SHT_DYNAMIC:
+ size_ok = shdr->sh_entsize == sizeof(Elf64_Dyn);
+ break;
+ case SHT_REL:
+ size_ok = shdr->sh_entsize == sizeof(Elf64_Rel);
+ break;
+ case SHT_NOTE:
+ case SHT_NULL:
+ case SHT_PROGBITS:
+ case SHT_HASH:
+ case SHT_NOBITS:
+ default:
+ /* This is a section whose entsize requirements
+ * I don't care about. If I don't know about
+ * the section I can't care about it's entsize
+ * requirements.
+ */
+ size_ok = 1;
+ break;
+ }
+
+ if (!size_ok) {
+ printf("Bad section header(%x) entsize: %lld\n",
+ shdr->sh_type, shdr->sh_entsize);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int build_mem_shdrs(const char *buf, off_t len, struct mem_ehdr *ehdr,
+ uint32_t flags)
+{
+ size_t shdr_size, mem_shdr_size, i;
+
+ /* e_shnum is at most 65536 so calculating
+ * the size of the section header cannot overflow.
+ */
+ /* Is the program header in the file buffer? */
+ shdr_size = 0;
+ if (ehdr->ei_class == ELFCLASS32) {
+ shdr_size = sizeof(Elf32_Shdr);
+ } else if (ehdr->ei_class == ELFCLASS64) {
+ shdr_size = sizeof(Elf64_Shdr);
+ } else {
+ printf("Invalid ei_class?\n");
+ return -1;
+ }
+ shdr_size *= ehdr->e_shnum;
+
+ /* Allocate the e_shdr array */
+ mem_shdr_size = sizeof(ehdr->e_shdr[0]) * ehdr->e_shnum;
+ ehdr->e_shdr = xmalloc(mem_shdr_size);
+
+ for (i = 0; i < ehdr->e_shnum; i++) {
+ struct mem_shdr *shdr;
+ int result;
+
+ result = -1;
+ if (ehdr->ei_class == ELFCLASS32)
+ result = build_mem_elf32_shdr(buf, ehdr, i);
+
+ if (ehdr->ei_class == ELFCLASS64)
+ result = build_mem_elf64_shdr(buf, ehdr, i);
+
+ if (result < 0)
+ return result;
+
+ /* Check the section headers to be certain
+ * they are safe to use.
+ */
+ shdr = &ehdr->e_shdr[i];
+ if ((shdr->sh_addr + shdr->sh_size) < shdr->sh_addr) {
+ printf("ELF address wrap around\n");
+ return -1;
+ }
+
+ /* Remember where the section lives in the buffer */
+ shdr->sh_data = (unsigned char *)(buf + shdr->sh_offset);
+ }
+
+ return 0;
+}
+
+void free_elf_info(struct mem_ehdr *ehdr)
+{
+ free(ehdr->e_phdr);
+ free(ehdr->e_shdr);
+ memset(ehdr, 0, sizeof(*ehdr));
+}
+
+int build_elf_info(const char *buf, off_t len, struct mem_ehdr *ehdr,
+ uint32_t flags)
+{
+ int result;
+
+ result = build_mem_ehdr(buf, len, ehdr);
+ if (result < 0)
+ return result;
+
+ if ((ehdr->e_phoff > 0) && (ehdr->e_phnum > 0)) {
+ result = build_mem_phdrs(buf, len, ehdr, flags);
+ if (result < 0) {
+ free_elf_info(ehdr);
+ return result;
+ }
+ }
+
+ if ((ehdr->e_shoff > 0) && (ehdr->e_shnum > 0)) {
+ result = build_mem_shdrs(buf, len, ehdr, flags);
+ if (result < 0) {
+ free_elf_info(ehdr);
+ return result;
+ }
+ }
+
+ return 0;
+}
+
+int check_room_for_elf(struct list_head *elf_segments)
+{
+ struct memory_bank *bank;
+ struct resource *res, *r;
+
+ list_for_each_entry(r, elf_segments, sibling) {
+ int got_bank;
+
+ got_bank = 0;
+ for_each_memory_bank(bank) {
+ resource_size_t start, end;
+
+ res = bank->res;
+
+ start = virt_to_phys((void *)res->start);
+ end = virt_to_phys((void *)res->end);
+
+ if ((start <= r->start) && (end >= r->end)) {
+ got_bank = 1;
+ break;
+ }
+ }
+
+ if (!got_bank)
+ return -1;
+ }
+
+ return 0;
+}
+
+/* sort by size */
+static int compare(struct list_head *a, struct list_head *b)
+{
+ struct resource *ra = (struct resource *)list_entry(a,
+ struct resource, sibling);
+ struct resource *rb = (struct resource *)list_entry(b,
+ struct resource, sibling);
+ resource_size_t sa, sb;
+
+ sa = ra->end - ra->start;
+ sb = rb->end - rb->start;
+
+ if (sa > sb)
+ return -1;
+ if (sa < sb)
+ return 1;
+ return 0;
+}
+
+void list_add_used_region(struct list_head *new, struct list_head *head)
+{
+ struct list_head *pos, *insert = head;
+ struct resource *rb =
+ (struct resource *)list_entry(new, struct resource, sibling);
+ struct list_head *n;
+
+ /* rb --- new region */
+ list_for_each_safe(pos, n, head) {
+ struct resource *ra = (struct resource *)list_entry(pos,
+ struct resource, sibling);
+
+ if (((rb->end >= ra->start) && (rb->end <= ra->end))
+ || ((rb->start >= ra->start) && (rb->start <= ra->end))
+ || ((rb->start >= ra->start) && (rb->end <= ra->end))
+ || ((ra->start >= rb->start) && (ra->end <= rb->end))
+ || (ra->start == rb->end + 1)
+ || (rb->start == ra->end + 1)) {
+ rb->start = min(ra->start, rb->start);
+ rb->end = max(ra->end, rb->end);
+ rb->name = "join";
+ list_del(pos);
+ }
+ }
+
+ list_for_each(pos, head) {
+ struct resource *ra = (struct resource *)list_entry(pos,
+ struct resource, sibling);
+
+ if (ra->start < rb->start)
+ continue;
+
+ insert = pos;
+ break;
+ }
+
+ list_add_tail(new, insert);
+}
+
+resource_size_t dcheck_res(struct list_head *elf_segments)
+{
+ struct memory_bank *bank;
+ struct resource *res, *r, *t;
+
+ LIST_HEAD(elf_relocate_banks);
+ LIST_HEAD(elf_relocate_banks_size_sorted);
+ LIST_HEAD(used_regions);
+
+ for_each_memory_bank(bank) {
+ res = bank->res;
+
+ list_for_each_entry(r, &res->children, sibling) {
+ t = create_resource("tmp",
+ virt_to_phys((void *)r->start),
+ virt_to_phys((void *)r->end));
+ list_add_used_region(&t->sibling, &used_regions);
+ }
+ }
+
+ list_for_each_entry(r, elf_segments, sibling) {
+ t = create_resource(r->name, r->start, r->end);
+ list_add_used_region(&t->sibling, &used_regions);
+ }
+
+ for_each_memory_bank(bank) {
+ resource_size_t start;
+
+ res = bank->res;
+ res = create_resource("tmp",
+ virt_to_phys((void *)res->start),
+ virt_to_phys((void *)res->end));
+ start = res->start;
+
+ list_for_each_entry(r, &used_regions, sibling) {
+ if (res->start > r->end)
+ continue;
+
+ if (res->end < r->start)
+ continue;
+
+ if (r->start - start) {
+ struct resource *t;
+
+ t = create_resource("ELF buffer", start,
+ r->start - 1);
+ list_add_used_region(&t->sibling,
+ &elf_relocate_banks);
+ }
+ start = r->end + 1;
+ }
+
+ if (res->end - start) {
+ struct resource *t;
+
+ t = create_resource("ELF buffer", start, res->end);
+ list_add_used_region(&t->sibling, &elf_relocate_banks);
+ }
+ }
+
+ list_for_each_entry(r, &elf_relocate_banks, sibling) {
+ struct resource *t;
+
+ t = create_resource("ELF buffer", r->start, r->end);
+ list_add_sort(&t->sibling,
+ &elf_relocate_banks_size_sorted, compare);
+ }
+
+ r = list_first_entry(&elf_relocate_banks_size_sorted, struct resource,
+ sibling);
+
+ /* FIXME */
+ return r->start;
+}
diff --git a/lib/kexec/kexec-elf.h b/lib/kexec/kexec-elf.h
new file mode 100644
index 000000000..06826c44c
--- /dev/null
+++ b/lib/kexec/kexec-elf.h
@@ -0,0 +1,66 @@
+#ifndef KEXEC_ELF_H
+#define KEXEC_ELF_H
+
+struct kexec_info;
+
+struct mem_ehdr {
+ unsigned ei_class;
+ unsigned ei_data;
+ unsigned e_type;
+ unsigned e_machine;
+ unsigned e_version;
+ unsigned e_flags;
+ unsigned e_phnum;
+ unsigned e_shnum;
+ unsigned e_shstrndx;
+ unsigned long long e_entry;
+ unsigned long long e_phoff;
+ unsigned long long e_shoff;
+ struct mem_phdr *e_phdr;
+ struct mem_shdr *e_shdr;
+};
+
+struct mem_phdr {
+ unsigned long long p_paddr;
+ unsigned long long p_vaddr;
+ unsigned long long p_filesz;
+ unsigned long long p_memsz;
+ unsigned long long p_offset;
+ const char *p_data;
+ unsigned p_type;
+ unsigned p_flags;
+ unsigned long long p_align;
+};
+
+struct mem_shdr {
+ unsigned sh_name;
+ unsigned sh_type;
+ unsigned long long sh_flags;
+ unsigned long long sh_addr;
+ unsigned long long sh_offset;
+ unsigned long long sh_size;
+ unsigned sh_link;
+ unsigned sh_info;
+ unsigned long long sh_addralign;
+ unsigned long long sh_entsize;
+ const unsigned char *sh_data;
+};
+
+extern void free_elf_info(struct mem_ehdr *ehdr);
+extern int build_elf_info(const char *buf, off_t len, struct mem_ehdr *ehdr,
+ uint32_t flags);
+extern int build_elf_exec_info(const char *buf, off_t len,
+ struct mem_ehdr *ehdr, uint32_t flags);
+
+extern int elf_exec_load(struct mem_ehdr *ehdr, struct kexec_info *info);
+
+uint16_t elf16_to_cpu(const struct mem_ehdr *ehdr, uint16_t value);
+uint32_t elf32_to_cpu(const struct mem_ehdr *ehdr, uint32_t value);
+uint64_t elf64_to_cpu(const struct mem_ehdr *ehdr, uint64_t value);
+
+unsigned long elf_max_addr(const struct mem_ehdr *ehdr);
+int check_room_for_elf(struct list_head *elf_segments);
+resource_size_t dcheck_res(struct list_head *elf_segments);
+void list_add_used_region(struct list_head *new, struct list_head *head);
+
+#endif /* KEXEC_ELF_H */
diff --git a/lib/kexec/kexec.c b/lib/kexec/kexec.c
new file mode 100644
index 000000000..3a021173e
--- /dev/null
+++ b/lib/kexec/kexec.c
@@ -0,0 +1,281 @@
+/*
+ * kexec: Linux boots Linux
+ *
+ * Copyright (C) 2003-2005 Eric Biederman (ebiederm@xmission.com)
+ *
+ * Modified (2007-05-15) by Francesco Chiechi to rudely handle mips platform
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation (version 2 of the License).
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#define _GNU_SOURCE
+#include <common.h>
+#include <fs.h>
+#include <stdarg.h>
+#include <string.h>
+#include <stdlib.h>
+#include <asm/io.h>
+
+#include <boot.h>
+#include <libfile.h>
+
+#include "kexec.h"
+#include "kexec-elf.h"
+
+static int sort_segments(struct kexec_info *info)
+{
+ int i, j;
+ void *end;
+
+ /* Do a stupid insertion sort... */
+ for (i = 0; i < info->nr_segments; i++) {
+ int tidx;
+ struct kexec_segment temp;
+ tidx = i;
+ for (j = i + 1; j < info->nr_segments; j++) {
+ if (info->segment[j].mem < info->segment[tidx].mem)
+ tidx = j;
+ }
+ if (tidx != i) {
+ temp = info->segment[tidx];
+ info->segment[tidx] = info->segment[i];
+ info->segment[i] = temp;
+ }
+ }
+
+ /* Now see if any of the segments overlap */
+ end = 0;
+ for (i = 0; i < info->nr_segments; i++) {
+ if (end > info->segment[i].mem) {
+ printf("Overlapping memory segments at %p\n",
+ end);
+ return -1;
+ }
+ end = ((char *)info->segment[i].mem) + info->segment[i].memsz;
+ }
+
+ return 0;
+}
+
+void add_segment_phys_virt(struct kexec_info *info,
+ const void *buf, size_t bufsz,
+ unsigned long base, size_t memsz, int phys)
+{
+ size_t size;
+ int pagesize;
+
+ if (bufsz > memsz)
+ bufsz = memsz;
+
+ /* Forget empty segments */
+ if (!memsz)
+ return;
+
+ /* Round memsz up to a multiple of pagesize */
+ pagesize = 4096;
+ memsz = (memsz + (pagesize - 1)) & ~(pagesize - 1);
+
+ if (phys)
+ base = virt_to_phys((void *)base);
+
+ size = (info->nr_segments + 1) * sizeof(info->segment[0]);
+ info->segment = xrealloc(info->segment, size);
+ info->segment[info->nr_segments].buf = buf;
+ info->segment[info->nr_segments].bufsz = bufsz;
+ info->segment[info->nr_segments].mem = (void *)base;
+ info->segment[info->nr_segments].memsz = memsz;
+ info->nr_segments++;
+ if (info->nr_segments > KEXEC_MAX_SEGMENTS) {
+ printf("Warning: kernel segment limit reached. "
+ "This will likely fail\n");
+ }
+}
+
+static int kexec_load_one_file(struct kexec_info *info, char *fname,
+ unsigned long kexec_flags)
+{
+ char *buf;
+ size_t fsize;
+ int i = 0;
+
+ buf = read_file(fname, &fsize);
+
+ /* FIXME: check buf */
+
+ for (i = 0; i < kexec_file_types; i++) {
+ if (kexec_file_type[i].probe(buf, fsize) >= 0)
+ break;
+ }
+
+ if (i == kexec_file_types) {
+ printf("Cannot determine the file type "
+ "of %s\n", fname);
+ return -1;
+ }
+
+ return kexec_file_type[i].load(buf, fsize, info);
+}
+
+static int kexec_load_binary_file(struct kexec_info *info, char *fname,
+ size_t *fsize, unsigned long base)
+{
+ char *buf;
+
+ buf = read_file(fname, fsize);
+ if (!buf)
+ return -1;
+
+ add_segment(info, buf, *fsize, base, *fsize);
+
+ return 0;
+}
+
+static int print_segments(struct kexec_info *info)
+{
+ int i;
+
+ printf("print_segments\n");
+ for (i = 0; i < info->nr_segments; i++) {
+ struct kexec_segment *seg = &info->segment[i];
+
+ printf(" %d. buf=%#08p bufsz=%#lx mem=%#08p memsz=%#lx\n", i,
+ seg->buf, seg->bufsz, seg->mem, seg->memsz);
+ }
+
+ return 0;
+}
+
+static unsigned long find_unused_base(struct kexec_info *info, int *padded)
+{
+ unsigned long base = 0;
+
+ if (info->nr_segments) {
+ int i = info->nr_segments - 1;
+ struct kexec_segment *seg = &info->segment[i];
+
+ base = (unsigned long)seg->mem + seg->memsz;
+ }
+
+ if (!*padded) {
+ /*
+ * Pad it; the kernel scribbles over memory
+ * beyond its load address.
+ * see grub-core/loader/mips/linux.c
+ */
+ base += 0x100000;
+ *padded = 1;
+ }
+
+ return base;
+}
+
+#include <environment.h>
+
+int kexec_load_bootm_data(struct image_data *data)
+{
+ int result;
+ struct kexec_info info;
+ char *cmdline;
+ const char *t;
+ size_t tlen, klen;
+ size_t fsize;
+ char initrd_cmdline[40];
+ int padded = 0;
+
+ memset(&info, 0, sizeof(info));
+
+ initrd_cmdline[0] = 0;
+
+ result = kexec_load_one_file(&info, data->os_file, 0);
+ if (result < 0) {
+ printf("Cannot load %s\n", data->os_file);
+ return result;
+ }
+
+ if (data->oftree_file) {
+ unsigned long base = find_unused_base(&info, &padded);
+
+ base = ALIGN(base, 8);
+
+ result = kexec_load_binary_file(&info,
+ data->oftree_file, &fsize, base);
+ if (result < 0) {
+ printf("Cannot load %s\n", data->oftree_file);
+ return result;
+ }
+ data->oftree_address = base;
+ }
+
+ if (data->initrd_file) {
+ unsigned long base = find_unused_base(&info, &padded);
+
+ /*
+ * initrd start must be page aligned,
+ * max page size for mips is 64k.
+ */
+ base = ALIGN(base, 0x10000);
+
+ result = kexec_load_binary_file(&info,
+ data->initrd_file, &fsize, base);
+ if (result < 0) {
+ printf("Cannot load %s\n", data->initrd_file);
+ return result;
+ }
+ data->initrd_address = base;
+
+ if (bootm_verbose(data)) {
+ printf("initrd: rd_start=0x%08x rd_size=0x%08x\n",
+ data->initrd_address, fsize);
+ }
+ snprintf(initrd_cmdline, sizeof(initrd_cmdline),
+ " rd_start=0x%08x rd_size=0x%08x",
+ phys_to_virt(data->initrd_address),
+ fsize);
+ }
+
+ /* FIXME: we can snprintf to cmdline directly */
+ t = linux_bootargs_get();
+ if (t)
+ tlen = strlen(t);
+ else
+ tlen = 0;
+
+ klen = strlen("kexec ");
+ cmdline = xzalloc(tlen + sizeof(initrd_cmdline) + klen);
+ memcpy(cmdline, "kexec ", klen);
+ if (tlen)
+ memcpy(cmdline + klen, t, tlen);
+
+ memcpy(cmdline + klen + tlen, initrd_cmdline, sizeof(initrd_cmdline));
+
+ if (cmdline) {
+ int cmdlinelen = strlen(cmdline) + 1;
+ unsigned long base = find_unused_base(&info, &padded);
+
+ base = ALIGN(base, 8);
+
+ /* FIXME */
+ add_segment(&info, cmdline, cmdlinelen, base, cmdlinelen);
+ data->cmdline_address = base;
+ }
+
+ if (bootm_verbose(data))
+ print_segments(&info);
+
+ /* Verify all of the segments load to a valid location in memory */
+
+ /* Sort the segments and verify we don't have overlaps */
+ if (sort_segments(&info) < 0)
+ return -1;
+
+ return kexec_load(info.entry,
+ info.nr_segments, info.segment, info.kexec_flags);
+}
diff --git a/lib/kexec/kexec.h b/lib/kexec/kexec.h
new file mode 100644
index 000000000..527a36861
--- /dev/null
+++ b/lib/kexec/kexec.h
@@ -0,0 +1,92 @@
+#ifndef KEXEC_H
+#define KEXEC_H
+
+#include "kexec-elf.h"
+
+struct kexec_segment {
+ const void *buf;
+ size_t bufsz;
+ const void *mem;
+ size_t memsz;
+};
+
+struct kexec_info {
+ struct kexec_segment *segment;
+ int nr_segments;
+ void *entry;
+ unsigned long kexec_flags;
+};
+
+typedef int (probe_t)(const char *kernel_buf, off_t kernel_size);
+typedef int (load_t)(const char *kernel_buf, off_t kernel_size,
+ struct kexec_info *info);
+struct kexec_file_type {
+ const char *name;
+ probe_t *probe;
+ load_t *load;
+};
+
+extern struct kexec_file_type kexec_file_type[];
+extern int kexec_file_types;
+
+extern void add_segment(struct kexec_info *info,
+ const void *buf, size_t bufsz, unsigned long base, size_t memsz);
+extern void add_segment_phys_virt(struct kexec_info *info,
+ const void *buf, size_t bufsz, unsigned long base, size_t memsz,
+ int phys);
+
+extern long kexec_load(void *entry, unsigned long nr_segments,
+ struct kexec_segment *segments, unsigned long flags);
+
+#include <bootm.h>
+
+extern int kexec_load_bootm_data(struct image_data *data);
+
+/* Limits of integral types. */
+#ifndef INT8_MIN
+#define INT8_MIN (-128)
+#endif
+#ifndef INT16_MIN
+#define INT16_MIN (-32767-1)
+#endif
+#ifndef INT32_MIN
+#define INT32_MIN (-2147483647-1)
+#endif
+#ifndef INT8_MAX
+#define INT8_MAX (127)
+#endif
+#ifndef INT16_MAX
+#define INT16_MAX (32767)
+#endif
+#ifndef INT32_MAX
+#define INT32_MAX (2147483647)
+#endif
+#ifndef UINT8_MAX
+#define UINT8_MAX (255U)
+#endif
+#ifndef UINT16_MAX
+#define UINT16_MAX (65535U)
+#endif
+#ifndef UINT32_MAX
+#define UINT32_MAX (4294967295U)
+#endif
+
+/* These values match the ELF architecture values.
+ * Unless there is a good reason that should continue to be the case.
+ */
+#define KEXEC_ARCH_DEFAULT (0 << 16)
+#define KEXEC_ARCH_386 (3 << 16)
+#define KEXEC_ARCH_X86_64 (62 << 16)
+#define KEXEC_ARCH_PPC (20 << 16)
+#define KEXEC_ARCH_PPC64 (21 << 16)
+#define KEXEC_ARCH_IA_64 (50 << 16)
+#define KEXEC_ARCH_ARM (40 << 16)
+#define KEXEC_ARCH_S390 (22 << 16)
+#define KEXEC_ARCH_SH (42 << 16)
+#define KEXEC_ARCH_MIPS_LE (10 << 16)
+#define KEXEC_ARCH_MIPS (8 << 16)
+#define KEXEC_ARCH_CRIS (76 << 16)
+
+#define KEXEC_MAX_SEGMENTS 16
+
+#endif /* KEXEC_H */
--
2.14.1
_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox
next prev parent reply other threads:[~2018-04-29 13:09 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
2018-04-29 13:09 [RFC PATCH 0/7] provide ELF/KEXEC support for MIPS ath79 and malta Oleksij Rempel
2018-04-29 13:09 ` [RFC PATCH 1/7] resource: add create_resource() helper function Oleksij Rempel
2018-04-29 13:09 ` [RFC PATCH 2/7] filetype: add ELF type Oleksij Rempel
2018-04-29 13:09 ` Oleksij Rempel [this message]
2018-05-04 5:54 ` [RFC PATCH 3/7] bootm: add kexec ELF support Sascha Hauer
2018-05-04 10:08 ` Antony Pavlov
2018-05-04 9:53 ` Oleksij Rempel
2018-05-04 13:09 ` Antony Pavlov
2018-04-29 13:09 ` [RFC PATCH 4/7] MIPS: add kexec ELF loading support Oleksij Rempel
2018-04-29 13:09 ` [RFC PATCH 5/7] MIPS: ath79: add kexec support Oleksij Rempel
2018-04-29 13:09 ` [RFC PATCH 6/7] MIPS: malta: enable kexec Oleksij Rempel
2018-04-29 13:09 ` [RFC PATCH 7/7] MIPS: configs: add KEXEC=y to atheros devices Oleksij Rempel
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20180429130907.20506-4-linux@rempel-privat.de \
--to=linux@rempel-privat.de \
--cc=barebox@lists.infradead.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox