Robert Zieba has uploaded this change for review. ( https://review.coreboot.org/c/coreboot/+/62795 )
Change subject: util: Add amdfwread utility ......................................................................
util: Add amdfwread utility
Amdfwtool creates AMD firmware images however there is currently no way to get information from an existing image. This commit adds amdfwread to support that functionality. At the moment only reading PSP soft fuse flags is supported.
BUG=b:202397678 TEST=Ran amdfwread and verified that it correctly read the soft fuse bits
Signed-off-by: Robert Zieba robertzieba@google.com Change-Id: I15fa07c9cad8e4640e9c40e5539b0dab44424850 --- A util/amdfwread/Makefile A util/amdfwread/amdfwread.c A util/amdfwread/description.md 3 files changed, 365 insertions(+), 0 deletions(-)
git pull ssh://review.coreboot.org:29418/coreboot refs/changes/95/62795/1
diff --git a/util/amdfwread/Makefile b/util/amdfwread/Makefile new file mode 100644 index 0000000..8cab478 --- /dev/null +++ b/util/amdfwread/Makefile @@ -0,0 +1,30 @@ +# SPDX-License-Identifier: BSD-3-Clause + +HOSTCC ?= cc + +SRC = amdfwread.c +OBJ = $(SRC:%.c=%.o) +TARGET = amdfwread +WERROR=-Werror -Wno-unused-function +CFLAGS=-O2 -Wall -Wextra -Wshadow ${WERROR} + +all: $(TARGET) + +$(TARGET): $(OBJ) + $(CC) $(OBJ) $(LDFLAGS) -o $@ + +%.o: %.c $(HEADER) + $(CC) $(CFLAGS) -c -o $@ $< + +clean: + @rm -f $(TARGET) $(OBJ) + +distclean: clean + +help: + @echo "${TARGET}: Create AMD Firmware combination" + @echo "Targets: all, clean, distclean, help" + @echo "To disable warnings as errors, run make as:" + @echo " make all WERROR=""" + +.PHONY: all clean distclean help diff --git a/util/amdfwread/amdfwread.c b/util/amdfwread/amdfwread.c new file mode 100644 index 0000000..055065b --- /dev/null +++ b/util/amdfwread/amdfwread.c @@ -0,0 +1,334 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#include <getopt.h> +#include <stddef.h> +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> +#include <unistd.h> + +#define ERR(...) fprintf(stderr, __VA_ARGS__) + +/* Structs and enums taken from amdfwtool */ +typedef enum _amd_fw_type { + AMD_FW_PSP_PUBKEY = 0, + AMD_FW_PSP_BOOTLOADER = 1, + AMD_FW_PSP_SMU_FIRMWARE = 8, + AMD_FW_PSP_RECOVERY = 3, + AMD_FW_PSP_RTM_PUBKEY = 5, + AMD_FW_PSP_SECURED_OS = 2, + AMD_FW_PSP_NVRAM = 4, + AMD_FW_PSP_SECURED_DEBUG = 9, + AMD_FW_PSP_TRUSTLETS = 12, + AMD_FW_PSP_TRUSTLETKEY = 13, + AMD_FW_PSP_SMU_FIRMWARE2 = 18, + AMD_PSP_FUSE_CHAIN = 11, + AMD_FW_PSP_SMUSCS = 95, + AMD_DEBUG_UNLOCK = 0x13, + AMD_HW_IPCFG = 0x20, + AMD_WRAPPED_IKEK = 0x21, + AMD_TOKEN_UNLOCK = 0x22, + AMD_SEC_GASKET = 0x24, + AMD_MP2_FW = 0x25, + AMD_DRIVER_ENTRIES = 0x28, + AMD_FW_KVM_IMAGE = 0x29, + AMD_S0I3_DRIVER = 0x2d, + AMD_ABL0 = 0x30, + AMD_ABL1 = 0x31, + AMD_ABL2 = 0x32, + AMD_ABL3 = 0x33, + AMD_ABL4 = 0x34, + AMD_ABL5 = 0x35, + AMD_ABL6 = 0x36, + AMD_ABL7 = 0x37, + AMD_FW_PSP_WHITELIST = 0x3a, + AMD_VBIOS_BTLOADER = 0x3c, + AMD_FW_L2_PTR = 0x40, + AMD_FW_USB_PHY = 0x44, + AMD_FW_TOS_SEC_POLICY = 0x45, + AMD_FW_DRTM_TA = 0x47, + AMD_FW_RECOVERYAB_A = 0x48, + AMD_FW_RECOVERYAB_B = 0x4A, + AMD_FW_BIOS_TABLE = 0x49, + AMD_FW_KEYDB_BL = 0x50, + AMD_FW_KEYDB_TOS = 0x51, + AMD_FW_PSP_VERSTAGE = 0x52, + AMD_FW_VERSTAGE_SIG = 0x53, + AMD_RPMC_NVRAM = 0x54, + AMD_FW_SPL = 0x55, + AMD_FW_DMCU_ERAM = 0x58, + AMD_FW_DMCU_ISR = 0x59, + AMD_FW_PSP_BOOTLOADER_AB = 0x73, + AMD_FW_IMC = 0x200, /* Large enough to be larger than the top BHD entry type. */ + AMD_FW_GEC, + AMD_FW_XHCI, + AMD_FW_INVALID, /* Real last one to detect the last entry in table. */ + AMD_FW_SKIP /* This is for non-applicable options. */ +} amd_fw_type; + +struct second_gen_efs { /* todo: expand for Server products */ + int gen:1; /* Client products only use bit 0 */ + int reserved:31; +} __attribute__((packed)); + +typedef struct _embedded_firmware { + uint32_t signature; /* 0x55aa55aa */ + uint32_t imc_entry; + uint32_t gec_entry; + uint32_t xhci_entry; + uint32_t psp_directory; + union { + uint32_t new_psp_directory; + uint32_t combo_psp_directory; + }; + uint32_t bios0_entry; /* todo: add way to select correct entry */ + uint32_t bios1_entry; + uint32_t bios2_entry; + struct second_gen_efs efs_gen; + uint32_t bios3_entry; + uint32_t reserved_2Ch; + uint32_t promontory_fw_ptr; + uint32_t lp_promontory_fw_ptr; + uint32_t reserved_38h; + uint32_t reserved_3Ch; + uint8_t spi_readmode_f15_mod_60_6f; + uint8_t fast_speed_new_f15_mod_60_6f; + uint8_t reserved_42h; + uint8_t spi_readmode_f17_mod_00_2f; + uint8_t spi_fastspeed_f17_mod_00_2f; + uint8_t qpr_dummy_cycle_f17_mod_00_2f; + uint8_t reserved_46h; + uint8_t spi_readmode_f17_mod_30_3f; + uint8_t spi_fastspeed_f17_mod_30_3f; + uint8_t micron_detect_f17_mod_30_3f; + uint8_t reserved_4Ah; + uint8_t reserved_4Bh; + uint32_t reserved_4Ch; +} __attribute__((packed, aligned(16))) embedded_firmware; + +typedef struct _psp_directory_header { + uint32_t cookie; + uint32_t checksum; + uint32_t num_entries; + union { + uint32_t additional_info; + struct { + uint32_t dir_size:10; + uint32_t spi_block_size:4; + uint32_t base_addr:15; + uint32_t address_mode:2; + uint32_t not_used:1; + } __attribute__((packed)) additional_info_fields; + }; +} __attribute__((packed, aligned(16))) psp_directory_header; + +typedef struct _psp_directory_entry { + uint8_t type; + uint8_t subprog; + uint16_t rsvd; + uint32_t size; + uint64_t addr:62; /* or a value in some cases */ + uint64_t address_mode:2; +} __attribute__((packed)) psp_directory_entry; + +typedef struct _amd_fw_entry { + amd_fw_type type; + char *filename; + uint8_t subprog; + int level; + uint64_t other; +} amd_fw_entry; + +#define EMBEDDED_FW_SIGNATURE 0x55aa55aa +#define PSP_COOKIE 0x50535024 /* 'PSP$' */ +#define PSPL2_COOKIE 0x324c5024 /* '2LP$' */ + +/* Possible locations for the header */ +const uint32_t fw_header_offsets[] = { + 0xfa0000, + 0xe20000, + 0xc20000, + 0x820000, + 0x020000, +}; + +int read_fw_header(FILE *fw, uint32_t offset, embedded_firmware *fw_header) +{ + if (fseek(fw, offset, SEEK_SET) != 0) { + ERR("Failed to seek to fw header offset 0x%x\n", offset); + return 1; + } + + if (fread(fw_header, sizeof(embedded_firmware), 1, fw) != 1) { + ERR("Failed to read fw header at 0x%x\n", offset); + return 1; + } + + return fw_header->signature != EMBEDDED_FW_SIGNATURE; +} + +int read_psp_directory(FILE *fw, uint32_t offset, uint32_t expected_cookie, + psp_directory_entry **entries, size_t *num_entries) +{ + /* Ensure the offset is relative to the file start instead of a direct + memory address. */ + offset &= 0xFFFFFF; + if (fseek(fw, offset, SEEK_SET) != 0) { + ERR("Failed to seek to PSP header at 0x%x\n", offset); + return 1; + } + + psp_directory_header header; + if (fread(&header, sizeof(psp_directory_header), 1, fw) != 1) { + ERR("Failed to read PSP header cookie\n"); + return 1; + } + + /* Ensure that we have a PSP directory. */ + if (header.cookie != expected_cookie) { + ERR("Invalid PSP header cookie value found: 0x%x, expected: 0x%x\n", + expected_cookie, header.cookie); + return 1; + } + + /* Read the entries. */ + *num_entries = header.num_entries; + *entries = malloc(sizeof(psp_directory_entry) * header.num_entries); + if (fread(*entries, sizeof(psp_directory_entry), header.num_entries, fw) + != header.num_entries) { + ERR("Failed to read %d PSP entries\n", header.num_entries); + return 1; + } + + return 0; +} + +int read_soft_fuse(FILE *fw, const embedded_firmware *fw_header) +{ + psp_directory_entry *current_entries = NULL; + size_t num_current_entries = 0; + + 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; + + if (read_psp_directory(fw, psp_offset, PSP_COOKIE, ¤t_entries, + &num_current_entries) != 0) + return 1; + + while (1) { + uint32_t l2_dir_offset = 0; + + for (size_t i = 0; i < num_current_entries; i++) { + uint32_t type = current_entries[i].type; + if (type == AMD_PSP_FUSE_CHAIN) { + amd_fw_entry *entry = (amd_fw_entry *)¤t_entries[i]; + printf("Soft-fuse:0x%lX\n", entry->other); + } else if (type == AMD_FW_L2_PTR) { + /* There's a second level PSP directory to read. */ + if (l2_dir_offset != 0) + return 1; + + l2_dir_offset = current_entries[i].addr; + } + } + + free(current_entries); + + /* Didn't find an L2 PSP directory so we can stop. */ + if (l2_dir_offset == 0) + break; + + /* Read the L2 PSP directory. */ + if (read_psp_directory(fw, l2_dir_offset, PSPL2_COOKIE, ¤t_entries, + &num_current_entries) != 0) + return 1; + } + + return 0; +} + +enum { + AMDFW_OPT_HELP = 'h', + + /* When bit 31 is set, options are a bitfield of info to print from the + firmware image. */ + AMDFW_OPT_SOFT_FUSE = 0xF0000001, +}; + +static char const optstring[] = {AMDFW_OPT_HELP}; + +static struct option long_options[] = { + {"help", no_argument, 0, AMDFW_OPT_HELP}, + {"soft-fuse", no_argument, 0, AMDFW_OPT_SOFT_FUSE}, +}; + +void print_usage() +{ + printf("amdfwread: Examine AMD firmware images\n"); + printf("Usage: amdfwread [options] <file>\n"); + printf("--soft-fuse Print soft fuse value\n"); +} + +int main(int argc, char **argv) +{ + char *fw_file = NULL; + + int selected_functions = 0; + int index = 0; + while (1) { + int opt = getopt_long(argc, argv, optstring, long_options, &index); + + if (opt == -1) { + index++; + if (index >= argc) + return 1; + + fw_file = argv[index]; + break; + } + + if (opt == AMDFW_OPT_HELP) { + print_usage(); + return 0; + } + + selected_functions |= opt; + } + + if (selected_functions == 0) { + print_usage(); + return 0; + } + + FILE *fw = fopen(fw_file, "rb"); + if (!fw) { + ERR("Failed to open FW file %s\n", fw_file); + return 1; + } + + /* Find the FW header by checking each possible location */ + embedded_firmware fw_header; + int found_header = 0; + for (size_t i = 0; i < sizeof(fw_header_offsets) / sizeof(fw_header_offsets[0]); i++) { + if (read_fw_header(fw, fw_header_offsets[i], &fw_header) == 0) { + found_header = 1; + break; + } + } + + if (!found_header) { + ERR("Failed to find FW header\n"); + return 1; + } + + if (selected_functions & AMDFW_OPT_SOFT_FUSE) { + if (read_soft_fuse(fw, &fw_header) != 0) + return 1; + } + + fclose(fw); + return 0; +} \ No newline at end of file diff --git a/util/amdfwread/description.md b/util/amdfwread/description.md new file mode 100644 index 0000000..8d2b55d --- /dev/null +++ b/util/amdfwread/description.md @@ -0,0 +1 @@ +Read information from AMD Firmware combination `C`