util: Add Microchip EC FW tool

The Microchip EC can share the SPI flash with the x86 host. Since
it boots first and does power sequencing, there's no problem with
concurrent access happening. Due to various vendor specific flash
layouts used on x86, the EC needs a pointer to it's own firmware.

The pointer resides at flash offset 0 and is read by MEC152x and
MEC1701 and MEC172x ECs, probably others as well.

The introduced tool generates the EC FW PTR at flash offset 0.

Allows to get rid of hand-crafted binary files (EC_SIG) being used
on AMD mainboards that hardcode the offset and must manually being
checked if those match the FMAP.

When there'll be additional firmware regions added it becomes
unconvienient to maintain those by hand.

Usage output:

Usage: ./util/mec152x/mec152xtool <rom-file> <command>
        -h|--help
        -f|--fmap_region_name

Command:
 GEN_ECFW_PTR - Writes the ECFW PTR

Based on https://chromium.googlesource.com/chromiumos/platform/ec/+/08f5a1e6fc2c9467230444ac9b582dcf4d9f0068/chip/mchp/util/pack_ec_mec172x.py

Change-Id: I3b74c9f65643ad4437de29d4aed307b1a2b33286
Signed-off-by: Patrick Rudolph <patrick.rudolph@9elements.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/87428
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Matt DeVillier <matt.devillier@gmail.com>
This commit is contained in:
Patrick Rudolph 2025-04-23 11:54:00 +02:00 committed by Matt DeVillier
commit e63620012c
8 changed files with 370 additions and 0 deletions

54
util/mec152x/Makefile Normal file
View file

@ -0,0 +1,54 @@
# SPDX-License-Identifier: GPL-2.0-or-later
PRG := mec152xtool
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) $(DESTDIR)$(PREFIX)/bin/$(PRG)
clean:
$(RM) -f $(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)

23
util/mec152x/Makefile.mk Normal file
View file

@ -0,0 +1,23 @@
# SPDX-License-Identifier: BSD-3-Clause
mec152xtoolobj = main.o utils.o rom.o fmap.o kv_pair.o valstr.o
mec152xheader = $(addprefix $(dir)/,utils.h rom.h)
WERROR ?= -Wno-error
MEC152XCFLAGS := -Wno-array-bounds -Wextra -O3 -Wshadow $(WERROR)
MEC152XCFLAGS += -I $(top)/util/cbfstool/flashmap/
MEC152XCFLAGS += -I $(top)/util/mec152x
MEC152XCFLAGS += -I $(top)/src/commonlib/bsd/include
additional-dirs += $(objutil)/mec152x
$(objutil)/mec152x/%.o: $(top)/util/mec152x/%.c $(mec152xheader) | $(objutil)
printf " MEC152X $@\n"
$(HOSTCC) $(MEC152XCFLAGS) $(HOSTCFLAGS) -c -o $@ $<
$(objutil)/mec152x/%.o: $(top)/util/cbfstool/flashmap/%.c $(mec152xheader) | $(objutil)
$(HOSTCC) $(MEC152XCFLAGS) $(HOSTCFLAGS) -c -o $@ $<
$(objutil)/mec152x/mec152xtool: $(addprefix $(objutil)/mec152x/,$(mec152xtoolobj)) $(mec152xheader) | $(objutil)
printf " MEC152X $@\n"
$(HOSTCC) $(addprefix $(objutil)/mec152x/,$(mec152xtoolobj)) $(LDFLAGS) -o $@

View file

@ -0,0 +1,8 @@
Offline Microchip EC FW modification tool `C`
Generates the EC firmware pointer at flash offset 0 based on
the FMAP and the specified FMAP region name. The EC will read
this pointer to find the EC firmware in the host SPI flash.
This will overwrite the first 4 bytes of flash. The user must
be aware of potential conflicts such as with the Intel IFD.

99
util/mec152x/main.c Normal file
View file

@ -0,0 +1,99 @@
/* 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>\n"
" -h|--help\n"
" -f|--fmap_region_name\n\n"
"Command:\n"
" GEN_ECFW_PTR - Writes the ECFW 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'},
{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 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:",
longopts, &idx)) != -1) {
switch (opt) {
case 'h':
case '?':
print_help();
exit(EXIT_SUCCESS);
break;
case 'f':
fmap_region_name = strdup(optarg);
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_ECFW_PTR")) {
if (!fmap_region_name)
ERROR("No fmap region specified\n\n");
if (!rom_set_ec_fw_ptr(rom_file, fmap_region_name))
ERROR("Failed to write EC FW PTR\n\n");
} else {
ERROR("Unknown command specified: '%s'\n\n", command);
}
if (fmap_region_name)
free(fmap_region_name);
return ret;
}

77
util/mec152x/rom.c Normal file
View file

@ -0,0 +1,77 @@
/* 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"
/* Generate EC FW PTR. EC FW must be 256 byte aligned and has a CRC-8 checksum */
static uint32_t gen_signature(uint32_t rom_offset)
{
uint32_t signature = rom_offset >> 8;
uint8_t crc = crc8_itu((uint8_t *)&signature, 3);
signature |= ((uint32_t)crc) << 24;
return signature;
}
/**
* @brief Write the EC FW 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 EC firmware
* @return True on success
*
* On MEC15x1 the firmware resides in the shared x86 SPI flash. As the EC
* boots first and does the power sequencing there's no concurrent access.
* The EC expects to find a pointer to the FW at offset 0.
*/
bool rom_set_ec_fw_ptr(const char rom_file[], const char *fmap_region_name)
{
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;
}
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 FW must be aligned */
if (area->offset % 256) {
fprintf(stderr,
"ERROR: Region '%s' not aligned to 256 in \"%s\"\n",
fmap_region_name, rom_file);
goto error;
}
/* EC FW pointer resides at offset 0 */
uint32_t *ec_fw_ptr = (uint32_t *)file.start;
*ec_fw_ptr = htole32(gen_signature(area->offset));
} else {
fprintf(stderr, "ERROR: FMAP not found \"%s\"\n", rom_file);
goto error;
}
ret = true;
error:
unmap_file(file);
return ret;
}

10
util/mec152x/rom.h Normal file
View file

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

79
util/mec152x/utils.c Normal file
View file

@ -0,0 +1,79 @@
/* 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>
/* Return CRC-8 of the data, using x^8 x^2 x 1 polynomial. */
uint8_t crc8_itu(const uint8_t *data, int len)
{
unsigned int crc = 0;
int i, j;
for (j = 0; j < len; j++, data++) {
crc ^= (*data << 8);
for (i = 8; i; i--) {
if (crc & 0x8000)
crc ^= (0x1070 << 3);
crc <<= 1;
}
}
return (uint8_t)(crc >> 8) ^ 0x55;
}
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));
}

20
util/mec152x/utils.h Normal file
View file

@ -0,0 +1,20 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#ifndef MEC152X__UTILS_H__
#define MEC152X__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 // MEC152X__UTILS_H__