util/amdfwtool: Add ability to split hash table

Hash table containing hashes of all signed PSP binaries is compiled at
build time and installed into the concerned CBFS. During boot, PSP
verstage reads the hash table binary and passes it to PSP bootloader.
PSP bootloader in turn uses the hash table to verify the signed PSP
binaries. Currently the hashes for all the signed PSP binaries are
compiled into one hash table. On upcoming platforms with more number of
signed PSP binaries, PSP bootloader does not have resources to handle
one monolithic hash table. Instead PSP bootloader recommends splitting
them into smaller hash tables (currently limited to 3 hash tables).

Update amdfwtool tool to support splitting hash tables. This is done by
adding an optional hash table id to the entries in the amdfw.cfg file.
By default, one hash table binary is always compiled and it's name is of
the format ${signed_rom}.hash. If an entry has a hash table id defined,
then this utility will compile a separate hash table binary whose name
is of the format ${signed_rom}.${N}.hash where N is the hash table id.

BUG=b:277292697
TEST=Build Skyrim BIOS image and boot to OS. Ensure that the hash table
is identical with and without this change. Perform suspend/resume
cycles, warm/cold reset cycles for 50 iterations each.
TEST=Artificially inject hash table id against some entries in
amdfw.cfg and ensure that the concerned hash table binaries are getting
compiled.

Change-Id: I7ef338d67695a34c33b5c166924832939f381191
Signed-off-by: Karthikeyan Ramasubramanian <kramasub@google.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/75188
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Raul Rangel <rrangel@chromium.org>
diff --git a/util/amdfwtool/amdfwtool.h b/util/amdfwtool/amdfwtool.h
index 7a2d24a..11b7996 100644
--- a/util/amdfwtool/amdfwtool.h
+++ b/util/amdfwtool/amdfwtool.h
@@ -356,6 +356,7 @@
 	/* Some files that don't have amd_fw_header have to be skipped from hashing. These files
 	   include but not limited to: *iKek*, *.tkn, *.stkn */
 	bool skip_hashing;
+	uint8_t hash_tbl_id;
 	uint32_t num_hash_entries;
 	amd_fw_entry_hash *hash_entries;
 	bool generate_manifest;
@@ -390,6 +391,9 @@
 	uint8_t reserved_80[128];
 } __packed;
 
+/* Based on the available PSP resources and increasing number of signed PSP binaries,
+   AMD recommends to split the hash table into 3 parts for now. */
+#define MAX_NUM_HASH_TABLES 3
 struct psp_fw_hash_table {
 	uint16_t version;
 	uint16_t no_of_entries_256;
diff --git a/util/amdfwtool/data_parse.c b/util/amdfwtool/data_parse.c
index b94e46f..c2dc3e2 100644
--- a/util/amdfwtool/data_parse.c
+++ b/util/amdfwtool/data_parse.c
@@ -46,6 +46,19 @@
 	      Lx1: Use default value for normal mode, level 1 for A/B mode
 	 */
 	"([[:space:]]+([Ll][12bxBX]{1,2}))?"
+	/* followed by an optional whitespace + chunk of nonwhitespace for hash table field
+	   1st char H: Indicator of field "Hash Table ID"
+	   2nd char:
+	      Table ID to be dropped in.
+	      0: Table 0 / Default Unified Table
+	      1: Table 1
+	      ...
+	      9: Table 9
+
+	   Examples:
+	      H2: Put the hash for the concerned entry in Hash Table 2
+	 */
+	"([[:space:]]+([Hh][0-9]+))?"
 	/* followed by optional whitespace */
 	"[[:space:]]*$";
 static regex_t entries_line_expr;
@@ -55,6 +68,8 @@
 	FW_FILE,
 	OPT_SPACE1,
 	OPT_LEVEL,
+	OPT_SPACE2,
+	OPT_HASH_TABLE_ID,
 	N_MATCHES,
 };
 
@@ -120,7 +135,7 @@
 extern amd_bios_entry amd_bios_table[];
 
 static uint8_t find_register_fw_filename_psp_dir(char *fw_name, char *filename,
-		char level_to_set, amd_cb_config *cb_config)
+		char level_to_set, uint8_t hash_tbl_id, amd_cb_config *cb_config)
 {
 	amd_fw_type fw_type = AMD_FW_INVALID;
 	amd_fw_entry *psp_tableptr;
@@ -477,6 +492,7 @@
 				psp_tableptr->filename = filename;
 				SET_LEVEL(psp_tableptr, level_to_set, PSP,
 					cb_config->recovery_ab);
+				psp_tableptr->hash_tbl_id = hash_tbl_id;
 				break;
 			}
 			psp_tableptr++;
@@ -584,10 +600,12 @@
 		/* match[1]: FW type
 		   match[2]: FW filename
 		   match[4]: Optional directory level to be dropped
+		   match[6]: Optional hash table ID to put the hash for the entry
 		 */
 		oneline[match[FW_TYPE].rm_eo] = '\0';
 		oneline[match[FW_FILE].rm_eo] = '\0';
 		oneline[match[OPT_LEVEL].rm_eo] = '\0';
+		oneline[match[OPT_HASH_TABLE_ID].rm_eo] = '\0';
 		retval = 1;
 	} else {
 		retval = 0;
@@ -611,11 +629,10 @@
 	return retval;
 }
 
-char get_level_from_config(char *line, regoff_t level_index, amd_cb_config *cb_config)
+static char get_level_from_config(char *line, regoff_t level_index, amd_cb_config *cb_config)
 {
 	char lvl = 'x';
-	/* If the optional level field is present,
-	   extract the level char. */
+	/* If the optional level field is present, extract the level char. */
 	if (level_index != -1) {
 		if (cb_config->recovery_ab == 0)
 			lvl = line[level_index + 1];
@@ -630,18 +647,29 @@
 	return lvl;
 }
 
+static uint8_t get_hash_tbl_id(char *line, regoff_t hash_tbl_index)
+{
+	uint8_t tbl = 0;
+	/* If the optional hash table field is present, extract the table id char. */
+	if (hash_tbl_index != -1)
+		tbl = (uint8_t)atoi(&line[hash_tbl_index + 1]);
+
+	assert(tbl < MAX_NUM_HASH_TABLES);
+	return tbl;
+}
+
 static uint8_t process_one_line(char *oneline, regmatch_t *match, char *dir,
 	amd_cb_config *cb_config)
 {
 	char *path_filename, *fn = &(oneline[match[FW_FILE].rm_so]);
 	char *fw_type_str = &(oneline[match[FW_TYPE].rm_so]);
-	char ch_lvl = 'x';
 	regoff_t ch_lvl_index = match[OPT_LEVEL].rm_so == match[OPT_LEVEL].rm_eo ?
 								-1 : match[OPT_LEVEL].rm_so;
-
-	/* If the optional level field is present,
-	   extract the level char. */
-	ch_lvl = get_level_from_config(oneline, ch_lvl_index, cb_config);
+	regoff_t ch_hash_tbl_index =
+		match[OPT_HASH_TABLE_ID].rm_so == match[OPT_HASH_TABLE_ID].rm_eo ?
+							-1 : match[OPT_HASH_TABLE_ID].rm_so;
+	char ch_lvl = get_level_from_config(oneline, ch_lvl_index, cb_config);
+	uint8_t ch_hash_tbl = get_hash_tbl_id(oneline, ch_hash_tbl_index);
 
 	path_filename = malloc(MAX_LINE_SIZE * 2 + 2);
 	if (strchr(fn, '/'))
@@ -652,7 +680,7 @@
 				MAX_LINE_SIZE, dir, MAX_LINE_SIZE, fn);
 
 	if (find_register_fw_filename_psp_dir(
-			fw_type_str, path_filename, ch_lvl, cb_config) == 0) {
+			fw_type_str, path_filename, ch_lvl, ch_hash_tbl, cb_config) == 0) {
 		if (find_register_fw_filename_bios_dir(
 				fw_type_str, path_filename, ch_lvl, cb_config) == 0) {
 			fprintf(stderr, "Module's name \"%s\" is not valid\n", fw_type_str);
diff --git a/util/amdfwtool/signed_psp.c b/util/amdfwtool/signed_psp.c
index 644e904..2e19f72 100644
--- a/util/amdfwtool/signed_psp.c
+++ b/util/amdfwtool/signed_psp.c
@@ -1,5 +1,6 @@
 /* SPDX-License-Identifier: GPL-2.0-only */
 
+#include <assert.h>
 #include <fcntl.h>
 #include <errno.h>
 #include <limits.h>
@@ -21,7 +22,14 @@
 	SIG_ID_RSA2048,
 	SIG_ID_RSA4096 = 2,
 };
+
 #define HASH_FILE_SUFFIX ".hash"
+struct psp_fw_hash_file_info {
+	int fd;
+	bool present;
+	struct psp_fw_hash_table hash_header;
+};
+static struct psp_fw_hash_file_info hash_files[MAX_NUM_HASH_TABLES];
 
 static uint16_t get_psp_fw_type(enum platform soc_id, struct amd_fw_header *header)
 {
@@ -126,26 +134,78 @@
 	write_or_fail(fd, entry->sha, entry->sha_len);
 }
 
-static void write_psp_firmware_hash(const char *filename,
-		amd_fw_entry *fw_table)
+static void open_psp_fw_hash_files(const char *file_prefix)
 {
-	struct psp_fw_hash_table hash_header = {0};
-	int fd = open(filename, O_RDWR | O_CREAT | O_TRUNC, 0666);
+	size_t hash_file_strlen;
+	char *hash_file_name;
 
-	if (fd < 0) {
-		fprintf(stderr, "Error opening file: %s: %s\n",
-				filename, strerror(errno));
+	/* Hash Table ID is part of the file name. For now only single digit ID is
+	   supported and is sufficient. Hence assert MAX_NUM_HASH_TABLES < 10 before
+	   constructing file name. Revisit later when > 10 hash tables are required. */
+	assert(MAX_NUM_HASH_TABLES < 10);
+	/* file_prefix + ".[1-9]" + ".hash" + '\0' */
+	hash_file_strlen = strlen(file_prefix) + 2 + strlen(HASH_FILE_SUFFIX) + 1;
+	hash_file_name = malloc(hash_file_strlen);
+	if (!hash_file_name)  {
+		fprintf(stderr, "malloc(%lu) failed\n", hash_file_strlen);
 		exit(-1);
 	}
 
-	hash_header.version = HASH_HDR_V1;
+	for (unsigned int i = 0; i < MAX_NUM_HASH_TABLES; i++) {
+		/* Hash table IDs are expected to be contiguous and hence holes are not
+		   expected. */
+		if (!hash_files[i].present)
+			break;
+
+		if (i)
+			snprintf(hash_file_name, hash_file_strlen, "%s.%d%s",
+				 file_prefix, i, HASH_FILE_SUFFIX);
+		else
+			/* Default file name without number for backwards compatibility. */
+			snprintf(hash_file_name, hash_file_strlen, "%s%s",
+				 file_prefix, HASH_FILE_SUFFIX);
+
+		hash_files[i].fd = open(hash_file_name, O_RDWR | O_CREAT | O_TRUNC, 0666);
+		if (hash_files[i].fd < 0) {
+			fprintf(stderr, "Error opening file: %s: %s\n",
+					hash_file_name, strerror(errno));
+			free(hash_file_name);
+			exit(-1);
+		}
+	}
+	free(hash_file_name);
+}
+
+static void close_psp_fw_hash_files(void)
+{
+	for (unsigned int i = 0; i < MAX_NUM_HASH_TABLES; i++) {
+		if (!hash_files[i].present)
+			break;
+
+		close(hash_files[i].fd);
+	}
+}
+
+static void write_psp_firmware_hash(amd_fw_entry *fw_table)
+{
+	uint8_t hash_tbl_id;
+
+	for (unsigned int i = 0; i < MAX_NUM_HASH_TABLES; i++) {
+		if (!hash_files[i].present)
+			continue;
+		hash_files[i].hash_header.version = HASH_HDR_V1;
+	}
+
 	for (unsigned int i = 0; fw_table[i].type != AMD_FW_INVALID; i++) {
+		hash_tbl_id = fw_table[i].hash_tbl_id;
+		assert(hash_files[hash_tbl_id].present);
+
 		for (unsigned int j = 0; j < fw_table[i].num_hash_entries; j++) {
 			if (fw_table[i].hash_entries[j].sha_len == SHA256_DIGEST_LENGTH) {
-				hash_header.no_of_entries_256++;
+				hash_files[hash_tbl_id].hash_header.no_of_entries_256++;
 			} else if (fw_table[i].hash_entries[j].sha_len ==
 								SHA384_DIGEST_LENGTH) {
-				hash_header.no_of_entries_384++;
+				hash_files[hash_tbl_id].hash_header.no_of_entries_384++;
 			} else if (fw_table[i].hash_entries[j].sha_len) {
 				fprintf(stderr, "%s: Error invalid sha_len %d\n",
 						__func__, fw_table[i].hash_entries[j].sha_len);
@@ -154,28 +214,34 @@
 		}
 	}
 
-	write_or_fail(fd, &hash_header, sizeof(hash_header));
+	for (unsigned int i = 0; i < MAX_NUM_HASH_TABLES; i++) {
+		if (!hash_files[i].present)
+			continue;
+		write_or_fail(hash_files[i].fd, &hash_files[i].hash_header,
+						sizeof(hash_files[i].hash_header));
+	}
 
 	/* Add all the SHA256 hash entries first followed by SHA384 entries. PSP verstage
 	   processes the table in that order. Mixing and matching SHA256 and SHA384 entries
 	   will cause the hash verification failure at run-time. */
 	for (unsigned int i = 0; fw_table[i].type != AMD_FW_INVALID; i++) {
+		hash_tbl_id = fw_table[i].hash_tbl_id;
 		for (unsigned int j = 0; j < fw_table[i].num_hash_entries; j++) {
 			if (fw_table[i].hash_entries[j].sha_len == SHA256_DIGEST_LENGTH)
-				write_one_psp_firmware_hash_entry(fd,
-						&fw_table[i].hash_entries[j]);
+				write_one_psp_firmware_hash_entry(hash_files[hash_tbl_id].fd,
+								&fw_table[i].hash_entries[j]);
 		}
 	}
 
 	for (unsigned int i = 0; fw_table[i].type != AMD_FW_INVALID; i++) {
+		hash_tbl_id = fw_table[i].hash_tbl_id;
 		for (unsigned int j = 0; j < fw_table[i].num_hash_entries; j++) {
 			if (fw_table[i].hash_entries[j].sha_len == SHA384_DIGEST_LENGTH)
-				write_one_psp_firmware_hash_entry(fd,
-						&fw_table[i].hash_entries[j]);
+				write_one_psp_firmware_hash_entry(hash_files[hash_tbl_id].fd,
+								&fw_table[i].hash_entries[j]);
 		}
 	}
 
-	close(fd);
 	for (unsigned int i = 0; fw_table[i].type != AMD_FW_INVALID; i++) {
 		if (!fw_table[i].num_hash_entries || !fw_table[i].hash_entries)
 			continue;
@@ -204,8 +270,6 @@
 	int signed_rom_fd;
 	ssize_t bytes, align_bytes;
 	uint8_t *buf;
-	char *signed_rom_hash;
-	size_t signed_rom_hash_strlen;
 	struct amd_fw_header header;
 	struct stat fd_stat;
 	/* Every blob in amdfw*.rom has to start at address aligned to 0x100. Prepare an
@@ -323,6 +387,7 @@
 		fw_table[i].fw_id = get_psp_fw_type(soc_id, &header);
 		fw_table[i].addr_signed = signed_start_addr;
 		fw_table[i].file_size = (uint32_t)fd_stat.st_size;
+		hash_files[fw_table[i].hash_tbl_id].present = true;
 
 		signed_start_addr += fd_stat.st_size + align_bytes;
 
@@ -332,15 +397,7 @@
 
 	close(signed_rom_fd);
 
-	/* signed_rom file name + ".hash" + '\0' */
-	signed_rom_hash_strlen = strlen(signed_rom) + strlen(HASH_FILE_SUFFIX) + 1;
-	signed_rom_hash = malloc(signed_rom_hash_strlen);
-	if (!signed_rom_hash) {
-		fprintf(stderr, "malloc(%lu) failed\n", signed_rom_hash_strlen);
-		exit(-1);
-	}
-	strcpy(signed_rom_hash, signed_rom);
-	strcat(signed_rom_hash, HASH_FILE_SUFFIX);
-	write_psp_firmware_hash(signed_rom_hash, fw_table);
-	free(signed_rom_hash);
+	open_psp_fw_hash_files(signed_rom);
+	write_psp_firmware_hash(fw_table);
+	close_psp_fw_hash_files();
 }