coreboot-kgpe-d16/util/cbfstool/cbfstool.c
Patrick Georgi 848e30daa1 cbfstool: show "preserved" flag in cbfstool layout output
The flag is useful for updaters to determine which areas to leave
alone, such as VPD (vital product data) regions that are set in
factory and might contain unique (MAC addresses) or hard to obtain
(calibration output) data.

It's also useful to see which regions are marked as such.

Change-Id: Ic0a229d474b32ac156cfabc917714ce9d339bac6
Signed-off-by: Patrick Georgi <pgeorgi@google.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/33604
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Paul Menzel <paulepanter@users.sourceforge.net>
Reviewed-by: Martin Roth <martinroth@google.com>
2019-07-02 18:48:02 +00:00

1817 lines
51 KiB
C

/*
* cbfstool, CLI utility for CBFS file manipulation
*
* Copyright (C) 2009 coresystems GmbH
* written by Patrick Georgi <patrick.georgi@coresystems.de>
* Copyright (C) 2012 Google, Inc.
* Copyright (C) 2016 Siemens AG
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <ctype.h>
#include <unistd.h>
#include <getopt.h>
#include "common.h"
#include "cbfs.h"
#include "cbfs_image.h"
#include "cbfs_sections.h"
#include "elfparsing.h"
#include "partitioned_file.h"
#include <commonlib/fsp.h>
#include <commonlib/endian.h>
#include <commonlib/helpers.h>
#define SECTION_WITH_FIT_TABLE "BOOTBLOCK"
struct command {
const char *name;
const char *optstring;
int (*function) (void);
// Whether to populate param.image_region before invoking function
bool accesses_region;
// This set to true means two things:
// - in case of a command operating on a region, the region's contents
// will be written back to image_file at the end
// - write access to the file is required
bool modifies_region;
};
static struct param {
partitioned_file_t *image_file;
struct buffer *image_region;
const char *name;
const char *filename;
const char *fmap;
const char *region_name;
const char *source_region;
const char *bootblock;
const char *ignore_section;
const char *ucode_region;
uint64_t u64val;
uint32_t type;
uint32_t baseaddress;
uint32_t baseaddress_assigned;
uint32_t loadaddress;
uint32_t headeroffset;
uint32_t headeroffset_assigned;
uint32_t entrypoint;
uint32_t size;
uint32_t alignment;
uint32_t pagesize;
uint32_t cbfsoffset;
uint32_t cbfsoffset_assigned;
uint32_t arch;
uint32_t padding;
uint32_t topswap_size;
bool u64val_assigned;
bool fill_partial_upward;
bool fill_partial_downward;
bool show_immutable;
bool stage_xip;
bool autogen_attr;
bool machine_parseable;
bool unprocessed;
enum comp_algo compression;
int precompression;
enum vb2_hash_algorithm hash;
/* For linux payloads */
char *initrd;
char *cmdline;
int force;
} param = {
/* All variables not listed are initialized as zero. */
.arch = CBFS_ARCHITECTURE_UNKNOWN,
.compression = CBFS_COMPRESS_NONE,
.hash = VB2_HASH_INVALID,
.headeroffset = ~0,
.region_name = SECTION_NAME_PRIMARY_CBFS,
.u64val = -1,
};
static bool region_is_flashmap(const char *region)
{
return partitioned_file_region_check_magic(param.image_file, region,
FMAP_SIGNATURE, strlen(FMAP_SIGNATURE));
}
/* @return Same as cbfs_is_valid_cbfs(), but for a named region. */
static bool region_is_modern_cbfs(const char *region)
{
return partitioned_file_region_check_magic(param.image_file, region,
CBFS_FILE_MAGIC, strlen(CBFS_FILE_MAGIC));
}
/*
* Converts between offsets from the start of the specified image region and
* "top-aligned" offsets from the top of the entire boot media. See comment
* below for convert_to_from_top_aligned() about forming addresses.
*/
static unsigned convert_to_from_absolute_top_aligned(
const struct buffer *region, unsigned offset)
{
assert(region);
size_t image_size = partitioned_file_total_size(param.image_file);
return image_size - region->offset - offset;
}
/*
* Converts between offsets from the start of the specified image region and
* "top-aligned" offsets from the top of the image region. Works in either
* direction: pass in one type of offset and receive the other type.
* N.B. A top-aligned offset is always a positive number, and should not be
* confused with a top-aligned *address*, which is its arithmetic inverse. */
static unsigned convert_to_from_top_aligned(const struct buffer *region,
unsigned offset)
{
assert(region);
/* Cover the situation where a negative base address is given by the
* user. Callers of this function negate it, so it'll be a positive
* number smaller than the region.
*/
if ((offset > 0) && (offset < region->size)) {
return region->size - offset;
}
return convert_to_from_absolute_top_aligned(region, offset);
}
static int do_cbfs_locate(int32_t *cbfs_addr, size_t metadata_size,
size_t data_size)
{
if (!param.filename) {
ERROR("You need to specify -f/--filename.\n");
return 1;
}
if (!param.name) {
ERROR("You need to specify -n/--name.\n");
return 1;
}
struct cbfs_image image;
if (cbfs_image_from_buffer(&image, param.image_region,
param.headeroffset))
return 1;
if (cbfs_get_entry(&image, param.name))
WARN("'%s' already in CBFS.\n", param.name);
if (!data_size) {
struct buffer buffer;
if (buffer_from_file(&buffer, param.filename) != 0) {
ERROR("Cannot load %s.\n", param.filename);
return 1;
}
data_size = buffer.size;
buffer_delete(&buffer);
}
DEBUG("File size is %zd (0x%zx)\n", data_size, data_size);
/* Include cbfs_file size along with space for with name. */
metadata_size += cbfs_calculate_file_header_size(param.name);
/* Adjust metadata_size if additional attributes were added */
if (param.autogen_attr) {
if (param.alignment)
metadata_size += sizeof(struct cbfs_file_attr_align);
if (param.baseaddress_assigned || param.stage_xip)
metadata_size += sizeof(struct cbfs_file_attr_position);
}
/* Take care of the hash attribute if it is used */
if (param.hash != VB2_HASH_INVALID)
metadata_size += sizeof(struct cbfs_file_attr_hash);
int32_t address = cbfs_locate_entry(&image, data_size, param.pagesize,
param.alignment, metadata_size);
if (address == -1) {
ERROR("'%s' can't fit in CBFS for page-size %#x, align %#x.\n",
param.name, param.pagesize, param.alignment);
return 1;
}
*cbfs_addr = address;
return 0;
}
typedef int (*convert_buffer_t)(struct buffer *buffer, uint32_t *offset,
struct cbfs_file *header);
static int cbfs_add_integer_component(const char *name,
uint64_t u64val,
uint32_t offset,
uint32_t headeroffset) {
struct cbfs_image image;
struct cbfs_file *header = NULL;
struct buffer buffer;
int i, ret = 1;
if (!name) {
ERROR("You need to specify -n/--name.\n");
return 1;
}
if (buffer_create(&buffer, 8, name) != 0)
return 1;
for (i = 0; i < 8; i++)
buffer.data[i] = (u64val >> i*8) & 0xff;
if (cbfs_image_from_buffer(&image, param.image_region, headeroffset)) {
ERROR("Selected image region is not a CBFS.\n");
goto done;
}
if (cbfs_get_entry(&image, name)) {
ERROR("'%s' already in ROM image.\n", name);
goto done;
}
if (IS_TOP_ALIGNED_ADDRESS(offset))
offset = convert_to_from_top_aligned(param.image_region,
-offset);
header = cbfs_create_file_header(CBFS_COMPONENT_RAW,
buffer.size, name);
if (cbfs_add_entry(&image, &buffer, offset, header) != 0) {
ERROR("Failed to add %llu into ROM image as '%s'.\n",
(long long unsigned)u64val, name);
goto done;
}
ret = 0;
done:
free(header);
buffer_delete(&buffer);
return ret;
}
static int is_valid_topswap(void)
{
switch (param.topswap_size) {
case (64 * KiB):
case (128 * KiB):
case (256 * KiB):
case (512 * KiB):
case (1 * MiB):
break;
default:
ERROR("Invalid topswap_size %d, topswap can be 64K|128K|256K|512K|1M\n",
param.topswap_size);
return 0;
}
return 1;
}
static void fill_header_offset(void *location, uint32_t offset)
{
// TODO: When we have a BE target, we'll need to store this as BE
write_le32(location, offset);
}
static int update_master_header_loc_topswap(struct cbfs_image *image,
void *h_loc, uint32_t header_offset)
{
struct cbfs_file *entry;
void *ts_h_loc = h_loc;
entry = cbfs_get_entry(image, "bootblock");
if (entry == NULL) {
ERROR("Bootblock not in ROM image?!?\n");
return 1;
}
/*
* Check if the existing topswap boundary matches with
* the one provided.
*/
if (param.topswap_size != ntohl(entry->len)/2) {
ERROR("Top swap boundary does not match\n");
return 1;
}
ts_h_loc -= param.topswap_size;
fill_header_offset(ts_h_loc, header_offset);
return 0;
}
static int cbfs_add_master_header(void)
{
const char * const name = "cbfs master header";
struct cbfs_image image;
struct cbfs_file *header = NULL;
struct buffer buffer;
int ret = 1;
size_t offset;
size_t size;
void *h_loc;
if (cbfs_image_from_buffer(&image, param.image_region,
param.headeroffset)) {
ERROR("Selected image region is not a CBFS.\n");
return 1;
}
if (cbfs_get_entry(&image, name)) {
ERROR("'%s' already in ROM image.\n", name);
return 1;
}
if (buffer_create(&buffer, sizeof(struct cbfs_header), name) != 0)
return 1;
struct cbfs_header *h = (struct cbfs_header *)buffer.data;
h->magic = htonl(CBFS_HEADER_MAGIC);
h->version = htonl(CBFS_HEADER_VERSION);
/* The 4 bytes are left out for two reasons:
* 1. the cbfs master header pointer resides there
* 2. some cbfs implementations assume that an image that resides
* below 4GB has a bootblock and get confused when the end of the
* image is at 4GB == 0.
*/
h->bootblocksize = htonl(4);
h->align = htonl(CBFS_ENTRY_ALIGNMENT);
/* The offset and romsize fields within the master header are absolute
* values within the boot media. As such, romsize needs to relfect
* the end 'offset' for a CBFS. To achieve that the current buffer
* representing the CBFS region's size is added to the offset of
* the region within a larger image.
*/
offset = buffer_get(param.image_region) -
buffer_get_original_backing(param.image_region);
size = buffer_size(param.image_region);
h->romsize = htonl(size + offset);
h->offset = htonl(offset);
h->architecture = htonl(CBFS_ARCHITECTURE_UNKNOWN);
header = cbfs_create_file_header(CBFS_COMPONENT_CBFSHEADER,
buffer_size(&buffer), name);
if (cbfs_add_entry(&image, &buffer, 0, header) != 0) {
ERROR("Failed to add cbfs master header into ROM image.\n");
goto done;
}
struct cbfs_file *entry;
if ((entry = cbfs_get_entry(&image, name)) == NULL) {
ERROR("'%s' not in ROM image?!?\n", name);
goto done;
}
uint32_t header_offset = CBFS_SUBHEADER(entry) -
buffer_get(&image.buffer);
header_offset = -(buffer_size(&image.buffer) - header_offset);
h_loc = (void *)(buffer_get(&image.buffer) +
buffer_size(&image.buffer) - 4);
fill_header_offset(h_loc, header_offset);
/*
* If top swap present, update the header
* location in secondary bootblock
*/
if (param.topswap_size) {
if (update_master_header_loc_topswap(&image, h_loc,
header_offset))
return 1;
}
ret = 0;
done:
free(header);
buffer_delete(&buffer);
return ret;
}
static int add_topswap_bootblock(struct buffer *buffer, uint32_t *offset)
{
size_t bb_buf_size = buffer_size(buffer);
if (bb_buf_size > param.topswap_size) {
ERROR("Bootblock bigger than the topswap boundary\n");
ERROR("size = %zd, ts = %d\n", bb_buf_size,
param.topswap_size);
return 1;
}
/*
* Allocate topswap_size*2 bytes for bootblock to
* accommodate the second bootblock.
*/
struct buffer new_bootblock, bb1, bb2;
if (buffer_create(&new_bootblock, 2 * param.topswap_size,
buffer->name))
return 1;
buffer_splice(&bb1, &new_bootblock, param.topswap_size - bb_buf_size,
bb_buf_size);
buffer_splice(&bb2, &new_bootblock,
buffer_size(&new_bootblock) - bb_buf_size,
bb_buf_size);
/* Copy to first bootblock */
memcpy(buffer_get(&bb1), buffer_get(buffer), bb_buf_size);
/* Copy to second bootblock */
memcpy(buffer_get(&bb2), buffer_get(buffer), bb_buf_size);
buffer_delete(buffer);
buffer_clone(buffer, &new_bootblock);
/* Update the location (offset) of bootblock in the region */
*offset = convert_to_from_top_aligned(param.image_region,
buffer_size(buffer));
return 0;
}
static int cbfs_add_component(const char *filename,
const char *name,
uint32_t type,
uint32_t offset,
uint32_t headeroffset,
convert_buffer_t convert)
{
if (!filename) {
ERROR("You need to specify -f/--filename.\n");
return 1;
}
if (!name) {
ERROR("You need to specify -n/--name.\n");
return 1;
}
if (type == 0) {
ERROR("You need to specify a valid -t/--type.\n");
return 1;
}
struct cbfs_image image;
if (cbfs_image_from_buffer(&image, param.image_region, headeroffset))
return 1;
if (cbfs_get_entry(&image, name)) {
ERROR("'%s' already in ROM image.\n", name);
return 1;
}
struct buffer buffer;
if (buffer_from_file(&buffer, filename) != 0) {
ERROR("Could not load file '%s'.\n", filename);
return 1;
}
/*
* Check if Intel CPU topswap is specified this will require a
* second bootblock to be added.
*/
if (type == CBFS_COMPONENT_BOOTBLOCK && param.topswap_size)
if (add_topswap_bootblock(&buffer, &offset))
return 1;
struct cbfs_file *header =
cbfs_create_file_header(type, buffer.size, name);
if (convert && convert(&buffer, &offset, header) != 0) {
ERROR("Failed to parse file '%s'.\n", filename);
buffer_delete(&buffer);
return 1;
}
if (param.hash != VB2_HASH_INVALID)
if (cbfs_add_file_hash(header, &buffer, param.hash) == -1) {
ERROR("couldn't add hash for '%s'\n", name);
free(header);
buffer_delete(&buffer);
return 1;
}
if (param.autogen_attr) {
/* Add position attribute if assigned */
if (param.baseaddress_assigned || param.stage_xip) {
struct cbfs_file_attr_position *attrs =
(struct cbfs_file_attr_position *)
cbfs_add_file_attr(header,
CBFS_FILE_ATTR_TAG_POSITION,
sizeof(struct cbfs_file_attr_position));
if (attrs == NULL)
return -1;
/* If we add a stage or a payload, we need to take */
/* care about the additional metadata that is added */
/* to the cbfs file and therefore set the position */
/* the real beginning of the data. */
if (type == CBFS_COMPONENT_STAGE)
attrs->position = htonl(offset +
sizeof(struct cbfs_stage));
else if (type == CBFS_COMPONENT_SELF)
attrs->position = htonl(offset +
sizeof(struct cbfs_payload));
else
attrs->position = htonl(offset);
}
/* Add alignment attribute if used */
if (param.alignment) {
struct cbfs_file_attr_align *attrs =
(struct cbfs_file_attr_align *)
cbfs_add_file_attr(header,
CBFS_FILE_ATTR_TAG_ALIGNMENT,
sizeof(struct cbfs_file_attr_align));
if (attrs == NULL)
return -1;
attrs->alignment = htonl(param.alignment);
}
}
if (param.padding) {
const uint32_t hs = sizeof(struct cbfs_file_attribute);
uint32_t size = MAX(hs, param.padding);
INFO("Padding %d bytes\n", size);
struct cbfs_file_attribute *attr =
(struct cbfs_file_attribute *)cbfs_add_file_attr(
header, CBFS_FILE_ATTR_TAG_PADDING,
size);
if (attr == NULL)
return -1;
}
if (IS_TOP_ALIGNED_ADDRESS(offset))
offset = convert_to_from_top_aligned(param.image_region,
-offset);
if (cbfs_add_entry(&image, &buffer, offset, header) != 0) {
ERROR("Failed to add '%s' into ROM image.\n", filename);
free(header);
buffer_delete(&buffer);
return 1;
}
free(header);
buffer_delete(&buffer);
return 0;
}
static int cbfstool_convert_raw(struct buffer *buffer,
unused uint32_t *offset, struct cbfs_file *header)
{
char *compressed;
int decompressed_size, compressed_size;
comp_func_ptr compress;
decompressed_size = buffer->size;
if (param.precompression) {
param.compression = read_le32(buffer->data);
decompressed_size = read_le32(buffer->data + sizeof(uint32_t));
compressed_size = buffer->size - 8;
compressed = malloc(compressed_size);
if (!compressed)
return -1;
memcpy(compressed, buffer->data + 8, compressed_size);
} else {
compress = compression_function(param.compression);
if (!compress)
return -1;
compressed = calloc(buffer->size, 1);
if (!compressed)
return -1;
if (compress(buffer->data, buffer->size,
compressed, &compressed_size)) {
WARN("Compression failed - disabled\n");
free(compressed);
return 0;
}
}
struct cbfs_file_attr_compression *attrs =
(struct cbfs_file_attr_compression *)
cbfs_add_file_attr(header,
CBFS_FILE_ATTR_TAG_COMPRESSION,
sizeof(struct cbfs_file_attr_compression));
if (attrs == NULL) {
free(compressed);
return -1;
}
attrs->compression = htonl(param.compression);
attrs->decompressed_size = htonl(decompressed_size);
free(buffer->data);
buffer->data = compressed;
buffer->size = compressed_size;
header->len = htonl(buffer->size);
return 0;
}
static int cbfstool_convert_fsp(struct buffer *buffer,
uint32_t *offset, struct cbfs_file *header)
{
uint32_t address;
struct buffer fsp;
int do_relocation = 1;
address = *offset;
/*
* If the FSP component is xip, then ensure that the address is a memory
* mapped one.
* If the FSP component is not xip, then use param.baseaddress that is
* passed in by the caller.
*/
if (param.stage_xip) {
if (!IS_TOP_ALIGNED_ADDRESS(address))
address = -convert_to_from_absolute_top_aligned(
param.image_region, address);
} else {
if (param.baseaddress_assigned == 0) {
INFO("Honoring pre-linked FSP module.\n");
do_relocation = 0;
} else {
address = param.baseaddress;
}
/*
* *offset should either be 0 or the value returned by
* do_cbfs_locate. do_cbfs_locate should not ever return a value
* that is TOP_ALIGNED_ADDRESS. Thus, if *offset contains a top
* aligned address, set it to 0.
*
* The only requirement in this case is that the binary should
* be relocated to the base address that is requested. There is
* no requirement on where the file ends up in the cbfs.
*/
if (IS_TOP_ALIGNED_ADDRESS(*offset))
*offset = 0;
}
/*
* Nothing left to do if relocation is not being attempted. Just add
* the file.
*/
if (!do_relocation)
return cbfstool_convert_raw(buffer, offset, header);
/* Create a copy of the buffer to attempt relocation. */
if (buffer_create(&fsp, buffer_size(buffer), "fsp"))
return -1;
memcpy(buffer_get(&fsp), buffer_get(buffer), buffer_size(buffer));
/* Replace the buffer contents w/ the relocated ones on success. */
if (fsp_component_relocate(address, buffer_get(&fsp), buffer_size(&fsp))
> 0) {
buffer_delete(buffer);
buffer_clone(buffer, &fsp);
} else {
buffer_delete(&fsp);
WARN("Invalid FSP variant.\n");
}
/* Let the raw path handle all the cbfs metadata logic. */
return cbfstool_convert_raw(buffer, offset, header);
}
static int cbfstool_convert_mkstage(struct buffer *buffer, uint32_t *offset,
struct cbfs_file *header)
{
struct buffer output;
int ret;
if (param.stage_xip) {
int32_t address;
size_t data_size;
if (elf_program_file_size(buffer, &data_size) < 0) {
ERROR("Could not obtain ELF size\n");
return 1;
}
if (do_cbfs_locate(&address, sizeof(struct cbfs_stage),
data_size)) {
ERROR("Could not find location for XIP stage.\n");
return 1;
}
/*
* Ensure the address is a memory mapped one. This assumes
* x86 semantics about the boot media being directly mapped
* below 4GiB in the CPU address space.
**/
address = -convert_to_from_absolute_top_aligned(
param.image_region, address);
*offset = address;
ret = parse_elf_to_xip_stage(buffer, &output, offset,
param.ignore_section);
} else
ret = parse_elf_to_stage(buffer, &output, param.compression,
offset, param.ignore_section);
if (ret != 0)
return -1;
buffer_delete(buffer);
// Direct assign, no dupe.
memcpy(buffer, &output, sizeof(*buffer));
header->len = htonl(output.size);
return 0;
}
static int cbfstool_convert_mkpayload(struct buffer *buffer,
unused uint32_t *offset, struct cbfs_file *header)
{
struct buffer output;
int ret;
/* Per default, try and see if payload is an ELF binary */
ret = parse_elf_to_payload(buffer, &output, param.compression);
/* If it's not an ELF, see if it's a FIT */
if (ret != 0) {
ret = parse_fit_to_payload(buffer, &output, param.compression);
if (ret == 0)
header->type = htonl(CBFS_COMPONENT_FIT);
}
/* If it's not an FIT, see if it's a UEFI FV */
if (ret != 0)
ret = parse_fv_to_payload(buffer, &output, param.compression);
/* If it's neither ELF nor UEFI Fv, try bzImage */
if (ret != 0)
ret = parse_bzImage_to_payload(buffer, &output,
param.initrd, param.cmdline, param.compression);
/* Not a supported payload type */
if (ret != 0) {
ERROR("Not a supported payload type (ELF / FV).\n");
buffer_delete(buffer);
return -1;
}
buffer_delete(buffer);
// Direct assign, no dupe.
memcpy(buffer, &output, sizeof(*buffer));
header->len = htonl(output.size);
return 0;
}
static int cbfstool_convert_mkflatpayload(struct buffer *buffer,
unused uint32_t *offset, struct cbfs_file *header)
{
struct buffer output;
if (parse_flat_binary_to_payload(buffer, &output,
param.loadaddress,
param.entrypoint,
param.compression) != 0) {
return -1;
}
buffer_delete(buffer);
// Direct assign, no dupe.
memcpy(buffer, &output, sizeof(*buffer));
header->len = htonl(output.size);
return 0;
}
static int cbfs_add(void)
{
int32_t address;
convert_buffer_t convert;
uint32_t local_baseaddress = param.baseaddress;
if (param.alignment && param.baseaddress) {
ERROR("Cannot specify both alignment and base address\n");
return 1;
}
convert = cbfstool_convert_raw;
/* Set the alignment to 4KiB minimum for FSP blobs when no base address
* is provided so that relocation can occur. */
if (param.type == CBFS_COMPONENT_FSP) {
if (!param.baseaddress_assigned)
param.alignment = 4*1024;
convert = cbfstool_convert_fsp;
} else if (param.stage_xip) {
ERROR("cbfs add supports xip only for FSP component type\n");
return 1;
}
if (param.alignment) {
/* CBFS compression file attribute is unconditionally added. */
size_t metadata_sz = sizeof(struct cbfs_file_attr_compression);
if (do_cbfs_locate(&address, metadata_sz, 0))
return 1;
local_baseaddress = address;
}
return cbfs_add_component(param.filename,
param.name,
param.type,
local_baseaddress,
param.headeroffset,
convert);
}
static int cbfs_add_stage(void)
{
if (param.stage_xip) {
if (param.baseaddress_assigned) {
ERROR("Cannot specify base address for XIP.\n");
return 1;
}
if (param.compression != CBFS_COMPRESS_NONE) {
ERROR("Cannot specify compression for XIP.\n");
return 1;
}
}
return cbfs_add_component(param.filename,
param.name,
CBFS_COMPONENT_STAGE,
param.baseaddress,
param.headeroffset,
cbfstool_convert_mkstage);
}
static int cbfs_add_payload(void)
{
return cbfs_add_component(param.filename,
param.name,
CBFS_COMPONENT_SELF,
param.baseaddress,
param.headeroffset,
cbfstool_convert_mkpayload);
}
static int cbfs_add_flat_binary(void)
{
if (param.loadaddress == 0) {
ERROR("You need to specify a valid "
"-l/--load-address.\n");
return 1;
}
if (param.entrypoint == 0) {
ERROR("You need to specify a valid "
"-e/--entry-point.\n");
return 1;
}
return cbfs_add_component(param.filename,
param.name,
CBFS_COMPONENT_SELF,
param.baseaddress,
param.headeroffset,
cbfstool_convert_mkflatpayload);
}
static int cbfs_add_integer(void)
{
if (!param.u64val_assigned) {
ERROR("You need to specify a value to write.\n");
return 1;
}
return cbfs_add_integer_component(param.name,
param.u64val,
param.baseaddress,
param.headeroffset);
}
static int cbfs_remove(void)
{
if (!param.name) {
ERROR("You need to specify -n/--name.\n");
return 1;
}
struct cbfs_image image;
if (cbfs_image_from_buffer(&image, param.image_region,
param.headeroffset))
return 1;
if (cbfs_remove_entry(&image, param.name) != 0) {
ERROR("Removing file '%s' failed.\n",
param.name);
return 1;
}
return 0;
}
static int cbfs_create(void)
{
struct cbfs_image image;
memset(&image, 0, sizeof(image));
buffer_clone(&image.buffer, param.image_region);
if (param.fmap) {
if (param.arch != CBFS_ARCHITECTURE_UNKNOWN || param.size ||
param.baseaddress_assigned ||
param.headeroffset_assigned ||
param.cbfsoffset_assigned ||
param.bootblock) {
ERROR("Since -M was provided, -m, -s, -b, -o, -H, and -B should be omitted\n");
return 1;
}
return cbfs_image_create(&image, image.buffer.size);
}
if (param.arch == CBFS_ARCHITECTURE_UNKNOWN) {
ERROR("You need to specify -m/--machine arch.\n");
return 1;
}
struct buffer bootblock;
if (!param.bootblock) {
DEBUG("-B not given, creating image without bootblock.\n");
if (buffer_create(&bootblock, 0, "(dummy)") != 0)
return 1;
} else if (buffer_from_file(&bootblock, param.bootblock)) {
return 1;
}
if (!param.alignment)
param.alignment = CBFS_ALIGNMENT;
// Set default offsets. x86, as usual, needs to be a special snowflake.
if (!param.baseaddress_assigned) {
if (param.arch == CBFS_ARCHITECTURE_X86) {
// Make sure there's at least enough room for rel_offset
param.baseaddress = param.size -
MAX(bootblock.size, sizeof(int32_t));
DEBUG("x86 -> bootblock lies at end of ROM (%#x).\n",
param.baseaddress);
} else {
param.baseaddress = 0;
DEBUG("bootblock starts at address 0x0.\n");
}
}
if (!param.headeroffset_assigned) {
if (param.arch == CBFS_ARCHITECTURE_X86) {
param.headeroffset = param.baseaddress -
sizeof(struct cbfs_header);
DEBUG("x86 -> CBFS header before bootblock (%#x).\n",
param.headeroffset);
} else {
param.headeroffset = align_up(param.baseaddress +
bootblock.size, sizeof(uint32_t));
DEBUG("CBFS header placed behind bootblock (%#x).\n",
param.headeroffset);
}
}
if (!param.cbfsoffset_assigned) {
if (param.arch == CBFS_ARCHITECTURE_X86) {
param.cbfsoffset = 0;
DEBUG("x86 -> CBFS entries start at address 0x0.\n");
} else {
param.cbfsoffset = align_up(param.headeroffset +
sizeof(struct cbfs_header),
CBFS_ALIGNMENT);
DEBUG("CBFS entries start beind master header (%#x).\n",
param.cbfsoffset);
}
}
int ret = cbfs_legacy_image_create(&image,
param.arch,
CBFS_ALIGNMENT,
&bootblock,
param.baseaddress,
param.headeroffset,
param.cbfsoffset);
buffer_delete(&bootblock);
return ret;
}
static int cbfs_layout(void)
{
const struct fmap *fmap = partitioned_file_get_fmap(param.image_file);
if (!fmap) {
LOG("This is a legacy image composed entirely of a single CBFS.\n");
return 1;
}
printf("This image contains the following sections that can be %s with this tool:\n",
param.show_immutable ? "accessed" : "manipulated");
puts("");
for (unsigned i = 0; i < fmap->nareas; ++i) {
const struct fmap_area *current = fmap->areas + i;
bool readonly = partitioned_file_fmap_count(param.image_file,
partitioned_file_fmap_select_children_of, current) ||
region_is_flashmap((const char *)current->name);
if (!param.show_immutable && readonly)
continue;
printf("'%s'", current->name);
// Detect consecutive sections that describe the same region and
// show them as aliases. This cannot find equivalent entries
// that aren't adjacent; however, fmaptool doesn't generate
// FMAPs with such sections, so this convenience feature works
// for all but the strangest manually created FMAP binaries.
// TODO: This could be done by parsing the FMAP into some kind
// of tree that had duplicate lists in addition to child lists,
// which would allow covering that weird, unlikely case as well.
unsigned lookahead;
for (lookahead = 1; i + lookahead < fmap->nareas;
++lookahead) {
const struct fmap_area *consecutive =
fmap->areas + i + lookahead;
if (consecutive->offset != current->offset ||
consecutive->size != current->size)
break;
printf(", '%s'", consecutive->name);
}
if (lookahead > 1)
fputs(" are aliases for the same region", stdout);
const char *qualifier = "";
if (readonly)
qualifier = "read-only, ";
else if (region_is_modern_cbfs((const char *)current->name))
qualifier = "CBFS, ";
else if (current->flags & FMAP_AREA_PRESERVE)
qualifier = "preserve, ";
printf(" (%ssize %u, offset %u)\n", qualifier, current->size,
current->offset);
i += lookahead - 1;
}
puts("");
if (param.show_immutable) {
puts("It is at least possible to perform the read action on every section listed above.");
} else {
puts("It is possible to perform either the write action or the CBFS add/remove actions on every section listed above.");
puts("To see the image's read-only sections as well, rerun with the -w option.");
}
return 0;
}
static int cbfs_print(void)
{
struct cbfs_image image;
if (cbfs_image_from_buffer(&image, param.image_region,
param.headeroffset))
return 1;
if (param.machine_parseable)
return cbfs_print_parseable_directory(&image);
else {
printf("FMAP REGION: %s\n", param.region_name);
return cbfs_print_directory(&image);
}
}
static int cbfs_extract(void)
{
if (!param.filename) {
ERROR("You need to specify -f/--filename.\n");
return 1;
}
if (!param.name) {
ERROR("You need to specify -n/--name.\n");
return 1;
}
struct cbfs_image image;
if (cbfs_image_from_buffer(&image, param.image_region,
param.headeroffset))
return 1;
return cbfs_export_entry(&image, param.name, param.filename,
param.arch, !param.unprocessed);
}
static int cbfs_write(void)
{
if (!param.filename) {
ERROR("You need to specify a valid input -f/--file.\n");
return 1;
}
if (!partitioned_file_is_partitioned(param.image_file)) {
ERROR("This operation isn't valid on legacy images having CBFS master headers\n");
return 1;
}
if (!param.force && region_is_modern_cbfs(param.region_name)) {
ERROR("Target image region '%s' is a CBFS and must be manipulated using add and remove\n",
param.region_name);
return 1;
}
struct buffer new_content;
if (buffer_from_file(&new_content, param.filename))
return 1;
if (buffer_check_magic(&new_content, FMAP_SIGNATURE,
strlen(FMAP_SIGNATURE))) {
ERROR("File '%s' appears to be an FMAP and cannot be added to an existing image\n",
param.filename);
buffer_delete(&new_content);
return 1;
}
if (!param.force && buffer_check_magic(&new_content, CBFS_FILE_MAGIC,
strlen(CBFS_FILE_MAGIC))) {
ERROR("File '%s' appears to be a CBFS and cannot be inserted into a raw region\n",
param.filename);
buffer_delete(&new_content);
return 1;
}
unsigned offset = 0;
if (param.fill_partial_upward && param.fill_partial_downward) {
ERROR("You may only specify one of -u and -d.\n");
buffer_delete(&new_content);
return 1;
} else if (!param.fill_partial_upward && !param.fill_partial_downward) {
if (new_content.size != param.image_region->size) {
ERROR("File to add is %zu bytes and would not fill %zu-byte target region (did you mean to pass either -u or -d?)\n",
new_content.size, param.image_region->size);
buffer_delete(&new_content);
return 1;
}
} else {
if (new_content.size > param.image_region->size) {
ERROR("File to add is %zu bytes and would overflow %zu-byte target region\n",
new_content.size, param.image_region->size);
buffer_delete(&new_content);
return 1;
}
if (param.u64val == (uint64_t)-1) {
WARN("Written area will abut %s of target region: any unused space will keep its current contents\n",
param.fill_partial_upward ? "bottom" : "top");
} else if (param.u64val > 0xff) {
ERROR("given fill value (%x) is larger than a byte\n", (unsigned)(param.u64val & 0xff));
buffer_delete(&new_content);
return 1;
} else {
memset(buffer_get(param.image_region),
param.u64val & 0xff,
buffer_size(param.image_region));
}
if (param.fill_partial_downward)
offset = param.image_region->size - new_content.size;
}
memcpy(param.image_region->data + offset, new_content.data,
new_content.size);
buffer_delete(&new_content);
return 0;
}
static int cbfs_read(void)
{
if (!param.filename) {
ERROR("You need to specify a valid output -f/--file.\n");
return 1;
}
if (!partitioned_file_is_partitioned(param.image_file)) {
ERROR("This operation isn't valid on legacy images having CBFS master headers\n");
return 1;
}
return buffer_write_file(param.image_region, param.filename);
}
static int cbfs_copy(void)
{
struct cbfs_image src_image;
struct buffer src_buf;
if (!param.source_region) {
ERROR("You need to specify -R/--source-region.\n");
return 1;
}
/* Obtain the source region and convert it to a cbfs_image. */
if (!partitioned_file_read_region(&src_buf, param.image_file,
param.source_region)) {
ERROR("Region not found in image: %s\n", param.source_region);
return 1;
}
if (cbfs_image_from_buffer(&src_image, &src_buf, param.headeroffset))
return 1;
return cbfs_copy_instance(&src_image, param.image_region);
}
static int cbfs_compact(void)
{
struct cbfs_image image;
if (cbfs_image_from_buffer(&image, param.image_region,
param.headeroffset))
return 1;
WARN("Compacting a CBFS doesn't honor alignment or fixed addresses!\n");
return cbfs_compact_instance(&image);
}
static int cbfs_expand(void)
{
struct buffer src_buf;
/* Obtain the source region. */
if (!partitioned_file_read_region(&src_buf, param.image_file,
param.region_name)) {
ERROR("Region not found in image: %s\n", param.source_region);
return 1;
}
return cbfs_expand_to_region(param.image_region);
}
static int cbfs_truncate(void)
{
struct buffer src_buf;
/* Obtain the source region. */
if (!partitioned_file_read_region(&src_buf, param.image_file,
param.region_name)) {
ERROR("Region not found in image: %s\n", param.source_region);
return 1;
}
uint32_t size;
int result = cbfs_truncate_space(param.image_region, &size);
printf("0x%x\n", size);
return result;
}
static const struct command commands[] = {
{"add", "H:r:f:n:t:c:b:a:p:yvA:j:gh?", cbfs_add, true, true},
{"add-flat-binary", "H:r:f:n:l:e:c:b:p:vA:gh?", cbfs_add_flat_binary,
true, true},
{"add-payload", "H:r:f:n:c:b:C:I:p:vA:gh?", cbfs_add_payload,
true, true},
{"add-stage", "a:H:r:f:n:t:c:b:P:S:p:yvA:gh?", cbfs_add_stage,
true, true},
{"add-int", "H:r:i:n:b:vgh?", cbfs_add_integer, true, true},
{"add-master-header", "H:r:vh?j:", cbfs_add_master_header, true, true},
{"compact", "r:h?", cbfs_compact, true, true},
{"copy", "r:R:h?", cbfs_copy, true, true},
{"create", "M:r:s:B:b:H:o:m:vh?", cbfs_create, true, true},
{"extract", "H:r:m:n:f:Uvh?", cbfs_extract, true, false},
{"layout", "wvh?", cbfs_layout, false, false},
{"print", "H:r:vkh?", cbfs_print, true, false},
{"read", "r:f:vh?", cbfs_read, true, false},
{"remove", "H:r:n:vh?", cbfs_remove, true, true},
{"write", "r:f:i:Fudvh?", cbfs_write, true, true},
{"expand", "r:h?", cbfs_expand, true, true},
{"truncate", "r:h?", cbfs_truncate, true, true},
};
static struct option long_options[] = {
{"alignment", required_argument, 0, 'a' },
{"base-address", required_argument, 0, 'b' },
{"bootblock", required_argument, 0, 'B' },
{"cmdline", required_argument, 0, 'C' },
{"compression", required_argument, 0, 'c' },
{"topswap-size", required_argument, 0, 'j' },
{"empty-fits", required_argument, 0, 'x' },
{"entry-point", required_argument, 0, 'e' },
{"file", required_argument, 0, 'f' },
{"fill-downward", no_argument, 0, 'd' },
{"fill-upward", no_argument, 0, 'u' },
{"flashmap", required_argument, 0, 'M' },
{"fmap-regions", required_argument, 0, 'r' },
{"force", no_argument, 0, 'F' },
{"source-region", required_argument, 0, 'R' },
{"hash-algorithm",required_argument, 0, 'A' },
{"header-offset", required_argument, 0, 'H' },
{"help", no_argument, 0, 'h' },
{"ignore-sec", required_argument, 0, 'S' },
{"initrd", required_argument, 0, 'I' },
{"int", required_argument, 0, 'i' },
{"load-address", required_argument, 0, 'l' },
{"machine", required_argument, 0, 'm' },
{"name", required_argument, 0, 'n' },
{"offset", required_argument, 0, 'o' },
{"padding", required_argument, 0, 'p' },
{"page-size", required_argument, 0, 'P' },
{"ucode-region", required_argument, 0, 'q' },
{"size", required_argument, 0, 's' },
{"top-aligned", required_argument, 0, 'T' },
{"type", required_argument, 0, 't' },
{"verbose", no_argument, 0, 'v' },
{"with-readonly", no_argument, 0, 'w' },
{"xip", no_argument, 0, 'y' },
{"gen-attribute", no_argument, 0, 'g' },
{"mach-parseable",no_argument, 0, 'k' },
{"unprocessed", no_argument, 0, 'U' },
{NULL, 0, 0, 0 }
};
static int dispatch_command(struct command command)
{
if (command.accesses_region) {
assert(param.image_file);
if (partitioned_file_is_partitioned(param.image_file)) {
INFO("Performing operation on '%s' region...\n",
param.region_name);
}
if (!partitioned_file_read_region(param.image_region,
param.image_file, param.region_name)) {
ERROR("The image will be left unmodified.\n");
return 1;
}
if (command.modifies_region) {
// We (intentionally) don't support overwriting the FMAP
// section. If you find yourself wanting to do this,
// consider creating a new image rather than performing
// whatever hacky transformation you were planning.
if (region_is_flashmap(param.region_name)) {
ERROR("Image region '%s' is read-only because it contains the FMAP.\n",
param.region_name);
ERROR("The image will be left unmodified.\n");
return 1;
}
// We don't allow writing raw data to regions that
// contain nested regions, since doing so would
// overwrite all such subregions.
if (partitioned_file_region_contains_nested(
param.image_file, param.region_name)) {
ERROR("Image region '%s' is read-only because it contains nested regions.\n",
param.region_name);
ERROR("The image will be left unmodified.\n");
return 1;
}
}
}
if (command.function()) {
if (partitioned_file_is_partitioned(param.image_file)) {
ERROR("Failed while operating on '%s' region!\n",
param.region_name);
ERROR("The image will be left unmodified.\n");
}
return 1;
}
return 0;
}
static void usage(char *name)
{
printf
("cbfstool: Management utility for CBFS formatted ROM images\n\n"
"USAGE:\n" " %s [-h]\n"
" %s FILE COMMAND [-v] [PARAMETERS]...\n\n" "OPTIONs:\n"
" -H header_offset Do not search for header; use this offset*\n"
" -T Output top-aligned memory address\n"
" -u Accept short data; fill upward/from bottom\n"
" -d Accept short data; fill downward/from top\n"
" -F Force action\n"
" -g Generate position and alignment arguments\n"
" -U Unprocessed; don't decompress or make ELF\n"
" -v Provide verbose output\n"
" -h Display this help message\n\n"
"COMMANDs:\n"
" add [-r image,regions] -f FILE -n NAME -t TYPE [-A hash] \\\n"
" [-c compression] [-b base-address | -a alignment] \\\n"
" [-p padding size] [-y|--xip if TYPE is FSP] \\\n"
" [-j topswap-size] (Intel CPUs only) "
"Add a component\n"
" "
" -j valid size: 0x10000 0x20000 0x40000 0x80000 0x100000 \n"
" add-payload [-r image,regions] -f FILE -n NAME [-A hash] \\\n"
" [-c compression] [-b base-address] \\\n"
" (linux specific: [-C cmdline] [-I initrd]) "
"Add a payload to the ROM\n"
" add-stage [-r image,regions] -f FILE -n NAME [-A hash] \\\n"
" [-c compression] [-b base] [-S section-to-ignore] \\\n"
" [-a alignment] [-y|--xip] [-P page-size] "
"Add a stage to the ROM\n"
" add-flat-binary [-r image,regions] -f FILE -n NAME \\\n"
" [-A hash] -l load-address -e entry-point \\\n"
" [-c compression] [-b base] "
"Add a 32bit flat mode binary\n"
" add-int [-r image,regions] -i INTEGER -n NAME [-b base] "
"Add a raw 64-bit integer value\n"
" add-master-header [-r image,regions] \\ \n"
" [-j topswap-size] (Intel CPUs only) "
"Add a legacy CBFS master header\n"
" remove [-r image,regions] -n NAME "
"Remove a component\n"
" compact -r image,regions "
"Defragment CBFS image.\n"
" copy -r image,regions -R source-region "
"Create a copy (duplicate) cbfs instance in fmap\n"
" create -m ARCH -s size [-b bootblock offset] \\\n"
" [-o CBFS offset] [-H header offset] [-B bootblock] "
"Create a legacy ROM file with CBFS master header*\n"
" create -M flashmap [-r list,of,regions,containing,cbfses] "
"Create a new-style partitioned firmware image\n"
" locate [-r image,regions] -f FILE -n NAME [-P page-size] \\\n"
" [-a align] [-T] "
"Find a place for a file of that size\n"
" layout [-w] "
"List mutable (or, with -w, readable) image regions\n"
" print [-r image,regions] "
"Show the contents of the ROM\n"
" extract [-r image,regions] [-m ARCH] -n NAME -f FILE [-U] "
"Extracts a file from ROM\n"
" write [-F] -r image,regions -f file [-u | -d] [-i int] "
"Write file into same-size [or larger] raw region\n"
" read [-r fmap-region] -f file "
"Extract raw region contents into binary file\n"
" truncate [-r fmap-region] "
"Truncate CBFS and print new size on stdout\n"
" expand [-r fmap-region] "
"Expand CBFS to span entire region\n"
"OFFSETs:\n"
" Numbers accompanying -b, -H, and -o switches* may be provided\n"
" in two possible formats: if their value is greater than\n"
" 0x80000000, they are interpreted as a top-aligned x86 memory\n"
" address; otherwise, they are treated as an offset into flash.\n"
"ARCHes:\n", name, name
);
print_supported_architectures();
printf("TYPEs:\n");
print_supported_filetypes();
printf(
"\n* Note that these actions and switches are only valid when\n"
" working with legacy images whose structure is described\n"
" primarily by a CBFS master header. New-style images, in\n"
" contrast, exclusively make use of an FMAP to describe their\n"
" layout: this must minimally contain an '%s' section\n"
" specifying the location of this FMAP itself and a '%s'\n"
" section describing the primary CBFS. It should also be noted\n"
" that, when working with such images, the -F and -r switches\n"
" default to '%s' for convenience, and both the -b switch to\n"
" CBFS operations and the output of the locate action become\n"
" relative to the selected CBFS region's lowest address.\n"
" The one exception to this rule is the top-aligned address,\n"
" which is always relative to the end of the entire image\n"
" rather than relative to the local region; this is true for\n"
" for both input (sufficiently large) and output (-T) data.\n",
SECTION_NAME_FMAP, SECTION_NAME_PRIMARY_CBFS,
SECTION_NAME_PRIMARY_CBFS
);
}
int main(int argc, char **argv)
{
size_t i;
int c;
if (argc < 3) {
usage(argv[0]);
return 1;
}
char *image_name = argv[1];
char *cmd = argv[2];
optind += 2;
for (i = 0; i < ARRAY_SIZE(commands); i++) {
if (strcmp(cmd, commands[i].name) != 0)
continue;
while (1) {
char *suffix = NULL;
int option_index = 0;
c = getopt_long(argc, argv, commands[i].optstring,
long_options, &option_index);
if (c == -1) {
if (optind < argc) {
ERROR("%s: excessive argument -- '%s'"
"\n", argv[0], argv[optind]);
return 1;
}
break;
}
/* Filter out illegal long options */
if (strchr(commands[i].optstring, c) == NULL) {
/* TODO maybe print actual long option instead */
ERROR("%s: invalid option -- '%c'\n",
argv[0], c);
c = '?';
}
switch(c) {
case 'n':
param.name = optarg;
break;
case 't':
if (intfiletype(optarg) != ((uint64_t) - 1))
param.type = intfiletype(optarg);
else
param.type = strtoul(optarg, NULL, 0);
if (param.type == 0)
WARN("Unknown type '%s' ignored\n",
optarg);
break;
case 'c': {
if (strcmp(optarg, "precompression") == 0) {
param.precompression = 1;
break;
}
int algo = cbfs_parse_comp_algo(optarg);
if (algo >= 0)
param.compression = algo;
else
WARN("Unknown compression '%s' ignored.\n",
optarg);
break;
}
case 'A': {
int algo = cbfs_parse_hash_algo(optarg);
if (algo >= 0)
param.hash = algo;
else {
ERROR("Unknown hash algorithm '%s'.\n",
optarg);
return 1;
}
break;
}
case 'M':
param.fmap = optarg;
break;
case 'r':
param.region_name = optarg;
break;
case 'R':
param.source_region = optarg;
break;
case 'b':
param.baseaddress = strtoul(optarg, &suffix, 0);
if (!*optarg || (suffix && *suffix)) {
ERROR("Invalid base address '%s'.\n",
optarg);
return 1;
}
// baseaddress may be zero on non-x86, so we
// need an explicit "baseaddress_assigned".
param.baseaddress_assigned = 1;
break;
case 'l':
param.loadaddress = strtoul(optarg, &suffix, 0);
if (!*optarg || (suffix && *suffix)) {
ERROR("Invalid load address '%s'.\n",
optarg);
return 1;
}
break;
case 'e':
param.entrypoint = strtoul(optarg, &suffix, 0);
if (!*optarg || (suffix && *suffix)) {
ERROR("Invalid entry point '%s'.\n",
optarg);
return 1;
}
break;
case 's':
param.size = strtoul(optarg, &suffix, 0);
if (!*optarg) {
ERROR("Empty size specified.\n");
return 1;
}
switch (tolower((int)suffix[0])) {
case 'k':
param.size *= 1024;
break;
case 'm':
param.size *= 1024 * 1024;
break;
case '\0':
break;
default:
ERROR("Invalid suffix for size '%s'.\n",
optarg);
return 1;
}
break;
case 'B':
param.bootblock = optarg;
break;
case 'H':
param.headeroffset = strtoul(
optarg, &suffix, 0);
if (!*optarg || (suffix && *suffix)) {
ERROR("Invalid header offset '%s'.\n",
optarg);
return 1;
}
param.headeroffset_assigned = 1;
break;
case 'a':
param.alignment = strtoul(optarg, &suffix, 0);
if (!*optarg || (suffix && *suffix)) {
ERROR("Invalid alignment '%s'.\n",
optarg);
return 1;
}
break;
case 'p':
param.padding = strtoul(optarg, &suffix, 0);
if (!*optarg || (suffix && *suffix)) {
ERROR("Invalid pad size '%s'.\n",
optarg);
return 1;
}
break;
case 'P':
param.pagesize = strtoul(optarg, &suffix, 0);
if (!*optarg || (suffix && *suffix)) {
ERROR("Invalid page size '%s'.\n",
optarg);
return 1;
}
break;
case 'o':
param.cbfsoffset = strtoul(optarg, &suffix, 0);
if (!*optarg || (suffix && *suffix)) {
ERROR("Invalid cbfs offset '%s'.\n",
optarg);
return 1;
}
param.cbfsoffset_assigned = 1;
break;
case 'f':
param.filename = optarg;
break;
case 'F':
param.force = 1;
break;
case 'i':
param.u64val = strtoull(optarg, &suffix, 0);
param.u64val_assigned = 1;
if (!*optarg || (suffix && *suffix)) {
ERROR("Invalid int parameter '%s'.\n",
optarg);
return 1;
}
break;
case 'u':
param.fill_partial_upward = true;
break;
case 'd':
param.fill_partial_downward = true;
break;
case 'w':
param.show_immutable = true;
break;
case 'j':
param.topswap_size = strtol(optarg, NULL, 0);
if (!is_valid_topswap())
return 1;
break;
case 'q':
param.ucode_region = optarg;
break;
case 'v':
verbose++;
break;
case 'm':
param.arch = string_to_arch(optarg);
break;
case 'I':
param.initrd = optarg;
break;
case 'C':
param.cmdline = optarg;
break;
case 'S':
param.ignore_section = optarg;
break;
case 'y':
param.stage_xip = true;
break;
case 'g':
param.autogen_attr = true;
break;
case 'k':
param.machine_parseable = true;
break;
case 'U':
param.unprocessed = true;
break;
case 'h':
case '?':
usage(argv[0]);
return 1;
default:
break;
}
}
if (commands[i].function == cbfs_create) {
if (param.fmap) {
struct buffer flashmap;
if (buffer_from_file(&flashmap, param.fmap))
return 1;
param.image_file = partitioned_file_create(
image_name, &flashmap);
buffer_delete(&flashmap);
} else if (param.size) {
param.image_file = partitioned_file_create_flat(
image_name, param.size);
} else {
ERROR("You need to specify a valid -M/--flashmap or -s/--size.\n");
return 1;
}
} else {
bool write_access = commands[i].modifies_region;
param.image_file =
partitioned_file_reopen(image_name,
write_access);
}
if (!param.image_file)
return 1;
unsigned num_regions = 1;
for (const char *list = strchr(param.region_name, ','); list;
list = strchr(list + 1, ','))
++num_regions;
// If the action needs to read an image region, as indicated by
// having accesses_region set in its command struct, that
// region's buffer struct will be stored here and the client
// will receive a pointer to it via param.image_region. It
// need not write the buffer back to the image file itself,
// since this behavior can be requested via its modifies_region
// field. Additionally, it should never free the region buffer,
// as that is performed automatically once it completes.
struct buffer image_regions[num_regions];
memset(image_regions, 0, sizeof(image_regions));
bool seen_primary_cbfs = false;
char region_name_scratch[strlen(param.region_name) + 1];
strcpy(region_name_scratch, param.region_name);
param.region_name = strtok(region_name_scratch, ",");
for (unsigned region = 0; region < num_regions; ++region) {
if (!param.region_name) {
ERROR("Encountered illegal degenerate region name in -r list\n");
ERROR("The image will be left unmodified.\n");
partitioned_file_close(param.image_file);
return 1;
}
if (strcmp(param.region_name, SECTION_NAME_PRIMARY_CBFS)
== 0)
seen_primary_cbfs = true;
param.image_region = image_regions + region;
if (dispatch_command(commands[i])) {
partitioned_file_close(param.image_file);
return 1;
}
param.region_name = strtok(NULL, ",");
}
if (commands[i].function == cbfs_create && !seen_primary_cbfs) {
ERROR("The creation -r list must include the mandatory '%s' section.\n",
SECTION_NAME_PRIMARY_CBFS);
ERROR("The image will be left unmodified.\n");
partitioned_file_close(param.image_file);
return 1;
}
if (commands[i].modifies_region) {
assert(param.image_file);
for (unsigned region = 0; region < num_regions;
++region) {
if (!partitioned_file_write_region(
param.image_file,
image_regions + region)) {
partitioned_file_close(
param.image_file);
return 1;
}
}
}
partitioned_file_close(param.image_file);
return 0;
}
ERROR("Unknown command '%s'.\n", cmd);
usage(argv[0]);
return 1;
}