Cherry-pick vboot_reference files from TOT to support crossystem

These files are picked from vboot_reference TOT at this tag:
7141571d55373fc2a84a70b5663409a653f8049d

There's about a dozen individual CLs in TOT which this is coalesced
from.  Rather than attempt to squash all of them, I've just pulled the
current versions.  This admittedly pulls in more files than I'd like
in a single CL.  Most of those are inside the firmware subdir, and are
required for unit tests (esp. load_kernel_test) to compile but aren't
used in the actual OS image.  I have NOT pulled in any unreleated
script changes, such as Gaurav's changes to the signing scripts.

Change-Id: I3521a6eed40ff12c82545f2170090e755d7007fc

R=dlaurie@chromium.org,reinauer@chromium.org,puneetster@chromium.org
BUG=12904
TEST=manual

1. make && make runtests && make clean
2. emerge-x86-alex vboot_reference, then run the resulting crossystem utility on Alex 0053G1+ firmware

Review URL: http://codereview.chromium.org/6719005
diff --git a/firmware/Makefile b/firmware/Makefile
index fcff8bb..accc835 100644
--- a/firmware/Makefile
+++ b/firmware/Makefile
@@ -15,6 +15,24 @@
 CFLAGS += -DDISABLE_ROLLBACK_TPM
 endif
 
+# TPM-specific flags.  These depend on the particular TPM we're targeting for.
+# They are needed here only for compiling parts of the firmware code into
+# user-level tests.
+
+# TPM_BLOCKING_CONTINUESELFTEST is defined if TPM_ContinueSelfTest blocks until
+# the self test has completed.
+
+CLAGS += -DTPM_BLOCKING_CONTINUESELFTEST
+
+# TPM_MANUAL_SELFTEST is defined if the self test must be started manually
+# (with a call to TPM_ContinueSelfTest) instead of starting automatically at
+# power on.
+#
+# We sincerely hope that TPM_BLOCKING_CONTINUESELFTEST and TPM_MANUAL_SELFTEST
+# are not both defined at the same time.  (See comment in code.)
+
+# CLAGS += -DTPM_MANUAL_SELFTEST
+
 INCLUDES = \
 	-I$(FWTOP)/include \
 	-I$(LIBDIR)/include \
@@ -40,6 +58,7 @@
 	./lib/cryptolib/sha2.c \
 	./lib/cryptolib/sha_utility.c \
 	./lib/rollback_index.c \
+	./lib/tpm_bootmode.c \
 	./lib/stateful_util.c \
 	./lib/tpm_lite/tlcl.c \
 	./lib/utility.c \
diff --git a/firmware/include/load_firmware_fw.h b/firmware/include/load_firmware_fw.h
index 1e42f1c..11fb0ce 100644
--- a/firmware/include/load_firmware_fw.h
+++ b/firmware/include/load_firmware_fw.h
@@ -11,9 +11,7 @@
 
 #include "sysincludes.h"
 #include "vboot_nvstorage.h"
-
-/* Recommended size of shared_data_blob in bytes. */
-#define LOAD_FIRMWARE_SHARED_DATA_REC_SIZE 16384
+#include "vboot_struct.h"
 
 /* Return codes for LoadFirmware() and S3Resume(). */
 #define LOAD_FIRMWARE_SUCCESS 0   /* Success */
@@ -33,15 +31,19 @@
   void* verification_block_1;    /* Key block + preamble for firmware 1 */
   uint64_t verification_size_0;  /* Verification block 0 size in bytes */
   uint64_t verification_size_1;  /* Verification block 1 size in bytes */
-  void* shared_data_blob;        /* Destination buffer for data shared between
-                                  * LoadFirmware() and LoadKernel().  Pass this
+
+  /* Shared data blob for data shared between LoadFirmware() and LoadKernel().
+   * This should be at least VB_SHARED_DATA_MIN_SIZE bytes long, and ideally
+   * is VB_SHARED_DATA_REC_SIZE bytes long. */
+  void* shared_data_blob;        /* Shared data blob buffer.  Pass this
                                   * data to LoadKernel() in
                                   * LoadKernelParams.shared_data_blob. */
-  uint64_t shared_data_size;     /* Size of shared data blob buffer, in bytes.
-                                  * On output, this will contain the actual
-                                  * data size placed into the buffer.  Caller
-                                  * need only pass this much data to
-                                  * LoadKernel().*/
+  uint64_t shared_data_size;     /* On input, set to size of shared data blob
+                                  * buffer, in bytes.  On output, this will
+                                  * contain the actual data size placed into
+                                  * the buffer.  Caller need only pass that
+                                  * much data to LoadKernel().*/
+
   uint64_t boot_flags;           /* Boot flags */
   VbNvContext* nv_context;       /* Context for NV storage.  nv_context->raw
                                   * must be filled before calling
diff --git a/firmware/include/tlcl.h b/firmware/include/tlcl.h
index 9531054..10d0802 100644
--- a/firmware/include/tlcl.h
+++ b/firmware/include/tlcl.h
@@ -35,6 +35,11 @@
  */
 uint32_t TlclStartup(void);
 
+/* Save the TPM state.  Normally done by the kernel before a suspend, included
+ * here for tests.  The TPM error code is returned (0 for success).
+ */
+uint32_t TlclSaveState(void);
+
 /* Resumes by sending a TPM_Startup(ST_STATE).  The TPM error code is returned
  * (0 for success).
  */
@@ -127,7 +132,7 @@
 
 /* Performs a TPM_Extend.
  */
-uint32_t TlclExtend(int pcr_num, uint8_t* in_digest, uint8_t* out_digest);
+uint32_t TlclExtend(int pcr_num, const uint8_t* in_digest, uint8_t* out_digest);
 
 /* Gets the permission bits for the NVRAM space with |index|.
  */
diff --git a/firmware/include/utility.h b/firmware/include/utility.h
index 3c4a3bb..4f88ab9 100644
--- a/firmware/include/utility.h
+++ b/firmware/include/utility.h
@@ -96,4 +96,18 @@
 #define memset _do_not_use_standard_memset
 #endif
 
+/* Read a high-resolution timer. */
+uint64_t VbGetTimer(void);
+
+/* Return the maximum frequency for the high-resolution timer, in Hz.
+ *
+ * Note that this call MUST be fast; the implementation must not
+ * attempt to actually measure the frequency.  This function need only
+ * return an upper bound for the timer frequency, so that minimum
+ * delays can be established.  For example, if the same BIOS can run
+ * on CPUs where the timer frequency varies between 1.2GHz and 1.8GHz,
+ * return 1800000000 (or even 2000000000). */
+uint64_t VbGetTimerMaxFreq(void);
+
+
 #endif  /* VBOOT_REFERENCE_UTILITY_H_ */
diff --git a/firmware/include/vboot_nvstorage.h b/firmware/include/vboot_nvstorage.h
index 824dfac..c2a722f 100644
--- a/firmware/include/vboot_nvstorage.h
+++ b/firmware/include/vboot_nvstorage.h
@@ -47,13 +47,11 @@
   VBNV_LOCALIZATION_INDEX,
   /* Field reserved for kernel/user-mode use; 32-bit value. */
   VBNV_KERNEL_FIELD,
-  /* Firmware checked RW slot B before slot A on the current boot because
-   * VBNV_TRY_B_COUNT was non-zero at that time.  0=no; 1=yes. */
-  VBNV_TRIED_FIRMWARE_B,
-  /* Firmware verified the kernel key block signature using the key stored
-   * in the firmware.  0=no, just used the key block hash; 1=yes, used the
-   * key block signature. */
-  VBNV_FW_VERIFIED_KERNEL_KEY,
+  /* Verified boot API function which should generate a test error, if
+   * error number (below) is non-zero. */
+  VBNV_TEST_ERROR_FUNC,
+  /* Verified boot API error to generate for the function, if non-zero. */
+  VBNV_TEST_ERROR_NUM,
 } VbNvParam;
 
 
@@ -76,6 +74,12 @@
 #define VBNV_RECOVERY_RO_TPM_ERROR    0x05
 /* Shared data error in read-only firmware */
 #define VBNV_RECOVERY_RO_SHARED_DATA  0x06
+/* Test error from S3Resume() */
+#define VBNV_RECOVERY_RO_TEST_S3      0x07
+/* Test error from LoadFirmwareSetup() */
+#define VBNV_RECOVERY_RO_TEST_LFS     0x08
+/* Test error from LoadFirmware() */
+#define VBNV_RECOVERY_RO_TEST_LF      0x09
 /* Unspecified/unknown error in read-only firmware */
 #define VBNV_RECOVERY_RO_UNSPECIFIED  0x3F
 /* User manually requested recovery by pressing a key at developer
@@ -91,6 +95,8 @@
 #define VBNV_RECOVERY_RW_DEV_MISMATCH 0x45
 /* Shared data error in rewritable firmware */
 #define VBNV_RECOVERY_RW_SHARED_DATA  0x46
+/* Test error from LoadKernel() */
+#define VBNV_RECOVERY_RW_TEST_LK      0x47
 /* Unspecified/unknown error in rewritable firmware */
 #define VBNV_RECOVERY_RW_UNSPECIFIED  0x7F
 /* DM-verity error */
@@ -103,6 +109,13 @@
 #define VBNV_RECOVERY_US_UNSPECIFIED  0xFF
 
 
+/* Function codes for VBNV_TEST_ERROR_FUNC */
+#define VBNV_TEST_ERROR_LOAD_FIRMWARE_SETUP  1
+#define VBNV_TEST_ERROR_LOAD_FIRMWARE        2
+#define VBNV_TEST_ERROR_LOAD_KERNEL          3
+#define VBNV_TEST_ERROR_S3_RESUME            4
+
+
 /* Initialize the NV storage library.  This must be called before any
  * other functions in this library.  Returns 0 if success, non-zero if
  * error.
diff --git a/firmware/include/vboot_struct.h b/firmware/include/vboot_struct.h
index 1e988ee..f8fd8c8 100644
--- a/firmware/include/vboot_struct.h
+++ b/firmware/include/vboot_struct.h
@@ -131,10 +131,117 @@
 
 #define EXPECTED_VBKERNELPREAMBLEHEADER_SIZE 96
 
+/* Constants and sub-structures for VbSharedDataHeader */
+
+/* Magic number for recognizing VbSharedDataHeader ("VbSD") */
+#define VB_SHARED_DATA_MAGIC 0x44536256
+
 /* Minimum and recommended size of shared_data_blob in bytes. */
 #define VB_SHARED_DATA_MIN_SIZE 3072
 #define VB_SHARED_DATA_REC_SIZE 16384
 
+/* Flags for VbSharedDataHeader */
+/* LoadFirmware() tried firmware B because of VbNvStorage firmware B tries */
+#define VBSD_FWB_TRIED                  0x00000001
+/* LoadKernel() verified the good kernel keyblock using the kernel subkey from
+ * the firmware.  If this flag is not present, it just used the hash of the
+ * kernel keyblock. */
+#define VBSD_KERNEL_KEY_VERIFIED        0x00000002
+/* LoadFirmware() was told the developer switch was on */
+#define VBSD_LF_DEV_SWITCH_ON           0x00000004
+
+/* Result codes for VbSharedDataHeader.check_fw_a_result (and b_result) */
+#define VBSD_LF_CHECK_NOT_DONE          0
+#define VBSD_LF_CHECK_DEV_MISMATCH      1
+#define VBSD_LF_CHECK_REC_MISMATCH      2
+#define VBSD_LF_CHECK_VERIFY_KEYBLOCK   3
+#define VBSD_LF_CHECK_KEY_ROLLBACK      4
+#define VBSD_LF_CHECK_DATA_KEY_PARSE    5
+#define VBSD_LF_CHECK_VERIFY_PREAMBLE   6
+#define VBSD_LF_CHECK_FW_ROLLBACK       7
+#define VBSD_LF_CHECK_HEADER_VALID      8
+#define VBSD_LF_CHECK_GET_FW_BODY       9
+#define VBSD_LF_CHECK_HASH_WRONG_SIZE   10
+#define VBSD_LF_CHECK_VERIFY_BODY       11
+#define VBSD_LF_CHECK_VALID             12
+
+/* Boot mode for VbSharedDataHeader.lk_boot_mode */
+#define VBSD_LK_BOOT_MODE_RECOVERY      0
+#define VBSD_LK_BOOT_MODE_NORMAL        1
+#define VBSD_LK_BOOT_MODE_DEVELOPER     2
+
+/* Flags for VbSharedDataKernelPart.flags */
+#define VBSD_LKP_FLAG_KEY_BLOCK_VALID   0x01
+
+/* Result codes for VbSharedDataKernelPart.check_result */
+#define VBSD_LKP_CHECK_NOT_DONE           0
+#define VBSD_LKP_CHECK_TOO_SMALL          1
+#define VBSD_LKP_CHECK_READ_START         2
+#define VBSD_LKP_CHECK_KEY_BLOCK_SIG      3
+#define VBSD_LKP_CHECK_KEY_BLOCK_HASH     4
+#define VBSD_LKP_CHECK_DEV_MISMATCH       5
+#define VBSD_LKP_CHECK_REC_MISMATCH       6
+#define VBSD_LKP_CHECK_KEY_ROLLBACK       7
+#define VBSD_LKP_CHECK_DATA_KEY_PARSE     8
+#define VBSD_LKP_CHECK_VERIFY_PREAMBLE    9
+#define VBSD_LKP_CHECK_KERNEL_ROLLBACK    10
+#define VBSD_LKP_CHECK_PREAMBLE_VALID     11
+#define VBSD_LKP_CHECK_BODY_ADDRESS       12
+#define VBSD_LKP_CHECK_BODY_OFFSET        13
+#define VBSD_LKP_CHECK_BODY_EXCEEDS_MEM   15
+#define VBSD_LKP_CHECK_BODY_EXCEEDS_PART  16
+#define VBSD_LKP_CHECK_READ_DATA          17
+#define VBSD_LKP_CHECK_VERIFY_DATA        18
+#define VBSD_LKP_CHECK_KERNEL_GOOD        19
+
+
+/* Information about a single kernel partition check in LoadKernel() */
+typedef struct VbSharedDataKernelPart {
+  uint64_t sector_start;              /* Start sector of partition */
+  uint64_t sector_count;              /* Sector count of partition */
+  uint32_t combined_version;          /* Combined key+kernel version */
+  uint8_t gpt_index;                  /* Index of partition in GPT */
+  uint8_t check_result;               /* Check result; see VBSD_LKP_CHECK_* */
+  uint8_t flags;                      /* Flags (see VBSD_LKP_FLAG_* */
+  uint8_t reserved0;                  /* Reserved for padding */
+} VbSharedDataKernelPart;
+
+/* Number of kernel partitions to track per call.  Must be power of 2. */
+#define VBSD_MAX_KERNEL_PARTS 8
+
+/* Flags for VbSharedDataKernelCall.flags */
+/* Error initializing TPM in recovery mode */
+#define VBSD_LK_FLAG_REC_TPM_INIT_ERROR 0x00000001
+
+/* Result codes for VbSharedDataKernelCall.check_result */
+#define VBSD_LKC_CHECK_NOT_DONE            0
+#define VBSD_LKC_CHECK_DEV_SWITCH_MISMATCH 1
+#define VBSD_LKC_CHECK_GPT_READ_ERROR      2
+#define VBSD_LKC_CHECK_GPT_PARSE_ERROR     3
+#define VBSD_LKC_CHECK_GOOD_PARTITION      4
+#define VBSD_LKC_CHECK_INVALID_PARTITIONS  5
+#define VBSD_LKC_CHECK_NO_PARTITIONS       6
+
+/* Information about a single call to LoadKernel() */
+typedef struct VbSharedDataKernelCall {
+  uint32_t boot_flags;                /* Bottom 32 bits of flags passed in
+                                       * LoadKernelParams.boot_flags */
+  uint32_t flags;                     /* Debug flags; see VBSD_LK_FLAG_* */
+  uint64_t sector_count;              /* Number of sectors on drive */
+  uint32_t sector_size;               /* Sector size in bytes */
+  uint8_t check_result;               /* Check result; see VBSD_LKC_CHECK_* */
+  uint8_t boot_mode;                  /* Boot mode for LoadKernel(); see
+                                       * VBSD_LK_BOOT_MODE_* constants */
+  uint8_t test_error_num;             /* Test error number, if non-zero */
+  uint8_t return_code;                /* Return code from LoadKernel() */
+  uint8_t kernel_parts_found;         /* Number of kernel partitions found */
+  uint8_t reserved0[7];               /* Reserved for padding */
+  VbSharedDataKernelPart parts[VBSD_MAX_KERNEL_PARTS]; /* Data on kernels */
+} VbSharedDataKernelCall;
+
+/* Number of kernel calls to track.  Must be power of 2. */
+#define VBSD_MAX_KERNEL_CALLS 4
+
 /* Data shared between LoadFirmware(), LoadKernel(), and OS.
  *
  * The boot process is:
@@ -149,17 +256,55 @@
  *      For example, via ACPI or ATAGs. */
 typedef struct VbSharedDataHeader {
   /* Fields present in version 1 */
+  uint32_t magic;                     /* Magic number for struct
+                                       * (VB_SHARED_DATA_MAGIC) */
   uint32_t struct_version;            /* Version of this structure */
   uint64_t struct_size;               /* Size of this structure in bytes */
   uint64_t data_size;                 /* Size of shared data buffer in bytes */
   uint64_t data_used;                 /* Amount of shared data used so far */
+  uint32_t flags;                     /* Flags */
+  uint32_t reserved0;                 /* Reserved for padding */
 
   VbPublicKey kernel_subkey;          /* Kernel subkey, from firmware */
   uint64_t kernel_subkey_data_offset; /* Offset of kernel subkey data from
                                        * start of this struct */
-  uint64_t kernel_subkey_data_size;   /* Offset of kernel subkey data */
+  uint64_t kernel_subkey_data_size;   /* Size of kernel subkey data */
 
-  uint64_t flags;                     /* Flags */
+  /* Timer values from VbGetTimer().  Unused values are set to 0.  If a
+   * function is called mutiple times, these are the times from the
+   * most recent call. */
+  uint64_t timer_load_firmware_start_enter;  /* LoadFirmwareStart() - enter */
+  uint64_t timer_load_firmware_start_exit;   /* LoadFirmwareStart() - exit */
+  uint64_t timer_load_firmware_enter;        /* LoadFirmware() - enter */
+  uint64_t timer_load_firmware_exit;         /* LoadFirmware() - exit */
+  uint64_t timer_load_kernel_enter;          /* LoadKernel() - enter */
+  uint64_t timer_load_kernel_exit;           /* LoadKernel() - exit */
+
+  /* Information stored in TPM, as retrieved by firmware */
+  uint32_t fw_version_tpm;            /* Current firmware version in TPM */
+  uint32_t kernel_version_tpm;        /* Current kernel version in TPM */
+
+  /* Debugging information from LoadFirmware() */
+  uint8_t check_fw_a_result;          /* Result of checking RW firmware A */
+  uint8_t check_fw_b_result;          /* Result of checking RW firmware B */
+  uint8_t firmware_index;             /* Firmware index returned by
+                                       * LoadFirmware() or 0xFF if failure */
+  uint8_t reserved1;                  /* Reserved for padding */
+  uint32_t fw_version_tpm_start;      /* Firmware TPM version at start of
+                                       * LoadFirmware() */
+  uint32_t fw_version_lowest;         /* Firmware lowest version found */
+
+  /* Debugging information from LoadKernel() */
+  uint32_t lk_call_count;             /* Number of times LoadKernel() called */
+  VbSharedDataKernelCall lk_calls[VBSD_MAX_KERNEL_CALLS];  /* Info on calls */
+
+  /* Offset and size of supplemental kernel data.  Reserve space for these
+   * fields now, so that future LoadKernel() versions can store information
+   * there without needing to shift down whatever data the original
+   * LoadFirmware() might have put immediately following its
+   * VbSharedDataHeader. */
+  uint64_t kernel_supplemental_offset;
+  uint64_t kernel_supplemental_size;
 
   /* After read-only firmware which uses version 1 is released, any additional
    * fields must be added below, and the struct version must be increased.
diff --git a/firmware/lib/include/rollback_index.h b/firmware/lib/include/rollback_index.h
index 0e630db..7db8515 100644
--- a/firmware/lib/include/rollback_index.h
+++ b/firmware/lib/include/rollback_index.h
@@ -94,6 +94,11 @@
  * mode. */
 uint32_t RollbackFirmwareSetup(int developer_mode, uint32_t* version);
 
+/* Read may be called to get the version.  This is not necessary in
+ * the normal boot path, because RollbackFirmwareSetup() provides the
+ * same information.  It may be used in the recovery path. */
+uint32_t RollbackFirmwareRead(uint32_t* version);
+
 /* Write may be called if the versions change */
 uint32_t RollbackFirmwareWrite(uint32_t version);
 
@@ -109,8 +114,7 @@
  * mode. */
 uint32_t RollbackKernelRecovery(int developer_mode);
 
-/* Read and write may be called if not in developer mode.  If called in
- * recovery mode, the effect is undefined. */
+/* Read and write may be called to read and write the kernel version. */
 uint32_t RollbackKernelRead(uint32_t* version);
 uint32_t RollbackKernelWrite(uint32_t version);
 
diff --git a/firmware/lib/include/tpm_bootmode.h b/firmware/lib/include/tpm_bootmode.h
new file mode 100644
index 0000000..6213cfe
--- /dev/null
+++ b/firmware/lib/include/tpm_bootmode.h
@@ -0,0 +1,25 @@
+/* Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ * Functions for updating the TPM state with the status of boot path.
+ */
+
+#ifndef VBOOT_REFERENCE_TPM_BOOTMODE_H_
+#define VBOOT_REFERENCE_TPM_BOOTMODE_H_
+
+#include "sysincludes.h"
+
+/* Update TPM PCR State with the boot path status.
+ *  [developer_mode]: State of the developer switch.
+ *  [recovery_mode}: State of the recovery mode.
+ *  [fw_keyblock_flags]: Keyblock flags on the to-be-booted
+ *                       RW firmware keyblock.
+ *
+ *  Returns: TPM_SUCCESS if the TPM extend operation succeeds.
+ */
+
+uint32_t SetTPMBootModeState(int developer_mode, int recovery_mode,
+                             int fw_keyblock_flags);
+
+#endif  /* VBOOT_REFERENCE_TPM_BOOTMODE_H_ */
diff --git a/firmware/lib/rollback_index.c b/firmware/lib/rollback_index.c
index 0d9dc8c..697bd0e 100644
--- a/firmware/lib/rollback_index.c
+++ b/firmware/lib/rollback_index.c
@@ -9,28 +9,10 @@
 #include "rollback_index.h"
 
 #include "tlcl.h"
+#include "tpm_bootmode.h"
 #include "tss_constants.h"
 #include "utility.h"
 
-/* TPM PCR to use for storing dev mode measurements */
-#define DEV_REC_MODE_PCR 0
-/* Input digests for PCR extend */
-#define DEV_OFF_REC_OFF_SHA1_DIGEST ((uint8_t*) "\x14\x89\xf9\x23\xc4\xdc\xa7" \
-                                     "\x29\x17\x8b\x3e\x32\x33\x45\x85\x50" \
-                                     "\xd8\xdd\xdf\x29") /* SHA1("\x00\x00") */
-
-#define DEV_OFF_REC_ON_SHA1_DIGEST ((uint8_t*) "\x3f\x29\x54\x64\x53\x67\x8b" \
-                                    "\x85\x59\x31\xc1\x74\xa9\x7d\x6c\x08" \
-                                    "\x94\xb8\xf5\x46") /* SHA1("\x00\x01") */
-
-#define DEV_ON_REC_OFF_SHA1_DIGEST ((uint8_t*) "\x0e\x35\x6b\xa5\x05\x63\x1f" \
-                                    "\xbf\x71\x57\x58\xbe\xd2\x7d\x50\x3f" \
-                                    "\x8b\x26\x0e\x3a") /* SHA1("\x01\x00") */
-
-#define DEV_ON_REC_ON_SHA1_DIGEST ((uint8_t*) "\x91\x59\xcb\x8b\xce\xe7\xfc" \
-                                    "\xb9\x55\x82\xf1\x40\x96\x0c\xda\xe7" \
-                                    "\x27\x88\xd3\x26") /* SHA1("\x01\x01") */
-
 static int g_rollback_recovery_mode = 0;
 
 /* disable MSVC warning on const logical expression (as in } while(0);) */
@@ -121,6 +103,15 @@
 
   VBDEBUG(("TPM: One-time initialization\n"));
 
+  /* Do a full test.  This only happens the first time the device is turned on
+   * in the factory, so performance is not an issue.  This is almost certainly
+   * not necessary, but it gives us more confidence about some code paths below
+   * that are difficult to test---specifically the ones that set lifetime
+   * flags, and are only executed once per physical TPM. */
+  result = TlclSelfTestFull();
+  if (result != TPM_SUCCESS)
+    return result;
+
   result = TlclGetPermanentFlags(&pflags);
   if (result != TPM_SUCCESS)
     return result;
@@ -200,12 +191,24 @@
   RETURN_ON_FAILURE(TlclLibInit());
 
   RETURN_ON_FAILURE(TlclStartup());
-  /* Use ContinueSelfTest rather than SelfTestFull().  It enables
-   * access to the subset of TPM commands we need in the firmware, and
-   * allows the full self test to run in paralle with firmware
-   * startup.  By the time we get to the OS, self test will have
-   * completed. */
+  /* Some TPMs start the self test automatically at power on.  In that case we
+   * don't need to call ContinueSelfTest.  On some (other) TPMs,
+   * ContinueSelfTest may block.  In that case, we definitely don't want to
+   * call it here.  For TPMs in the intersection of these two sets, we're
+   * screwed.  (In other words: TPMs that require manually starting the
+   * self-test AND block will have poor performance until we split
+   * TlclSendReceive() into Send() and Receive(), and have a state machine to
+   * control setup.)
+   *
+   * This comment is likely to become obsolete in the near future, so don't
+   * trust it.  It may have not been updated.
+   */
+#ifdef TPM_MANUAL_SELFTEST
+#ifdef TPM_BLOCKING_CONTINUESELFTEST
+#warning "lousy TPM!"
+#endif
   RETURN_ON_FAILURE(TlclContinueSelfTest());
+#endif
   result = TlclAssertPhysicalPresence();
   if (result != 0) {
     /* It is possible that the TPM was delivered with the physical presence
@@ -294,7 +297,11 @@
   TlclStartup();
   TlclContinueSelfTest();
 #endif
+  *version = 0;
+  return TPM_SUCCESS;
+}
 
+uint32_t RollbackFirmwareRead(uint32_t* version) {
   *version = 0;
   return TPM_SUCCESS;
 }
@@ -348,20 +355,21 @@
 
 uint32_t RollbackFirmwareSetup(int developer_mode, uint32_t* version) {
   RollbackSpaceFirmware rsf;
-  uint8_t out_digest[20];  /* For PCR extend output */
 
   RETURN_ON_FAILURE(SetupTPM(0, developer_mode, &rsf));
   *version = rsf.fw_versions;
   VBDEBUG(("TPM: RollbackFirmwareSetup %x\n", (int)rsf.fw_versions));
-  if (developer_mode)
-    RETURN_ON_FAILURE(TlclExtend(DEV_REC_MODE_PCR, DEV_ON_REC_OFF_SHA1_DIGEST,
-                                 out_digest));
-  else
-    RETURN_ON_FAILURE(TlclExtend(DEV_REC_MODE_PCR, DEV_OFF_REC_OFF_SHA1_DIGEST,
-                                 out_digest));
-  VBDEBUG(("TPM: RollbackFirmwareSetup dev mode PCR out_digest %02x %02x %02x "
-           "%02x\n", out_digest, out_digest+1, out_digest+2, out_digest+3));
+  return TPM_SUCCESS;
+}
 
+uint32_t RollbackFirmwareRead(uint32_t* version) {
+  RollbackSpaceFirmware rsf;
+
+  RETURN_ON_FAILURE(ReadSpaceFirmware(&rsf));
+  VBDEBUG(("TPM: RollbackFirmwareRead %x --> %x\n", (int)rsf.fw_versions,
+           (int)version));
+  *version = rsf.fw_versions;
+  VBDEBUG(("TPM: RollbackFirmwareRead %x\n", (int)rsf.fw_versions));
   return TPM_SUCCESS;
 }
 
