util/amdfwtool/amdfwread: List AMDFW RO binary entries

Add support to walk through PSP L1, PSP L2, BIOS L1, BIOS L2 directories
and list the entries present in them. Accommodate both recovery A/B
layout and normal layout. This is required to identify the location and
size of each entries in the finally built amdfw.rom. This in turn can be
used to perform any platform specific verification on the relevant
components.

BUG=None
TEST=Build and list the contents of AMDFW binary.
/usr/bin/amdfwread --ro-list /build/skyrim/firmware/image-skyrim.bin
Table: FW   Offset     Size
PSPL1: Dir  0x00d97000
+-->PSPL1: 0x48 0x00d98000 0x00001000
    +-->PSPL2: Dir  0x00c30000
        +-->PSPL2: 0x00 0x00c31000 0x00000440
        +-->PSPL2: 0x01 0x00c31500 0x00007580
        +-->PSPL2: 0x02 0x00c38b00 0x00019470
        +-->PSPL2: 0x08 0x00c52000 0x0001f560
        +-->PSPL2: 0x09 0x00c71600 0x00000440
        +-->PSPL2: 0x0b 0x430000041(Soft-fuse)
        +-->PSPL2: 0x0c 0x00c71b00 0x00023100
        +-->PSPL2: 0x12 0x00c94c00 0x00015890
        +-->PSPL2: 0x13 0x00caa500 0x000021c0
        +-->PSPL2: 0x20 0x00cac700 0x00000640
        +-->PSPL2: 0x21 0x00cace00 0x00000030
        +-->PSPL2: 0x22 0x00cad000 0x00001000
        +-->PSPL2: 0x24 0x00cae000 0x00003b60
        +-->PSPL2: 0x28 0x00cb1c00 0x00022890
        +-->PSPL2: 0x2d 0x00cd4500 0x00003100
        +-->PSPL2: 0x30 0x00cd7600 0x0006b550
        +-->PSPL2: 0x3a 0x00d42c00 0x000006d0
        +-->PSPL2: 0x3c 0x00d43300 0x000018c0
        +-->PSPL2: 0x44 0x00d44c00 0x00006610
        +-->PSPL2: 0x45 0x00d4b300 0x00001c70
        +-->PSPL2: 0x50 0x00d4d000 0x00001a00
        +-->PSPL2: 0x51 0x00d4ea00 0x00001020
        +-->PSPL2: 0x52 0x00d4fb00 0x00010180
        +-->PSPL2: 0x55 0x00d5fd00 0x00000600
        +-->PSPL2: 0x5a 0x00d60300 0x00000570
        +-->PSPL2: 0x5c 0x00d60900 0x00000b20
        +-->PSPL2: 0x71 0x00d61500 0x00024710
        +-->PSPL2: 0x73 0x00d85d00 0x00010640
        +-->PSPL2: 0x8d 0x00d96400 0x00000030
        +-->PSPL2: 0x49 0x00d99000 0x00001000
            +-->BIOSL2: Dir  0x00d99000
                +-->BIOSL2: 0x60 0x00d9a000 0x00009924
                +-->BIOSL2: 0x68 0x00da4000 0x00009924
                +-->BIOSL2: 0x61 0x2001000(DRAM-Address)
                +-->BIOSL2: 0x62 0x00dada00 0x00010000
                +-->BIOSL2: 0x63 0x00000000 0x0001e000
                +-->BIOSL2: 0x64 0x00db4200 0x00006310
                +-->BIOSL2: 0x65 0x00dba600 0x000004e0
                +-->BIOSL2: 0x64 0x00dbab00 0x00006180
                +-->BIOSL2: 0x65 0x00dc0d00 0x00000250
                +-->BIOSL2: 0x6b 0x201f000(DRAM-Address)
+-->PSPL1: 0x4a 0x00d98000 0x00001000

