| /* fmaptool, CLI utility for converting plaintext fmd files into fmap blobs */ |
| /* SPDX-License-Identifier: GPL-2.0-only */ |
| |
| #include "common.h" |
| #include "cbfs_sections.h" |
| #include "fmap_from_fmd.h" |
| |
| #include <stdio.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| #define STDIN_FILENAME_SENTINEL "-" |
| |
| #define HEADER_FMAP_OFFSET "FMAP_OFFSET" |
| #define HEADER_FMAP_SIZE "FMAP_SIZE" |
| |
| /* |
| * Macro name used in the generated C header file to provide list of section names that do not |
| * have any sub-sections. |
| */ |
| #define HEADER_FMAP_TERMINAL_SECTIONS "FMAP_TERMINAL_SECTIONS" |
| |
| enum fmaptool_return { |
| FMAPTOOL_EXIT_SUCCESS = 0, |
| FMAPTOOL_EXIT_BAD_ARGS, |
| FMAPTOOL_EXIT_BAD_INPUT_PATH, |
| FMAPTOOL_EXIT_BAD_OUTPUT_PATH, |
| FMAPTOOL_EXIT_FAILED_DESCRIPTOR, |
| FMAPTOOL_EXIT_MISSING_FMAP_SECTION, |
| FMAPTOOL_EXIT_MISSING_PRIMARY_CBFS, |
| FMAPTOOL_EXIT_FAILED_FMAP_CONVERSION, |
| FMAPTOOL_EXIT_UNKNOWN_FMAP_SIZE, |
| FMAPTOOL_EXIT_FAILED_WRITING_OUTPUT, |
| FMAPTOOL_EXIT_FAILED_WRITING_HEADER, |
| }; |
| |
| static void usage(const char *invoked_as) |
| { |
| fputs("fmaptool: Compiler for fmd (flashmap descriptor) files\n", |
| stderr); |
| fputs("\nUSAGE:\n", stderr); |
| fprintf(stderr, |
| "\t%s [-h <header output file>] [-R <region output file>] <fmd input file> <binary output file>\n", |
| invoked_as); |
| fputs("\nMANDATORY ARGUMENTS:\n", stderr); |
| fprintf(stderr, |
| "<fmd input file> may be '%s' to read from standard input\n", |
| STDIN_FILENAME_SENTINEL); |
| fputs("<binary output file> must be a regular file\n", stderr); |
| fputs("\nOPTIONAL SWITCHES:\n", stderr); |
| fprintf(stderr, |
| "-h\tAlso produce a C header defining %s to the FMAP section's flash offset.\n", |
| HEADER_FMAP_OFFSET); |
| fprintf(stderr, |
| "-R\tAlso produce a text file listing the CBFS regions, comma separated.\n"); |
| fputs("\nOUTPUT:\n", stderr); |
| fputs("A successful invocation prints a summary of work done to standard error, and a comma-separated list\n", |
| stderr); |
| fputs("of those sections that contain CBFSes, starting with the primary such section, to standard output.\n", |
| stderr); |
| } |
| |
| static void list_cbfs_section_names(FILE *out) |
| { |
| cbfs_section_iterator_t cbfs_it = cbfs_sections_iterator(); |
| assert(cbfs_it); |
| |
| bool subsequent = false; |
| while (cbfs_it) { |
| const char *cur_name = |
| cbfs_sections_iterator_deref(cbfs_it)->name; |
| if (cbfs_sections_iterator_advance(&cbfs_it) && subsequent) |
| fputc(',', out); |
| fputs(cur_name, out); |
| subsequent = true; |
| } |
| fputc('\n', out); |
| } |
| |
| static void write_header_fmap_terminal_section_names(FILE *header, |
| const struct flashmap_descriptor *root) |
| { |
| assert(root); |
| |
| if (root->list_len == 0) { |
| fprintf(header, "%s ", root->name); |
| return; |
| } |
| |
| fmd_foreach_child(child, root) |
| write_header_fmap_terminal_section_names(header, child); |
| } |
| |
| static void write_header_fmap_sections(FILE *header, const struct flashmap_descriptor *root, |
| unsigned int offset) |
| { |
| assert(root); |
| /* |
| * The offset may only be unknown for the root node in a system where the flash isn't |
| * memory-mapped. |
| */ |
| if (!root->offset_known && offset != 0) |
| return; |
| |
| const unsigned int current_offset = offset + (root->offset_known ? root->offset : 0); |
| fprintf(header, "#define FMAP_SECTION_%s_START %#x\n", root->name, current_offset); |
| |
| if (!root->size_known) |
| return; |
| |
| fprintf(header, "#define FMAP_SECTION_%s_SIZE %#x\n", root->name, root->size); |
| |
| fmd_foreach_child(child, root) |
| write_header_fmap_sections(header, child, current_offset); |
| } |
| |
| static bool write_header(const char *out_fname, |
| const struct flashmap_descriptor *root, |
| const int fmap_size) |
| { |
| assert(out_fname); |
| |
| FILE *header = fopen(out_fname, "w"); |
| if (!header) { |
| fprintf(stderr, "FATAL: Unable to open file '%s' for writing\n", |
| out_fname); |
| return false; |
| } |
| |
| unsigned fmap_offset = |
| fmd_calc_absolute_offset(root, SECTION_NAME_FMAP); |
| assert(fmap_offset != FMD_NOTFOUND); |
| |
| fputs("#ifndef FMAPTOOL_GENERATED_HEADER_H_\n", header); |
| fputs("#define FMAPTOOL_GENERATED_HEADER_H_\n\n", header); |
| fprintf(header, "#define %s %#x\n", HEADER_FMAP_OFFSET, fmap_offset); |
| fprintf(header, "#define %s %#x\n\n", HEADER_FMAP_SIZE, fmap_size); |
| |
| fprintf(header, "#define %s \"", HEADER_FMAP_TERMINAL_SECTIONS); |
| write_header_fmap_terminal_section_names(header, root); |
| fprintf(header, "\"\n\n"); |
| |
| write_header_fmap_sections(header, root, 0); |
| fputs("\n", header); |
| |
| fputs("#endif\n", header); |
| |
| fclose(header); |
| return true; |
| } |
| |
| static void full_fmd_cleanup(struct flashmap_descriptor **victim) |
| { |
| assert(victim); |
| |
| cbfs_sections_cleanup(); |
| fmd_cleanup(*victim); |
| *victim = NULL; |
| } |
| |
| int main(int argc, char **argv) |
| { |
| struct { |
| // Mandatory |
| const char *fmd_filename; |
| const char *fmap_filename; |
| |
| // Optional |
| const char *header_filename; |
| const char *region_filename; |
| } args = {NULL, NULL, NULL, NULL}; |
| |
| bool show_usage = false; |
| int each_arg; |
| while (!show_usage && (each_arg = getopt(argc, argv, ":h:R:")) != -1) { |
| switch (each_arg) { |
| case 'h': |
| args.header_filename = optarg; |
| break; |
| case 'R': |
| args.region_filename = optarg; |
| break; |
| case ':': |
| fprintf(stderr, "-%c: Expected an accompanying value\n", |
| optopt); |
| show_usage = true; |
| break; |
| default: |
| fprintf(stderr, "-%c: Unexpected command-line switch\n", |
| optopt); |
| show_usage = true; |
| } |
| } |
| |
| if (show_usage || argc - optind != 2) { |
| usage(argv[0]); |
| return FMAPTOOL_EXIT_BAD_ARGS; |
| } |
| args.fmd_filename = argv[optind]; |
| args.fmap_filename = argv[optind + 1]; |
| |
| FILE *fmd_file = stdin; |
| if (strcmp(args.fmd_filename, STDIN_FILENAME_SENTINEL) != 0) { |
| fmd_file = fopen(args.fmd_filename, "r"); |
| if (!fmd_file) { |
| fprintf(stderr, "FATAL: Unable to open file '%s'\n", |
| args.fmd_filename); |
| return FMAPTOOL_EXIT_BAD_INPUT_PATH; |
| } |
| } |
| |
| struct flashmap_descriptor *descriptor = fmd_create(fmd_file); |
| fclose(fmd_file); |
| if (!descriptor) { |
| fputs("FATAL: Failed while processing provided descriptor\n", |
| stderr); |
| full_fmd_cleanup(&descriptor); |
| return FMAPTOOL_EXIT_FAILED_DESCRIPTOR; |
| } |
| |
| if (!fmd_find_node(descriptor, SECTION_NAME_FMAP)) { |
| fprintf(stderr, |
| "FATAL: Flashmap descriptor must have an '%s' section\n", |
| SECTION_NAME_FMAP); |
| full_fmd_cleanup(&descriptor); |
| return FMAPTOOL_EXIT_MISSING_FMAP_SECTION; |
| } |
| |
| if (!cbfs_sections_primary_cbfs_accounted_for()) { |
| fprintf(stderr, |
| "FATAL: Flashmap descriptor must have a '%s' section that is annotated with '(%s)'\n", |
| SECTION_NAME_PRIMARY_CBFS, |
| SECTION_ANNOTATION_CBFS); |
| full_fmd_cleanup(&descriptor); |
| return FMAPTOOL_EXIT_MISSING_PRIMARY_CBFS; |
| } |
| |
| struct fmap *flashmap = fmap_from_fmd(descriptor); |
| if (!flashmap) { |
| fputs("FATAL: Failed while constructing FMAP section\n", |
| stderr); |
| full_fmd_cleanup(&descriptor); |
| return FMAPTOOL_EXIT_FAILED_FMAP_CONVERSION; |
| } |
| |
| int size = fmap_size(flashmap); |
| if (size < 0) { |
| fputs("FATAL: Failed to determine FMAP section size\n", |
| stderr); |
| fmap_destroy(flashmap); |
| full_fmd_cleanup(&descriptor); |
| return FMAPTOOL_EXIT_UNKNOWN_FMAP_SIZE; |
| } |
| |
| FILE *fmap_file = fopen(args.fmap_filename, "wb"); |
| if (!fmap_file) { |
| fprintf(stderr, "FATAL: Unable to open file '%s' for writing\n", |
| args.fmap_filename); |
| fmap_destroy(flashmap); |
| full_fmd_cleanup(&descriptor); |
| return FMAPTOOL_EXIT_BAD_OUTPUT_PATH; |
| } |
| |
| if (!fwrite(flashmap, size, 1, fmap_file)) { |
| fputs("FATAL: Failed to write final FMAP to file\n", stderr); |
| fclose(fmap_file); |
| fmap_destroy(flashmap); |
| full_fmd_cleanup(&descriptor); |
| return FMAPTOOL_EXIT_FAILED_WRITING_OUTPUT; |
| } |
| fclose(fmap_file); |
| fmap_destroy(flashmap); |
| |
| if (args.header_filename && |
| !write_header(args.header_filename, descriptor, size)) { |
| full_fmd_cleanup(&descriptor); |
| return FMAPTOOL_EXIT_FAILED_WRITING_HEADER; |
| } |
| |
| fprintf(stderr, "SUCCESS: Wrote %d bytes to file '%s'%s\n", size, |
| args.fmap_filename, |
| args.header_filename ? " (and generated header)" : ""); |
| fputs("The sections containing CBFSes are: ", stderr); |
| list_cbfs_section_names(stdout); |
| if (args.region_filename) { |
| FILE *region_file = fopen(args.region_filename, "w"); |
| if (region_file == NULL) |
| return FMAPTOOL_EXIT_FAILED_WRITING_OUTPUT; |
| |
| list_cbfs_section_names(region_file); |
| fclose(region_file); |
| } |
| |
| full_fmd_cleanup(&descriptor); |
| return FMAPTOOL_EXIT_SUCCESS; |
| } |