Skip to content

Instantly share code, notes, and snippets.

@alvinhochun
Created December 13, 2013 14:53
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alvinhochun/7945428 to your computer and use it in GitHub Desktop.
Save alvinhochun/7945428 to your computer and use it in GitHub Desktop.
Patch to implement kexec hardboot on Xperia M/M Dual kernel
From 2e818d81f21f6a627bc629e477052045426b0663 Mon Sep 17 00:00:00 2001
From: Alvin Wong <alvinhochun@gmail.com>
Date: Fri, 13 Dec 2013 22:19:52 +0800
Subject: [PATCH] Implement kexec-hardboot
This allows performing kexec with a full hardware reboot so that the "guest"
kernel does not have to worry about reinitializing hardware states.
I've ported the patch to this Xperia M stock kernel. The hardboot page is
located at 0x88000000, which is exactly 1MiB below the ram_console.
I've tested kexec hardboot with the following parameters:
--mem-min=0x85000000 --mem-max=0x87ffffff
Keep in mind that normally, the bootloader will append parameters (e.g. IMEI)
to the cmdline, but doing kexec will require manually appending them.
Here is a sample script that uses `sed` to do the work:
- https://gist.github.com/alvinhochun/7945270
Please refer to the following links for more information:
- https://gist.github.com/Tasssadar/6766757
- http://forum.xda-developers.com/showthread.php?p=46223952
Original author: Mike Kasick <mike@kasick.org>
Vojtech Bocek <vbocek@gmail.com>:
I've ported it to mako and flo, it is based of my grouper port, which is
based of Asus TF201 patche 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.
ayysir <dresadd09691@gmail.com>:
kexec: use mem_text_write_kernel_word to set reboot_code_buffer args
in order to avoid protection faults (writes to read-only
kernel memory) when CONFIG_STRICT_MEMORY_RWX is enabled.
---
arch/arm/Kconfig | 26 +++++++++
arch/arm/boot/compressed/head.S | 96 +++++++++++++++++++++++++++++++
arch/arm/include/asm/kexec.h | 8 +++
arch/arm/kernel/atags.c | 51 ++++++++++------
arch/arm/kernel/machine_kexec.c | 22 +++++--
arch/arm/kernel/relocate_kernel.S | 47 +++++++++++++++
arch/arm/mach-msm/board-8930.c | 11 ++++
arch/arm/mach-msm/include/mach/memory.h | 8 +++
arch/arm/mach-msm/restart.c | 16 ++++++
include/linux/kexec.h | 19 +++++-
kernel/crash_notes.c | 2 +-
kernel/kexec.c | 4 ++
12 files changed, 285 insertions(+), 25 deletions(-)
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index 8b31d48..731bc97 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -2117,6 +2117,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 64a6d6f..c7f1e64 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/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..05a6f2a 100644
--- a/arch/arm/kernel/machine_kexec.c
+++ b/arch/arm/kernel/machine_kexec.c
@@ -14,6 +14,7 @@
#include <asm/cacheflush.h>
#include <asm/mach-types.h>
#include <asm/system_misc.h>
+#include <asm/mmu_writeable.h>
extern const unsigned char relocate_new_kernel[];
extern const unsigned int relocate_new_kernel_size;
@@ -22,6 +23,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;
@@ -119,10 +124,13 @@ 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);
+ 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,
@@ -136,5 +144,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..f4469a1 100644
--- a/arch/arm/kernel/relocate_kernel.S
+++ b/arch/arm/kernel/relocate_kernel.S
@@ -4,6 +4,13 @@
#include <asm/kexec.h>
+#ifdef CONFIG_KEXEC_HARDBOOT
+#include <asm/memory.h>
+#if defined(CONFIG_ARCH_MSM8930)
+ #include <mach/msm_iomap.h>
+#endif
+#endif
+
.globl relocate_new_kernel
relocate_new_kernel:
@@ -52,6 +59,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 +73,34 @@ 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]
+
+#if defined(CONFIG_ARCH_MSM8930)
+ /* Restart using the PMIC chip, see mach-msm/restart.c */
+ ldr r0, =MSM8930_TLMM_PHYS
+ mov r1, #0
+ str r1, [r0, #0x820] @ PSHOLD_CTL_SU
+loop: b loop
+#else
+#error "No reboot method defined for hardboot."
+#endif
+
+ .ltorg
+#endif
.align
.globl kexec_start_address
@@ -79,6 +120,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-msm/board-8930.c b/arch/arm/mach-msm/board-8930.c
index 4ecf2ad..65924cd 100644
--- a/arch/arm/mach-msm/board-8930.c
+++ b/arch/arm/mach-msm/board-8930.c
@@ -1100,6 +1100,17 @@ early_param("ext_display", ext_display_setup);
static void __init msm8930_reserve(void)
{
+#ifdef CONFIG_KEXEC_HARDBOOT
+ // Reserve space for hardboot page, just before the ram_console
+ //struct membank* bank = &meminfo.bank[0];
+ //phys_addr_t start = bank->start + bank->size - SZ_1M - 0x00300000;
+ phys_addr_t start = KEXEC_HB_PAGE_ADDR;
+ int 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
msm8930_set_display_params(prim_panel_name, ext_panel_name);
msm_reserve();
if (msm8930_fmem_pdata.size) {
diff --git a/arch/arm/mach-msm/include/mach/memory.h b/arch/arm/mach-msm/include/mach/memory.h
index e8c9567..37ecfbc 100644
--- a/arch/arm/mach-msm/include/mach/memory.h
+++ b/arch/arm/mach-msm/include/mach/memory.h
@@ -20,6 +20,14 @@
/* physical offset of RAM */
#define PLAT_PHYS_OFFSET UL(CONFIG_PHYS_OFFSET)
+#if defined(CONFIG_KEXEC_HARDBOOT)
+#if defined(CONFIG_FIH_MACH_S3A)
+#define KEXEC_HB_PAGE_ADDR UL(0x88000000)
+#else
+#error "Adress for kexec hardboot page not defined"
+#endif
+#endif
+
#define MAX_PHYSMEM_BITS 32
#define SECTION_SIZE_BITS 28
diff --git a/arch/arm/mach-msm/restart.c b/arch/arm/mach-msm/restart.c
index 5033f5e..6baa64d 100644
--- a/arch/arm/mach-msm/restart.c
+++ b/arch/arm/mach-msm/restart.c
@@ -36,6 +36,10 @@
#include "timer.h"
#include <linux/fih_sw_info.h> //MTD-KERNEL-DL-POC-00
+#ifdef CONFIG_KEXEC_HARDBOOT
+#include <asm/kexec.h>
+#endif
+
#define WDT0_RST 0x38
#define WDT0_EN 0x40
#define WDT0_BARK_TIME 0x4C
@@ -303,6 +307,14 @@ 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 PMIC to restart-on-poweroff
+ pm8xxx_reset_pwr_off(1);
+}
+#endif
+
static int __init msm_restart_init(void)
{
#ifdef CONFIG_MSM_DLOAD_MODE
@@ -314,6 +326,10 @@ static int __init msm_restart_init(void)
restart_reason = MSM_IMEM_BASE + RESTART_REASON_ADDR;
pm_power_off = msm_power_off;
+#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/crash_notes.c b/kernel/crash_notes.c
index 76bcf8d..73e2324 100644
--- a/kernel/crash_notes.c
+++ b/kernel/crash_notes.c
@@ -57,7 +57,7 @@ struct crash_extras {
typedef u32 note_buf_t[CRASH_NOTE_BYTES/4];
-note_buf_t* crash_notes;
+static note_buf_t* crash_notes;
static inline void dump_regs(struct pt_regs *regs)
{
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.7.9.5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment