Add nvstorage / crossystem support for new vboot2 fields

This allows testing vboot2.  These fields are ignored by original
vboot firmware.

BUG=chromium:370082
BRANCH=none
TEST=manual
	crossystem -> fw_tried=A, fw_result=unknown, fw_try_next=A

	crossystem fw_tried=B
	echo $? -> 1
	crossystem -> fw_tried=A, fw_result=unknown, fw_try_next=A

	crossystem fw_try_next=B
	crossystem -> fw_tried=A, fw_result=unknown, fw_try_next=B
	crossystem fw_try_next=beats_me
	echo $? -> 1
	crossystem -> fw_tried=A, fw_result=unknown, fw_try_next=B
	crossystem fw_try_next=A
	crossystem -> fw_tried=A, fw_result=unknown, fw_try_next=A

	crossystem fw_result=trying
	crossystem -> fw_tried=A, fw_result=trying, fw_try_next=A
	crossystem fw_result=bupkis
	echo $? -> 1
	crossystem -> fw_tried=A, fw_result=trying, fw_try_next=A
	crossystem fw_result=success
	crossystem -> fw_tried=A, fw_result=success, fw_try_next=A
	crossystem fw_result=failure
	crossystem -> fw_tried=A, fw_result=failure, fw_try_next=A
	crossystem fw_result=unknown
	crossystem -> fw_tried=A, fw_result=unknown, fw_try_next=A

	crossystem -> fw_try_count = 0, fwb_tries = 0
	crossystem fw_try_count=6
	crossystem -> fw_try_count = 6, fwb_tries = 6
	crossystem fwb_tries=0
	crossystem -> fw_try_count = 0, fwb_tries = 0

Change-Id: I1532f3384f8c05de2a7ff3f35abcc35d18049491
Signed-off-by: Randall Spangler <rspangler@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/205475
diff --git a/firmware/2lib/2nvstorage.c b/firmware/2lib/2nvstorage.c
index 067f0ae..0290474 100644
--- a/firmware/2lib/2nvstorage.c
+++ b/firmware/2lib/2nvstorage.c
@@ -15,6 +15,11 @@
  * Constants for NV storage.  We use this rather than structs and bitfields so
  * the data format is consistent across platforms and compilers.  Total NV
  * storage size is VB2_NVDATA_SIZE = 16 bytes.
+ *
+ * These constants must match the equivalent constants in
+ * lib/vboot_nvstorage.c.  (We currently don't share a common header file
+ * because we're tring to keep the two libs independent, and we hope to
+ * deprecate that one.)
  */
 
 enum vb2_nv_offset {
diff --git a/firmware/include/vboot_nvstorage.h b/firmware/include/vboot_nvstorage.h
index 534fb7e..0a9a484 100644
--- a/firmware/include/vboot_nvstorage.h
+++ b/firmware/include/vboot_nvstorage.h
@@ -45,8 +45,14 @@
 	/*
 	 * Number of times to try booting RW firmware slot B before slot A.
 	 * Valid range: 0-15.
+	 *
+	 * Vboot2: Number of times to try the firmware in VBNV_FW_TRY_NEXT.
+	 *
+	 * These refer to the same field, but have different enum values so
+	 * case statement don't complain about duplicates.
 	 */
 	VBNV_TRY_B_COUNT,
+	VBNV_FW_TRY_COUNT,
 	/*
 	 * Request recovery mode on next boot; see VBNB_RECOVERY_* below for
 	 * currently defined reason codes.  8-bit value.
@@ -85,8 +91,33 @@
 	VBNV_RECOVERY_SUBCODE,
 	/* Request that NVRAM be backed up at next boot if possible. */
 	VBNV_BACKUP_NVRAM_REQUEST,
+
+	/* Vboot2: Firmware slot to try next.  0=A, 1=B */
+	VBNV_FW_TRY_NEXT,
+	/* Vboot2: Firmware slot tried this boot (0=A, 1=B) */
+	VBNV_FW_TRIED,
+	/* Vboot2: Result of trying that firmware (see vb2_fw_result) */
+	VBNV_FW_RESULT,
+
+
 } VbNvParam;
 