@@ -382,7 +390,6 @@
 uint32_t RollbackKernelRecovery(int developer_mode) {
   uint32_t rvs, rve;
   RollbackSpaceFirmware rsf;
-  uint8_t out_digest[20];  /* For PCR extend output */
 
   /* In recovery mode we ignore TPM malfunctions or corruptions, and *
    * leave the TPM complelely unlocked; we call neither
@@ -390,50 +397,40 @@
    * kernel will fix the TPM (if needed) and lock it ASAP.  We leave
    * Physical Presence on in either case. */
   rvs = SetupTPM(1, developer_mode, &rsf);
-  if (developer_mode)
-    rve = TlclExtend(DEV_REC_MODE_PCR, DEV_ON_REC_ON_SHA1_DIGEST, out_digest);
-  else
-    rve = TlclExtend(DEV_REC_MODE_PCR, DEV_OFF_REC_ON_SHA1_DIGEST, out_digest);
-  VBDEBUG(("TPM: RollbackKernelRecovery dev mode PCR out_digest %02x %02x %02x "
-           "%02x\n", out_digest, out_digest+1, out_digest+2, out_digest+3));
+  rve = SetTPMBootModeState(developer_mode,
+                            1,  /* Recovery Mode Status. */
+                            0);  /* In recovery mode, there is no RW firmware
+                                  * keyblock flag. */
   return (TPM_SUCCESS == rvs) ? rve : rvs;
 }
 
 uint32_t RollbackKernelRead(uint32_t* version) {
-  if (g_rollback_recovery_mode) {
-    *version = 0;
-  } else {
-    RollbackSpaceKernel rsk;
-    uint32_t perms;
+  RollbackSpaceKernel rsk;
+  uint32_t perms;
 
-    /* Read the kernel space and verify its permissions.  If the kernel
-     * space has the wrong permission, or it doesn't contain the right
-     * identifier, we give up.  This will need to be fixed by the
-     * recovery kernel.  We have to worry about this because at any time
-     * (even with PP turned off) the TPM owner can remove and redefine a
-     * PP-protected space (but not write to it). */
-    RETURN_ON_FAILURE(ReadSpaceKernel(&rsk));
-    RETURN_ON_FAILURE(TlclGetPermissions(KERNEL_NV_INDEX, &perms));
-    if (TPM_NV_PER_PPWRITE != perms || ROLLBACK_SPACE_KERNEL_UID != rsk.uid)
-      return TPM_E_CORRUPTED_STATE;
+  /* Read the kernel space and verify its permissions.  If the kernel
+   * space has the wrong permission, or it doesn't contain the right
+   * identifier, we give up.  This will need to be fixed by the
+   * recovery kernel.  We have to worry about this because at any time
+   * (even with PP turned off) the TPM owner can remove and redefine a
+   * PP-protected space (but not write to it). */
+  RETURN_ON_FAILURE(ReadSpaceKernel(&rsk));
+  RETURN_ON_FAILURE(TlclGetPermissions(KERNEL_NV_INDEX, &perms));
+  if (TPM_NV_PER_PPWRITE != perms || ROLLBACK_SPACE_KERNEL_UID != rsk.uid)
+    return TPM_E_CORRUPTED_STATE;
 
-    *version = rsk.kernel_versions;
-    VBDEBUG(("TPM: RollbackKernelRead %x\n", (int)rsk.kernel_versions));
-  }
+  *version = rsk.kernel_versions;
+  VBDEBUG(("TPM: RollbackKernelRead %x\n", (int)rsk.kernel_versions));
   return TPM_SUCCESS;
 }
 
 uint32_t RollbackKernelWrite(uint32_t version) {
-  if (g_rollback_recovery_mode) {
-    return TPM_SUCCESS;
-  } else {
-    RollbackSpaceKernel rsk;
-    RETURN_ON_FAILURE(ReadSpaceKernel(&rsk));
-    VBDEBUG(("TPM: RollbackKernelWrite %x --> %x\n", (int)rsk.kernel_versions,
-             (int)version));
-    rsk.kernel_versions = version;
-    return WriteSpaceKernel(&rsk);
-  }
+  RollbackSpaceKernel rsk;
+  RETURN_ON_FAILURE(ReadSpaceKernel(&rsk));
+  VBDEBUG(("TPM: RollbackKernelWrite %x --> %x\n", (int)rsk.kernel_versions,
+           (int)version));
+  rsk.kernel_versions = version;
+  return WriteSpaceKernel(&rsk);
 }
 
 uint32_t RollbackKernelLock(void) {
diff --git a/firmware/lib/tpm_bootmode.c b/firmware/lib/tpm_bootmode.c
new file mode 100644
index 0000000..13371c4
--- /dev/null
+++ b/firmware/lib/tpm_bootmode.c
@@ -0,0 +1,134 @@
+/* Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ * Functions for updating the TPM state with the status of boot path.
+ */
+
+#include "tpm_bootmode.h"
+
+#include "tlcl.h"
+#include "utility.h"
+
+/* TPM PCR to use for storing boot mode measurements. */
+#define BOOT_MODE_PCR 0
+
+/* Input digests for PCR extend.
+ * These are calculated as:
+ *    SHA1("|Developer_Mode||Recovery_Mode||Keyblock_Mode|").
+ * Developer_Mode can be 0 or 1.
+ * Recovery_Mode can be 0 or 1.
+ * Keyblock flags are defined in vboot_struct.h
+ *
+ * We map them to Keyblock_Mode as follows:
+ *   -----------------------------------------
+ *   Keyblock Flags            | Keyblock Mode
+ *   -----------------------------------------
+ *   6 (Dev-signed firmware)   |     2
+ *   7 Normal-signed firmware  |     1
+ *   (anything else)           |     0
+ */
+
+const char* kBootStateSHA1Digests[] = {
+  /* SHA1("\x00\x00\x00") */
+  "\x29\xe2\xdc\xfb\xb1\x6f\x63\xbb\x02\x54\xdf\x75\x85\xa1\x5b\xb6"
+  "\xfb\x5e\x92\x7d",
+
+  /* SHA1("\x00\x00\x01") */
+  "\x25\x47\xcc\x73\x6e\x95\x1f\xa4\x91\x98\x53\xc4\x3a\xe8\x90\x86"
+  "\x1a\x3b\x32\x64",
+
+  /* SHA1("\x00\x00\x02") */
+  "\x1e\xf6\x24\x48\x2d\x62\x0e\x43\xe6\xd3\x4d\xa1\xaf\xe4\x62\x67"
+  "\xfc\x69\x5d\x9b",
+
+  /* SHA1("\x00\x01\x00") */
+  "\x62\x57\x18\x91\x21\x5b\x4e\xfc\x1c\xea\xb7\x44\xce\x59\xdd\x0b"
+  "\x66\xea\x6f\x73",
+
+  /* SHA1("\x00\x01\x01") */
+  "\xee\xe4\x47\xed\xc7\x9f\xea\x1c\xa7\xc7\xd3\x4e\x46\x32\x61\xcd"
+  "\xa4\xba\x33\x9e",
+
+  /* SHA1("\x00\x01\x02") */
+  "\x0c\x7a\x62\x3f\xd2\xbb\xc0\x5b\x06\x42\x3b\xe3\x59\xe4\x02\x1d"
+  "\x36\xe7\x21\xad",
+
+  /* SHA1("\x01\x00\x00") */
+  "\x95\x08\xe9\x05\x48\xb0\x44\x0a\x4a\x61\xe5\x74\x3b\x76\xc1\xe3"
+  "\x09\xb2\x3b\x7f",
+
+  /* SHA1("\x01\x00\x01") */
+  "\xc4\x2a\xc1\xc4\x6f\x1d\x4e\x21\x1c\x73\x5c\xc7\xdf\xad\x4f\xf8"
+  "\x39\x11\x10\xe9",
+
+  /* SHA1("\x01\x00\x02") */
+  "\xfa\x01\x0d\x26\x64\xcc\x5b\x3b\x82\xee\x48\x8f\xe2\xb9\xf5\x0f"
+  "\x49\x32\xeb\x8f",
+
+  /* SHA1("\x01\x01\x00") */
+  "\x47\xec\x8d\x98\x36\x64\x33\xdc\x00\x2e\x77\x21\xc9\xe3\x7d\x50"
+  "\x67\x54\x79\x37",
+
+  /* SHA1("\x01\x01\x01") */
+  "\x28\xd8\x6c\x56\xb3\xbf\x26\xd2\x36\x56\x9b\x8d\xc8\xc3\xf9\x1f"
+  "\x32\xf4\x7b\xc7",
+
+  /* SHA1("\x01\x01\x02") */
+  "\x12\xa3\x40\xd7\x89\x7f\xe7\x13\xfc\x8f\x02\xac\x53\x65\xb8\x6e"
+  "\xbf\x35\x31\x78",
+};
+
+#define MAX_BOOT_STATE_INDEX (sizeof(kBootStateSHA1Digests)/sizeof(char*))
+
+/* Used for PCR extend when the passed-in boot state is invalid or
+ * if there is an internal error. */
+const uint8_t kBootInvalidSHA1Digest[] = {
+  "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
+  "\xff\xff\xff\xff"
+};
+
+/* Given the boot state, return the correct SHA1 digest index for TPMExtend
+ * in kBootStateSHA1Digests[]. */
+int GetBootStateIndex(int dev_mode, int rec_mode, int keyblock_flags) {
+  int index = 0;
+
+  /* Convert keyblock flags into keyblock mode which we use to index into
+   * kBootStateSHA1Digest[]. */
+  switch(keyblock_flags) {
+    case 6:  /* KEY_BLOCK_FLAG_RECOVERY_0 | KEY_BLOCK_FLAG_DEVELOPER_1 */
+      /* Developer firmware. */
+      index = 2;
+      break;
+    case 7:  /* KEY_BLOCK_FLAG_RECOVERY_0 | KEY_BLOCK_FLAG_DEVELOPER_0
+              *  | KEY_BLOCK_FLAGS_DEVELOPER_1 */
+      index = 1;
+      break;
+    default:
+      index = 0;  /* Any other keyblock flags. */
+  };
+
+  if (rec_mode)
+    index += 3;
+  if (dev_mode)
+    index += 6;
+  return index;
+}
+
+uint32_t SetTPMBootModeState(int developer_mode, int recovery_mode,
+                             int fw_keyblock_flags) {
+  uint32_t result;
+  const uint8_t* in_digest = NULL;
+  uint8_t out_digest[20];  /* For PCR extend output. */
+  int digest_index = GetBootStateIndex(developer_mode, recovery_mode,
+                                       fw_keyblock_flags);
+
+  if (digest_index >= 0 && digest_index < MAX_BOOT_STATE_INDEX)
+    in_digest = (const uint8_t*)kBootStateSHA1Digests[digest_index];
+  else
+    in_digest = kBootInvalidSHA1Digest;  /* Internal out of bounds error. */
+  result = TlclExtend(BOOT_MODE_PCR, in_digest, out_digest);
+  VBDEBUG(("TPM: SetTPMBootModeState boot mode PCR out_digest %02x %02x %02x "
+           "%02x\n", out_digest, out_digest+1, out_digest+2, out_digest+3));
+  return result;
+}
diff --git a/firmware/lib/tpm_lite/include/tlcl_structures.h b/firmware/lib/tpm_lite/include/tlcl_structures.h
index 53e70ae..a53e2ba 100644
--- a/firmware/lib/tpm_lite/include/tlcl_structures.h
+++ b/firmware/lib/tpm_lite/include/tlcl_structures.h
@@ -64,6 +64,11 @@
 } tpm_resume_cmd = {{0x0, 0xc1, 0x0, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0x99, 0x0, 0x2, },
 };
 
+const struct s_tpm_savestate_cmd{
+  uint8_t buffer[10];
+} tpm_savestate_cmd = {{0x0, 0xc1, 0x0, 0x0, 0x0, 0xa, 0x0, 0x0, 0x0, 0x98, },
+};
+
 const struct s_tpm_startup_cmd{
   uint8_t buffer[12];
 } tpm_startup_cmd = {{0x0, 0xc1, 0x0, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0x99, 0x0, 0x1, },
diff --git a/firmware/lib/tpm_lite/tlcl.c b/firmware/lib/tpm_lite/tlcl.c
index 12742d8..f36f463 100644
--- a/firmware/lib/tpm_lite/tlcl.c
+++ b/firmware/lib/tpm_lite/tlcl.c
@@ -44,11 +44,11 @@
   return TpmCommandCode(buffer);
 }
 
