/* SPDX-License-Identifier: GPL-2.0-only */ /* CSE FPT tool */ #include <commonlib/endian.h> #include <getopt.h> #include <errno.h> #include <stdlib.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> #include "common.h" #include "cse_fpt.h" static struct params { const char *output_dir; const char *partition_name; struct fpt_hdr_ops *hdr_ops; } params; #define FPT_ENTRY_TYPE_MASK 0x7f #define FPT_ENTRY_TYPE_SHIFT 0 #define GET_FPT_ENTRY_TYPE(x) (((x) >> FPT_ENTRY_TYPE_SHIFT) & FPT_ENTRY_TYPE_MASK) #define FPT_ENTRY_TYPE_CODE 0x0 #define FPT_ENTRY_TYPE_DATA 0x1 #define FPT_ENTRY_VALID_MASK 0xff #define FPT_ENTRY_VALID_SHIFT 24 #define GET_FPT_ENTRY_VALID(x) (((x) >> FPT_ENTRY_VALID_SHIFT) & FPT_ENTRY_VALID_MASK) #define FPT_ENTRY_INVALID 0xff #define FPT_ENTRY_VALID 0x0 struct fpt_entry { uint8_t name[4]; /* ASCII short name */ uint8_t rsvd1[4]; uint32_t offset; /* Offset in bytes from start of FPT binary */ uint32_t length; /* Size in bytes */ uint8_t rsvd2[12]; uint32_t flags; } __packed; static struct fpt { struct buffer input_buff; const struct fpt_hdr_ops *hdr_ops; fpt_hdr_ptr hdr; struct fpt_entry *entries; } fpt; static void usage(const char *name) { printf("%s: Utility for CSE FPT\n\n" "USAGE:\n" " %s FILE COMMAND\n\n" "COMMANDs:\n" " print\n" " dump [-o OUTPUT_DIR] [-n NAME]\n" "\nOPTIONS:\n" " -o OUTPUT_DIR : Directory to dump the partition files in\n" " -n NAME : Name of partition to dump\n" "\n", name, name); } static int get_fpt_buff(struct buffer *input_buff, struct buffer *fpt_buff) { /* * FPT marker is typically at offset 0x10 in the released CSE binary. Check at offset * 0x10 first and if that fails fall back to checking offset 0. */ const size_t fpt_offsets[] = { 0x10, 0 }; size_t i; for (i = 0; i < ARRAY_SIZE(fpt_offsets); i++) { if (buffer_size(input_buff) < (strlen(FPT_MARKER) + fpt_offsets[i])) continue; const uint8_t *data = buffer_get(input_buff); if (!memcmp(data + fpt_offsets[i], FPT_MARKER, strlen(FPT_MARKER))) break; } if (i == ARRAY_SIZE(fpt_offsets)) { ERROR("Could not locate FPT at known offsets.\n"); return -1; } buffer_clone(fpt_buff, input_buff); buffer_seek(fpt_buff, fpt_offsets[i]); return 0; } static int read_fpt_entries(struct buffer *buff) { size_t i; struct fpt_entry *e; const size_t entries = fpt.hdr_ops->get_entry_count(fpt.hdr); const size_t fpt_entries_size = sizeof(struct fpt_entry) * entries; if (buffer_size(buff) < fpt_entries_size) { ERROR("Not enough bytes(actual=0x%zx, expected=0x%zx) for FPT entries!\n", buffer_size(buff), fpt_entries_size); return -1; } e = fpt.entries = malloc(fpt_entries_size); for (i = 0; i < entries; i++, e++) { READ_MEMBER(buff, e->name); READ_MEMBER(buff, e->rsvd1); READ_MEMBER(buff, e->offset); READ_MEMBER(buff, e->length); READ_MEMBER(buff, e->rsvd2); READ_MEMBER(buff, e->flags); } return 0; } static const struct fpt_hdr_ops *get_fpt_hdr_ops(struct buffer *buff) { static const struct fpt_hdr_ops *hdr_ops[] = { &fpt_hdr_20_ops, &fpt_hdr_21_ops, }; for (size_t i = 0; i < ARRAY_SIZE(hdr_ops); i++) { if (hdr_ops[i]->match_version(buff)) return hdr_ops[i]; } return NULL; } static int fpt_parse(const char *image_name) { struct buffer *input_buff = &fpt.input_buff; struct buffer fpt_buff; if (buffer_from_file(input_buff, image_name)) { ERROR("Failed to read input file %s\n", image_name); return -1; } if (get_fpt_buff(input_buff, &fpt_buff)) return -1; fpt.hdr_ops = get_fpt_hdr_ops(&fpt_buff); if (fpt.hdr_ops == NULL) { ERROR("FPT header format not supported!\n"); return -1; } fpt.hdr = fpt.hdr_ops->read(&fpt_buff); if (!fpt.hdr) { ERROR("Unable to read FPT header!\n"); return -1; } return read_fpt_entries(&fpt_buff); } static bool is_partition_valid(const struct fpt_entry *e) { return e->offset != 0 && e->length != 0 && GET_FPT_ENTRY_VALID(e->flags) != FPT_ENTRY_INVALID; } static bool is_partition_code(const struct fpt_entry *e) { return GET_FPT_ENTRY_TYPE(e->flags) == FPT_ENTRY_TYPE_CODE; } static void print_fpt_entry(const struct fpt_entry *e) { printf("%-25s0x%-23x0x%-23x%c,%c (0x%.8x)\n", e->name, e->offset, e->length, is_partition_code(e) ? 'C' : 'D', is_partition_valid(e) ? 'V' : 'I', e->flags); } static void print_fpt_entries(const struct fpt_entry *e, size_t count) { printf("\n * FPT entries\n"); printf("%-25s%-25s%-25s%-25s\n", "Name", "Offset", "Size", "Flags"); printf("==============================================================" "===============================\n"); for (size_t i = 0; i < count; i++) print_fpt_entry(&e[i]); printf("==============================================================" "================================\n"); printf("Flags: I=invalid, V=valid, C=code, D=data\n"); } static bool partition_name_match(const struct fpt_entry *e, const char *name) { if (!name) return false; return !memcmp(e->name, name, sizeof(e->name)); } static const struct fpt_entry *get_partition_entry(const char *name) { for (size_t i = 0; i < fpt.hdr_ops->get_entry_count(fpt.hdr); i++) { if (partition_name_match(&fpt.entries[i], name)) return &fpt.entries[i]; } return NULL; } static int cmd_print(void) { if (params.partition_name == NULL) { fpt.hdr_ops->print(fpt.hdr); print_fpt_entries(fpt.entries, fpt.hdr_ops->get_entry_count(fpt.hdr)); } else { const struct fpt_entry *e = get_partition_entry(params.partition_name); if (e) print_fpt_entry(e); else { ERROR("Partition %s not found!\n", params.partition_name); return -1; } } return 0; } static bool should_dump_partition(const struct fpt_entry *e) { if (!is_partition_valid(e)) { if (partition_name_match(e, params.partition_name)) { ERROR("Invalid partition requested to be dumped!\n"); exit(-1); } return false; } /* Dump all partitions if no name provided. */ if (params.partition_name == NULL) return true; return partition_name_match(e, params.partition_name); } static char *get_file_path(const struct fpt_entry *e) { size_t filename_len = sizeof(e->name) + 1; char *filepath; /* output_dir name followed by '/' */ if (params.output_dir) filename_len += strlen(params.output_dir) + 1; filepath = malloc(filename_len); if (!filepath) return NULL; snprintf(filepath, filename_len, "%s%s%s", params.output_dir ? : "", params.output_dir ? "/" : "", e->name); return filepath; } static int write_partition_to_file(const struct fpt_entry *e) { size_t end_offset = e->offset + e->length - 1; struct buffer part_buffer; char *filepath; if (end_offset > buffer_size(&fpt.input_buff)) { ERROR("Offset out of bounds for the partition!\n"); return -1; } filepath = get_file_path(e); if (!filepath) { ERROR("Failed to allocate space for filepath!\n"); return -1; } printf("Dumping %.4s in %s\n", e->name, filepath); buffer_splice(&part_buffer, &fpt.input_buff, e->offset, e->length); buffer_write_file(&part_buffer, filepath); free(filepath); return 0; } static int cmd_dump(void) { size_t i; bool found = false; struct stat sb; if (params.output_dir && (stat(params.output_dir, &sb) == -1)) { ERROR("Failed to stat %s: %s\n", params.output_dir, strerror(errno)); return -1; } for (i = 0; i < fpt.hdr_ops->get_entry_count(fpt.hdr); i++) { if (!should_dump_partition(&fpt.entries[i])) continue; found = true; if (write_partition_to_file(&fpt.entries[i])) return -1; } if (found == false) { if (params.partition_name) ERROR("%s not found!\n", params.partition_name); ERROR("No partitions dumped!\n"); return -1; } return 0; } static struct command { const char *name; const char *optstring; int (*function)(void); } commands[] = { { "print", "n:?", cmd_print }, { "dump", "n:o:?", cmd_dump }, }; static struct option long_options[] = { {"help", required_argument, 0, 'h'}, {"partition_name", required_argument, 0, 'n'}, {"output_dir", required_argument, 0, 'o'}, {NULL, 0, 0, 0 } }; int main(int argc, char **argv) { if (argc < 3) { ERROR("Incorrect number of args(%d)!\n", argc); usage(argv[0]); return 1; } const char *prog_name = argv[0]; const char *image_name = argv[1]; const char *cmd = argv[2]; size_t i; for (i = 0; i < ARRAY_SIZE(commands); i++) { if (strcmp(cmd, commands[i].name)) continue; int c; int option_index; while (1) { c = getopt_long(argc, argv, commands[i].optstring, long_options, &option_index); if (c == -1) break; if (strchr(commands[i].optstring, c) == NULL) { ERROR("Invalid option '%c'\n", c); usage(prog_name); return 1; } switch (c) { case 'o': params.output_dir = optarg; break; case 'n': params.partition_name = optarg; break; case 'h': case '?': default: usage(prog_name); return 1; } } break; } if (i == ARRAY_SIZE(commands)) { ERROR("No command match %s\n", cmd); usage(prog_name); return 1; } if (fpt_parse(image_name)) return 1; return commands[i].function(); }