util/amdtools: Add ec_usb_pd_fw

ec_usb_pd_fw is a board specific utility to generate pointers to
firmware images found in the SPI flash. On some AMD boards the
x86 SPI flash is shared with the EC. The EC can also update the
USB Power Delivery controllers firmware, but it needs to know where
to load the firmware from. It uses pointers stored in the first
128 bytes of the x86 SPI flash.

Add a small utility to generate pointers to the USB PD firmware,
located somewhere in the ROM identified by the FMAP region.

There can be up to 12 USB PD firmwares, depending on the used
vendor or model.

Change-Id: I98717e849592f83eb7bacbfed33a8d4b811a5e18
Signed-off-by: Patrick Rudolph <patrick.rudolph@9elements.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/87430
Reviewed-by: David Hendricks <david.hendricks@gmail.com>
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
This commit is contained in:
Patrick Rudolph 2025-04-23 15:17:14 +02:00 committed by Matt DeVillier
commit 5a0953614b
8 changed files with 400 additions and 0 deletions

View file

@ -0,0 +1,55 @@
# SPDX-License-Identifier: GPL-2.0-or-later
PRG := ec_usb_pd_fw
TOP ?= $(abspath ../../..)
ROOT := $(TOP)/src
CC ?= $(CROSS_COMPILE)gcc
HOSTCC ?= $(CC)
INSTALL ?= /usr/bin/env install
PREFIX ?= /usr/local
HOSTCFLAGS ?= $(CFLAGS)
HOSTCFLAGS += -Wall -Wextra -MMD -MP -O3
HOSTCFLAGS += -I $(TOP)/util/cbfstool/flashmap/
HOSTCFLAGS += -I $(ROOT)/commonlib/bsd/include
HOSTLDFLAGS ?= $(LDFLAGS)
# there files are in this directory
SRC := main.c utils.c rom.c
# and these are in $(TOP)/util/cbfstool/flashmap/
SRC += fmap.c kv_pair.c valstr.c
OBJ := $(SRC:.c=.o)
DEP := $(SRC:.c=.o.d)
.PHONY: all debug clean install
all: $(PRG)
debug: HOSTCFLAGS += -O0 -g
debug: HOSTLDFLAGS += -g
debug: all
install: $(PRG)
$(INSTALL) -d $(DESTDIR)$(PREFIX)/bin/
$(INSTALL) $^ $(DESTDIR)$(PREFIX)/bin/
uninstall:
$(RM) $(PRG) $(DESTDIR)$(PREFIX)/bin/
$(RMDIR) $(DESTDIR)$(PREFIX)/bin/
clean:
-$(RM) $(PRG) $(OBJ) $(DEP)
$(PRG): $(OBJ)
$(HOSTCC) -o $@ $^ $(HOSTLDFLAGS)
%.o: %.c
$(HOSTCC) $(HOSTCFLAGS) -c -o $@ -MF $@.d $<
%.o: $(TOP)/util/cbfstool/flashmap/%.c
$(HOSTCC) $(HOSTCFLAGS) -c -o $@ -MF $@.d $<
-include $(DEP)

View file

@ -0,0 +1,24 @@
# SPDX-License-Identifier: BSD-3-Clause
ec_usb_pd_fwtoolobj = main.o utils.o rom.o fmap.o kv_pair.o valstr.o
ec_usb_pd_fwheader = $(addprefix $(dir)/,utils.h rom.h)
WERROR ?= -Wno-error
EC_USB_PD_FWCFLAGS ?= $(CFLAGS)
EC_USB_PD_FWCFLAGS += -Wno-array-bounds -Wextra -MMD -MP -O3 -Wshadow $(WERROR)
EC_USB_PD_FWCFLAGS += -I $(top)/util/cbfstool/flashmap/
EC_USB_PD_FWCFLAGS += -I $(top)/util/amdtools/ec_usb_pd_fw
EC_USB_PD_FWCFLAGS += -I $(top)/src/commonlib/bsd/include
additional-dirs += $(objutil)/ec_usb_pd_fw
$(objutil)/ec_usb_pd_fw/%.o: $(top)/util/amdtools/ec_usb_pd_fw/%.c $(ec_usb_pd_fwheader) | $(objutil)/ec_usb_pd_fw
printf " PD_FWTOOL $@\n"
$(HOSTCC) $(EC_USB_PD_FWCFLAGS) $(HOSTCFLAGS) -c -o $@ $<
$(objutil)/ec_usb_pd_fw/%.o: $(top)/util/cbfstool/flashmap/%.c $(ec_usb_pd_fwheader) | $(objutil)/ec_usb_pd_fw
$(HOSTCC) $(EC_USB_PD_FWCFLAGS) $(HOSTCFLAGS) -c -o $@ $<
$(objutil)/ec_usb_pd_fw/ec_usb_pd_fw: $(addprefix $(objutil)/ec_usb_pd_fw/,$(ec_usb_pd_fwtoolobj)) $(ec_usb_pd_fwheader) | $(objutil)/ec_usb_pd_fw
printf " PD_FWTOOL $@\n"
$(HOSTCC) $(addprefix $(objutil)/ec_usb_pd_fw/,$(ec_usb_pd_fwtoolobj)) $(LDFLAGS) -o $@