-/* Sends a TPM command and gets a response.  Returns 0 if success or the TPM
- * error code if error. */
-static uint32_t TlclSendReceive(const uint8_t* request, uint8_t* response,
-                                int max_length) {
-
+/* Like TlclSendReceive below, but do not retry if NEEDS_SELFTEST or
+ * DOING_SELFTEST errors are returned.
+ */
+static uint32_t TlclSendReceiveNoRetry(const uint8_t* request,
+                                       uint8_t* response, int max_length) {
   uint32_t result;
 
 #ifdef EXTRA_LOGGING
@@ -83,6 +83,40 @@
 }
 
 
+/* Sends a TPM command and gets a response.  Returns 0 if success or the TPM
+ * error code if error. In the firmware, waits for the self test to complete
+ * if needed. In the host, reports the first error without retries. */
+static uint32_t TlclSendReceive(const uint8_t* request, uint8_t* response,
+                                int max_length) {
+  uint32_t result = TlclSendReceiveNoRetry(request, response, max_length);
+  /* When compiling for the firmware, hide command failures due to the self
+   * test not having run or completed. */
+#ifndef CHROMEOS_ENVIRONMENT
+  /* If the command fails because the self test has not completed, try it
+   * again after attempting to ensure that the self test has completed. */
+  if (result == TPM_E_NEEDS_SELFTEST || result == TPM_E_DOING_SELFTEST) {
+    result = TlclContinueSelfTest();
+    if (result != TPM_SUCCESS) {
+      return result;
+    }
+#if defined(TPM_BLOCKING_CONTINUESELFTEST) || defined(VB_RECOVERY_MODE)
+    /* Retry only once */
+    result = TlclSendReceiveNoRetry(request, response, max_length);
+#else
+    /* This needs serious testing.  The TPM specification says: "iii. The
+     * caller MUST wait for the actions of TPM_ContinueSelfTest to complete
+     * before reissuing the command C1."  But, if ContinueSelfTest is
+     * non-blocking, how do we know that the actions have completed other than
+     * trying again? */
+    do {
+      result = TlclSendReceiveNoRetry(request, response, max_length);
+    } while (result == TPM_E_DOING_SELFTEST);
+#endif
+  }
+#endif  /* ! defined(CHROMEOS_ENVIRONMENT) */
+  return result;
+}
+
 /* Sends a command and returns the error code. */
 static uint32_t Send(const uint8_t* command) {
   uint8_t response[TPM_LARGE_ENOUGH_COMMAND_SIZE];
@@ -100,6 +134,11 @@
   return Send(tpm_startup_cmd.buffer);
 }
 
+uint32_t TlclSaveState(void) {
+  VBDEBUG(("TPM: SaveState\n"));
+  return Send(tpm_savestate_cmd.buffer);
+}
+
 uint32_t TlclResume(void) {
   VBDEBUG(("TPM: Resume\n"));
   return Send(tpm_resume_cmd.buffer);
@@ -111,8 +150,11 @@
 }
 
 uint32_t TlclContinueSelfTest(void) {
+  uint8_t response[TPM_LARGE_ENOUGH_COMMAND_SIZE];
   VBDEBUG(("TPM: Continue self test\n"));
-  return Send(tpm_continueselftest_cmd.buffer);
+  /* Call the No Retry version of SendReceive to avoid recursion. */
+  return TlclSendReceiveNoRetry(tpm_continueselftest_cmd.buffer,
+                                response, sizeof(response));
 }
 
 uint32_t TlclDefineSpace(uint32_t index, uint32_t perm, uint32_t size) {
@@ -291,7 +333,8 @@
   return TlclWrite(TPM_NV_INDEX0, (uint8_t*) &x, 0);
 }
 
-uint32_t TlclExtend(int pcr_num, uint8_t* in_digest, uint8_t* out_digest) {
+uint32_t TlclExtend(int pcr_num, const uint8_t* in_digest,
+                    uint8_t* out_digest) {
   struct s_tpm_extend_cmd cmd;
   uint8_t response[kTpmResponseHeaderLength + kPcrDigestLength];
   uint32_t result;
diff --git a/firmware/lib/vboot_common.c b/firmware/lib/vboot_common.c
index abba130..edc6e16 100644
--- a/firmware/lib/vboot_common.c
+++ b/firmware/lib/vboot_common.c
@@ -381,6 +381,10 @@
 
 
 int VbSharedDataInit(VbSharedDataHeader* header, uint64_t size) {
+
+  VBDEBUG(("VbSharedDataInit, %d bytes, header %d bytes\n", (int)size,
+           sizeof(VbSharedDataHeader)));
+
   if (size < sizeof(VbSharedDataHeader)) {
     VBDEBUG(("Not enough data for header.\n"));
     return VBOOT_SHARED_DATA_INVALID;
@@ -397,10 +401,12 @@
   Memset(header, 0, sizeof(VbSharedDataHeader));
 
   /* Initialize fields */
+  header->magic = VB_SHARED_DATA_MAGIC;
   header->struct_version = VB_SHARED_DATA_VERSION;
   header->struct_size = sizeof(VbSharedDataHeader);
   header->data_size = size;
   header->data_used = sizeof(VbSharedDataHeader);
+  header->firmware_index = 0xFF;
 
   /* Success */
   return VBOOT_SUCCESS;
@@ -410,6 +416,8 @@
 uint64_t VbSharedDataReserve(VbSharedDataHeader* header, uint64_t size) {
   uint64_t offs = header->data_used;
 
+  VBDEBUG(("VbSharedDataReserve %d bytes at %d\n", (int)size, (int)offs));
+
   if (!header || size > header->data_size - header->data_used) {
     VBDEBUG(("VbSharedData buffer out of space.\n"));
     return 0;  /* Not initialized, or not enough space left. */
diff --git a/firmware/lib/vboot_firmware.c b/firmware/lib/vboot_firmware.c
index 2f35852..4be4cb2 100644
--- a/firmware/lib/vboot_firmware.c
+++ b/firmware/lib/vboot_firmware.c
@@ -9,6 +9,7 @@
 #include "gbb_header.h"
 #include "load_firmware_fw.h"
 #include "rollback_index.h"
+#include "tpm_bootmode.h"
 #include "utility.h"
 #include "vboot_common.h"
 #include "vboot_nvstorage.h"
@@ -34,6 +35,8 @@
 
 
 int LoadFirmwareSetup(void) {
+  /* TODO: handle test errors (requires passing in VbNvContext) */
+  /* TODO: record timer values (requires passing in VbSharedData) */
   /* TODO: start initializing the TPM */
   return LOAD_FIRMWARE_SUCCESS;
 }
@@ -50,7 +53,9 @@
   uint32_t tpm_version = 0;
   uint64_t lowest_version = 0xFFFFFFFF;
   uint32_t status;
+  uint32_t test_err = 0;
   int good_index = -1;
+  uint64_t boot_fw_keyblock_flags = 0;
   int is_dev;
   int index;
   int i;
@@ -72,6 +77,28 @@
     recovery = VBNV_RECOVERY_RO_SHARED_DATA;
     goto LoadFirmwareExit;
   }
+  shared->timer_load_firmware_enter = VbGetTimer();
+
+  /* Handle test errors */
+  VbNvGet(vnc, VBNV_TEST_ERROR_FUNC, &test_err);
+  if (VBNV_TEST_ERROR_LOAD_FIRMWARE == test_err) {
+    /* Get error code */
+    VbNvGet(vnc, VBNV_TEST_ERROR_NUM, &test_err);
+    /* Clear test params so we don't repeat the error */
+    VbNvSet(vnc, VBNV_TEST_ERROR_FUNC, 0);
+    VbNvSet(vnc, VBNV_TEST_ERROR_NUM, 0);
+    /* Handle error codes */
+    switch (test_err) {
+      case LOAD_FIRMWARE_RECOVERY:
+        recovery = VBNV_RECOVERY_RO_TEST_LF;
+        goto LoadFirmwareExit;
+      case LOAD_FIRMWARE_REBOOT:
+        retval = test_err;
+        goto LoadFirmwareExit;
+      default:
+        break;
+    }
+  }
 
   /* Must have a root key from the GBB */
   if (!gbb) {
@@ -82,6 +109,8 @@
 
   /* Parse flags */
   is_dev = (params->boot_flags & BOOT_FLAG_DEVELOPER ? 1 : 0);
+  if (is_dev)
+    shared->flags |= VBSD_LF_DEV_SWITCH_ON;
 
   /* Initialize the TPM and read rollback indices. */
   VBPERFSTART("VB_TPMI");
@@ -95,13 +124,16 @@
       recovery = VBNV_RECOVERY_RO_TPM_ERROR;
     goto LoadFirmwareExit;
   }
+  shared->fw_version_tpm_start = tpm_version;
+  shared->fw_version_tpm = tpm_version;
   VBPERFEND("VB_TPMI");
 
   /* Read try-b count and decrement if necessary */
   VbNvGet(vnc, VBNV_TRY_B_COUNT, &try_b_count);
-  if (0 != try_b_count)
+  if (0 != try_b_count) {
     VbNvSet(vnc, VBNV_TRY_B_COUNT, try_b_count - 1);
-  VbNvSet(vnc, VBNV_TRIED_FIRMWARE_B, try_b_count ? 1 : 0);
+    shared->flags |= VBSD_FWB_TRIED;
+  }
 
   /* Allocate our internal data */
   lfi = (VbLoadFirmwareInternal*)Malloc(sizeof(VbLoadFirmwareInternal));
@@ -119,15 +151,18 @@
     uint64_t key_version;
     uint64_t combined_version;
     uint8_t* body_digest;
+    uint8_t* check_result;
 
     /* If try B count is non-zero try firmware B first */
     index = (try_b_count ? 1 - i : i);
     if (0 == index) {
       key_block = (VbKeyBlockHeader*)params->verification_block_0;
       vblock_size = params->verification_size_0;
+      check_result = &shared->check_fw_a_result;
     } else {
       key_block = (VbKeyBlockHeader*)params->verification_block_1;
       vblock_size = params->verification_size_1;
+      check_result = &shared->check_fw_b_result;
     }
 
     /* Check the key block flags against the current boot mode.  Do this
@@ -137,11 +172,13 @@
           (is_dev ? KEY_BLOCK_FLAG_DEVELOPER_1 :
            KEY_BLOCK_FLAG_DEVELOPER_0))) {
       VBDEBUG(("Developer flag mismatch.\n"));
+      *check_result = VBSD_LF_CHECK_DEV_MISMATCH;
       continue;
     }
     /* RW firmware never runs in recovery mode. */
     if (!(key_block->key_block_flags & KEY_BLOCK_FLAG_RECOVERY_0)) {
       VBDEBUG(("Recovery flag mismatch.\n"));
+      *check_result = VBSD_LF_CHECK_REC_MISMATCH;
       continue;
     }
 
@@ -149,6 +186,7 @@
     VBPERFSTART("VB_VKB");
     if ((0 != KeyBlockVerify(key_block, vblock_size, root_key, 0))) {
       VBDEBUG(("Key block verification failed.\n"));
+      *check_result = VBSD_LF_CHECK_VERIFY_KEYBLOCK;
       VBPERFEND("VB_VKB");
       continue;
     }
@@ -158,6 +196,7 @@
     key_version = key_block->data_key.key_version;
     if (key_version < (tpm_version >> 16)) {
       VBDEBUG(("Key rollback detected.\n"));
+      *check_result = VBSD_LF_CHECK_KEY_ROLLBACK;
       continue;
     }
 
@@ -165,6 +204,7 @@
     data_key = PublicKeyToRSA(&key_block->data_key);
     if (!data_key) {
       VBDEBUG(("Unable to parse data key.\n"));
+      *check_result = VBSD_LF_CHECK_DATA_KEY_PARSE;
       continue;
     }
 
@@ -176,6 +216,7 @@
                                      vblock_size - key_block->key_block_size,
                                      data_key))) {
       VBDEBUG(("Preamble verfication failed.\n"));
+      *check_result = VBSD_LF_CHECK_VERIFY_PREAMBLE;
       RSAPublicKeyFree(data_key);
       VBPERFEND("VB_VPB");
       continue;
@@ -187,10 +228,14 @@
                         (preamble->firmware_version & 0xFFFF));
     if (combined_version < tpm_version) {
       VBDEBUG(("Firmware version rollback detected.\n"));
+      *check_result = VBSD_LF_CHECK_FW_ROLLBACK;
       RSAPublicKeyFree(data_key);
       continue;
     }
 
+    /* Header for this firmware is valid */
+    *check_result = VBSD_LF_CHECK_HEADER_VALID;
+
     /* Check for lowest key version from a valid header. */
     if (lowest_version > combined_version)
       lowest_version = combined_version;
@@ -207,6 +252,7 @@
     lfi->body_size_accum = 0;
     if (0 != GetFirmwareBody(params, index)) {
       VBDEBUG(("GetFirmwareBody() failed for index %d\n", index));
+      *check_result = VBSD_LF_CHECK_GET_FW_BODY;
       RSAPublicKeyFree(data_key);
       VBPERFEND("VB_RFD");
       continue;
@@ -215,6 +261,7 @@
       VBDEBUG(("Hash updated %d bytes but expected %d\n",
                (int)lfi->body_size_accum,
                (int)preamble->body_signature.data_size));
+      *check_result = VBSD_LF_CHECK_HASH_WRONG_SIZE;
       RSAPublicKeyFree(data_key);
       VBPERFEND("VB_RFD");
       continue;
@@ -226,6 +273,7 @@
     body_digest = DigestFinal(&lfi->body_digest_context);
     if (0 != VerifyDigest(body_digest, &preamble->body_signature, data_key)) {
       VBDEBUG(("Firmware body verification failed.\n"));
+      *check_result = VBSD_LF_CHECK_VERIFY_BODY;
       RSAPublicKeyFree(data_key);
       Free(body_digest);
       VBPERFEND("VB_VFD");
@@ -239,6 +287,7 @@
 
     /* If we're still here, the firmware is valid. */
     VBDEBUG(("Firmware %d is valid.\n", index));
+    *check_result = VBSD_LF_CHECK_VALID;
     if (-1 == good_index) {
       /* Save the key we actually used */
       if (0 != VbSharedDataSetKernelKey(shared, &preamble->kernel_subkey)) {
@@ -251,6 +300,9 @@
        * this firmware.  That's the one we'll boot. */
       good_index = index;
       params->firmware_index = index;
+      /* Since we now know which firmware to boot, we can update the
+       * bootable firmware key block mode. */
+      boot_fw_keyblock_flags = key_block->key_block_flags;
 
       /* If the good firmware's key version is the same as the tpm,
        * then the TPM doesn't need updating; we can stop now.
@@ -261,6 +313,19 @@
     }
   }
 
+  /* At this point, we have a good idea of how we are going to boot. Update the
+   * TPM with this state information.
+   */
+  status = SetTPMBootModeState(is_dev, 0, (int)boot_fw_keyblock_flags);
+  if (0 != status) {
+    VBDEBUG(("Unable to update the TPM with boot mode information.\n"));
+    if (status == TPM_E_MUST_REBOOT)
+      retval = LOAD_FIRMWARE_REBOOT;
+    else
+      recovery = VBNV_RECOVERY_RO_TPM_ERROR;
+    goto LoadFirmwareExit;
+  }
+
   /* Free internal data */
   Free(lfi);
   params->load_firmware_internal = NULL;
@@ -269,6 +334,7 @@
   if (good_index >= 0) {
 
     /* Update TPM if necessary */
+    shared->fw_version_lowest = (uint32_t)lowest_version;
     if (lowest_version > tpm_version) {
       VBPERFSTART("VB_TPMU");
       status = RollbackFirmwareWrite((uint32_t)lowest_version);
@@ -281,6 +347,7 @@
           recovery = VBNV_RECOVERY_RO_TPM_ERROR;
         goto LoadFirmwareExit;
       }
+      shared->fw_version_tpm = (uint32_t)lowest_version;
     }
 
     /* Lock firmware versions in TPM */
@@ -298,6 +365,7 @@
 
     /* Success */
     VBDEBUG(("Will boot firmware index %d\n", (int)params->firmware_index));
+    shared->firmware_index = (uint8_t)params->firmware_index;
     retval = LOAD_FIRMWARE_SUCCESS;
   } else {
     /* No good firmware, so go to recovery mode. */
@@ -311,6 +379,8 @@
           recovery : VBNV_RECOVERY_NOT_REQUESTED);
   VbNvTeardown(vnc);
 
+  shared->timer_load_firmware_exit = VbGetTimer();
+
   /* Note that we don't reduce params->shared_data_size to shared->data_used,
    * since we want to leave space for LoadKernel() to add to the shared data
    * buffer. */
@@ -320,6 +390,9 @@
 
 
 int S3Resume(void) {
+
+  /* TODO: handle test errors (requires passing in VbNvContext) */
+
   /* Resume the TPM */
   uint32_t status = RollbackS3Resume();
 
diff --git a/firmware/lib/vboot_kernel.c b/firmware/lib/vboot_kernel.c
index 3d08267..cfdd9b4 100644
--- a/firmware/lib/vboot_kernel.c
+++ b/firmware/lib/vboot_kernel.c
@@ -21,9 +21,9 @@
 #define LOWEST_TPM_VERSION 0xffffffff
 
 typedef enum BootMode {
-  kBootNormal,   /* Normal firmware */
-  kBootDev,      /* Dev firmware AND dev switch is on */
-  kBootRecovery  /* Recovery firmware, regardless of dev switch position */
+  kBootRecovery = 0,  /* Recovery firmware, regardless of dev switch position */
+  kBootNormal = 1,    /* Normal firmware */
+  kBootDev = 2        /* Dev firmware AND dev switch is on */
 } BootMode;
 
 
@@ -121,6 +121,7 @@
 
 int LoadKernel(LoadKernelParams* params) {
   VbSharedDataHeader* shared = (VbSharedDataHeader*)params->shared_data_blob;
+  VbSharedDataKernelCall* shcall = NULL;
   VbNvContext* vnc = params->nv_context;
   GoogleBinaryBlockHeader* gbb = (GoogleBinaryBlockHeader*)params->gbb_data;
   VbPublicKey* kernel_subkey;
@@ -136,13 +137,12 @@
   uint64_t lowest_version = LOWEST_TPM_VERSION;
   int rec_switch, dev_switch;
   BootMode boot_mode;
+  uint32_t test_err = 0;
   uint32_t status;
 
-  /* TODO: differentiate between finding an invalid kernel (found_partitions>0)
-   * and not finding one at all.  Right now we treat them the same, and return
-   * LOAD_KERNEL_INVALID for both. */
-  int retval = LOAD_KERNEL_INVALID;
+  int retval = LOAD_KERNEL_RECOVERY;
   int recovery = VBNV_RECOVERY_RO_UNSPECIFIED;
+  uint64_t timer_enter = VbGetTimer();
 
   /* Setup NV storage */
   VbNvSetup(vnc);
@@ -157,39 +157,24 @@
     goto LoadKernelExit;
   }
 
-  /* Initialization */
-  blba = params->bytes_per_lba;
-  kbuf_sectors = KBUF_SIZE / blba;
-  if (0 == kbuf_sectors) {
-    VBDEBUG(("LoadKernel() called with sector size > KBUF_SIZE\n"));
-    goto LoadKernelExit;
-  }
-
-  rec_switch = (BOOT_FLAG_RECOVERY & params->boot_flags ? 1 : 0);
-  dev_switch = (BOOT_FLAG_DEVELOPER & params->boot_flags ? 1 : 0);
-
-  if (rec_switch)
-    boot_mode = kBootRecovery;
-  else if (BOOT_FLAG_DEV_FIRMWARE & params->boot_flags) {
-    if (!dev_switch) {
-      /* Dev firmware should be signed such that it never boots with the dev
-       * switch is off; so something is terribly wrong. */
-      VBDEBUG(("LoadKernel() called with dev firmware but dev switch off\n"));
-      recovery = VBNV_RECOVERY_RW_DEV_MISMATCH;
-      goto LoadKernelExit;
-    }
-    boot_mode = kBootDev;
-  } else {
-    /* Normal firmware */
-    boot_mode = kBootNormal;
-    dev_switch = 0;  /* Always do a fully verified boot */
-  }
-
   /* Clear output params in case we fail */
   params->partition_number = 0;
   params->bootloader_address = 0;
   params->bootloader_size = 0;
 
+  /* Calculate switch positions and boot mode */
+  rec_switch = (BOOT_FLAG_RECOVERY & params->boot_flags ? 1 : 0);
+  dev_switch = (BOOT_FLAG_DEVELOPER & params->boot_flags ? 1 : 0);
+  if (rec_switch)
+    boot_mode = kBootRecovery;
+  else if (BOOT_FLAG_DEV_FIRMWARE & params->boot_flags)
+    boot_mode = kBootDev;
+  else {
+    /* Normal firmware */
+    boot_mode = kBootNormal;
+    dev_switch = 0;  /* Always do a fully verified boot */
+  }
+
   if (kBootRecovery == boot_mode) {
     /* Initialize the shared data structure, since LoadFirmware() didn't do it
      * for us. */
@@ -200,16 +185,82 @@
       params->shared_data_size = 0;
       shared = NULL;
     }
+  }
 
+  if (shared) {
+    /* Set up tracking for this call.  This wraps around if called many times,
+     * so we need to initialize the call entry each time. */
+    shcall = shared->lk_calls + (shared->lk_call_count
+                                 & (VBSD_MAX_KERNEL_CALLS - 1));
+    Memset(shcall, 0, sizeof(VbSharedDataKernelCall));
+    shcall->boot_flags = (uint32_t)params->boot_flags;
+    shcall->boot_mode = boot_mode;
+    shcall->sector_size = (uint32_t)params->bytes_per_lba;
+    shcall->sector_count = params->ending_lba + 1;
+    shared->lk_call_count++;
+  }
+
+  /* Handle test errors */
+  VbNvGet(vnc, VBNV_TEST_ERROR_FUNC, &test_err);
+  if (VBNV_TEST_ERROR_LOAD_KERNEL == test_err) {
+    /* Get error code */
+    VbNvGet(vnc, VBNV_TEST_ERROR_NUM, &test_err);
+    if (shcall)
+      shcall->test_error_num = (uint8_t)test_err;
+    /* Clear test params so we don't repeat the error */
+    VbNvSet(vnc, VBNV_TEST_ERROR_FUNC, 0);
+    VbNvSet(vnc, VBNV_TEST_ERROR_NUM, 0);
+    /* Handle error codes */
+    switch (test_err) {
+      case LOAD_KERNEL_RECOVERY:
+        recovery = VBNV_RECOVERY_RW_TEST_LK;
+        goto LoadKernelExit;
+      case LOAD_KERNEL_NOT_FOUND:
+      case LOAD_KERNEL_INVALID:
+      case LOAD_KERNEL_REBOOT:
+        retval = test_err;
+        goto LoadKernelExit;
+      default:
+        break;
+    }
+  }
+
+  /* Initialization */
+  blba = params->bytes_per_lba;
+  kbuf_sectors = KBUF_SIZE / blba;
+  if (0 == kbuf_sectors) {
+    VBDEBUG(("LoadKernel() called with sector size > KBUF_SIZE\n"));
+    goto LoadKernelExit;
+  }
+
+  if (kBootDev == boot_mode && !dev_switch) {
+      /* Dev firmware should be signed such that it never boots with the dev
+       * switch is off; so something is terribly wrong. */
+      VBDEBUG(("LoadKernel() called with dev firmware but dev switch off\n"));
+    if (shcall)
+      shcall->check_result = VBSD_LKC_CHECK_DEV_SWITCH_MISMATCH;
+      recovery = VBNV_RECOVERY_RW_DEV_MISMATCH;
+      goto LoadKernelExit;
+    }
+
+  if (kBootRecovery == boot_mode) {
     /* Use the recovery key to verify the kernel */
     kernel_subkey = (VbPublicKey*)((uint8_t*)gbb + gbb->recovery_key_offset);
 
     /* Let the TPM know if we're in recovery mode */
     if (0 != RollbackKernelRecovery(dev_switch)) {
       VBDEBUG(("Error setting up TPM for recovery kernel\n"));
+      if (shcall)
+        shcall->flags |= VBSD_LK_FLAG_REC_TPM_INIT_ERROR;
       /* Ignore return code, since we need to boot recovery mode to
        * fix the TPM. */
     }
+
+    /* Read the key indices from the TPM; ignore any errors */
+    if (shared) {
+      RollbackFirmwareRead(&shared->fw_version_tpm);
+      RollbackKernelRead(&shared->kernel_version_tpm);
+    }
   } else {
     /* Use the kernel subkey passed from LoadFirmware(). */
     kernel_subkey = &shared->kernel_subkey;
@@ -225,6 +276,8 @@
         recovery = VBNV_RECOVERY_RW_TPM_ERROR;
       goto LoadKernelExit;
     }
+    if (shared)
+      shared->kernel_version_tpm = tpm_version;
   }
 
   do {
@@ -233,12 +286,16 @@
     gpt.drive_sectors = params->ending_lba + 1;
     if (0 != AllocAndReadGptData(&gpt)) {
       VBDEBUG(("Unable to read GPT data\n"));
+      if (shcall)
+        shcall->check_result = VBSD_LKC_CHECK_GPT_READ_ERROR;
       break;
     }
 
     /* Initialize GPT library */
     if (GPT_SUCCESS != GptInit(&gpt)) {
       VBDEBUG(("Error parsing GPT\n"));
+      if (shcall)
+        shcall->check_result = VBSD_LKC_CHECK_GPT_PARSE_ERROR;
       break;
     }
 
@@ -249,6 +306,7 @@
 
     /* Loop over candidate kernel partitions */
     while (GPT_SUCCESS == GptNextKernelEntry(&gpt, &part_start, &part_size)) {
+      VbSharedDataKernelPart* shpart = NULL;
       VbKeyBlockHeader* key_block;
       VbKernelPreambleHeader* preamble;
       RSAPublicKey* data_key = NULL;
@@ -262,17 +320,35 @@
       VBDEBUG(("Found kernel entry at %" PRIu64 " size %" PRIu64 "\n",
               part_start, part_size));
 
+      if (shcall) {
+        /* Set up tracking for this partition.  This wraps around if called
+         * many times, so initialize the partition entry each time. */
+        shpart = shcall->parts + (shcall->kernel_parts_found
+                                 & (VBSD_MAX_KERNEL_PARTS - 1));
+        Memset(shpart, 0, sizeof(VbSharedDataKernelPart));
+        shpart->sector_start = part_start;
+        shpart->sector_count = part_size;
+        /* TODO: GPT partitions start at 1, but cgptlib starts them at 0.
+         * Adjust here, until cgptlib is fixed. */
+        shpart->gpt_index = (uint8_t)(gpt.current_kernel + 1);
+        shcall->kernel_parts_found++;
+      }
+
       /* Found at least one kernel partition. */
       found_partitions++;
 
       /* Read the first part of the kernel partition. */
       if (part_size < kbuf_sectors) {
         VBDEBUG(("Partition too small to hold kernel.\n"));
+        if (shpart)
+          shpart->check_result = VBSD_LKP_CHECK_TOO_SMALL;
         goto bad_kernel;
       }
 
       if (0 != BootDeviceReadLBA(part_start, kbuf_sectors, kbuf)) {
         VBDEBUG(("Unable to read start of partition.\n"));
+        if (shpart)
+          shpart->check_result = VBSD_LKP_CHECK_READ_START;
         goto bad_kernel;
       }
 
@@ -280,6 +356,9 @@
       key_block = (VbKeyBlockHeader*)kbuf;
       if (0 != KeyBlockVerify(key_block, KBUF_SIZE, kernel_subkey, 0)) {
         VBDEBUG(("Verifying key block signature failed.\n"));
+        if (shpart)
+          shpart->check_result = VBSD_LKP_CHECK_KEY_BLOCK_SIG;
+
         key_block_valid = 0;
 
         /* If we're not in developer mode, this kernel is bad. */
@@ -290,6 +369,8 @@
          * block is valid. */
         if (0 != KeyBlockVerify(key_block, KBUF_SIZE, kernel_subkey, 1)) {
           VBDEBUG(("Verifying key block hash failed.\n"));
+          if (shpart)
+            shpart->check_result = VBSD_LKP_CHECK_KEY_BLOCK_HASH;
           goto bad_kernel;
         }
       }
@@ -299,12 +380,16 @@
             (dev_switch ? KEY_BLOCK_FLAG_DEVELOPER_1 :
              KEY_BLOCK_FLAG_DEVELOPER_0))) {
         VBDEBUG(("Key block developer flag mismatch.\n"));
+        if (shpart)
+          shpart->check_result = VBSD_LKP_CHECK_DEV_MISMATCH;
         key_block_valid = 0;
       }
       if (!(key_block->key_block_flags &
             (rec_switch ? KEY_BLOCK_FLAG_RECOVERY_1 :
              KEY_BLOCK_FLAG_RECOVERY_0))) {
         VBDEBUG(("Key block recovery flag mismatch.\n"));
+        if (shpart)
+          shpart->check_result = VBSD_LKP_CHECK_REC_MISMATCH;
         key_block_valid = 0;
       }
 
@@ -313,6 +398,8 @@
       if (kBootRecovery != boot_mode) {
         if (key_version < (tpm_version >> 16)) {
           VBDEBUG(("Key version too old.\n"));
+          if (shpart)
+            shpart->check_result = VBSD_LKP_CHECK_KEY_ROLLBACK;
           key_block_valid = 0;
         }
       }
@@ -327,6 +414,8 @@
       data_key = PublicKeyToRSA(&key_block->data_key);
       if (!data_key) {
         VBDEBUG(("Data key bad.\n"));
+        if (shpart)
+          shpart->check_result = VBSD_LKP_CHECK_DATA_KEY_PARSE;
         goto bad_kernel;
       }
 
@@ -336,6 +425,8 @@
                                      KBUF_SIZE - key_block->key_block_size,
                                      data_key))) {
         VBDEBUG(("Preamble verification failed.\n"));
+        if (shpart)
+          shpart->check_result = VBSD_LKP_CHECK_VERIFY_PREAMBLE;
         goto bad_kernel;
       }
 
@@ -343,9 +434,13 @@
        * rollback of the kernel version. */
       combined_version = ((key_version << 16) |
                           (preamble->kernel_version & 0xFFFF));
+      if (shpart)
+        shpart->combined_version = (uint32_t)combined_version;
       if (key_block_valid && kBootRecovery != boot_mode) {
         if (combined_version < tpm_version) {
           VBDEBUG(("Kernel version too low.\n"));
+          if (shpart)
+            shpart->check_result = VBSD_LKP_CHECK_KERNEL_ROLLBACK;
           /* If we're not in developer mode, kernel version must be valid. */
           if (kBootDev != boot_mode)
             goto bad_kernel;
@@ -353,6 +448,8 @@
       }
 
       VBDEBUG(("Kernel preamble is good.\n"));
+      if (shpart)
+        shpart->check_result = VBSD_LKP_CHECK_PREAMBLE_VALID;
 
       /* Check for lowest version from a valid header. */
       if (key_block_valid && lowest_version > combined_version)
@@ -372,6 +469,8 @@
       if ((preamble->body_load_address != (size_t)params->kernel_buffer) &&
           !(params->boot_flags & BOOT_FLAG_SKIP_ADDR_CHECK)) {
         VBDEBUG(("Wrong body load address.\n"));
+        if (shpart)
+          shpart->check_result = VBSD_LKP_CHECK_BODY_ADDRESS;
         goto bad_kernel;
       }
 
@@ -379,6 +478,8 @@
       body_offset = key_block->key_block_size + preamble->preamble_size;
       if (0 != body_offset % blba) {
         VBDEBUG(("Kernel body not at multiple of sector size.\n"));
+        if (shpart)
+          shpart->check_result = VBSD_LKP_CHECK_BODY_OFFSET;
         goto bad_kernel;
       }
       body_offset_sectors = body_offset / blba;
@@ -387,12 +488,16 @@
       body_sectors = (preamble->body_signature.data_size + blba - 1) / blba;
       if (body_sectors * blba > params->kernel_buffer_size) {
         VBDEBUG(("Kernel body doesn't fit in memory.\n"));
+        if (shpart)
+          shpart->check_result = VBSD_LKP_CHECK_BODY_EXCEEDS_MEM;
         goto bad_kernel;
       }
 
       /* Verify kernel body fits in the partition */
       if (body_offset_sectors + body_sectors > part_size) {
         VBDEBUG(("Kernel body doesn't fit in partition.\n"));
+        if (shpart)
+          shpart->check_result = VBSD_LKP_CHECK_BODY_EXCEEDS_PART;
         goto bad_kernel;
       }
 
@@ -403,6 +508,8 @@
                                  params->kernel_buffer)) {
         VBDEBUG(("Unable to read kernel data.\n"));
         VBPERFEND("VB_RKD");
+        if (shpart)
+          shpart->check_result = VBSD_LKP_CHECK_READ_DATA;
         goto bad_kernel;
       }
       VBPERFEND("VB_RKD");
@@ -412,6 +519,8 @@
                           params->kernel_buffer_size,
                           &preamble->body_signature, data_key)) {
         VBDEBUG(("Kernel data verification failed.\n"));
+        if (shpart)
+          shpart->check_result = VBSD_LKP_CHECK_VERIFY_DATA;
         goto bad_kernel;
       }
 