+/* Result of trying the firmware in VBNV_FW_TRIED */
+typedef enum VbFwResult {
+	/* Unknown */
+	VBNV_FW_RESULT_UNKNOWN = 0,
+
+	/* Trying a new slot, but haven't reached success/failure */
+	VBNV_FW_RESULT_TRYING = 1,
+
+	/* Successfully booted to the OS */
+	VBNV_FW_RESULT_SUCCESS = 2,
+
+	/* Known failure */
+	VBNV_FW_RESULT_FAILURE = 3,
+
+} VbFwResult;
+
 /* Recovery reason codes for VBNV_RECOVERY_REQUEST */
 /* Recovery not requested. */
 #define VBNV_RECOVERY_NOT_REQUESTED   0x00
diff --git a/firmware/lib/vboot_nvstorage.c b/firmware/lib/vboot_nvstorage.c
index 258e5af..3c5d1e2 100644
--- a/firmware/lib/vboot_nvstorage.c
+++ b/firmware/lib/vboot_nvstorage.c
@@ -17,6 +17,10 @@
 /*
  * Constants for NV storage.  We use this rather than structs and bitfields so
  * the data format is consistent across platforms and compilers.
+ *
+ * These constants must match the equivalent constants in 2lib/2nvstorage.c.
+ * (We currently don't share a common header file because we're tring to keep
+ * the two libs independent, and we hope to deprecate this one.)
  */
 #define HEADER_OFFSET                0
 #define HEADER_MASK                     0xC0
@@ -45,6 +49,11 @@
 
 #define RECOVERY_SUBCODE_OFFSET      6
 
+#define BOOT2_OFFSET                 7
+#define BOOT2_RESULT_MASK               0x03
+#define BOOT2_TRIED                     0x04
+#define BOOT2_TRY_NEXT                  0x08
+
 #define KERNEL_FIELD_OFFSET         11
 #define CRC_OFFSET                  15
 
@@ -103,6 +112,7 @@
 		return 0;
 
 	case VBNV_TRY_B_COUNT:
+	case VBNV_FW_TRY_COUNT:
 		*dest = raw[BOOT_OFFSET] & BOOT_TRY_B_COUNT_MASK;
 		return 0;
 
@@ -159,6 +169,18 @@
 		*dest = (raw[BOOT_OFFSET] & BOOT_BACKUP_NVRAM ? 1 : 0);
 		return 0;
 
+	case VBNV_FW_TRY_NEXT:
+		*dest = (raw[BOOT2_OFFSET] & BOOT2_TRY_NEXT ? 1 : 0);
+		return 0;
+
+	case VBNV_FW_TRIED:
+		*dest = (raw[BOOT2_OFFSET] & BOOT2_TRIED ? 1 : 0);
+		return 0;
+
+	case VBNV_FW_RESULT:
+		*dest = raw[BOOT2_OFFSET] & BOOT2_RESULT_MASK;
+		return 0;
+
 	default:
 		return 1;
 	}
@@ -196,6 +218,7 @@
 		break;
 
 	case VBNV_TRY_B_COUNT:
+	case VBNV_FW_TRY_COUNT:
 		/* Clip to valid range. */
 		if (value > BOOT_TRY_B_COUNT_MASK)
 			value = BOOT_TRY_B_COUNT_MASK;
@@ -289,6 +312,28 @@
 			raw[BOOT_OFFSET] &= ~BOOT_BACKUP_NVRAM;
 		break;
 
+	case VBNV_FW_TRY_NEXT:
+		if (value)
+			raw[BOOT2_OFFSET] |= BOOT2_TRY_NEXT;
+		else
+			raw[BOOT2_OFFSET] &= ~BOOT2_TRY_NEXT;
+		break;
+
+	case VBNV_FW_TRIED:
+		if (value)
+			raw[BOOT2_OFFSET] |= BOOT2_TRIED;
+		else
+			raw[BOOT2_OFFSET] &= ~BOOT2_TRIED;
+		break;
+
+	case VBNV_FW_RESULT:
+		/* Map out of range values to unknown */
+		if (value > BOOT2_RESULT_MASK)
+			value = VBNV_FW_RESULT_UNKNOWN;
+
+		raw[BOOT2_OFFSET] &= ~BOOT2_RESULT_MASK;
+		raw[BOOT2_OFFSET] |= (uint8_t)value;
+		break;
 
 	default:
 		return 1;
diff --git a/host/lib/crossystem.c b/host/lib/crossystem.c
index f88d22c..d6555c6 100644
--- a/host/lib/crossystem.c
+++ b/host/lib/crossystem.c
@@ -60,6 +60,7 @@
   VB_BUILD_OPTION_NODEBUG
 } VbBuildOption;
 
