xref: /openbmc/u-boot/board/gdsys/a38x/hre.c (revision 172e3c11901229f0fb88317ac73a47d944a74f46)
183d290c5STom Rini // SPDX-License-Identifier: GPL-2.0+
260083261SDirk Eibach /*
360083261SDirk Eibach  * (C) Copyright 2013
460083261SDirk Eibach  * Reinhard Pfau, Guntermann & Drunck GmbH, reinhard.pfau@gdsys.cc
560083261SDirk Eibach  */
660083261SDirk Eibach 
760083261SDirk Eibach #include <common.h>
860083261SDirk Eibach #include <malloc.h>
960083261SDirk Eibach #include <fs.h>
1060083261SDirk Eibach #include <i2c.h>
1160083261SDirk Eibach #include <mmc.h>
12d677bfe2SMiquel Raynal #include <tpm-v1.h>
1360083261SDirk Eibach #include <u-boot/sha1.h>
1460083261SDirk Eibach #include <asm/byteorder.h>
1560083261SDirk Eibach #include <asm/unaligned.h>
1660083261SDirk Eibach #include <pca9698.h>
1760083261SDirk Eibach 
1860083261SDirk Eibach #include "hre.h"
1960083261SDirk Eibach 
2060083261SDirk Eibach /* other constants */
2160083261SDirk Eibach enum {
2260083261SDirk Eibach 	ESDHC_BOOT_IMAGE_SIG_OFS	= 0x40,
2360083261SDirk Eibach 	ESDHC_BOOT_IMAGE_SIZE_OFS	= 0x48,
2460083261SDirk Eibach 	ESDHC_BOOT_IMAGE_ADDR_OFS	= 0x50,
2560083261SDirk Eibach 	ESDHC_BOOT_IMAGE_TARGET_OFS	= 0x58,
2660083261SDirk Eibach 	ESDHC_BOOT_IMAGE_ENTRY_OFS	= 0x60,
2760083261SDirk Eibach };
2860083261SDirk Eibach 
2960083261SDirk Eibach enum {
3060083261SDirk Eibach 	I2C_SOC_0 = 0,
3160083261SDirk Eibach 	I2C_SOC_1 = 1,
3260083261SDirk Eibach };
3360083261SDirk Eibach 
3460083261SDirk Eibach enum access_mode {
3560083261SDirk Eibach 	HREG_NONE	= 0,
3660083261SDirk Eibach 	HREG_RD		= 1,
3760083261SDirk Eibach 	HREG_WR		= 2,
3860083261SDirk Eibach 	HREG_RDWR	= 3,
3960083261SDirk Eibach };
4060083261SDirk Eibach 
4160083261SDirk Eibach /* register constants */
4260083261SDirk Eibach enum {
4360083261SDirk Eibach 	FIX_HREG_DEVICE_ID_HASH	= 0,
4460083261SDirk Eibach 	FIX_HREG_UNUSED1	= 1,
4560083261SDirk Eibach 	FIX_HREG_UNUSED2	= 2,
4660083261SDirk Eibach 	FIX_HREG_VENDOR		= 3,
4760083261SDirk Eibach 	COUNT_FIX_HREGS
4860083261SDirk Eibach };
4960083261SDirk Eibach 
5060083261SDirk Eibach static struct h_reg pcr_hregs[24];
5160083261SDirk Eibach static struct h_reg fix_hregs[COUNT_FIX_HREGS];
5260083261SDirk Eibach static struct h_reg var_hregs[8];
5360083261SDirk Eibach 
5460083261SDirk Eibach /* hre opcodes */
5560083261SDirk Eibach enum {
5660083261SDirk Eibach 	/* opcodes w/o data */
5760083261SDirk Eibach 	HRE_NOP		= 0x00,
5860083261SDirk Eibach 	HRE_SYNC	= HRE_NOP,
5960083261SDirk Eibach 	HRE_CHECK0	= 0x01,
6060083261SDirk Eibach 	/* opcodes w/o data, w/ sync dst */
6160083261SDirk Eibach 	/* opcodes w/ data */
6260083261SDirk Eibach 	HRE_LOAD	= 0x81,
6360083261SDirk Eibach 	/* opcodes w/data, w/sync dst */
6460083261SDirk Eibach 	HRE_XOR		= 0xC1,
6560083261SDirk Eibach 	HRE_AND		= 0xC2,
6660083261SDirk Eibach 	HRE_OR		= 0xC3,
6760083261SDirk Eibach 	HRE_EXTEND	= 0xC4,
6860083261SDirk Eibach 	HRE_LOADKEY	= 0xC5,
6960083261SDirk Eibach };
7060083261SDirk Eibach 
7160083261SDirk Eibach /* hre errors */
7260083261SDirk Eibach enum {
7360083261SDirk Eibach 	HRE_E_OK	= 0,
7460083261SDirk Eibach 	HRE_E_TPM_FAILURE,
7560083261SDirk Eibach 	HRE_E_INVALID_HREG,
7660083261SDirk Eibach };
7760083261SDirk Eibach 
7860083261SDirk Eibach static uint64_t device_id;
7960083261SDirk Eibach static uint64_t device_cl;
8060083261SDirk Eibach static uint64_t device_type;
8160083261SDirk Eibach 
8260083261SDirk Eibach static uint32_t platform_key_handle;
8360083261SDirk Eibach 
8460083261SDirk Eibach static uint32_t hre_tpm_err;
8560083261SDirk Eibach static int hre_err = HRE_E_OK;
8660083261SDirk Eibach 
8760083261SDirk Eibach #define IS_PCR_HREG(spec) ((spec) & 0x20)
8860083261SDirk Eibach #define IS_FIX_HREG(spec) (((spec) & 0x38) == 0x08)
8960083261SDirk Eibach #define IS_VAR_HREG(spec) (((spec) & 0x38) == 0x10)
9060083261SDirk Eibach #define HREG_IDX(spec) ((spec) & (IS_PCR_HREG(spec) ? 0x1f : 0x7))
9160083261SDirk Eibach 
9260083261SDirk Eibach static const uint8_t vendor[] = "Guntermann & Drunck";
9360083261SDirk Eibach 
9460083261SDirk Eibach /**
9560083261SDirk Eibach  * @brief get the size of a given (TPM) NV area
96*abdc7b8aSSimon Glass  * @param tpm		TPM device
9760083261SDirk Eibach  * @param index	NV index of the area to get size for
9860083261SDirk Eibach  * @param size	pointer to the size
9960083261SDirk Eibach  * @return 0 on success, != 0 on error
10060083261SDirk Eibach  */
get_tpm_nv_size(struct udevice * tpm,uint32_t index,uint32_t * size)101*abdc7b8aSSimon Glass static int get_tpm_nv_size(struct udevice *tpm, uint32_t index, uint32_t *size)
10260083261SDirk Eibach {
10360083261SDirk Eibach 	uint32_t err;
10460083261SDirk Eibach 	uint8_t info[72];
10560083261SDirk Eibach 	uint8_t *ptr;
10660083261SDirk Eibach 	uint16_t v16;
10760083261SDirk Eibach 
108*abdc7b8aSSimon Glass 	err = tpm_get_capability(tpm, TPM_CAP_NV_INDEX, index,
10960083261SDirk Eibach 				 info, sizeof(info));
11060083261SDirk Eibach 	if (err) {
11160083261SDirk Eibach 		printf("tpm_get_capability(CAP_NV_INDEX, %08x) failed: %u\n",
11260083261SDirk Eibach 		       index, err);
11360083261SDirk Eibach 		return 1;
11460083261SDirk Eibach 	}
11560083261SDirk Eibach 
11660083261SDirk Eibach 	/* skip tag and nvIndex */
11760083261SDirk Eibach 	ptr = info + 6;
11860083261SDirk Eibach 	/* skip 2 pcr info fields */
11960083261SDirk Eibach 	v16 = get_unaligned_be16(ptr);
12060083261SDirk Eibach 	ptr += 2 + v16 + 1 + 20;
12160083261SDirk Eibach 	v16 = get_unaligned_be16(ptr);
12260083261SDirk Eibach 	ptr += 2 + v16 + 1 + 20;
12360083261SDirk Eibach 	/* skip permission and flags */
12460083261SDirk Eibach 	ptr += 6 + 3;
12560083261SDirk Eibach 
12660083261SDirk Eibach 	*size = get_unaligned_be32(ptr);
12760083261SDirk Eibach 	return 0;
12860083261SDirk Eibach }
12960083261SDirk Eibach 
13060083261SDirk Eibach /**
13160083261SDirk Eibach  * @brief search for a key by usage auth and pub key hash.
132*abdc7b8aSSimon Glass  * @param tpm		TPM device
13360083261SDirk Eibach  * @param auth	usage auth of the key to search for
13460083261SDirk Eibach  * @param pubkey_digest	(SHA1) hash of the pub key structure of the key
13560083261SDirk Eibach  * @param[out] handle	the handle of the key iff found
13660083261SDirk Eibach  * @return 0 if key was found in TPM; != 0 if not.
13760083261SDirk Eibach  */
find_key(struct udevice * tpm,const uint8_t auth[20],const uint8_t pubkey_digest[20],uint32_t * handle)138*abdc7b8aSSimon Glass static int find_key(struct udevice *tpm, const uint8_t auth[20],
139*abdc7b8aSSimon Glass 		    const uint8_t pubkey_digest[20], uint32_t *handle)
14060083261SDirk Eibach {
14160083261SDirk Eibach 	uint16_t key_count;
14260083261SDirk Eibach 	uint32_t key_handles[10];
14360083261SDirk Eibach 	uint8_t buf[288];
14460083261SDirk Eibach 	uint8_t *ptr;
14560083261SDirk Eibach 	uint32_t err;
14660083261SDirk Eibach 	uint8_t digest[20];
14760083261SDirk Eibach 	size_t buf_len;
14860083261SDirk Eibach 	unsigned int i;
14960083261SDirk Eibach 
15060083261SDirk Eibach 	/* fetch list of already loaded keys in the TPM */
151*abdc7b8aSSimon Glass 	err = tpm_get_capability(tpm, TPM_CAP_HANDLE, TPM_RT_KEY, buf,
152*abdc7b8aSSimon Glass 				 sizeof(buf));
15360083261SDirk Eibach 	if (err)
15460083261SDirk Eibach 		return -1;
15560083261SDirk Eibach 	key_count = get_unaligned_be16(buf);
15660083261SDirk Eibach 	ptr = buf + 2;
15760083261SDirk Eibach 	for (i = 0; i < key_count; ++i, ptr += 4)
15860083261SDirk Eibach 		key_handles[i] = get_unaligned_be32(ptr);
15960083261SDirk Eibach 
16060083261SDirk Eibach 	/* now search a(/ the) key which we can access with the given auth */
16160083261SDirk Eibach 	for (i = 0; i < key_count; ++i) {
16260083261SDirk Eibach 		buf_len = sizeof(buf);
163*abdc7b8aSSimon Glass 		err = tpm_get_pub_key_oiap(tpm, key_handles[i], auth, buf,
164*abdc7b8aSSimon Glass 					   &buf_len);
16560083261SDirk Eibach 		if (err && err != TPM_AUTHFAIL)
16660083261SDirk Eibach 			return -1;
16760083261SDirk Eibach 		if (err)
16860083261SDirk Eibach 			continue;
16960083261SDirk Eibach 		sha1_csum(buf, buf_len, digest);
17060083261SDirk Eibach 		if (!memcmp(digest, pubkey_digest, 20)) {
17160083261SDirk Eibach 			*handle = key_handles[i];
17260083261SDirk Eibach 			return 0;
17360083261SDirk Eibach 		}
17460083261SDirk Eibach 	}
17560083261SDirk Eibach 	return 1;
17660083261SDirk Eibach }
17760083261SDirk Eibach 
17860083261SDirk Eibach /**
17960083261SDirk Eibach  * @brief read CCDM common data from TPM NV
180*abdc7b8aSSimon Glass  * @param tpm		TPM device
18160083261SDirk Eibach  * @return 0 if CCDM common data was found and read, !=0 if something failed.
18260083261SDirk Eibach  */
read_common_data(struct udevice * tpm)183*abdc7b8aSSimon Glass static int read_common_data(struct udevice *tpm)
18460083261SDirk Eibach {
18560083261SDirk Eibach 	uint32_t size = 0;
18660083261SDirk Eibach 	uint32_t err;
18760083261SDirk Eibach 	uint8_t buf[256];
18860083261SDirk Eibach 	sha1_context ctx;
18960083261SDirk Eibach 
190*abdc7b8aSSimon Glass 	if (get_tpm_nv_size(tpm, NV_COMMON_DATA_INDEX, &size) ||
19160083261SDirk Eibach 	    size < NV_COMMON_DATA_MIN_SIZE)
19260083261SDirk Eibach 		return 1;
193*abdc7b8aSSimon Glass 	err = tpm_nv_read_value(tpm, NV_COMMON_DATA_INDEX,
19460083261SDirk Eibach 				buf, min(sizeof(buf), size));
19560083261SDirk Eibach 	if (err) {
19660083261SDirk Eibach 		printf("tpm_nv_read_value() failed: %u\n", err);
19760083261SDirk Eibach 		return 1;
19860083261SDirk Eibach 	}
19960083261SDirk Eibach 
20060083261SDirk Eibach 	device_id = get_unaligned_be64(buf);
20160083261SDirk Eibach 	device_cl = get_unaligned_be64(buf + 8);
20260083261SDirk Eibach 	device_type = get_unaligned_be64(buf + 16);
20360083261SDirk Eibach 
20460083261SDirk Eibach 	sha1_starts(&ctx);
20560083261SDirk Eibach 	sha1_update(&ctx, buf, 24);
20660083261SDirk Eibach 	sha1_finish(&ctx, fix_hregs[FIX_HREG_DEVICE_ID_HASH].digest);
20760083261SDirk Eibach 	fix_hregs[FIX_HREG_DEVICE_ID_HASH].valid = true;
20860083261SDirk Eibach 
20960083261SDirk Eibach 	platform_key_handle = get_unaligned_be32(buf + 24);
21060083261SDirk Eibach 
21160083261SDirk Eibach 	return 0;
21260083261SDirk Eibach }
21360083261SDirk Eibach 
21460083261SDirk Eibach /**
21560083261SDirk Eibach  * @brief get pointer to  hash register by specification
21660083261SDirk Eibach  * @param spec	specification of a hash register
21760083261SDirk Eibach  * @return pointer to hash register or NULL if @a spec does not qualify a
21860083261SDirk Eibach  * valid hash register; NULL else.
21960083261SDirk Eibach  */
get_hreg(uint8_t spec)22060083261SDirk Eibach static struct h_reg *get_hreg(uint8_t spec)
22160083261SDirk Eibach {
22260083261SDirk Eibach 	uint8_t idx;
22360083261SDirk Eibach 
22460083261SDirk Eibach 	idx = HREG_IDX(spec);
22560083261SDirk Eibach 	if (IS_FIX_HREG(spec)) {
22660083261SDirk Eibach 		if (idx < ARRAY_SIZE(fix_hregs))
22760083261SDirk Eibach 			return fix_hregs + idx;
22860083261SDirk Eibach 		hre_err = HRE_E_INVALID_HREG;
22960083261SDirk Eibach 	} else if (IS_PCR_HREG(spec)) {
23060083261SDirk Eibach 		if (idx < ARRAY_SIZE(pcr_hregs))
23160083261SDirk Eibach 			return pcr_hregs + idx;
23260083261SDirk Eibach 		hre_err = HRE_E_INVALID_HREG;
23360083261SDirk Eibach 	} else if (IS_VAR_HREG(spec)) {
23460083261SDirk Eibach 		if (idx < ARRAY_SIZE(var_hregs))
23560083261SDirk Eibach 			return var_hregs + idx;
23660083261SDirk Eibach 		hre_err = HRE_E_INVALID_HREG;
23760083261SDirk Eibach 	}
23860083261SDirk Eibach 	return NULL;
23960083261SDirk Eibach }
24060083261SDirk Eibach 
24160083261SDirk Eibach /**
24260083261SDirk Eibach  * @brief get pointer of a hash register by specification and usage.
243*abdc7b8aSSimon Glass  * @param tpm		TPM device
24460083261SDirk Eibach  * @param spec	specification of a hash register
24560083261SDirk Eibach  * @param mode	access mode (read or write or read/write)
24660083261SDirk Eibach  * @return pointer to hash register if found and valid; NULL else.
24760083261SDirk Eibach  *
24860083261SDirk Eibach  * This func uses @a get_reg() to determine the hash register for a given spec.
24960083261SDirk Eibach  * If a register is found it is validated according to the desired access mode.
25060083261SDirk Eibach  * The value of automatic registers (PCR register and fixed registers) is
25160083261SDirk Eibach  * loaded or computed on read access.
25260083261SDirk Eibach  */
access_hreg(struct udevice * tpm,uint8_t spec,enum access_mode mode)253*abdc7b8aSSimon Glass static struct h_reg *access_hreg(struct udevice *tpm, uint8_t spec,
254*abdc7b8aSSimon Glass 				 enum access_mode mode)
25560083261SDirk Eibach {
25660083261SDirk Eibach 	struct h_reg *result;
25760083261SDirk Eibach 
25860083261SDirk Eibach 	result = get_hreg(spec);
25960083261SDirk Eibach 	if (!result)
26060083261SDirk Eibach 		return NULL;
26160083261SDirk Eibach 
26260083261SDirk Eibach 	if (mode & HREG_WR) {
26360083261SDirk Eibach 		if (IS_FIX_HREG(spec)) {
26460083261SDirk Eibach 			hre_err = HRE_E_INVALID_HREG;
26560083261SDirk Eibach 			return NULL;
26660083261SDirk Eibach 		}
26760083261SDirk Eibach 	}
26860083261SDirk Eibach 	if (mode & HREG_RD) {
26960083261SDirk Eibach 		if (!result->valid) {
27060083261SDirk Eibach 			if (IS_PCR_HREG(spec)) {
271*abdc7b8aSSimon Glass 				hre_tpm_err = tpm_pcr_read(tpm, HREG_IDX(spec),
27260083261SDirk Eibach 					result->digest, 20);
27360083261SDirk Eibach 				result->valid = (hre_tpm_err == TPM_SUCCESS);
27460083261SDirk Eibach 			} else if (IS_FIX_HREG(spec)) {
27560083261SDirk Eibach 				switch (HREG_IDX(spec)) {
27660083261SDirk Eibach 				case FIX_HREG_DEVICE_ID_HASH:
277*abdc7b8aSSimon Glass 					read_common_data(tpm);
27860083261SDirk Eibach 					break;
27960083261SDirk Eibach 				case FIX_HREG_VENDOR:
28060083261SDirk Eibach 					memcpy(result->digest, vendor, 20);
28160083261SDirk Eibach 					result->valid = true;
28260083261SDirk Eibach 					break;
28360083261SDirk Eibach 				}
28460083261SDirk Eibach 			} else {
28560083261SDirk Eibach 				result->valid = true;
28660083261SDirk Eibach 			}
28760083261SDirk Eibach 		}
28860083261SDirk Eibach 		if (!result->valid) {
28960083261SDirk Eibach 			hre_err = HRE_E_INVALID_HREG;
29060083261SDirk Eibach 			return NULL;
29160083261SDirk Eibach 		}
29260083261SDirk Eibach 	}
29360083261SDirk Eibach 
29460083261SDirk Eibach 	return result;
29560083261SDirk Eibach }
29660083261SDirk Eibach 
compute_and(void * _dst,const void * _src,size_t n)29760083261SDirk Eibach static void *compute_and(void *_dst, const void *_src, size_t n)
29860083261SDirk Eibach {
29960083261SDirk Eibach 	uint8_t *dst = _dst;
30060083261SDirk Eibach 	const uint8_t *src = _src;
30160083261SDirk Eibach 	size_t i;
30260083261SDirk Eibach 
30360083261SDirk Eibach 	for (i = n; i-- > 0; )
30460083261SDirk Eibach 		*dst++ &= *src++;
30560083261SDirk Eibach 
30660083261SDirk Eibach 	return _dst;
30760083261SDirk Eibach }
30860083261SDirk Eibach 
compute_or(void * _dst,const void * _src,size_t n)30960083261SDirk Eibach static void *compute_or(void *_dst, const void *_src, size_t n)
31060083261SDirk Eibach {
31160083261SDirk Eibach 	uint8_t *dst = _dst;
31260083261SDirk Eibach 	const uint8_t *src = _src;
31360083261SDirk Eibach 	size_t i;
31460083261SDirk Eibach 
31560083261SDirk Eibach 	for (i = n; i-- > 0; )
31660083261SDirk Eibach 		*dst++ |= *src++;
31760083261SDirk Eibach 
31860083261SDirk Eibach 	return _dst;
31960083261SDirk Eibach }
32060083261SDirk Eibach 
compute_xor(void * _dst,const void * _src,size_t n)32160083261SDirk Eibach static void *compute_xor(void *_dst, const void *_src, size_t n)
32260083261SDirk Eibach {
32360083261SDirk Eibach 	uint8_t *dst = _dst;
32460083261SDirk Eibach 	const uint8_t *src = _src;
32560083261SDirk Eibach 	size_t i;
32660083261SDirk Eibach 
32760083261SDirk Eibach 	for (i = n; i-- > 0; )
32860083261SDirk Eibach 		*dst++ ^= *src++;
32960083261SDirk Eibach 
33060083261SDirk Eibach 	return _dst;
33160083261SDirk Eibach }
33260083261SDirk Eibach 
compute_extend(void * _dst,const void * _src,size_t n)33360083261SDirk Eibach static void *compute_extend(void *_dst, const void *_src, size_t n)
33460083261SDirk Eibach {
33560083261SDirk Eibach 	uint8_t digest[20];
33660083261SDirk Eibach 	sha1_context ctx;
33760083261SDirk Eibach 
33860083261SDirk Eibach 	sha1_starts(&ctx);
33960083261SDirk Eibach 	sha1_update(&ctx, _dst, n);
34060083261SDirk Eibach 	sha1_update(&ctx, _src, n);
34160083261SDirk Eibach 	sha1_finish(&ctx, digest);
34260083261SDirk Eibach 	memcpy(_dst, digest, min(n, sizeof(digest)));
34360083261SDirk Eibach 
34460083261SDirk Eibach 	return _dst;
34560083261SDirk Eibach }
34660083261SDirk Eibach 
hre_op_loadkey(struct udevice * tpm,struct h_reg * src_reg,struct h_reg * dst_reg,const void * key,size_t key_size)347*abdc7b8aSSimon Glass static int hre_op_loadkey(struct udevice *tpm, struct h_reg *src_reg,
348*abdc7b8aSSimon Glass 			  struct h_reg *dst_reg, const void *key,
349*abdc7b8aSSimon Glass 			  size_t key_size)
35060083261SDirk Eibach {
35160083261SDirk Eibach 	uint32_t parent_handle;
35260083261SDirk Eibach 	uint32_t key_handle;
35360083261SDirk Eibach 
35460083261SDirk Eibach 	if (!src_reg || !dst_reg || !src_reg->valid || !dst_reg->valid)
35560083261SDirk Eibach 		return -1;
356*abdc7b8aSSimon Glass 	if (find_key(tpm, src_reg->digest, dst_reg->digest, &parent_handle))
35760083261SDirk Eibach 		return -1;
358*abdc7b8aSSimon Glass 	hre_tpm_err = tpm_load_key2_oiap(tpm, parent_handle, key, key_size,
35960083261SDirk Eibach 					 src_reg->digest, &key_handle);
36060083261SDirk Eibach 	if (hre_tpm_err) {
36160083261SDirk Eibach 		hre_err = HRE_E_TPM_FAILURE;
36260083261SDirk Eibach 		return -1;
36360083261SDirk Eibach 	}
36460083261SDirk Eibach 
36560083261SDirk Eibach 	return 0;
36660083261SDirk Eibach }
36760083261SDirk Eibach 
36860083261SDirk Eibach /**
36960083261SDirk Eibach  * @brief executes the next opcode on the hash register engine.
370*abdc7b8aSSimon Glass  * @param tpm		TPM device
37160083261SDirk Eibach  * @param[in,out] ip	pointer to the opcode (instruction pointer)
37260083261SDirk Eibach  * @param[in,out] code_size	(remaining) size of the code
37360083261SDirk Eibach  * @return new instruction pointer on success, NULL on error.
37460083261SDirk Eibach  */
hre_execute_op(struct udevice * tpm,const uint8_t ** ip,size_t * code_size)375*abdc7b8aSSimon Glass static const uint8_t *hre_execute_op(struct udevice *tpm, const uint8_t **ip,
376*abdc7b8aSSimon Glass 				     size_t *code_size)
37760083261SDirk Eibach {
37860083261SDirk Eibach 	bool dst_modified = false;
37960083261SDirk Eibach 	uint32_t ins;
38060083261SDirk Eibach 	uint8_t opcode;
38160083261SDirk Eibach 	uint8_t src_spec;
38260083261SDirk Eibach 	uint8_t dst_spec;
38360083261SDirk Eibach 	uint16_t data_size;
38460083261SDirk Eibach 	struct h_reg *src_reg, *dst_reg;
38560083261SDirk Eibach 	uint8_t buf[20];
38660083261SDirk Eibach 	const uint8_t *src_buf, *data;
38760083261SDirk Eibach 	uint8_t *ptr;
38860083261SDirk Eibach 	int i;
38960083261SDirk Eibach 	void * (*bin_func)(void *, const void *, size_t);
39060083261SDirk Eibach 
39160083261SDirk Eibach 	if (*code_size < 4)
39260083261SDirk Eibach 		return NULL;
39360083261SDirk Eibach 
39460083261SDirk Eibach 	ins = get_unaligned_be32(*ip);
39560083261SDirk Eibach 	opcode = **ip;
39660083261SDirk Eibach 	data = *ip + 4;
39760083261SDirk Eibach 	src_spec = (ins >> 18) & 0x3f;
39860083261SDirk Eibach 	dst_spec = (ins >> 12) & 0x3f;
39960083261SDirk Eibach 	data_size = (ins & 0x7ff);
40060083261SDirk Eibach 
40160083261SDirk Eibach 	debug("HRE: ins=%08x (op=%02x, s=%02x, d=%02x, L=%d)\n", ins,
40260083261SDirk Eibach 	      opcode, src_spec, dst_spec, data_size);
40360083261SDirk Eibach 
40460083261SDirk Eibach 	if ((opcode & 0x80) && (data_size + 4) > *code_size)
40560083261SDirk Eibach 		return NULL;
40660083261SDirk Eibach 
407*abdc7b8aSSimon Glass 	src_reg = access_hreg(tpm, src_spec, HREG_RD);
40860083261SDirk Eibach 	if (hre_err || hre_tpm_err)
40960083261SDirk Eibach 		return NULL;
410*abdc7b8aSSimon Glass 	dst_reg = access_hreg(tpm, dst_spec,
411*abdc7b8aSSimon Glass 			      (opcode & 0x40) ? HREG_RDWR : HREG_WR);
41260083261SDirk Eibach 	if (hre_err || hre_tpm_err)
41360083261SDirk Eibach 		return NULL;
41460083261SDirk Eibach 
41560083261SDirk Eibach 	switch (opcode) {
41660083261SDirk Eibach 	case HRE_NOP:
41760083261SDirk Eibach 		goto end;
41860083261SDirk Eibach 	case HRE_CHECK0:
41960083261SDirk Eibach 		if (src_reg) {
42060083261SDirk Eibach 			for (i = 0; i < 20; ++i) {
42160083261SDirk Eibach 				if (src_reg->digest[i])
42260083261SDirk Eibach 					return NULL;
42360083261SDirk Eibach 			}
42460083261SDirk Eibach 		}
42560083261SDirk Eibach 		break;
42660083261SDirk Eibach 	case HRE_LOAD:
42760083261SDirk Eibach 		bin_func = memcpy;
42860083261SDirk Eibach 		goto do_bin_func;
42960083261SDirk Eibach 	case HRE_XOR:
43060083261SDirk Eibach 		bin_func = compute_xor;
43160083261SDirk Eibach 		goto do_bin_func;
43260083261SDirk Eibach 	case HRE_AND:
43360083261SDirk Eibach 		bin_func = compute_and;
43460083261SDirk Eibach 		goto do_bin_func;
43560083261SDirk Eibach 	case HRE_OR:
43660083261SDirk Eibach 		bin_func = compute_or;
43760083261SDirk Eibach 		goto do_bin_func;
43860083261SDirk Eibach 	case HRE_EXTEND:
43960083261SDirk Eibach 		bin_func = compute_extend;
44060083261SDirk Eibach do_bin_func:
44160083261SDirk Eibach 		if (!dst_reg)
44260083261SDirk Eibach 			return NULL;
44360083261SDirk Eibach 		if (src_reg) {
44460083261SDirk Eibach 			src_buf = src_reg->digest;
44560083261SDirk Eibach 		} else {
44660083261SDirk Eibach 			if (!data_size) {
44760083261SDirk Eibach 				memset(buf, 0, 20);
44860083261SDirk Eibach 				src_buf = buf;
44960083261SDirk Eibach 			} else if (data_size == 1) {
45060083261SDirk Eibach 				memset(buf, *data, 20);
45160083261SDirk Eibach 				src_buf = buf;
45260083261SDirk Eibach 			} else if (data_size >= 20) {
45360083261SDirk Eibach 				src_buf = data;
45460083261SDirk Eibach 			} else {
45560083261SDirk Eibach 				src_buf = buf;
45660083261SDirk Eibach 				for (ptr = (uint8_t *)src_buf, i = 20; i > 0;
45760083261SDirk Eibach 					i -= data_size, ptr += data_size)
45860083261SDirk Eibach 					memcpy(ptr, data,
45960083261SDirk Eibach 					       min_t(size_t, i, data_size));
46060083261SDirk Eibach 			}
46160083261SDirk Eibach 		}
46260083261SDirk Eibach 		bin_func(dst_reg->digest, src_buf, 20);
46360083261SDirk Eibach 		dst_reg->valid = true;
46460083261SDirk Eibach 		dst_modified = true;
46560083261SDirk Eibach 		break;
46660083261SDirk Eibach 	case HRE_LOADKEY:
467*abdc7b8aSSimon Glass 		if (hre_op_loadkey(tpm, src_reg, dst_reg, data, data_size))
46860083261SDirk Eibach 			return NULL;
46960083261SDirk Eibach 		break;
47060083261SDirk Eibach 	default:
47160083261SDirk Eibach 		return NULL;
47260083261SDirk Eibach 	}
47360083261SDirk Eibach 
47460083261SDirk Eibach 	if (dst_reg && dst_modified && IS_PCR_HREG(dst_spec)) {
475*abdc7b8aSSimon Glass 		hre_tpm_err = tpm_extend(tpm, HREG_IDX(dst_spec),
476*abdc7b8aSSimon Glass 					 dst_reg->digest, dst_reg->digest);
47760083261SDirk Eibach 		if (hre_tpm_err) {
47860083261SDirk Eibach 			hre_err = HRE_E_TPM_FAILURE;
47960083261SDirk Eibach 			return NULL;
48060083261SDirk Eibach 		}
48160083261SDirk Eibach 	}
48260083261SDirk Eibach end:
48360083261SDirk Eibach 	*ip += 4;
48460083261SDirk Eibach 	*code_size -= 4;
48560083261SDirk Eibach 	if (opcode & 0x80) {
48660083261SDirk Eibach 		*ip += data_size;
48760083261SDirk Eibach 		*code_size -= data_size;
48860083261SDirk Eibach 	}
48960083261SDirk Eibach 
49060083261SDirk Eibach 	return *ip;
49160083261SDirk Eibach }
49260083261SDirk Eibach 
49360083261SDirk Eibach /**
49460083261SDirk Eibach  * @brief runs a program on the hash register engine.
495*abdc7b8aSSimon Glass  * @param tpm		TPM device
49660083261SDirk Eibach  * @param code		pointer to the (HRE) code.
49760083261SDirk Eibach  * @param code_size	size of the code (in bytes).
49860083261SDirk Eibach  * @return 0 on success, != 0 on failure.
49960083261SDirk Eibach  */
hre_run_program(struct udevice * tpm,const uint8_t * code,size_t code_size)500*abdc7b8aSSimon Glass int hre_run_program(struct udevice *tpm, const uint8_t *code, size_t code_size)
50160083261SDirk Eibach {
50260083261SDirk Eibach 	size_t code_left;
50360083261SDirk Eibach 	const uint8_t *ip = code;
50460083261SDirk Eibach 
50560083261SDirk Eibach 	code_left = code_size;
50660083261SDirk Eibach 	hre_tpm_err = 0;
50760083261SDirk Eibach 	hre_err = HRE_E_OK;
50860083261SDirk Eibach 	while (code_left > 0)
509*abdc7b8aSSimon Glass 		if (!hre_execute_op(tpm, &ip, &code_left))
51060083261SDirk Eibach 			return -1;
51160083261SDirk Eibach 
51260083261SDirk Eibach 	return hre_err;
51360083261SDirk Eibach }
51460083261SDirk Eibach 
hre_verify_program(struct key_program * prg)51560083261SDirk Eibach int hre_verify_program(struct key_program *prg)
51660083261SDirk Eibach {
51760083261SDirk Eibach 	uint32_t crc;
51860083261SDirk Eibach 
51960083261SDirk Eibach 	crc = crc32(0, prg->code, prg->code_size);
52060083261SDirk Eibach 
52160083261SDirk Eibach 	if (crc != prg->code_crc) {
52260083261SDirk Eibach 		printf("HRC crc mismatch: %08x != %08x\n",
52360083261SDirk Eibach 		       crc, prg->code_crc);
52460083261SDirk Eibach 		return 1;
52560083261SDirk Eibach 	}
52660083261SDirk Eibach 	return 0;
52760083261SDirk Eibach }
528