From 89fdd93c6994b4d6e5cd7acaa207af7ea366bb27 Mon Sep 17 00:00:00 2001
From: Tad <tad@spotco.us>
Date: Sat, 17 Oct 2015 20:49:21 -0400
Subject: [PATCH] Implement KEXEC

---
 arch/arm/Kconfig                             | 26 ++++++++++
 arch/arm/boot/compressed/head.S              | 64 ++++++++++++++++++++++++
 arch/arm/configs/cyanogenmod_bacon_defconfig |  6 ++-
 arch/arm/include/asm/kexec.h                 |  8 +++
 arch/arm/kernel/machine_kexec.c              | 58 +++++++++++++++++++--
 arch/arm/kernel/relocate_kernel.S            | 75 ++++++++++++++++++++++++++++
 arch/arm/mach-msm/include/mach/memory.h      | 16 ++++++
 arch/arm/mach-msm/oppo/board-8974-oppo.c     | 27 ++++++++++
 arch/arm/mach-msm/restart.c                  | 28 +++++++++++
 include/linux/kexec.h                        | 19 +++++--
 kernel/kexec.c                               |  4 ++
 11 files changed, 322 insertions(+), 9 deletions(-)

diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index 44e86c9..9edd8cf 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -2324,6 +2324,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 d3892ef..be05aa1 100644
--- a/arch/arm/boot/compressed/head.S
+++ b/arch/arm/boot/compressed/head.S
@@ -11,6 +11,12 @@
 #include <linux/linkage.h>
 
 	.arch	armv7-a