Change-Id: Ia1b8f1a2b9bc7dc6925a305cdff1442aaff182cd
Signed-off-by: Karthikeyan Ramasubramanian <kramasub@google.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/66761
Reviewed-by: Raul Rangel <rrangel@chromium.org>
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
diff --git a/util/amdfwtool/amdfwread.c b/util/amdfwtool/amdfwread.c
index a65fb26..711b158 100644
--- a/util/amdfwtool/amdfwread.c
+++ b/util/amdfwtool/amdfwread.c
@@ -4,6 +4,7 @@
 #include <stdio.h>
 #include <stdint.h>
 #include <stdlib.h>
+#include <string.h>
 #include <unistd.h>
 #include "amdfwtool.h"
 
@@ -116,6 +117,36 @@
 	return read_header(fw, offset & FILE_REL_MASK, table, sizeof(*table));
 }
 
+static int read_bios_directory(FILE *fw, uint32_t offset, uint32_t expected_cookie,
+			bios_directory_hdr *header, bios_directory_entry **entries,
+			size_t *num_entries)
+{
+	offset &= FILE_REL_MASK;
+
+	if (read_header(fw, offset, header, sizeof(bios_directory_hdr))) {
+		ERR("Failed to read BIOS header\n");
+		return 1;
+	}
+
+	/* Ensure that we have a BIOS directory */
+	if (header->cookie != expected_cookie) {
+		ERR("Invalid BIOS header cookie value found: 0x%x, expected: 0x%x\n",
+			header->cookie, expected_cookie);
+		return 1;
+	}
+
+	/* Read the entries */
+	*num_entries = header->num_entries;
+	*entries = malloc(sizeof(bios_directory_entry) * header->num_entries);
+	if (fread(*entries, sizeof(bios_directory_entry), header->num_entries, fw)
+		!= header->num_entries) {
+		ERR("Failed to read %d BIOS entries\n", header->num_entries);
+		return 1;
+	}
+
+	return 0;
+}
+
 static int read_soft_fuse(FILE *fw, const embedded_firmware *fw_header)
 {
 	psp_directory_entry *current_entries = NULL;
@@ -203,9 +234,189 @@
 	return 1;
 }
 
