core/host_fw/host_fw_util.c (644 lines of code) (raw):
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include "host_fw_util.h"
#include "platform_api.h"
#include "common/buffer_util.h"
#include "flash/flash_util.h"
/**
* Search a list of host firmware version identifiers to find the longest string.
*
* @param list The list of versions to search.
*
* @return The length of the longest identifier.
*/
static int host_fw_find_longest_version_id (const struct pfm_firmware_versions *list)
{
size_t i;
int max = 0;
int len;
if (list != NULL) {
for (i = 0; i < list->count; i++) {
len = strlen (list->versions[i].fw_version_id);
if (len > max) {
max = len;
}
}
}
return max;
}
/**
* Determine the version of the host firmware image stored on flash. In order for the version to be
* recognized, it must be present in the list of firmware versions from the PFM.
*
* @param flash The flash device that contains the host firmware.
* @param allowed The list of allowed versions to use when inspecting the flash.
* @param version A pointer that will be updated to reference the version information for the
* matched version. This will be a pointer to the entry within the versions list.
*
* @return 0 if the firmware version was found or an error code.
*/
int host_fw_determine_version (const struct flash *flash,
const struct pfm_firmware_versions *allowed, const struct pfm_firmware_version **version)
{
return host_fw_determine_offset_version (flash, 0, allowed, version);
}
/**
* Determine the version of the host firmware image stored on flash. In order for the version to be
* recognized, it must be present in the list of firmware versions from the PFM.
*
* All version ID addresses specified in the PFM will be offset by a fixed amount.
*
* @param flash The flash device that contains the host firmware.
* @param offset The offset to apply to version addresses.
* @param allowed The list of allowed versions to use when inspecting the flash.
* @param version A pointer that will be updated to reference the version information for the
* matched version. This will be a pointer to the entry within the versions list.
*
* @return 0 if the firmware version was found or an error code.
*/
int host_fw_determine_offset_version (const struct flash *flash, uint32_t offset,
const struct pfm_firmware_versions *allowed, const struct pfm_firmware_version **version)
{
char *fw_version;
size_t version_len;
size_t current_len;
int status;
int i;
int prev = -1;
if ((flash == NULL) || (allowed == NULL) || (version == NULL)) {
return HOST_FW_UTIL_INVALID_ARGUMENT;
}
if ((allowed->count == 0) || (allowed->versions == NULL)) {
return HOST_FW_UTIL_UNSUPPORTED_VERSION;
}
fw_version = platform_malloc (host_fw_find_longest_version_id (allowed));
if (fw_version == NULL) {
return HOST_FW_UTIL_NO_MEMORY;
}
*version = NULL;
i = allowed->count - 1;
while (!(*version) && (i >= 0)) {
current_len = strlen (allowed->versions[i].fw_version_id);
if ((prev < 0) ||
((allowed->versions[i].version_addr) != (allowed->versions[prev].version_addr))) {
/* We need to read a fresh version ID from the flash. */
version_len = current_len;
status = flash->read (flash, allowed->versions[i].version_addr + offset,
(uint8_t*) fw_version, version_len);
if (status != 0) {
goto exit;
}
if (strncmp (allowed->versions[i].fw_version_id, fw_version, version_len) == 0) {
*version = &allowed->versions[i];
status = 0;
}
}
else {
int extra = current_len - version_len;
if (extra < 0) {
/* Our current ID is shorter than the cached one, so only compare against the length
* of the current ID. */
if (strncmp (allowed->versions[i].fw_version_id, fw_version,
version_len + extra) == 0) {
*version = &allowed->versions[i];
status = 0;
}
}
else {
/* The current ID is at least as long at the cached one. Check the parts we
* already have to see if it matches at all. */
if (strncmp (allowed->versions[i].fw_version_id, fw_version, version_len) == 0) {
if (extra != 0) {
/* Get more data to finish the version check, but don't read the data we
* already have. */
status = flash->read (flash,
allowed->versions[i].version_addr + offset + version_len,
(uint8_t*) (fw_version + version_len), extra);
if (status != 0) {
goto exit;
}
if (strncmp (allowed->versions[i].fw_version_id + version_len,
fw_version + version_len, extra) == 0) {
*version = &allowed->versions[i];
status = 0;
}
version_len = current_len;
}
else {
*version = &allowed->versions[i];
status = 0;
}
}
}
}
prev = i;
i--;
}
if (*version == NULL) {
status = HOST_FW_UTIL_UNSUPPORTED_VERSION;
}
exit:
platform_free (fw_version);
return status;
}
/**
* Find the next flash region based on the starting address of the region.
*
* @param last_addr The flash address to start looking for the next region.
* @param regions The list of defined flash regions.
* @param count The number of regions defined in the list.
*
* @return The region description for the next defined flash region or null if there are no more
* defined regions.
*/
static const struct flash_region* host_fw_find_next_region (uint32_t last_addr,
const struct flash_region *regions, size_t count)
{
const struct flash_region *next = NULL;
size_t i;
for (i = 0; i < count; i++) {
if (regions[i].start_addr >= last_addr) {
if (regions[i].start_addr == last_addr) {
return ®ions[i];
}
if (!next || (regions[i].start_addr < next->start_addr)) {
next = ®ions[i];
}
}
}
return next;
}
/**
* Determine if two lists of regions are different. Ordering of the regions in the list doesn't
* matter.
*
* @param region1 The first list of defined regions.
* @param count1 The number of regions in the first list.
* @param region2 The second list of defined regions.
* @param count2 The number of regions in the second list.
*
* @return true if the region lists are different or false if they are the same.
*/
static bool host_fw_are_regions_different (const struct flash_region *region1, size_t count1,
const struct flash_region *region2, size_t count2)
{
uint32_t last_addr;
const struct flash_region *pos1;
const struct flash_region *pos2;
if (count1 != count2) {
return true;
}
last_addr = 0;
pos1 = host_fw_find_next_region (last_addr, region1, count1);
while (pos1) {
pos2 = host_fw_find_next_region (last_addr, region2, count2);
if (!pos2) {
return true;
}
if (pos1->start_addr != pos2->start_addr) {
return true;
}
if (pos1->length != pos2->length) {
return true;
}
last_addr = pos1->start_addr + pos1->length;
pos1 = host_fw_find_next_region (last_addr, region1, count1);
}
return false;
}
/**
* Determine if the lists of signed images are different.
*
* @param img_list1 The first list of signed images.
* @param img_list2 The second list of signed images.
*
* @return true if the images lists are different or false if they are the identical.
*/
bool host_fw_are_images_different (const struct pfm_image_list *img_list1,
const struct pfm_image_list *img_list2)
{
size_t i;
if ((img_list1 == NULL) || (img_list2 == NULL)) {
if ((img_list1 == NULL) && (img_list2 == NULL)) {
return false;
}
return true;
}
if (img_list1->count != img_list2->count) {
return true;
}
if ((img_list1->images_sig && img_list2->images_hash) ||
(img_list1->images_hash && img_list2->images_sig)) {
return true;
}
for (i = 0; i < img_list1->count; i++) {
if (img_list1->images_sig) {
if (!rsa_same_public_key (&img_list1->images_sig[i].key,
&img_list2->images_sig[i].key)) {
return true;
}
if ((img_list1->images_sig[i].sig_length != img_list2->images_sig[i].sig_length) ||
(img_list1->images_sig[i].always_validate !=
img_list2->images_sig[i].always_validate)) {
return true;
}
if (buffer_compare (img_list1->images_sig[i].signature,
img_list2->images_sig[i].signature, img_list1->images_sig->sig_length) != 0) {
return true;
}
if (host_fw_are_regions_different (img_list1->images_sig[i].regions,
img_list1->images_sig[i].count, img_list2->images_sig[i].regions,
img_list2->images_sig[i].count)) {
return true;
}
}
else {
if ((img_list1->images_hash[i].hash_length != img_list2->images_hash[i].hash_length) ||
(img_list1->images_hash[i].hash_type != img_list2->images_hash[i].hash_type) ||
(img_list1->images_hash[i].always_validate !=
img_list2->images_hash[i].always_validate)) {
return true;
}
if (buffer_compare (img_list1->images_hash[i].hash, img_list2->images_hash[i].hash,
img_list1->images_hash->hash_length) != 0) {
return true;
}
if (host_fw_are_regions_different (img_list1->images_hash[i].regions,
img_list1->images_hash[i].count, img_list2->images_hash[i].regions,
img_list2->images_hash[i].count)) {
return true;
}
}
}
return false;
}
/**
* Verify that images on the flash are valid. All image addresses specified in the PFM will be
* offset by a fixed amount.
*
* @param flash The flash that contains the images to validate.
* @param img_list The list of images to validate.
* @param validate_all Override the image validation flag and validate all images in the list.
* @param offset The offset to apply to image addresses.
* @param hash The hashing engine to use for validation.
* @param rsa The RSA engine to use for signature checking. This is only necessary if signature
* verification of images is supported. If only hash verification is needed, this can be null.
*
* @return 0 if all images that should be validated are good or an error code.
*/
static int host_fw_verify_images_on_flash (const struct flash *flash,
const struct pfm_image_list *img_list, bool validate_all, uint32_t offset,
const struct hash_engine *hash, const struct rsa_engine *rsa)
{
size_t i;
int status = 0;
for (i = 0; i < img_list->count; i++) {
if (img_list->images_sig) {
if (validate_all || img_list->images_sig[i].always_validate) {
status = flash_verify_noncontiguous_contents_at_offset (flash, offset,
img_list->images_sig[i].regions, img_list->images_sig[i].count, hash,
HASH_TYPE_SHA256, rsa, img_list->images_sig[i].signature,
img_list->images_sig[i].sig_length, &img_list->images_sig[i].key, NULL, 0);
if (status != 0) {
return status;
}
}
}
else if (validate_all || img_list->images_hash[i].always_validate) {
uint8_t img_hash[SHA512_HASH_LENGTH];
status = flash_hash_noncontiguous_contents_at_offset (flash, offset,
img_list->images_hash[i].regions, img_list->images_hash[i].count, hash,
img_list->images_hash[i].hash_type, img_hash, sizeof (img_hash));
if (status != 0) {
return status;
}
if (buffer_compare (img_list->images_hash[i].hash, img_hash,
img_list->images_hash[i].hash_length) != 0) {
return HOST_FW_UTIL_BAD_IMAGE_HASH;
}
}
}
return status;
}
/**
* Verify that images on the flash are valid. Only images flagged for validation will be checked.
*
* @param flash The flash that contains the images to validate.
* @param img_list The list of images to validate.
* @param hash The hashing engine to use for validation.
* @param rsa The RSA engine to use for signature checking. This is only necessary if signature
* verification of images is supported. If only hash verification is needed, this can be null.
*
* @return 0 if all images that should be validated are good or an error code.
*/
int host_fw_verify_images (const struct flash *flash, const struct pfm_image_list *img_list,
const struct hash_engine *hash, const struct rsa_engine *rsa)
{
return host_fw_verify_offset_images_multiple_fw (flash, img_list, 1, 0, hash, rsa);
}
/**
* Verify that images on the flash are valid. Only images flagged for validation will be checked.
*
* All image addresses specified in the PFM will be offset by a fixed amount.
*
* @param flash The flash that contains the images to validate.
* @param img_list The list of images to validate.
* @param offset The offset to apply to image addresses.
* @param hash The hashing engine to use for validation.
* @param rsa The RSA engine to use for signature checking. This is only necessary if signature
* verification of images is supported. If only hash verification is needed, this can be null.
*
* @return 0 if all images that should be validated are good or an error code.
*/
int host_fw_verify_offset_images (const struct flash *flash, const struct pfm_image_list *img_list,
uint32_t offset, const struct hash_engine *hash, const struct rsa_engine *rsa)
{
return host_fw_verify_offset_images_multiple_fw (flash, img_list, 1, offset, hash, rsa);
}
/**
* Verify that images from multiple different firmware components on the flash are valid. Only
* images flagged for validation will be checked.
*
* @param flash The flash that contains the images to validate.
* @param img_list An array of firmware images that should be validated.
* @param fw_count The number of firmware components in the list.
* @param hash The hashing engine to use for validation.
* @param rsa The RSA engine to use for signature checking. This is only necessary if signature
* verification of images is supported. If only hash verification is needed, this can be null.
*
* @return 0 if all images that should be validated are good or an error code.
*/
int host_fw_verify_images_multiple_fw (const struct flash *flash,
const struct pfm_image_list *img_list, size_t fw_count, const struct hash_engine *hash,
const struct rsa_engine *rsa)
{
return host_fw_verify_offset_images_multiple_fw (flash, img_list, fw_count, 0, hash, rsa);
}
/**
* Verify that images from multiple different firmware components on the flash are valid. Only
* images flagged for validation will be checked.
*
* All image addresses specified in the PFM will be offset by a fixed amount.
*
* @param flash The flash that contains the images to validate.
* @param img_list An array of firmware images that should be validated.
* @param fw_count The number of firmware components in the list.
* @param offset The offset to apply to image addresses.
* @param hash The hashing engine to use for validation.
* @param rsa The RSA engine to use for signature checking. This is only necessary if signature
* verification of images is supported. If only hash verification is needed, this can be null.
*
* @return 0 if all images that should be validated are good or an error code.
*/
int host_fw_verify_offset_images_multiple_fw (const struct flash *flash,
const struct pfm_image_list *img_list, size_t fw_count, uint32_t offset,
const struct hash_engine *hash, const struct rsa_engine *rsa)
{
size_t i;
int status;
if ((flash == NULL) || (img_list == NULL) || (hash == NULL)) {
return HOST_FW_UTIL_INVALID_ARGUMENT;
}
for (i = 0; i < fw_count; i++) {
status = host_fw_verify_images_on_flash (flash, &img_list[i], false, offset, hash, rsa);
if (status != 0) {
return status;
}
}
return 0;
}
/**
* Find the next flash region defined to be part of a firmware image.
*
* @param last_addr The flash address to start looking for the next region.
* @param img_list The list of firmware images in flash.
* @param fw_count The number of image instances.
*
* @return The region description for the next defined image region or null if there are no more
* defined regions.
*/
static const struct flash_region* host_fw_find_next_img_region (uint32_t last_addr,
const struct pfm_image_list *img_list, size_t fw_count)
{
const struct flash_region *next = NULL;
const struct flash_region *img_next;
size_t i;
size_t j;
for (i = 0; i < fw_count; i++) {
for (j = 0; j < img_list[i].count; j++) {
if (img_list[i].images_sig) {
img_next = host_fw_find_next_region (last_addr, img_list[i].images_sig[j].regions,
img_list[i].images_sig[j].count);
}
else {
img_next = host_fw_find_next_region (last_addr, img_list[i].images_hash[j].regions,
img_list[i].images_hash[j].count);
}
if (img_next) {
if (img_next->start_addr == last_addr) {
return img_next;
}
else if (!next || (img_next->start_addr < next->start_addr)) {
next = img_next;
}
}
}
}
return next;
}
/**
* Find the next read/write region defined in the flash.
*
* @param last_addr The flash address to start looking for the next region.
* @param writable The list of read/write regions in flash.
* @param fw_count The number of read/write region instances.
*
* @return The region description for the next defined read/write region or null if there are no
* more defined regions.
*/
static const struct flash_region* host_fw_find_next_rw_region (uint32_t last_addr,
const struct pfm_read_write_regions *writable, size_t fw_count)
{
const struct flash_region *next = NULL;
const struct flash_region *rw_next;
size_t i;
for (i = 0; i < fw_count; i++) {
rw_next = host_fw_find_next_region (last_addr, writable[i].regions, writable[i].count);
if (rw_next) {
if (rw_next->start_addr == last_addr) {
return rw_next;
}
else if (!next || (rw_next->start_addr < next->start_addr)) {
next = rw_next;
}
}
}
return next;
}
/**
* Find the next used region of flash.
*
* @param last_addr The flash address to start looking for the next region.
* @param img_list The list of images in the flash.
* @param writable The list of read/write regions in the flash.
* @param fw_count The number of different firmware instances.
*
* @return The region description for the next used region or null if there are no more used
* regions.
*/
static const struct flash_region* host_fw_find_next_flash_region (uint32_t last_addr,
const struct pfm_image_list *img_list, const struct pfm_read_write_regions *writable,
size_t fw_count)
{
const struct flash_region *next;
const struct flash_region *rw_next;
next = host_fw_find_next_img_region (last_addr, img_list, fw_count);
if (next && (next->start_addr == last_addr)) {
return next;
}
rw_next = host_fw_find_next_rw_region (last_addr, writable, fw_count);
if (rw_next) {
if (!next || (rw_next->start_addr == last_addr)) {
next = rw_next;
}
else if (rw_next->start_addr < next->start_addr) {
next = rw_next;
}
}
return next;
}
/**
* Verify that the entire flash contents are good. All images will be verified and unused regions
* of read-only flash will be verified to be empty.
*
* @param flash The flash that should be validated.
* @param img_list The list of images contained in the flash.
* @param writable The list of writable regions of flash.
* @param unused_byte The byte value to check for in unused flash regions.
* @param hash The hashing engine to use for validation.
* @param rsa The RSA engine to use for signature checking. This is only necessary if signature
* verification of images is supported. If only hash verification is needed, this can be null.
*
* @return 0 if the flash contents are good or an error code.
*/
int host_fw_full_flash_verification (const struct flash *flash,
const struct pfm_image_list *img_list, const struct pfm_read_write_regions *writable,
uint8_t unused_byte, const struct hash_engine *hash, const struct rsa_engine *rsa)
{
return host_fw_full_flash_verification_multiple_fw (flash, img_list, writable, 1, unused_byte,
hash, rsa);
}
/**
* Verify that the entire flash contents are good. All images will be verified and unused regions
* of read-only flash will be verified to be empty.
*
* The flash contains multiple, independent firmware components.
*
* @param flash The flash that should be validated.
* @param img_list The list of images contained in the flash.
* @param writable The list of writable regions of flash.
* @param img_list An array of firmware images that should be validated.
* @param writable An array of writable regions for each firmware component.
* @param fw_count The number of firmware components in the list. Both arrays of firmware
* information must be the same length.
* @param unused_byte The byte value to check for in unused flash regions.
* @param hash The hashing engine to use for validation.
* @param rsa The RSA engine to use for signature checking. This is only necessary if signature
* verification of images is supported. If only hash verification is needed, this can be null.
*
* @return 0 if the flash contents are good or an error code.
*/
int host_fw_full_flash_verification_multiple_fw (const struct flash *flash,
const struct pfm_image_list *img_list, const struct pfm_read_write_regions *writable,
size_t fw_count, uint8_t unused_byte, const struct hash_engine *hash,
const struct rsa_engine *rsa)
{
const struct flash_region *pos;
uint32_t flash_size;
uint32_t last_addr;
int status;
size_t i;
if ((flash == NULL) || (img_list == NULL) || (writable == NULL) || (hash == NULL)) {
return HOST_FW_UTIL_INVALID_ARGUMENT;
}
status = flash->get_device_size (flash, &flash_size);
if (status != 0) {
return status;
}
for (i = 0; i < fw_count; i++) {
status = host_fw_verify_images_on_flash (flash, &img_list[i], true, 0, hash, rsa);
if (status != 0) {
return status;
}
}
last_addr = 0;
pos = host_fw_find_next_flash_region (last_addr, img_list, writable, fw_count);
while (pos) {
status = flash_value_check (flash, last_addr, pos->start_addr - last_addr, unused_byte);
if (status != 0) {
return status;
}
last_addr = pos->start_addr + pos->length;
pos = host_fw_find_next_flash_region (last_addr, img_list, writable, fw_count);
}
return flash_value_check (flash, last_addr, flash_size - last_addr, unused_byte);
}
/**
* Determine if the defined regions for read/write data are different between different PFM entries.
*
* @param rw1 The first list of read/write regions.
* @param rw2 The second list of read/write regions.
*
* @return true if the defined regions are different or false if they are the same.
*/
bool host_fw_are_read_write_regions_different (const struct pfm_read_write_regions *rw1,
const struct pfm_read_write_regions *rw2)
{
if ((rw1 == NULL) || (rw2 == NULL)) {
if ((rw1 == NULL) && (rw2 == NULL)) {
return false;
}
return true;
}
return host_fw_are_regions_different (rw1->regions, rw1->count, rw2->regions, rw2->count);
}
/**
* Migrate the read/write data from one flash device to another. The migration will only happen if
* the read/write regions defined for the two flash devices are exactly the same. Any change in
* defined read/write regions will cause the migration to fail. It is possible to bypass this error
* checking and force the migration, if that behavior is necessary.
*
* The read/write regions of the destination flash are always erased, even if the migration can't
* happen. This ensures blank data on the destination read/write regions instead of allowing
* data previously in that location to persist.
*
* @param dest The flash device that will receive the read/write data.
* @param dest_writable The read/write regions defined on the destination flash.
* @param src The flash device that contains the read/write data to migrate.
* @param src_writable The read/write regions that should be migrated. This can be null to force
* the migration with no compatibility checking.
*
* @return 0 if the data migration was successful or an error code. If the data regions are not
* compatible for migration, one of the following errors will be returned:
* - HOST_FW_UTIL_DIFF_REGION_COUNT
* - HOST_FW_UTIL_DIFF_REGION_ADDR
* - HOST_FW_UTIL_DIFF_REGION_SIZE
*/
int host_fw_migrate_read_write_data (const struct flash *dest,
const struct pfm_read_write_regions *dest_writable, const struct flash *src,
const struct pfm_read_write_regions *src_writable)
{
uint32_t last_addr;
const struct flash_region *dest_pos;
const struct flash_region *src_pos;
int status;
int migrate_fail = 0;
if ((dest == NULL) || (dest_writable == NULL) || (src == NULL)) {
return HOST_FW_UTIL_INVALID_ARGUMENT;
}
if (src_writable && (src_writable->count != dest_writable->count)) {
migrate_fail = HOST_FW_UTIL_DIFF_REGION_COUNT;
}
last_addr = 0;
dest_pos = host_fw_find_next_rw_region (last_addr, dest_writable, 1);
while (dest_pos) {
status = flash_erase_region_and_verify (dest, dest_pos->start_addr, dest_pos->length);
if (status != 0) {
return status;
}
if (src_writable && !migrate_fail) {
src_pos = host_fw_find_next_rw_region (last_addr, src_writable, 1);
if (src_pos) {
if (dest_pos->start_addr != src_pos->start_addr) {
migrate_fail = HOST_FW_UTIL_DIFF_REGION_ADDR;
}
else if (dest_pos->length != src_pos->length) {
migrate_fail = HOST_FW_UTIL_DIFF_REGION_SIZE;
}
}
}
last_addr = dest_pos->start_addr + dest_pos->length;
dest_pos = host_fw_find_next_rw_region (last_addr, dest_writable, 1);
}
if (migrate_fail) {
return migrate_fail;
}
last_addr = 0;
dest_pos = host_fw_find_next_rw_region (last_addr, dest_writable, 1);
while (dest_pos) {
status = flash_copy_ext_to_blank_and_verify (dest, dest_pos->start_addr, src,
dest_pos->start_addr, dest_pos->length);
if (status != 0) {
return status;
}
last_addr = dest_pos->start_addr + dest_pos->length;
dest_pos = host_fw_find_next_rw_region (last_addr, dest_writable, 1);
}
return 0;
}
/**
* Migrate the read/write data from one flash device to another. The migration will only happen if
* the read/write regions defined for the two flash devices are exactly the same. Any change in
* defined read/write regions will cause the migration to fail. It is possible to bypass this error
* checking and force the migration, if that behavior is necessary.
*
* The flash contains multiple firmware components with defined read/write regions. Comparison for
* migration compatiblity will be done for each individual firmware component.
*
* The read/write regions of the destination flash are always erased, even if the migration can't
* happen. This ensures blank data on the destination read/write regions instead of allowing
* data previously in that location to persist.
*
* @param dest The flash device that will receive the read/write data.
* @param dest_writable The read/write regions defined on the destination flash.
* @param dest_count The number of firmware components in the destination list.
* @param src The flash device that contains the read/write data to migrate.
* @param src_writable The read/write regions that should be migrated. This can be null to force
* the migration with no compatibility checking.
* @param src_count The number of firmware components in the source list.
*
* @return 0 if the data migration was successful or an error code. If the data regions are not
* compatible for migration, one of the following errors will be returned:
* - HOST_FW_UTIL_DIFF_REGION_COUNT
* - HOST_FW_UTIL_DIFF_REGION_ADDR
* - HOST_FW_UTIL_DIFF_REGION_SIZE
* - HOST_FW_UTIL_DIFF_FW_COUNT
*/
int host_fw_migrate_read_write_data_multiple_fw (const struct flash *dest,
const struct pfm_read_write_regions *dest_writable, size_t dest_count, const struct flash *src,
const struct pfm_read_write_regions *src_writable, size_t src_count)
{
size_t i;
int status;
int migrate_fail = 0;
if ((dest == NULL) || (dest_writable == NULL) || (src == NULL)) {
return HOST_FW_UTIL_INVALID_ARGUMENT;
}
if (src_writable && (dest_count != src_count)) {
return HOST_FW_UTIL_DIFF_FW_COUNT;
}
for (i = 0; i < dest_count; i++) {
status = host_fw_migrate_read_write_data (dest, &dest_writable[i], src,
(src_writable) ? &src_writable[i] : NULL);
if (status != 0) {
if ((status == HOST_FW_UTIL_DIFF_REGION_COUNT) ||
(status == HOST_FW_UTIL_DIFF_REGION_ADDR) ||
(status == HOST_FW_UTIL_DIFF_REGION_SIZE)) {
migrate_fail = status;
}
else {
return status;
}
}
}
return migrate_fail;
}
/**
* Restore the firmware images in a flash device from the contents of a different device. No
* verification will be performed on the restored device.
*
* @param restore The flash device that should be restored.
* @param from The device to restore from.
* @param img_list The list of firmware images in the good flash device.
* @param writable The list of read/write regions in the good flash device.
*
* @return 0 if the bad flash was restored to a good state or an error code.
*/
int host_fw_restore_flash_device (const struct flash *restore, const struct flash *from,
const struct pfm_image_list *img_list, const struct pfm_read_write_regions *writable)
{
uint32_t flash_size;
uint32_t last_addr;
const struct flash_region *pos;
const struct flash_region *img_data;
size_t img_count;
int status;
size_t i;
size_t j;
if ((restore == NULL) || (from == NULL) || (img_list == NULL) || (writable == NULL)) {
return HOST_FW_UTIL_INVALID_ARGUMENT;
}
status = restore->get_device_size (restore, &flash_size);
if (status != 0) {
return status;
}
/* TODO: Flash operations should include a verify step. At least for program. */
/* Erase all read-only regions. */
last_addr = 0;
pos = host_fw_find_next_rw_region (last_addr, writable, 1);
while (pos) {
status = flash_erase_region (restore, last_addr, pos->start_addr - last_addr);
if (status != 0) {
return status;
}
last_addr = pos->start_addr + pos->length;
pos = host_fw_find_next_rw_region (last_addr, writable, 1);
}
status = flash_erase_region (restore, last_addr, flash_size - last_addr);
if (status != 0) {
return status;
}
/* Copy firmware images. */
for (i = 0; i < img_list->count; i++) {
if (img_list->images_sig) {
img_data = img_list->images_sig[i].regions;
img_count = img_list->images_sig[i].count;
}
else {
img_data = img_list->images_hash[i].regions;
img_count = img_list->images_hash[i].count;
}
for (j = 0; j < img_count; j++) {
status = flash_copy_ext_to_blank (restore, img_data[j].start_addr, from,
img_data[j].start_addr, img_data[j].length);
if (status != 0) {
return status;
}
}
}
return 0;
}
/**
* Restore the read/write data in a flash device. Based on the configuration of each region, the
* destination flash will either be left unchanged, completely erased, or copied from a different
* flash device.
*
* @param restore The flash device that should be restored.
* @param from The device to restore data from. If this is null, regions that are configured to be
* copied will instead remain unchanged.
* @param writable The list of read/write regions to restore.
*
* @return 0 if all regions were restored successfully or an error code.
*/
int host_fw_restore_read_write_data (const struct flash *restore, const struct flash *from,
const struct pfm_read_write_regions *writable)
{
size_t i;
int status;
if ((restore == NULL) || (writable == NULL)) {
return HOST_FW_UTIL_INVALID_ARGUMENT;
}
for (i = 0; i < writable->count; i++) {
switch (writable->properties[i].on_failure) {
case PFM_RW_ERASE:
status = flash_erase_region_and_verify (restore, writable->regions[i].start_addr,
writable->regions[i].length);
if (status != 0) {
return status;
}
break;
case PFM_RW_RESTORE:
if (from != NULL) {
status = flash_copy_ext_and_verify (restore, writable->regions[i].start_addr,
from, writable->regions[i].start_addr, writable->regions[i].length);
if (status != 0) {
return status;
}
}
break;
default:
break;
}
}
return 0;
}
/**
* Restore the read/write data in a flash device. Based on the configuration of each region, the
* destination flash will either be left unchanged, completely erased, or copied from a different
* flash device.
*
* Read/write data from multiple firmware components will be restored.
*
* @param restore The flash device that should be restored.
* @param from The device to restore data from. If this is null, regions that are configured to be
* copied will instead remain unchanged.
* @param writable An array of read/write regions to restore.
* @param fw_count The number of firmware components in the list.
*
* @return 0 if all regions were restored successfully or an error code.
*/
int host_fw_restore_read_write_data_multiple_fw (const struct flash *restore,
const struct flash *from, const struct pfm_read_write_regions *writable, size_t fw_count)
{
size_t i;
int status;
if ((restore == NULL) || (writable == NULL)) {
return HOST_FW_UTIL_INVALID_ARGUMENT;
}
for (i = 0; i < fw_count; i++) {
status = host_fw_restore_read_write_data (restore, from, &writable[i]);
if (status != 0) {
return status;
}
}
return 0;
}
/**
* Configure the SPI filter with the read/write region definitions from a PFM entry.
*
* @param filter The SPI filter to configure.
* @param writable The defined read/write regions to configure in the filter.
*
* @return 0 if the SPI filter was successfully configured or an error code.
*/
int host_fw_config_spi_filter_read_write_regions (const struct spi_filter_interface *filter,
const struct pfm_read_write_regions *writable)
{
size_t i;
int status;
if ((filter == NULL) || (writable == NULL)) {
return HOST_FW_UTIL_INVALID_ARGUMENT;
}
status = filter->clear_filter_rw_regions (filter);
if (status != 0) {
return status;
}
for (i = 0; i < writable->count; i++) {
status = filter->set_filter_rw_region (filter, i + 1, writable->regions[i].start_addr,
writable->regions[i].start_addr + writable->regions[i].length);
if (status != 0) {
return status;
}
}
return 0;
}
/**
* Configure the SPI filter with the read/write region definitions from the PFM. The read/write
* regions from multiple different firmware components will be inspected to generate the fewest
* number of contiguous regions for the filter.
*
* @param filter The SPI filter to configure.
* @param writable An array of read/write regions defined for all firmware components.
* @param fw_count The number of firmware components in the list.
*
* @return 0 if the SPI filter was successfully configured or an error code.
*/
int host_fw_config_spi_filter_read_write_regions_multiple_fw (
const struct spi_filter_interface *filter, const struct pfm_read_write_regions *writable,
size_t fw_count)
{
uint8_t region_id = 0;
uint32_t last_addr = 0;
const struct flash_region *next = NULL;
const struct flash_region *prev = NULL;
size_t total_len = 0;
int status;
if ((filter == NULL) || ((writable == NULL) && (fw_count != 0))) {
return HOST_FW_UTIL_INVALID_ARGUMENT;
}
status = filter->clear_filter_rw_regions (filter);
if (status != 0) {
return status;
}
do {
if (!prev) {
prev = next;
}
next = host_fw_find_next_rw_region (last_addr, writable, fw_count);
if (prev) {
if (next && (next->start_addr == last_addr)) {
/* The next region is contiguous with the previous one. */
total_len += next->length;
last_addr += next->length;
}
else {
/* Found the end of a R/W region. */
status = filter->set_filter_rw_region (filter, ++region_id, prev->start_addr,
prev->start_addr + total_len);
if (status != 0) {
return status;
}
prev = NULL;
if (next) {
total_len = next->length;
last_addr = next->start_addr + next->length;
}
}
}
else if (next) {
total_len = next->length;
last_addr = next->start_addr + next->length;
}
} while (prev || next);
return 0;
}