+	
+#ifdef CONFIG_KEXEC_HARDBOOT
+  #include <asm/kexec.h>
+  #include <asm/memory.h>
+#endif
+
 /*
  * Debugging stuff
  *
@@ -136,6 +142,64 @@ 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 dtb from location up high in memory to default location.
+		 * Kernel freezes if this is not done.
+		 */
+		ldr	r1, [r3, #12]			@ kexec_boot_atags
+		ldr	r2, [r3, #16]			@ kexec_boot_atags_len
+		mov	r5, #0					@ iterator
+catags_cpy:
+		ldr	r0, [r1, r5]			@ from kexec_boot_atags
+		str	r0, [r8, r5]			@ to atags_pointer
+		add	r5, r5, #4
+		cmp	r5, r2
+		blo	catags_cpy
+
+#ifdef KEXEC_HB_KERNEL_LOC
+		/*
+		 * Copy kernel from location up high in memory to location in first 128MB.
+		 * Bootloader on hammerhead erases first 128MB of ram on reboot, so it can't
+		 * be in there before reboot, but decompressing in location above 128MB takes
+		 * a long time. This memcpy is much quicker, for some reason.
+		 */
+		ldr	r2, [r3, #4]				@ kexec_start_address
+		ldr	r4, [r3, #20]				@ kexec_kernel_len
+		ldr	r6, =KEXEC_HB_KERNEL_LOC    @ target
+		mov	r5, #0						@ iterator
+kernel_cpy:
+		ldr	r0, [r2, r5]				@ from kexec_start_address
+		str	r0, [r6, r5]				@ to KEXEC_HB_KERNEL_LOC
+		add	r5, r5, #4
+		cmp	r5, r4
+		blo	kernel_cpy
+#else
+		ldr	r6, [r3, #4]				@ kexec_start_address
+#endif
+
+		/* set registers and boot kexecd' kernel */
+		mov	r0, #0
+		ldr	r1, [r3, #8]				@ kexec_mach_type
+		mov	r2, r8						@ atags pointer
+		mov	pc, r6
+
+		.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_bacon_defconfig b/arch/arm/configs/cyanogenmod_bacon_defconfig
index 1738c22..73f8fe7 100644
--- a/arch/arm/configs/cyanogenmod_bacon_defconfig
+++ b/arch/arm/configs/cyanogenmod_bacon_defconfig
@@ -695,7 +695,9 @@ CONFIG_ZBOOT_ROM_BSS=0
 # CONFIG_ARM_APPENDED_DTB is not set
 CONFIG_CMDLINE=""
 # CONFIG_XIP_KERNEL is not set
-# CONFIG_KEXEC is not set
+CONFIG_KEXEC=y
+CONFIG_KEXEC_HARDBOOT=y
+CONFIG_ATAGS_PROC=n
 # CONFIG_CRASH_DUMP is not set
 # CONFIG_AUTO_ZRELADDR is not set
 
@@ -1300,7 +1302,7 @@ CONFIG_OF=y
 #
 # Device Tree and Open Firmware support
 #
-# CONFIG_PROC_DEVICETREE is not set
+CONFIG_PROC_DEVICETREE=y
 # CONFIG_OF_SELFTEST is not set
 CONFIG_OF_FLATTREE=y
 CONFIG_OF_EARLY_FLATTREE=y
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/machine_kexec.c b/arch/arm/kernel/machine_kexec.c
index 357b651..29cdd2f 100644
--- a/arch/arm/kernel/machine_kexec.c
+++ b/arch/arm/kernel/machine_kexec.c
@@ -14,6 +14,9 @@
 #include <asm/cacheflush.h>
 #include <asm/mach-types.h>
 #include <asm/system_misc.h>
+#include <linux/memblock.h>
+#include <linux/of_fdt.h>
+#include <asm/mmu_writeable.h>
 
 extern const unsigned char relocate_new_kernel[];
 extern const unsigned int relocate_new_kernel_size;
@@ -22,6 +25,12 @@ 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;
+extern unsigned long kexec_boot_atags_len;
+extern unsigned long kexec_kernel_len;
+void (*kexec_hardboot_hook)(void);
+#endif
 
 static atomic_t waiting_for_crash_ipi;
 
@@ -32,6 +41,37 @@ static atomic_t waiting_for_crash_ipi;
 
 int machine_kexec_prepare(struct kimage *image)
 {
+	struct kexec_segment *current_segment;
+	__be32 header;
+	int i, err;
+
+	/* No segment at default ATAGs address. try to locate
+	 * a dtb using magic */
+	for (i = 0; i < image->nr_segments; i++) {
+		current_segment = &image->segment[i];
+
+		err = memblock_is_region_memory(current_segment->mem,
+						current_segment->memsz);
+		if (!err)
+			return - EINVAL;
+
+#ifdef CONFIG_KEXEC_HARDBOOT
+		if(current_segment->mem == image->start)
+			mem_text_write_kernel_word(&kexec_kernel_len, current_segment->memsz);
+#endif
+
+		err = get_user(header, (__be32*)current_segment->buf);
+		if (err)
+			return err;
+
+		if (be32_to_cpu(header) == OF_DT_HEADER)
+		{
+			mem_text_write_kernel_word(&kexec_boot_atags, current_segment->mem);
+#ifdef CONFIG_KEXEC_HARDBOOT
+			mem_text_write_kernel_word(&kexec_boot_atags_len, current_segment->memsz);
+#endif
+		}
+	}
 	return 0;
 }
 
@@ -123,10 +163,14 @@ void machine_kexec(struct kimage *image)
 	reboot_code_buffer = page_address(image->control_code_page);
 
 	/* Prepare parameters for reboot_code_buffer*/
-	kexec_start_address = image->start;
-	kexec_indirection_page = page_list;
-	kexec_mach_type = machine_arch_type;
-	kexec_boot_atags = image->start - KEXEC_ARM_ZIMAGE_OFFSET + KEXEC_ARM_ATAGS_OFFSET;
+	mem_text_write_kernel_word(&kexec_start_address, image->start);
+	mem_text_write_kernel_word(&kexec_indirection_page, page_list);
+	mem_text_write_kernel_word(&kexec_mach_type, machine_arch_type);
+	if (!kexec_boot_atags)
+		mem_text_write_kernel_word(&kexec_boot_atags, image->start - KEXEC_ARM_ZIMAGE_OFFSET + KEXEC_ARM_ATAGS_OFFSET);
+#ifdef CONFIG_KEXEC_HARDBOOT
+	mem_text_write_kernel_word(&kexec_hardboot, image->hardboot);
+#endif
 
 	/* copy our kernel relocation code to the control code page */
 	memcpy(reboot_code_buffer,
@@ -140,6 +184,12 @@ 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..0e45ffc 100644
--- a/arch/arm/kernel/relocate_kernel.S
+++ b/arch/arm/kernel/relocate_kernel.S
@@ -4,6 +4,15 @@
 
 #include <asm/kexec.h>
 
+#ifdef CONFIG_KEXEC_HARDBOOT
+#include <asm/memory.h>
+#if defined(CONFIG_ARCH_TEGRA_2x_SOC) || defined(CONFIG_ARCH_TEGRA_3x_SOC)
+  #include <mach/iomap.h>
+#elif defined(CONFIG_ARCH_APQ8064) || defined(CONFIG_ARCH_MSM8974)
+  #include <mach/msm_iomap.h>
+#endif
+#endif
+
 	.globl relocate_new_kernel
 relocate_new_kernel:
 
@@ -52,6 +61,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 +75,52 @@ 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
+	 * 16: kexec_boot_atags_len
+	 * 20: kexec_kernel_len */
+	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_boot_atags_len
+	str	r1, [r0, #16]
+	ldr	r1, kexec_kernel_len
+	str	r1, [r0, #20]
+	ldr	r1, =KEXEC_HB_PAGE_MAGIC
+	str	r1, [r0]
+
+#if defined(CONFIG_ARCH_TEGRA_2x_SOC) || defined(CONFIG_ARCH_TEGRA_3x_SOC)
+	ldr     r0, =TEGRA_PMC_BASE
+	ldr	r1, [r0]
+	orr	r1, r1, #0x10
+	str	r1, [r0]
+loop:	b	loop
+#elif defined(CONFIG_ARCH_APQ8064)
+	/* Restart using the PMIC chip, see mach-msm/restart.c */
+	ldr	r0, =APQ8064_TLMM_PHYS
+	mov	r1, #0
+	str	r1, [r0, #0x820]  @ PSHOLD_CTL_SU
+loop:	b	loop
+#elif defined(CONFIG_ARCH_MSM8974)
+	/* Restart using the PMIC chip, see mach-msm/restart.c */
+	ldr	r0, =MSM8974_MPM2_PSHOLD_PHYS
+	mov	r1, #0
+	str	r1, [r0, #0]
+loop:	b	loop
+#else
+#error "No reboot method defined for hardboot."
+#endif
+
+	.ltorg
+#endif
 	.align
 
 	.globl kexec_start_address
@@ -79,6 +140,20 @@ kexec_mach_type:
 kexec_boot_atags:
 	.long	0x0
 
+#ifdef CONFIG_KEXEC_HARDBOOT
+	.globl kexec_boot_atags_len
+kexec_boot_atags_len:
+	.long	0x0
+
+	.globl kexec_kernel_len
+kexec_kernel_len:
+	.long	0x0
+
+	.globl kexec_hardboot
+kexec_hardboot:
+	.long	0x0
+#endif
+
 relocate_new_kernel_end:
 
 	.globl relocate_new_kernel_size
diff --git a/arch/arm/mach-msm/include/mach/memory.h b/arch/arm/mach-msm/include/mach/memory.h
index 9225230..1c87b96 100644
--- a/arch/arm/mach-msm/include/mach/memory.h
+++ b/arch/arm/mach-msm/include/mach/memory.h
@@ -20,6 +20,22 @@
 /* physical offset of RAM */
 #define PLAT_PHYS_OFFSET UL(CONFIG_PHYS_OFFSET)
 
+#if defined(CONFIG_KEXEC_HARDBOOT)
+#if defined(CONFIG_MACH_APQ8064_FLO)
+#define KEXEC_HB_PAGE_ADDR		UL(0x88C00000)
+#elif defined(CONFIG_MACH_APQ8064_MAKO)
+#define KEXEC_HB_PAGE_ADDR		UL(0x88600000)
+#elif defined(CONFIG_MACH_MSM8974_HAMMERHEAD)
+#define KEXEC_HB_PAGE_ADDR		UL(0x10100000)
+#define KEXEC_HB_KERNEL_LOC		UL(0x3208000)
+#elif defined(CONFIG_MACH_OPPO_MSM8974)
+#define KEXEC_HB_PAGE_ADDR		UL(0x2F600000)
+#define KEXEC_HB_KERNEL_LOC		UL(0x3208000)
+#else
+#error "Adress for kexec hardboot page not defined"
+#endif
+#endif
+
 #ifndef __ASSEMBLY__
 void clean_and_invalidate_caches(unsigned long, unsigned long, unsigned long);
 void clean_caches(unsigned long, unsigned long, unsigned long);
diff --git a/arch/arm/mach-msm/oppo/board-8974-oppo.c b/arch/arm/mach-msm/oppo/board-8974-oppo.c
index eb24545..10bbbda 100644
--- a/arch/arm/mach-msm/oppo/board-8974-oppo.c
+++ b/arch/arm/mach-msm/oppo/board-8974-oppo.c
@@ -54,6 +54,13 @@
 
 #include <linux/pcb_version.h>
 
+#ifdef CONFIG_KEXEC_HARDBOOT
+#include <asm/setup.h>
+#include <asm/memory.h>
+#include <linux/memblock.h>
+#define OPPO_PERSISTENT_RAM_SIZE	(SZ_1M)
+#endif
+
 static struct platform_device *ram_console_dev;
 
 static struct persistent_ram_descriptor msm_prd[] __initdata = {
@@ -72,6 +79,26 @@ static struct persistent_ram msm_pr __initdata = {
 
 void __init msm_8974_reserve(void)
 {
+#ifdef CONFIG_KEXEC_HARDBOOT
+	// Reserve space for hardboot page - just after ram_console,
+	// at the start of second memory bank
+	int ret;
+	phys_addr_t start;
+	struct membank* bank;
+	
+	if (meminfo.nr_banks < 2) {
+		pr_err("%s: not enough membank\n", __func__);
+		return;
+	}
+	
+	bank = &meminfo.bank[1];
+	start = bank->start + SZ_1M + OPPO_PERSISTENT_RAM_SIZE;
+	ret = memblock_remove(start, SZ_1M);
+	if(!ret)
+		pr_info("Hardboot page reserved at 0x%X\n", start);
+	else
+		pr_err("Failed to reserve space for hardboot page at 0x%X!\n", start);
+#endif
 	persistent_ram_early_init(&msm_pr);
 	of_scan_flat_dt(dt_scan_for_memory_reserve, NULL);
 }
diff --git a/arch/arm/mach-msm/restart.c b/arch/arm/mach-msm/restart.c
index a04ab8d..fe89976 100644
--- a/arch/arm/mach-msm/restart.c
+++ b/arch/arm/mach-msm/restart.c
@@ -38,6 +38,10 @@
 #include "timer.h"
 #include "wdog_debug.h"
 
+#ifdef CONFIG_KEXEC_HARDBOOT
+#include <asm/kexec.h>
+#endif
+
 #define WDT0_RST	0x38
 #define WDT0_EN		0x40
 #define WDT0_BARK_TIME	0x4C
@@ -373,6 +377,26 @@ static int __init msm_pmic_restart_init(void)
 
 late_initcall(msm_pmic_restart_init);
 
+#ifdef CONFIG_KEXEC_HARDBOOT
+static void msm_kexec_hardboot_hook(void)
+{
+	set_dload_mode(0);
+
+	// Set PMIC to restart-on-poweroff
+	pm8xxx_reset_pwr_off(1);
+
+	// These are executed on normal reboot, but with kexec-hardboot,
+	// they reboot/panic the system immediately.
+#if 0
+	qpnp_pon_system_pwr_off(PON_POWER_OFF_WARM_RESET);
+
+	/* Needed to bypass debug image on some chips */
+	msm_disable_wdog_debug();
+	halt_spmi_pmic_arbiter();
+#endif
+}
+#endif
+
 static int __init msm_restart_init(void)
 {
 #ifdef CONFIG_MSM_DLOAD_MODE
@@ -392,6 +416,10 @@ static int __init msm_restart_init(void)
 	if (scm_is_call_available(SCM_SVC_PWR, SCM_IO_DISABLE_PMIC_ARBITER) > 0)
 		scm_pmic_arbiter_disable_supported = true;
 
+#ifdef CONFIG_KEXEC_HARDBOOT
+	kexec_hardboot_hook = msm_kexec_hardboot_hook;
+#endif
+
 	return 0;
 }
 early_initcall(msm_restart_init);
diff --git a/include/linux/kexec.h b/include/linux/kexec.h
index af84a25..a4509ad 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
@@ -178,6 +182,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.
@@ -196,10 +205,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;
