mirror of
https://source.denx.de/u-boot/u-boot.git
synced 2026-04-21 05:51:03 +02:00
Add a new fwumdata tool to allows users to read, display, and modify FWU (Firmware Update) metadata from Linux userspace. It provides functionality similar to fw_printenv/fw_setenv but for FWU metadata. Users can view metadata, change active/previous bank indices, modify bank states, and set image acceptance flags. Configuration is done via fwumdata.config file. Signed-off-by: Kory Maincent <kory.maincent@bootlin.com> Tested-by: Dario Binacchi <dario.binacchi@amarulasolutions.com> Signed-off-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>
855 lines
20 KiB
C
855 lines
20 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* FWU Metadata Read/Write Tool
|
|
* Copyright (c) 2025, Kory Maincent <kory.maincent@bootlin.com>
|
|
*
|
|
* Tool to read, display, and modify FWU (Firmware Update) metadata
|
|
* from Linux userspace. Similar to fw_printenv/fw_setenv for U-Boot
|
|
* environment, but for FWU metadata.
|
|
*
|
|
* Usage:
|
|
* fwumdata - Print all metadata
|
|
* fwumdata -u - Print metadata and update it if CRC corrupted
|
|
* fwumdata -c <config> - Use custom config file
|
|
* fwumdata -a <bank> - Set active bank
|
|
* fwumdata -p <bank> - Set previous bank
|
|
* fwumdata -s <bank> <state> - Set bank state (V2 only)
|
|
* fwumdata -i <id> -b <bank> -A - Accept image
|
|
* fwumdata -i <id> -b <bank> -C - Clear image acceptance
|
|
* fwumdata -i <id> -b <bank>
|
|
* -B <num_banks>
|
|
* -I <num_images> -C - Clear image acceptance (V1 only)
|
|
* fwumdata -l - List detailed info with GUIDs
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <getopt.h>
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <mtd/mtd-user.h>
|
|
#include <sys/ioctl.h>
|
|
#include <u-boot/crc.h>
|
|
#include "fwumdata.h"
|
|
|
|
/* Device configuration */
|
|
struct fwumdata_device {
|
|
const char *devname;
|
|
long long devoff;
|
|
unsigned long mdata_size;
|
|
unsigned long erase_size;
|
|
int fd;
|
|
bool is_mtd;
|
|
};
|
|
|
|
/* Global state */
|
|
static struct fwumdata_device devices[2]; /* Primary and secondary */
|
|
static struct fwu_mdata *mdata;
|
|
static int have_redundant;
|
|
static struct fwu_mdata *valid_mdata;
|
|
static bool mdata_mod;
|
|
static const char *config_file;
|
|
static int nbanks, nimages; /* For V1 only */
|
|
static const char * const default_config_files[] = {
|
|
"./fwumdata.config",
|
|
"/etc/fwumdata.config",
|
|
NULL
|
|
};
|
|
|
|
/* GUID/UUID utilities */
|
|
static void guid_to_string(const struct efi_guid *guid, char *str)
|
|
{
|
|
sprintf(str, "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
|
|
guid->time_high, guid->time_low, guid->reserved,
|
|
guid->family, guid->node[0],
|
|
guid->node[1], guid->node[2], guid->node[3],
|
|
guid->node[4], guid->node[5], guid->node[6]);
|
|
}
|
|
|
|
/* Config file parsing */
|
|
static int parse_config(const char *fname)
|
|
{
|
|
size_t linesize = 0;
|
|
char *line = NULL;
|
|
char *devname;
|
|
int i = 0;
|
|
FILE *fp;
|
|
int rc;
|
|
|
|
fp = fopen(fname, "r");
|
|
if (!fp)
|
|
return -ENOENT;
|
|
|
|
while (i < 2 && getline(&line, &linesize, fp) != -1) {
|
|
/* Skip comments and empty lines */
|
|
if (line[0] == '#' || line[0] == '\n')
|
|
continue;
|
|
|
|
rc = sscanf(line, "%ms %lli %lx %lx",
|
|
&devname,
|
|
&devices[i].devoff,
|
|
&devices[i].mdata_size,
|
|
&devices[i].erase_size);
|
|
|
|
if (rc < 3) {
|
|
free(devname);
|
|
continue;
|
|
}
|
|
|
|
if (rc < 4)
|
|
devices[i].erase_size = devices[i].mdata_size;
|
|
|
|
devices[i].devname = devname;
|
|
i++;
|
|
}
|
|
|
|
free(line);
|
|
fclose(fp);
|
|
|
|
if (i == 2) {
|
|
have_redundant = true;
|
|
if (devices[0].mdata_size != devices[1].mdata_size) {
|
|
fprintf(stderr,
|
|
"Size mismatch between the two metadata\n");
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
if (!i) {
|
|
fprintf(stderr,
|
|
"Can't read config %s content\n", fname);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int find_parse_config(void)
|
|
{
|
|
int i;
|
|
|
|
if (config_file)
|
|
return parse_config(config_file);
|
|
|
|
for (i = 0; default_config_files[i]; i++) {
|
|
int ret;
|
|
|
|
ret = parse_config(default_config_files[i]);
|
|
if (ret == -ENOENT)
|
|
continue;
|
|
if (ret)
|
|
return ret;
|
|
|
|
config_file = default_config_files[i];
|
|
return 0;
|
|
}
|
|
|
|
fprintf(stderr, "Error: Cannot find config file\n");
|
|
return -ENOENT;
|
|
}
|
|
|
|
static int open_device(struct fwumdata_device *dev)
|
|
{
|
|
if (strstr(dev->devname, "/dev/mtd"))
|
|
dev->is_mtd = true;
|
|
|
|
dev->fd = open(dev->devname, O_RDWR | O_SYNC);
|
|
if (dev->fd < 0) {
|
|
fprintf(stderr, "Cannot open %s: %s\n", dev->devname,
|
|
strerror(errno));
|
|
return -ENODEV;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mtd_erase(int fd, unsigned long offset, unsigned long size)
|
|
{
|
|
struct erase_info_user erase;
|
|
int ret;
|
|
|
|
erase.start = offset;
|
|
erase.length = size;
|
|
|
|
ret = ioctl(fd, MEMERASE, &erase);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "MTD erase failed: %s\n", strerror(errno));
|
|
return -errno;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int read_device(struct fwumdata_device *dev, void *buf, size_t count)
|
|
{
|
|
if (lseek(dev->fd, dev->devoff, SEEK_SET) < 0) {
|
|
fprintf(stderr, "Seek failed: %s\n", strerror(errno));
|
|
return -errno;
|
|
}
|
|
|
|
if (read(dev->fd, buf, count) < 0) {
|
|
fprintf(stderr, "Read failed: %s\n", strerror(errno));
|
|
return -errno;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int write_device(struct fwumdata_device *dev, const void *buf,
|
|
size_t count)
|
|
{
|
|
int ret;
|
|
|
|
/* Erase if MTD device */
|
|
if (dev->is_mtd) {
|
|
ret = mtd_erase(dev->fd, dev->devoff, dev->erase_size);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
if (lseek(dev->fd, dev->devoff, SEEK_SET) < 0) {
|
|
fprintf(stderr, "Seek failed: %s\n", strerror(errno));
|
|
return -errno;
|
|
}
|
|
|
|
if (write(dev->fd, buf, count) < 0) {
|
|
fprintf(stderr, "Write failed: %s\n", strerror(errno));
|
|
return -errno;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Metadata operations */
|
|
static int validate_crc(struct fwu_mdata *mdata, size_t size)
|
|
{
|
|
u32 calc_crc, stored_crc;
|
|
|
|
stored_crc = mdata->crc32;
|
|
calc_crc = crc32(0, (const u8 *)&mdata->version, size - sizeof(u32));
|
|
|
|
if (calc_crc != stored_crc) {
|
|
fprintf(stderr,
|
|
"CRC mismatch: calculated 0x%08x, stored 0x%08x\n",
|
|
calc_crc, stored_crc);
|
|
if (mdata->version == 1)
|
|
fprintf(stderr,
|
|
"Metadata is V1, this may be size description issue\n");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void update_crc(struct fwu_mdata *mdata, size_t size)
|
|
{
|
|
mdata->crc32 = crc32(0, (const u8 *)&mdata->version, size - sizeof(u32));
|
|
}
|
|
|
|
static int read_one_metadata(int mdata_id, size_t size)
|
|
{
|
|
int ret;
|
|
|
|
ret = open_device(&devices[mdata_id]);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = read_device(&devices[mdata_id], &mdata[mdata_id], size);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (mdata[mdata_id].version != 1 && mdata[mdata_id].version != 2) {
|
|
fprintf(stderr, "Invalid metadata %d version: %u\n",
|
|
mdata_id, mdata[mdata_id].version);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int read_metadata(bool update)
|
|
{
|
|
size_t alloc_size;
|
|
int ret;
|
|
|
|
/* Allocate initial buffer */
|
|
alloc_size = devices[0].mdata_size;
|
|
mdata = calloc(have_redundant ? 2 : 1, alloc_size);
|
|
if (!mdata) {
|
|
fprintf(stderr, "Memory allocation failed\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
ret = read_one_metadata(0, alloc_size);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (validate_crc(&mdata[0], alloc_size) < 0) {
|
|
fprintf(stderr,
|
|
"Warning: Primary metadata CRC validation failed\n");
|
|
mdata_mod = update;
|
|
} else {
|
|
valid_mdata = &mdata[0];
|
|
}
|
|
|
|
if (have_redundant) {
|
|
ret = read_one_metadata(1, alloc_size);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (validate_crc(&mdata[1], alloc_size) < 0) {
|
|
fprintf(stderr,
|
|
"Warning: Secondary metadata CRC validation failed\n");
|
|
mdata_mod = update;
|
|
} else if (valid_mdata && mdata[0].crc32 != mdata[1].crc32) {
|
|
fprintf(stderr,
|
|
"Metadatas valid but not equal, use first one as default\n");
|
|
mdata_mod = update;
|
|
} else {
|
|
valid_mdata = &mdata[1];
|
|
}
|
|
}
|
|
|
|
if (!valid_mdata) {
|
|
fprintf(stderr,
|
|
"No metadata valid, use first one as default\n");
|
|
mdata_mod = update;
|
|
valid_mdata = &mdata[0];
|
|
}
|
|
|
|
if (valid_mdata->version == 2) {
|
|
struct fwu_mdata_ext *mdata_ext;
|
|
|
|
mdata_ext = fwu_get_fw_mdata_ext(valid_mdata);
|
|
if (mdata_ext->metadata_size != alloc_size) {
|
|
fprintf(stderr,
|
|
"Metadata real size 0x%x mismatch with the config 0x%zx\n",
|
|
mdata_ext->metadata_size, alloc_size);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int write_metadata(void)
|
|
{
|
|
size_t write_size = devices[0].mdata_size;
|
|
int ret;
|
|
|
|
if (!mdata_mod)
|
|
return 0;
|
|
|
|
/* Update CRC */
|
|
update_crc(valid_mdata, write_size);
|
|
|
|
/* Write primary */
|
|
ret = write_device(&devices[0], valid_mdata, write_size);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Failed to write primary metadata\n");
|
|
return ret;
|
|
}
|
|
|
|
/* Write secondary if redundant */
|
|
if (have_redundant) {
|
|
ret = write_device(&devices[1], valid_mdata, write_size);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Failed to write secondary metadata\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
printf("FWU metadata updated successfully\n");
|
|
mdata_mod = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Display functions */
|
|
static const char *bank_state_to_string(u8 state)
|
|
{
|
|
switch (state) {
|
|
case FWU_BANK_ACCEPTED:
|
|
return "accepted";
|
|
case FWU_BANK_VALID:
|
|
return "valid";
|
|
case FWU_BANK_INVALID:
|
|
return "invalid";
|
|
default:
|
|
return "unknown";
|
|
}
|
|
}
|
|
|
|
static void print_metadata_summary(void)
|
|
{
|
|
int i;
|
|
|
|
printf("FWU Metadata:\n");
|
|
printf("\tVersion: %u\n", valid_mdata->version);
|
|
printf("\tActive Index: %u\n", valid_mdata->active_index);
|
|
printf("\tPrevious Index: %u\n", valid_mdata->previous_active_index);
|
|
printf("\tCRC32: 0x%08x\n", valid_mdata->crc32);
|
|
|
|
if (valid_mdata->version == 2) {
|
|
struct fwu_fw_store_desc *fw_desc;
|
|
struct fwu_mdata_ext *mdata_ext;
|
|
|
|
mdata_ext = fwu_get_fw_mdata_ext(valid_mdata);
|
|
printf("\tMetadata Size: %u bytes\n", mdata_ext->metadata_size);
|
|
printf("\tDescriptor Offset: %u\n", mdata_ext->desc_offset);
|
|
printf("\tBank States:\n");
|
|
|
|
fw_desc = fwu_get_fw_desc(valid_mdata);
|
|
for (i = 0; i < fw_desc->num_banks && i < MAX_BANKS_V2; i++) {
|
|
printf("\t\tBank %d: %s (0x%02x)\n", i,
|
|
bank_state_to_string(mdata_ext->bank_state[i]),
|
|
mdata_ext->bank_state[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void print_metadata_detailed(void)
|
|
{
|
|
struct fwu_fw_store_desc *fw_desc = NULL;
|
|
struct fwu_image_bank_info *bank_info;
|
|
struct fwu_image_entry *img_entry;
|
|
int num_images, num_banks;
|
|
char guid_str[64];
|
|
int i, j;
|
|
|
|
print_metadata_summary();
|
|
|
|
if (valid_mdata->version == 1) {
|
|
num_images = nimages;
|
|
num_banks = nbanks;
|
|
} else {
|
|
fw_desc = fwu_get_fw_desc(valid_mdata);
|
|
num_images = fw_desc->num_images;
|
|
num_banks = fw_desc->num_banks;
|
|
}
|
|
|
|
if (fw_desc) {
|
|
printf("\n\tFirmware Store Descriptor:\n");
|
|
printf("\t\tNumber of Banks: %u\n", num_banks);
|
|
printf("\t\tNumber of Images: %u\n", num_images);
|
|
printf("\t\tImage Entry Size: %u\n", fw_desc->img_entry_size);
|
|
printf("\t\tBank Info Entry Size: %u\n", fw_desc->bank_info_entry_size);
|
|
}
|
|
|
|
printf("\n\tImages:\n");
|
|
for (i = 0; i < num_images; i++) {
|
|
img_entry = fwu_get_image_entry(valid_mdata, valid_mdata->version,
|
|
num_banks, i);
|
|
|
|
printf("\t\tImage %d:\n", i);
|
|
|
|
guid_to_string(&img_entry->image_type_guid, guid_str);
|
|
printf("\t\t\tImage Type GUID: %s\n", guid_str);
|
|
|
|
guid_to_string(&img_entry->location_guid, guid_str);
|
|
printf("\t\t\tLocation GUID: %s\n", guid_str);
|
|
|
|
printf("\t\t\tBanks:\n");
|
|
for (j = 0; j < num_banks; j++) {
|
|
bank_info = fwu_get_bank_info(valid_mdata,
|
|
valid_mdata->version,
|
|
num_banks, i, j);
|
|
|
|
guid_to_string(&bank_info->image_guid, guid_str);
|
|
printf("\t\t\t\tBank %d:\n", j);
|
|
printf("\t\t\t\t\tImage GUID: %s\n", guid_str);
|
|
printf("\t\t\t\t\tAccepted: %s (%u)\n",
|
|
(bank_info->accepted & FWU_IMAGE_ACCEPTED) ? "yes" : "no",
|
|
bank_info->accepted);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Modification functions */
|
|
static int set_active_index(int bank)
|
|
{
|
|
struct fwu_fw_store_desc *fw_desc;
|
|
int num_banks;
|
|
|
|
if (valid_mdata->version == 2) {
|
|
fw_desc = fwu_get_fw_desc(valid_mdata);
|
|
num_banks = fw_desc->num_banks;
|
|
} else {
|
|
num_banks = nbanks;
|
|
}
|
|
|
|
if (bank < 0 || bank >= num_banks) {
|
|
fprintf(stderr, "Error: Invalid bank %d (must be 0-%d)\n",
|
|
bank, num_banks - 1);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (valid_mdata->active_index == bank)
|
|
return 0;
|
|
|
|
valid_mdata->active_index = bank;
|
|
mdata_mod = 1;
|
|
|
|
printf("Active bank set to %d\n", bank);
|
|
return 0;
|
|
}
|
|
|
|
static int set_previous_index(int bank)
|
|
{
|
|
struct fwu_fw_store_desc *fw_desc;
|
|
int num_banks;
|
|
|
|
if (valid_mdata->version == 2) {
|
|
fw_desc = fwu_get_fw_desc(valid_mdata);
|
|
num_banks = fw_desc->num_banks;
|
|
} else {
|
|
num_banks = nbanks;
|
|
}
|
|
|
|
if (bank < 0 || bank >= num_banks) {
|
|
fprintf(stderr, "Error: Invalid bank %d (must be 0-%d)\n",
|
|
bank, num_banks - 1);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (valid_mdata->previous_active_index == bank)
|
|
return 0;
|
|
|
|
valid_mdata->previous_active_index = bank;
|
|
mdata_mod = 1;
|
|
|
|
printf("Previous bank set to %d\n", bank);
|
|
return 0;
|
|
}
|
|
|
|
static int set_image_accepted(int image, int bank, int accept)
|
|
{
|
|
struct fwu_image_bank_info *bank_info;
|
|
int num_images, num_banks;
|
|
|
|
if (valid_mdata->version == 1) {
|
|
num_images = nimages;
|
|
num_banks = nbanks;
|
|
} else {
|
|
struct fwu_fw_store_desc *fw_desc;
|
|
|
|
fw_desc = fwu_get_fw_desc(valid_mdata);
|
|
num_images = fw_desc->num_images;
|
|
num_banks = fw_desc->num_banks;
|
|
}
|
|
|
|
if (bank < 0 || bank >= num_banks) {
|
|
fprintf(stderr, "Error: Invalid bank %d (must be 0-%d)\n",
|
|
bank, num_banks - 1);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (image < 0 || image >= num_images) {
|
|
fprintf(stderr, "Error: Invalid image %d (must be 0-%d)\n",
|
|
image, num_images - 1);
|
|
return -EINVAL;
|
|
}
|
|
|
|
bank_info = fwu_get_bank_info(valid_mdata, valid_mdata->version,
|
|
num_banks, image, bank);
|
|
if (accept == bank_info->accepted)
|
|
return 0;
|
|
|
|
if (accept) {
|
|
bank_info->accepted = FWU_IMAGE_ACCEPTED;
|
|
} else {
|
|
bank_info->accepted = 0;
|
|
|
|
/* According to the spec: bank_state[index] have to be set
|
|
* to invalid before any content in the img_bank_info[index]
|
|
* is overwritten.
|
|
*/
|
|
if (valid_mdata->version == 2) {
|
|
struct fwu_mdata_ext *mdata_ext;
|
|
|
|
mdata_ext = fwu_get_fw_mdata_ext(valid_mdata);
|
|
mdata_ext->bank_state[bank] = FWU_BANK_INVALID;
|
|
}
|
|
}
|
|
|
|
mdata_mod = 1;
|
|
printf("Image %d in bank %d: acceptance %s\n",
|
|
image, bank, accept ? "set" : "cleared");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int set_bank_state(int bank, const char *state_str)
|
|
{
|
|
struct fwu_fw_store_desc *fw_desc;
|
|
struct fwu_mdata_ext *mdata_ext;
|
|
u8 state;
|
|
int i;
|
|
|
|
if (valid_mdata->version != 2) {
|
|
fprintf(stderr,
|
|
"Error: Bank state is only supported in V2 metadata\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
fw_desc = fwu_get_fw_desc(valid_mdata);
|
|
mdata_ext = fwu_get_fw_mdata_ext(valid_mdata);
|
|
|
|
if (bank < 0 || bank >= fw_desc->num_banks || bank >= MAX_BANKS_V2) {
|
|
fprintf(stderr, "Error: Invalid bank %d (must be 0-%d)\n",
|
|
bank, fw_desc->num_banks - 1);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Parse state string */
|
|
if (!strcmp(state_str, "accepted")) {
|
|
state = FWU_BANK_ACCEPTED;
|
|
} else if (!strcmp(state_str, "valid")) {
|
|
state = FWU_BANK_VALID;
|
|
} else if (!strcmp(state_str, "invalid")) {
|
|
state = FWU_BANK_INVALID;
|
|
} else {
|
|
fprintf(stderr,
|
|
"Error: Invalid state '%s' (must be accepted/valid/invalid)\n",
|
|
state_str);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (mdata_ext->bank_state[bank] == state)
|
|
return 0;
|
|
|
|
/* If a bank is set in a accepted state all firmware images in
|
|
* that bank must be marked as accepted as described in the spec.
|
|
*/
|
|
if (state == FWU_BANK_ACCEPTED) {
|
|
for (i = 0; i < fw_desc->num_images; i++) {
|
|
int ret;
|
|
|
|
ret = set_image_accepted(i, bank, true);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
}
|
|
mdata_ext->bank_state[bank] = state;
|
|
mdata_mod = 1;
|
|
|
|
printf("Bank %d state set to %s (0x%02x)\n", bank, state_str, state);
|
|
return 0;
|
|
}
|
|
|
|
static int metadata_v1_validate_size(void)
|
|
{
|
|
int calc_size;
|
|
|
|
calc_size = sizeof(struct fwu_mdata) +
|
|
(sizeof(struct fwu_image_entry) +
|
|
sizeof(struct fwu_image_bank_info) * nbanks) * nimages;
|
|
|
|
if (devices[0].mdata_size != calc_size) {
|
|
fprintf(stderr,
|
|
"Metadata calculate size (-B and -I options) 0x%x mismatch with the config 0x%zx\n",
|
|
calc_size, devices[0].mdata_size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Command-line interface */
|
|
static void print_usage(void)
|
|
{
|
|
fprintf(stderr, "Usage: fwumdata [options]\n\n");
|
|
fprintf(stderr, "Options:\n"
|
|
"\t-c, --config <file> Use custom config file, defaults:\n"
|
|
"\t ./fwumdata.config or /etc/fwumdata.config\n"
|
|
"\t-l, --list List detailed metadata with GUIDs\n"
|
|
"\t-a, --active <bank> Set active bank index\n"
|
|
"\t-p, --previous <bank> Set previous bank index\n"
|
|
"\t-s, --state <bank> <state> Set bank state (V2 only)\n"
|
|
"\t state: accepted|valid|invalid\n"
|
|
"\t-i, --image <id> Image number (for -A/-C)\n"
|
|
"\t-b, --bank <bank> Bank number (for -A/-C)\n"
|
|
"\t-A, --accept Accept image (requires -i and -b)\n"
|
|
"\t-C, --clear Clear image acceptance (requires -i and -b)\n"
|
|
"\t-u, --update Update metadata if there is a checksum issue\n"
|
|
"\t-B, --nbanks <num_banks> Number of banks (required for V1 metadata)\n"
|
|
"\t-I, --nimages <num_images> Number of images (required for V1 metadata)\n"
|
|
"\t-h, --help Print this help\n\n");
|
|
fprintf(stderr, "Config file format (fwumdata.config):\n"
|
|
"\t# Device Name Device Offset Metadata Size Erase Size\n"
|
|
"\t/dev/mtd0 0x0 0x78 0x1000\n"
|
|
"\t/dev/mtd1 0x0 0x78 0x1000\n\n");
|
|
fprintf(stderr, "Examples:\n"
|
|
"\tfwumdata # Print metadata summary\n"
|
|
"\tfwumdata -l # Print detailed metadata\n"
|
|
"\tfwumdata -a 1 # Set active bank to 1\n"
|
|
"\tfwumdata -s 1 accepted # Set bank 1 to accepted state\n"
|
|
"\tfwumdata -i 0 -b 0 -A # Accept image in bank 0\n"
|
|
"\tfwumdata -B 2 -I 2 -i 1 -b 1 -A -l # Accept image 1 in bank 1 with metadata V1\n");
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
char *bank_state_str = NULL;
|
|
bool list_detailed = false;
|
|
int bank_state_num = -1;
|
|
int active_index = -1;
|
|
int bank_id = -1;
|
|
int prev_index = -1;
|
|
bool do_accept = 0;
|
|
bool do_clear = 0;
|
|
bool do_update = 0;
|
|
int image_id = -1;
|
|
int ret = 0;
|
|
int opt;
|
|
|
|
static struct option long_options[] = {
|
|
{"config", required_argument, 0, 'c'},
|
|
{"list", no_argument, 0, 'l'},
|
|
{"active", required_argument, 0, 'a'},
|
|
{"previous", required_argument, 0, 'p'},
|
|
{"state", required_argument, 0, 's'},
|
|
{"image", required_argument, 0, 'i'},
|
|
{"bank", required_argument, 0, 'b'},
|
|
{"accept", no_argument, 0, 'A'},
|
|
{"clear", no_argument, 0, 'C'},
|
|
{"update", no_argument, 0, 'u'},
|
|
{"nbanks", required_argument, 0, 'B'},
|
|
{"nimages", required_argument, 0, 'I'},
|
|
{"help", no_argument, 0, 'h'},
|
|
{0, 0, 0, 0}
|
|
};
|
|
|
|
/* Parse arguments */
|
|
while ((opt = getopt_long(argc, argv, "c:la:p:s:i:b:ACuB:I:h", long_options, NULL)) != -1) {
|
|
switch (opt) {
|
|
case 'c':
|
|
config_file = optarg;
|
|
break;
|
|
case 'l':
|
|
list_detailed = 1;
|
|
break;
|
|
case 'a':
|
|
active_index = atoi(optarg);
|
|
break;
|
|
case 'p':
|
|
prev_index = atoi(optarg);
|
|
break;
|
|
case 's':
|
|
bank_state_num = atoi(optarg);
|
|
if (optind < argc && argv[optind][0] != '-') {
|
|
bank_state_str = argv[optind++];
|
|
} else {
|
|
fprintf(stderr,
|
|
"Error: -s requires bank number and state\n");
|
|
return 1;
|
|
}
|
|
break;
|
|
case 'i':
|
|
image_id = atoi(optarg);
|
|
break;
|
|
case 'b':
|
|
bank_id = atoi(optarg);
|
|
break;
|
|
case 'A':
|
|
do_accept = 1;
|
|
break;
|
|
case 'C':
|
|
do_clear = 1;
|
|
break;
|
|
case 'u':
|
|
do_update = 1;
|
|
break;
|
|
case 'B':
|
|
nbanks = atoi(optarg);
|
|
break;
|
|
case 'I':
|
|
nimages = atoi(optarg);
|
|
break;
|
|
case 'h':
|
|
print_usage();
|
|
return 0;
|
|
default:
|
|
print_usage();
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
ret = find_parse_config();
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Error: Cannot read configuration\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = read_metadata(do_update);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "Error: Cannot read metadata\n");
|
|
goto cleanup;
|
|
}
|
|
|
|
if (valid_mdata->version == 1) {
|
|
ret = metadata_v1_validate_size();
|
|
if (ret)
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Perform operations */
|
|
if (active_index >= 0) {
|
|
ret = set_active_index(active_index);
|
|
if (ret < 0)
|
|
goto cleanup;
|
|
}
|
|
|
|
if (prev_index >= 0) {
|
|
ret = set_previous_index(prev_index);
|
|
if (ret < 0)
|
|
goto cleanup;
|
|
}
|
|
|
|
if (do_accept || do_clear) {
|
|
if (image_id < 0 || bank_id < 0) {
|
|
fprintf(stderr,
|
|
"Error: -A/-C requires both -i <guid> and -b <bank>\n");
|
|
ret = -EINVAL;
|
|
goto cleanup;
|
|
}
|
|
|
|
ret = set_image_accepted(image_id, bank_id, do_accept);
|
|
if (ret < 0)
|
|
goto cleanup;
|
|
}
|
|
|
|
if (bank_state_num >= 0 && bank_state_str) {
|
|
ret = set_bank_state(bank_state_num, bank_state_str);
|
|
if (ret < 0)
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Write back if modified */
|
|
if (mdata_mod) {
|
|
ret = write_metadata();
|
|
if (ret)
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Display metadata if no modifications or list requested */
|
|
if (list_detailed)
|
|
print_metadata_detailed();
|
|
else
|
|
print_metadata_summary();
|
|
|
|
cleanup:
|
|
/* Close devices and free memory */
|
|
if (devices[0].fd)
|
|
close(devices[0].fd);
|
|
if (devices[1].fd)
|
|
close(devices[1].fd);
|
|
|
|
free(mdata);
|
|
|
|
for (int i = 0; i < 2; i++) {
|
|
if (devices[i].devname)
|
|
free((void *)devices[i].devname);
|
|
}
|
|
|
|
return ret;
|
|
}
|