@@ -422,6 +531,12 @@
       /* If we're still here, the kernel is valid. */
       /* Save the first good partition we find; that's the one we'll boot */
       VBDEBUG(("Partition is good.\n"));
+      if (shpart) {
+        shpart->check_result = VBSD_LKP_CHECK_KERNEL_GOOD;
+        if (key_block_valid)
+          shpart->flags |= VBSD_LKP_FLAG_KEY_BLOCK_VALID;
+      }
+
       good_partition_key_block_valid = key_block_valid;
       /* TODO: GPT partitions start at 1, but cgptlib starts them at 0.
        * Adjust here, until cgptlib is fixed. */
@@ -478,6 +593,8 @@
   /* Handle finding a good partition */
   if (good_partition >= 0) {
     VBDEBUG(("Good_partition >= 0\n"));
+    if (shcall)
+      shcall->check_result = VBSD_LKC_CHECK_GOOD_PARTITION;
 
     /* See if we need to update the TPM */
     if (kBootRecovery != boot_mode && good_partition_key_block_valid) {
@@ -499,6 +616,8 @@
             recovery = VBNV_RECOVERY_RW_TPM_ERROR;
           goto LoadKernelExit;
         }
+        if (shared)
+          shared->kernel_version_tpm = (uint32_t)lowest_version;
       }
     }
 
@@ -518,21 +637,39 @@
 
     /* Success! */
     retval = LOAD_KERNEL_SUCCESS;
+  } else {
+    if (shcall)
+      shcall->check_result = (found_partitions > 0
+                              ? VBSD_LKC_CHECK_INVALID_PARTITIONS
+                              : VBSD_LKC_CHECK_NO_PARTITIONS);
+
+    /* TODO: differentiate between finding an invalid kernel
+     * (found_partitions>0) and not finding one at all.  Right now we
+     * treat them the same, and return LOAD_KERNEL_INVALID for both. */
+    retval = LOAD_KERNEL_INVALID;
   }
 
 LoadKernelExit:
 
-  /* Save whether the good partition's key block was fully verified */
-  VbNvSet(vnc, VBNV_FW_VERIFIED_KERNEL_KEY, good_partition_key_block_valid);
-
   /* Store recovery request, if any, then tear down non-volatile storage */
   VbNvSet(vnc, VBNV_RECOVERY_REQUEST, LOAD_KERNEL_RECOVERY == retval ?
           recovery : VBNV_RECOVERY_NOT_REQUESTED);
   VbNvTeardown(vnc);
 
-  /* Store how much shared data we used, if any */
-  if (shared)
+  if (shared) {
+    if (shcall)
+      shcall->return_code = (uint8_t)retval;
+
+    /* Save whether the good partition's key block was fully verified */
+    if (good_partition_key_block_valid)
+      shared->flags |= VBSD_KERNEL_KEY_VERIFIED;
+
+    /* Save timer values */
+    shared->timer_load_kernel_enter = timer_enter;
+    shared->timer_load_kernel_exit = VbGetTimer();
+    /* Store how much shared data we used, if any */
     params->shared_data_size = shared->data_used;
+  }
 
   return retval;
 }
diff --git a/firmware/lib/vboot_nvstorage.c b/firmware/lib/vboot_nvstorage.c
index 419a9fb..575fcb9 100644
--- a/firmware/lib/vboot_nvstorage.c
+++ b/firmware/lib/vboot_nvstorage.c
@@ -27,8 +27,9 @@
 #define LOCALIZATION_OFFSET          3
 
 #define FIRMWARE_FLAGS_OFFSET        5
-#define FIRMWARE_TRIED_FIRMWARE_B       0x80
-#define FIRMWARE_FW_VERIFIED_KERNEL_KEY 0x40
+#define FIRMWARE_TEST_ERR_FUNC_MASK     0x38
+#define FIRMWARE_TEST_ERR_FUNC_SHIFT    3
+#define FIRMWARE_TEST_ERR_NUM_MASK      0x07
 
 #define KERNEL_FIELD_OFFSET         11
 #define CRC_OFFSET                  15
@@ -125,13 +126,13 @@
                | (raw[KERNEL_FIELD_OFFSET + 3] << 24));
       return 0;
 
-    case VBNV_TRIED_FIRMWARE_B:
-      *dest = (raw[FIRMWARE_FLAGS_OFFSET] & FIRMWARE_TRIED_FIRMWARE_B ? 1 : 0);
+    case VBNV_TEST_ERROR_FUNC:
+      *dest = (raw[FIRMWARE_FLAGS_OFFSET] & FIRMWARE_TEST_ERR_FUNC_MASK)
+          >> FIRMWARE_TEST_ERR_FUNC_SHIFT;
       return 0;
 
-    case VBNV_FW_VERIFIED_KERNEL_KEY:
-      *dest = (raw[FIRMWARE_FLAGS_OFFSET] & FIRMWARE_FW_VERIFIED_KERNEL_KEY ?
-               1 : 0);
+    case VBNV_TEST_ERROR_NUM:
+      *dest = raw[FIRMWARE_FLAGS_OFFSET] & FIRMWARE_TEST_ERR_NUM_MASK;
       return 0;
 
     default:
@@ -201,18 +202,15 @@
       raw[KERNEL_FIELD_OFFSET + 3] = (uint8_t)(value >> 24);
       break;
 
-    case VBNV_TRIED_FIRMWARE_B:
-      if (value)
-        raw[FIRMWARE_FLAGS_OFFSET] |= FIRMWARE_TRIED_FIRMWARE_B;
-      else
-        raw[FIRMWARE_FLAGS_OFFSET] &= ~FIRMWARE_TRIED_FIRMWARE_B;
+    case VBNV_TEST_ERROR_FUNC:
+      raw[FIRMWARE_FLAGS_OFFSET] &= ~FIRMWARE_TEST_ERR_FUNC_MASK;
+      raw[FIRMWARE_FLAGS_OFFSET] |= (value << FIRMWARE_TEST_ERR_FUNC_SHIFT)
+          & FIRMWARE_TEST_ERR_FUNC_MASK;
       break;
 
-    case VBNV_FW_VERIFIED_KERNEL_KEY:
-      if (value)
-        raw[FIRMWARE_FLAGS_OFFSET] |= FIRMWARE_FW_VERIFIED_KERNEL_KEY;
-      else
-        raw[FIRMWARE_FLAGS_OFFSET] &= ~FIRMWARE_FW_VERIFIED_KERNEL_KEY;
+    case VBNV_TEST_ERROR_NUM:
+      raw[FIRMWARE_FLAGS_OFFSET] &= ~FIRMWARE_TEST_ERR_NUM_MASK;
+      raw[FIRMWARE_FLAGS_OFFSET] |= (value & FIRMWARE_TEST_ERR_NUM_MASK);
       break;
 
     default:
diff --git a/firmware/linktest/main.c b/firmware/linktest/main.c
index 6f5b83e..a3ed21a 100644
--- a/firmware/linktest/main.c
+++ b/firmware/linktest/main.c
@@ -8,6 +8,7 @@
 #include "load_kernel_fw.h"
 #include "rollback_index.h"
 #include "tlcl.h"
+#include "tpm_bootmode.h"
 #include "vboot_common.h"
 #include "vboot_kernel.h"
 #include "vboot_nvstorage.h"
@@ -30,6 +31,7 @@
   /* rollback_index.h */
   RollbackS3Resume();
   RollbackFirmwareSetup(0, 0);
+  RollbackFirmwareRead(0);
   RollbackFirmwareWrite(0);
   RollbackFirmwareLock();
   RollbackKernelRecovery(0);
@@ -37,6 +39,9 @@
   RollbackKernelWrite(0);
   RollbackKernelLock();
 
+  /* tpm_bootmode.c */
+  SetTPMBootModeState(0, 0, 0);
+
   /* tlcl.h */
   TlclLibInit();
   TlclCloseDevice();
diff --git a/firmware/stub/load_firmware_stub.c b/firmware/stub/load_firmware_stub.c
index 0b0671a..26ce3d5 100644
--- a/firmware/stub/load_firmware_stub.c
+++ b/firmware/stub/load_firmware_stub.c
@@ -99,8 +99,8 @@
   p.nv_context = &vnc;
 
   /* Allocate a shared data buffer */
-  p.shared_data_blob = Malloc(LOAD_FIRMWARE_SHARED_DATA_REC_SIZE);
-  p.shared_data_size = LOAD_FIRMWARE_SHARED_DATA_REC_SIZE;
+  p.shared_data_blob = Malloc(VB_SHARED_DATA_REC_SIZE);
+  p.shared_data_size = VB_SHARED_DATA_REC_SIZE;
 
   /* TODO: YOU NEED TO SET THE BOOT FLAGS SOMEHOW */
   p.boot_flags = 0;
diff --git a/firmware/stub/utility_stub.c b/firmware/stub/utility_stub.c
index 6141785..2d56f72 100644
--- a/firmware/stub/utility_stub.c
+++ b/firmware/stub/utility_stub.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+/* Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file.
  *
@@ -13,6 +13,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/time.h>
 
 void error(const char *format, ...) {
   va_list ap;
@@ -51,3 +52,13 @@
 void* Memcpy(void* dest, const void* src, uint64_t n) {
   return memcpy(dest, src, (size_t)n);
 }
+
+uint64_t VbGetTimer(void) {
+  struct timeval tv;
+  gettimeofday(&tv, NULL);
+  return (uint64_t)tv.tv_sec * 1000000 + (uint64_t)tv.tv_usec;
+}
+
+uint64_t VbGetTimerMaxFreq(void) {
+  return UINT64_C(1000000);
+}
diff --git a/host/lib/crossystem.c b/host/lib/crossystem.c
index 04dc975..5c5dec0 100644
--- a/host/lib/crossystem.c
+++ b/host/lib/crossystem.c
@@ -5,6 +5,10 @@
 
 #include <stdio.h>
 #include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <ctype.h>
 
 #include "host_common.h"
 
@@ -12,6 +16,7 @@
 #include "utility.h"
 #include "vboot_common.h"
 #include "vboot_nvstorage.h"
+#include "vboot_struct.h"
 
 /* ACPI constants from Chrome OS Main Processor Firmware Spec */
 /* GPIO signal types */
@@ -68,6 +73,7 @@
 #define ACPI_FMAP_PATH ACPI_BASE_PATH "/FMAP"
 #define ACPI_GPIO_PATH ACPI_BASE_PATH "/GPIO"
 #define ACPI_VBNV_PATH ACPI_BASE_PATH "/VBNV"
+#define ACPI_VDAT_PATH ACPI_BASE_PATH "/VDAT"
 
 /* Base name for GPIO files */
 #define GPIO_BASE_PATH "/sys/class/gpio"
@@ -79,6 +85,31 @@
 /* Filename for kernel command line */
 #define KERNEL_CMDLINE_PATH "/proc/cmdline"
 
