vboot2: Add previously tried slot and result to NV storage

This gives recovery mode information on two boots back instead of one,
which may be handy for debugging.

It also allows determining whether a failure of the current boot
should try the other slot or go to recovery, using only information
stored in NV storage.

Added crossystem support for printing the fields, and unit tests.

BUG=chrome-os-partner:32585
BRANCH=none
TEST=make runtests; VBOOT2=1 make runtests

Change-Id: Ia9f4186210d30217b902db7c513ae4ab8851f8f4
Signed-off-by: Randall Spangler <rspangler@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/221230
Reviewed-by: Daisuke Nojiri <dnojiri@chromium.org>
diff --git a/firmware/2lib/2misc.c b/firmware/2lib/2misc.c
index 94ad0c3..bcf5b36 100644
--- a/firmware/2lib/2misc.c
+++ b/firmware/2lib/2misc.c
@@ -316,6 +316,10 @@
 	sd->last_fw_slot = vb2_nv_get(ctx, VB2_NV_FW_TRIED);
 	sd->last_fw_result = vb2_nv_get(ctx, VB2_NV_FW_RESULT);
 
+	/* Save to the previous result fields in NV storage */
+	vb2_nv_set(ctx, VB2_NV_FW_PREV_TRIED, sd->last_fw_slot);
+	vb2_nv_set(ctx, VB2_NV_FW_PREV_RESULT, sd->last_fw_result);
+
 	/* Clear result, since we don't know what will happen this boot */
 	vb2_nv_set(ctx, VB2_NV_FW_RESULT, VB2_FW_RESULT_UNKNOWN);
 
diff --git a/firmware/2lib/2nvstorage.c b/firmware/2lib/2nvstorage.c
index 0290474..5c7b980 100644
--- a/firmware/2lib/2nvstorage.c
+++ b/firmware/2lib/2nvstorage.c
@@ -31,7 +31,7 @@
 	VB2_NV_OFFS_TPM = 5,
 	VB2_NV_OFFS_RECOVERY_SUBCODE = 6,
 	VB2_NV_OFFS_BOOT2 = 7,
-	/* Offsets 7-10 are currently unused */
+	/* Offsets 8-10 are currently unused */
 	VB2_NV_OFFS_KERNEL = 11, /* 11-14; field is 32 bits */
 	/* CRC must be last field */
 	VB2_NV_OFFS_CRC = 15
@@ -50,10 +50,13 @@
 #define VB2_NV_BOOT_DISABLE_DEV                0x40
 #define VB2_NV_BOOT_DEBUG_RESET                0x80
 
-/* Fields in VB2_NV_OFFS_BOOT2 (unused = 0xf0) */
+/* Fields in VB2_NV_OFFS_BOOT2 (unused = 0x80) */
 #define VB2_NV_BOOT2_RESULT_MASK               0x03
 #define VB2_NV_BOOT2_TRIED                     0x04
 #define VB2_NV_BOOT2_TRY_NEXT                  0x08
+#define VB2_NV_BOOT2_PREV_RESULT_MASK          0x30
+#define VB2_NV_BOOT2_PREV_RESULT_SHIFT 4  /* Number of bits to shift result */
+#define VB2_NV_BOOT2_PREV_TRIED                0x40
 
 /* Fields in VB2_NV_OFFS_DEV (unused = 0xf8) */
 #define VB2_NV_DEV_FLAG_USB                    0x01
@@ -156,6 +159,13 @@
 	case VB2_NV_FW_RESULT:
 		return p[VB2_NV_OFFS_BOOT2] & VB2_NV_BOOT2_RESULT_MASK;
 
+	case VB2_NV_FW_PREV_TRIED:
+		return GETBIT(VB2_NV_OFFS_BOOT2, VB2_NV_BOOT2_PREV_TRIED);
+
+	case VB2_NV_FW_PREV_RESULT:
+		return (p[VB2_NV_OFFS_BOOT2] & VB2_NV_BOOT2_PREV_RESULT_MASK)
+			>> VB2_NV_BOOT2_PREV_RESULT_SHIFT;
+
 	case VB2_NV_RECOVERY_REQUEST:
 		return p[VB2_NV_OFFS_RECOVERY];
 
@@ -262,6 +272,20 @@
 		p[VB2_NV_OFFS_BOOT2] |= (uint8_t)value;
 		break;
 