+static const char *fw_results[] = {"unknown", "trying", "success", "failure"};
 
 /* Masks for kern_nv usage by kernel. */
 #define KERN_NV_FWUPDATE_TRIES_MASK 0x0000000F
@@ -464,6 +465,8 @@
     value = VbGetNvStorage(VBNV_TRY_B_COUNT);
   } else if (!strcasecmp(name,"fw_vboot2")) {
     value = GetVdatInt(VDAT_INT_FW_BOOT2);
+  } else if (!strcasecmp(name,"fw_try_count")) {
+    value = VbGetNvStorage(VBNV_FW_TRY_COUNT);
   } else if (!strcasecmp(name,"fwupdate_tries")) {
     value = VbGetNvStorage(VBNV_KERNEL_FIELD);
     if (value != -1)
@@ -547,6 +550,16 @@
     return GetVdatString(dest, size, VDAT_STRING_LOAD_KERNEL_DEBUG);
   } else if (!strcasecmp(name, "ddr_type")) {
     return unknown_string;
+  } else if (!strcasecmp(name, "fw_try_next")) {
+    return VbGetNvStorage(VBNV_FW_TRY_NEXT) ? "B" : "A";
+  } else if (!strcasecmp(name, "fw_tried")) {
+    return VbGetNvStorage(VBNV_FW_TRIED) ? "B" : "A";
+  } else if (!strcasecmp(name, "fw_result")) {
+    int v = VbGetNvStorage(VBNV_FW_RESULT);
+    if (v < ARRAY_SIZE(fw_results))
+      return fw_results[v];
+    else
+      return "unknown";
   }
 
   return NULL;