+/* A structure to contain buffer data retrieved from the ACPI. */
+typedef struct {
+  int buffer_size;
+  uint8_t* buffer;
+} AcpiBuffer;
+
+
+/* Fields that GetVdatString() can get */
+typedef enum VdatStringField {
+  VDAT_STRING_TIMERS = 0,           /* Timer values */
+  VDAT_STRING_LOAD_FIRMWARE_DEBUG,  /* LoadFirmware() debug information */
+  VDAT_STRING_LOAD_KERNEL_DEBUG     /* LoadKernel() debug information */
+} VdatStringField;
+
+
+/* Fields that GetVdatInt() can get */
+typedef enum VdatIntField {
+  VDAT_INT_FLAGS = 0,                /* Flags */
+  VDAT_INT_FW_VERSION_TPM,           /* Current firmware version in TPM */
+  VDAT_INT_KERNEL_VERSION_TPM,       /* Current kernel version in TPM */
+  VDAT_INT_TRIED_FIRMWARE_B,         /* Tried firmware B due to fwb_tries */
+  VDAT_INT_KERNEL_KEY_VERIFIED       /* Kernel key verified using
+                                      * signature, not just hash */
+} VdatIntField;
+
 
 /* Copy up to dest_size-1 characters from src to dest, ensuring null
    termination (which strncpy() doesn't do).  Returns the destination
@@ -284,6 +315,97 @@
   return 0;
 }
 
+/*
+ * Get buffer data from ACPI.
+ *
+ * Buffer data is expected to be represented by a file which is a text dump of
+ * the buffer, representing each byte by two hex numbers, space and newline
+ * separated.
+ *
+ * Input - ACPI file name to get data from.
+ *
+ * Output: a pointer to AcpiBuffer structure containing the binary
+ *         representation of the data. The caller is responsible for
+ *         deallocating the pointer, this will take care of both the structure
+ *         and the buffer. Null in case of error.
+ */
+
+AcpiBuffer* VbGetBuffer(const char* filename)
+{
+  FILE* f = NULL;
+  char* file_buffer = NULL;
+  AcpiBuffer* acpi_buffer = NULL;
+  AcpiBuffer* return_value = NULL;
+
+  do {
+    struct stat fs;
+    uint8_t* output_ptr;
+    int rv, i, real_size;
+
+    rv = stat(filename, &fs);
+    if (rv || !S_ISREG(fs.st_mode))
+      break;
+
+    f = fopen(filename, "r");
+    if (!f)
+      break;
+
+    file_buffer = Malloc(fs.st_size + 1);
+    if (!file_buffer)
+      break;
+
+    real_size = fread(file_buffer, 1, fs.st_size, f);
+    if (!real_size)
+      break;
+    file_buffer[real_size] = '\0';
+
+    /* Each byte in the output will replace two characters and a space
+     * in the input, so the output size does not exceed input side/3
+     * (a little less if account for newline characters). */
+    acpi_buffer = Malloc(sizeof(AcpiBuffer) + real_size/3);
+    if (!acpi_buffer)
+      break;
+    acpi_buffer->buffer = (uint8_t*)(acpi_buffer + 1);
+    acpi_buffer->buffer_size = 0;
+    output_ptr = acpi_buffer->buffer;
+
+    /* process the file contents */
+    for (i = 0; i < real_size; i++) {
+      char* base, *end;
+
+      base = file_buffer + i;
+
+      if (!isxdigit(*base))
+        continue;
+
+      output_ptr[acpi_buffer->buffer_size++] = strtol(base, &end, 16) & 0xff;
+
+      if ((end - base) != 2)
+        /* Input file format error */
+        break;
+
+      i += 2; /* skip the second character and the following space */
+    }
+
+    if (i == real_size) {
+      /* all is well */
+      return_value = acpi_buffer;
+      acpi_buffer = NULL; /* prevent it from deallocating */
+    }
+  } while(0);
+
+  /* wrap up */
+  if (f)
+    fclose(f);
+
+  if (file_buffer)
+    Free(file_buffer);
+
+  if (acpi_buffer)
+    Free(acpi_buffer);
+
+  return return_value;
+}
 
 /* Read an integer property from VbNvStorage.
  *
@@ -497,6 +619,193 @@
 }
 
 
+char* GetVdatLoadFirmwareDebug(char* dest, int size,
+                               const VbSharedDataHeader* sh) {
+  snprintf(dest, size,
+           "Check A result=%d\n"
+           "Check B result=%d\n"
+           "Firmware index booted=0x%02x\n"
+           "TPM combined version at start=0x%08x\n"
+           "Lowest combined version from firmware=0x%08x\n",
+           sh->check_fw_a_result,
+           sh->check_fw_b_result,
+           sh->firmware_index,
+           sh->fw_version_tpm_start,
+           sh->fw_version_lowest);
+  return dest;
+}
+
+
+#define TRUNCATED "\n(truncated)\n"
+
+char* GetVdatLoadKernelDebug(char* dest, int size,
+                             const VbSharedDataHeader* sh) {
+  int used = 0;
+  int first_call_tracked = 0;
+  int call;
+
+  /* Make sure we have space for truncation warning */
+  if (size < strlen(TRUNCATED) + 1)
+    return NULL;
+  size -= strlen(TRUNCATED) + 1;
+
+  used += snprintf(
+      dest + used, size - used,
+      "Calls to LoadKernel()=%d\n",
+      sh->lk_call_count);
+  if (used > size)
+    goto LoadKernelDebugExit;
+
+  /* Report on the last calls */
+  if (sh->lk_call_count > VBSD_MAX_KERNEL_CALLS)
+    first_call_tracked = sh->lk_call_count - VBSD_MAX_KERNEL_CALLS;
+  for (call = first_call_tracked; call < sh->lk_call_count; call++) {
+    const VbSharedDataKernelCall* shc =
+        sh->lk_calls + (call & (VBSD_MAX_KERNEL_CALLS - 1));
+    int first_part_tracked = 0;
+    int part;
+
+    used += snprintf(
+        dest + used, size - used,
+        "Call %d:\n"
+        "  Boot flags=0x%02x\n"
+        "  Boot mode=%d\n"
+        "  Test error=%d\n"
+        "  Return code=%d\n"
+        "  Debug flags=0x%02x\n"
+        "  Drive sectors=%" PRIu64 "\n"
+        "  Sector size=%d\n"
+        "  Check result=%d\n"
+        "  Kernel partitions found=%d\n",
+        call + 1,
+        shc->boot_flags,
+        shc->boot_mode,
+        shc->test_error_num,
+        shc->return_code,
+        shc->flags,
+        shc->sector_count,
+        shc->sector_size,
+        shc->check_result,
+        shc->kernel_parts_found);
+    if (used > size)
+      goto LoadKernelDebugExit;
+
+    /* If we found too many partitions, only prints ones where the
+     * structure has info. */
+    if (shc->kernel_parts_found > VBSD_MAX_KERNEL_PARTS)
+      first_part_tracked = shc->kernel_parts_found - VBSD_MAX_KERNEL_PARTS;
+
+    /* Report on the partitions checked */
+    for (part = first_part_tracked; part < shc->kernel_parts_found; part++) {
+      const VbSharedDataKernelPart* shp =
+          shc->parts + (part & (VBSD_MAX_KERNEL_PARTS - 1));
+
+      used += snprintf(
+          dest + used, size - used,
+          "  Kernel %d:\n"
+          "    GPT index=%d\n"
+          "    Start sector=%" PRIu64 "\n"
+          "    Sector count=%" PRIu64 "\n"
+          "    Combined version=0x%08x\n"
+          "    Check result=%d\n"
+          "    Debug flags=0x%02x\n",
+          part + 1,
+          shp->gpt_index,
+          shp->sector_start,
+          shp->sector_count,
+          shp->combined_version,
+          shp->check_result,
+          shp->flags);
+      if (used > size)
+        goto LoadKernelDebugExit;
+    }
+  }
+
+LoadKernelDebugExit:
+
+  /* Warn if data was truncated; we left space for this above. */
+  if (used > size)
+    strcat(dest, TRUNCATED);
+
+  return dest;
+}
+
+
+char* GetVdatString(char* dest, int size, VdatStringField field)
+{
+  VbSharedDataHeader* sh;
+  AcpiBuffer* ab = VbGetBuffer(ACPI_VDAT_PATH);
+  char* value = dest;
+  if (!ab)
+    return NULL;
+
+  sh = (VbSharedDataHeader*)ab->buffer;
+
+  switch (field) {
+    case VDAT_STRING_TIMERS:
+      snprintf(dest, size,
+               "LFS=%" PRIu64 ",%" PRIu64
+               " LF=%" PRIu64 ",%" PRIu64
+               " LK=%" PRIu64 ",%" PRIu64,
+               sh->timer_load_firmware_start_enter,
+               sh->timer_load_firmware_start_exit,
+               sh->timer_load_firmware_enter,
+               sh->timer_load_firmware_exit,
+               sh->timer_load_kernel_enter,
+               sh->timer_load_kernel_exit);
+      break;
+
+    case VDAT_STRING_LOAD_FIRMWARE_DEBUG:
+      value = GetVdatLoadFirmwareDebug(dest, size, sh);
+      break;
+
+    case VDAT_STRING_LOAD_KERNEL_DEBUG:
+      value = GetVdatLoadKernelDebug(dest, size, sh);
+      break;
+
+    default:
+      Free(ab);
+      return NULL;
+  }
+
+  Free(ab);
+  return value;
+}
+
+
+int GetVdatInt(VdatIntField field) {
+  VbSharedDataHeader* sh;
+  AcpiBuffer* ab = VbGetBuffer(ACPI_VDAT_PATH);
+  int value = -1;
+
+  if (!ab)
+    return -1;
+
+  sh = (VbSharedDataHeader*)ab->buffer;
+
+  switch (field) {
+    case VDAT_INT_FLAGS:
+      value = (int)sh->flags;
+      break;
+    case VDAT_INT_FW_VERSION_TPM:
+      value = (int)sh->fw_version_tpm;
+      break;
+    case VDAT_INT_KERNEL_VERSION_TPM:
+      value = (int)sh->kernel_version_tpm;
+      break;
+    case VDAT_INT_TRIED_FIRMWARE_B:
+      value = (sh->flags & VBSD_FWB_TRIED ? 1 : 0);
+      break;
+    case VDAT_INT_KERNEL_KEY_VERIFIED:
+      value = (sh->flags & VBSD_KERNEL_KEY_VERIFIED ? 1 : 0);
+      break;
+  }
+
+  Free(ab);
+  return value;
+}
+
+
 /* Read a system property integer.
  *
  * Returns the property value, or -1 if error. */
@@ -531,12 +840,14 @@
     return (-1 == ReadFileInt(ACPI_CHSW_PATH) ? -1 : 0x00100000);
   }
   /* NV storage values with no defaults for older BIOS. */
-  else if (!strcasecmp(name,"tried_fwb")) {
-    value = VbGetNvStorage(VBNV_TRIED_FIRMWARE_B);
-  } else if (!strcasecmp(name,"kern_nv")) {
+  else if (!strcasecmp(name,"kern_nv")) {
     value = VbGetNvStorage(VBNV_KERNEL_FIELD);
   } else if (!strcasecmp(name,"nvram_cleared")) {
     value = VbGetNvStorage(VBNV_KERNEL_SETTINGS_RESET);
+  } else if (!strcasecmp(name,"vbtest_errfunc")) {
+    value = VbGetNvStorage(VBNV_TEST_ERROR_FUNC);
+  } else if (!strcasecmp(name,"vbtest_errno")) {
+    value = VbGetNvStorage(VBNV_TEST_ERROR_NUM);
   }
   /* NV storage values.  If unable to get from NV storage, fall back to the
    * CMOS reboot field used by older BIOS. */
@@ -560,12 +871,19 @@
     value = ReadFileInt(ACPI_FMAP_PATH);
   } else if (!strcasecmp(name,"cros_debug")) {
     value = VbGetCrosDebug();
+  } else if (!strcasecmp(name,"vdat_flags")) {
+    value = GetVdatInt(VDAT_INT_FLAGS);
+  } else if (!strcasecmp(name,"tpm_fwver")) {
+    value = GetVdatInt(VDAT_INT_FW_VERSION_TPM);
+  } else if (!strcasecmp(name,"tpm_kernver")) {
+    value = GetVdatInt(VDAT_INT_KERNEL_VERSION_TPM);
+  } else if (!strcasecmp(name,"tried_fwb")) {
+    value = GetVdatInt(VDAT_INT_TRIED_FIRMWARE_B);
   }
 
   return value;
 }
 
-
 /* Read a system property string into a destination buffer of the specified
  * size.
  *
@@ -601,7 +919,7 @@
         return NULL;
     }
   } else if (!strcasecmp(name,"kernkey_vfy")) {
-    switch(VbGetNvStorage(VBNV_FW_VERIFIED_KERNEL_KEY)) {
+    switch(GetVdatInt(VDAT_INT_KERNEL_KEY_VERIFIED)) {
       case 0:
         return "hash";
       case 1:
@@ -609,6 +927,12 @@
       default:
         return NULL;
     }
+  } else if (!strcasecmp(name, "vdat_timers")) {
+    return GetVdatString(dest, size, VDAT_STRING_TIMERS);
+  } else if (!strcasecmp(name, "vdat_lfdebug")) {
+    return GetVdatString(dest, size, VDAT_STRING_LOAD_FIRMWARE_DEBUG);
+  } else if (!strcasecmp(name, "vdat_lkdebug")) {
+    return GetVdatString(dest, size, VDAT_STRING_LOAD_KERNEL_DEBUG);
   } else
     return NULL;
 }
@@ -625,6 +949,10 @@
     return VbSetNvStorage(VBNV_KERNEL_SETTINGS_RESET, 0);
   } else if (!strcasecmp(name,"kern_nv")) {
     return VbSetNvStorage(VBNV_KERNEL_FIELD, value);
+  } else if (!strcasecmp(name,"vbtest_errfunc")) {
+    return VbSetNvStorage(VBNV_TEST_ERROR_FUNC, value);
+  } else if (!strcasecmp(name,"vbtest_errno")) {
+    return VbSetNvStorage(VBNV_TEST_ERROR_NUM, value);
   }
   /* NV storage values.  If unable to get from NV storage, fall back to the
    * CMOS reboot field used by older BIOS. */