View file

@ -0,0 +1,8 @@
Offline AMD Board specific USB Type-C PD FW modification tool `C`
Generates pointers at flash offset 16 for the EC firmware to consume
by reading the FMAP and the provided FMAP region name. The EC will
load the USB PD firmware into the corresponding chip where necessary.
This will overwrite parts of the flash without warning. The user must
be aware of potential conflicts such as with the Intel IFD.

View file

@ -0,0 +1,114 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include <errno.h>
#include <getopt.h>
#include <unistd.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "rom.h"
static const char *USAGE_FMT = "Usage: %s <rom-file> <command> [arguments]\n"
" -h|--help\n"
" -f|--fmap_region_name FMAP region\n"
" -s|--slot [0-11] Specify slot to use\n"
" -n|--next_free Use next free slot\n\n"
"Command:\n"
" GEN_PDFW_PTR - Writes the USB Type-C PD firmware PTR\n";
static const char *program_name;
static void print_program_usage(void)
{
fprintf(stderr, USAGE_FMT, program_name);
exit(EXIT_FAILURE);
}
#define ERROR(...) do { fprintf(stderr, "ERROR: " __VA_ARGS__); print_program_usage(); } while(0)
static void print_help(void)
{
printf(USAGE_FMT, program_name);
}
static const struct option longopts[] = {
{"help", 0, 0, 'h'},
{"fmap_region_name", 1, 0, 'f'},
{"next_free", 0, 0, 'n'},
{"slot", 1, 0, 's'},
{NULL, 0, 0, 0}
};
int main(int argc, char *argv[])
{
char *fmap_region_name = NULL;
const char *rom_file = NULL;
const char *command = NULL;
int slot = -1;
bool next_free = false;
int opt, idx, ret = 0;
program_name = argv[0];
if (argc < 3)
ERROR("Not enough argument given\n\n");
while (!ret &&
(opt = getopt_long(argc, argv, "?hf:s:n",
longopts, &idx)) != -1) {
switch (opt) {
case 'h':
case '?':
print_help();
exit(EXIT_SUCCESS);
break;
case 'n':
next_free = true;
break;
case 'f':
fmap_region_name = strdup(optarg);
break;
case 's':
slot = strtol(optarg, NULL, 0);
break;
}
}
if (optind < argc) {
rom_file = argv[optind];
optind++;
}
if (optind < argc) {
command = argv[optind];
optind++;
}
if (optind < argc)
ERROR("Unknown argument: %s\n\n", argv[optind]);
if (!rom_file)
ERROR("No ROM specified\n\n");
if (!command) {
ERROR("No command specified\n\n");
} else if (!strcmp(command, "GEN_PDFW_PTR")) {
if (!fmap_region_name)
ERROR("No fmap region specified\n\n");
if (next_free && slot >= 0)
ERROR("Specified slot and auto allocation\n\n");
if (!next_free && slot < 0)
ERROR("Didn't specify a slot and not auto allocating\n\n");
if (slot > 11)
ERROR("Invalid slot specified\n\n");
if (!rom_set_pd_fw_ptr(rom_file, fmap_region_name, slot, next_free))
ERROR("Failed to write EC PD PTR\n\n");
} else
ERROR("Unknown command specified: '%s'\n\n", command);
if (fmap_region_name)
free(fmap_region_name);
return ret;
}

View file

@ -0,0 +1,108 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include <assert.h>
#include <commonlib/bsd/compiler.h>
#include <endian.h>
#include <fmap.h>
#include <stdio.h>
#include "rom.h"
#include "utils.h"
/**
* @brief Generate pointer to PD firmware.
* @param rom_offset Offset of the PD firmware
* @param size Size of the PD firmware
*
* Each pointer is 8 byte wide consists of:
* - 4 byte offset
* - 3 byte size
* - 1 byte checksum (all 8 bytes sum up to 0xff)
*/
static uint64_t gen_signature(uint32_t rom_offset, uint32_t size)
{
uint64_t signature = rom_offset;
signature |= ((uint64_t)size & 0xffffffULL) << 32;
uint8_t crc = 0;
for (int i = 0; i < 7; i++)
crc += (signature >> (i * 8)) & 0xff;
signature |= (0xffULL - (uint64_t)crc) << 56;
return signature;
}
static bool check_signature(uint64_t signature)
{
uint8_t crc = 0;
for (int i = 0; i < 8; i++)
crc += (signature >> (i * 8)) & 0xff;
return crc == 0xff;
}
/**
* @brief Write the EC PD pointer to the ROM
* @param rom_file The filename of the ROM to update
* The ROM must contain a valid FMAP
* @param fmap_region_name FMAP region name containing the PD firmware
* @return True on success
*
* The EC firmware copies the USB Type-C PD firmware from the x86 SPI flash
* to the corresponding controller when necessary. It expects to find a list
* of pointers at offset 10h. The ROM can contain multiple PD firmwares depending
* on the used IC vendor/familiy.
*
*/
bool rom_set_pd_fw_ptr(const char rom_file[], const char *fmap_region_name, int slot, bool automatic)
{
uint64_t *ec_pd_fw_ptr;
struct mem_range file = {0};
bool ret = false;
file = map_file(rom_file, true);
if (file.start == NULL) {
fprintf(stderr, "ERROR: Failed to load \"%s\"\n", rom_file);
return false;
}
if (automatic)
slot = 0;
long fmap_offset = fmap_find(file.start, file.length);
if (fmap_offset >= 0) {
struct fmap *fmap = (void *)(file.start + fmap_offset);
const struct fmap_area *area = fmap_find_area(fmap, fmap_region_name);
if (area == NULL) {
fprintf(stderr,
"ERROR: Found FMAP without '%s' in \"%s\"\n\n",
fmap_region_name, rom_file);
printf("FMAP is:\n");
fmap_print(fmap);
goto error;
}
/* EC PD pointer resides at offset 0x10 */
ec_pd_fw_ptr = (uint64_t *)(file.start + 0x10 + slot * 8);
/* Find free slot if in auto mode */
for (; automatic && check_signature(le64toh(*ec_pd_fw_ptr)) && slot < 12; slot++)
ec_pd_fw_ptr++;
if (slot == 12) {
fprintf(stderr, "ERROR: No free slots left\n\n");
goto error;
}
*ec_pd_fw_ptr = htole64(gen_signature(area->offset, area->size));
} else {
fprintf(stderr, "ERROR: FMAP not found \"%s\"\n", rom_file);
goto error;
}
ret = true;
error:
unmap_file(file);
return ret;
}

View file

@ -0,0 +1,10 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#ifndef EC_USB_PD_FW__ROM_H__
#define EC_USB_PD_FW__ROM_H__
#include <stdbool.h>
bool rom_set_pd_fw_ptr(const char rom_file[], const char *fmap_region_name, int slot, bool automatic);
#endif // EC_USB_PD_FW__ROM_H__

View file

@ -0,0 +1,61 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "utils.h"
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <limits.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct mem_range map_file(const char path[], bool rw)
{
struct mem_range range = {0};
int open_flags = rw ? O_RDWR : O_RDONLY;
int mmap_flags = rw ? PROT_READ | PROT_WRITE : PROT_READ;
int fd = open(path, open_flags);
if (fd == -1) {
fprintf(stderr, "Failed to open(): %s\n", strerror(errno));
return range;
}
struct stat stat_buf;
if (fstat(fd, &stat_buf) != 0) {
(void)close(fd);
fprintf(stderr, "Failed to fstat(): %s\n", strerror(errno));
return range;
}
if (stat_buf.st_size == 0) {
(void)close(fd);
fprintf(stderr, "Can't map an empty \"%s\" file\n", path);
return range;
}
uint8_t *mem = mmap(/*addr=*/NULL, stat_buf.st_size, mmap_flags,
MAP_SHARED | MAP_POPULATE, fd, /*offset=*/0);
(void)close(fd);
if (mem == MAP_FAILED) {
fprintf(stderr, "Failed to mmap(): %s\n", strerror(errno));
return range;
}
range.start = mem;
range.length = stat_buf.st_size;
return range;
}
void unmap_file(struct mem_range store)
{
if (munmap(store.start, store.length) != 0)
fprintf(stderr, "Failed to munmap(): %s\n", strerror(errno));
}

View file

@ -0,0 +1,20 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#ifndef EC_USB_PD_FW__UTILS_H__
#define EC_USB_PD_FW__UTILS_H__
#include <stddef.h>
#include <stdbool.h>
#include <stdint.h>
struct mem_range {
uint8_t *start;
size_t length;
};
uint8_t crc8_itu(const uint8_t *data, int len);
struct mem_range map_file(const char path[], bool rw);
void unmap_file(struct mem_range store);
#endif // EC_USB_PD_FW__UTILS_H__