sandbox: tpm: Enhance to support the latest Chromium OS

This driver was originally written against Chromium OS circa 2012. A few
new features have been added. Enhance the TPM driver to match. This mostly
includes a few new messages and properly modelling whether a particular
'space' is present or not.

Signed-off-by: Simon Glass <sjg@chromium.org>
diff --git a/drivers/tpm/tpm_tis_sandbox.c b/drivers/tpm/tpm_tis_sandbox.c
index c0b35a0..79517f0 100644
--- a/drivers/tpm/tpm_tis_sandbox.c
+++ b/drivers/tpm/tpm_tis_sandbox.c
@@ -13,6 +13,10 @@
 /* TPM NVRAM location indices. */
 #define FIRMWARE_NV_INDEX		0x1007
 #define KERNEL_NV_INDEX			0x1008
+#define BACKUP_NV_INDEX                 0x1009
+#define FWMP_NV_INDEX                   0x100a
+#define REC_HASH_NV_INDEX               0x100b
+#define REC_HASH_NV_SIZE                VB2_SHA256_DIGEST_SIZE
 
 #define NV_DATA_PUBLIC_PERMISSIONS_OFFSET	60
 
@@ -45,18 +49,28 @@
 	NV_GLOBAL_LOCK,
 	NV_SEQ_FIRMWARE,
 	NV_SEQ_KERNEL,
+	NV_SEQ_BACKUP,
+	NV_SEQ_FWMP,
+	NV_SEQ_REC_HASH,
+
 	NV_SEQ_COUNT,
 };
 
 /* Size of each non-volatile space */
 #define NV_DATA_SIZE		0x20
 
+struct nvdata_state {
+	bool present;
+	u8 data[NV_DATA_SIZE];
+};
+
 /*
  * Information about our TPM emulation. This is preserved in the sandbox
  * state file if enabled.
  */
 static struct tpm_state {
-	uint8_t nvdata[NV_SEQ_COUNT][NV_DATA_SIZE];
+	bool valid;
+	struct nvdata_state nvdata[NV_SEQ_COUNT];
 } g_state;
 
 /**
@@ -82,9 +96,12 @@
 
 		sprintf(prop_name, "nvdata%d", i);
 		prop = fdt_getprop(blob, node, prop_name, &len);
-		if (prop && len == NV_DATA_SIZE)
-			memcpy(g_state.nvdata[i], prop, NV_DATA_SIZE);
+		if (prop && len == NV_DATA_SIZE) {
+			memcpy(g_state.nvdata[i].data, prop, NV_DATA_SIZE);
+			g_state.nvdata[i].present = true;
+		}
 	}
+	g_state.valid = true;
 
 	return 0;
 }
@@ -110,9 +127,11 @@
 	for (i = 0; i < NV_SEQ_COUNT; i++) {
 		char prop_name[20];
 
-		sprintf(prop_name, "nvdata%d", i);
-		fdt_setprop(blob, node, prop_name, g_state.nvdata[i],
-			    NV_DATA_SIZE);
+		if (g_state.nvdata[i].present) {
+			sprintf(prop_name, "nvdata%d", i);
+			fdt_setprop(blob, node, prop_name,
+				    g_state.nvdata[i].data, NV_DATA_SIZE);
+		}
 	}
 
 	return 0;
@@ -128,6 +147,12 @@
 		return NV_SEQ_FIRMWARE;
 	case KERNEL_NV_INDEX:
 		return NV_SEQ_KERNEL;
+	case BACKUP_NV_INDEX:
+		return NV_SEQ_BACKUP;
+	case FWMP_NV_INDEX:
+		return NV_SEQ_FWMP;
+	case REC_HASH_NV_INDEX:
+		return NV_SEQ_REC_HASH;
 	case 0:
 		return NV_GLOBAL_LOCK;
 	}
@@ -136,6 +161,21 @@
 	return -1;
 }
 
+static void handle_cap_flag_space(u8 **datap, uint index)
+{
+	struct tpm_nv_data_public pub;
+
+	/* TPM_NV_PER_PPWRITE */
+	memset(&pub, '\0', sizeof(pub));
+	pub.nv_index = __cpu_to_be32(index);
+	pub.pcr_info_read.pcr_selection.size_of_select = __cpu_to_be16(
+		sizeof(pub.pcr_info_read.pcr_selection.pcr_select));
+	pub.permission.attributes = __cpu_to_be32(1);
+	pub.pcr_info_write = pub.pcr_info_read;
+	memcpy(*datap, &pub, sizeof(pub));
+	*datap += sizeof(pub);
+}
+
 static int sandbox_tpm_xfer(struct udevice *dev, const uint8_t *sendbuf,
 			    size_t send_size, uint8_t *recvbuf,
 			    size_t *recv_len)
@@ -159,19 +199,35 @@
 			printf("Get flags index %#02x\n", index);
 			*recv_len = 22;
 			memset(recvbuf, '\0', *recv_len);