diff --git a/tests/tpm_lite/timing.c b/tests/tpm_lite/timing.c
index 9300d0c..677b533 100644
--- a/tests/tpm_lite/timing.c
+++ b/tests/tpm_lite/timing.c
@@ -21,7 +21,7 @@
 /* Runs [op] and ensures it returns success and doesn't run longer than
  * [time_limit] in milliseconds.
  */
-#define TTPM_CHECK(op, time_limit) do {                                     \
+#define TTPM_CHECK(op, time_limit) do {                                 \
     struct timeval before, after;                                       \
     int time;                                                           \
     uint32_t __result;                                                  \
@@ -29,7 +29,7 @@
     __result = op;                                                      \
     if (__result != TPM_SUCCESS) {                                      \
       printf(#op ": error 0x%x\n", __result);                           \
-      exit(1);                                                          \
+      errors++;                                                          \
     }                                                                   \
     gettimeofday(&after, NULL);                                         \
     time = (int) ((after.tv_sec - before.tv_sec) * 1000 +               \
@@ -37,15 +37,18 @@
     printf(#op ": %d ms\n", time);                                      \
     if (time > time_limit) {                                            \
       printf(#op " exceeded " #time_limit " ms\n");                     \
-      exit(1);                                                          \
+      time_limit_exceeded = 1;                                          \
     }                                                                   \
   } while (0)
 
 int main(int argc, char** argv) {
   uint32_t x;
   uint8_t in[20], out[20];
+  int time_limit_exceeded = 0;
+  int errors = 0;
 
   TlclLibInit();
+  TTPM_CHECK(0, 50);
   TTPM_CHECK(TlclStartupIfNeeded(), 50);
   TTPM_CHECK(TlclContinueSelfTest(), 100);
   TTPM_CHECK(TlclSelfTestFull(), 1000);
@@ -55,6 +58,11 @@
   TTPM_CHECK(TlclExtend(0, in, out), 200);
   TTPM_CHECK(TlclSetGlobalLock(), 50);
   TTPM_CHECK(TlclLockPhysicalPresence(), 100);
-  printf("TEST SUCCEEDED\n");
-  return 0;
+  if (time_limit_exceeded || errors > 0) {
+    printf("TEST FAILED\n");
+    exit(1);
+  } else {
+    printf("TEST SUCCEEDED\n");
+    return 0;
+  }
 }
diff --git a/tests/vboot_nvstorage_test.c b/tests/vboot_nvstorage_test.c
index 40fa689..5306a64 100644
--- a/tests/vboot_nvstorage_test.c
+++ b/tests/vboot_nvstorage_test.c
@@ -29,8 +29,8 @@
   {VBNV_RECOVERY_REQUEST, 0, 0x42, 0xED, "recovery request"},
   {VBNV_LOCALIZATION_INDEX, 0, 0x69, 0xB0, "localization index"},
   {VBNV_KERNEL_FIELD, 0, 0x12345678, 0xFEDCBA98, "kernel field"},
-  {VBNV_TRIED_FIRMWARE_B, 0, 1, 0, "tried firmware B"},
-  {VBNV_FW_VERIFIED_KERNEL_KEY, 0, 1, 0, "firmware verified kernel key"},
+  {VBNV_TEST_ERROR_FUNC, 0, 1, 7, "verified boot test error func"},
+  {VBNV_TEST_ERROR_NUM, 0, 3, 6, "verified boot test error number"},
   {0, 0, 0, 0, NULL}
 };
 
diff --git a/utility/crossystem_main.c b/utility/crossystem_main.c
index 90ad718..53166e4 100644
--- a/utility/crossystem_main.c
+++ b/utility/crossystem_main.c
@@ -11,10 +11,18 @@
 
 #include "crossystem.h"
 
+/* Max length of a string parameter */
+#define MAX_STRING 8192
+
+/* Flags for Param */
+#define IS_STRING      0x01  /* String (not present = integer) */
+#define CAN_WRITE      0x02  /* Writable (not present = read-only */
+#define NO_PRINT_ALL   0x04  /* Don't print contents of parameter when
+                              * doing a print-all */
+
 typedef struct Param {
   const char* name;  /* Parameter name */
-  int is_string;     /* 0 if integer, 1 if string */
-  int can_write;     /* 0 if read-only, 1 if writable */
+  int flags;         /* Flags (see above) */
   const char* desc;  /* Human-readable description */
   const char* format; /* Format string, if non-NULL and 0==is_string*/
 } Param;
@@ -22,40 +30,46 @@
 /* List of parameters, terminated with a param with NULL name */
 const Param sys_param_list[] = {
   /* Read-only integers */
-  {"devsw_cur",  0, 0, "Developer switch current position"},
-  {"devsw_boot", 0, 0, "Developer switch position at boot"},
-  {"recoverysw_cur", 0, 0, "Recovery switch current position"},
-  {"recoverysw_boot", 0, 0, "Recovery switch position at boot"},
-  {"recoverysw_ec_boot", 0, 0, "Recovery switch position at EC boot"},
-  {"wpsw_cur",  0, 0, "Firmware write protect switch current position"},
-  {"wpsw_boot", 0, 0, "Firmware write protect switch position at boot"},
-  {"recovery_reason",  0, 0, "Recovery mode reason for current boot"},
-  {"savedmem_base", 0, 0, "RAM debug data area physical address", "0x%08x"},
-  {"savedmem_size", 0, 0, "RAM debug data area size in bytes"},
-  {"fmap_base", 0, 0, "Main firmware flashmap physical address", "0x%08x"},
-  {"tried_fwb", 0, 0, "Tried firmware B before A this boot"},
-  {"cros_debug", 0, 0, "OS should allow debug features"},
+  {"devsw_cur",  0, "Developer switch current position"},
+  {"devsw_boot", 0, "Developer switch position at boot"},
+  {"recoverysw_cur", 0, "Recovery switch current position"},
+  {"recoverysw_boot", 0, "Recovery switch position at boot"},
+  {"recoverysw_ec_boot", 0, "Recovery switch position at EC boot"},
+  {"wpsw_cur", 0, "Firmware write protect hardware switch current position"},
+  {"wpsw_boot", 0, "Firmware write protect hardware switch position at boot"},
+  {"recovery_reason", 0, "Recovery mode reason for current boot"},
+  {"savedmem_base", 0, "RAM debug data area physical address", "0x%08x"},
+  {"savedmem_size", 0, "RAM debug data area size in bytes"},
+  {"fmap_base", 0, "Main firmware flashmap physical address", "0x%08x"},
+  {"tried_fwb", 0, "Tried firmware B before A this boot"},
+  {"cros_debug", 0, "OS should allow debug features"},
+  {"vdat_flags", 0, "Flags from VbSharedData", "0x%08x"},
+  {"tpm_fwver", 0, "Firmware version stored in TPM", "0x%08x"},
+  {"tpm_kernver", 0, "Kernel version stored in TPM", "0x%08x"},
   /* Read-only strings */
-  {"hwid", 1, 0, "Hardware ID"},
-  {"fwid", 1, 0, "Active firmware ID"},
-  {"ro_fwid", 1, 0, "Read-only firmware ID"},
-  {"mainfw_act", 1, 0, "Active main firmware"},
-  {"mainfw_type", 1, 0, "Active main firmware type"},
-  {"ecfw_act", 1, 0, "Active EC firmware"},
-  {"kernkey_vfy", 1, 0, "Type of verification done on kernel key block"},
+  {"hwid", IS_STRING, "Hardware ID"},
+  {"fwid", IS_STRING, "Active firmware ID"},
+  {"ro_fwid", IS_STRING, "Read-only firmware ID"},
+  {"mainfw_act", IS_STRING, "Active main firmware"},
+  {"mainfw_type", IS_STRING, "Active main firmware type"},
+  {"ecfw_act", IS_STRING, "Active EC firmware"},
+  {"kernkey_vfy", IS_STRING, "Type of verification done on kernel key block"},
+  {"vdat_timers", IS_STRING, "Timer values from VbSharedData"},
   /* Writable integers */
-  {"nvram_cleared", 0, 1, "Have NV settings been lost?  Write 0 to clear"},
-  {"kern_nv", 0, 1, "Non-volatile field for kernel use", "0x%08x"},
-  {"recovery_request", 0, 1, "Recovery mode request (writable)"},
-  {"dbg_reset", 0, 1, "Debug reset mode request (writable)"},
-  {"fwb_tries", 0, 1, "Try firmware B count (writable)"},
-
-  /* TODO: implement the following:
-   *   nvram_cleared
-   */
-
+  {"nvram_cleared", CAN_WRITE, "Have NV settings been lost?  Write 0 to clear"},
+  {"kern_nv", CAN_WRITE, "Non-volatile field for kernel use", "0x%08x"},
+  {"recovery_request", CAN_WRITE, "Recovery mode request (writable)"},
+  {"dbg_reset", CAN_WRITE, "Debug reset mode request (writable)"},
+  {"fwb_tries", CAN_WRITE, "Try firmware B count (writable)"},
+  {"vbtest_errfunc", CAN_WRITE, "Verified boot test error function (writable)"},
+  {"vbtest_errno", CAN_WRITE, "Verified boot test error number (writable)"},
+  /* Fields not shown in a print-all list */
+  {"vdat_lfdebug", IS_STRING|NO_PRINT_ALL,
+   "LoadFirmware() debug data (not in print-all)"},
+  {"vdat_lkdebug", IS_STRING|NO_PRINT_ALL,
+   "LoadKernel() debug data (not in print-all)"},
   /* Terminate with null name */
-  {NULL, 0, 0, NULL}
+  {NULL, 0, NULL}
 };
 
 
@@ -97,10 +111,10 @@
  *
  * Returns 0 if success, non-zero if error. */
 int SetParam(const Param* p, const char* value) {
-  if (!p->can_write)
+  if (!(p->flags & CAN_WRITE))
     return 1;  /* Parameter is read-only */
 
-  if (p->is_string) {
+  if (p->flags & IS_STRING) {
     return (0 == VbSetSystemPropertyString(p->name, value) ? 0 : 1);
   } else {
     char* e;
@@ -116,8 +130,8 @@
  *
  * Returns 0 if success (match), non-zero if error (mismatch). */
 int CheckParam(const Param* p, char* expect) {
-  if (p->is_string) {
-    char buf[256];
+  if (p->flags & IS_STRING) {
+    char buf[MAX_STRING];
     const char* v = VbGetSystemPropertyString(p->name, buf, sizeof(buf));
     if (!v || 0 != strcmp(v, expect))
       return 1;
@@ -138,8 +152,8 @@
  *
  * Returns 0 if success, non-zero if error. */
 int PrintParam(const Param* p) {
-  if (p->is_string) {
-    char buf[256];
+  if (p->flags & IS_STRING) {
+    char buf[MAX_STRING];
     const char* v = VbGetSystemPropertyString(p->name, buf, sizeof(buf));
     if (!v)
       return 1;
@@ -160,11 +174,13 @@
 int PrintAllParams(void) {
   const Param* p;
   int retval = 0;
-  char buf[256];
+  char buf[MAX_STRING];
   const char* value;
 
   for (p = sys_param_list; p->name; p++) {
-    if (p->is_string) {
+    if (p->flags & NO_PRINT_ALL)
+      continue;
+    if (p->flags & IS_STRING) {
       value = VbGetSystemPropertyString(p->name, buf, sizeof(buf));
     } else {
       int v = VbGetSystemPropertyInt(p->name);
@@ -175,7 +191,7 @@
         value = buf;
       }
     }
-    printf("%-22s = %-20s # %s\n",
+    printf("%-22s = %-30s # %s\n",
            p->name, (value ? value : "(error)"), p->desc);
   }
   return retval;
diff --git a/utility/load_kernel_test.c b/utility/load_kernel_test.c
index d26c7cd..73a3626 100644
--- a/utility/load_kernel_test.c
+++ b/utility/load_kernel_test.c
@@ -171,8 +171,8 @@
   }
 
   /* Initialize the shared data area */
-  lkp.shared_data_blob = Malloc(LOAD_FIRMWARE_SHARED_DATA_REC_SIZE);
-  lkp.shared_data_size = LOAD_FIRMWARE_SHARED_DATA_REC_SIZE;
+  lkp.shared_data_blob = Malloc(VB_SHARED_DATA_REC_SIZE);
+  lkp.shared_data_size = VB_SHARED_DATA_REC_SIZE;
   shared = (VbSharedDataHeader*)lkp.shared_data_blob;
   if (0 != VbSharedDataInit(shared, lkp.shared_data_size)) {
     fprintf(stderr, "Unable to init shared data\n");
diff --git a/utility/tlcl_generator.c b/utility/tlcl_generator.c
index efb0f34..86b7e4e 100644
--- a/utility/tlcl_generator.c
+++ b/utility/tlcl_generator.c
@@ -222,6 +222,13 @@
   return cmd;
 }
 
+Command* BuildSaveStateCommand(void) {
+  int size = kTpmRequestHeaderLength;
+  Command* cmd = newCommand(TPM_ORD_SaveState, size);
+  cmd->name = "tpm_savestate_cmd";
+  return cmd;
+}
+
 Command* BuildResumeCommand(void) {
   int size = kTpmRequestHeaderLength + sizeof(TPM_STARTUP_TYPE);
   Command* cmd = newCommand(TPM_ORD_Startup, size);
@@ -452,6 +459,7 @@
   BuildPPLockCommand,
   BuildFinalizePPCommand,
   BuildStartupCommand,
+  BuildSaveStateCommand,
   BuildResumeCommand,
   BuildSelftestfullCommand,
   BuildContinueSelfTestCommand,
diff --git a/utility/tpmc.c b/utility/tpmc.c
index 494a1ee..fba3ff5 100644
--- a/utility/tpmc.c
+++ b/utility/tpmc.c
@@ -290,6 +290,7 @@
   { "getstclearflags", "getvf", "print all volatile (ST_CLEAR) flags",
     HandlerGetSTClearFlags },
   { "resume", "res", "execute TPM_Startup(ST_STATE)", TlclResume },
+  { "savestate", "save", "execute TPM_SaveState", TlclSaveState },
 };
 
 static int n_commands = sizeof(command_table) / sizeof(command_table[0]);