Skip to content

Instantly share code, notes, and snippets.

@Tasssadar
Last active January 7, 2022 17:10
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save Tasssadar/6766757 to your computer and use it in GitHub Desktop.
Save Tasssadar/6766757 to your computer and use it in GitHub Desktop.
kexec-hardboot for LG Nexus 4 (mako)
From a5c71c50da839c6932af1903bcc0036daa033f7e Mon Sep 17 00:00:00 2001
From: Vojtech Bocek <vbocek@gmail.com>
Date: Mon, 30 Sep 2013 18:02:43 +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 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.
Signed-off-by: Vojtech Bocek <vbocek@gmail.com>
---
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 | 55 +++++++++++++++++++
arch/arm/mach-msm/include/mach/memory.h | 10 ++++
arch/arm/mach-msm/lge/devices_lge.c | 15 ++++++
arch/arm/mach-msm/restart.c | 16 ++++++
include/linux/kexec.h | 19 +++++--
kernel/kexec.c | 4 ++
11 files changed, 298 insertions(+), 24 deletions(-)
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index c450e1d..a577ae7 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -2180,6 +2180,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 c355aeb..449394d 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;
@@ -120,10 +125,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,
@@ -137,5 +145,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..f534293 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)
+ #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,40 @@ 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_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
+#else
+#error "No reboot method defined for hardboot."
+#endif
+
+ .ltorg
+#endif
.align
.globl kexec_start_address
@@ -79,6 +128,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/include/mach/memory.h b/arch/arm/mach-msm/include/mach/memory.h
index 8329611..d9f6b78 100644
--- a/arch/arm/mach-msm/include/mach/memory.h
+++ b/arch/arm/mach-msm/include/mach/memory.h
@@ -20,6 +20,16 @@
/* 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)
+#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/lge/devices_lge.c b/arch/arm/mach-msm/lge/devices_lge.c
index 504cc1e..f74e4e7 100644
--- a/arch/arm/mach-msm/lge/devices_lge.c
+++ b/arch/arm/mach-msm/lge/devices_lge.c
@@ -26,6 +26,10 @@
#include <ram_console.h>
+#ifdef CONFIG_KEXEC_HARDBOOT
+#include <linux/memblock.h>
+#endif
+
/* setting whether uart console is enalbed or disabled */
static int uart_console_mode = 0;
@@ -187,6 +191,17 @@ void __init lge_add_persistent_ram(void)
void __init lge_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 - LGE_PERSISTENT_RAM_SIZE;
+ 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
+
lge_add_persistent_ram();
}
diff --git a/arch/arm/mach-msm/restart.c b/arch/arm/mach-msm/restart.c
index 8fac40c..74ef77a 100644
--- a/arch/arm/mach-msm/restart.c
+++ b/arch/arm/mach-msm/restart.c
@@ -35,6 +35,10 @@
#include "msm_watchdog.h"
#include "timer.h"
+#ifdef CONFIG_KEXEC_HARDBOOT
+#include <asm/kexec.h>
+#endif
+
#define WDT0_RST 0x38
#define WDT0_EN 0x40
#define WDT0_BARK_TIME 0x4C
@@ -322,6 +326,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
@@ -337,6 +349,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/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.8.4.rc3
From e133376adb4140dd11772f143ad1790dcbbe37cf Mon Sep 17 00:00:00 2001
From: Vojtech Bocek <vbocek@gmail.com>
Date: Sun, 6 Oct 2013 16:35:23 +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 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.
Signed-off-by: Vojtech Bocek <vbocek@gmail.com>
---
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 | 55 +++++++++++++++++
arch/arm/mach-msm/asustek/devices_asustek.c | 15 +++++
arch/arm/mach-msm/include/mach/memory.h | 10 +++
arch/arm/mach-msm/lge/devices_lge.c | 15 +++++
arch/arm/mach-msm/restart.c | 16 +++++
include/linux/kexec.h | 19 +++++-
kernel/kexec.c | 4 ++
12 files changed, 313 insertions(+), 24 deletions(-)
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index b3b7d18..3bad5f7 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -2205,6 +2205,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 576e623..a2f492c 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
+
.arch armv7-a
/*
* Debugging stuff
@@ -136,6 +141,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 c355aeb..449394d 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;
@@ -120,10 +125,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,
@@ -137,5 +145,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..f534293 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)
+ #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,40 @@ 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_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
+#else
+#error "No reboot method defined for hardboot."
+#endif
+
+ .ltorg
+#endif
.align
.globl kexec_start_address
@@ -79,6 +128,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/asustek/devices_asustek.c b/arch/arm/mach-msm/asustek/devices_asustek.c
index 85ed946..43ab567 100644
--- a/arch/arm/mach-msm/asustek/devices_asustek.c
+++ b/arch/arm/mach-msm/asustek/devices_asustek.c
@@ -17,6 +17,10 @@
#include <linux/memory.h>
#include <linux/persistent_ram.h>
+#ifdef CONFIG_KEXEC_HARDBOOT
+#include <linux/memblock.h>
+#endif
+
#include <asm/setup.h>
#include <asm/sizes.h>
#include <asm/system_info.h>
@@ -56,6 +60,17 @@ void __init asustek_add_persistent_ram(void)
void __init asustek_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 - ASUSTEK_PERSISTENT_RAM_SIZE;
+ 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
+
asustek_add_persistent_ram();
}
diff --git a/arch/arm/mach-msm/include/mach/memory.h b/arch/arm/mach-msm/include/mach/memory.h
index 33fe673..daee9e6 100644
--- a/arch/arm/mach-msm/include/mach/memory.h
+++ b/arch/arm/mach-msm/include/mach/memory.h
@@ -20,6 +20,16 @@
/* 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)
+#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/lge/devices_lge.c b/arch/arm/mach-msm/lge/devices_lge.c
index 504cc1e..f74e4e7 100644
--- a/arch/arm/mach-msm/lge/devices_lge.c
+++ b/arch/arm/mach-msm/lge/devices_lge.c
@@ -26,6 +26,10 @@
#include <ram_console.h>
+#ifdef CONFIG_KEXEC_HARDBOOT
+#include <linux/memblock.h>
+#endif
+
/* setting whether uart console is enalbed or disabled */
static int uart_console_mode = 0;
@@ -187,6 +191,17 @@ void __init lge_add_persistent_ram(void)
void __init lge_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 - LGE_PERSISTENT_RAM_SIZE;
+ 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
+
lge_add_persistent_ram();
}
diff --git a/arch/arm/mach-msm/restart.c b/arch/arm/mach-msm/restart.c
index 369f70d..bc49721 100644
--- a/arch/arm/mach-msm/restart.c
+++ b/arch/arm/mach-msm/restart.c
@@ -35,6 +35,10 @@
#include "msm_watchdog.h"
#include "timer.h"
+#ifdef CONFIG_KEXEC_HARDBOOT
+#include <asm/kexec.h>
+#endif
+
extern unsigned get_cable_status(void);
#define WDT0_RST 0x38
@@ -337,6 +341,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
@@ -352,6 +364,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/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.8.4.rc3
@spider623
Copy link

hi i'm interested of using those patches, can you please point me to an article or instruct me how to?

Copy link

ghost commented May 1, 2015

@spider623 commented on May 17, 2014, 5:22 AM PDT:

hi i'm interested of using those patches, can you please point me to an article or instruct me how to?

https://github.com/Tasssadar/multirom/wiki/Porting-MultiROM
https://github.com/Tasssadar/multirom/wiki/Porting-kexec-hardboot

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment