From 87c1af3d6431458d8530de57990e1bdd5a975acd Mon Sep 17 00:00:00 2001 From: Sergii Dmytruk Date: Mon, 10 Oct 2022 00:16:01 +0300 Subject: [PATCH 1/4] Fix build on PPC64 Signed-off-by: Sergii Dmytruk --- spiflash.c | 2 +- util.c | 2 +- util.h | 6 ++++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/spiflash.c b/spiflash.c index b1938e1..b02208f 100644 --- a/spiflash.c +++ b/spiflash.c @@ -53,7 +53,7 @@ write_mmio_##NAME( \ ) \ { \ *(volatile TYPE*)(offset + (uint8_t*) base) = value; \ - __asm__ __volatile__ ("mfence" : : : "memory"); \ + __asm__ __volatile__ (MFENCE_ASM : : : "memory"); \ } \ diff --git a/util.c b/util.c index 52feb71..8606794 100644 --- a/util.c +++ b/util.c @@ -80,7 +80,7 @@ memcpy_##WIDTH( \ dest[i] |= src[i]; \ else \ dest[i] = src[i]; \ - __asm__ __volatile__("mfence"); \ + __asm__ __volatile__(MFENCE_ASM); \ } \ } diff --git a/util.h b/util.h index de7156a..fe94b11 100644 --- a/util.h +++ b/util.h @@ -5,6 +5,12 @@ #include #include +#ifdef __PPC64__ +#define MFENCE_ASM "sync" +#else +#define MFENCE_ASM "mfence" +#endif + extern void * map_physical( uint64_t phys_addr, From 3bc75d19262888a85327483700b0ddb945a5aca2 Mon Sep 17 00:00:00 2001 From: Sergii Dmytruk Date: Mon, 10 Oct 2022 00:16:36 +0300 Subject: [PATCH 2/4] Add pnor tool to read/write PNOR partitions Signed-off-by: Sergii Dmytruk --- Makefile | 2 + pnor.c | 243 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ pnor.h | 41 ++++++++++ 3 files changed, 286 insertions(+) create mode 100644 pnor.c create mode 100644 pnor.h diff --git a/Makefile b/Makefile index 9f9a483..13c00b8 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,7 @@ TARGETS += poke TARGETS += peek TARGETS += cbfs TARGETS += uefi +TARGETS += pnor CFLAGS += \ -std=c99 \ @@ -21,6 +22,7 @@ peek: peek.o util.o poke: poke.o util.o cbfs: cbfs.o util.o uefi: uefi.o util.o +pnor: pnor.o util.o $(TARGETS): $(CC) $(LDFLAGS) -o $@ $^ diff --git a/pnor.c b/pnor.c new file mode 100644 index 0000000..7b24c9f --- /dev/null +++ b/pnor.c @@ -0,0 +1,243 @@ +#define _DEFAULT_SOURCE + +#include +#include + +#include +#include +#include +#include +#include + +#include "pnor.h" +#include "util.h" + +static int verbose = 0; +static int as_is = 0; + +static const struct option long_options[] = { + { "verbose", 0, NULL, 'v' }, + { "read", 1, NULL, 'r' }, + { "write", 1, NULL, 'w' }, + { "asis", 1, NULL, 'a' }, + { NULL, 0, NULL, 0 }, +}; + +static const char usage[] = +"Usage: pnor pnor.rom [options]\n" +"\n" +" -h | -? | --help This help\n" +" -v | --verbose Increase verbosity\n" +" -r | --read part_name Export a PNOR file to stdout\n" +" -w | --write part_name Import a PNOR file from stdin\n" +" -a | --asis Raw read/write of PNOR partition\n" +"\n"; + +static uint8_t generate_ecc(uint64_t data) { + static uint64_t ecc_matrix[] = { + 0x0000e8423c0f99ff, + 0x00e8423c0f99ff00, + 0xe8423c0f99ff0000, + 0x423c0f99ff0000e8, + 0x3c0f99ff0000e842, + 0x0f99ff0000e8423c, + 0x99ff0000e8423c0f, + 0xff0000e8423c0f99 + }; + + uint8_t result = 0; + for (int i = 0; i < 8; i++) + result |= __builtin_parityll(ecc_matrix[i] & data) << i; + return result; +} + +static int read_part(struct ffs_entry *e, uint8_t *start, uint32_t size) { + uint32_t i; + + if (!as_is && (be32toh(e->user.data[0]) & FFS_ENRY_INTEG_ECC)) { + for (i = 0; i < size; i++) { + // TODO: verify and correct data using ECC + if ((i + 1) % 9 != 0) + putchar(start[i]); + } + } else { + for (i = 0; i < size; i++) + putchar(start[i]); + } + + return EXIT_SUCCESS; +} + +static uint32_t checksum(const struct ffs_entry *e) +{ + uint32_t j; + uint32_t sum = 0; + for (j = 0; j < sizeof(*e) / sizeof(uint32_t) - 1; j++) { + /* Avoid warning about possible unaligned access */ + uint32_t as_uint32; + memcpy(&as_uint32, (uint8_t *)e + j * sizeof(uint32_t), + sizeof(uint32_t)); + sum ^= as_uint32; + } + return sum; +} + +static int write_part(struct ffs_entry *e, uint8_t *start, uint32_t max_size) { + uint32_t i = 0; + + if (!as_is && (be32toh(e->user.data[0]) & FFS_ENRY_INTEG_ECC)) { + uint64_t data; + while (fread(&data, sizeof(data), 1, stdin) == 1) { + if (i == max_size) { + fprintf(stderr, "Input data is too large.\n"); + return EXIT_FAILURE; + } + + *(uint64_t *)&start[i] = data; + i += 8; + start[i] = generate_ecc(be64toh(data)); + ++i; + } + } else { + int c; + while ((c = getchar()) != EOF) { + if (i == max_size) { + fprintf(stderr, "Input data is too large.\n"); + return EXIT_FAILURE; + } + start[i++] = c; + } + } + + e->actual = htobe32(i); + e->checksum = checksum(e); + return EXIT_SUCCESS; +} + +int main(int argc, char** argv) { + const char * const prog_name = argv[0]; + + if (argc <= 2) { + fprintf(stderr, "%s", usage); + return EXIT_FAILURE; + } + + int opt; + int do_read = 0; + int do_write = 0; + const char * part_name = NULL; + while ((opt = getopt_long(argc, argv, "h?vr:w:a", + long_options, NULL)) != -1) { + switch(opt) { + case 'v': + verbose = 1; + break; + case 'r': + do_read = 1; + part_name = optarg; + break; + case 'w': + do_write = 1; + part_name = optarg; + break; + case 'a': + as_is = 1; + break; + case '?': case 'h': + fprintf(stderr, "%s", usage); + return EXIT_SUCCESS; + default: + fprintf(stderr, "%s", usage); + return EXIT_FAILURE; + } + } + + if (!do_read && !do_write) { + fprintf(stderr, "%s", usage); + return EXIT_FAILURE; + } + + argc -= optind; + argv += optind; + if (argc != 1) { + fprintf(stderr, "%s: Excess arguments?\n", prog_name); + return EXIT_FAILURE; + } + + const char * romname = argv[0]; + + uint64_t size; + int readonly = do_read; + void *rom = map_file(romname, &size, readonly); + if (rom == NULL) { + fprintf(stderr, "Failed to map ROM file: %s '%s'\n", romname, + strerror(errno)); + return EXIT_FAILURE; + } + + struct ffs_hdr *hdr = rom; + + if (be32toh(hdr->magic) != FFS_MAGIC) { + fprintf(stderr, "Invalid header magic: 0x%08llx\n", + (unsigned long long)be32toh(hdr->magic)); + return EXIT_FAILURE; + } + + if (be32toh(hdr->version) != FFS_VERSION_1) { + fprintf(stderr, "Invalid header version: 0x%08llx\n", + (unsigned long long)be32toh(hdr->version)); + return EXIT_FAILURE; + } + + uint32_t i; + for (i = 0; i < be32toh(hdr->entry_count); i++) { + struct ffs_entry *e = &hdr->entries[i]; + if (verbose) { + fprintf(stderr, + "%s: base %x, size %x (%x) type %x, flags %x\n", + e->name, + be32toh(e->base) * be32toh(hdr->block_size), + be32toh(e->size) * be32toh(hdr->block_size), + be32toh(e->actual), be32toh(e->type), + be32toh(e->flags)); + } + + if (strcmp(e->name, part_name) != 0) + continue; + + if (checksum(e) != e->checksum) { + fprintf(stderr, "Entry for %s is broken\n", part_name); + return EXIT_FAILURE; + } + + uint8_t *start = rom + + be32toh(hdr->block_size) * be32toh(e->base); + uint32_t size = be32toh(e->actual); + uint32_t max_size = be32toh(e->size) * be32toh(hdr->block_size); + + if (!as_is && (be32toh(e->user.data[1]) & FFS_ENTRY_VERS_SHA512)) { + if (verbose) + fprintf(stderr, "Skipping partition header\n"); + + /* Skip PNOR partition header */ + start += 0x1000; + size -= 0x1000; + + /* Possibly skip ECC of the header */ + if (be32toh(e->user.data[0]) & FFS_ENRY_INTEG_ECC) { + start += 0x200; + size -= 0x200; + } + } + + if (verbose && (be32toh(e->user.data[0]) & FFS_ENRY_INTEG_ECC)) + fprintf(stderr, "%s partition has ECC\n", part_name); + + if (do_read) + return read_part(e, start, size); + return write_part(e, start, max_size); + } + + fprintf(stderr, "Couldn't find %s partition\n", part_name); + return EXIT_FAILURE; +} diff --git a/pnor.h b/pnor.h new file mode 100644 index 0000000..f8593d3 --- /dev/null +++ b/pnor.h @@ -0,0 +1,41 @@ +#ifndef _pnor_h_ +#define _pnor_h_ + +#define FFS_VERSION_1 1 +#define FFS_MAGIC 0x50415254 +#define PART_NAME_MAX 15 +#define FFS_USER_WORDS 16 + +#define FFS_ENRY_INTEG_ECC 0x00008000 +#define FFS_ENTRY_VERS_SHA512 0x80000000 + +struct ffs_entry { + char name[PART_NAME_MAX + 1]; + uint32_t base; + uint32_t size; + uint32_t pid; + uint32_t id; + uint32_t type; + uint32_t flags; + uint32_t actual; // includes ECC + uint32_t resvd[4]; + struct { + uint32_t data[FFS_USER_WORDS]; + } user; + uint32_t checksum; +} __attribute__ ((packed)); + +struct ffs_hdr { + uint32_t magic; + uint32_t version; + uint32_t size; + uint32_t entry_size; + uint32_t entry_count; + uint32_t block_size; + uint32_t block_count; + uint32_t resvd[4]; + uint32_t checksum; + struct ffs_entry entries[]; +} __attribute__ ((packed)); + +#endif From 7a95b763cd8dcec183030d702eb0241a53b3c7df Mon Sep 17 00:00:00 2001 From: Sergii Dmytruk Date: Tue, 11 Oct 2022 00:30:12 +0300 Subject: [PATCH 3/4] cbfs: handle ROM images without CFBS pointer * don't crash if last 4 bytes are larger than ROM size * look for FMAP and from there for CBFS master header Signed-off-by: Sergii Dmytruk --- cbfs.c | 208 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 207 insertions(+), 1 deletion(-) diff --git a/cbfs.c b/cbfs.c index 1fe7de2..ed4991e 100644 --- a/cbfs.c +++ b/cbfs.c @@ -1,3 +1,10 @@ +/* + * FMAP search code was taken from coreboot and is licensed under BSD-3-Clause + */ + +#define _DEFAULT_SOURCE + +#include #include #include #include @@ -10,6 +17,7 @@ #include #include #include +#include #include "util.h" #define CBFS_HEADER_MAGIC 0x4F524243 @@ -76,6 +84,175 @@ struct cbfs_file { char filename[]; }; +#define FMAP_SIGNATURE "__FMAP__" +#define FMAP_VER_MAJOR 1 /* this header's FMAP minor version */ +#define FMAP_VER_MINOR 1 /* this header's FMAP minor version */ +#define FMAP_STRLEN 32 /* maximum length for strings, */ + +/* Mapping of volatile and static regions in firmware binary */ +struct fmap_area { + uint32_t offset; /* offset relative to base */ + uint32_t size; /* size in bytes */ + uint8_t name[FMAP_STRLEN]; /* descriptive name */ + uint16_t flags; /* flags for this area */ +} __attribute__((__packed__)); + +struct fmap { + uint8_t signature[8]; /* "__FMAP__" (0x5F5F464D41505F5F) */ + uint8_t ver_major; /* major version */ + uint8_t ver_minor; /* minor version */ + uint64_t base; /* address of the firmware binary */ + uint32_t size; /* size of firmware binary in bytes */ + uint8_t name[FMAP_STRLEN]; /* name of this firmware binary */ + uint16_t nareas; /* number of areas described by + fmap_areas[] below */ + struct fmap_area areas[]; +} __attribute__((__packed__)); + +/* returns size of fmap data structure if successful, <0 to indicate error */ +int fmap_size(const struct fmap *fmap) +{ + if (!fmap) + return -1; + + return sizeof(*fmap) + (le16toh(fmap->nareas) * sizeof(struct fmap_area)); +} + +/* Make a best-effort assessment if the given fmap is real */ +static int is_valid_fmap(const struct fmap *fmap) +{ + if (memcmp(fmap, FMAP_SIGNATURE, strlen(FMAP_SIGNATURE)) != 0) + return 0; + /* strings containing the magic tend to fail here */ + if (fmap->ver_major != FMAP_VER_MAJOR) + return 0; + /* a basic consistency check: flash should be larger than fmap */ + if (le32toh(fmap->size) < + sizeof(*fmap) + le16toh(fmap->nareas) * sizeof(struct fmap_area)) + return 0; + + /* fmap-alikes along binary data tend to fail on having a valid, + * null-terminated string in the name field.*/ + int i = 0; + while (i < FMAP_STRLEN) { + if (fmap->name[i] == 0) + break; + if (!isgraph(fmap->name[i])) + return 0; + if (i == FMAP_STRLEN - 1) { + /* name is specified to be null terminated single-word string + * without spaces. We did not break in the 0 test, we know it + * is a printable spaceless string but we're seeing FMAP_STRLEN + * symbols, which is one too many. + */ + return 0; + } + i++; + } + + return 1; +} + +/* brute force linear search */ +static long int fmap_lsearch(const uint8_t *image, size_t len) +{ + unsigned long int offset; + int fmap_found = 0; + + for (offset = 0; offset < len - strlen(FMAP_SIGNATURE); offset++) { + if (is_valid_fmap((const struct fmap *)&image[offset])) { + fmap_found = 1; + break; + } + } + + if (!fmap_found) + return -1; + + if (offset + fmap_size((const struct fmap *)&image[offset]) > len) + return -1; + + return offset; +} + +/* if image length is a power of 2, use binary search */ +static long int fmap_bsearch(const uint8_t *image, size_t len) +{ + unsigned long int offset = -1; + int fmap_found = 0, stride; + + /* + * For efficient operation, we start with the largest stride possible + * and then decrease the stride on each iteration. Also, check for a + * remainder when modding the offset with the previous stride. This + * makes it so that each offset is only checked once. + */ + for (stride = len / 2; stride >= 16; stride /= 2) { + if (fmap_found) + break; + + for (offset = 0; + offset < len - strlen(FMAP_SIGNATURE); + offset += stride) { + if ((offset % (stride * 2) == 0) && (offset != 0)) + continue; + if (is_valid_fmap( + (const struct fmap *)&image[offset])) { + fmap_found = 1; + break; + } + } + } + + if (!fmap_found) + return -1; + + if (offset + fmap_size((const struct fmap *)&image[offset]) > len) + return -1; + + return offset; +} + +static int popcnt(unsigned int u) +{ + int count; + + /* K&R method */ + for (count = 0; u; count++) + u &= (u - 1); + + return count; +} + +static long int fmap_find(const uint8_t *image, unsigned int image_len) +{ + long int ret = -1; + + if ((image == NULL) || (image_len == 0)) + return -1; + + if (popcnt(image_len) == 1) + ret = fmap_bsearch(image, image_len); + else + ret = fmap_lsearch(image, image_len); + + return ret; +} + +/* brute force linear search */ +static long int cbfs_lsearch(const uint8_t *image, size_t image_len, int start) +{ + size_t offset; + uint32_t magic = be32toh(CBFS_HEADER_MAGIC); + + for (offset = start; offset < image_len - sizeof(magic); offset++) { + if (memcmp(&image[offset], &magic, sizeof(magic)) == 0) + return offset; + } + + return -1; +} + size_t cbfs_calculate_file_header_size(const char *name) { return (sizeof(struct cbfs_file) + @@ -97,6 +274,24 @@ struct cbfs_file *cbfs_create_file_header(int type, return entry; } +static int64_t find_cbfs(const char *romname, const uint8_t *rom, size_t size) +{ + long int fmap_offset = fmap_find(rom, size); + if (fmap_offset < 0) { + fprintf(stderr, "Failed to find FMAP in ROM file: %s\n", romname); + return -1; + } + + int64_t offset = cbfs_lsearch(rom, size, fmap_offset); + if (offset < 0) { + fprintf(stderr, "Failed to find CBFS in ROM file with FMAP: %s\n", + romname); + return -1; + } + + return offset; +} + int main(int argc, char** argv) { const char * const prog_name = argv[0]; if (argc <= 1) @@ -191,8 +386,19 @@ int main(int argc, char** argv) { strerror(errno)); return EXIT_FAILURE; } + + int64_t offset; + header_delta = *((int32_t *)(rom + size - 4)); - memcpy(&header, rom + size + header_delta, sizeof(header)); + if ((uint64_t)header_delta > size) { + offset = find_cbfs(romname, rom, size); + if (offset < 0) + return EXIT_FAILURE; + } else { + offset = size + header_delta; + } + + memcpy(&header, rom + offset, sizeof(header)); } else { copy_physical(mem_end - 4, sizeof(header_delta), &header_delta); copy_physical(mem_end + header_delta, sizeof(header), &header); From 76bdfa21d65caeb7dbe9c2fa1a837369732f50af Mon Sep 17 00:00:00 2001 From: Sergii Dmytruk Date: Wed, 26 Oct 2022 21:09:29 +0300 Subject: [PATCH 4/4] Make cbfs tool read mtd of PowerPC * Flash isn't accessible via /dev/mem there. * Flash has a format called PNOR with coreboot being stored in its HBI partition. * Some PNOR partitions have ECC, which needs to be removed for the rest of code to work. Signed-off-by: Sergii Dmytruk --- cbfs.c | 145 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 140 insertions(+), 5 deletions(-) diff --git a/cbfs.c b/cbfs.c index ed4991e..6252488 100644 --- a/cbfs.c +++ b/cbfs.c @@ -18,6 +18,7 @@ #include #include #include +#include "pnor.h" #include "util.h" #define CBFS_HEADER_MAGIC 0x4F524243 @@ -274,6 +275,125 @@ struct cbfs_file *cbfs_create_file_header(int type, return entry; } +/* pnor.c has contains code, but that one uses mmaped file */ +static void *copy_cb(uint64_t *rom_size) +{ +#ifndef __PPC64__ + return NULL; +#endif + + FILE *mtd = fopen("/dev/mtd0", "r"); + if (mtd == NULL) { + fprintf(stderr, "Failed to open /dev/mtd0\n"); + return NULL; + } + + struct ffs_hdr ffs_hdr; + if (fread(&ffs_hdr, sizeof(ffs_hdr), 1, mtd) != 1) { + fclose(mtd); + fprintf(stderr, "Failed to read PNOR header from /dev/mtd0\n"); + return NULL; + } + + if (be32toh(ffs_hdr.magic) != FFS_MAGIC) { + fclose(mtd); + fprintf(stderr, "Invalid header magic: 0x%08llx\n", + (unsigned long long)be32toh(ffs_hdr.magic)); + return NULL; + } + + if (be32toh(ffs_hdr.version) != FFS_VERSION_1) { + fclose(mtd); + fprintf(stderr, "Invalid header version: 0x%08llx\n", + (unsigned long long)be32toh(ffs_hdr.version)); + return NULL; + } + + uint32_t i; + struct ffs_entry entry; + + for (i = 0; i < be32toh(ffs_hdr.entry_count); i++) { + if (fread(&entry, sizeof(entry), 1, mtd) != 1) { + fclose(mtd); + fprintf(stderr, "Failed to read PNOR entry #%d from /dev/mtd0\n", + i); + return NULL; + } + + if (strcmp(entry.name, "HBI") == 0) + break; + } + + if (i >= be32toh(ffs_hdr.entry_count)) { + fclose(mtd); + fprintf(stderr, "Failed to find HBI PNOR entry in /dev/mtd0\n"); + return NULL; + } + + uint32_t start = be32toh(ffs_hdr.block_size) * be32toh(entry.base); + uint32_t size = be32toh(entry.actual); + + if (be32toh(entry.user.data[1]) & FFS_ENTRY_VERS_SHA512) { + /* Skip PNOR partition header */ + start += 0x1000; + + /* Possibly skip ECC of the header */ + if (be32toh(entry.user.data[0]) & FFS_ENRY_INTEG_ECC) + start += 0x200; + } + + if (fseek(mtd, start, SEEK_SET) != 0) { + fclose(mtd); + fprintf(stderr, "Failed to seek to HBI partition in /dev/mtd0\n"); + return NULL; + } + + uint8_t *hbi = malloc(size); + if (hbi == NULL) { + fclose(mtd); + fprintf(stderr, "Failed to allocate memory for HBI partition\n"); + return NULL; + } + + uint8_t *p = hbi; + if (be32toh(entry.user.data[0]) & FFS_ENRY_INTEG_ECC) { + char buf[16 * 1024]; + for (i = 0; i < size; i++) { + if (i % sizeof(buf) == 0) { + if (fread(buf, sizeof(buf), 1, mtd) != 1) { + fclose(mtd); + free(hbi); + fprintf(stderr, "Failed reading HBI partition\n"); + return NULL; + } + } + + // TODO: verify and correct data using ECC + if ((i + 1) % 9 != 0) + *p++ = buf[i % sizeof(buf)]; + } + } else { + char buf[16 * 1024]; + for (i = 0; i < size; i++) { + if (i % sizeof(buf) == 0) { + if (fread(buf, sizeof(buf), 1, mtd) != 1) { + fclose(mtd); + free(hbi); + fprintf(stderr, "Failed reading HBI partition\n"); + return NULL; + } + } + + *p++ = buf[i % sizeof(buf)]; + } + } + + fclose(mtd); + + *rom_size = p - hbi; + return hbi; +} + static int64_t find_cbfs(const char *romname, const uint8_t *rom, size_t size) { long int fmap_offset = fmap_find(rom, size); @@ -375,8 +495,9 @@ int main(int argc, char** argv) { int32_t header_delta; struct cbfs_header header; void *rom = NULL, *off = NULL; - uint64_t size; + uint64_t size, cb_size; const uint64_t mem_end = 0x100000000; + void *cb_map; if (use_file) { int readonly = do_add || do_delete ? 0 : 1; @@ -400,8 +521,17 @@ int main(int argc, char** argv) { memcpy(&header, rom + offset, sizeof(header)); } else { - copy_physical(mem_end - 4, sizeof(header_delta), &header_delta); - copy_physical(mem_end + header_delta, sizeof(header), &header); + cb_map = copy_cb(&cb_size); + if (cb_map != NULL) { + int64_t offset = find_cbfs("/dev/mtd0", cb_map, cb_size); + if (offset < 0) + return EXIT_FAILURE; + + memcpy(&header, cb_map + offset, sizeof(header)); + } else { + copy_physical(mem_end - 4, sizeof(header_delta), &header_delta); + copy_physical(mem_end + header_delta, sizeof(header), &header); + } } header.magic = ntohl(header.magic); @@ -429,8 +559,13 @@ int main(int argc, char** argv) { } if (!use_file) { - size = (uint64_t) header.romsize; - rom = map_physical(mem_end - size, size); + if (cb_map == NULL) { + size = (uint64_t) header.romsize; + rom = map_physical(mem_end - size, size); + } else { + rom = cb_map; + size = cb_size; + } } // Setup file to add to ROM