core/host_fw/host_flash_manager.c (462 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_flash_manager.h"
#include "host_fw_util.h"
#include "common/unused.h"
/**
* Get the information from a PFM entry for the image on flash.
*
* @param pfm The PFM to query for image information.
* @param flash The flash containing the image.
* @param offset An offset to apply to version addresses when matching the PFM entry.
* @param fw_id Identifier for the firmware type to query in the PFM.
* @param versions Output for the list of supported versions in the PFM.
* @param version Output for the version entry for the image on flash.
* @param fw_images Output for the list of images specified for the version.
* @param writable Output for the list of read/write regions for the version. This can be null if
* this information is not needed.
*
* @return 0 if the entry information was successfully queried or an error code.
*/
int host_flash_manager_get_image_entry (const struct pfm *pfm, const struct spi_flash *flash,
uint32_t offset, const char *fw_id, struct pfm_firmware_versions *versions,
const struct pfm_firmware_version **version, struct pfm_image_list *fw_images,
struct pfm_read_write_regions *writable)
{
int status;
status = pfm->get_supported_versions (pfm, fw_id, versions);
if (status != 0) {
return status;
}
status = host_fw_determine_offset_version (&flash->base, offset, versions, version);
if (status != 0) {
goto free_versions;
}
status = pfm->get_firmware_images (pfm, fw_id, (*version)->fw_version_id, fw_images);
if (status != 0) {
goto free_versions;
}
if (writable) {
status = pfm->get_read_write_regions (pfm, fw_id, (*version)->fw_version_id, writable);
if (status != 0) {
goto free_images;
}
}
return 0;
free_images:
pfm->free_firmware_images (pfm, fw_images);
free_versions:
pfm->free_fw_versions (pfm, versions);
return status;
}
/**
* Get the list of firmware components expected on flash and initialize containers for PFM entries.
*
* @param pfm The PFM to query for firmware information.
* @param host_fw Output for the list of host firmware.
* @param host_img Output for the container of host firmware images. Null if not necessary.
* @param host_rw Output for the container of host read/write regions. Null if not necessary.
*
* @return 0 if the operation was successful or an error code.
*/
int host_flash_manager_get_firmware_types (const struct pfm *pfm, struct pfm_firmware *host_fw,
struct host_flash_manager_images *host_img, struct host_flash_manager_rw_regions *host_rw)
{
int status;
status = pfm->get_firmware (pfm, host_fw);
if (status != 0) {
return status;
}
if (host_img) {
host_img->pfm = pfm;
host_img->count = 0;
host_img->fw_images = platform_calloc (host_fw->count, sizeof (struct pfm_image_list));
if (host_img->fw_images == NULL) {
goto free_firmware;
}
}
if (host_rw) {
host_rw->pfm = pfm;
host_rw->count = 0;
host_rw->writable = platform_calloc (host_fw->count,
sizeof (struct pfm_read_write_regions));
if (host_rw->writable == NULL) {
goto free_img;
}
}
return 0;
free_img:
if (host_img) {
platform_free (host_img->fw_images);
}
free_firmware:
pfm->free_firmware (pfm, host_fw);
return HOST_FLASH_MGR_NO_MEMORY;
}
void host_flash_manager_free_read_write_regions (struct host_flash_manager *manager,
struct host_flash_manager_rw_regions *host_rw)
{
size_t i;
UNUSED (manager);
if (host_rw && host_rw->pfm) {
if (host_rw->writable) {
for (i = 0; i < host_rw->count; i++) {
host_rw->pfm->free_read_write_regions (host_rw->pfm, &host_rw->writable[i]);
}
platform_free (host_rw->writable);
}
memset (host_rw, 0, sizeof (*host_rw));
}
}
/**
* Free a list of authenticated firmware images on flash.
*
* @param host_img The list to free.
*/
void host_flash_manager_free_images (struct host_flash_manager_images *host_img)
{
size_t i;
if (host_img->fw_images) {
for (i = 0; i < host_img->count; i++) {
host_img->pfm->free_firmware_images (host_img->pfm, &host_img->fw_images[i]);
}
platform_free (host_img->fw_images);
}
}
/**
* Validate the image on a flash device.
*
* @param pfm The PFM to use for validation.
* @param hash The hash to use for image validation.
* @param rsa The RSA engine to use for signature verification.
* @param full_validation Flag to control level of flash validation.
* @param flash The flash device to validate.
* @param host_rw Output for the read/write regions of the validated flash. This will only be
* valid if the flash is successfully validated. This can be null if full_validation is false.
*
* @return 0 if the validation was successful or an error code.
*/
int host_flash_manager_validate_flash (const struct pfm *pfm, const struct hash_engine *hash,
const struct rsa_engine *rsa, bool full_validation, const struct spi_flash *flash,
struct host_flash_manager_rw_regions *host_rw)
{
return host_flash_manager_validate_offset_flash (pfm, hash, rsa, full_validation, flash, 0,
host_rw);
}
/**
* Validate the image on a flash device.
*
* @param pfm The PFM to use for validation.
* @param hash The hash to use for image validation.
* @param rsa The RSA engine to use for signature verification.
* @param full_validation Flag to control level of flash validation.
* @param flash The flash device to validate.
* @param offset An offset in flash for images that will be validated. Ignored if full_validation
* is set.
* @param host_rw Output for the read/write regions of the validated flash. This will only be
* valid if the flash is successfully validated. This can be null if full_validation is false.
*
* @return 0 if the validation was successful or an error code.
*/
int host_flash_manager_validate_offset_flash (const struct pfm *pfm, const struct hash_engine *hash,
const struct rsa_engine *rsa, bool full_validation, const struct spi_flash *flash,
uint32_t offset, struct host_flash_manager_rw_regions *host_rw)
{
struct pfm_firmware host_fw;
struct pfm_firmware_versions versions;
const struct pfm_firmware_version *version;
struct host_flash_manager_images host_img;
size_t i;
int status;
status = host_flash_manager_get_firmware_types (pfm, &host_fw, &host_img, host_rw);
if (status != 0) {
return status;
}
for (i = 0; i < host_fw.count; i++) {
status = host_flash_manager_get_image_entry (pfm, flash, offset, host_fw.ids[i], &versions,
&version, &host_img.fw_images[i], (host_rw) ? &host_rw->writable[i] : NULL);
if (status != 0) {
goto free_host;
}
host_img.count++;
if (host_rw) {
host_rw->count++;
}
pfm->free_fw_versions (pfm, &versions);
}
if (full_validation) {
status = host_fw_full_flash_verification_multiple_fw (&flash->base, host_img.fw_images,
host_rw->writable, host_fw.count, version->blank_byte, hash, rsa);
}
else {
status = host_fw_verify_offset_images_multiple_fw (&flash->base, host_img.fw_images,
host_img.count, offset, hash, rsa);
}
free_host:
if ((status != 0) && host_rw) {
host_flash_manager_free_read_write_regions (NULL, host_rw);
}
host_flash_manager_free_images (&host_img);
pfm->free_firmware (pfm, &host_fw);
return status;
}
/**
* Validate a PFM against the image on flash using a different PFM that is known to validate that
* image.
*
* @param pfm The PFM to use for validation.
* @param good_pfm The PFM that is known to be good for the flash image.
* @param hash The hash to use for image validation.
* @param rsa The RSA engine to use for signature verification.
* @param flash The flash device to validate.
* @param host_rw Output for the read/write regions of the validated flash. This will only be
* valid if the flash is successfully validated. This can be null.
*
* @return 0 if the validation was successful or an error code.
*/
int host_flash_manager_validate_pfm (const struct pfm *pfm, const struct pfm *good_pfm,
const struct hash_engine *hash, const struct rsa_engine *rsa, const struct spi_flash *flash,
struct host_flash_manager_rw_regions *host_rw)
{
struct pfm_firmware host_fw;
struct pfm_firmware_versions versions;
const struct pfm_firmware_version *version;
struct host_flash_manager_images host_img;
struct pfm_image_list fw_images_good;
size_t i;
int status;
int match_status = 0;
status = host_flash_manager_get_firmware_types (pfm, &host_fw, &host_img, host_rw);
if (status != 0) {
return status;
}
for (i = 0; i < host_fw.count; i++) {
status = host_flash_manager_get_image_entry (pfm, flash, 0, host_fw.ids[i], &versions,
&version, &host_img.fw_images[i], (host_rw) ? &host_rw->writable[i] : NULL);
if (status != 0) {
goto free_host;
}
host_img.count++;
if (host_rw) {
host_rw->count++;
}
if (match_status == 0) {
match_status = good_pfm->get_firmware_images (good_pfm, host_fw.ids[i],
version->fw_version_id, &fw_images_good);
if (match_status == 0) {
match_status = host_fw_are_images_different (&host_img.fw_images[i],
&fw_images_good);
good_pfm->free_firmware_images (good_pfm, &fw_images_good);
}
}
pfm->free_fw_versions (pfm, &versions);
}
if (match_status != 0) {
status = host_fw_verify_images_multiple_fw (&flash->base, host_img.fw_images,
host_img.count, hash, rsa);
}
free_host:
if ((status != 0) && host_rw) {
host_flash_manager_free_read_write_regions (NULL, host_rw);
}
host_flash_manager_free_images (&host_img);
pfm->free_firmware (pfm, &host_fw);
return status;
}
/**
* Find the entry in the PFM for the firmware version stored on flash.
*
* @param flash The flash to inspect.
* @param pfm The PFM to check the flash contents against.
* @param fw_id Identifier for the firmware type to query in the PFM.
* @param versions Output for the list of supported versions in the PFM.
* @param version Output for the version entry that matches the flash contents.
*
* @return 0 if a match was found in the PFM or an error code.
*/
static int host_flash_manager_find_flash_version (const struct spi_flash *flash,
const struct pfm *pfm, const char *fw_id, struct pfm_firmware_versions *versions,
const struct pfm_firmware_version **version)
{
int status;
status = pfm->get_supported_versions (pfm, fw_id, versions);
if (status != 0) {
return status;
}
status = host_fw_determine_version (&flash->base, versions, version);
if (status != 0) {
pfm->free_fw_versions (pfm, versions);
}
return status;
}
/**
* Determine the the read/write regions for the host firmware on flash.
*
* @param flash The flash containing the firmware.
* @param pfm The PFM to use to determine R/W regions.
* @param host_rw Output for the firmware read/write regions.
*
* @return 0 if the regions were successfully determined or an error code.
*/
int host_flash_manager_get_flash_read_write_regions (const struct spi_flash *flash,
const struct pfm *pfm, struct host_flash_manager_rw_regions *host_rw)
{
struct pfm_firmware host_fw;
struct pfm_firmware_versions versions;
const struct pfm_firmware_version *version;
size_t i;
int status;
status = host_flash_manager_get_firmware_types (pfm, &host_fw, NULL, host_rw);
if (status != 0) {
return status;
}
for (i = 0; i < host_fw.count; i++, host_rw->count++) {
status = host_flash_manager_find_flash_version (flash, pfm, host_fw.ids[i], &versions,
&version);
if (status != 0) {
goto free_rw;
}
status = pfm->get_read_write_regions (pfm, host_fw.ids[i], version->fw_version_id,
&host_rw->writable[i]);
pfm->free_fw_versions (pfm, &versions);
if (status != 0) {
goto free_rw;
}
}
free_rw:
if (status != 0) {
host_flash_manager_free_read_write_regions (NULL, host_rw);
}
pfm->free_firmware (pfm, &host_fw);
return status;
}
/**
* Ensure both flash devices are operating in the same address mode.
*
* @param cs0 The flash device connected to CS0.
* @param cs1 The flash device connected to CS1. Null if there is only a single flash device.
* @param mode Output indicating the current address mode of both devices.
*
* @return 0 if the address mode was configured successfully or an error code.
*/
static int host_flash_manager_flash_address_mode (const struct spi_flash *cs0,
const struct spi_flash *cs1, spi_filter_address_mode *mode)
{
int addr_4byte;
int status;
addr_4byte = spi_flash_is_4byte_address_mode (cs0);
if (cs1) {
if (addr_4byte != spi_flash_is_4byte_address_mode (cs1)) {
status = spi_flash_enable_4byte_address_mode (cs1, addr_4byte);
if (status != 0) {
if (status == SPI_FLASH_UNSUPPORTED_ADDR_MODE) {
status = HOST_FLASH_MGR_MISMATCH_ADDR_MODE;
}
return status;
}
}
}
*mode = (addr_4byte) ? SPI_FILTER_ADDRESS_MODE_4 : SPI_FILTER_ADDRESS_MODE_3;
return 0;
}
/**
* Detect the address mode properties of the flash devices.
*
* @param cs0 The flash device connected to CS0.
* @param cs1 The flash device connected to CS1. Null if there is only a single flash device.
* @param wen_required Output indicating if write enable is required to switch address modes.
* @param fixed_addr Output indicating if the device address mode is fixed.
* @param mode Output indicating the current address mode of the device.
* @param reset_mode Output indicating the default address mode on device reset.
*
* @return 0 if the address mode properties were successfully detected or an error code.
*/
static int host_flash_manager_detect_flash_address_mode_properties (const struct spi_flash *cs0,
const struct spi_flash *cs1, bool *wen_required, bool *fixed_addr,
spi_filter_address_mode *mode, spi_filter_address_mode *reset_mode)
{
int req_write_en[2];
int reset_addr[2];
int status;
req_write_en[0] = spi_flash_address_mode_requires_write_enable (cs0);
if (cs1) {
req_write_en[1] = spi_flash_address_mode_requires_write_enable (cs1);
if (req_write_en[0] != req_write_en[1]) {
return HOST_FLASH_MGR_MISMATCH_ADDR_MODE;
}
}
if (req_write_en[0] == SPI_FLASH_ADDR_MODE_FIXED) {
*wen_required = false;
*fixed_addr = true;
}
else {
*wen_required = req_write_en[0];
*fixed_addr = false;
}
status = host_flash_manager_flash_address_mode (cs0, cs1, mode);
if (status != 0) {
return status;
}
reset_addr[0] = spi_flash_is_4byte_address_mode_on_reset (cs0);
if ((reset_addr[0] != 0) && (reset_addr[0] != 1)) {
return reset_addr[0];
}
if (cs1) {
reset_addr[1] = spi_flash_is_4byte_address_mode_on_reset (cs1);
if ((reset_addr[1] != 0) && (reset_addr[1] != 1)) {
return reset_addr[1];
}
if (reset_addr[0] != reset_addr[1]) {
return HOST_FLASH_MGR_MISMATCH_ADDR_MODE;
}
}
*reset_mode = (reset_addr[0] == 1) ? SPI_FILTER_ADDRESS_MODE_4 : SPI_FILTER_ADDRESS_MODE_3;
return 0;
}
/**
* Detect the flash device properties and configure the SPI filter to match. If there are two flash
* devices, they must match exactly or an error will be generated.
*
* @param cs0 The flash device connected to CS0.
* @param cs1 The flash device connected to CS1. Null if there is only a single flash device.
* @param filter The SPI filter to configure.
* @param mfg_handler Handler for configuring flash manufacturer details into the filter.
*
* @return 0 if the flash is supported and the filter was configured successfully or an error code.
*/
int host_flash_manager_config_spi_filter_flash_type (const struct spi_flash *cs0,
const struct spi_flash *cs1, const struct spi_filter_interface *filter,
const struct flash_mfg_filter_handler *mfg_handler)
{
uint8_t vendor[2];
uint16_t device[2];
uint32_t bytes[2];
bool req_write_en;
bool fixed;
spi_filter_address_mode mode;
spi_filter_address_mode reset_mode;
int status;
/* Validate and configure the type of devices being used. */
status = spi_flash_get_device_id (cs0, &vendor[0], &device[0]);
if (status != 0) {
return status;
}
if (cs1) {
status = spi_flash_get_device_id (cs1, &vendor[1], &device[1]);
if (status != 0) {
return status;
}
if (vendor[0] != vendor[1]) {
return HOST_FLASH_MGR_MISMATCH_VENDOR;
}
else if (device[0] != device[1]) {
return HOST_FLASH_MGR_MISMATCH_DEVICE;
}
}
status = mfg_handler->set_flash_manufacturer (mfg_handler, vendor[0], device[0]);
if (status != 0) {
return status;
}
/* Validate and configure the flash device capacity. */
spi_flash_get_device_size (cs0, &bytes[0]);
if (cs1) {
spi_flash_get_device_size (cs1, &bytes[1]);
if (bytes[0] != bytes[1]) {
return HOST_FLASH_MGR_MISMATCH_SIZES;
}
}
status = filter->set_flash_size (filter, bytes[0]);
if ((status != 0) && (status != SPI_FILTER_UNSUPPORTED_OPERATION)) {
return status;
}
/* Validate and configure the address byte mode of the devices. */
status = host_flash_manager_detect_flash_address_mode_properties (cs0, cs1, &req_write_en,
&fixed, &mode, &reset_mode);
if (status != 0) {
return status;
}
if (!fixed) {
status = filter->set_addr_byte_mode (filter, mode);
}
else {
status = filter->set_fixed_addr_byte_mode (filter, mode);
}
if (status != 0) {
return status;
}
status = filter->require_addr_byte_mode_write_enable (filter, req_write_en);
if ((status != 0) && (status != SPI_FILTER_UNSUPPORTED_OPERATION)) {
return status;
}
status = filter->set_reset_addr_byte_mode (filter, reset_mode);
if (status == SPI_FILTER_UNSUPPORTED_OPERATION) {
status = 0;
}
return status;
}
/**
* Configure a single SPI flash device and the driver to allow the RoT access to the device.
*
* @param flash The flash device to configure.
*
* @return 0 if the device was configured successfully or an error code.
*/
int host_flash_manager_configure_flash_for_rot_access (const struct spi_flash *flash)
{
uint8_t vendor;
int status;
status = spi_flash_get_device_id (flash, &vendor, NULL);
if (status != 0) {
return status;
}
if ((vendor == 0xff) || (vendor == 0x00)) {
return HOST_FLASH_MGR_INVALID_VENDOR;
}
/* We don't know what state the flash device is in at this point, so make sure there is no
* write in progress before we try to use it. */
status = spi_flash_wait_for_write (flash, 10000);
if (status != 0) {
return status;
}
status = spi_flash_clear_block_protect (flash);
if (status != 0) {
return status;
}
if (spi_flash_is_quad_spi_enabled (flash) != 1) {
status = spi_flash_enable_quad_spi (flash, true);
if (status != 0) {
return status;
}
}
/* Detect the address mode of the device so we know how to talk with it. */
status = spi_flash_detect_4byte_address_mode (flash);
if (status != 0) {
return status;
}
return 0;
}
/**
* Enable RoT access to the protected flash devices.
*
* @param control The interface for hardware controls for flash access.
* @param filter The SPI filter connected to the flash devices.
* @param cs0 Flash device connected to CS0.
* @param cs1 Flash device connected to CS1. Null if there is only a single flash device.
* @param flash_init Initialization handler for the protected flash devices. Null to skip
* initialization.
*
* @return 0 if RoT flash access has been enabled or an error code.
*/
int host_flash_manager_set_flash_for_rot_access (const struct host_control *control,
const struct spi_filter_interface *filter, const struct spi_flash *cs0,
const struct spi_flash *cs1, struct host_flash_initialization *flash_init)
{
const struct spi_flash *flash;
int i;
int status;
status = filter->enable_filter (filter, false);
if (status != 0) {
return status;
}
status = control->enable_processor_flash_access (control, false);
if (status != 0) {
return status;
}
if (flash_init) {
status = host_flash_initialization_initialize_flash (flash_init);
if (status != 0) {
return status;
}
}
flash = cs0;
for (i = 0; i < 2; i++) {
if (flash) {
status = host_flash_manager_configure_flash_for_rot_access (flash);
if (status != 0) {
return status;
}
}
flash = cs1;
}
return 0;
}
/**
* Enable host access to the protected flash devices.
*
* @param control The interface for hardware controls for flash access.
* @param filter The SPI filter connected to the flash devices.
*
* @return 0 if host flash access has been enabled or an error code.
*/
int host_flash_manager_set_flash_for_host_access (const struct host_control *control,
const struct spi_filter_interface *filter)
{
int status;
status = control->enable_processor_flash_access (control, true);
if (status != 0) {
return status;
}
return filter->enable_filter (filter, true);
}
/**
* Determine if the host has access to the protected flash devices.
*
* @param control The interface for hardware controls for flash access.
* @param filter The SPI filter connected to the flash devices.
*
* @return 0 if the host doesn't if access, 1 if it does, or an error code.
*/
int host_flash_manager_host_has_flash_access (const struct host_control *control,
const struct spi_filter_interface *filter)
{
bool enabled;
int status;
status = filter->get_filter_enabled (filter, &enabled);
if (status != 0) {
return status;
}
status = control->processor_has_flash_access (control);
if (ROT_IS_ERROR (status)) {
return status;
}
if ((status == 0) || !enabled) {
return 0;
}
return 1;
}