532 lines
12 KiB
C
532 lines
12 KiB
C
|
/* 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 <ctype.h>
|
||
|
#include <errno.h>
|
||
|
#include <fcntl.h>
|
||
|
#include <getopt.h>
|
||
|
#include <limits.h>
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
#include <stdint.h>
|
||
|
#include <stdbool.h>
|
||
|
#include <sys/stat.h>
|
||
|
#include <sys/mman.h>
|
||
|
#include <unistd.h>
|
||
|
|
||
|
#include <libflash/libflash.h>
|
||
|
#include <libflash/libffs.h>
|
||
|
#include <libflash/blocklevel.h>
|
||
|
#include <libflash/ecc.h>
|
||
|
#include <common/arch_flash.h>
|
||
|
|
||
|
/*
|
||
|
* 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;
|
||
|
}
|