/* Copyright 2013-2017 IBM Corp. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Flags: * - E: ECC for this part */ /* * TODO FIXME * Max line theoretical max size: * - name: 15 chars = 15 * - base: 0xffffffff = 10 * - size: 0xffffffff = 10 * - flag: E = 1 * * 36 + 3 separators = 39 * Plus \n 40 * Lets do 50. */ #define MAX_LINE (PATH_MAX+255) #define MAX_TOCS 10 #define SEPARATOR ',' /* Full version number (possibly includes gitid). */ extern const char version[]; static int read_u32(const char *input, uint32_t *val) { char *endptr; *val = strtoul(input, &endptr, 0); return (*endptr == SEPARATOR) ? 0 : 1; } static const char *advance_line(const char *input) { char *pos = strchr(input, SEPARATOR); if (!pos) return NULL; return pos + 1; } static struct ffs_hdr *parse_toc(const char *line, uint32_t block_size, uint32_t block_count) { struct ffs_entry_user user; struct ffs_entry *ent; struct ffs_hdr *hdr; uint32_t tbase; int rc; if (read_u32(line, &tbase)) { fprintf(stderr, "Couldn't parse TOC base address\n"); return NULL; } line = advance_line(line); if (!line) { fprintf(stderr, "Couldn't find TOC flags\n"); return NULL; } rc = ffs_string_to_entry_user(line, strlen(line), &user); if (rc) { fprintf(stderr, "Couldn't parse TOC flags\n"); return NULL; } rc = ffs_entry_new("part", tbase, 0, &ent); if (rc) { fprintf(stderr, "Couldn't make entry for TOC@0x%08x\n", tbase); return NULL; } rc = ffs_entry_user_set(ent, &user); if (rc) { fprintf(stderr, "Invalid TOC flag\n"); ffs_entry_put(ent); return NULL; } rc = ffs_hdr_new(block_size, block_count, &ent, &hdr); if (rc) { hdr = NULL; fprintf(stderr, "Couldn't make header for TOC@0x%08x\n", tbase); } ffs_entry_put(ent); return hdr; } static int parse_entry(struct blocklevel_device *bl, struct ffs_hdr **tocs, const char *line, bool allow_empty) { char name[FFS_PART_NAME_MAX + 2] = { 0 }; struct ffs_entry_user user = { 0 }; uint32_t pbase, psize, pactual, i; struct ffs_entry *new_entry; struct stat data_stat; const char *filename; bool added = false; uint8_t *data_ptr, ecc = 0; int data_fd, rc; char *pos; memcpy(name, line, FFS_PART_NAME_MAX + 1); pos = strchr(name, SEPARATOR); /* There is discussion to be had as to if we should bail here */ if (!pos) { fprintf(stderr, "WARNING: Long partition name will get truncated to '%s'\n", name); name[FFS_PART_NAME_MAX] = '\0'; } else { *pos = '\0'; } line = advance_line(line); if (!line || read_u32(line, &pbase)) { fprintf(stderr, "Couldn't parse '%s' partition base address\n", name); return -1; } line = advance_line(line); if (!line || read_u32(line, &psize)) { fprintf(stderr, "Couldn't parse '%s' partition length\n", name); return -1; } line = advance_line(line); if (!line || !advance_line(line)) { fprintf(stderr, "Couldn't find '%s' partition flags\n", name); return -1; } rc = ffs_string_to_entry_user(line, advance_line(line) - 1 - line, &user); if (rc) { fprintf(stderr, "Couldn't parse '%s' partition flags\n", name); return -1; } line = advance_line(line); /* Already checked return value */ rc = ffs_entry_new(name, pbase, psize, &new_entry); if (rc) { fprintf(stderr, "Invalid entry '%s' 0x%08x for 0x%08x\n", name, pbase, psize); return -1; } rc = ffs_entry_user_set(new_entry, &user); if (rc) { fprintf(stderr, "Couldn't set '%s' partition flags\n", name); ffs_entry_put(new_entry); return -1; } if (has_flag(new_entry, FFS_MISCFLAGS_BACKUP)) { rc = ffs_entry_set_act_size(new_entry, 0); if (rc) { fprintf(stderr, "Couldn't set '%s' partition actual size\n", name); ffs_entry_put(new_entry); return -1; } } if (!advance_line(line)) { fprintf(stderr, "Missing TOC field for '%s' partition\n", name); ffs_entry_put(new_entry); return -1; } while (*line != SEPARATOR) { int toc = *(line++); if (!isdigit(toc)) { fprintf(stderr, "Bad TOC value %d (%c) for '%s' partition\n", toc, toc, name); ffs_entry_put(new_entry); return -1; } toc -= '0'; if (!tocs[toc]) { fprintf(stderr, "No TOC with ID %d for '%s' partition\n", toc, name); ffs_entry_put(new_entry); return -1; } rc = ffs_entry_add(tocs[toc], new_entry); if (rc) { fprintf(stderr, "Couldn't add '%s' partition to TOC %d: %d\n", name, toc, rc); ffs_entry_put(new_entry); return rc; } added = true; } if (!added) { /* * They didn't specify a TOC in the TOC field, use * TOC@0 as the default */ rc = ffs_entry_add(tocs[0], new_entry); if (rc) { fprintf(stderr, "Couldn't add '%s' partition to default TOC: %d\n", name, rc); ffs_entry_put(new_entry); return rc; } } ffs_entry_put(new_entry); if (*line != '\0' && *(line + 1) != '\0') { filename = line + 1; /* * Support flashing already ecc'd data as this is the case * for POWER8 SBE image binary. */ if (has_ecc(new_entry) && !strstr(filename, ".ecc")) blocklevel_ecc_protect(bl, pbase, psize); data_fd = open(filename, O_RDONLY); if (data_fd == -1) { fprintf(stderr, "Couldn't open file '%s' for '%s' partition " "(%m)\n", filename, name); return -1; } if (fstat(data_fd, &data_stat) == -1) { fprintf(stderr, "Couldn't stat file '%s' for '%s' partition " "(%m)\n", filename, name); close(data_fd); return -1; } pactual = data_stat.st_size; /* * Sanity check that the file isn't too large for * partition */ if (has_ecc(new_entry) && !strstr(filename, ".ecc")) psize = ecc_buffer_size_minus_ecc(psize); if (pactual > psize) { fprintf(stderr, "File '%s' for partition '%s' is too large," " %u > %u\n", filename, name, pactual, psize); close(data_fd); return -1; } data_ptr = mmap(NULL, pactual, PROT_READ, MAP_SHARED, data_fd, 0); if (!data_ptr) { fprintf(stderr, "Couldn't mmap file '%s' for '%s' partition " "(%m)\n", filename, name); close(data_fd); return -1; } rc = blocklevel_write(bl, pbase, data_ptr, pactual); if (rc) fprintf(stderr, "Couldn't write file '%s' for '%s' partition to PNOR " "(%m)\n", filename, name); munmap(data_ptr, pactual); close(data_fd); } else { if (!allow_empty) { fprintf(stderr, "Filename missing for partition %s!\n", name); return -1; } if (has_ecc(new_entry)) { i = pbase + 8; while (i < pbase + psize) { rc = blocklevel_write(bl, i, &ecc, sizeof(ecc)); if (rc) { fprintf(stderr, "\nError setting ECC byte at 0x%08x\n", i); return rc; } i += 9; } } } return 0; } static void print_version(void) { printf("Open-Power FFS format tool %s\n", version); } static void print_help(const char *pname) { print_version(); printf("Usage: %s [options] -e -s size -c num -i layout_file -p pnor_file ...\n\n", pname); printf(" Options:\n"); printf("\t-e, --allow_empty\n"); printf("\t\tCreate partition as blank if not specified (sets ECC if flag set)\n\n"); printf("\t-s, --block_size=size\n"); printf("\t\tSize (in hex with leading 0x) of the blocks on the flash in bytes\n\n"); printf("\t-c, --block_count=num\n"); printf("\t\tNumber of blocks on the flash\n\n"); printf("\t-i, --input=file\n"); printf("\t\tFile containing the required partition data\n\n"); printf("\t-p, --pnor=file\n"); printf("\t\tOutput file to write data\n\n"); } int main(int argc, char *argv[]) { static char line[MAX_LINE]; char *pnor = NULL, *input = NULL; bool toc_created = false, bad_input = false, allow_empty = false; uint32_t block_size = 0, block_count = 0; struct ffs_hdr *tocs[MAX_TOCS] = { 0 }; struct blocklevel_device *bl = NULL; const char *pname = argv[0]; int line_number, rc, i; FILE *in_file; while(1) { struct option long_opts[] = { {"allow_empty", no_argument, NULL, 'e'}, {"block_count", required_argument, NULL, 'c'}, {"block_size", required_argument, NULL, 's'}, {"debug", no_argument, NULL, 'g'}, {"input", required_argument, NULL, 'i'}, {"pnor", required_argument, NULL, 'p'}, {NULL, 0, 0, 0} }; int c, oidx = 0; c = getopt_long(argc, argv, "+:ec:gi:p:s:", long_opts, &oidx); if (c == EOF) break; switch(c) { case 'e': allow_empty = true; break; case 'c': block_count = strtoul(optarg, NULL, 0); break; case 'g': libflash_debug = true; break; case 'i': free(input); input = strdup(optarg); if (!input) fprintf(stderr, "Out of memory!\n"); break; case 'p': free(pnor); pnor = strdup(optarg); if (!pnor) fprintf(stderr, "Out of memory!\n"); break; case 's': block_size = strtoul(optarg, NULL, 0); break; case ':': fprintf(stderr, "Unrecognised option \"%s\" to '%c'\n", optarg, optopt); bad_input = true; break; case '?': fprintf(stderr, "Unrecognised option '%c'\n", optopt); bad_input = true; break; default: fprintf(stderr , "Encountered unknown error parsing options\n"); bad_input = true; } } if (bad_input || !block_size || !block_count || !input || !pnor) { print_help(pname); return 1; } in_file = fopen(input, "r"); if (!in_file) { fprintf(stderr, "Couldn't open your input file %s: %m\n", input); return 2; } /* * TODO: This won't create the file. * We should do this */ rc = arch_flash_init(&bl, pnor, true); if (rc) { fprintf(stderr, "Couldn't initialise architecture flash structures\n"); fclose(in_file); return 3; } /* * 'Erase' the file, make it all 0xFF * TODO: Add sparse option and don't do this. */ rc = blocklevel_erase(bl, 0, block_size * block_count); if (rc) { fprintf(stderr, "Couldn't erase '%s' pnor file\n", pnor); fclose(in_file); return 4; } line_number = 0; while (fgets(line, MAX_LINE, in_file) != NULL) { line_number++; /* Inline comments in input file */ if (line[0] == '#') continue; if (line[strlen(line) - 1] == '\n') line[strlen(line) - 1] = '\0'; if (line[0] == '@') { int toc_num = line[1]; rc = 5; if (!isdigit(toc_num)) { fprintf(stderr, "Invalid TOC ID %d (%c)\n", toc_num, toc_num); goto parse_out; } toc_num -= '0'; if (line[2] != SEPARATOR) { fprintf(stderr, "TOC ID too long\n"); goto parse_out; } if (tocs[toc_num]) { fprintf(stderr, "Duplicate TOC ID %d\n", toc_num); goto parse_out; } tocs[toc_num] = parse_toc(&line[3], block_size, block_count); if (!tocs[toc_num]) goto parse_out; toc_created = true; } else { if (!toc_created) { fprintf(stderr, "WARNING: Attempting to parse a partition line without any TOCs created.\n"); fprintf(stderr, " Generating a default TOC at zero\n"); rc = ffs_hdr_new(block_size, block_count, NULL, &tocs[0]); if (rc) { rc = 7; fprintf(stderr, "Couldn't generate a default TOC at zero\n"); goto parse_out; } toc_created = true; } rc = parse_entry(bl, tocs, line, allow_empty); if (rc) { rc = 6; goto parse_out; } } } for(i = 0; i < MAX_TOCS; i++) { if (tocs[i]) { rc = ffs_hdr_finalise(bl, tocs[i]); if (rc) { rc = 7; fprintf(stderr, "Failed to write out TOC values\n"); break; } } } parse_out: if (rc == 5 || rc == 6) fprintf(stderr, "Failed to parse input file '%s' at line %d\n", input, line_number); arch_flash_close(bl, pnor); fclose(in_file); for(i = 0; i < MAX_TOCS; i++) ffs_hdr_free(tocs[i]); free(input); free(pnor); return rc; }