+	case VB2_NV_FW_PREV_TRIED:
+		SETBIT(VB2_NV_OFFS_BOOT2, VB2_NV_BOOT2_PREV_TRIED);
+		break;
+
+	case VB2_NV_FW_PREV_RESULT:
+		/* Map out of range values to unknown */
+		if (value > VB2_NV_BOOT2_RESULT_MASK)
+			value = VB2_FW_RESULT_UNKNOWN;
+
+		p[VB2_NV_OFFS_BOOT2] &= ~VB2_NV_BOOT2_PREV_RESULT_MASK;
+		p[VB2_NV_OFFS_BOOT2] |=
+			(uint8_t)(value << VB2_NV_BOOT2_PREV_RESULT_SHIFT);
+		break;
+
 	case VB2_NV_RECOVERY_REQUEST:
 		/*
 		 * Map values outside the valid range to the legacy reason,
diff --git a/firmware/2lib/include/2nvstorage.h b/firmware/2lib/include/2nvstorage.h
index 11a7796..3bda9d7 100644
--- a/firmware/2lib/include/2nvstorage.h
+++ b/firmware/2lib/include/2nvstorage.h
@@ -74,9 +74,13 @@
 	VB2_NV_FW_TRIED,
 	/* Result of trying that firmware (see vb2_fw_result) */
 	VB2_NV_FW_RESULT,
+	/* Firmware slot tried previous boot (0=A, 1=B) */
+	VB2_NV_FW_PREV_TRIED,
+	/* Result of trying that firmware (see vb2_fw_result) */
+	VB2_NV_FW_PREV_RESULT,
 };
 
-/* Result of trying the firmware in VB2_NV_FW_TRIED */
+/* Firmware result codes for VB2_NV_FW_RESULT and VB2_NV_FW_PREV_RESULT */
 enum vb2_fw_result {
 	/* Unknown */
 	VB2_FW_RESULT_UNKNOWN = 0,
diff --git a/firmware/include/vboot_nvstorage.h b/firmware/include/vboot_nvstorage.h
index 0a9a484..f71a55a 100644
--- a/firmware/include/vboot_nvstorage.h
+++ b/firmware/include/vboot_nvstorage.h
@@ -98,7 +98,10 @@
 	VBNV_FW_TRIED,
 	/* Vboot2: Result of trying that firmware (see vb2_fw_result) */
 	VBNV_FW_RESULT,
-
+	/* Firmware slot tried previous boot (0=A, 1=B) */
+	VBNV_FW_PREV_TRIED,
+	/* Result of trying that firmware (see vb2_fw_result) */
+	VBNV_FW_PREV_RESULT,
 
 } VbNvParam;
 
diff --git a/firmware/lib/vboot_nvstorage.c b/firmware/lib/vboot_nvstorage.c
index 5476e85..a0721d7 100644
--- a/firmware/lib/vboot_nvstorage.c
+++ b/firmware/lib/vboot_nvstorage.c
@@ -51,6 +51,9 @@
 #define BOOT2_RESULT_MASK               0x03
 #define BOOT2_TRIED                     0x04
 #define BOOT2_TRY_NEXT                  0x08
+#define BOOT2_PREV_RESULT_MASK          0x30
+#define BOOT2_PREV_RESULT_SHIFT 4  /* Number of bits to shift result */
+#define BOOT2_PREV_TRIED                0x40
 
 #define KERNEL_FIELD_OFFSET         11
 #define CRC_OFFSET                  15
@@ -179,6 +182,15 @@
 		*dest = raw[BOOT2_OFFSET] & BOOT2_RESULT_MASK;
 		return 0;
 
+	case VBNV_FW_PREV_TRIED:
+		*dest = (raw[BOOT2_OFFSET] & BOOT2_PREV_TRIED ? 1 : 0);
+		return 0;
+
+	case VBNV_FW_PREV_RESULT:
+		*dest = (raw[BOOT2_OFFSET] & BOOT2_PREV_RESULT_MASK)
+			>> BOOT2_PREV_RESULT_SHIFT;
+		return 0;
+
 	default:
 		return 1;
 	}
@@ -333,6 +345,22 @@
 		raw[BOOT2_OFFSET] |= (uint8_t)value;
 		break;
 
+	case VBNV_FW_PREV_TRIED:
+		if (value)
+			raw[BOOT2_OFFSET] |= BOOT2_PREV_TRIED;
+		else
+			raw[BOOT2_OFFSET] &= ~BOOT2_PREV_TRIED;
+		break;
+
+	case VBNV_FW_PREV_RESULT:
+		/* Map out of range values to unknown */
+		if (value > BOOT2_RESULT_MASK)
+			value = VBNV_FW_RESULT_UNKNOWN;
+
+		raw[BOOT2_OFFSET] &= ~BOOT2_PREV_RESULT_MASK;
+		raw[BOOT2_OFFSET] |= (uint8_t)value << BOOT2_PREV_RESULT_SHIFT;
+		break;
+
 	default:
 		return 1;
 	}