-			put_unaligned_be32(22, recvbuf +
-					   TPM_RESPONSE_HEADER_LENGTH);
 			data = recvbuf + TPM_RESPONSE_HEADER_LENGTH +
 					sizeof(uint32_t);
 			switch (index) {
 			case FIRMWARE_NV_INDEX:
 				break;
 			case KERNEL_NV_INDEX:
-				/* TPM_NV_PER_PPWRITE */
-				put_unaligned_be32(1, data +
-					NV_DATA_PUBLIC_PERMISSIONS_OFFSET);
+				handle_cap_flag_space(&data, index);
+				*recv_len = data - recvbuf -
+					TPM_RESPONSE_HEADER_LENGTH -
+					sizeof(uint32_t);
+				break;
+			case TPM_CAP_FLAG_PERMANENT: {
+				struct tpm_permanent_flags *pflags;
+
+				pflags = (struct tpm_permanent_flags *)data;
+				memset(pflags, '\0', sizeof(*pflags));
+				put_unaligned_be32(TPM_TAG_PERMANENT_FLAGS,
+						   &pflags->tag);
+				*recv_len = TPM_HEADER_SIZE + 4 +
+						sizeof(*pflags);
 				break;
 			}
+			default:
+				printf("   ** Unknown flags index %x\n", index);
+				return -ENOSYS;
+			}
+			put_unaligned_be32(*recv_len,
+					   recvbuf +
+					   TPM_RESPONSE_HEADER_LENGTH);
 			break;
 		case TPM_CAP_NV_INDEX:
 			index = get_unaligned_be32(sendbuf + 18);
@@ -192,7 +248,8 @@
 		if (seq < 0)
 			return -EINVAL;
 		printf("tpm: nvwrite index=%#02x, len=%#02x\n", index, length);
-		memcpy(&tpm->nvdata[seq], sendbuf + 22, length);
+		memcpy(&tpm->nvdata[seq].data, sendbuf + 22, length);
+		tpm->nvdata[seq].present = true;
 		*recv_len = 12;
 		memset(recvbuf, '\0', *recv_len);
 		break;
@@ -202,7 +259,8 @@
 		seq = index_to_seq(index);
 		if (seq < 0)
 			return -EINVAL;
-		printf("tpm: nvread index=%#02x, len=%#02x\n", index, length);
+		printf("tpm: nvread index=%#02x, len=%#02x, seq=%#02x\n", index,
+		       length, seq);
 		*recv_len = TPM_RESPONSE_HEADER_LENGTH + sizeof(uint32_t) +
 					length;
 		memset(recvbuf, '\0', *recv_len);
@@ -220,17 +278,26 @@
 					offsetof(struct rollback_space_kernel,
 						 crc8));
 			memcpy(data, &rsk, sizeof(rsk));
+		} else if (!tpm->nvdata[seq].present) {
+			put_unaligned_be32(TPM_BADINDEX, recvbuf +
+					   sizeof(uint16_t) + sizeof(uint32_t));
 		} else {
 			memcpy(recvbuf + TPM_RESPONSE_HEADER_LENGTH +
-			       sizeof(uint32_t), &tpm->nvdata[seq], length);
+			       sizeof(uint32_t), &tpm->nvdata[seq].data,
+			       length);
 		}
 		break;
-	case TPM_CMD_EXTEND: /* tpm extend */
+	case TPM_CMD_EXTEND:
+		*recv_len = 30;
+		memset(recvbuf, '\0', *recv_len);
+		break;
+	case TPM_CMD_NV_DEFINE_SPACE:
 	case 0x15: /* pcr read */
 	case 0x5d: /* force clear */
 	case 0x6f: /* physical enable */
 	case 0x72: /* physical set deactivated */
 	case 0x99: /* startup */
+	case 0x50: /* self test full */
 	case 0x4000000a:  /* assert physical presence */
 		*recv_len = 12;
 		memset(recvbuf, '\0', *recv_len);
diff --git a/include/tpm-v1.h b/include/tpm-v1.h
index 29788b5..f9ffbb2 100644
--- a/include/tpm-v1.h
+++ b/include/tpm-v1.h
@@ -245,6 +245,40 @@
 	u8	disable_full_da_logic_info;
 } __packed;
 
+#define TPM_SHA1_160_HASH_LEN	0x14
+
+struct __packed tpm_composite_hash {
+	u8	digest[TPM_SHA1_160_HASH_LEN];
+};
+
+struct __packed tpm_pcr_selection {
+	__be16	size_of_select;
+	u8	pcr_select[3];	/* matches vboot's struct */
+};
+
+struct __packed tpm_pcr_info_short {
+	struct tpm_pcr_selection pcr_selection;
+	u8	locality_at_release;
+	struct tpm_composite_hash digest_at_release;
+};
+
+struct __packed tpm_nv_attributes {
+	__be16	tag;
+	__be32	attributes;
+};
+
+struct __packed tpm_nv_data_public {
+	__be16	tag;
+	__be32	nv_index;
+	struct tpm_pcr_info_short pcr_info_read;
+	struct tpm_pcr_info_short pcr_info_write;
+	struct tpm_nv_attributes permission;
+	u8	read_st_clear;
+	u8	write_st_clear;
+	u8	write_define;
+	__be32	data_size;
+};
+
 /**
  * Issue a TPM_Startup command.
  *