Anti reboot loop feature

The anti reboot loop feature is a communication protocol between the
bootloader and the kernel that allows the bootloader to determine if a
certain kernel/rootfs image is "good". The way it works is that the
bootloader writes a certain bit pattern to a previously agreed memory
location. If a userspace app decides that the kernel has booted ok and
that the user space environment is sane, it tells the kernel which then
flips a bit in DRAM to communicate to the bootloader that this image is
considered "good".  Now, if the system reboots due to some failure
condition before said bit has been flipped, the bootloader is able to
detect this situation and switch back to a different kernel/rootfs
image.

Change-Id: Ief095aa50adee565730704f687fefbfe124ce7e9
diff --git a/arch/arm/boards/optimus/env/bin/init b/arch/arm/boards/optimus/env/bin/init
index 911d75f..30546b5 100644
--- a/arch/arm/boards/optimus/env/bin/init
+++ b/arch/arm/boards/optimus/env/bin/init
@@ -27,7 +27,11 @@
 [ $HNV_MAC_ADDR_WAN ] && eth1.ethaddr=$HNV_MAC_ADDR_WAN
 [ $HNV_MAC_ADDR_MOCA ] && eth2.ethaddr=$HNV_MAC_ADDR_MOCA
 
-if [ x$HNV_ACTIVATED_KERNEL_NAME = xkernel1 ]; then
+if [ x$arl  = xfailover ]; then
+  echo "Failing over to other image because antirebootloop=$arl"
+fi
+
+if [ x$HNV_ACTIVATED_KERNEL_NAME = xkernel1 -a x$arl != xfailover ] || [ x$HNV_ACTIVATED_KERNEL_NAME = xkernel0 -a x$arl  = xfailover ]; then
   kernpart=kernel1
   rootpart=rootfs1
 else
diff --git a/commands/bootm.c b/commands/bootm.c
index 6c946e8..a76cf6f 100644
--- a/commands/bootm.c
+++ b/commands/bootm.c
@@ -46,6 +46,7 @@
 #include <rsa_verify.h>
 #include <sha1.h>
 #include <secure_boot.h>
+#include <antirebootloop.h>
 
 #ifdef CONFIG_NAND_COMCERTO_ECC_HW_BCH
 extern uint32_t temp_nand_ecc_errors[];
@@ -646,6 +647,8 @@
 
 	puts ("OK\n");
 
+	antirebootloop_preboot_hook();
+
 	/* loop through the registered handlers */
 	list_for_each_entry(handler, &handler_list, list) {
 		if (image_get_os(os_header) == handler->image_type) {
diff --git a/common/Makefile b/common/Makefile
index 2268084..3b0b94d 100644
--- a/common/Makefile
+++ b/common/Makefile
@@ -8,6 +8,7 @@
 obj-$(CONFIG_POLLER)		+= poller.o
 obj-$(CONFIG_BLOCK)		+= block.o
 
+obj-y += antirebootloop.o
 obj-y += memory.o
 obj-$(CONFIG_MALLOC_DLMALLOC) += dlmalloc.o
 obj-$(CONFIG_MALLOC_DUMMY) += dummy_malloc.o
diff --git a/common/antirebootloop.c b/common/antirebootloop.c
new file mode 100644
index 0000000..6c3a858
--- /dev/null
+++ b/common/antirebootloop.c
@@ -0,0 +1,62 @@
+#include <antirebootloop.h>
+#include <common.h>
+#include <environment.h>
+#include <init.h>
+
+#define FAILOVER_THRESHOLD 3
+#define FAILOVER_THRESHOLD_NO_KERNEL_SUPPORT 6
+
+static void *antirebootloop_scratchspace = (void *) 0x1000;
+
+static int write_marker(struct arl_marker *m) {
+	int i;
+	memset(m, 0, sizeof(*m));
+	for (i=0; i < ARRAY_SIZE(m->magic); i++) {
+		m->magic[i] = ARL_MAGIC + i;
+	}
+	m->kernel_version = 0;
+	/* m->bootloader_version is populated in antirebootloop_init() */
+	return 0;
+}
+
+static int verify_marker(struct arl_marker *m) {
+	int i;
+	for (i=0; i < ARRAY_SIZE(m->magic); i++) {
+		if (m->magic[i] != ARL_MAGIC + i) return -1;
+	}
+	return 0;
+}
+
+static int antirebootloop_init(void) {
+	struct arl_marker *m = antirebootloop_scratchspace;
+	if (verify_marker(m)) {
+		printf("Cannot find antirebootloop marker\n");
+		write_marker(m);
+		m->counter = 0;
+	} else {
+		printf("Found antirebootloop marker. Counter %u."
+			       " Kernel support %u\n",
+			       m->counter, m->kernel_version);
+	}
+	m->bootloader_version = ARL_BOOTLOADER_VERSION;
+	if ( (m->kernel_version == 1 && m->counter >= FAILOVER_THRESHOLD) ||
+		(m->kernel_version == 0 &&
+			m->counter >= FAILOVER_THRESHOLD_NO_KERNEL_SUPPORT) ) {
+		printf("Antirebootloop threshold reached\n");
+		setenv("arl", "failover");
+	} else {
+		setenv("arl", "ok");
+	}
+	return 0;
+}
+
+void antirebootloop_preboot_hook(void) {
+	struct arl_marker *m = antirebootloop_scratchspace;
+	m->counter++;
+	/* Reset kernel_version to ensure that, on the next boot, we don't read
+	 * some stale value from a previously booted kernel. */
+	m->kernel_version = 0;
+}
+EXPORT_SYMBOL(antirebootloop_preboot_hook)
+
+late_initcall(antirebootloop_init);
diff --git a/include/antirebootloop.h b/include/antirebootloop.h
new file mode 100644
index 0000000..8762b82
--- /dev/null
+++ b/include/antirebootloop.h
@@ -0,0 +1,19 @@
+#include <common.h>
+
+#define ARL_MAGIC 0x1c93f311
+#define ARL_BOOTLOADER_VERSION 1
+
+struct arl_marker {
+	u32 magic[16]; /* Use a 64 byte magic value to increase the likelihood
+			  of detecting bit flips. magic[i] = ARL_MAGIC + i for
+			  0<i<15. */
+	u32 counter; /* bootloader increments this counter on every boot
+			attempt.  Kernel resets it to 0. */
+	/* The term version refers to this anti-reboot-loop mechanism not to
+	 * the barebox or kernel version number */
+	u32 bootloader_version; /* ARL version supported by bootloader */
+	u32 kernel_version; /* ARL version supported by bootloader. Filled in
+			       by kernel. */
+};
+
+void antirebootloop_preboot_hook(void);