Skip to content

Instantly share code, notes, and snippets.

@C457
Last active May 28, 2017 19:46
Show Gist options
  • Save C457/c7633fbb517c6c27040f to your computer and use it in GitHub Desktop.
Save C457/c7633fbb517c6c27040f to your computer and use it in GitHub Desktop.
Exynos Kexec Hardboot Patch
From f1190e69caac8087c201951b01f50baeb5da5ec7 Mon Sep 17 00:00:00 2001
From: Vojtech Bocek <vbocek@gmail.com>
Date: Mon, 23 Sep 2013 22:30:02 +0200
Subject: [PATCH] Implement kexec-hardboot
"Allows hard booting (i.e., with a full hardware reboot) to a kernel
previously loaded in memory by kexec. This works around the problem of
soft-booted kernel hangs due to improper device shutdown and/or
reinitialization."
More info in /arch/arm/Kconfig.
Original author: Mike Kasick <mike@kasick.org>
Vojtech Bocek <vbocek@gmail.com>:
I've ported it to flo, it is based of my grouper port, which is based of
Asus TF201 patches ported by Jens Andersen <jens.andersen@gmail.com>.
I've moved atags copying from guest to the host kernel, which means there
is no need to patch the guest kernel, assuming the --mem-min in kexec call
is within the first 256MB of System RAM, otherwise it will take a long time
to load. I've also fixed /proc/atags entry, which would give the kexec-tools
userspace binary only the first 1024 bytes of atags,
see arch/arm/kernel/atags.c for more details.
Other than that, memory-reservation code for the hardboot page and
some assembler to do the watchdog reset on MSM chip are new for this device.
Signed-off-by: Vojtech Bocek <vbocek@gmail.com>
Change-Id: I4b53b59cf61ee77cdc3369b3f5c579fba70837dc
---
arch/arm/Kconfig | 26 ++++++++
arch/arm/boot/compressed/head.S | 96 ++++++++++++++++++++++++++++
arch/arm/configs/cyanogenmod_i9500_defconfig | 4 +-
arch/arm/include/asm/kexec.h | 8 +++
arch/arm/kernel/atags.c | 51 ++++++++++-----
arch/arm/kernel/machine_kexec.c | 13 ++++
arch/arm/kernel/relocate_kernel.S | 41 ++++++++++++
arch/arm/mach-exynos/include/mach/memory.h | 4 ++
arch/arm/mach-exynos/mach-universal5410.c | 15 ++++-
arch/arm/mach-exynos/sec-reboot.c | 19 ++++++
include/linux/kexec.h | 19 +++++-
kernel/kexec.c | 4 ++
12 files changed, 278 insertions(+), 22 deletions(-)
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index 20911e0..b551dca 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -2152,6 +2152,32 @@ config ATAGS_PROC
Should the atags used to boot the kernel be exported in an "atags"
file in procfs. Useful with kexec.
+config KEXEC_HARDBOOT
+ bool "Support hard booting to a kexec kernel"
+ depends on KEXEC
+ help
+ Allows hard booting (i.e., with a full hardware reboot) to a kernel
+ previously loaded in memory by kexec. This works around the problem of
+ soft-booted kernel hangs due to improper device shutdown and/or
+ reinitialization. Support is comprised of two components:
+
+ First, a "hardboot" flag is added to the kexec syscall to force a hard
+ reboot in relocate_new_kernel() (which requires machine-specific assembly
+ code). This also requires the kexec userspace tool to load the kexec'd
+ kernel in memory region left untouched by the bootloader (i.e., not
+ explicitly cleared and not overwritten by the boot kernel). Just prior
+ to reboot, the kexec kernel arguments are stashed in a machine-specific
+ memory page that must also be preserved. Note that this hardboot page
+ need not be reserved during regular kernel execution.
+
+ Second, the zImage decompresor of the boot (bootloader-loaded) kernel is
+ modified to check the hardboot page for fresh kexec arguments, and if
+ present, attempts to jump to the kexec'd kernel preserved in memory.
+
+ Note that hardboot support is only required in the boot kernel and any
+ kernel capable of performing a hardboot kexec. It is _not_ required by a
+ kexec'd kernel.
+
config CRASH_DUMP
bool "Build kdump crash kernel (EXPERIMENTAL)"
depends on EXPERIMENTAL
diff --git a/arch/arm/boot/compressed/head.S b/arch/arm/boot/compressed/head.S
index ace2dee..4ffcac2 100644
--- a/arch/arm/boot/compressed/head.S
+++ b/arch/arm/boot/compressed/head.S
@@ -10,6 +10,11 @@
*/
#include <linux/linkage.h>
+#ifdef CONFIG_KEXEC_HARDBOOT
+ #include <asm/kexec.h>
+ #include <asm/memory.h>
+#endif
+
/*
* Debugging stuff
*
@@ -135,6 +140,97 @@ start:
1: mov r7, r1 @ save architecture ID
mov r8, r2 @ save atags pointer
+#ifdef CONFIG_KEXEC_HARDBOOT
+ /* Check hardboot page for a kexec kernel. */
+ ldr r3, =KEXEC_HB_PAGE_ADDR
+ ldr r0, [r3]
+ ldr r1, =KEXEC_HB_PAGE_MAGIC
+ teq r0, r1
+ bne not_booting_other
+
+ /* Clear hardboot page magic to avoid boot loop. */
+ mov r0, #0
+ str r0, [r3]
+
+/* Copy the kernel tagged list (atags):
+ *
+ * The kernel requires atags to be located in a direct-mapped region,
+ * usually below the kernel in the first 16 kB of RAM. If they're above
+ * (the start of) the kernel, they need to be copied to a suitable
+ * location, e.g., the machine-defined params_phys.
+ *
+ * The assumption is that the tags will only be "out of place" if the
+ * decompressor code is also, so copying is implemented only in the "won't
+ * overwrite" case (which should be fixed). Still need to make sure that
+ * the copied tags don't overwrite either the kernel or decompressor code
+ * (or rather, the remainder of it since everything up to here has already
+ * been executed).
+ *
+ * Vojtech Bocek <vbocek@gmail.com>: I've moved atags copying from guest
+ * kernel to the host and rewrote it from C to assembler in order to remove
+ * the need for guest kernel to be patched. I don't know assembler very well,
+ * so it doesn't look very good and I have no idea if I didn't accidentally
+ * break something, causing problems down the road. It's worked every time
+ * and I didn't notice any problems so far though.
+ *
+ * r4: zreladdr (kernel start)
+ * r8: kexec_boot_atags
+ * r2: boot_atags */
+ ldr r8, [r3, #12] @ kexec_boot_atags (r2: boot_atags)
+ ldr r4, =zreladdr @ zreladdr
+
+ /* No need to copy atags if they're already below kernel */
+ cmp r8, r4
+ blo no_atags_cpy
+
+ /* r0: min(zreladdr, pc) */
+ mov r0, pc
+ cmp r4, r0
+ movlo r0, r4
+
+ /* Compute max space for atags, if max <= 0 don't copy. */
+ subs r5, r0, r2 @ max = min(zreladdr, pc) - dest
+ bls no_atags_cpy
+
+ /* Copy atags to params_phys. */
+ /* r8 src, r2 dest, r5 max */
+
+ ldr r0, [r8] @ first tag size
+ cmp r0, #0
+ moveq r4, #8
+ beq catags_empty
+ mov r4, r8
+
+catags_foreach:
+ lsl r0, r0, #2 @ Multiply by 4
+ ldr r0, [r4, r0]! @ Load next tag size to r0 and address to r4
+ cmp r0, #0
+ bne catags_foreach
+
+ rsb r4, r8, r4 @ r4 -= r8 (get only size)
+ add r4, r4, #8 @ add size of the last tag
+catags_empty:
+ cmp r5, r4 @ if(max <= size)
+ bcc no_atags_cpy
+
+ mov r5, #0 @ iterator
+catags_cpy:
+ ldr r0, [r8, r5]
+ str r0, [r2, r5]
+ add r5, r5, #4
+ cmp r5, r4
+ blo catags_cpy
+
+no_atags_cpy:
+ /* Load boot arguments and jump to kexec kernel. */
+ ldr r1, [r3, #8] @ kexec_mach_type
+ ldr pc, [r3, #4] @ kexec_start_address
+
+ .ltorg
+
+not_booting_other:
+#endif
+
#ifndef __ARM_ARCH_2__
/*
* Booting from Angel - need to enter SVC mode and disable
diff --git a/arch/arm/configs/cyanogenmod_i9500_defconfig b/arch/arm/configs/cyanogenmod_i9500_defconfig
index 7807840..61fb87c 100644
--- a/arch/arm/configs/cyanogenmod_i9500_defconfig
+++ b/arch/arm/configs/cyanogenmod_i9500_defconfig
@@ -674,7 +674,9 @@ CONFIG_CMDLINE="console=ttySAC2,115200n8 vmalloc=512M androidboot.console=ttySAC
CONFIG_CMDLINE_EXTEND=y
# CONFIG_CMDLINE_FORCE is not set
# CONFIG_XIP_KERNEL is not set
-# CONFIG_KEXEC is not set
+CONFIG_KEXEC=y
+CONFIG_KEXEC_HARDBOOT=y
+CONFIG_ATAGS_PROC=y
# CONFIG_CRASH_DUMP is not set
# CONFIG_AUTO_ZRELADDR is not set
diff --git a/arch/arm/include/asm/kexec.h b/arch/arm/include/asm/kexec.h
index c2b9b4b..564c55b 100644
--- a/arch/arm/include/asm/kexec.h
+++ b/arch/arm/include/asm/kexec.h
@@ -17,6 +17,10 @@
#define KEXEC_ARM_ATAGS_OFFSET 0x1000
#define KEXEC_ARM_ZIMAGE_OFFSET 0x8000
+#ifdef CONFIG_KEXEC_HARDBOOT
+ #define KEXEC_HB_PAGE_MAGIC 0x4a5db007
+#endif
+
#ifndef __ASSEMBLY__
/**
@@ -53,6 +57,10 @@ static inline void crash_setup_regs(struct pt_regs *newregs,
/* Function pointer to optional machine-specific reinitialization */
extern void (*kexec_reinit)(void);
+#ifdef CONFIG_KEXEC_HARDBOOT
+extern void (*kexec_hardboot_hook)(void);
+#endif
+
#endif /* __ASSEMBLY__ */
#endif /* CONFIG_KEXEC */
diff --git a/arch/arm/kernel/atags.c b/arch/arm/kernel/atags.c
index 42a1a14..0cfd7e4 100644
--- a/arch/arm/kernel/atags.c
+++ b/arch/arm/kernel/atags.c
@@ -4,29 +4,45 @@
#include <asm/types.h>
#include <asm/page.h>
+/*
+ * [PATCH] Backport arch/arm/kernel/atags.c from 3.10
+ *
+ * There is a bug in older kernels, causing kexec-tools binary to
+ * only read first 1024 bytes from /proc/atags. I guess the bug is
+ * somewhere in /fs/proc/, since I don't think the callback in atags.c
+ * does something wrong. It might affect all procfs files using that
+ * old read callback instead of fops. Doesn't matter though, since it
+ * was accidentally fixed when 3.10 removed it.
+ *
+ * This might have no particular effect on real devices, because the
+ * atags _might_ be organized "just right", but it might be very hard
+ * to track down on a device where it causes problems.
+ *
+ */
+
struct buffer {
size_t size;
char data[];
};
-static int
-read_buffer(char* page, char** start, off_t off, int count,
- int* eof, void* data)
-{
- struct buffer *buffer = (struct buffer *)data;
-
- if (off >= buffer->size) {
- *eof = 1;
- return 0;
- }
-
- count = min((int) (buffer->size - off), count);
+static struct buffer* atags_buffer = NULL;
- memcpy(page, &buffer->data[off], count);
-
- return count;
+static ssize_t atags_read(struct file *file, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ // These are introduced in kernel 3.10. I don't want to backport
+ // the whole chunk, and other things (ram_console) use static
+ // variable to keep data too, so I guess it's okay.
+ //struct buffer *b = PDE_DATA(file_inode(file));
+ struct buffer *b = atags_buffer;
+ return simple_read_from_buffer(buf, count, ppos, b->data, b->size);
}
+static const struct file_operations atags_fops = {
+ .read = atags_read,
+ .llseek = default_llseek,
+};
+
#define BOOT_PARAMS_SIZE 1536
static char __initdata atags_copy[BOOT_PARAMS_SIZE];
@@ -66,12 +82,13 @@ static int __init init_atags_procfs(void)
b->size = size;
memcpy(b->data, atags_copy, size);
- tags_entry = create_proc_read_entry("atags", 0400,
- NULL, read_buffer, b);
+ tags_entry = proc_create_data("atags", 0400, NULL, &atags_fops, b);
if (!tags_entry)
goto nomem;
+ atags_buffer = b;
+
return 0;
nomem:
diff --git a/arch/arm/kernel/machine_kexec.c b/arch/arm/kernel/machine_kexec.c
index dfcdb9f..98bdae3 100644
--- a/arch/arm/kernel/machine_kexec.c
+++ b/arch/arm/kernel/machine_kexec.c
@@ -22,6 +22,10 @@ extern unsigned long kexec_start_address;
extern unsigned long kexec_indirection_page;
extern unsigned long kexec_mach_type;
extern unsigned long kexec_boot_atags;
+#ifdef CONFIG_KEXEC_HARDBOOT
+extern unsigned long kexec_hardboot;
+void (*kexec_hardboot_hook)(void);
+#endif
static atomic_t waiting_for_crash_ipi;
@@ -123,6 +127,9 @@ void machine_kexec(struct kimage *image)
kexec_indirection_page = page_list;
kexec_mach_type = machine_arch_type;
kexec_boot_atags = image->start - KEXEC_ARM_ZIMAGE_OFFSET + KEXEC_ARM_ATAGS_OFFSET;
+#ifdef CONFIG_KEXEC_HARDBOOT
+ kexec_hardboot = image->hardboot;
+#endif
/* copy our kernel relocation code to the control code page */
memcpy(reboot_code_buffer,
@@ -136,5 +143,11 @@ void machine_kexec(struct kimage *image)
if (kexec_reinit)
kexec_reinit();
+#ifdef CONFIG_KEXEC_HARDBOOT
+ /* Run any final machine-specific shutdown code. */
+ if (image->hardboot && kexec_hardboot_hook)
+ kexec_hardboot_hook();
+#endif
+
soft_restart(reboot_code_buffer_phys);
}
diff --git a/arch/arm/kernel/relocate_kernel.S b/arch/arm/kernel/relocate_kernel.S
index d0cdedf..e28cc6d 100644
--- a/arch/arm/kernel/relocate_kernel.S
+++ b/arch/arm/kernel/relocate_kernel.S
@@ -4,6 +4,12 @@
#include <asm/kexec.h>
+#ifdef CONFIG_KEXEC_HARDBOOT
+#include <asm/memory.h>
+#include <mach/map.h>
+#include <mach/regs-pmu.h>
+#endif
+
.globl relocate_new_kernel
relocate_new_kernel:
@@ -52,6 +58,12 @@ relocate_new_kernel:
b 0b
2:
+#ifdef CONFIG_KEXEC_HARDBOOT
+ ldr r0, kexec_hardboot
+ teq r0, #0
+ bne hardboot
+#endif
+
/* Jump to relocated kernel */
mov lr,r1
mov r0,#0
@@ -60,6 +72,29 @@ relocate_new_kernel:
ARM( mov pc, lr )
THUMB( bx lr )
+#ifdef CONFIG_KEXEC_HARDBOOT
+hardboot:
+ /* Stash boot arguments in hardboot page:
+ * 0: KEXEC_HB_PAGE_MAGIC
+ * 4: kexec_start_address
+ * 8: kexec_mach_type
+ * 12: kexec_boot_atags */
+ ldr r0, =KEXEC_HB_PAGE_ADDR
+ str r1, [r0, #4]
+ ldr r1, kexec_mach_type
+ str r1, [r0, #8]
+ ldr r1, kexec_boot_atags
+ str r1, [r0, #12]
+ ldr r1, =KEXEC_HB_PAGE_MAGIC
+ str r1, [r0]
+
+ ldr r0, =EXYNOS5_PA_PMU
+ mov r1, #1
+ str r1, [r0, #EXYNOS_SWRESET-EXYNOS_PMUREG(0)]
+loop: b loop
+
+ .ltorg
+#endif
.align
.globl kexec_start_address
@@ -79,6 +114,12 @@ kexec_mach_type:
kexec_boot_atags:
.long 0x0
+#ifdef CONFIG_KEXEC_HARDBOOT
+ .globl kexec_hardboot
+kexec_hardboot:
+ .long 0x0
+#endif
+
relocate_new_kernel_end:
.globl relocate_new_kernel_size
diff --git a/arch/arm/mach-exynos/include/mach/memory.h b/arch/arm/mach-exynos/include/mach/memory.h
index 7bd7765..c688a3e 100644
--- a/arch/arm/mach-exynos/include/mach/memory.h
+++ b/arch/arm/mach-exynos/include/mach/memory.h
@@ -21,4 +21,8 @@
#define ARCH_HAS_SG_CHAIN
+#if defined(CONFIG_KEXEC_HARDBOOT)
+#define KEXEC_HB_PAGE_ADDR UL(0xbfd00000)
+#endif
+
#endif /* __ASM_ARCH_MEMORY_H */
diff --git a/arch/arm/mach-exynos/mach-universal5410.c b/arch/arm/mach-exynos/mach-universal5410.c
index 787204b..547294e 100644
--- a/arch/arm/mach-exynos/mach-universal5410.c
+++ b/arch/arm/mach-exynos/mach-universal5410.c
@@ -48,6 +48,10 @@
#include "mach/sec_debug.h"
+#ifdef CONFIG_KEXEC_HARDBOOT
+#include <asm/kexec.h>
+#endif
+
extern phys_addr_t bootloaderfb_start;
extern phys_addr_t bootloaderfb_size;
@@ -193,10 +197,19 @@ static struct notifier_block exynos5_reboot_notifier = {
.notifier_call = exynos5_notifier_call,
};
+#define RAMCONSOLE_PHYS_ADDR PLAT_PHYS_OFFSET + SZ_512M
static struct persistent_ram_descriptor universal5410_prd[] __initdata = {
{
.name = "ram_console",
+#ifdef CONFIG_KEXEC_HARDBOOT
+ .size = KEXEC_HB_PAGE_ADDR - RAMCONSOLE_PHYS_ADDR,
+ },
+ {
+ .name = "kexec_hb_page",
+ .size = SZ_2M - (KEXEC_HB_PAGE_ADDR - RAMCONSOLE_PHYS_ADDR),
+#else
.size = SZ_2M,
+#endif
},
#ifdef CONFIG_PERSISTENT_TRACER
{
@@ -209,7 +222,7 @@ static struct persistent_ram_descriptor universal5410_prd[] __initdata = {
static struct persistent_ram universal5410_pr __initdata = {
.descs = universal5410_prd,
.num_descs = ARRAY_SIZE(universal5410_prd),
- .start = PLAT_PHYS_OFFSET + SZ_512M,
+ .start = RAMCONSOLE_PHYS_ADDR,
#ifdef CONFIG_PERSISTENT_TRACER
.size = 3 * SZ_1M,
#else
diff --git a/arch/arm/mach-exynos/sec-reboot.c b/arch/arm/mach-exynos/sec-reboot.c
index a4cd30f..750ebde 100644
--- a/arch/arm/mach-exynos/sec-reboot.c
+++ b/arch/arm/mach-exynos/sec-reboot.c
@@ -7,6 +7,10 @@
#include <linux/gpio.h>
#include "common.h"
+#ifdef CONFIG_KEXEC_HARDBOOT
+#include <asm/kexec.h>
+#endif
+
static void sec_power_off(void)
{
int poweroff_try = 0;
@@ -133,10 +137,25 @@ static void sec_reboot(char str, const char *cmd)
;
}
+#ifdef CONFIG_KEXEC_HARDBOOT
+static void sec_kexec_hardboot(void)
+{
+ /* Don't enter lpm mode */
+ writel(0x12345678, EXYNOS_INFORM2);
+
+ /* Reboot with boot kernel */
+ writel(REBOOT_MODE_PREFIX|REBOOT_MODE_NONE, EXYNOS_INFORM3);
+}
+#endif
+
static int __init sec_reboot_init(void)
{
pm_power_off = sec_power_off;
arm_pm_restart = sec_reboot;
+#ifdef CONFIG_KEXEC_HARDBOOT
+ kexec_hardboot_hook = sec_kexec_hardboot;
+#endif
+
return 0;
}
diff --git a/include/linux/kexec.h b/include/linux/kexec.h
index 0d7d6a1..6af4224 100644
--- a/include/linux/kexec.h
+++ b/include/linux/kexec.h
@@ -111,6 +111,10 @@ struct kimage {
#define KEXEC_TYPE_CRASH 1
unsigned int preserve_context : 1;
+#ifdef CONFIG_KEXEC_HARDBOOT
+ unsigned int hardboot : 1;
+#endif
+
#ifdef ARCH_HAS_KIMAGE_ARCH
struct kimage_arch arch;
#endif
@@ -177,6 +181,11 @@ extern struct kimage *kexec_crash_image;
#define KEXEC_ON_CRASH 0x00000001
#define KEXEC_PRESERVE_CONTEXT 0x00000002
+
+#ifdef CONFIG_KEXEC_HARDBOOT
+#define KEXEC_HARDBOOT 0x00000004
+#endif
+
#define KEXEC_ARCH_MASK 0xffff0000
/* These values match the ELF architecture values.
@@ -195,10 +204,14 @@ extern struct kimage *kexec_crash_image;
#define KEXEC_ARCH_MIPS ( 8 << 16)
/* List of defined/legal kexec flags */
-#ifndef CONFIG_KEXEC_JUMP
-#define KEXEC_FLAGS KEXEC_ON_CRASH
-#else
+#if defined(CONFIG_KEXEC_JUMP) && defined(CONFIG_KEXEC_HARDBOOT)
+#define KEXEC_FLAGS (KEXEC_ON_CRASH | KEXEC_PRESERVE_CONTEXT | KEXEC_HARDBOOT)
+#elif defined(CONFIG_KEXEC_JUMP)
#define KEXEC_FLAGS (KEXEC_ON_CRASH | KEXEC_PRESERVE_CONTEXT)
+#elif defined(CONFIG_KEXEC_HARDBOOT)
+#define KEXEC_FLAGS (KEXEC_ON_CRASH | KEXEC_HARDBOOT)
+#else
+#define KEXEC_FLAGS (KEXEC_ON_CRASH)
#endif
#define VMCOREINFO_BYTES (4096)
diff --git a/kernel/kexec.c b/kernel/kexec.c
index 4e2e472..aef7893 100644
--- a/kernel/kexec.c
+++ b/kernel/kexec.c
@@ -1004,6 +1004,10 @@ SYSCALL_DEFINE4(kexec_load, unsigned long, entry, unsigned long, nr_segments,
if (flags & KEXEC_PRESERVE_CONTEXT)
image->preserve_context = 1;
+#ifdef CONFIG_KEXEC_HARDBOOT
+ if (flags & KEXEC_HARDBOOT)
+ image->hardboot = 1;
+#endif
result = machine_kexec_prepare(image);
if (result)
goto out;
--
1.9.1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment