summaryrefslogtreecommitdiff
path: root/util/cbfstool/cse_fpt.c
diff options
context:
space:
mode:
Diffstat (limited to 'util/cbfstool/cse_fpt.c')
-rw-r--r--util/cbfstool/cse_fpt.c434
1 files changed, 434 insertions, 0 deletions
diff --git a/util/cbfstool/cse_fpt.c b/util/cbfstool/cse_fpt.c
new file mode 100644
index 0000000000..bde3a8c165
--- /dev/null
+++ b/util/cbfstool/cse_fpt.c
@@ -0,0 +1,434 @@
+/* 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);
+}
+
+void read_member(struct buffer *buff, void *dst, size_t size)
+{
+ uint8_t *src = buffer_get(buff);
+
+ switch (size) {
+ case 1:
+ *(uint8_t *)dst = read_le8(src);
+ break;
+ case 2:
+ *(uint16_t *)dst = read_le16(src);
+ break;
+ case 4:
+ *(uint32_t *)dst = read_le32(src);
+ break;
+ case 8:
+ *(uint64_t *)dst = read_le64(src);
+ break;
+ default:
+ memcpy(dst, src, size);
+ }
+
+ buffer_seek(buff, size);
+}
+
+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();
+}