diff --git a/host/lib/crossystem.c b/host/lib/crossystem.c
index d6555c6..b28fd87 100644
--- a/host/lib/crossystem.c
+++ b/host/lib/crossystem.c
@@ -560,6 +560,14 @@
       return fw_results[v];
     else
       return "unknown";
+  } else if (!strcasecmp(name, "fw_prev_tried")) {
+    return VbGetNvStorage(VBNV_FW_PREV_TRIED) ? "B" : "A";
+  } else if (!strcasecmp(name, "fw_prev_result")) {
+    int v = VbGetNvStorage(VBNV_FW_PREV_RESULT);
+    if (v < ARRAY_SIZE(fw_results))
+      return fw_results[v];
+    else
+      return "unknown";
   }
 
   return NULL;
diff --git a/tests/vb2_misc_tests.c b/tests/vb2_misc_tests.c
index 72c284c..6155f23 100644
--- a/tests/vb2_misc_tests.c
+++ b/tests/vb2_misc_tests.c
@@ -450,6 +450,23 @@
 	TEST_EQ(sd->fw_slot, 0, "selected A");
 	TEST_EQ(cc.flags & VB2_CONTEXT_FW_SLOT_B, 0, "didn't choose B");
 	TEST_EQ(vb2_nv_get(&cc, VB2_NV_TRY_COUNT), 2, "tries decremented");
+
+	/* Tried/result get copied to the previous fields */
+	reset_common_data();
+	vb2_nv_set(&cc, VB2_NV_FW_TRIED, 0);
+	vb2_nv_set(&cc, VB2_NV_FW_RESULT, VB2_FW_RESULT_SUCCESS);
+	vb2_select_fw_slot(&cc);
+	TEST_EQ(vb2_nv_get(&cc, VB2_NV_FW_PREV_TRIED), 0, "prev A");
+	TEST_EQ(vb2_nv_get(&cc, VB2_NV_FW_PREV_RESULT),	VB2_FW_RESULT_SUCCESS,
+		"prev success");
+
+	reset_common_data();
+	vb2_nv_set(&cc, VB2_NV_FW_TRIED, 1);
+	vb2_nv_set(&cc, VB2_NV_FW_RESULT, VB2_FW_RESULT_FAILURE);
+	vb2_select_fw_slot(&cc);
+	TEST_EQ(vb2_nv_get(&cc, VB2_NV_FW_PREV_TRIED), 1, "prev B");
+	TEST_EQ(vb2_nv_get(&cc, VB2_NV_FW_PREV_RESULT),	VB2_FW_RESULT_FAILURE,
+		"prev failure");
 }
 
 int main(int argc, char* argv[])
diff --git a/tests/vb2_nvstorage_tests.c b/tests/vb2_nvstorage_tests.c
index b648ca9..46547f8 100644
--- a/tests/vb2_nvstorage_tests.c
+++ b/tests/vb2_nvstorage_tests.c
@@ -34,6 +34,8 @@
 	{VB2_NV_TRY_COUNT, 0, 6, 15, "try B count"},
 	{VB2_NV_FW_TRIED, 0, 1, 0, "firmware tried"},
 	{VB2_NV_FW_RESULT, 0, 1, 2, "firmware result"},
+	{VB2_NV_FW_PREV_TRIED, 0, 1, 0, "firmware prev tried"},
+	{VB2_NV_FW_PREV_RESULT, 0, 1, 3, "firmware prev result"},
 	{VB2_NV_RECOVERY_REQUEST, 0, 0x42, 0xED, "recovery request"},
 	{VB2_NV_RECOVERY_SUBCODE, 0, 0x56, 0xAC, "recovery subcode"},
 	{VB2_NV_LOCALIZATION_INDEX, 0, 0x69, 0xB0, "localization index"},
diff --git a/tests/vboot_nvstorage_test.c b/tests/vboot_nvstorage_test.c
index 2d09618..ec85738 100644
--- a/tests/vboot_nvstorage_test.c
+++ b/tests/vboot_nvstorage_test.c
@@ -41,6 +41,8 @@
   {VBNV_FW_TRY_NEXT, 0, 1, 0, "try next"},
   {VBNV_FW_TRIED, 0, 1, 0, "firmware tried"},
   {VBNV_FW_RESULT, VBNV_FW_RESULT_UNKNOWN, 1, 2, "firmware result"},
+  {VBNV_FW_PREV_TRIED, 0, 1, 0, "firmware prev tried"},
+  {VBNV_FW_PREV_RESULT, VBNV_FW_RESULT_UNKNOWN, 1, 3, "firmware prev result"},
   {0, 0, 0, 0, NULL}
 };