+#define MAX_NUM_LEVELS 10
+#define MAX_INDENT_PER_LEVEL 4
+#define MAX_INDENTATION_LEN (MAX_NUM_LEVELS * MAX_INDENT_PER_LEVEL + 1)
+static void do_indentation_string(char *dest, uint8_t level)
+{
+	for (uint8_t i = 0; i < level && i < MAX_NUM_LEVELS; i++)
+		strcat(dest, "    ");
+	strcat(dest, "+-->");
+}
+
+static int amdfw_bios_dir_walk(FILE *fw, uint32_t bios_offset, uint32_t cookie, uint8_t level)
+{
+	bios_directory_entry *current_entries = NULL;
+	size_t num_current_entries = 0;
+	bios_directory_hdr header;
+	uint32_t l2_dir_offset = 0;
+	char indent[MAX_INDENTATION_LEN] = {0};
+
+	if (read_bios_directory(fw, bios_offset, cookie, &header,
+		       &current_entries, &num_current_entries) != 0)
+		return 1;
+
+	do_indentation_string(indent, level);
+	for (size_t i = 0; i < num_current_entries; i++) {
+		uint32_t type = current_entries[i].type;
+		uint64_t mode = current_entries[i].address_mode;
+		uint64_t addr = current_entries[i].source;
+
+		if (type == AMD_BIOS_APOB || type == AMD_BIOS_PSP_SHARED_MEM)
+			printf("%sBIOS%s: 0x%02x 0x%lx(DRAM-Address)\n",
+				indent, cookie == BHD_COOKIE ? "L1" : "L2",
+				type, current_entries[i].dest);
+		else if (type == AMD_BIOS_APOB_NV)
+			printf("%sBIOS%s: 0x%02x 0x%08lx 0x%08x\n",
+				indent, cookie == BHD_COOKIE ? "L1" : "L2",
+				type, relative_offset(bios_offset, addr, AMD_ADDR_PHYSICAL),
+				current_entries[i].size);
+		else
+			printf("%sBIOS%s: 0x%02x 0x%08lx 0x%08x\n",
+				indent, cookie == BHD_COOKIE ? "L1" : "L2",
+				type, relative_offset(bios_offset, addr, mode),
+				current_entries[i].size);
+
+		if (type == AMD_BIOS_L2_PTR) {
+			/* There's a second level BIOS directory to read */
+			if (l2_dir_offset != 0) {
+				ERR("Duplicate BIOS L2 Entry, prior offset: %08x\n",
+									l2_dir_offset);
+				free(current_entries);
+				return 1;
+			}
+
+			l2_dir_offset = relative_offset(bios_offset, addr, mode);
+			printf("    %sBIOSL2: Dir  0x%08x\n", indent, l2_dir_offset);
+			amdfw_bios_dir_walk(fw, l2_dir_offset, BHDL2_COOKIE, level + 2);
+		}
+	}
+
+	free(current_entries);
+	return 0;
+}
+
+static int amdfw_psp_dir_walk(FILE *fw, uint32_t psp_offset, uint32_t cookie, uint8_t level)
+{
+	psp_directory_entry *current_entries = NULL;
+	size_t num_current_entries = 0;
+	psp_directory_header header;
+	uint32_t l2_dir_offset = 0;
+	uint32_t bios_dir_offset = 0;
+	uint32_t ish_dir_offset = 0;
+	ish_directory_table ish_dir;
+	char indent[MAX_INDENTATION_LEN] = {0};
+
+	if (read_psp_directory(fw, psp_offset, cookie, &header,
+		       &current_entries, &num_current_entries) != 0)
+		return 1;
+
+	do_indentation_string(indent, level);
+	for (size_t i = 0; i < num_current_entries; i++) {
+		uint32_t type = current_entries[i].type;
+		uint64_t mode = current_entries[i].address_mode;
+		uint64_t addr = current_entries[i].addr;
+
+		if (type == AMD_PSP_FUSE_CHAIN)
+			printf("%sPSP%s: 0x%02x 0x%lx(Soft-fuse)\n",
+				indent, cookie == PSP_COOKIE ? "L1" : "L2",
+				type, mode << 62 | addr);
+		else
+			printf("%sPSP%s: 0x%02x 0x%08lx 0x%08x\n",
+				indent, cookie == PSP_COOKIE ? "L1" : "L2",
+				type, relative_offset(psp_offset, addr, mode),
+				current_entries[i].size);
+
+		switch (type) {
+		case AMD_FW_L2_PTR:
+			/* There's a second level PSP directory to read */
+			if (l2_dir_offset != 0) {
+				ERR("Duplicate PSP L2 Entry, prior offset: %08x\n",
+									l2_dir_offset);
+				free(current_entries);
+				return 1;
+			}
+
+			l2_dir_offset = relative_offset(psp_offset, addr, mode);
+			printf("    %sPSPL2: Dir  0x%08x\n", indent, l2_dir_offset);
+			amdfw_psp_dir_walk(fw, l2_dir_offset, PSPL2_COOKIE, level + 2);
+			break;
+
+		case AMD_FW_RECOVERYAB_A:
+			if (l2_dir_offset != 0) {
+				ERR("Duplicate PSP L2 Entry, prior offset: %08x\n",
+									l2_dir_offset);
+				free(current_entries);
+				return 1;
+			}
+
+			ish_dir_offset = relative_offset(psp_offset, addr, mode);
+			if (read_ish_directory(fw, ish_dir_offset, &ish_dir) != 0) {
+				ERR("Error reading ISH directory\n");
+				free(current_entries);
+				return 1;
+			}
+
+			l2_dir_offset = ish_dir.pl2_location;
+			printf("    %sPSPL2: Dir  0x%08x\n", indent, l2_dir_offset);
+			amdfw_psp_dir_walk(fw, l2_dir_offset, PSPL2_COOKIE, level + 2);
+			break;
+
+		case AMD_FW_BIOS_TABLE:
+			bios_dir_offset = relative_offset(psp_offset, addr, mode);
+			printf("    %sBIOSL2: Dir  0x%08x\n", indent, bios_dir_offset);
+			amdfw_bios_dir_walk(fw, bios_dir_offset, BHDL2_COOKIE, level + 2);
+			break;
+
+		default:
+			/* No additional processing required, continue to the next entry. */
+			break;
+		}
+	}
+
+	free(current_entries);
+	return 0;
+}
+
+static int list_amdfw_psp_dir(FILE *fw, const embedded_firmware *fw_header)
+{
+	uint32_t psp_offset = 0;
+
+	/* 0xffffffff indicates that the offset is in new_psp_directory */
+	if (fw_header->psp_directory != 0xffffffff)
+		psp_offset = fw_header->psp_directory;
+	else
+		psp_offset = fw_header->new_psp_directory;
+
+	printf("PSPL1: Dir  0x%08x\n", psp_offset);
+	amdfw_psp_dir_walk(fw, psp_offset, PSP_COOKIE, 0);
+	return 0;
+}
+
+static int list_amdfw_bios_dir(FILE *fw, const embedded_firmware *fw_header)
+{
+	/* 0xffffffff implies that the SoC uses recovery A/B layout. Only BIOS L2 directory
+	   is present and that too as part of PSP L2 directory. */
+	if (fw_header->bios3_entry != 0xffffffff) {
+		printf("BIOSL1: Dir  0x%08x\n", fw_header->bios3_entry);
+		amdfw_bios_dir_walk(fw, fw_header->bios3_entry, BHD_COOKIE, 0);
+	}
+	return 0;
+}
+
+
+static int list_amdfw_ro(FILE *fw, const embedded_firmware *fw_header)
+{
+	printf("Table: FW   Offset     Size\n");
+	list_amdfw_psp_dir(fw, fw_header);
+	list_amdfw_bios_dir(fw, fw_header);
+	return 0;
+}
+
 enum {
 	AMDFW_OPT_HELP = 'h',
 	AMDFW_OPT_SOFT_FUSE = 1UL << 0, /* Print Softfuse */
+	AMDFW_OPT_RO_LIST = 1UL << 1, /* List entries in AMDFW RO */
 };
 
 static char const optstring[] = {AMDFW_OPT_HELP};
@@ -213,6 +424,7 @@
 static struct option long_options[] = {
 	{"help", no_argument, 0, AMDFW_OPT_HELP},
 	{"soft-fuse", no_argument, 0, AMDFW_OPT_SOFT_FUSE},
+	{"ro-list", no_argument, 0, AMDFW_OPT_RO_LIST},
 };
 
 static void print_usage(void)
@@ -220,6 +432,7 @@
 	printf("amdfwread: Examine AMD firmware images\n");
 	printf("Usage: amdfwread [options] <file>\n");
 	printf("--soft-fuse                Print soft fuse value\n");
+	printf("--ro-list                  List the programs under AMDFW in RO region\n");
 }
 
 int main(int argc, char **argv)
@@ -248,6 +461,7 @@
 			return 0;
 
 		case AMDFW_OPT_SOFT_FUSE:
+		case AMDFW_OPT_RO_LIST:
 			selected_functions |= opt;
 			break;
 
@@ -285,6 +499,13 @@
 		}
 	}
 
+	if (selected_functions & AMDFW_OPT_RO_LIST) {
+		if (list_amdfw_ro(fw, &fw_header) != 0) {
+			fclose(fw);
+			return 1;
+		}
+	}
+
 	fclose(fw);
 	return 0;
 }