@@ -578,6 +591,8 @@
     return VbSetNvStorage(VBNV_CLEAR_TPM_OWNER_DONE, 0);
   } else if (!strcasecmp(name,"fwb_tries")) {
     return VbSetNvStorage(VBNV_TRY_B_COUNT, value);
+  } else if (!strcasecmp(name,"fw_try_count")) {
+    return VbSetNvStorage(VBNV_FW_TRY_COUNT, value);
   } else if (!strcasecmp(name,"oprom_needed")) {
     return VbSetNvStorage(VBNV_OPROM_NEEDED, value);
   } else if (!strcasecmp(name,"backup_nvram_request")) {
@@ -614,5 +629,26 @@
 
 int VbSetSystemPropertyString(const char* name, const char* value) {
   /* Chain to architecture-dependent properties */
-  return VbSetArchPropertyString(name, value);
+  if (0 == VbSetArchPropertyString(name, value))
+    return 0;
+
+  if (!strcasecmp(name, "fw_try_next")) {
+    if (!strcasecmp(value, "A"))
+      return VbSetNvStorage(VBNV_FW_TRY_NEXT, 0);
+    else if (!strcasecmp(value, "B"))
+      return VbSetNvStorage(VBNV_FW_TRY_NEXT, 1);
+    else
+      return -1;
+
+  } else if (!strcasecmp(name, "fw_result")) {
+    int i;
+
+    for (i = 0; i < ARRAY_SIZE(fw_results); i++) {
+      if (!strcasecmp(value, fw_results[i]))
+	return VbSetNvStorage(VBNV_FW_RESULT, i);
+    }
+    return -1;
+  }
+
+  return -1;
 }
diff --git a/tests/vboot_nvstorage_test.c b/tests/vboot_nvstorage_test.c
index 63e93d9..2d09618 100644
--- a/tests/vboot_nvstorage_test.c
+++ b/tests/vboot_nvstorage_test.c
@@ -37,6 +37,10 @@
   {VBNV_CLEAR_TPM_OWNER_REQUEST, 0, 1, 0, "clear tpm owner request"},
   {VBNV_CLEAR_TPM_OWNER_DONE, 0, 1, 0, "clear tpm owner done"},
   {VBNV_OPROM_NEEDED, 0, 1, 0, "oprom needed"},
+  {VBNV_FW_TRY_COUNT, 0, 8, 15, "try count"},
+  {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"},
   {0, 0, 0, 0, NULL}
 };
 
@@ -125,17 +129,22 @@
 
   /* Test other fields */
   VbNvSetup(&c);
+  /* Test all defaults first, since some fields alias onto others */
   for (vnf = nvfields; vnf->desc; vnf++) {
-    TEST_EQ(VbNvGet(&c, vnf->param, &data), 0, vnf->desc);
-    TEST_EQ(data, vnf->default_value, vnf->desc);
+    printf("Testing field: %s\n", vnf->desc);
+    TEST_EQ(VbNvGet(&c, vnf->param, &data), 0, "  get");
+    TEST_EQ(data, vnf->default_value, "  default");
+  }
+  /* Now test get/set */
+  for (vnf = nvfields; vnf->desc; vnf++) {
+    printf("Testing field: %s\n", vnf->desc);
+    TEST_EQ(VbNvSet(&c, vnf->param, vnf->test_value), 0, "  set 1");
+    TEST_EQ(VbNvGet(&c, vnf->param, &data), 0, "  get 1");
+    TEST_EQ(data, vnf->test_value, "  value 1");
 
-    TEST_EQ(VbNvSet(&c, vnf->param, vnf->test_value), 0, vnf->desc);
-    TEST_EQ(VbNvGet(&c, vnf->param, &data), 0, vnf->desc);
-    TEST_EQ(data, vnf->test_value, vnf->desc);
-
-    TEST_EQ(VbNvSet(&c, vnf->param, vnf->test_value2), 0, vnf->desc);
-    TEST_EQ(VbNvGet(&c, vnf->param, &data), 0, vnf->desc);
-    TEST_EQ(data, vnf->test_value2, vnf->desc);
+    TEST_EQ(VbNvSet(&c, vnf->param, vnf->test_value2), 0, "  set 2");
+    TEST_EQ(VbNvGet(&c, vnf->param, &data), 0, "  get 2");
+    TEST_EQ(data, vnf->test_value2, "  value 2");
   }
   VbNvTeardown(&c);
 
@@ -161,12 +170,19 @@
   VbNvSet(&c, VBNV_TRY_B_COUNT, 16);
   VbNvGet(&c, VBNV_TRY_B_COUNT, &data);
   TEST_EQ(data, 15, "Try b count out of range");
+  VbNvSetup(&c);
+  VbNvSet(&c, VBNV_FW_TRY_COUNT, 16);
+  VbNvGet(&c, VBNV_FW_TRY_COUNT, &data);
+  TEST_EQ(data, 15, "Try count out of range");
   VbNvSet(&c, VBNV_RECOVERY_REQUEST, 0x101);
   VbNvGet(&c, VBNV_RECOVERY_REQUEST, &data);
   TEST_EQ(data, VBNV_RECOVERY_LEGACY, "Recovery request out of range");
   VbNvSet(&c, VBNV_LOCALIZATION_INDEX, 0x102);
   VbNvGet(&c, VBNV_LOCALIZATION_INDEX, &data);
   TEST_EQ(data, 0, "Localization index out of range");
+  VbNvSet(&c, VBNV_FW_RESULT, VBNV_FW_RESULT_UNKNOWN + 100);
+  VbNvGet(&c, VBNV_FW_RESULT, &data);
+  TEST_EQ(data, VBNV_FW_RESULT_UNKNOWN, "Firmware result out of range");
   VbNvTeardown(&c);
 }
 
diff --git a/utility/crossystem.c b/utility/crossystem.c
index 6b16ff4..5251f7d 100644
--- a/utility/crossystem.c
+++ b/utility/crossystem.c
@@ -57,6 +57,12 @@
   {"fwid", IS_STRING, "Active firmware ID"},
   {"fwupdate_tries", CAN_WRITE,
    "Times to try OS firmware update (writable, inside kern_nv)"},
+  {"fw_tried", IS_STRING, "Firmware tried this boot (vboot2)"},
+  {"fw_try_count", CAN_WRITE, "Number of times to try fw_try_next (writable)"},
+  {"fw_try_next", IS_STRING|CAN_WRITE,
+   "Firmware to try next (vboot2,writable)"},
+  {"fw_result", IS_STRING|CAN_WRITE,
+   "Firmware result this boot (vboot2,writable)"},
   {"hwid", IS_STRING, "Hardware ID"},
   {"kern_nv", 0, "Non-volatile field for kernel use", "0x%08x"},
   {"kernkey_vfy", IS_STRING, "Type of verification done on kernel key block"},